Просмотр исходного кода

Merge branch 'master' into feat/mcp-server

Tienson Qin 2 месяцев назад
Родитель
Сommit
0422d75f68

+ 3 - 2
deps/db/src/logseq/db.cljs

@@ -111,12 +111,13 @@
                (not (:reset-conn! tx-meta))
                (not (:initial-db? tx-meta))
                (not (:skip-validate-db? tx-meta false))
+               (not (:rtc-download-graph? tx-meta))
                (not (:logseq.graph-parser.exporter/new-graph? tx-meta)))
         (let [tx-report* (d/with db tx-data tx-meta)
               pipeline-f @*transact-pipeline-fn
               tx-report (if-let [f pipeline-f] (f tx-report*) tx-report*)
               _ (throw-if-page-has-block-parent! (:db-after tx-report) (:tx-data tx-report))
-              validate-result (db-validate/validate-tx-report tx-report nil)]
+              [validate-result errors] (db-validate/validate-tx-report tx-report nil)]
           (if validate-result
             (when (and tx-report (seq (:tx-data tx-report)))
               ;; perf enhancement: avoid repeated call on `d/with`
@@ -126,7 +127,7 @@
             (do
               ;; notify ui
               (when-let [f @*transact-invalid-callback]
-                (f tx-report))
+                (f tx-report errors))
               (throw (ex-info "DB write failed with invalid data" {:tx-data tx-data}))))
           tx-report)
         (d/transact! conn tx-data tx-meta)))

+ 3 - 4
deps/db/src/logseq/db/frontend/rules.cljc

@@ -230,10 +230,9 @@
 
     :tags
     '[(tags ?b ?tags)
-      [?b :block/tags ?t]
-      [?t :block/name ?tag]
-      [(missing? $ ?b :block/link)]
-      [(contains? ?tags ?tag)]]
+      [?b :block/tags ?tag]
+      [(contains? ?tags ?tag)]
+      [(missing? $ ?b :block/link)]]
 
     :task
     '[(task ?b ?statuses)

+ 22 - 17
deps/db/src/logseq/db/frontend/validate.cljs

@@ -24,7 +24,7 @@
 (defn validate-tx-report
   "Validates the datascript tx-report for entities that have changed. Returns
   boolean indicating if db is valid"
-  [{:keys [db-after tx-data _tx-meta]} validate-options]
+  [{:keys [db-after tx-data tx-meta]} validate-options]
   (let [changed-ids (->> tx-data (keep :e) distinct)
         tx-datoms (mapcat #(d/datoms db-after :eavt %) changed-ids)
         ent-maps* (map (fn [[db-id m]]
@@ -38,23 +38,28 @@
                               ;; remove :db/id as it adds needless declarations to schema
                               #(validator [(dissoc % :db/id)])
                               ent-maps)]
-        ;; (prn "changed eids:" changed-ids :tx-meta tx-meta)
         (if (seq invalid-ent-maps)
-          (let [explainer (get-schema-explainer (:closed-schema? validate-options))]
-            (prn "Invalid datascript entities detected amongst changed entity ids:" changed-ids)
-            (doseq [m invalid-ent-maps]
-              (let [m' (update m :block/properties (fn [properties]
-                                                     (map (fn [[p v]]
-                                                            [(:db/ident p) v])
-                                                          properties)))
-                    data {:entity-map m'
-                          :errors (me/humanize (explainer [(dissoc m :db/id)]))}]
-                (try
-                  (pprint/pprint data)
-                  (catch :default _e
-                    (prn data)))))
-            false)
-          true)))))
+          (do
+            (prn "Invalid datascript entities detected amongst changed entity ids:" changed-ids :tx-meta tx-meta)
+            (let [explainer (get-schema-explainer (:closed-schema? validate-options))
+                  errors (doall
+                          (map
+                           (fn [m]
+                             (let [m' (update m :block/properties (fn [properties]
+                                                                    (map (fn [[p v]]
+                                                                           [(:db/ident p) v])
+                                                                         properties)))
+                                   data {:entity-map m'
+                                         :errors (me/humanize (explainer [(dissoc m :db/id)]))}]
+                               (try
+                                 (pprint/pprint data)
+                                 (catch :default _e
+                                   (prn data)))
+                               data))
+                           invalid-ent-maps))]
+
+              [false errors]))
+          [true nil])))))
 
 (defn group-errors-by-entity
   "Groups malli errors by entities. db is used for providing more debugging info"

+ 1 - 1
libs/cljs-sdk/deps.edn

@@ -1,5 +1,5 @@
 {:paths ["src" "test"]
- :deps {org.clojure/clojurescript {:mvn/version "1.11.60"}
+ :deps {org.clojure/clojurescript {:mvn/version "1.12.42"}
         cljs-bean/cljs-bean       {:mvn/version "1.5.0"}}
 
  :npm-deps {"@logseq/libs" "0.2.3"}

+ 1 - 1
libs/cljs-sdk/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@logseq/cljs-libs",
-  "version": "0.0.10",
+  "version": "0.0.11",
   "description": "Logseq plugin API wrapper",
   "dependencies": {
     "@logseq/libs": "^0.2.3"

+ 15 - 6
src/main/frontend/components/block.cljs

@@ -2493,6 +2493,10 @@
       (and (:page-title? config) (ldb/page? block) (string/blank? (:block/title block)))
       [:div.opacity-75 "Untitled"]
 
+      (and (ldb/asset? block)
+           (= :pdf (some-> (:logseq.property.asset/type block) string/lower-case keyword)))
+      (asset-cp config block)
+
       (:raw-title? config)
       (text-block-title (dissoc config :raw-title?) block)
 
@@ -3182,18 +3186,23 @@
                                      edit-input-id
                                      config))]
              show-editor? (and editor-box edit? (not type-block-editor?))]
