Merge 'master' into enhance/i18n

This commit is contained in:
Mega Yu
2026-04-16 15:30:25 +08:00
39 changed files with 353 additions and 62 deletions

View File

@@ -15,7 +15,7 @@
[frontend.components.settings :as settings]
[frontend.components.svg :as svg]
[frontend.config :as config]
[frontend.context.i18n :refer [t]]
[frontend.context.i18n :as i18n :refer [t]]
[frontend.db :as db]
[frontend.handler :as handler]
[frontend.handler.db-based.rtc-flows :as rtc-flows]
@@ -351,11 +351,25 @@
(ldb/page? page) (:block/parent page))
[:div.ls-block-breadcrumb
[:div.text-sm
(component-block/breadcrumb {}
(component-block/breadcrumb {}
(state/get-current-repo)
(:block/uuid page)
{:header? true})]])))
(rum/defc search-index-progress < rum/reactive
[]
(let [current-repo (state/get-current-repo)
{:keys [running? repo progress]} (or (state/sub :search/index-build) {})
progress' (-> (or progress 0)
(max 0)
(min 100))]
(when (and running? (= repo current-repo))
[:div.search-index-progress
[ui/loading ""]
[:span.search-index-progress__text (t :search/index-progress progress')]
[:div.search-index-progress__bar
[:div.search-index-progress__bar-fill {:style {:width (str progress' "%")}}]]])))
(rum/defc ^:large-vars/cleanup-todo header-aux < rum/reactive
[{:keys [current-repo default-home new-block-mode]}]
(let [electron-mac? (and util/mac? (util/electron?))
@@ -419,6 +433,7 @@
(rtc-indicator/downloading-detail))
(when (user-handler/logged-in?)
(rtc-indicator/uploading-detail))
(search-index-progress)
(when (and (not= (state/get-current-route) :home)
(not custom-home-page?))

View File

@@ -341,4 +341,24 @@ html.is-zoomed-native-ios {
max-width: 34ch;
}
}
.search-index-progress {
@apply flex items-center gap-2 rounded px-2 py-1 text-xs opacity-90;
-webkit-app-region: no-drag;
background-color: var(--ls-tertiary-background-color);
}
.search-index-progress__text {
@apply whitespace-nowrap;
}
.search-index-progress__bar {
@apply h-1 w-16 overflow-hidden rounded;
background-color: var(--ls-quaternary-background-color);
}
.search-index-progress__bar-fill {
@apply h-full transition-all duration-200;
background-color: var(--ls-link-text-color);
}
}

View File

@@ -1034,7 +1034,7 @@
(ui/icon "alert-triangle")]]
[:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
[:h3#modal-headline.text-lg.leading-6.font-medium
(t :page.delete/confirm-title)]]]
(t :page.delete/batch-confirm-title)]]]
[:ol.p-2.pt-4
(for [page-item pages]

View File

@@ -72,7 +72,9 @@
{:title [:h3.text-lg.leading-6.font-medium.flex.gap-2.items-center
[:span.top-1.relative
(shui/tabler-icon "alert-triangle")]
(t :page.delete/confirm-title)]
(if (or (ldb/class? page) (ldb/property? page))
(t :page.delete/permanent-confirm-title)
(t :page.delete/confirm-title))]
:content [:p.opacity-60 (str "- " (:block/title page))]
:outside-cancel? true
:cancel-label (t :ui/cancel)

View File

@@ -1597,9 +1597,10 @@
[:pre (pr-str opts)]))
(rum/defc renderer-resolver < rum/static
[key']
(when-let [[pid key] (some-> key' (string/split "."))]
(let [[renderer set-renderer!] (rum/use-state nil)]
[nskey']
(when-let [pid (some-> nskey' (namespace))]
(let [key (name nskey')
[renderer set-renderer!] (rum/use-state nil)]
(hooks/use-effect!
(fn []
@@ -1607,10 +1608,10 @@
(when-let [renderer (plugin-handler/resolve-hosted-render pid key :sidebar)]
(let [r (bean/->clj renderer)
title (:title r)]
(when-let [^js dom (and title (js/document.getElementById key'))]
(when-let [^js dom (and title (js/document.getElementById nskey'))]
(set! (. dom -textContent) title))
(set-renderer! r)))
(catch js/Error e (js/console.error "Failed to resolve renderer:" key' e))))
(catch js/Error e (js/console.error "Failed to resolve renderer:" nskey' e))))
[pid key])
(when renderer

View File

@@ -17,6 +17,7 @@
[frontend.handler.editor :as editor-handler]
[frontend.handler.route :as route-handler]
[frontend.handler.ui :as ui-handler]
[frontend.handler.plugin :as plugin-handler]
[frontend.state :as state]
[frontend.ui :as ui]
[frontend.undo-redo.debug-ui :as undo-redo-debug-ui]
@@ -432,6 +433,20 @@
:tabIndex "0"
:data-expanded sidebar-open?}]))
(rum/defc plugin-renderer-menu-items
[renderers]
(for [r renderers]
(shui/dropdown-menu-item
{:on-click #(state/sidebar-add-block!
(state/get-current-repo)
(keyword (:pid r) (:key r))
:plugin
)}
[:div.flex.items-center
{:title (str (:pid r))}
[:span.pr-1.flex.items-center (shui/tabler-icon "puzzle")]
[:strong (:title r)]])))
(rum/defcs sidebar-inner <
(rum/local false ::anim-finished?)
{:will-mount (fn [state]
@@ -446,7 +461,21 @@
[:div.cp__right-sidebar-scrollable
{:on-drag-over util/stop}
[:div.cp__right-sidebar-topbar.flex.flex-row.justify-between.items-center
[:div.cp__right-sidebar-settings.hide-scrollbar.gap-0 {:key "right-sidebar-settings"}
[:div.cp__right-sidebar-settings.hide-scrollbar.gap-1 {:key "right-sidebar-settings"}
;; sidebar renderers from plugins
(when-let [renderers (and config/lsp-enabled?
(some->> (plugin-handler/get-hosted-renderers)
(filter #(= (:type %) "sidebar"))
(seq)))]
[:div.text-sm
[:button.button.cp__right-sidebar-settings-btn
{:on-click (fn [e]
(shui/popup-show! e
(plugin-renderer-menu-items renderers)
{:as-dropdown? true
:content-props {:on-click (fn [] (shui/popup-hide!))}}))}
[:span.nu.flex.items-center.opacity-80 (shui/tabler-icon "cube-plus")]]])
[:div.text-sm
[:button.button.cp__right-sidebar-settings-btn {:on-click (fn [_e]
(state/sidebar-add-block! repo "contents" :contents))}

View File

@@ -63,6 +63,12 @@
[error]
(string/includes? (or (ex-message error) (str error)) "decrypt-aes-key"))
(defn- <build-search-index!
[repo]
(-> (state/<invoke-db-worker :thread-api/search-build-blocks-indice-in-worker repo)
(p/catch (fn [error]
(js/console.error "Search index build error:" error)))))
(defn- schedule-search-index-build!
[repo]
(when-let [timeout-id @*search-index-build-timeout]
@@ -77,9 +83,7 @@
(state/input-idle? repo :diff 5000)
(do
(reset! *search-index-build-timeout nil)
(-> (state/<invoke-db-worker :thread-api/search-build-blocks-indice-in-worker repo)
(p/catch (fn [error]
(js/console.error "Search index build error:" error)))))
(<build-search-index! repo))
:else
(schedule-search-index-build! repo)))
@@ -111,7 +115,7 @@
(p/do!
(p/delay 5000)
(p/let [repo (state/get-current-repo)
_ (state/<invoke-db-worker :thread-api/search-build-blocks-indice-in-worker repo)]
_ (<build-search-index! repo)]
(when state/lsp-enabled?
(doseq [service (state/get-all-plugin-services-with-type :search)]
(search-plugin/call-service! service "search:rebuildPagesIndice" {})

View File

@@ -36,6 +36,13 @@
(uuid? x) (str x)
:else x)) input))))
(defn- normalize-user-key-without-ns
[k]
(some-> k (name)
(string/replace "/" "$")
(string/replace " " "_")
(string/replace #"^[:_\s]+" "")))
(defn invoke-exported-api
[type & args]
(try
@@ -528,7 +535,7 @@
(defn- create-local-renderer-register
[type *providers]
(fn [pid key {subs' :subs :keys [render] :as opts}]
(when-let [key (and key (keyword key))]
(when-let [key (some-> key (normalize-user-key-without-ns) (keyword))]
(register-plugin-resources pid type
(merge opts {:key key :subs subs' :render render}))
(swap! *providers conj pid)
@@ -539,7 +546,7 @@
([type *providers many?]
(fn [key]
(when (seq @*providers)
(if key
(if-let [key (some-> key (normalize-user-key-without-ns) (keyword))]
(when-let [rs (->> @*providers
(map (fn [pid] (state/get-plugin-resource pid type key)))
(remove nil?)

View File

@@ -1,6 +1,7 @@
(ns frontend.modules.outliner.op
"Build outliner ops"
(:require [datascript.impl.entity :as de]
[frontend.db.utils :as db-utils]
[frontend.handler.user :as user-handler]))
(defn- current-user-delete-opts
@@ -30,9 +31,21 @@
(map? block-or-id)
(:block/uuid block-or-id)
(number? block-or-id)
(:block/uuid (db-utils/entity block-or-id))
:else
block-or-id))
(defn- ->property-id
[property-id]
(cond
(number? property-id)
(:db/ident (db-utils/entity property-id))
:else
property-id))
(defn save-block!
[block & {:as opts}]
(op-transact!
@@ -83,67 +96,67 @@
(defn upsert-property!
[property-id schema property-opts]
(op-transact!
[:upsert-property [property-id schema property-opts]]))
[:upsert-property [(->property-id property-id) schema property-opts]]))
(defn set-block-property!
[block-eid property-id value]
(op-transact!
[:set-block-property [(->block-id block-eid) property-id value]]))
[:set-block-property [(->block-id block-eid) (->property-id property-id) value]]))
(defn remove-block-property!
[block-eid property-id]
(op-transact!
[:remove-block-property [(->block-id block-eid) property-id]]))
[:remove-block-property [(->block-id block-eid) (->property-id property-id)]]))
(defn delete-property-value!
[block-eid property-id property-value]
(op-transact!
[:delete-property-value [(->block-id block-eid) property-id property-value]]))
[:delete-property-value [(->block-id block-eid) (->property-id property-id) property-value]]))
(defn batch-delete-property-value!
[block-eids property-id property-value]
(op-transact!
[:batch-delete-property-value [(mapv ->block-id block-eids) property-id property-value]]))
[:batch-delete-property-value [(mapv ->block-id block-eids) (->property-id property-id) property-value]]))
(defn create-property-text-block!
[block-id property-id value opts]
(op-transact!
[:create-property-text-block [(->block-id block-id) property-id value opts]]))
[:create-property-text-block [(->block-id block-id) (->property-id property-id) value opts]]))
(defn batch-set-property!
[block-ids property-id value opts]
(op-transact!
[:batch-set-property [(mapv ->block-id block-ids) property-id value opts]]))
[:batch-set-property [(mapv ->block-id block-ids) (->property-id property-id) value opts]]))
(defn batch-remove-property!
[block-ids property-id]
(op-transact!
[:batch-remove-property [(mapv ->block-id block-ids) property-id]]))
[:batch-remove-property [(mapv ->block-id block-ids) (->property-id property-id)]]))
(defn class-add-property!
[class-id property-id]
(op-transact!
[:class-add-property [(->block-id class-id) property-id]]))
[:class-add-property [(->block-id class-id) (->property-id property-id)]]))
(defn class-remove-property!
[class-id property-id]
(op-transact!
[:class-remove-property [(->block-id class-id) property-id]]))
[:class-remove-property [(->block-id class-id) (->property-id property-id)]]))
(defn upsert-closed-value!
[property-id closed-value-config]
(op-transact!
[:upsert-closed-value [property-id closed-value-config]]))
[:upsert-closed-value [(->property-id property-id) closed-value-config]]))
(defn delete-closed-value!
[property-id value-block-id]
(op-transact!
[:delete-closed-value [property-id (->block-id value-block-id)]]))
[:delete-closed-value [(->property-id property-id) (->block-id value-block-id)]]))
(defn add-existing-values-to-closed-values!
[property-id values]
(op-transact!
[:add-existing-values-to-closed-values [property-id values]]))
[:add-existing-values-to-closed-values [(->property-id property-id) values]]))
(defn toggle-reaction!
[target-uuid emoji-id user-uuid]

View File

@@ -22,6 +22,38 @@
[repo diff]
(state/input-idle? repo :diff diff))
(def-thread-api :thread-api/search-index-build-progress
[repo {:keys [status progress processed total]}]
(let [prev-state (get @state/state :search/index-build)
current-repo (state/get-current-repo)
visible-repo? (or (= repo current-repo)
(= repo (:repo prev-state)))]
(when visible-repo?
(case status
:idle
(state/set-state! :search/index-build
(assoc (or prev-state {})
:running? false
:repo repo))
:running
(state/set-state! :search/index-build
{:running? true
:repo repo
:progress (or progress 0)
:processed (or processed 0)
:total (or total 0)})
:completed
(state/set-state! :search/index-build
{:running? false
:repo repo
:progress (or progress 0)
:processed (or processed 0)
:total (or total 0)})
nil))
nil))
(defn- ask-persist-permission!
[]
(p/let [persistent? (.persist js/navigator.storage)]

View File

@@ -112,6 +112,11 @@
:search/result nil
:search/graph-filters []
:search/engines {}
:search/index-build {:running? false
:repo nil
:progress 0
:processed 0
:total 0}
;; modals
:modal/dropdowns {}

View File

@@ -73,7 +73,7 @@
Bump to force a rebuild when the index format changes."
1)
(def ^:private search-index-build-batch-size 200)
(def ^:private search-index-build-batch-size 5000)
(def ^:private search-index-build-time-budget-ms 8)
(def ^:private search-index-build-idle-diff-ms 1000)
(def ^:private search-index-build-pause-ms 300)
@@ -500,6 +500,11 @@
:repo repo
:build-id build-id}))))
(defn- report-search-index-progress!
[repo payload]
(-> (worker-state/<invoke-main-thread :thread-api/search-index-build-progress repo payload)
(p/catch (fn [_error] nil))))
(comment
(def-thread-api :thread-api/get-version
[]
@@ -892,25 +897,51 @@
[repo search-db conn build-id]
(ensure-active-search-index-build! repo build-id)
(search/truncate-table! search-db)
(let [db @conn]
(p/loop [remaining (seq (d/datoms db :avet :block/uuid))]
(ensure-active-search-index-build! repo build-id)
(p/let [_ (<wait-for-search-index-idle! repo build-id)]
(if (seq remaining)
(let [[batch remaining'] (take-block-datoms-batch remaining
search-index-build-batch-size
search-index-build-time-budget-ms)
indexed (->> batch
(keep #(d/entity db (:e %)))
(remove search/hidden-entity?)
(keep search/block->index))]
(when (seq indexed)
(search/upsert-blocks! search-db (bean/->js indexed)))
(p/let [_ (js/Promise. (fn [resolve] (js/setTimeout resolve 0)))]
(p/recur remaining')))
(do
(ensure-active-search-index-build! repo build-id)
(.exec search-db (str "PRAGMA user_version = " search-db-version))))))))
(let [db @conn
datoms (d/datoms db :avet :block/uuid)
total (count datoms)]
(p/do!
(report-search-index-progress! repo {:build-id build-id
:status :running
:progress 0
:processed 0
:total total})
(<wait-for-search-index-idle! repo build-id)
(p/loop [remaining (seq datoms)
processed 0
last-progress 0]
(ensure-active-search-index-build! repo build-id)
(if (seq remaining)
(let [[batch remaining'] (take-block-datoms-batch remaining
search-index-build-batch-size
search-index-build-time-budget-ms)
processed' (+ processed (count batch))
indexed (->> batch
(keep #(d/entity db (:e %)))
(remove search/hidden-entity?)
(keep search/block->index))
progress (if (zero? total)
100
(min 100 (int (* 100 (/ processed' total)))))
should-report? (> progress last-progress)]
(when (seq indexed)
(search/upsert-blocks! search-db (bean/->js indexed)))
(when should-report?
(report-search-index-progress! repo {:build-id build-id
:status :running
:progress progress
:processed processed'
:total total}))
(p/let [_ (js/Promise. (fn [resolve] (js/setTimeout resolve 0)))]
(p/recur remaining' processed' (if should-report? progress last-progress))))
(do
(ensure-active-search-index-build! repo build-id)
(.exec search-db (str "PRAGMA user_version = " search-db-version))
(report-search-index-progress! repo {:build-id build-id
:status :completed
:progress 100
:processed total
:total total})))))))
(def-thread-api :thread-api/search-build-blocks-indice-in-worker
[repo & [force?]]
@@ -928,6 +959,9 @@
(when-not (= :search/stale-index-build (:type (ex-data error)))
(throw error))))
(p/finally (fn []
(when (= build-id (get @*search-index-build-ids repo))
(report-search-index-progress! repo {:build-id build-id
:status :idle}))
(clear-search-index-build! repo build-id)))))))))))
(def-thread-api :thread-api/search-build-pages-indice

View File

@@ -102,21 +102,43 @@ DROP TRIGGER IF EXISTS blocks_au;
(str "(" (->> (map (fn [id] (str "'" id "'")) ids)
(string/join ", ")) ")"))
(def ^:private upsert-blocks-batch-size 2000)
(def ^:private upsert-blocks-sql
(memoize
(fn [row-count]
(str "INSERT INTO blocks (id, title, page) VALUES "
(string/join ", " (repeat row-count "(?, ?, ?)"))
" ON CONFLICT (id) DO UPDATE SET (title, page) = (excluded.title, excluded.page)"))))
(defn- valid-upsert-block?
[item]
(and (common-util/uuid-string? (.-id item))
(common-util/uuid-string? (.-page item))))
(defn- throw-upsert-blocks-error!
[item]
(js/console.error "Upsert blocks wrong data: ")
(js/console.dir item)
(throw (ex-info "Search upsert-blocks wrong data: "
(bean/->clj item))))
(defn- upsert-bind-params
[batch]
(into-array
(mapcat (fn [item]
[(.-id item) (.-title item) (.-page item)])
batch)))
(defn upsert-blocks!
[^Object db blocks]
(.transaction db (fn [tx]
(doseq [item blocks]
(if (and (common-util/uuid-string? (.-id item))
(common-util/uuid-string? (.-page item)))
(.exec tx #js {:sql "INSERT INTO blocks (id, title, page) VALUES ($id, $title, $page) ON CONFLICT (id) DO UPDATE SET (title, page) = ($title, $page)"
:bind #js {:$id (.-id item)
:$title (.-title item)
:$page (.-page item)}})
(do
(js/console.error "Upsert blocks wrong data: ")
(js/console.dir item)
(throw (ex-info "Search upsert-blocks wrong data: "
(bean/->clj item)))))))))
(doseq [batch (partition-all upsert-blocks-batch-size blocks)]
(doseq [item blocks]
(when-not (valid-upsert-block? item)
(throw-upsert-blocks-error! item)))
(.exec tx #js {:sql (upsert-blocks-sql (count batch))
:bind (upsert-bind-params batch)})))))
(defn delete-blocks!
[db ids]

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "'n Bladsy met die naam \"{1}\" bestaan reeds."
:page.convert/tag-to-page-has-children "Tag heeft onderliggende elementen en kan nie worden geconverteerd"
:page.delete/batch-confirm-title "Is jy seker jy wil hierdie bladsye uitvee? Eienskappe en etikette sal permanent uitgevee word en bladsye sal na Herwinning geskuif word."
:page.delete/confirm-title "Is jy seker jy wil hierdie bladsy uitvee?"
:page.delete/permanent-confirm-title "Is jy seker jy wil hierdie bladsy permanent uitvee?"
:page.delete/success "Bladsy \"{1}\" is suksesvol uitgevee!"
:page.delete/total "Totaal: {1}"
:page.delete/warning "Die inhoud van hierdie bladsye is uitgevee, maar kon nie uitgevee word nie: {1}. Sien javascript-konsole vir meer besonderhede."
@@ -1528,6 +1530,7 @@
:search/blank-input "Leë invoer"
:search/full-text-placeholder "Voltekssoektog"
:search/index-progress "Indekseer {1}%"
:search/indices-rebuilt-success "Soekindekse is suksesvol herbou!"
:search/no-result "Geen resultate nie"
:search/result-count "{1} resultate"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "توجد صفحة بالاسم \"{1}\" بالفعل."
:page.convert/tag-to-page-has-children "الوسم له عناصر فرعية ولا يمكن تحويله"
:page.delete/batch-confirm-title "هل أنت متأكد أنك تريد حذف هذه الصفحات؟ سيتم حذف الخصائص والوسوم نهائياً وستُنقل الصفحات إلى سلة المحذوفات."
:page.delete/confirm-title "هل أنت متأكد أنك تريد حذف هذه الصفحة؟"
:page.delete/permanent-confirm-title "هل أنت متأكد أنك تريد حذف هذه الصفحة نهائياً؟"
:page.delete/success "تم حذف الصفحة «{1}» بنجاح!"
:page.delete/total "الإجمالي: {1}"
:page.delete/warning "تم حذف محتواها من هذه الصفحات ولكن تعذر حذفها: {1}. راجع وحدة تحكم جافا سكريبت لمزيد من التفاصيل."
@@ -1528,6 +1530,7 @@
:search/blank-input "إدخال فارغ"
:search/full-text-placeholder "بحث في النص الكامل"
:search/index-progress "جارٍ فهرسة {1}%"
:search/indices-rebuilt-success "تم إعادة بناء فهارس البحث بنجاح!"
:search/no-result "لا توجد نتائج"
:search/result-count "{1} نتيجة"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Ja existeix una pàgina amb el nom \"{1}\"."
:page.convert/tag-to-page-has-children "L'etiqueta té fills i no es pot convertir"
:page.delete/batch-confirm-title "Esteu segur que voleu suprimir aquestes pàgines? Les propietats i les etiquetes se suprimiran permanentment i les pàgines es mouran a la Paperera."
:page.delete/confirm-title "Esteu segur que voleu suprimir aquesta pàgina?"
:page.delete/permanent-confirm-title "Esteu segur que voleu suprimir permanentment aquesta pàgina?"
:page.delete/success "La pàgina \"{1}\" s'ha suprimit correctament!"
:page.delete/total "Total: {1}"
:page.delete/warning "S'ha suprimit el contingut d'aquestes pàgines, però no s'han pogut suprimir: {1}. Vegeu la consola javascript per a més detalls."
@@ -1528,6 +1530,7 @@
:search/blank-input "Entrada buida"
:search/full-text-placeholder "Cerca de text complet"
:search/index-progress "Indexant {1}%"
:search/indices-rebuilt-success "Índexs de recerca reconstruïts correctament!"
:search/no-result "Sense resultats"
:search/result-count "{1} resultats"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Stránka s názvem „{1}“ již existuje."
:page.convert/tag-to-page-has-children "Štítek má podřízené prvky a nelze ho převést"
:page.delete/batch-confirm-title "Opravdu chcete smazat tyto stránky? Vlastnosti a štítky budou trvale smazány a stránky budou přesunuty do Koše."
:page.delete/confirm-title "Opravdu chcete smazat tuto stránku?"
:page.delete/permanent-confirm-title "Opravdu chcete trvale smazat tuto stránku?"
:page.delete/success "Stránka „{1}“ byla úspěšně smazána!"
:page.delete/total "Celkem: {1}"
:page.delete/warning "Obsah těchto stránek byl smazán, ale nepodařilo se je smazat: {1}. Další podrobnosti naleznete v konzole javascript."
@@ -1528,6 +1530,7 @@
:search/blank-input "Prázdný vstup"
:search/full-text-placeholder "Fulltextové vyhledávání"
:search/index-progress "Indexování {1} %"
:search/indices-rebuilt-success "Indexy vyhledávání byly úspěšně znovu vytvořeny!"
:search/no-result "Žádné výsledky"
:search/result-count "{1} výsledků"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Eine Seite mit dem Namen „{1}“ existiert bereits."
:page.convert/tag-to-page-has-children "Tag mit Unterelementen kann nicht konvertiert werden"
:page.delete/batch-confirm-title "Diese Seiten wirklich entfernen? Eigenschaften und Tags werden dauerhaft gelöscht und Seiten werden in den Papierkorb verschoben."
:page.delete/confirm-title "Diese Seite wirklich entfernen?"
:page.delete/permanent-confirm-title "Diese Seite wirklich dauerhaft entfernen?"
:page.delete/success "Seite „{1}“ wurde erfolgreich gelöscht!"
:page.delete/total "Gesamt: {1}"
:page.delete/warning "Der Inhalt dieser Seiten wurde gelöscht, konnte aber nicht gelöscht werden: {1}. Weitere Informationen finden Sie in der Javascript-Konsole."
@@ -1528,6 +1530,7 @@
:search/blank-input "Leere Eingabe"
:search/full-text-placeholder "Volltextsuche"
:search/index-progress "Indexierung {1} %"
:search/indices-rebuilt-success "Suchindizes erfolgreich neu aufgebaut!"
:search/no-result "Keine übereinstimmenden Treffer"
:search/result-count "{1} Ergebnisse"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "A page with the name \"{1}\" already exists."
:page.convert/tag-to-page-has-children "This tag cannot be converted because it has tag children. All tag children must be removed or converted before converting this tag."
:page.delete/batch-confirm-title "Are you sure you want to delete these pages? Properties and tags will be permanently deleted and pages will be moved to Recycle."
:page.delete/confirm-title "Are you sure you want to delete this page?"
:page.delete/permanent-confirm-title "Are you sure you want to permanently delete this page?"
:page.delete/success "Page \"{1}\" was deleted successfully!"
:page.delete/total "Total: {1}"
:page.delete/warning "These pages had their content deleted but were unable to be deleted: {1}. See javascript console for more details."
@@ -1528,6 +1530,7 @@
:search/blank-input "Blank input"
:search/full-text-placeholder "Full text search"
:search/index-progress "Indexing {1}%"
:search/indices-rebuilt-success "Search indices rebuilt successfully!"
:search/no-result "No matched result"
:search/result-count (fn [n] (str n (if (> n 1) " results" " result")))

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Ya existe una página con el nombre \"{1}\"."
:page.convert/tag-to-page-has-children "La etiqueta tiene elementos hijos y no se puede convertir"
:page.delete/batch-confirm-title "¿Está seguro de que desea eliminar estas páginas? Las propiedades y etiquetas se eliminarán permanentemente y las páginas se moverán a la Papelera."
:page.delete/confirm-title "¿Está seguro de que desea eliminar esta página?"
:page.delete/permanent-confirm-title "¿Está seguro de que desea eliminar permanentemente esta página?"
:page.delete/success "¡La página \"{1}\" se eliminó correctamente!"
:page.delete/total "Total: {1}"
:page.delete/warning "Se eliminó el contenido de estas páginas pero no se pudo eliminar: {1}. Consulte la consola de JavaScript para obtener más detalles."
@@ -1528,6 +1530,7 @@
:search/blank-input "Entrada vacía"
:search/full-text-placeholder "Búsqueda de texto completo"
:search/index-progress "Indexando {1}%"
:search/indices-rebuilt-success "¡Índices de búsqueda reconstruidos exitosamente!"
:search/no-result "Sin resultados que coincidan"
:search/result-count "{1} resultados"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "صفحه ای با نام \"{1}\" از قبل وجود دارد."
:page.convert/tag-to-page-has-children "برچسب دارای فرزندان است و نمی‌توان تبدیل کرد"
:page.delete/batch-confirm-title "آیا مطمئن هستید که می‌خواهید این صفحات را حذف کنید؟ ویژگی‌ها و برچسب‌ها برای همیشه حذف می‌شوند و صفحه‌ها به سطل بازیافت منتقل می‌شوند."
:page.delete/confirm-title "آیا مطمئن هستید که می خواهید این صفحه را حذف کنید؟"
:page.delete/permanent-confirm-title "آیا مطمئن هستید که می‌خواهید این صفحه را برای همیشه حذف کنید؟"
:page.delete/success "صفحه «{1}» با موفقیت حذف شد!"
:page.delete/total "مجموع: {1}"
:page.delete/warning "محتوای این صفحات حذف شد اما حذف نشدند: {1}. برای جزئیات بیشتر به کنسول جاوا اسکریپت مراجعه کنید."
@@ -1528,6 +1530,7 @@
:search/blank-input "ورودی خالی"
:search/full-text-placeholder "جستجوی متن کامل"
:search/index-progress "در حال ایندکس‌گذاری {1}٪"
:search/indices-rebuilt-success "فهرس‌های جستجو با موفقیت بازسازی شدند!"
:search/no-result "نتیجه‌ای یافت نشد"
:search/result-count "{1} نتیجه"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Une page portant le nom « {1} » existe déjà."
:page.convert/tag-to-page-has-children "Le tag a des éléments enfants et ne peut pas être converti"
:page.delete/batch-confirm-title "Etes-vous sûr de vouloir supprimer ces pages ? Les propriétés et les tags seront supprimés définitivement et les pages seront déplacées dans la Corbeille."
:page.delete/confirm-title "Etes-vous sûr de vouloir supprimer cette page ?"
:page.delete/permanent-confirm-title "Etes-vous sûr de vouloir supprimer définitivement cette page ?"
:page.delete/success "La page « {1} » a été supprimée avec succès !"
:page.delete/total "Total : {1}"
:page.delete/warning "Le contenu de ces pages a été supprimé mais n'a pas pu être supprimé : {1}. Voir la console javascript pour plus de détails."
@@ -1528,6 +1530,7 @@
:search/blank-input "Entrée vide"
:search/full-text-placeholder "Recherche en texte intégral"
:search/index-progress "Indexation {1} %"
:search/indices-rebuilt-success "Les indices de recherche ont été reconstruits avec succès !"
:search/no-result "Aucun résultat correspondant"
:search/result-count "{1} résultats"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Halaman dengan nama \"{1}\" sudah ada."
:page.convert/tag-to-page-has-children "Tag memiliki anak dan tidak bisa diubah"
:page.delete/batch-confirm-title "Apakah Anda yakin ingin menghapus halaman-halaman ini? Properti dan tag akan dihapus secara permanen dan halaman akan dipindahkan ke Tempat Sampah."
:page.delete/confirm-title "Apakah Anda yakin ingin menghapus halaman ini?"
:page.delete/permanent-confirm-title "Apakah Anda yakin ingin menghapus halaman ini secara permanen?"
:page.delete/success "Halaman \"{1}\" berhasil dihapus!"
:page.delete/total "Total: {1}"
:page.delete/warning "Halaman-halaman ini telah dihapus isinya tetapi tidak dapat dihapus: {1}. Lihat konsol javascript untuk detail selengkapnya."
@@ -1528,6 +1530,7 @@
:search/blank-input "Input kosong"
:search/full-text-placeholder "Pencarian teks lengkap"
:search/index-progress "Mengindeks {1}%"
:search/indices-rebuilt-success "Indeks pencarian berhasil dibangun kembali!"
:search/no-result "Tidak ada hasil yang cocok"
:search/result-count "{1} hasil"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Esiste già una pagina con il nome \"{1}\"."
:page.convert/tag-to-page-has-children "Il tag ha elementi figli e non può essere convertito"
:page.delete/batch-confirm-title "Sei sicuro di voler eliminare queste pagine? Proprietà e tag saranno eliminati definitivamente e le pagine verranno spostate nel Cestino."
:page.delete/confirm-title "Sei sicuro di voler eliminare questa pagina?"
:page.delete/permanent-confirm-title "Sei sicuro di voler eliminare definitivamente questa pagina?"
:page.delete/success "La pagina \"{1}\" è stata eliminata con successo!"
:page.delete/total "Totale: {1}"
:page.delete/warning "I contenuti di queste pagine sono stati eliminati ma non è stato possibile eliminarli: {1}. Vedi la console JavaScript per maggiori dettagli."
@@ -1528,6 +1530,7 @@
:search/blank-input "Input vuoto"
:search/full-text-placeholder "Ricerca full text"
:search/index-progress "Indicizzazione {1}%"
:search/indices-rebuilt-success "Indici di ricerca ricostruiti con successo!"
:search/no-result "Nessun risultato"
:search/result-count "{1} risultati"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "「{1}」という名前のページはすでに存在します。"
:page.convert/tag-to-page-has-children "子要素を持つタグは変換できません"
:page.delete/batch-confirm-title "これらのページを削除してもよろしいですか?プロパティとタグは完全に削除され、ページはごみ箱に移動されます。"
:page.delete/confirm-title "このページを削除してもよろしいですか?"
:page.delete/permanent-confirm-title "このページを完全に削除してもよろしいですか?"
:page.delete/success "ページ「{1}」は正常に削除されました。"
:page.delete/total "合計: {1}"
:page.delete/warning "これらのページのコンテンツは削除されましたが、削除できませんでした: {1}。詳細については、JavaScript コンソールを参照してください。"
@@ -1528,6 +1530,7 @@
:search/blank-input "空の入力"
:search/full-text-placeholder "全文検索"
:search/index-progress "索引作成中 {1}%"
:search/indices-rebuilt-success "検索インデックスが正常に再構築されました!"
:search/no-result "結果なし"
:search/result-count "{1} 件の結果"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "이름이 \"{1}\"인 페이지가 이미 존재합니다."
:page.convert/tag-to-page-has-children "자식 요소가 있는 태그는 변환할 수 없습니다"
:page.delete/batch-confirm-title "이 페이지들을 삭제하시겠습니까? 속성과 태그는 영구적으로 삭제되고 페이지는 휴지통으로 이동됩니다."
:page.delete/confirm-title "이 페이지를 삭제하시겠습니까?"
:page.delete/permanent-confirm-title "이 페이지를 영구적으로 삭제하시겠습니까?"
:page.delete/success "페이지 \"{1}\"이(가) 성공적으로 삭제되었습니다!"
:page.delete/total "합계: {1}"
:page.delete/warning "다음 페이지의 콘텐츠가 삭제되었지만 삭제할 수 없습니다. {1}. 자세한 내용은 자바스크립트 콘솔을 참조하세요."
@@ -1528,6 +1530,7 @@
:search/blank-input "빈 입력"
:search/full-text-placeholder "전문 검색"
:search/index-progress "인덱싱 {1}%"
:search/indices-rebuilt-success "검색 인덱스가 성공적으로 재구성되었습니다!"
:search/no-result "결과 없음"
:search/result-count "{1}개 결과"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "En side med navnet \"{1}\" eksisterer allerede."
:page.convert/tag-to-page-has-children "Tagg har underordnede elementer og kan ikke konverteres"
:page.delete/batch-confirm-title "Er du sikker på at du vil slette disse sidene? Egenskaper og tagger blir slettet permanent, og sidene blir flyttet til Papirkurv."
:page.delete/confirm-title "Er du sikker på at du vil slette denne siden?"
:page.delete/permanent-confirm-title "Er du sikker på at du vil slette denne siden permanent?"
:page.delete/success "Side \"{1}\" ble slettet!"
:page.delete/total "Totalt: {1}"
:page.delete/warning "Innholdet på disse sidene ble slettet, men kunne ikke slettes: {1}. Se javascript-konsollen for mer informasjon."
@@ -1528,6 +1530,7 @@
:search/blank-input "Tom inndata"
:search/full-text-placeholder "Fulltekstsøk"
:search/index-progress "Indekserer {1} %"
:search/indices-rebuilt-success "Søkeindekser bygget på nytt!"
:search/no-result "Ingen resultater"
:search/result-count "{1} resultater"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Er bestaat al een pagina met de naam \"{1}\"."
:page.convert/tag-to-page-has-children "Tag heeft onderliggende elementen en kan niet worden geconverteerd"
:page.delete/batch-confirm-title "Weet u zeker dat u deze pagina's wilt verwijderen? Eigenschappen en tags worden permanent verwijderd en pagina's worden naar de Prullenbak verplaatst."
:page.delete/confirm-title "Weet u zeker dat u deze pagina wilt verwijderen?"
:page.delete/permanent-confirm-title "Weet u zeker dat u deze pagina permanent wilt verwijderen?"
:page.delete/success "Pagina \"{1}\" is succesvol verwijderd!"
:page.delete/total "Totaal: {1}"
:page.delete/warning "Van deze pagina's is de inhoud verwijderd, maar deze kon niet worden verwijderd: {1}. Zie JavaScript-console voor meer details."
@@ -1528,6 +1530,7 @@
:search/blank-input "Lege invoer"
:search/full-text-placeholder "Zoeken in volledige tekst"
:search/index-progress "Indexeren {1}%"
:search/indices-rebuilt-success "Zoekindexen zijn opnieuw opgebouwd!"
:search/no-result "Geen resultaten"
:search/result-count "{1} resultaten"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Strona o nazwie „{1}” już istnieje."
:page.convert/tag-to-page-has-children "Tag ma elementy podrzędne i nie może być przekonwertowany"
:page.delete/batch-confirm-title "Czy na pewno chcesz usunąć te strony? Właściwości i tagi zostaną trwale usunięte, a strony trafią do Kosza."
:page.delete/confirm-title "Czy na pewno chcesz usunąć tę stronę?"
:page.delete/permanent-confirm-title "Czy na pewno chcesz trwale usunąć tę stronę?"
:page.delete/success "Strona „{1}” została pomyślnie usunięta!"
:page.delete/total "Łącznie: {1}"
:page.delete/warning "Treść tych stron została usunięta, ale nie udało się ich usunąć: {1}. Więcej szczegółów znajdziesz w konsoli JavaScript."
@@ -1528,6 +1530,7 @@
:search/blank-input "Puste wejście"
:search/full-text-placeholder "Wyszukiwanie pełnotekstowe"
:search/index-progress "Indeksowanie {1}%"
:search/indices-rebuilt-success "Indeksy wyszukiwania zostały pomyślnie przebudowane!"
:search/no-result "Brak wyników"
:search/result-count "{1} wyników"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Já existe uma página com o nome \"{1}\"."
:page.convert/tag-to-page-has-children "A tag possui filhos e não pode ser convertida"
:page.delete/batch-confirm-title "Tem certeza de que deseja excluir estas páginas? Propriedades e tags serão excluídas permanentemente e as páginas serão movidas para a Lixeira."
:page.delete/confirm-title "Tem certeza de que deseja excluir esta página?"
:page.delete/permanent-confirm-title "Tem certeza de que deseja excluir permanentemente esta página?"
:page.delete/success "A página \"{1}\" foi excluída com sucesso!"
:page.delete/total "Total: {1}"
:page.delete/warning "Estas páginas tiveram seu conteúdo excluído, mas não puderam ser excluídos: {1}. Consulte o console javascript para obter mais detalhes."
@@ -1528,6 +1530,7 @@
:search/blank-input "Entrada vazia"
:search/full-text-placeholder "Pesquisa de texto completo"
:search/index-progress "Indexando {1}%"
:search/indices-rebuilt-success "Índices de busca reconstruídos com sucesso!"
:search/no-result "Nenhum resultado correspondente"
:search/result-count "{1} resultados"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Já existe uma página com o nome \"{1}\"."
:page.convert/tag-to-page-has-children "A tag possui filhos e não pode ser convertida"
:page.delete/batch-confirm-title "Tem a certeza de que pretende eliminar estas páginas? As propriedades e tags serão eliminadas permanentemente e as páginas serão movidas para a Reciclagem."
:page.delete/confirm-title "Tem a certeza que pretende eliminar esta página?"
:page.delete/permanent-confirm-title "Tem a certeza de que pretende eliminar permanentemente esta página?"
:page.delete/success "A página \"{1}\" foi eliminada com sucesso!"
:page.delete/total "Total: {1}"
:page.delete/warning "Estas páginas tiveram o seu conteúdo eliminado, mas não puderam ser eliminadas: {1}. Consulte a consola javascript para obter mais detalhes."
@@ -1528,6 +1530,7 @@
:search/blank-input "Entrada vazia"
:search/full-text-placeholder "Pesquisa de texto completo"
:search/index-progress "A indexar {1}%"
:search/indices-rebuilt-success "Índices de busca reconstruídos com sucesso!"
:search/no-result "Sem resultados"
:search/result-count "{1} resultados"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Страница с названием «{1}» уже существует."
:page.convert/tag-to-page-has-children "Тег имеет дочерние элементы и не может быть преобразован"
:page.delete/batch-confirm-title "Вы уверены, что хотите удалить эти страницы? Свойства и теги будут удалены навсегда, а страницы будут перемещены в Корзину."
:page.delete/confirm-title "Вы уверены, что хотите удалить эту страницу?"
:page.delete/permanent-confirm-title "Вы уверены, что хотите удалить эту страницу навсегда?"
:page.delete/success "Страница «{1}» успешно удалена!"
:page.delete/total "Всего: {1}"
:page.delete/warning "Содержимое этих страниц было удалено, но удалить их не удалось: {1}. Дополнительную информацию см. в консоли JavaScript."
@@ -1528,6 +1530,7 @@
:search/blank-input "Пустой ввод"
:search/full-text-placeholder "Полнотекстовый поиск"
:search/index-progress "Индексация {1}%"
:search/indices-rebuilt-success "Индексы поиска успешно перестроены!"
:search/no-result "Нет результатов"
:search/result-count "{1} результатов"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Stránka s názvom „{1}“ už existuje."
:page.convert/tag-to-page-has-children "Štítok má podriadené prvky a nemožno ho převést"
:page.delete/batch-confirm-title "Naozaj chcete odstrániť tieto stránky? Vlastnosti a tagy budú natrvalo odstránené a stránky budú presunuté do Koša."
:page.delete/confirm-title "Naozaj chcete odstrániť túto stránku?"
:page.delete/permanent-confirm-title "Naozaj chcete natrvalo odstrániť túto stránku?"
:page.delete/success "Stránka „{1}“ bola úspešne odstránená!"
:page.delete/total "Celkom: {1}"
:page.delete/warning "Obsah týchto stránok bol odstránený, ale nebolo možné ho odstrániť: {1}. Ďalšie podrobnosti nájdete v konzole javascript."
@@ -1528,6 +1530,7 @@
:search/blank-input "Prázdny vstup"
:search/full-text-placeholder "Fulltextové vyhľadávanie"
:search/index-progress "Indexovanie {1} %"
:search/indices-rebuilt-success "Indexy vyhľadávania boli úspešne znovu vytvorené!"
:search/no-result "Žiadne výsledky"
:search/result-count "{1} výsledkov"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "\"{1}\" adında bir sayfa zaten mevcut."
:page.convert/tag-to-page-has-children "Etiketin alt öğeleri var ve dönüştürülemez"
:page.delete/batch-confirm-title "Bu sayfaları silmek istediğinizden emin misiniz? Özellikler ve etiketler kalıcı olarak silinecek ve sayfalar Geri Dönüşüm'e taşınacaktır."
:page.delete/confirm-title "Bu sayfayı silmek istediğinizden emin misiniz?"
:page.delete/permanent-confirm-title "Bu sayfayı kalıcı olarak silmek istediğinizden emin misiniz?"
:page.delete/success "Sayfa \"{1}\" başarıyla silindi!"
:page.delete/total "Toplam: {1}"
:page.delete/warning "Bu sayfaların içeriği silindi ancak silinemedi: {1}. Daha fazla ayrıntı için javascript konsoluna bakın."
@@ -1528,6 +1530,7 @@
:search/blank-input "Boş giriş"
:search/full-text-placeholder "Tam metin araması"
:search/index-progress "Dizinleniyor {1}%"
:search/indices-rebuilt-success "Arama dizinleri başarıyla yeniden oluşturuldu!"
:search/no-result "Sonuç yok"
:search/result-count "{1} sonuç"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "Сторінка з назвою \"{1}\" вже існує."
:page.convert/tag-to-page-has-children "Тег имеет дочірние элементы и не может быть преобразован"
:page.delete/batch-confirm-title "Ви впевнені, що хочете видалити ці сторінки? Властивості й теги буде видалено назавжди, а сторінки буде переміщено до Кошика."
:page.delete/confirm-title "Ви впевнені, що хочете видалити цю сторінку?"
:page.delete/permanent-confirm-title "Ви впевнені, що хочете назавжди видалити цю сторінку?"
:page.delete/success "Сторінка \"{1}\" успішно видалена!"
:page.delete/total "Усього: {1}"
:page.delete/warning "Вміст цих сторінок було видалено, але його не вдалося видалити: {1}. Дивіться консоль javascript для отримання додаткової інформації."
@@ -1528,6 +1530,7 @@
:search/blank-input "Порожнє введення"
:search/full-text-placeholder "Повнотекстовий пошук"
:search/index-progress "Індексація {1}%"
:search/indices-rebuilt-success "Індекси пошуку успішно перебудовані!"
:search/no-result "Немає результатів"
:search/result-count "{1} результатів"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "页面“{1}”已存在。"
:page.convert/tag-to-page-has-children "此标签无法转换,因为它仍有子标签。必须先移除或转换所有子标签,才能转换此标签。"
:page.delete/batch-confirm-title "确定要删除这些页面吗?属性和标签将被永久删除,页面将被移入回收站。"
:page.delete/confirm-title "确认要删除此页面吗?"
:page.delete/permanent-confirm-title "确定要永久删除此页面吗?"
:page.delete/success "页面“{1}”已成功删除!"
:page.delete/total "总计: {1}"
:page.delete/warning "这些页面的内容已删除,但无法删除:{1}。有关更多详细信息,请参阅 JavaScript 控制台。"
@@ -1528,6 +1530,7 @@
:search/blank-input "空白输入"
:search/full-text-placeholder "全文搜索"
:search/index-progress "正在索引 {1}%"
:search/indices-rebuilt-success "搜索索引已成功重建!"
:search/no-result "未找到匹配项"
:search/result-count "{1} 个结果"

View File

@@ -1093,7 +1093,9 @@
:page.convert/tag-to-page-duplicate "頁面「{1}」已存在。"
:page.convert/tag-to-page-has-children "此標籤無法轉換,因為它仍有子標籤。必須先移除或轉換所有子標籤,才能轉換此標籤。"
:page.delete/batch-confirm-title "確定要刪除這些頁面嗎?屬性與標籤將被永久刪除,頁面將被移入資源回收筒。"
:page.delete/confirm-title "確認要刪除此頁面嗎?"
:page.delete/permanent-confirm-title "確定要永久刪除此頁面嗎?"
:page.delete/success "頁面「{1}」已成功刪除!"
:page.delete/total "總計: {1}"
:page.delete/warning "這些頁面的內容已刪除,但無法刪除:{1}。有關更多詳細信息,請參閱 JavaScript 控制台。"
@@ -1528,6 +1530,7 @@
:search/blank-input "空白輸入"
:search/full-text-placeholder "全文搜尋"
:search/index-progress "正在建立索引 {1}%"
:search/indices-rebuilt-success "搜尋索引已成功重建!"
:search/no-result "未找到匹配項"
:search/result-count "{1} 個結果"

View File

@@ -224,3 +224,38 @@
(is (some? ctor))
(is (= 1 (count result)))
(is (= "alpha beta" (get-in result [0 :item :title])))))))
(deftest upsert-blocks-batches-rows-into-single-sql-statement
(let [calls (atom [])
tx #js {:exec (fn [opts]
(swap! calls conj {:sql (aget opts "sql")
:bind (js->clj (aget opts "bind"))}))}
db #js {:transaction (fn [f] (f tx))}
blocks (clj->js [{:id "67e55044-10b1-426f-9247-bb680e5fe0c8"
:title "alpha"
:page "67e55044-10b1-426f-9247-bb680e5fe0c8"}
{:id "8f14e45f-ea6e-4be8-b53f-bf0f2ca8a5db"
:title "beta"
:page "8f14e45f-ea6e-4be8-b53f-bf0f2ca8a5db"}
{:id "9d5ed678-fe57-4bcf-bf4d-6f2fd5f8995d"
:title "gamma"
:page "9d5ed678-fe57-4bcf-bf4d-6f2fd5f8995d"}])]
(search/upsert-blocks! db blocks)
(is (= 1 (count @calls)))
(is (= "INSERT INTO blocks (id, title, page) VALUES (?, ?, ?), (?, ?, ?), (?, ?, ?) ON CONFLICT (id) DO UPDATE SET (title, page) = (excluded.title, excluded.page)"
(:sql (first @calls))))
(is (= ["67e55044-10b1-426f-9247-bb680e5fe0c8" "alpha" "67e55044-10b1-426f-9247-bb680e5fe0c8"
"8f14e45f-ea6e-4be8-b53f-bf0f2ca8a5db" "beta" "8f14e45f-ea6e-4be8-b53f-bf0f2ca8a5db"
"9d5ed678-fe57-4bcf-bf4d-6f2fd5f8995d" "gamma" "9d5ed678-fe57-4bcf-bf4d-6f2fd5f8995d"]
(:bind (first @calls))))))
(deftest upsert-blocks-throws-on-invalid-input
(let [tx #js {:exec (fn [_opts] nil)}
db #js {:transaction (fn [f] (f tx))}
error (try
(search/upsert-blocks! db (clj->js [{:id "not-uuid" :title "alpha" :page "not-uuid"}]))
nil
(catch :default e e))]
(is (some? error))
(is (re-find #"Search upsert-blocks wrong data"
(or (ex-message error) (str error))))))