-         (if (ldb/asset? block)
+         (cond
+           (and (ldb/asset? block) (img-audio-video? block))
            [:div.flex.flex-col.asset-block-wrap.w-full
             (block-content-f {:custom-block-content
                               [:div.flex.flex-1
                                (asset-cp config block)]})
             (if show-editor?
               [:div.mt-1 editor-cp]
-              (when (img-audio-video? block)
-                [:div.text-xs.opacity-60.mt-1.cursor-text
-                 {:on-click #(edit-block-content config block edit-input-id)}
-                 (text-block-title (dissoc config :raw-title?) block)]))]
-           (if show-editor? editor-cp (block-content-f {}))))
+              [:div.text-xs.opacity-60.mt-1.cursor-text
+               {:on-click #(edit-block-content config block edit-input-id)}
+               (text-block-title (dissoc config :raw-title?) block)])]
+
+           show-editor?
+           editor-cp
+
+           :else
+           (block-content-f {})))
 
        (when-not (:table-block-title? config)
          [:div.ls-block-right.flex.flex-row.items-center.self-start.gap-1

+ 9 - 7
src/main/frontend/components/property/value.cljs

@@ -550,13 +550,15 @@
         class? (or (= :block/tags (:db/ident property))
                    (and (= :logseq.property.class/extends (:db/ident property))
                         (ldb/class? block))
-                   (every? (fn [class]
-                             (or
-                              (= :logseq.class/Tag (:db/ident class))
-                              (some (fn [e]
-                                      (= :logseq.class/Tag (:db/ident e)))
-                                    (ldb/get-class-extends class))))
-                           (:logseq.property/classes property)))
+                   (let [classes (:logseq.property/classes property)]
+                     (and (seq classes)
+                          (every? (fn [class]
+                                    (or
+                                     (= :logseq.class/Tag (:db/ident class))
+                                     (some (fn [e]
+                                             (= :logseq.class/Tag (:db/ident e)))
+                                           (ldb/get-class-extends class))))
+                                  classes))))
         ;; Note: property and other types shouldn't be converted to class
         page? (ldb/internal-page? page-entity)]
     (cond

+ 3 - 3
src/main/frontend/components/views.cljs

@@ -716,9 +716,9 @@
         :class-objects
         (when (seq page-ids)
           (when-not (= :logseq.class/Page (:db/ident view-parent))
-            (let [tx-data (map (fn [pid] [:db/retract pid :block/tags (:db/id view-parent)]) page-ids)]
-              (when (seq tx-data)
-                (outliner-op/transact! tx-data {:outliner-op :save-block})))))
+            (doseq [page pages]
+              (when-let [id (:block/uuid page)]
+                (outliner-op/delete-page! id)))))
 
         :property-objects
         ;; Relationships with built-in properties must not be deleted e.g. built-in? or parent

+ 18 - 3
src/main/frontend/db/query_dsl.cljs

@@ -8,6 +8,7 @@
             [clojure.walk :as walk]
             [frontend.config :as config]
             [frontend.date :as date]
+            [frontend.db.conn :as db-conn]
             [frontend.db.file-based.model :as file-model]
             [frontend.db.query-react :as query-react]
             [frontend.db.utils :as db-utils]
@@ -18,7 +19,9 @@
             [logseq.common.util :as common-util]
             [logseq.common.util.date-time :as date-time-util]
             [logseq.common.util.page-ref :as page-ref]
+            [logseq.db :as ldb]
             [logseq.db.file-based.rules :as file-rules]
+            [logseq.db.frontend.class :as db-class]
             [logseq.db.frontend.property :as db-property]
             [logseq.db.frontend.rules :as rules]
             [logseq.graph-parser.text :as text]))
@@ -414,10 +417,22 @@
   (let [tags (if (coll? (first (rest e)))
                (first (rest e))
                (rest e))
-        tags (map (comp string/lower-case name) tags)]
+        tags (map name tags)]
     (when (seq tags)
-      (let [tags (set (map (comp page-ref/get-page-name! string/lower-case name) tags))]
-        {:query (list 'tags (if db-graph? '?b '?p) tags)
+      (let [tags (set (map (comp page-ref/get-page-name!) tags))
+            lower-cased-tags (set (map (comp page-ref/get-page-name! string/lower-case) tags))]
+        {:query (list 'tags
+                      (if db-graph? '?b '?p)
+                      (if db-graph?
+                        (let [db (db-conn/get-db)]
+                          (->> tags
+                               (mapcat (fn [tag-name]
+                                         (when-let [tag-id (first (ldb/page-exists? db tag-name #{:logseq.class/Tag}))]
+                                           (when-let [tag (db-utils/entity tag-id)]
+                                             (->> (db-class/get-structured-children db (:db/id tag))
+                                                  (cons (:db/id tag)))))))
+                               set))
+                        lower-cased-tags))
          :rules [:tags]}))))
 
 (defn- build-page-tags

+ 2 - 2
src/main/frontend/db/utils.cljs

@@ -36,10 +36,10 @@
                (str "Invalid entity eid: " (pr-str eid))))
      (let [eid (if (uuid? eid) [:block/uuid eid] eid)]
        (when-let [db (if (string? repo-or-db)
-                     ;; repo
+                       ;; repo
                        (let [repo (or repo-or-db (state/get-current-repo))]
                          (conn/get-db repo))
-                     ;; db
+                       ;; db
                        repo-or-db)]
          (d/entity db eid))))))
 

+ 10 - 8
src/main/frontend/extensions/fsrs.cljs

@@ -47,10 +47,7 @@
   "Return nil if block is not #card.
   Return default card-map if `:logseq.property.fsrs/state` or `:logseq.property.fsrs/due` is nil"
   [block-entity]
-  (when (some (fn [tag]
-                (assert (some? (:db/ident tag)) tag)
-                (= :logseq.class/Card (:db/ident tag))) ;block should contains #Card
-              (:block/tags block-entity))
+  (when (ldb/class-instance? (db/entity :logseq.class/Card) block-entity)
     (let [fsrs-state (:logseq.property.fsrs/state block-entity)
           fsrs-due (:logseq.property.fsrs/due block-entity)
           return-default-card-map? (not (and fsrs-state fsrs-due))]
@@ -84,10 +81,13 @@
         cards (when (and cards-id (not= (keyword cards-id) :global)) (db/entity cards-id))
         query (:block/title cards)
         result (query-dsl/parse query {:db-graph? true})
+        card-tag-id (:db/id (db/entity :logseq.class/Card))
+        card-tag-children-ids (db-model/get-structured-children repo card-tag-id)
+        card-ids (cons card-tag-id card-tag-children-ids)
         q '[:find [?b ...]
-            :in $ ?now-inst-ms %
+            :in $ [?t ...] ?now-inst-ms %
             :where
-            [?b :block/tags :logseq.class/Card]
+            [?b :block/tags ?t]
             (or-join [?b ?now-inst-ms]
                      (and
                       [?b :logseq.property.fsrs/due ?due]
@@ -100,7 +100,7 @@
                 q
                 (if (coll? (first query*)) query* [query*])))
              q)]
-    (db-async/<q repo {:transact-db? false} q' now-inst-ms (:rules result))))
+    (db-async/<q repo {:transact-db? false} q' card-ids now-inst-ms (:rules result))))
 
 (defn- btn-with-shortcut [{:keys [shortcut id btn-text due on-click class]}]
   (let [bg-class (case id
@@ -252,7 +252,9 @@
         all-cards (concat
                    [{:db/id :global
                      :block/title "All cards"}]
-                   (db-model/get-class-objects repo (:db/id (entity-plus/entity-memoized (db/get-db) :logseq.class/Cards))))
+                   (db-model/get-class-objects repo (:db/id (entity-plus/entity-memoized (db/get-db) :logseq.class/Cards)))
+                   ;; TODO: list all children tags of #Card
+                   )
         *block-ids (::block-ids state)
         block-ids (rum/react *block-ids)
         loading? (rum/react (::loading? state))

+ 1 - 1
src/main/frontend/extensions/pdf/assets.cljs

@@ -161,7 +161,7 @@
                                              (:db/id color))) colors)]
           (when color-id
             (let [properties (cond->
-                              {:block/tags :logseq.class/Pdf-annotation
+                              {:block/tags #{(:db/id (db/entity :logseq.class/Pdf-annotation))}
                                :logseq.property/ls-type  :annotation
                                :logseq.property.pdf/hl-color color-id
                                :logseq.property/asset (:db/id pdf-block)

+ 88 - 79
src/main/frontend/handler/editor.cljs

@@ -631,15 +631,16 @@
                                         nil)]
           (when target-block
             (p/do!
-             (ui-outliner-tx/transact!
-              {:outliner-op :insert-blocks}
-              (outliner-insert-block! config target-block new-block
-                                      {:sibling? sibling?
-                                       :keep-uuid? true
-                                       :ordered-list? ordered-list?
-                                       :replace-empty-target? replace-empty-target?})
-              (when (and db-based? (seq properties))
-                (property-handler/set-block-properties! repo (:block/uuid new-block) properties)))
+             (let [new-block' (if (and db-based? (seq properties))
+                                (into new-block properties)
+                                new-block)]
+               (ui-outliner-tx/transact!
+                {:outliner-op :insert-blocks}
+                (outliner-insert-block! config target-block new-block'
+                                        {:sibling? sibling?
+                                         :keep-uuid? true
+                                         :ordered-list? ordered-list?
+                                         :replace-empty-target? replace-empty-target?})))
              (when edit-block?
                (if (and replace-empty-target?
                         (string/blank? (:block/title last-block)))
@@ -1527,87 +1528,95 @@
         ;; actually, writing binary using memory fs
         (fs/write-plain-text-file! repo dir file-rpath content nil)))))
 
+(defn- new-asset-block
+  [repo ^js file repo-dir asset-dir-rpath]
+  ;; WARN file name maybe fully qualified path when paste file
+  (p/let [file-name (node-path/basename (.-name file))
+          file-name-without-ext* (db-asset/asset-name->title file-name)
+          file-name-without-ext (if (= file-name-without-ext* "image")
+                                  (date/get-date-time-string-2)
+                                  file-name-without-ext*)
+          checksum (assets-handler/get-file-checksum file)
+          existing-asset (db-async/<get-asset-with-checksum repo checksum)]
+    (if existing-asset
+      (do
+        (notification/show! (str "Asset exists already, title: " (:block/title existing-asset)
+                                 ", node reference: [[" (:block/uuid existing-asset) "]]")
+                            :warning
+                            false)
+        nil)
+      (p/let [block-id (ldb/new-block-id)
+              ext (when file-name (db-asset/asset-path->type file-name))
+              _ (when (string/blank? ext)
+                  (throw (ex-info "File doesn't have a valid ext."
+                                  {:file-name file-name})))
+              file-path   (str block-id "." ext)
+              file-rpath  (str asset-dir-rpath "/" file-path)
+              dir repo-dir
+              asset (db/entity :logseq.class/Asset)]
+
+        (if (assets-handler/exceed-limit-size? file)
+          (do
+            (notification/show! [:div "Asset size shouldn't be larger than 100M"]
+                                :warning
+                                false)
+            (throw (ex-info "Asset size shouldn't be larger than 100M" {:file-name file-name})))
+          (p/do!
+           (db-based-save-asset! repo dir file file-rpath)
+           {:block/title file-name-without-ext
+            :block/uuid block-id
+            :logseq.property.asset/type ext
+            :logseq.property.asset/size (.-size file)
+            :logseq.property.asset/checksum checksum
+            :block/tags #{(:db/id asset)}}))))))
+
 (defn db-based-save-assets!
   "Save incoming(pasted) assets to assets directory.
 
-   Returns: asset entity"
+   Returns: asset entities"
   [repo files & {:keys [pdf-area? last-edit-block]}]
-  (p/let [[repo-dir asset-dir-rpath] (assets-handler/ensure-assets-dir! repo)]
-    (p/all
-     (for [[_index ^js file] (map-indexed vector files)]
-      ;; WARN file name maybe fully qualified path when paste file
-       (p/let [file-name (node-path/basename (.-name file))
-               file-name-without-ext* (db-asset/asset-name->title file-name)
-               file-name-without-ext (if (= file-name-without-ext* "image")
-                                       (date/get-date-time-string-2)
-                                       file-name-without-ext*)
-               checksum (assets-handler/get-file-checksum file)
-               existing-asset (db-async/<get-asset-with-checksum repo checksum)]
-         (if existing-asset
-           existing-asset
-           (p/let [block-id (ldb/new-block-id)
-                   ext (when file-name (db-asset/asset-path->type file-name))
-                   _ (when (string/blank? ext)
-                       (throw (ex-info "File doesn't have a valid ext."
-                                       {:file-name file-name})))
-                   file-path   (str block-id "." ext)
-                   file-rpath  (str asset-dir-rpath "/" file-path)
-                   dir repo-dir
-                   asset (db/entity :logseq.class/Asset)]
-
-             (if (assets-handler/exceed-limit-size? file)
-               (do
-                 (notification/show! [:div "Asset size shouldn't be larger than 100M"]
-                                     :warning
-                                     false)
-                 (throw (ex-info "Asset size shouldn't be larger than 100M" {:file-name file-name})))
-               (p/let [properties {:logseq.property.asset/type ext
-                                   :logseq.property.asset/size (.-size file)
-                                   :logseq.property.asset/checksum checksum
-                                   :block/tags (:db/id asset)}
-                       insert-opts {:custom-uuid block-id
-                                    :edit-block? false
-                                    :properties properties}
-                       _ (db-based-save-asset! repo dir file file-rpath)
-                       edit-block (or (state/get-edit-block) last-edit-block)
-                       today-page-name (date/today)
-                       today-page-e (db-model/get-journal-page today-page-name)
-                       today-page (if (nil? today-page-e)
-                                    (state/pub-event! [:page/create today-page-name])
-                                    today-page-e)
-                       insert-to-current-block-page? (and (:block/uuid edit-block) (not pdf-area?))
-                       insert-opts' (if insert-to-current-block-page?
-                                      (assoc insert-opts
-                                             :block-uuid (:block/uuid edit-block)
-                                             :replace-empty-target? true
-                                             :sibling? true)
-                                      (assoc insert-opts :page (:block/uuid today-page)))
-                       new-block (api-insert-new-block! file-name-without-ext insert-opts')]
-                 (when insert-to-current-block-page?
-                   (state/clear-edit!))
-                 (or new-block
-                     (throw (ex-info "Can't save asset" {:files files}))))))))))))
+  (p/let [[repo-dir asset-dir-rpath] (assets-handler/ensure-assets-dir! repo)
+          today-page-name (date/today)
+          today-page-e (db-model/get-journal-page today-page-name)
+          today-page (if (nil? today-page-e)
+                       (state/pub-event! [:page/create today-page-name])
+                       today-page-e)
+          blocks* (p/all
+                   (for [^js file files]
+                     (new-asset-block repo file repo-dir asset-dir-rpath)))
+          blocks (remove nil? blocks*)
+          edit-block (or (state/get-edit-block) last-edit-block)
+          insert-to-current-block-page? (and (:block/uuid edit-block) (not pdf-area?))
+          target (if insert-to-current-block-page?
+                   edit-block
+                   today-page)]
+    (when-not target
+      (throw (ex-info "invalid target" {:files files
+                                        :today-page today-page
+                                        :edit-block edit-block})))
+    (when (seq blocks)
+      (p/do!
+       (ui-outliner-tx/transact!
+        {:outliner-op :insert-blocks}
+        (outliner-op/insert-blocks! blocks target {:keep-uuid? true
+                                                   :sibling? (= edit-block target)
+                                                   :replace-empty-target? true}))
+       (map (fn [b] (db/entity [:block/uuid (:block/uuid b)])) blocks)))))
 
 (def insert-command! editor-common-handler/insert-command!)
 
 (defn db-upload-assets!
   "Paste asset for db graph and insert link to current editing block"
   [repo id ^js files format uploading? drop-or-paste?]
-  (when (or (config/local-file-based-graph? repo)
-            (config/db-based-graph? repo))
+  (when (config/db-based-graph? repo)
+    (insert-command!
+     id
+     ""
+     format
+     {:last-pattern (if drop-or-paste? "" commands/command-trigger)
+      :restore?     true
+      :command      :insert-asset})
     (-> (db-based-save-assets! repo (js->clj files))
-          ;; FIXME: only the first asset is handled
-        (p/then
-         (fn [entities]
-           (let [entity (first entities)]
-             (insert-command!
-              id
-              (ref/->page-ref (:block/uuid entity))
-              format
-              {:last-pattern (if drop-or-paste? "" commands/command-trigger)
-               :restore?     true
-               :command      :insert-asset})
-             entities)))
         (p/catch (fn [e]
                    (js/console.error e)))
         (p/finally

+ 3 - 7
src/main/frontend/handler/paste.cljs

@@ -257,13 +257,9 @@
   (when id
     (let [clipboard-data (gobj/get e "clipboardData")
           files (.-files clipboard-data)]
-      (loop [files files]
-        (when-let [file (first files)]
-          (when-let [block (state/get-edit-block)]
-            (editor-handler/upload-asset! id #js[file]
-                                          (get block :block/format :markdown)
-                                          editor-handler/*asset-uploading? true))
-          (recur (rest files))))
+      (editor-handler/upload-asset! id files
+                                    (get (state/get-edit-block) :block/format :markdown)
+                                    editor-handler/*asset-uploading? true)
       (util/stop e))))
 
 (defn editor-on-paste!

+ 1 - 1
src/main/frontend/handler/plugin.cljs

@@ -631,7 +631,7 @@
   [dirname ^js default]
   (fn [key]
     (when-let [key (and key (name key))]
-      (let [repo ""
+      (let [repo (state/get-current-repo)
             dotroot (get-ls-dotdir-root)
             filepath (util/node-path.join dotroot dirname (str key ".json"))]
         (if (util/electron?)

+ 1 - 1
src/main/frontend/handler/plugin_config.cljs

@@ -55,7 +55,7 @@ when a plugin is installed, updated or removed"
                     (update-vals #(select-keys % common-plugin-keys))
                     pprint/pprint
                     with-out-str)]
-    (fs/create-if-not-exists "" nil (plugin-config-path) content)))
+    (fs/create-if-not-exists (state/get-current-repo) nil (plugin-config-path) content)))
 
 (defn- determine-plugins-to-change
   "Given installed plugins state and plugins from plugins.edn,

+ 2 - 0
src/main/frontend/persist_db/browser.cljs

@@ -104,10 +104,12 @@
 (defn- reload-app-if-old-db-worker-exists
   []
   (when (util/capacitor?)
+    (log/info ::reload-app {:client-id @state/*db-worker-client-id})
     (when-let [client-id @state/*db-worker-client-id]
       (js/navigator.locks.request client-id #js {:mode "exclusive"
                                                  :ifAvailable true}
                                   (fn [lock]
+                                    (log/info ::reload-app-lock {:acquired? (some? lock)})
                                     (when-not lock
                                       (js/window.location.reload)))))))
 

+ 1 - 1
src/main/frontend/worker/db_listener.cljs

@@ -65,7 +65,7 @@
                                       (map (fn [id] [:db/add id :logseq.property.embedding/hnsw-label-updated-at 0])))
           tx-data (concat remove-old-hnsw-tx-data mark-embedding-tx-data)]
       (when (seq tx-data)
-        (ldb/transact! conn tx-data {})))))
+        (ldb/transact! conn tx-data {:skip-validate-db? true})))))
 
 (defn listen-db-changes!
   [repo conn & {:keys [handler-keys]}]

+ 5 - 2
src/main/frontend/worker/db_worker.cljs

@@ -914,12 +914,15 @@
           service)))))
 
 (defn- notify-invalid-data
-  [{:keys [tx-meta]}]
+  [{:keys [tx-meta]} errors]
   ;; don't notify on production when undo/redo failed
   (when-not (and (or (:undo? tx-meta) (:redo? tx-meta))
                  (not worker-util/dev?))
     (shared-service/broadcast-to-clients! :notification
-                                          [["Invalid DB!"] :error])))
+                                          [["Invalid DB!"] :error])
+    (worker-util/post-message :capture-error
+                              {:error (ex-info "Invalid DB" {})
+                               :payload {:errors (str errors)}})))
 
 (defn init
   "web worker entry"

+ 52 - 38
src/main/frontend/worker/pipeline.cljs

@@ -96,44 +96,48 @@
 
 (defn- fix-page-tags
   "Add missing attributes and remove #Page when inserting or updating block/title with inline tags"
-  [{:keys [db-after tx-data tx-meta]}]
-  (when-not (rtc-tx-or-download-graph? tx-meta)
-    (let [page-tag (d/entity db-after :logseq.class/Page)
-          tag (d/entity db-after :logseq.class/Tag)]
-      (assert page-tag "Page tag doesn't exist")
-      (->>
-       (keep
-        (fn [datom]
-          (cond
-            ;; add missing :db/ident and :logseq.property.class/extends for new tag
-            (and (= :block/tags (:a datom))
-                 (:added datom)
-                 (= (:v datom) (:db/id tag)))
-            (let [t (d/entity db-after (:e datom))]
-              (when (and (not (ldb/inline-tag? (:block/raw-title t) tag))
-                         (not (:db/ident t))) ; new tag without db/ident
-                (let [eid (:db/id t)]
-                  [[:db/add eid :db/ident (db-class/create-user-class-ident-from-name db-after (:block/title t))]
-                   [:db/add eid :logseq.property.class/extends :logseq.class/Root]
-                   [:db/retract eid :block/tags :logseq.class/Page]])))
+  [{:keys [db-after tx-data]}]
+  (let [page-tag (d/entity db-after :logseq.class/Page)
+        tag (d/entity db-after :logseq.class/Tag)]
+    (assert page-tag "Page tag doesn't exist")
+    (mapcat
+     (fn [datom]
+       (when (and (= :block/tags (:a datom))
+                  (:added datom))
+         (let [entity (d/entity db-after (:e datom))
+               v-entity (d/entity db-after (:v datom))]
+           (cond
+             ;; add missing :db/ident and :logseq.property.class/extends for new tag
+             (and (= (:v datom) (:db/id tag))
+                  (not (ldb/inline-tag? (:block/raw-title entity) tag))
+                  (not (:db/ident entity)))
+             (let [eid (:db/id entity)]
+               [[:db/add eid :db/ident (db-class/create-user-class-ident-from-name db-after (:block/title entity))]
+                [:db/add eid :logseq.property.class/extends :logseq.class/Root]
+                [:db/retract eid :block/tags :logseq.class/Page]])
 
-            ;; remove #Page from tags/journals/whiteboards, etc.
-            (and (= :block/tags (:a datom))
-                 (:added datom)
-                 (= (:db/id page-tag) (:v datom)))
-            (let [tags (->> (d/entity db-after (:e datom))
-                            :block/tags
-                            (map :db/ident)
-                            (remove #{:logseq.class/Page}))]
-              (when (and (seq tags)
-                         ;; has other page-classes other than `:logseq.class/Page`
-                         (some db-class/page-classes tags))
-                [[:db/retract (:e datom) :block/tags :logseq.class/Page]]))
+             ;; remove #Page from tags/journals/whiteboards, etc.
+             (= (:db/id page-tag) (:v datom))
+             (let [tags (->> entity
+                             :block/tags
+                             (map :db/ident)
+                             (remove #{:logseq.class/Page}))]
+               (when (and (seq tags)
+                          ;; has other page-classes other than `:logseq.class/Page`
+                          (some db-class/page-classes tags))
+                 [[:db/retract (:e datom) :block/tags :logseq.class/Page]]))
 
-            :else
-            nil))
-        tx-data)
-       (apply concat)))))
+             ;; Add other page classes to an existing page
+             ;; Caused by invalid tags data from server
+             ;; TODO: remove this case
+             ;; DEADLINE: 2025-11-30
+             (and (contains? (disj db-class/page-classes :logseq.class/Page) (:db/ident v-entity))
+                  (ldb/internal-page? entity))
+             [[:db/retract (:e datom) :block/tags :logseq.class/Page]]
+
+             :else
+             nil))))
+     tx-data)))
 
 (defn- remove-inline-page-class-from-title
   "Remove inline page tag from title"
@@ -341,6 +345,15 @@
             fix-page-tags-tx-data
             fix-inline-page-tx-data)))
 
+(defn- remove-conflict-datoms
+  [datoms]
+  (->> datoms
+       (group-by (fn [d] (take 4 d))) ; group by '(e a v tx)
+       (keep (fn [[_eavt same-eavt-datoms]]
+               (first (rseq same-eavt-datoms))))
+       ;; sort by :tx, use nth to make this fn works on both vector and datom
+       (sort-by #(nth % 3))))
+
 (defn transact-pipeline
   "Compute extra tx-data and block/refs, should ensure it's a pure function and
   doesn't call `d/transact!` or `ldb/transact!`."
@@ -373,8 +386,9 @@
         replace-tx-report (when (seq block-refs-tx-id-data)
                             (d/with (:db-after tx-report*) block-refs-tx-id-data))
         tx-report' (or replace-tx-report tx-report*)
-        full-tx-data (concat (:tx-data tx-report*)
-                             (:tx-data replace-tx-report))]
+        full-tx-data (-> (concat (:tx-data tx-report*)
+                                 (:tx-data replace-tx-report))
+                         remove-conflict-datoms)]
     (assoc tx-report'
            :tx-data full-tx-data
            :tx-meta tx-meta

+ 0 - 4
src/main/frontend/worker/rtc/full_upload_download_graph.cljs

@@ -375,8 +375,6 @@
          repo init-tx-data
          {:rtc-download-graph? true
           :gen-undo-ops? false
-            ;; only transact db schema, skip validation to avoid warning
-          :skip-validate-db? true
           :persist-op? false}
          (worker-state/get-context))
         (rtc-log-and-state/rtc-log :rtc.log/download {:sub-type :transact-graph-data-to-db-2
@@ -533,8 +531,6 @@
     repo init-tx-data
     {:rtc-download-graph? true
      :gen-undo-ops? false
-      ;; only transact db schema, skip validation to avoid warning
-     :skip-validate-db? true
      :persist-op? false}
     (worker-state/get-context))
    (prn :xxx3 (js/Date.))

+ 5 - 1
src/main/frontend/worker/rtc/remote_update.cljs

@@ -567,7 +567,11 @@ so need to pull earlier remote-data from websocket."})
              title :block/title
              :as op-value} update-page-ops]
       (let [db-ident (:db/ident op-value)]
-        (when-not (and db-ident (d/entity @conn db-ident)) ; property or class exists
+        (when-not (or
+                   ;; property or class exists
+                   (and db-ident (d/entity @conn db-ident))
+                   ;; journal with the same block/uuid exists
+                   (ldb/journal? (d/entity @conn [:block/uuid self])))
           (let [create-opts {:uuid self
                              :old-db-id (@worker-state/*deleted-block-uuid->db-id self)}
                 [_ page-name page-uuid] (worker-page/rtc-create-page! conn config

+ 38 - 0
src/test/frontend/worker/pipeline_test.cljs

@@ -0,0 +1,38 @@
+(ns frontend.worker.pipeline-test
+  (:require [cljs.test :refer [deftest is testing]]
+            [frontend.worker.pipeline :as worker-pipeline]))
+
+(deftest remove-conflict-datoms-test
+  (testing "remove-conflict-datoms (1)"
+    (let [datoms [[1 :a 1 1]
+                  [1 :a 1 1]
+                  [1 :a 2 1]
+                  [2 :a 1 1]]]
+      (is (= (set [[1 :a 1 1]
+                   [1 :a 2 1]
+                   [2 :a 1 1]])
+             (set (#'worker-pipeline/remove-conflict-datoms datoms))))))
+  (testing "check block/tags"
+    (let [datoms [[163 :block/tags 2 536870930 true]
+                  [163 :block/tags 136 536870930 true]
+                  [163 :block/tags 136 536870930 false]]]
+      (is (= (set [[163 :block/tags 2 536870930 true]
+                   [163 :block/tags 136 536870930 false]])
+             (set (#'worker-pipeline/remove-conflict-datoms datoms))))))
+  (testing "check block/refs"
+    (let [datoms [[176 :block/refs 177 536871080 true]
+                  [158 :block/refs 21 536871082 false]
+                  [158 :block/refs 137 536871082 false]
+                  [158 :block/refs 137 536871082 true]
+                  [158 :block/refs 21 536871082 true]
+                  [176 :block/refs 177 536871082 false]
+                  [176 :block/refs 177 536871082 true]
+                  [177 :block/refs 136 536871082 true]
+                  [177 :block/refs 21 536871082 true]]]
+      (is (= (set [[176 :block/refs 177 536871080 true]
+                   [158 :block/refs 137 536871082 true]
+                   [158 :block/refs 21 536871082 true]
+                   [176 :block/refs 177 536871082 true]
+                   [177 :block/refs 136 536871082 true]
+                   [177 :block/refs 21 536871082 true]])
+             (set (#'worker-pipeline/remove-conflict-datoms datoms)))))))