Browse Source

Merge branch 'feat/db' into feat/blocks-action-bar

Tienson Qin 7 months ago
parent
commit
5d2ab65d7c
49 changed files with 808 additions and 552 deletions
  1. 1 1
      .github/workflows/build.yml
  2. 2 2
      bb.edn
  3. 2 1
      deps/db/nbb.edn
  4. 2 1
      deps/db/package.json
  5. 27 13
      deps/db/script/create_graph.cljs
  6. 7 7
      deps/db/script/dump_datoms.cljs
  7. 14 14
      deps/db/script/query.cljs
  8. 13 10
      deps/db/script/validate_db.cljs
  9. 1 1
      deps/db/src/logseq/db.cljs
  10. 6 1
      deps/db/src/logseq/db/frontend/class.cljs
  11. 24 7
      deps/db/src/logseq/db/frontend/property.cljs
  12. 1 1
      deps/db/src/logseq/db/frontend/schema.cljs
  13. 21 15
      deps/db/src/logseq/db/sqlite/build.cljs
  14. 4 2
      deps/db/src/logseq/db/sqlite/create_graph.cljs
  15. 20 12
      deps/db/src/logseq/db/sqlite/export.cljs
  16. 3 3
      deps/db/test/logseq/db/sqlite/create_graph_test.cljs
  17. 23 1
      deps/db/test/logseq/db/sqlite/export_test.cljs
  18. 6 0
      deps/db/test/logseq/db/test_runner.cljs
  19. 1 1
      deps/graph-parser/script/db_import.cljs
  20. 1 1
      deps/graph-parser/src/logseq/graph_parser/block.cljs
  21. 2 1
      deps/graph-parser/src/logseq/graph_parser/db.cljs
  22. 8 8
      deps/outliner/script/transact.cljs
  23. 123 70
      deps/outliner/src/logseq/outliner/core.cljs
  24. 2 1
      deps/outliner/src/logseq/outliner/pipeline.cljs
  25. 25 11
      deps/outliner/src/logseq/outliner/property.cljs
  26. 5 5
      deps/publishing/script/publishing.cljs
  27. 13 24
      deps/shui/src/logseq/shui/popup/core.cljs
  28. 2 2
      resources/css/shui.css
  29. 5 5
      scripts/src/logseq/tasks/db_graph/create_graph_with_large_sizes.cljs
  30. 1 1
      scripts/src/logseq/tasks/db_graph/create_graph_with_properties.cljs
  31. 1 1
      scripts/src/logseq/tasks/db_graph/create_graph_with_schema_org.cljs
  32. 3 4
      src/main/frontend/commands.cljs
  33. 53 43
      src/main/frontend/components/block.cljs
  34. 5 1
      src/main/frontend/components/block.css
  35. 6 3
      src/main/frontend/components/editor.cljs
  36. 1 1
      src/main/frontend/components/header.cljs
  37. 4 4
      src/main/frontend/components/page.cljs
  38. 112 81
      src/main/frontend/components/property/value.cljs
  39. 2 6
      src/main/frontend/components/table.css
  40. 5 3
      src/main/frontend/components/views.cljs
  41. 3 3
      src/main/frontend/db/model.cljs
  42. 5 5
      src/main/frontend/handler/common/page.cljs
  43. 78 59
      src/main/frontend/handler/editor.cljs
  44. 32 25
      src/main/frontend/handler/paste.cljs
  45. 34 28
      src/main/frontend/search.cljs
  46. 6 5
      src/main/frontend/ui.cljs
  47. 3 1
      src/main/frontend/worker/db/migrate.cljs
  48. 43 9
      src/main/frontend/worker/pipeline.cljs
  49. 47 48
      src/test/frontend/modules/outliner/core_test.cljs

+ 1 - 1
.github/workflows/build.yml

@@ -175,7 +175,7 @@ jobs:
         run: cd deps/db && yarn install --frozen-lockfile
 
       - name: Validate created DB graphs
-        run: cd deps/db && yarn nbb-logseq script/validate_client_db.cljs ../../scripts/db-graph-with-props ../../scripts/db-graph-with-schema --closed-maps --group-errors
+        run: cd deps/db && yarn nbb-logseq script/validate_db.cljs ../../scripts/db-graph-with-props ../../scripts/db-graph-with-schema --closed-maps --group-errors
 
   e2e-test:
     # TODO: Re-enable when ready to enable tests for file graphs

+ 2 - 2
bb.edn

@@ -64,7 +64,7 @@
   {:doc "Validate a DB graph's datascript schema"
    :requires ([babashka.fs :as fs])
    :task (apply shell {:dir "deps/db" :extra-env {"ORIGINAL_PWD" (fs/cwd)}}
-                "yarn -s nbb-logseq script/validate_client_db.cljs"
+                "yarn -s nbb-logseq script/validate_db.cljs"
                 *command-line-args*)}
 
   dev:db-query
@@ -81,7 +81,7 @@
   {:doc "Create a DB graph given a sqlite.build EDN file"
    :requires ([babashka.fs :as fs])
    :task (apply shell {:dir "deps/db" :extra-env {"ORIGINAL_PWD" (fs/cwd)}}
-                "yarn -s nbb-logseq -cp src:../outliner/src script/create_graph.cljs" *command-line-args*)}
+                "yarn -s nbb-logseq -cp src:../outliner/src:script script/create_graph.cljs" *command-line-args*)}
 
   dev:db-import
   {:doc "Import a file graph to db graph"

+ 2 - 1
deps/db/nbb.edn

@@ -7,4 +7,5 @@
   logseq/clj-fractional-indexing        {:git/url "https://github.com/logseq/clj-fractional-indexing"
                                          :sha     "7182b7878410f78536dc2b6df35ed32ef9cd6b61"}
   io.github.nextjournal/nbb-test-runner
-  {:git/sha "60ed57aa04bca8d604f5ba6b28848bd887109347"}}}
+  {:git/sha "60ed57aa04bca8d604f5ba6b28848bd887109347"}
+  io.github.pez/baldr {:mvn/version "1.0.9"}}}

+ 2 - 1
deps/db/package.json

@@ -9,6 +9,7 @@
     "better-sqlite3": "9.3.0"
   },
   "scripts": {
-    "test": "yarn nbb-logseq -cp test -m nextjournal.test-runner"
+    "test": "yarn nbb-logseq -cp test -m nextjournal.test-runner",
+    "test-v": "yarn nbb-logseq -cp test -m logseq.db.test-runner"
   }
 }

+ 27 - 13
deps/db/script/create_graph.cljs

@@ -1,14 +1,17 @@
 (ns create-graph
-  "An example script that creates a DB graph given a sqlite.build EDN file"
-  (:require [logseq.outliner.cli :as outliner-cli]
-            [clojure.string :as string]
+  "A script that creates a DB graph given a sqlite.build EDN file"
+  (:require ["fs" :as fs]
+            ["os" :as os]
+            ["path" :as node-path]
+            #_:clj-kondo/ignore
+            [babashka.cli :as cli]
             [clojure.edn :as edn]
+            [clojure.string :as string]
             [datascript.core :as d]
-            ["path" :as node-path]
-            ["os" :as os]
-            ["fs" :as fs]
+            [logseq.outliner.cli :as outliner-cli]
             [nbb.classpath :as cp]
-            [nbb.core :as nbb]))
+            [nbb.core :as nbb]
+            [validate-db]))
 
 (defn- resolve-path
   "If relative path, resolve with $ORIGINAL_PWD"
@@ -17,11 +20,20 @@
     path
     (node-path/join (or js/process.env.ORIGINAL_PWD ".") path)))
 
+(def spec
+  "Options spec"
+  {:help {:alias :h
+          :desc "Print help"}
+   :validate {:alias :v
+              :desc "Validate db after creation"}})
+
 (defn -main [args]
-  (when (not= 2 (count args))
-    (println "Usage: $0 GRAPH-DIR EDN-PATH")
-    (js/process.exit 1))
-  (let [[graph-dir edn-path] args
+  (let [{options :opts args' :args} (cli/parse-args args {:spec spec})
+        [graph-dir edn-path] args'
+        _ (when (or (nil? graph-dir) (nil? edn-path) (:help options))
+            (println (str "Usage: $0 GRAPH-NAME EDN-PATH [OPTIONS]\nOptions:\n"
+                          (cli/format-opts {:spec spec})))
+            (js/process.exit 1))
         [dir db-name] (if (string/includes? graph-dir "/")
                         ((juxt node-path/dirname node-path/basename) graph-dir)
                         [(node-path/join (os/homedir) "logseq" "graphs") graph-dir])
@@ -34,7 +46,9 @@
     ;; (cljs.pprint/pprint _txs)
     (d/transact! conn init-tx)
     (d/transact! conn block-props-tx)
-    (println "Created graph" (str db-name "!"))))
+    (println "Created graph" (str db-name "!"))
+    (when (:validate options)
+      (validate-db/validate-db @conn db-name {:group-errors true :closed-maps true :humanize true}))))
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))

+ 7 - 7
deps/db/script/dump_datoms.cljs

@@ -1,14 +1,14 @@
   (ns dump-datoms
-    "An example script that dumps all eavt datoms to a specified edn file
+    "A script that dumps all eavt datoms to a specified edn file
 
      $ yarn -s nbb-logseq script/dump_datoms.cljs db-name datoms.edn"
-    (:require [datascript.core :as d]
+    (:require ["fs" :as fs]
+              ["os" :as os]
+              ["path" :as path]
               [clojure.pprint :as pprint]
+              [datascript.core :as d]
               [logseq.db.sqlite.cli :as sqlite-cli]
-              [nbb.core :as nbb]
-              ["path" :as path]
-              ["os" :as os]
-              ["fs" :as fs]))
+              [nbb.core :as nbb]))
 
 (defn read-graph
   "The db graph bare version of gp-cli/parse-graph"
@@ -28,5 +28,5 @@
     (println "Writing" (count datoms) "datoms to" file)
     (fs/writeFileSync file (with-out-str (pprint/pprint datoms)))))
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))

+ 14 - 14
deps/db/script/query.cljs

@@ -1,18 +1,18 @@
 (ns query
-  "An example script that queries any db graph from the commandline e.g.
+  "A script that queries any db graph from the commandline e.g.
 
   $ yarn -s nbb-logseq script/query.cljs db-name '[:find (pull ?b [:block/name :block/title]) :where [?b :block/created-at]]'"
-  (:require [datascript.core :as d]
+  (:require ["child_process" :as child-process]
+            ["os" :as os]
+            ["path" :as node-path]
+            [babashka.cli :as cli]
             [clojure.edn :as edn]
-            [logseq.db.sqlite.cli :as sqlite-cli]
-            [logseq.db.frontend.rules :as rules]
-            [nbb.core :as nbb]
-            [clojure.string :as string]
             [clojure.pprint :as pprint]
-            [babashka.cli :as cli]
-            ["child_process" :as child-process]
-            ["path" :as node-path]
-            ["os" :as os]))
+            [clojure.string :as string]
+            [datascript.core :as d]
+            [logseq.db.frontend.rules :as rules]
+            [logseq.db.sqlite.cli :as sqlite-cli]
+            [nbb.core :as nbb]))
 
 (defn- sh
   "Run shell cmd synchronously and print to inherited streams by default. Aims
@@ -44,8 +44,8 @@
             :desc "Lookup entities instead of query"}})
 
 (defn -main [args]
-  (let [[graph-dir & args'] args
-        options (cli/parse-opts args' {:spec spec})
+  (let [{options :opts args' :args} (cli/parse-args args {:spec spec})
+        [graph-dir & args''] args'
         _ (when (or (nil? graph-dir) (:help options))
             (println (str "Usage: $0 GRAPH-NAME [& ARGS] [OPTIONS]\nOptions:\n"
                           (cli/format-opts {:spec spec})))
@@ -60,7 +60,7 @@
                             (update :block/properties (fn [props] (map (fn [m] (into {} m)) props)))))
                        (:entity options))
                   ;; assumes no :in are in queries
-                  (let [query (into (edn/read-string (first args')) [:in '$ '%])
+                  (let [query (into (edn/read-string (first args'')) [:in '$ '%])
                         res (d/q query @conn (rules/extract-rules rules/db-query-dsl-rules))]
                     ;; Remove nesting for most queries which just have one :find binding
                     (if (= 1 (count (first res))) (mapv first res) res)))]
@@ -71,5 +71,5 @@
         (sh ["puget"] {:input (pr-str results) :stdio ["pipe" "inherit" "inherit"]})
         (pprint/pprint results)))))
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))

+ 13 - 10
deps/db/script/validate_client_db.cljs → deps/db/script/validate_db.cljs

@@ -1,4 +1,4 @@
-(ns validate-client-db
+(ns validate-db
   "Script that validates the datascript db of a DB graph
    NOTE: This script is also used in CI to confirm our db's schema is up to date"
   (:require ["os" :as os]
@@ -14,7 +14,7 @@
             [malli.error :as me]
             [nbb.core :as nbb]))
 
-(defn validate-client-db
+(defn validate-db*
   "Validate datascript db as a vec of entity maps"
   [db ent-maps* {:keys [verbose group-errors humanize closed-maps]}]
   (let [ent-maps (db-malli-schema/update-properties-in-ents db ent-maps*)
@@ -66,6 +66,14 @@
                   :default true
                   :desc "Groups errors by their entity id"}})
 
+(defn validate-db [db db-name options]
+  (let [datoms (d/datoms db :eavt)
+        ent-maps (db-malli-schema/datoms->entities datoms)]
+    (println "Read graph" (str db-name " with counts: "
+                               (pr-str (assoc (db-validate/graph-counts db ent-maps)
+                                              :datoms (count datoms)))))
+    (validate-db* db ent-maps options)))
+
 (defn- validate-graph [graph-dir options]
   (let [[dir db-name] (if (string/includes? graph-dir "/")
                         (let [graph-dir'
@@ -75,13 +83,8 @@
         conn (try (sqlite-cli/open-db! dir db-name)
                   (catch :default e
                     (println "Error: For graph" (str (pr-str graph-dir) ":") (str e))
-                    (js/process.exit 1)))
-        datoms (d/datoms @conn :eavt)
-        ent-maps (db-malli-schema/datoms->entities datoms)]
-    (println "Read graph" (str db-name " with counts: "
-                               (pr-str (assoc (db-validate/graph-counts @conn ent-maps)
-                                              :datoms (count datoms)))))
-    (validate-client-db @conn ent-maps options)))
+                    (js/process.exit 1)))]
+    (validate-db @conn db-name options)))
 
 (defn -main [argv]
   (let [{:keys [args opts]} (cli/parse-args argv {:spec spec})
@@ -92,5 +95,5 @@
     (doseq [graph-dir args]
       (validate-graph graph-dir opts))))
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))

+ 1 - 1
deps/db/src/logseq/db.cljs

@@ -334,7 +334,7 @@
      (sort-by-order (:block/_parent parent)))))
 
 (defn get-block-parents
-  [db block-id {:keys [depth] :or {depth 100}}]
+  [db block-id & {:keys [depth] :or {depth 100}}]
   (loop [block-id block-id
          parents' (list)
          d 1]

+ 6 - 1
deps/db/src/logseq/db/frontend/class.cljs

@@ -80,6 +80,11 @@
                             :logseq.property.pdf/hl-type :logseq.property.pdf/hl-image]
                :required-properties [:logseq.property/ls-type :logseq.property.pdf/hl-color :logseq.property/asset
                                      :logseq.property.pdf/hl-page :logseq.property.pdf/hl-value]}}
+
+     :logseq.class/Template
+     {:title "Template"
+      :schema {:properties [:logseq.property/template-applied-to]}}
+
      ;; TODO: Add more classes such as :book, :paper, :movie, :music, :project)
      )))
 
@@ -133,4 +138,4 @@
 (defn logseq-class?
   "Determines if keyword is a logseq class"
   [kw]
-  (= logseq-class (namespace kw)))
+  (= logseq-class (namespace kw)))

+ 24 - 7
deps/db/src/logseq/db/frontend/property.cljs

@@ -74,8 +74,7 @@
                             :attribute :block/tags
                             :schema {:type :class
                                      :cardinality :many
-                                     :public? true
-                                     :classes #{:logseq.class/Root}}
+                                     :public? true}
                             :queryable? true}
      :block/parent         {:title "Node parent"
                             :attribute :block/parent
@@ -154,7 +153,9 @@
                               :schema {:type :node
                                        :public? true
                                        :view-context :page}
-                              :queryable? true}
+                              :queryable? true
+                              :properties
+                              {:logseq.property/description "Provides parent-child relationships between nodes. For tags this enables inheritance and for pages this enables namespaces."}}
      :logseq.property/default-value {:title "Default value"
                                      :schema {:type :entity
                                               :public? false
@@ -173,7 +174,9 @@
      :logseq.property/hide-empty-value {:title "Hide empty value"
                                         :schema {:type :checkbox
                                                  :public? true
-                                                 :view-context :property}}
+                                                 :view-context :property}
+                                        :properties
+                                        {:logseq.property/description "Hides a property's value on any node when empty e.g. when a property appears on a node through a tag."}}
      :logseq.property.class/hide-from-node {:title "Hide from Node"
                                             :schema {:type :checkbox
                                                      :public? true
@@ -187,7 +190,9 @@
                                  :schema {:type :page
                                           :public? true
                                           :view-context :page
-                                          :cardinality :many}}
+                                          :cardinality :many}
+                                 :properties
+                                 {:logseq.property/description "Provides a way for a page to associate to another page i.e. backward compatible tagging."}}
      :logseq.property/background-color {:title "Background color"
                                         :schema {:type :default :hide? true}}
      :logseq.property/background-image {:title "Background image"
@@ -549,7 +554,9 @@
      :logseq.property/enable-history? {:title "Enable property history"
                                        :schema {:type :checkbox
                                                 :public? true
-                                                :view-context :property}}
+                                                :view-context :property}
+                                       :properties
+                                       {:logseq.property/description "Records history anytime a property's value changes on a node."}}
      :logseq.property.history/block {:title "History block"
                                      :schema {:type :entity
                                               :hide? true}}
@@ -567,7 +574,17 @@
                                            ;; - avoid losing this attr when the user-block is deleted
                                            ;; - related user-block maybe not exists yet in graph
                                            :type :string
-                                           :hide? true}})))
+                                           :hide? true}}
+     :logseq.property/used-template {:title "Used template"
+                                     :schema {:type :node
+                                              :public? false
+                                              :hide? true
+                                              :classes #{:logseq.class/Template}}}
+     :logseq.property/template-applied-to {:title "Apply template to tags"
+                                           :schema {:type :class
+                                                    :cardinality :many
+                                                    :public? true}
+                                           :queryable? true})))
 
 (def built-in-properties
   (->> built-in-properties*

+ 1 - 1
deps/db/src/logseq/db/frontend/schema.cljs

@@ -37,7 +37,7 @@
          (map (juxt :major :minor)
               [(parse-schema-version x) (parse-schema-version y)])))
 
-(def version (parse-schema-version "64.2"))
+(def version (parse-schema-version "64.3"))
 
 (defn major-version
   "Return a number.

+ 21 - 15
deps/db/src/logseq/db/sqlite/build.cljs

@@ -183,14 +183,14 @@
              {:property-attributes
               (merge {:db/id (or (property-db-ids prop-name)
                                  (throw (ex-info "No :db/id for property" {:property prop-name})))}
-                     (select-keys prop-m [:build/properties-ref-types]))}))
+                     (select-keys prop-m [:build/properties-ref-types :block/created-at :block/updated-at]))}))
           [(merge (sqlite-util/build-new-property (get-ident all-idents prop-name)
                                                   (db-property/get-property-schema prop-m)
                                                   {:block-uuid (:block/uuid prop-m)
                                                    :title (:block/title prop-m)})
                   {:db/id (or (property-db-ids prop-name)
                               (throw (ex-info "No :db/id for property" {:property prop-name})))}
-                  (select-keys prop-m [:build/properties-ref-types]))])
+                  (select-keys prop-m [:build/properties-ref-types :block/created-at :block/updated-at]))])
         pvalue-tx-m
         (->property-value-tx-m new-block (:build/properties prop-m) properties all-idents)]
     (cond-> []
@@ -344,7 +344,7 @@
                                              (mapcat (fn [m]
                                                        (if-let [pvalue-pages
                                                                 (->> (vals (:build/properties m))
-                                                                     (mapcat #(if (set? %) % [%]) )
+                                                                     (mapcat #(if (set? %) % [%]))
                                                                      (filter page-prop-value?)
                                                                      (map second)
                                                                      seq)]
@@ -415,8 +415,8 @@
   (vec
    (mapcat
     (fn [{:keys [page blocks]}]
-      (let [ignore-page-tx? (and build-existing-tx? (not (::new-page? (meta page))) (not (:build/keep-uuid? page)))
-            page' (if ignore-page-tx?
+      (let [build-existing-tx?' (and build-existing-tx? (not (::new-page? (meta page))) (not (:build/keep-uuid? page)))
+            page' (if build-existing-tx?'
                     page
                     (merge
                      ;; TODO: Use sqlite-util/build-new-page
@@ -430,8 +430,8 @@
                           page-id-fn)]
         (into
          ;; page tx
-         (if ignore-page-tx?
-           []
+         (if build-existing-tx?'
+           [(select-keys page [:block/uuid :block/created-at :block/updated-at])]
            (build-page-tx page' all-idents page-uuids properties))
          ;; blocks tx
          (reduce (fn [acc m]
@@ -618,12 +618,18 @@
                                  m))
                              properties-tx)
         pages-and-blocks-tx (build-pages-and-blocks-tx pages-and-blocks' all-idents page-uuids
-                                                       (assoc options :properties properties))]
-    ;; Properties first b/c they have schema and are referenced by all. Then classes b/c they can be referenced by pages. Then pages
-    (split-blocks-tx (concat properties-tx'
-                             classes-tx
-                             pages-and-blocks-tx)
-                     properties)))
+                                                       (assoc options :properties properties))
+        ;; Properties first b/c they have schema and are referenced by all. Then
+        ;; classes b/c they can be referenced by pages. Then pages
+        split-txs (split-blocks-tx (concat properties-tx' classes-tx pages-and-blocks-tx)
+                                   properties)]
+    (cond-> split-txs
+      ;; Just add indices option as there are too many out of order uuid cases with importing user content
+      (:build-existing-tx? options)
+      (update :init-tx
+              (fn [init-tx]
+                (let [indices (mapv #(select-keys % [:block/uuid]) (filter :block/uuid init-tx))]
+                  (into indices init-tx)))))))
 
 ;; Public API
 ;; ==========
@@ -701,8 +707,8 @@
     See auto-create-ontology for more details
   * :build-existing-tx? - When set to true, blocks, pages, properties and classes with :block/uuid are treated as
      existing in DB and are skipped for creation. This is useful for building tx on existing DBs e.g. for importing.
-     Blocks are updated with any attributes passed to it while all other node types are ignored for update unless
-     :build/keep-uuid? is set.
+     Blocks and pages are updated with any attributes passed to it while all other node types are ignored for update
+     unless :build/keep-uuid? is set.
   * :page-id-fn - custom fn that returns ent lookup id for page refs e.g. `[:block/uuid X]`
     Default is :db/id
 

+ 4 - 2
deps/db/src/logseq/db/sqlite/create_graph.cljs

@@ -105,7 +105,9 @@
   as built-in?. Returns their tx data as well as data needed for subsequent build steps"
   []
   ;; bootstrap-idents must either be necessary to define a property or be used on every property
-  (let [bootstrap-idents #{:logseq.property/type :logseq.property/hide? :logseq.property/built-in?}
+  (let [bootstrap-idents #{:logseq.property/type :logseq.property/hide? :logseq.property/built-in?
+                           ;; Required to define :properties on a property
+                           :logseq.property/created-from-property}
         bootstrap-properties (map build-bootstrap-property bootstrap-idents)
         ;; First tx bootstrap properties so they can take affect. Then tx the bootstrap properties on themselves
         bootstrap-properties-tx (into (mapv #(apply dissoc % bootstrap-idents) bootstrap-properties)
@@ -226,7 +228,7 @@
         hidden-pages (concat (build-initial-views) (build-favorites-page))
         ;; These classes bootstrap our tags and properties as they depend on each other e.g.
         ;; Root <-> Tag, classes-tx depends on logseq.property/parent, properties-tx depends on Property
-        bootstrap-class? (fn [c] (contains? #{:logseq.class/Root :logseq.class/Property :logseq.class/Tag} (:db/ident c)))
+        bootstrap-class? (fn [c] (contains? #{:logseq.class/Root :logseq.class/Property :logseq.class/Tag :logseq.class/Template} (:db/ident c)))
         bootstrap-classes (filter bootstrap-class? default-classes)
         bootstrap-class-ids (map #(select-keys % [:db/ident :block/uuid]) bootstrap-classes)
         classes-tx (concat (map #(dissoc % :db/ident) bootstrap-classes)

+ 20 - 12
deps/db/src/logseq/db/sqlite/export.cljs

@@ -10,6 +10,7 @@
             [logseq.db.frontend.content :as db-content]
             [logseq.db.frontend.entity-plus :as entity-plus]
             [logseq.db.frontend.entity-util :as entity-util]
+            [logseq.db.frontend.malli-schema :as db-malli-schema]
             [logseq.db.frontend.property :as db-property]
             [logseq.db.sqlite.build :as sqlite-build]))
 
@@ -48,8 +49,11 @@
         [:build/page {:build/journal (:block/journal-day pvalue)}]
         :else
         (if (= :node (:logseq.property/type property-ent))
-          ;; Have to distinguish from block references that don't exist like closed values
-          ^::existing-property-value? [:block/uuid (:block/uuid pvalue)]
+          ;; Internal idents take precedence over uuid because they are keep data graph-agnostic
+          (if (some-> pvalue :db/ident db-malli-schema/internal-ident?)
+            (:db/ident pvalue)
+            ;; Use metadata distinguish from block references that don't exist like closed values
+            ^::existing-property-value? [:block/uuid (:block/uuid pvalue)])
           (or (:db/ident pvalue)
               ;; nbb-compatible version of db-property/property-value-content
               (or (block-title pvalue)
@@ -177,16 +181,16 @@
 (defn- build-node-export
   "Given a block/page entity and optional existing properties, build an export map of its
    tags and properties"
-  [db entity {:keys [properties include-uuid-fn keep-uuid? shallow-copy?]
+  [db entity {:keys [properties include-uuid-fn shallow-copy?]
               :or {include-uuid-fn (constantly false)}}]
   (let [ent-properties (dissoc (db-property/properties entity) :block/tags)
         build-tags (when (seq (:block/tags entity)) (->build-tags (:block/tags entity)))
         new-properties (when-not shallow-copy? (build-node-properties db entity ent-properties properties))
         build-node (cond-> {:block/title (block-title entity)}
+                     (:block/link entity)
+                     (assoc :block/link [:block/uuid (:block/uuid (:block/link entity))])
                      (include-uuid-fn (:block/uuid entity))
                      (assoc :block/uuid (:block/uuid entity) :build/keep-uuid? true)
-                     keep-uuid?
-                     (assoc :build/keep-uuid? true)
                      (and (not shallow-copy?) (seq build-tags))
                      (assoc :build/tags build-tags)
                      (and (not shallow-copy?) (seq ent-properties))
@@ -247,12 +251,16 @@
       classes (assoc :classes classes))))
 
 (defn- build-content-ref-export
-  "Builds an export config (and additional info) for refs in the given blocks. All the exported
+  "Builds an export config (and additional info) for refs in the given blocks. Refs are detected
+   if they are a :block/link or if a `[[UUID]]` ref in the content. All the exported
    entities found in block refs include their uuid in order to preserve the relationship to the blocks"
   [db blocks*]
   (let [;; Remove property value blocks that can't have content refs
         blocks (remove :logseq.property/value blocks*)
-        content-ref-uuids (set (mapcat (comp db-content/get-matched-ids block-title) blocks))
+        block-links (->> (filter :block/link blocks)
+                         (map #(:block/uuid (:block/link %))))
+        content-ref-uuids (into (set (mapcat (comp db-content/get-matched-ids block-title) blocks))
+                                block-links)
         content-ref-ents (map #(d/entity db [:block/uuid %]) content-ref-uuids)
         content-ref-pages (filter #(or (entity-util/internal-page? %) (entity-util/journal? %)) content-ref-ents)
         {:keys [properties classes]}
@@ -326,7 +334,6 @@
                       (merge (build-blocks-export db
                                                   (sort-by :block/order blocks)
                                                   {:include-uuid-fn (constantly true)
-                                                   :keep-uuid? true
                                                    ;; shallow copy to disallow failing pvalues
                                                    :shallow-copy? true})
                              {:page (shallow-copy-page parent-page-ent)})))))]
@@ -485,11 +492,12 @@
                      (mapcat #(sqlite-build/extract-from-blocks (:blocks %) (fn [m] (some-> m :block/uuid vector)))
                              pages-and-blocks))
              set)
+        ;; only looks one-level deep in properties e.g. not inside :build/page
         ref-uuids
-        (->> (concat (mapcat (comp get-pvalue-uuids :build/properties) (vals classes))
-                     (mapcat (comp get-pvalue-uuids :build/properties) (vals properties))
-                     (mapcat (comp get-pvalue-uuids :build/properties :page) pages-and-blocks)
-                     (mapcat #(sqlite-build/extract-from-blocks (:blocks %) (comp get-pvalue-uuids :build/properties)) pages-and-blocks))
+        (->> (concat (mapcat get-pvalue-uuids (vals classes))
+                     (mapcat get-pvalue-uuids (vals properties))
+                     (mapcat (comp get-pvalue-uuids :page) pages-and-blocks)
+                     (mapcat #(sqlite-build/extract-from-blocks (:blocks %) get-pvalue-uuids) pages-and-blocks))
              set)]
     (set/difference ref-uuids known-uuids)))
 

+ 3 - 3
deps/db/test/logseq/db/sqlite/create_graph_test.cljs

@@ -163,9 +163,9 @@
                                        :block/order ;; TODO: block/order should be same as well
                                        ))
                      init-data*)))]
-      (let [[first-only second-only common]
+      (let [[first-only second-only _common]
             (data/diff (remove-ignored-attrs&entities (sqlite-create-graph/build-db-initial-data ""))
                        (remove-ignored-attrs&entities (sqlite-create-graph/build-db-initial-data "")))]
+        ;; (pr-str [first-only second-only _common])
         (is (and (every? nil? first-only)
-                 (every? nil? second-only))
-            (pr-str [first-only second-only common]))))))
+                 (every? nil? second-only)))))))

+ 23 - 1
deps/db/test/logseq/db/sqlite/export_test.cljs

@@ -311,6 +311,27 @@
                                       (into (vec (remove #(= "hola" (:block/title %)) bs))
                                             bs))})))
 
+(deftest import-page-with-block-links
+  (let [block-uuid (random-uuid)
+        original-data
+        {:pages-and-blocks
+         [{:page {:block/title "page1"}
+           :blocks [{:block/title "b1" :block/uuid block-uuid :build/keep-uuid? true}
+                    {:block/title "" :block/link [:block/uuid block-uuid]}]}]}
+        ;; add option to test out of order uuids
+        conn (db-test/create-conn-with-blocks (assoc original-data :build-existing-tx? true))
+        conn2 (db-test/create-conn)
+        imported-page (export-page-and-import-to-another-graph conn conn2 "page1")]
+    (is (= (:pages-and-blocks original-data) (:pages-and-blocks imported-page))
+        "Page's blocks are imported")
+
+    (import-second-time-assertions conn conn2 "page1" original-data
+                                   {:transform-expected-blocks
+                                    (fn [bs]
+                                      ;; internal referenced block doesn't get copied b/c it already exists
+                                      (into (vec (remove #(= "b1" (:block/title %)) bs))
+                                            bs))})))
+
 (deftest import-page-with-different-page-and-classes
   (let [original-data
         {:properties {:user.property/p1 {:logseq.property/type :default}
@@ -392,7 +413,8 @@
                     {:block/title "node block"
                      :build/properties {:user.property/node #{[:build/page {:block/title "page object"
                                                                             :build/tags [:user.class/MyClass]}]
-                                                              [:block/uuid block-object-uuid]}}}]}
+                                                              [:block/uuid block-object-uuid]
+                                                              :logseq.class/Task}}}]}
           {:page {:block/title "Blocks"}
            :blocks [{:block/title "myclass object"
                      :build/tags [:user.class/MyClass]

+ 6 - 0
deps/db/test/logseq/db/test_runner.cljs

@@ -0,0 +1,6 @@
+(ns logseq.db.test-runner
+  "Test runner which enables https://github.com/PEZ/baldr by default"
+  (:require [nextjournal.test-runner :as next-runner]
+            [pez.baldr]))
+
+(def -main next-runner/-main)

+ 1 - 1
deps/graph-parser/script/db_import.cljs

@@ -189,5 +189,5 @@
       (when (:verbose options') (println "Transacted" (count (d/datoms @conn :eavt)) "datoms"))
       (println "Created graph" (str db-name "!")))))
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))

+ 1 - 1
deps/graph-parser/src/logseq/graph_parser/block.cljs

@@ -561,7 +561,7 @@
 (defn- macro->block
   "macro: {:name \"\" arguments [\"\"]}"
   [macro]
-  {:block/uuid (random-uuid)
+  {:block/uuid (common-uuid/gen-uuid)
    :block/type "macro"
    :block/properties {:logseq.macro-name (:name macro)
                       :logseq.macro-arguments (:arguments macro)}})

+ 2 - 1
deps/graph-parser/src/logseq/graph_parser/db.cljs

@@ -4,6 +4,7 @@
             [clojure.string :as string]
             [datascript.core :as d]
             [logseq.common.util :as common-util]
+            [logseq.common.uuid :as common-uuid]
             [logseq.db :as ldb]
             [logseq.db.file-based.schema :as file-schema]))
 
@@ -23,7 +24,7 @@
   [title]
   {:block/name (string/lower-case title)
    :block/title title
-   :block/uuid (random-uuid)
+   :block/uuid (common-uuid/gen-uuid)
    :block/type "page"})
 
 (def built-in-pages

+ 8 - 8
deps/outliner/script/transact.cljs

@@ -1,14 +1,14 @@
 (ns transact
   "This script generically runs transactions against the queried blocks"
-  (:require [logseq.outliner.db-pipeline :as db-pipeline]
-            [logseq.db.sqlite.cli :as sqlite-cli]
-            [logseq.db.frontend.rules :as rules]
-            [datascript.core :as d]
+  (:require ["os" :as os]
+            ["path" :as node-path]
             [clojure.edn :as edn]
             [clojure.string :as string]
-            [nbb.core :as nbb]
-            ["path" :as node-path]
-            ["os" :as os]))
+            [datascript.core :as d]
+            [logseq.db.frontend.rules :as rules]
+            [logseq.db.sqlite.cli :as sqlite-cli]
+            [logseq.outliner.db-pipeline :as db-pipeline]
+            [nbb.core :as nbb]))
 
 (defn -main [args]
   (when (< (count args) 3)
@@ -37,5 +37,5 @@
         (d/transact! conn update-tx)
         (println "Updated" (count update-tx) "block(s) for graph" (str db-name "!"))))))
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))

+ 123 - 70
deps/outliner/src/logseq/outliner/core.cljs

@@ -2,9 +2,12 @@
   "Provides the primary outliner operations and fns"
   (:require [clojure.set :as set]
             [clojure.string :as string]
+            [clojure.walk :as walk]
             [datascript.core :as d]
             [datascript.impl.entity :as de :refer [Entity]]
             [logseq.common.util :as common-util]
+            [logseq.common.util.page-ref :as page-ref]
+            [logseq.common.uuid :as common-uuid]
             [logseq.db :as ldb]
             [logseq.db.common.order :as db-order]
             [logseq.db.file-based.schema :as file-schema]
@@ -497,22 +500,62 @@
      {}
      block)))
 
+(defn- build-insert-blocks-tx
+  [db target-block blocks uuids get-new-id {:keys [sibling? outliner-op replace-empty-target? insert-template? keep-block-order?]}]
+  (let [block-ids (set (map :block/uuid blocks))
+        target-page (or (:db/id (:block/page target-block))
+                        ;; target block is a page itself
+                        (:db/id target-block))
+        orders (get-block-orders blocks target-block sibling? keep-block-order?)]
+    (map-indexed (fn [idx {:block/keys [parent] :as block}]
+                   (when-let [uuid' (get uuids (:block/uuid block))]
+                     (let [top-level? (= (:block/level block) 1)
+                           parent (compute-block-parent block parent target-block top-level? sibling? get-new-id outliner-op replace-empty-target? idx)
+
+                           order (nth orders idx)
+                           _ (assert (and parent order) (str "Parent or order is nil: " {:parent parent :order order}))
+                           template-ref-block-ids (when insert-template?
+                                                    (when-let [block (d/entity db (:db/id block))]
+                                                      (let [ref-ids (set (map :block/uuid (:block/refs block)))]
+                                                        (->> (set/intersection block-ids ref-ids)
+                                                             (remove #{(:block/uuid block)})))))
+                           m {:db/id (:db/id block)
+                              :block/uuid uuid'
+                              :block/page target-page
+                              :block/parent parent
+                              :block/order order}
+                           result (->
+                                   (if (de/entity? block)
+                                     (assoc m :block/level (:block/level block))
+                                     (merge block m))
+                                   (update :block/title (fn [value]
+                                                          (if (seq template-ref-block-ids)
+                                                            (reduce
+                                                             (fn [value id]
+                                                               (string/replace value
+                                                                               (page-ref/->page-ref id)
+                                                                               (page-ref/->page-ref (uuids id))))
+                                                             value
+                                                             template-ref-block-ids)
+                                                            value)))
+                                   (dissoc :db/id))]
+                       (update-property-ref-when-paste result uuids))))
+                 blocks)))
+
 (defn- insert-blocks-aux
-  [blocks target-block {:keys [sibling? replace-empty-target? keep-uuid? keep-block-order? outliner-op]}]
+  [db blocks target-block {:keys [replace-empty-target? keep-uuid?]
+                           :as opts}]
   (let [block-uuids (map :block/uuid blocks)
         uuids (zipmap block-uuids
                       (if keep-uuid?
                         block-uuids
-                        (repeatedly random-uuid)))
+                        (repeatedly common-uuid/gen-uuid)))
         uuids (if (and (not keep-uuid?) replace-empty-target?)
                 (assoc uuids (:block/uuid (first blocks)) (:block/uuid target-block))
                 uuids)
         id->new-uuid (->> (map (fn [block] (when-let [id (:db/id block)]
                                              [id (get uuids (:block/uuid block))])) blocks)
                           (into {}))
-        target-page (or (:db/id (:block/page target-block))
-                        ;; target block is a page itself
-                        (:db/id target-block))
         get-new-id (fn [block lookup]
                      (cond
                        (or (map? lookup) (vector? lookup) (de/entity? lookup))
@@ -526,26 +569,9 @@
 
                        :else
                        (throw (js/Error. (str "[insert-blocks] illegal lookup: " lookup ", block: " block)))))
-        orders (get-block-orders blocks target-block sibling? keep-block-order?)]
-    (map-indexed (fn [idx {:block/keys [parent] :as block}]
-                   (when-let [uuid' (get uuids (:block/uuid block))]
-                     (let [top-level? (= (:block/level block) 1)
-                           parent (compute-block-parent block parent target-block top-level? sibling? get-new-id outliner-op replace-empty-target? idx)
-
-                           order (nth orders idx)
-                           _ (assert (and parent order) (str "Parent or order is nil: " {:parent parent :order order}))
-                           m {:db/id (:db/id block)
-                              :block/uuid uuid'
-                              :block/page target-page
-                              :block/parent parent
-                              :block/order order}
-                           result (->
-                                   (if (de/entity? block)
-                                     (assoc m :block/level (:block/level block))
-                                     (merge block m))
-                                   (dissoc :db/id))]
-                       (update-property-ref-when-paste result uuids))))
-                 blocks)))
+        blocks-tx (build-insert-blocks-tx db target-block blocks uuids get-new-id opts)]
+    {:blocks-tx blocks-tx
+     :id->new-uuid id->new-uuid}))
 
 (defn- get-target-block
   [db blocks target-block {:keys [outliner-op indent? sibling? up?]}]
@@ -614,7 +640,7 @@
               m' (vec (conj m block))]
           (recur m' (rest blocks)))))))
 
-(defn- ^:large-vars/cleanup-todo insert-blocks
+(defn ^:api ^:large-vars/cleanup-todo insert-blocks
   "Insert blocks as children (or siblings) of target-node.
   Args:
     `conn`: db connection.
@@ -634,12 +660,27 @@
     ``"
   [repo conn blocks target-block {:keys [_sibling? keep-uuid? keep-block-order?
                                          outliner-op replace-empty-target? update-timestamps?
-                                         created-by]
+                                         created-by insert-template?]
                                   :as opts
                                   :or {update-timestamps? true}}]
   {:pre [(seq blocks)
          (m/validate block-map-or-entity target-block)]}
-  (let [[target-block sibling?] (get-target-block @conn blocks target-block opts)
+  (let [blocks (keep (fn [b]
+                       (if-let [eid (or (:db/id b)
+                                        (when-let [id (:block/uuid b)]
+                                          [:block/uuid id]))]
+                         (->
+                          (if-let [e (if (de/entity? b) b (d/entity @conn eid))]
+                            (merge
+                             (into {} e)
+                             {:db/id (:db/id e)
+                              :block/title (or (:block/raw-title e) (:block/title e))}
+                             b)
+                            b)
+                          (dissoc :block/tx-id :block/refs :block/path-refs))
+                         b))
+                     blocks)
+        [target-block sibling?] (get-target-block @conn blocks target-block opts)
         _ (assert (some? target-block) (str "Invalid target: " target-block))
         sibling? (if (ldb/page? target-block) false sibling?)
         replace-empty-target? (if (and (some? replace-empty-target?)
@@ -650,48 +691,60 @@
                                      (:block/title target-block)
                                      (string/blank? (:block/title target-block))
                                      (> (count blocks) 1)))
-        db-based? (sqlite-util/db-based-graph? repo)
-        blocks' (let [blocks' (blocks-with-level blocks)]
-                  (cond->> (blocks-with-ordered-list-props repo blocks' target-block sibling?)
-                    update-timestamps?
-                    (mapv #(dissoc % :block/created-at :block/updated-at))
-                    true
-                    (mapv block-with-timestamps)
-                    db-based?
-                    (mapv #(-> %
-                               (dissoc :block/properties)
-                               (update-property-created-by created-by)))))
-        insert-opts {:sibling? sibling?
-                     :replace-empty-target? replace-empty-target?
-                     :keep-uuid? keep-uuid?
-                     :keep-block-order? keep-block-order?
-                     :outliner-op outliner-op}
-        tx' (insert-blocks-aux blocks' target-block insert-opts)]
-    (if (some (fn [b] (or (nil? (:block/parent b)) (nil? (:block/order b)))) tx')
-      (throw (ex-info "Invalid outliner data"
-                      {:opts insert-opts
-                       :tx (vec tx')
-                       :blocks (vec blocks)
-                       :target-block target-block}))
-      (let [uuids-tx (->> (map :block/uuid tx')
-                          (remove nil?)
-                          (map (fn [uuid'] {:block/uuid uuid'})))
-            tx (assign-temp-id tx' replace-empty-target? target-block)
-            from-property (:logseq.property/created-from-property target-block)
-            property-values-tx (when (and sibling? from-property)
-                                 (let [top-level-blocks (filter #(= 1 (:block/level %)) blocks')]
-                                   (mapcat (fn [block]
-                                             [{:block/uuid (:block/uuid block)
-                                               :logseq.property/created-from-property (:db/id from-property)}
-                                              [:db/add
-                                               (:db/id (:block/parent target-block))
-                                               (:db/ident (d/entity @conn (:db/id from-property)))
-                                               [:block/uuid (:block/uuid block)]]]) top-level-blocks)))
-            full-tx (common-util/concat-without-nil (if (and keep-uuid? replace-empty-target?) (rest uuids-tx) uuids-tx)
-                                                    tx
-                                                    property-values-tx)]
-        {:tx-data full-tx
-         :blocks  tx}))))
+        db-based? (sqlite-util/db-based-graph? repo)]
+    (when (seq blocks)
+      (let [blocks' (let [blocks' (blocks-with-level blocks)]
+                      (cond->> (blocks-with-ordered-list-props repo blocks' target-block sibling?)
+                        update-timestamps?
+                        (mapv #(dissoc % :block/created-at :block/updated-at))
+                        true
+                        (mapv block-with-timestamps)
+                        db-based?
+                        (mapv #(-> %
+                                   (dissoc :block/properties)
+                                   (update-property-created-by created-by)))))
+            insert-opts {:sibling? sibling?
+                         :replace-empty-target? replace-empty-target?
+                         :keep-uuid? keep-uuid?
+                         :keep-block-order? keep-block-order?
+                         :outliner-op outliner-op
+                         :insert-template? insert-template?}
+            {:keys [id->new-uuid blocks-tx]} (insert-blocks-aux @conn blocks' target-block insert-opts)]
+        (if (some (fn [b] (or (nil? (:block/parent b)) (nil? (:block/order b)))) blocks-tx)
+          (throw (ex-info "Invalid outliner data"
+                          {:opts insert-opts
+                           :tx (vec blocks-tx)
+                           :blocks (vec blocks)
+                           :target-block target-block}))
+          (let [uuids-tx (->> (map :block/uuid blocks-tx)
+                              (remove nil?)
+                              (map (fn [uuid'] {:block/uuid uuid'})))
+                tx (assign-temp-id blocks-tx replace-empty-target? target-block)
+                from-property (:logseq.property/created-from-property target-block)
+                property-values-tx (when (and sibling? from-property)
+                                     (let [top-level-blocks (filter #(= 1 (:block/level %)) blocks')]
+                                       (mapcat (fn [block]
+                                                 (when-let [new-id (or (id->new-uuid (:db/id block)) (:block/uuid block))]
+                                                   [{:block/uuid new-id
+                                                     :logseq.property/created-from-property (:db/id from-property)}
+                                                    [:db/add
+                                                     (:db/id (:block/parent target-block))
+                                                     (:db/ident (d/entity @conn (:db/id from-property)))
+                                                     [:block/uuid new-id]]])) top-level-blocks)))
+                full-tx (common-util/concat-without-nil (if (and keep-uuid? replace-empty-target?) (rest uuids-tx) uuids-tx)
+                                                        tx
+                                                        property-values-tx)
+                ;; Replace entities with eid because Datascript doesn't support entity transaction
+                full-tx' (walk/prewalk
+                          (fn [f]
+                            (if (de/entity? f)
+                              (if-let [id (id->new-uuid (:db/id f))]
+                                [:block/uuid id]
+                                (:db/id f))
+                              f))
+                          full-tx)]
+            {:tx-data full-tx'
+             :blocks  tx}))))))
 
 (defn- sort-non-consecutive-blocks
   [db blocks]

+ 2 - 1
deps/outliner/src/logseq/outliner/pipeline.cljs

@@ -15,7 +15,8 @@
   (keep
    (fn [d]
      (when (and (= :block/uuid (:a d)) (false? (:added d)))
-       (:v d)))
+       {:db/id (:e d)
+        :block/uuid (:v d)}))
    datoms))
 
 (defn- calculate-children-refs

+ 25 - 11
deps/outliner/src/logseq/outliner/property.cljs

@@ -280,6 +280,14 @@
     ;; only value-ref-property types should call this
     (find-or-create-property-value conn property-id v)))
 
+(defn- throw-error-if-self-value
+  [block value ref?]
+  (when (and ref? (= value (:db/id block)))
+    (throw (ex-info "Can't set this block itself as own property value"
+                    {:type :notification
+                     :payload {:message "Can't set this block itself as own property value"
+                               :type :error}}))))
+
 (defn set-block-property!
   "Updates a block property's value for an existing property-id and block.  If
   property is a ref type, automatically handles a raw property value i.e. you
@@ -304,10 +312,12 @@
       (let [property (d/entity @conn property-id)
             _ (assert (some? property) (str "Property " property-id " doesn't exist yet"))
             property-type (get property :logseq.property/type :default)
-            new-value (if (db-property-type/all-ref-property-types property-type)
+            ref? (db-property-type/all-ref-property-types property-type)
+            new-value (if ref?
                         (convert-ref-property-value conn property-id v property-type)
                         v)
             existing-value (get block property-id)]
+        (throw-error-if-self-value block new-value ref?)
         (when-not (= existing-value new-value)
           (raw-set-block-property! conn block property property-type new-value))))))
 
@@ -328,17 +338,21 @@
         _ (assert (some? property) (str "Property " property-id " doesn't exist yet"))
         property-type (get property :logseq.property/type :default)
         _ (assert (some? v) "Can't set a nil property value must be not nil")
-        v' (if (db-property-type/value-ref-property-types property-type)
+        ref? (db-property-type/value-ref-property-types property-type)
+        v' (if ref?
              (convert-ref-property-value conn property-id v property-type)
-             v)
-        txs (mapcat
-             (fn [eid]
-               (if-let [block (d/entity @conn eid)]
-                 (build-property-value-tx-data conn block property-id v')
-                 (js/console.error "Skipping setting a block's property because the block id could not be found:" eid)))
-             block-eids)]
-    (when (seq txs)
-      (ldb/transact! conn txs {:outliner-op :save-block}))))
+             v)]
+    (doseq [eid block-eids]
+      (let [block (d/entity @conn eid)]
+        (throw-error-if-self-value block v' ref?)))
+    (let [txs (mapcat
+               (fn [eid]
+                 (if-let [block (d/entity @conn eid)]
+                   (build-property-value-tx-data conn block property-id v')
+                   (js/console.error "Skipping setting a block's property because the block id could not be found:" eid)))
+               block-eids)]
+      (when (seq txs)
+        (ldb/transact! conn txs {:outliner-op :save-block})))))
 
 (defn batch-remove-property!
   [conn block-ids property-id]

+ 5 - 5
deps/publishing/script/publishing.cljs

@@ -1,13 +1,13 @@
 (ns publishing
   "Basic script for publishing from CLI"
-  (:require [logseq.graph-parser.cli :as gp-cli]
-            [logseq.publishing :as publishing]
-            [logseq.db.sqlite.cli :as sqlite-cli]
-            ["fs" :as fs]
+  (:require ["fs" :as fs]
             ["path" :as node-path]
             [clojure.edn :as edn]
             [datascript.core :as d]
+            [logseq.db.sqlite.cli :as sqlite-cli]
             [logseq.db.sqlite.util :as sqlite-util]
+            [logseq.graph-parser.cli :as gp-cli]
+            [logseq.publishing :as publishing]
             [nbb.core :as nbb]))
 
 (defn- get-db [graph-dir]
@@ -60,5 +60,5 @@
       (publish-db-graph static-dir graph-dir output-path options)
       (publish-file-graph static-dir graph-dir output-path options))))
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))

+ 13 - 24
deps/shui/src/logseq/shui/popup/core.cljs

@@ -123,14 +123,13 @@
   ([] (when-let [id (some-> (get-popups) (last) :id)] (hide! id 0)))
   ([id] (hide! id 0 {}))
   ([id delay] (hide! id delay {}))
-  ([id delay {:keys [all?]}]
+  ([id delay {:keys [_all?]}]
    (when-let [popup (get-popup id)]
      (let [config (last popup)
            target (:target config)
-           f #(if all?
-                (reset! *popups [])
-                (do (detach-popup! id)
-                    (some-> (:on-after-hide config) (apply []))))]
+           f (fn []
+               (detach-popup! id)
+               (some-> (:on-after-hide config) (apply [])))]
        (some-> (:on-before-hide config) (apply []))
        (some-> target (d/remove-attr! "data-popup-active"))
        (if (and (number? delay) (> delay 0))
@@ -147,20 +146,6 @@
            auto-side? _auto-focus? _target root-props content-props
            _on-before-hide _on-after-hide]
     :as _props}]
-  ;; disableOutsidePointerEvents
-  ;(rum/use-effect!
-  ;  (fn []
-  ;    (when-not as-dropdown?
-  ;      (let [^js style js/document.body.style
-  ;            set-pointer-event! #(set! (. style -pointerEvents) %)
-  ;            try-unset! #(when (nil? (seq @*popups))
-  ;                          (set-pointer-event! nil))]
-  ;        (if open?
-  ;          (set-pointer-event! "none")
-  ;          (try-unset!))
-  ;        #(try-unset!))))
-  ;  [open?])
-
   (when-let [[x y _ height] position]
     (let [popup-root (if (not force-popover?) dropdown-menu popover)
           popup-trigger (if (not force-popover?) dropdown-menu-trigger popover-trigger)
@@ -175,7 +160,12 @@
           auto-side? (if (boolean? auto-side?) auto-side? true)
           content-props (cond-> content-props
                           auto-side? (assoc :side (auto-side-fn)))
-          hide (fn [] (hide! id 1))]
+          handle-key-escape! (fn [^js e]
+                               (when-not (false? (some-> content-props (:onEscapeKeyDown) (apply [e])))
+                                 (hide! id 1)))
+          handle-pointer-outside! (fn [^js e]
+                                    (when-not (false? (some-> content-props (:onPointerDownOutside) (apply [e])))
+                                      (hide! id 1)))]
       (popup-root
        (merge root-props {:open open?})
        (popup-trigger
@@ -187,10 +177,9 @@
                          :width 1
                          :top y
                          :left x}} ""))
-       (let [content-props (cond-> (merge {:onEscapeKeyDown hide
-                                           :disableOutsideScroll false
-                                           :onPointerDownOutside hide}
-                                          content-props)
+       (let [content-props (cond-> (merge content-props {:onEscapeKeyDown handle-key-escape!
+                                                         :disableOutsideScroll false
+                                                         :onPointerDownOutside handle-pointer-outside!})
                              (and (not force-popover?)
                                   (not as-dropdown?))
                              (assoc :on-key-down (fn [^js e]

+ 2 - 2
resources/css/shui.css

@@ -284,11 +284,11 @@ div[data-radix-popper-content-wrapper] {
   @apply overflow-y-auto;
 
   &[data-side=top] {
-    max-height: calc(var(--radix-dropdown-menu-content-available-height) - 40px);
+    max-height: calc(var(--radix-dropdown-menu-content-available-height) - 20px);
   }
 
   &[data-side=bottom] {
-    max-height: calc(var(--radix-dropdown-menu-content-available-height) - 20px);
+    max-height: calc(var(--radix-dropdown-menu-content-available-height) - 10px);
   }
 
   &.text-popover-foreground {

+ 5 - 5
scripts/src/logseq/tasks/db_graph/create_graph_with_large_sizes.cljs

@@ -1,11 +1,11 @@
 (ns logseq.tasks.db-graph.create-graph-with-large-sizes
   "Script that generates graphs at large sizes"
-  (:require [logseq.outliner.cli :as outliner-cli]
+  (:require ["os" :as os]
+            ["path" :as node-path]
+            [babashka.cli :as cli]
             [clojure.string :as string]
             [datascript.core :as d]
-            [babashka.cli :as cli]
-            ["path" :as node-path]
-            ["os" :as os]
+            [logseq.outliner.cli :as outliner-cli]
             [nbb.classpath :as cp]
             [nbb.core :as nbb]))
 
@@ -83,5 +83,5 @@
     #_(d/transact! conn blocks-tx)
     (println "Created graph" (str db-name " with " (count (d/datoms @conn :eavt)) " datoms!"))))
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))

+ 1 - 1
scripts/src/logseq/tasks/db_graph/create_graph_with_properties.cljs

@@ -222,5 +222,5 @@
     (d/transact! conn block-props-tx)
     (println "Created graph" (str db-name " with " (count (d/datoms @conn :eavt)) " datoms!"))))
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))

+ 1 - 1
scripts/src/logseq/tasks/db_graph/create_graph_with_schema_org.cljs

@@ -423,5 +423,5 @@
     (when (:debug options) (write-debug-file @conn))
     (println "Created graph" (str db-name "!"))))
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))

+ 3 - 4
src/main/frontend/commands.cljs

@@ -435,10 +435,9 @@
           "Upload file types like image, pdf, docx, etc.)"
           :icon/upload])
 
-       (when-not db?
-         ["Template" [[:editor/input command-trigger nil]
-                      [:editor/search-template]] "Insert a created template here"
-          :icon/template])
+       ["Template" [[:editor/input command-trigger nil]
+                    [:editor/search-template]] "Insert a created template here"
+        :icon/template]
 
        ["Embed HTML " (->inline "html") "" :icon/htmlEmbed]
 

+ 53 - 43
src/main/frontend/components/block.cljs

@@ -2606,51 +2606,61 @@
 
 (rum/defcs block-tag <
   (rum/local false ::hover?)
+  (rum/local false ::hover-container?)
   [state block tag config popup-opts]
   (let [*hover? (::hover? state)
-        hover? @*hover?]
-    [:div.block-tag.items-center
+        *hover-container? (::hover-container? state)]
+    [:div.block-tag.items-center.relative
      {:key (str "tag-" (:db/id tag))
-      :on-mouse-over #(reset! *hover? true)
-      :on-mouse-out #(reset! *hover? false)
-      :on-context-menu
-      (fn [e]
-        (util/stop e)
-        (shui/popup-show! e
-                          (fn []
-                            [:<>
-                             (shui/dropdown-menu-item
-                              {:key "Go to tag"
-                               :on-click #(route-handler/redirect-to-page! (:block/uuid tag))}
-                              (str "Go to #" (:block/title tag))
-                              (shui/dropdown-menu-shortcut (shortcut-utils/decorate-binding "mod+click")))
-                             (shui/dropdown-menu-item
-                              {:key "Open tag in sidebar"
-                               :on-click #(state/sidebar-add-block! (state/get-current-repo) (:db/id tag) :page)}
-                              "Open tag in sidebar"
-                              (shui/dropdown-menu-shortcut (shortcut-utils/decorate-binding "shift+click")))
-                             (shui/dropdown-menu-item
-                              {:key "Remove tag"
-                               :on-click #(db-property-handler/delete-property-value! (:db/id block) :block/tags (:db/id tag))}
-                              "Remove tag")])
-                          popup-opts))}
-     (if (and hover? (not (ldb/private-tags (:db/ident tag))))
-       [:a.inline.close.flex.transition-opacity.duration-300.ease-in
-        {:class (if @*hover? "!opacity-100" "!opacity-0")
-         :title "Remove this tag"
-         :on-pointer-down
-         (fn [e]
-           (util/stop e)
-           (db-property-handler/delete-property-value! (:db/id block) :block/tags (:db/id tag)))}
-        (ui/icon "x" {:size 14
-                      :style {:margin-top 1}})]
-       [:a.hash-symbol.select-none {:style {:margin-left 5}}
-        "#"])
-     (page-cp (assoc config
-                     :disable-preview? true
-                     :tag? true
-                     :hide-tag-symbol? true)
-              tag)]))
+      :on-mouse-over #(reset! *hover-container? true)
+      :on-mouse-out #(reset! *hover-container? false)}
+     (when (not (ldb/private-tags (:db/ident tag)))
+       [:div.absolute.bg-gray-03.transition-opacity.duration-300.ease-in-out
+        {:class (if @*hover-container? "!opacity-100" "!opacity-0")
+         :style {:top -2
+                 :z-index (if @*hover-container? 99 -1)
+                 :left -23}}
+        (shui/button
+         {:size :sm
+          :variant :ghost
+          :class "px-1 py-1 h-6 text-muted-foreground"
+          :title "Remove this tag"
+          :on-pointer-down
+          (fn [e]
+            (util/stop e)
+            (db-property-handler/delete-property-value! (:db/id block) :block/tags (:db/id tag)))}
+         (ui/icon "x" {:size 14}))])
+     [:div.flex.items-center
+      {:on-mouse-over #(reset! *hover? true)
+       :on-mouse-out #(reset! *hover? false)
+       :on-context-menu
+       (fn [e]
+         (util/stop e)
+         (shui/popup-show! e
+                           (fn []
+                             [:<>
+                              (shui/dropdown-menu-item
+                               {:key "Go to tag"
+                                :on-click #(route-handler/redirect-to-page! (:block/uuid tag))}
+                               (str "Go to #" (:block/title tag))
+                               (shui/dropdown-menu-shortcut (shortcut-utils/decorate-binding "mod+click")))
+                              (shui/dropdown-menu-item
+                               {:key "Open tag in sidebar"
+                                :on-click #(state/sidebar-add-block! (state/get-current-repo) (:db/id tag) :page)}
+                               "Open tag in sidebar"
+                               (shui/dropdown-menu-shortcut (shortcut-utils/decorate-binding "shift+click")))
+                              (shui/dropdown-menu-item
+                               {:key "Remove tag"
+                                :on-click #(db-property-handler/delete-property-value! (:db/id block) :block/tags (:db/id tag))}
+                               "Remove tag")])
+                           popup-opts))}
+      [:a.hash-symbol.select-none.flex
+       "#"]
+      (page-cp (assoc config
+                      :disable-preview? true
+                      :tag? true
+                      :hide-tag-symbol? true)
+               tag)]]))
 
 (rum/defc tags-cp
   "Tags without inline or hidden tags"
@@ -2959,7 +2969,7 @@
         repo (state/get-current-repo)
         db-based? (config/db-based-graph? repo)
         refs-count (if (seq (:block/_refs block))
-                     (count (:block/_refs block))
+                     (count (remove :logseq.property/view-for (:block/_refs block)))
                      (rum/react *refs-count))
         table? (:table? config)
         raw-mode-block (state/sub :editor/raw-mode-block)

+ 5 - 1
src/main/frontend/components/block.css

@@ -245,7 +245,11 @@
   @apply w-full inline;
 
   > .ui__checkbox {
-    @apply relative top-[2px];
+    @apply relative top-0.5;
+
+    &.checked {
+      @apply top-1;
+    }
 
     > span {
       @apply h-full;

+ 6 - 3
src/main/frontend/components/editor.cljs

@@ -352,7 +352,8 @@
 
 (rum/defc template-search-aux
   [id q]
-  (let [[matched-templates set-matched-templates!] (rum/use-state nil)]
+  (let [db-based? (config/db-based-graph?)
+        [matched-templates set-matched-templates!] (rum/use-state nil)]
     (hooks/use-effect! (fn []
                          (p/let [result (editor-handler/<get-matched-templates q)]
                            (set-matched-templates! result)))
@@ -362,8 +363,8 @@
      {:on-chosen   (editor-handler/template-on-chosen-handler id)
       :on-enter    (fn [_state] (state/clear-editor-action!))
       :empty-placeholder [:div.text-gray-500.px-4.py-2.text-sm "Search for a template"]
-      :item-render (fn [[template _block-db-id]]
-                     template)
+      :item-render (fn [template]
+                     (if db-based? (:block/title template) (:template template)))
       :class       "black"})))
 
 (rum/defc template-search < rum/reactive
@@ -686,8 +687,10 @@
                  :input
                  (open-editor-popup! :input
                                      (editor-input id
+                      ;; on-submit
                                                    (fn [command m]
                                                      (editor-handler/handle-command-input command id format m))
+                      ;; on-cancel
                                                    (fn []
                                                      (editor-handler/handle-command-input-close id)))
                                      {:content-props {:onOpenAutoFocus #()}})

+ 1 - 1
src/main/frontend/components/header.cljs

@@ -354,7 +354,7 @@
                              (mobile-util/native-platform?)
                              (util/scroll-to-top true))))
       :style           {:fontSize 50}}
-     [:div.l.flex.drag-region
+     [:div.l.flex.items-center.drag-region
       [left-menu
        (if (mobile-util/native-platform?)
          ;; back button for mobile

+ 4 - 4
src/main/frontend/components/page.cljs

@@ -428,13 +428,13 @@
 
 (rum/defc db-page-title-actions
   [page]
-  [:div.absolute.-top-3.left-0.opacity-0.db-page-title-actions
+  [:div.absolute.-top-4.left-0.opacity-0.db-page-title-actions
    [:div.flex.flex-row.items-center.gap-2
     (when-not (:logseq.property/icon (db/entity (:db/id page)))
       (shui/button
        {:variant :outline
         :size :sm
-        :class "px-2 py-0 h-4 text-xs text-muted-foreground"
+        :class "px-2 py-0 h-6 text-xs text-muted-foreground"
         :on-click (fn [e]
                     (state/pub-event! [:editor/new-property {:property-key "Icon"
                                                              :block page
@@ -444,11 +444,11 @@
     (shui/button
      {:variant :outline
       :size :sm
-      :class "px-2 py-0 h-4 text-xs text-muted-foreground"
+      :class "px-2 py-0 h-6 text-xs text-muted-foreground"
       :on-click (fn [e]
                   (state/pub-event! [:editor/new-property {:block page
                                                            :target (.-target e)}]))}
-     "Set page property")]])
+     "Set property")]])
 
 (rum/defc db-page-title
   [page whiteboard-page? sidebar? container-id]

+ 112 - 81
src/main/frontend/components/property/value.cljs

@@ -79,7 +79,9 @@
                        (db-property-handler/remove-block-property!
                         (:db/id block)
                         :logseq.property/icon))
-                     (clear-overlay!))]
+                     (clear-overlay!)
+                     (when editing?
+                       (editor-handler/restore-last-saved-cursor!)))]
 
     (hooks/use-effect!
      (fn []
@@ -92,12 +94,15 @@
             (fn []
               (when-let [^js target (some-> (.querySelector container (str "#ls-block-" (str (:block/uuid block))))
                                             (.querySelector ".block-main-container"))]
+                (state/set-editor-action! :property-icon-picker)
                 (shui/popup-show! target
                                   #(icon-component/icon-search
                                     {:on-chosen on-chosen!
                                      :icon-value icon
                                      :del-btn? (some? icon)})
                                   {:id :ls-icon-picker
+                                   :on-after-hide #(state/set-editor-action! nil)
+                                   :content-props {:onEscapeKeyDown #(when editing? (editor-handler/restore-last-saved-cursor!))}
                                    :align :start})))))))
      [editing?])
 
@@ -632,6 +637,10 @@
                                [(:db/id v)])))
         parent-property? (= (:db/ident property) :logseq.property/parent)
         children-pages (when parent-property? (model/get-structured-children repo (:db/id block)))
+        property-type (:logseq.property/type property)
+        get-all-classes-f (fn []
+                            (model/get-all-classes repo {:except-root-class? true
+                                                         :except-private-tags? (not (contains? #{:logseq.property/template-applied-to} (:db/ident property)))}))
         nodes
         (->>
          (cond
@@ -649,40 +658,40 @@
                  excluded-options (remove (fn [e] (contains? exclude-ids (:block/uuid e))) options)]
              excluded-options)
 
+           (= property-type :class)
+           (get-all-classes-f)
+
            (seq classes)
            (->>
             (mapcat
              (fn [class]
-               (if (= :logseq.class/Root (:db/ident class))
-                 (model/get-all-classes repo {:except-root-class? true})
-                 (model/get-class-objects repo (:db/id class))))
+               (model/get-class-objects repo (:db/id class)))
              classes)
             distinct)
 
            :else
-           (let [property-type (:logseq.property/type property)]
-             (if (empty? result)
-               (let [v (get block (:db/ident property))]
-                 (remove #(= :logseq.property/empty-placeholder (:db/ident %))
-                         (if (every? de/entity? v) v [v])))
-               (remove (fn [node]
-                         (or (= (:db/id block) (:db/id node))
+           (if (empty? result)
+             (let [v (get block (:db/ident property))]
+               (remove #(= :logseq.property/empty-placeholder (:db/ident %))
+                       (if (every? de/entity? v) v [v])))
+             (remove (fn [node]
+                       (or (= (:db/id block) (:db/id node))
                              ;; A page's alias can't be itself
-                             (and alias? (= (or (:db/id (:block/page block))
-                                                (:db/id block))
-                                            (:db/id node)))
-                             (cond
-                               (= property-type :class)
-                               (ldb/private-tags (:db/ident node))
-
-                               (and property-type (not= property-type :node))
-                               (if (= property-type :page)
-                                 (not (db/page? node))
-                                 (not (contains? (ldb/get-entity-types node) property-type)))
-
-                               :else
-                               false)))
-                       result)))))
+                           (and alias? (= (or (:db/id (:block/page block))
+                                              (:db/id block))
+                                          (:db/id node)))
+                           (cond
+                             (= property-type :class)
+                             (ldb/private-tags (:db/ident node))
+
+                             (and property-type (not= property-type :node))
+                             (if (= property-type :page)
+                               (not (db/page? node))
+                               (not (contains? (ldb/get-entity-types node) property-type)))
+
+                             :else
+                             false)))
+                     result))))
 
         options (map (fn [node]
                        (let [id (or (:value node) (:db/id node))
@@ -929,11 +938,14 @@
                      :property-block? true
                      :on-block-content-pointer-down (when default-value?
                                                       (fn [_e]
-                                                        (<create-new-block! block property (or (:block/title default-value) ""))))}]
+                                                        (<create-new-block! block property (or (:block/title default-value) ""))))
+                     :p-block (:db/id block)
+                     :p-property (:db/id property)}]
          (if (set? value-block)
            (blocks-container config (ldb/sort-by-order value-block))
            (rum/with-key
-             (block-container (assoc config :property-default-value? default-value?) value-block)
+             (block-container (assoc config
+                                     :property-default-value? default-value?) value-block)
              (str (:db/id property) "-" (:block/uuid value-block)))))]
       [:div
        {:tabIndex 0
@@ -1231,7 +1243,7 @@
     (multiple-values-inner block property value' opts)))
 
 (rum/defcs property-value < rum/reactive db-mixins/query
-  [state block property {:keys [show-tooltip?]
+  [state block property {:keys [show-tooltip? p-block p-property]
                          :as opts}]
   (ui/catch-error
    (ui/block-error "Something wrong" {})
@@ -1258,55 +1270,74 @@
              (first v)
              :else
              v)
-         empty-value? (when (coll? v) (= :logseq.property/empty-placeholder (:db/ident (first v))))
-         closed-values? (seq (:property/closed-values property))
-         property-ident (:db/ident property)
-         value-cp [:div.property-value-inner
-                   {:data-type type
-                    :class (str (when empty-value? "empty-value")
-                                (when-not (:other-position? opts) " w-full"))}
-                   (cond
-                     (= property-ident :logseq.property.class/properties)
-                     (properties-cp {} block {:selected? false
-                                              :class-schema? true})
-
-                     (and multiple-values? (contains? #{:default :url} type) (not closed-values?))
-                     (property-normal-block-value block property v)
-
-                     multiple-values?
-                     (multiple-values block property opts)
-
-                     :else
-                     (let [parent? (= property-ident :logseq.property/parent)
-                           value-cp (property-scalar-value block property v
-                                                           (merge
-                                                            opts
-                                                            {:editor-id editor-id
-                                                             :dom-id dom-id}))
-                           page-ancestors (when parent?
-                                            (let [ancestor-pages (loop [parents [block]]
-                                                                   (if-let [parent (:logseq.property/parent (last parents))]
-                                                                     (when-not (contains? (set parents) parent)
-                                                                       (recur (conj parents parent)))
-                                                                     parents))]
-                                              (->> (reverse ancestor-pages)
-                                                   (remove (fn [e] (= (:db/id block) (:db/id e))))
-                                                   butlast)))]
-                       (if (seq page-ancestors)
-                         [:div.flex.flex-1.items-center.gap-1
-                          (interpose [:span.opacity-50.text-sm " > "]
-                                     (concat
-                                      (map (fn [{title :block/title :as ancestor}]
-                                             [:a.whitespace-nowrap {:on-click #(route-handler/redirect-to-page! (:block/uuid ancestor))} title])
-                                           page-ancestors)
-                                      [value-cp]))]
-                         value-cp)))]]
-     (if show-tooltip?
-       (shui/tooltip-provider
-        (shui/tooltip
-         {:delayDuration 1200}
-         (shui/tooltip-trigger
-          {:onFocusCapture #(util/stop-propagation %)} value-cp)
-         (shui/tooltip-content
-          (str "Change " (:block/title property)))))
-       value-cp))))
+         self-value-or-embedded? (fn [v]
+                                   (or (= (:db/id v) (:db/id block))
+                                       ;; property value self embedded
+                                       (= (:db/id (:block/link v)) (:db/id block))))]
+     (if (and (or (and (de/entity? v) (self-value-or-embedded? v))
+                  (and (coll? v) (every? de/entity? v)
+                       (some self-value-or-embedded? v))
+                  (and (= p-block (:db/id block)) (= p-property (:db/id property))))
+              (not= :logseq.class/Tag (:db/ident block)))
+       [:div.flex.flex-row.items-center.gap-1
+        [:div.warning "Self reference"]
+        (shui/button {:variant :outline
+                      :size :sm
+                      :class "h-5"
+                      :on-click (fn []
+                                  (db-property-handler/remove-block-property!
+                                   (:db/id block)
+                                   (:db/ident property)))}
+                     "Fix it!")]
+       (let [empty-value? (when (coll? v) (= :logseq.property/empty-placeholder (:db/ident (first v))))
+             closed-values? (seq (:property/closed-values property))
+             property-ident (:db/ident property)
+             value-cp [:div.property-value-inner
+                       {:data-type type
+                        :class (str (when empty-value? "empty-value")
+                                    (when-not (:other-position? opts) " w-full"))}
+                       (cond
+                         (= property-ident :logseq.property.class/properties)
+                         (properties-cp {} block {:selected? false
+                                                  :class-schema? true})
+
+                         (and multiple-values? (contains? #{:default :url} type) (not closed-values?))
+                         (property-normal-block-value block property v)
+
+                         multiple-values?
+                         (multiple-values block property opts)
+
+                         :else
+                         (let [parent? (= property-ident :logseq.property/parent)
+                               value-cp (property-scalar-value block property v
+                                                               (merge
+                                                                opts
+                                                                {:editor-id editor-id
+                                                                 :dom-id dom-id}))
+                               page-ancestors (when parent?
+                                                (let [ancestor-pages (loop [parents [block]]
+                                                                       (if-let [parent (:logseq.property/parent (last parents))]
+                                                                         (when-not (contains? (set parents) parent)
+                                                                           (recur (conj parents parent)))
+                                                                         parents))]
+                                                  (->> (reverse ancestor-pages)
+                                                       (remove (fn [e] (= (:db/id block) (:db/id e))))
+                                                       butlast)))]
+                           (if (seq page-ancestors)
+                             [:div.flex.flex-1.items-center.gap-1
+                              (interpose [:span.opacity-50.text-sm " > "]
+                                         (concat
+                                          (map (fn [{title :block/title :as ancestor}]
+                                                 [:a.whitespace-nowrap {:on-click #(route-handler/redirect-to-page! (:block/uuid ancestor))} title])
+                                               page-ancestors)
+                                          [value-cp]))]
+                             value-cp)))]]
+         (if show-tooltip?
+           (shui/tooltip-provider
+            (shui/tooltip
+             {:delayDuration 1200}
+             (shui/tooltip-trigger
+              {:onFocusCapture #(util/stop-propagation %)} value-cp)
+             (shui/tooltip-content
+              (str "Change " (:block/title property)))))
+           value-cp))))))

+ 2 - 6
src/main/frontend/components/table.css

@@ -15,12 +15,8 @@
     @apply w-full py-0;
   }
 
-  .block-control, .block-control-wrap {
-    @apply h-5 min-h-[20px];
-  }
-
-  .block-main-container, .block-content {
-    @apply min-h-[20px];
+  .block-main-content, .block-head-wrap {
+    @apply min-h-[24px];
   }
 
   .property-value-inner {

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

@@ -1291,7 +1291,8 @@
   (when-let [->hiccup (state/get-component :block/->hiccup)]
     (let [group-by-page? (not (every? db/page? result))
           result (if group-by-page?
-                   (group-by :block/page result)
+                   (-> (group-by :block/page result)
+                       (update-vals ldb/sort-by-order))
                    result)
           config' (cond-> (assoc config
                                  :current-block (:db/id view-entity)
@@ -1480,7 +1481,8 @@
                            ""))
             result (editor-handler/api-insert-new-block! view-title
                                                          {:page (:block/uuid page)
-                                                          :properties properties})]
+                                                          :properties properties
+                                                          :edit-block? false})]
       (db/entity [:block/uuid (:block/uuid result)]))))
 
 (rum/defc views-tab < rum/reactive db-mixins/query
@@ -1528,7 +1530,7 @@
           (if (= title "")
             "New view"
             title))
-        (when show-items-count?
+        (when (and show-items-count? (> (count data) 0))
           [:span.text-muted-foreground.text-xs
            (count data)]))))
 

+ 3 - 3
src/main/frontend/db/model.cljs

@@ -424,9 +424,9 @@ independent of format as format specific heading characters are stripped"
           (db-utils/pull-many repo '[*] ids'))))))
 
 (defn get-block-and-children
-  [repo block-uuid]
+  [repo block-uuid & {:as opts}]
   (let [db (conn/get-db repo)]
-    (ldb/get-block-and-children db block-uuid)))
+    (ldb/get-block-and-children db block-uuid opts)))
 
 (defn get-file-page
   ([file-path]
@@ -651,7 +651,7 @@ independent of format as format specific heading characters are stripped"
                    [:frontend.worker.react/refs eid]
                    {:query-fn (fn []
                                 (let [entities (mapcat (fn [id]
-                                                         (:block/_path-refs (db-utils/entity id))) ids)
+                                                         (:block/_refs (db-utils/entity id))) ids)
                                       blocks (map (fn [e]
                                                     {:block/parent (:block/parent e)
                                                      :block/order (:block/order e)

+ 5 - 5
src/main/frontend/handler/common/page.cljs

@@ -73,11 +73,11 @@
                            (outliner-op/create-page! title' options'))
                    [_page-name page-uuid] (ldb/read-transit-str result)]
              (when redirect?
-               (route-handler/redirect-to-page! page-uuid))
-             (let [page (db/get-page (or page-uuid title'))]
-               (when-let [first-block (ldb/get-first-child @conn (:db/id page))]
-                 (block-handler/edit-block! first-block :max {:container-id :unknown-container}))
-               page))))))))
+               (route-handler/redirect-to-page! page-uuid)
+               (let [page (db/get-page (or page-uuid title'))]
+                 (when-let [first-block (ldb/get-first-child @conn (:db/id page))]
+                   (block-handler/edit-block! first-block :max {:container-id :unknown-container}))
+                 page)))))))))
 
 ;; favorite fns
 ;; ============

+ 78 - 59
src/main/frontend/handler/editor.cljs

@@ -1897,6 +1897,12 @@
 
   (handle-command-input-close id))
 
+(defn restore-last-saved-cursor!
+  ([] (restore-last-saved-cursor! (state/get-input)))
+  ([input]
+   (when-let [saved-cursor (and input (state/get-editor-last-pos))]
+     (cursor/move-cursor-to input saved-cursor true))))
+
 (defn- close-autocomplete-if-outside
   [input]
   (when (and input
@@ -2219,73 +2225,86 @@
   ([element-id db-id {:keys [target] :as opts}]
    (let [repo (state/get-current-repo)
          db? (config/db-based-graph? repo)]
-     (when-not db?
-       (p/let [block (if (integer? db-id)
-                       (db-async/<pull repo db-id)
-                       (db-async/<get-template-by-name (name db-id)))
-               block (when (:block/uuid block)
-                       (db-async/<get-block repo (:block/uuid block)
-                                            {:children? true
-                                             :nested-children? true}))]
-         (when (:db/id block)
-           (let [journal? (ldb/journal? target)
-                 target (or target (state/get-edit-block))
-                 format (get block :block/format :markdown)
-                 block-uuid (:block/uuid block)
-                 template-including-parent? (not (false? (:template-including-parent (:block/properties block))))
-                 blocks (db/get-block-and-children repo block-uuid)
-                 sorted-blocks (cons
+     (p/let [block (if (integer? db-id)
+                     (db-async/<pull repo db-id)
+                     (db-async/<get-template-by-name (name db-id)))
+             block (when (:block/uuid block)
+                     (db-async/<get-block repo (:block/uuid block)
+                                          {:children? true
+                                           :nested-children? true}))]
+       (when (:db/id block)
+         (let [journal? (ldb/journal? target)
+               target (or target (state/get-edit-block))
+               format (get block :block/format :markdown)
+               block-uuid (:block/uuid block)
+               template-including-parent? (not (false? (:template-including-parent (:block/properties block))))
+               blocks (db/get-block-and-children repo block-uuid {:include-property-block? true})
+               sorted-blocks (if db?
+                               (let [blocks' (rest blocks)]
+                                 (cons
+                                  (-> (first blocks')
+                                      (assoc :logseq.property/used-template (:db/id block)))
+                                  (rest blocks')))
+                               (cons
                                 (-> (first blocks)
                                     (update :block/properties-text-values dissoc :template)
                                     (update :block/properties-order (fn [keys]
                                                                       (vec (remove #{:template} keys)))))
-                                (rest blocks))
-                 blocks (if template-including-parent?
-                          sorted-blocks
-                          (drop 1 sorted-blocks))]
-             (when element-id
-               (insert-command! element-id "" format {:end-pattern commands/command-trigger}))
-             (let [exclude-properties [:id :template :template-including-parent]
-                   content-update-fn (fn [content]
-                                       (->> content
-                                            (property-file/remove-property-when-file-based repo format "template")
-                                            (property-file/remove-property-when-file-based repo format "template-including-parent")
-                                            template/resolve-dynamic-template!))
-                   page (if (:block/name block) block
-                            (when target (:block/page (db/entity (:db/id target)))))
-                   blocks' (map (fn [block]
+                                (rest blocks)))
+               blocks (cond
+                        db?
+                        sorted-blocks
+                        template-including-parent?
+                        sorted-blocks
+                        :else
+                        (drop 1 sorted-blocks))]
+           (when element-id
+             (insert-command! element-id "" format {:end-pattern commands/command-trigger}))
+           (let [exclude-properties [:id :template :template-including-parent]
+                 content-update-fn (fn [content]
+                                     (->> content
+                                          (property-file/remove-property-when-file-based repo format "template")
+                                          (property-file/remove-property-when-file-based repo format "template-including-parent")
+                                          template/resolve-dynamic-template!))
+                 page (if (:block/name block) block
+                          (when target (:block/page (db/entity (:db/id target)))))
+                 blocks' (if (config/db-based-graph?)
+                           blocks
+                           (map (fn [block]
                                   (paste-block-cleanup repo block page exclude-properties format content-update-fn false))
-                                blocks)
-                   sibling? (:sibling? opts)
-                   sibling?' (cond
-                               (some? sibling?)
-                               sibling?
-
-                               (db/has-children? (:block/uuid target))
-                               false
-
-                               :else
-                               true)]
-               (when (seq blocks')
-                 (try
-                   (p/let [result (ui-outliner-tx/transact!
-                                   {:outliner-op :insert-blocks
-                                    :created-from-journal-template? journal?}
-                                   (when-not (string/blank? (state/get-edit-content))
-                                     (save-current-block!))
-                                   (outliner-op/insert-blocks! blocks' target
-                                                               (assoc opts :sibling? sibling?')))]
-                     (when result (edit-last-block-after-inserted! (ldb/read-transit-str result))))
-
-                   (catch :default ^js/Error e
-                     (notification/show!
-                      [:p.content
-                       (util/format "Template insert error: %s" (.-message e))]
-                      :error))))))))))))
+                                blocks))
+                 sibling? (:sibling? opts)
+                 sibling?' (cond
+                             (some? sibling?)
+                             sibling?
+
+                             (db/has-children? (:block/uuid target))
+                             false
+
+                             :else
+                             true)]
+             (when (seq blocks')
+               (try
+                 (p/let [result (ui-outliner-tx/transact!
+                                 {:outliner-op :insert-blocks
+                                  :created-from-journal-template? journal?}
+                                 (when-not (string/blank? (state/get-edit-content))
+                                   (save-current-block!))
+                                 (outliner-op/insert-blocks! blocks' target
+                                                             (assoc opts
+                                                                    :sibling? sibling?'
+                                                                    :insert-template? true)))]
+                   (when result (edit-last-block-after-inserted! (ldb/read-transit-str result))))
+
+                 (catch :default ^js/Error e
+                   (notification/show!
+                    [:p.content
+                     (util/format "Template insert error: %s" (.-message e))]
+                    :error)))))))))))
 
 (defn template-on-chosen-handler
   [element-id]
-  (fn [[_template template-block] _click?]
+  (fn [template-block]
     (when-let [db-id (:db/id template-block)]
       (insert-template! element-id db-id
                         {:replace-empty-target? true}))))

+ 32 - 25
src/main/frontend/handler/paste.cljs

@@ -8,6 +8,7 @@
             [frontend.format.block :as block]
             [frontend.format.mldoc :as mldoc]
             [frontend.handler.editor :as editor-handler]
+            [frontend.handler.notification :as notification]
             [frontend.mobile.util :as mobile-util]
             [frontend.state :as state]
             [frontend.util :as util]
@@ -189,32 +190,38 @@
   ;; todo: logseq/whiteboard-shapes is now text/html
   [input text e html]
   (util/stop e)
-  (->
-   (p/let [{:keys [graph blocks embed-block?]} (get-copied-blocks)]
-     (if (and (seq blocks) (= graph (state/get-current-repo)))
+  (let [repo (state/get-current-repo)]
+    (->
+     (p/let [{:keys [graph blocks embed-block?]} (get-copied-blocks)]
+       (if (and (seq blocks) (= graph repo))
        ;; Handle internal paste
-       (let [revert-cut-txs (get-revert-cut-txs blocks)
-             keep-uuid? (= (state/get-block-op-type) :cut)
-             blocks (if (config/db-based-graph? (state/get-current-repo))
-                      (map (fn [b] (dissoc b :block/properties)) blocks)
-                      blocks)]
-         (if embed-block?
-           (when-let [block-id (:block/uuid (first blocks))]
-             (when-let [current-block (state/get-edit-block)]
-               (p/do!
-                (editor-handler/api-insert-new-block! ""
-                                                      {:block-uuid (:block/uuid current-block)
-                                                       :sibling? true
-                                                       :replace-empty-target? true
-                                                       :other-attrs {:block/link (:db/id (db/entity [:block/uuid block-id]))}})
-                (state/clear-edit!))))
-           (editor-handler/paste-blocks blocks {:revert-cut-txs revert-cut-txs
-                                                :keep-uuid? keep-uuid?})))
-       (paste-copied-text input text html)))
-   (p/catch (fn [error]
-              (log/error :msg "Paste failed" :exception error)
-              (state/pub-event! [:capture-error {:error error
-                                                 :payload {:type ::paste-copied-blocks-or-text}}])))))
+         (let [revert-cut-txs (get-revert-cut-txs blocks)
+               keep-uuid? (= (state/get-block-op-type) :cut)
+               blocks (if (config/db-based-graph? (state/get-current-repo))
+                        (map (fn [b] (dissoc b :block/properties)) blocks)
+                        blocks)]
+           (if embed-block?
+             (when-let [block-id (:block/uuid (first blocks))]
+               (when-let [current-block (state/get-edit-block)]
+                 (cond
+                   (some #(= block-id (:block/uuid %)) (db/get-block-parents repo (:block/uuid current-block) {}))
+                   (notification/show! "Can't embed parent block as its own property" :error)
+
+                   :else
+                   (p/do!
+                    (editor-handler/api-insert-new-block! ""
+                                                          {:block-uuid (:block/uuid current-block)
+                                                           :sibling? true
+                                                           :replace-empty-target? true
+                                                           :other-attrs {:block/link (:db/id (db/entity [:block/uuid block-id]))}})
+                    (state/clear-edit!)))))
+             (editor-handler/paste-blocks blocks {:revert-cut-txs revert-cut-txs
+                                                  :keep-uuid? keep-uuid?})))
+         (paste-copied-text input text html)))
+     (p/catch (fn [error]
+                (log/error :msg "Paste failed" :exception error)
+                (state/pub-event! [:capture-error {:error error
+                                                   :payload {:type ::paste-copied-blocks-or-text}}]))))))
 
 (defn paste-text-in-one-block-at-point
   []

+ 34 - 28
src/main/frontend/search.cljs

@@ -2,19 +2,20 @@
   "Provides search functionality for a number of features including Cmd-K
   search. Most of these fns depend on the search protocol"
   (:require [clojure.string :as string]
+            [datascript.core :as d]
+            [frontend.common.search-fuzzy :as fuzzy]
+            [frontend.config :as config]
+            [frontend.db :as db]
+            [frontend.db.async :as db-async]
+            [frontend.db.model :as db-model]
+            [frontend.db.utils :as db-utils]
             [frontend.search.agency :as search-agency]
             [frontend.search.protocol :as protocol]
             [frontend.state :as state]
             [frontend.util :as util]
-            [promesa.core :as p]
-            [frontend.common.search-fuzzy :as fuzzy]
             [logseq.common.config :as common-config]
-            [frontend.db.async :as db-async]
-            [frontend.db :as db]
-            [frontend.db.model :as db-model]
-            [frontend.db.utils :as db-utils]
             [logseq.db :as ldb]
-            [datascript.core :as d]))
+            [promesa.core :as p]))
 
 (def fuzzy-search fuzzy/fuzzy-search)
 
@@ -35,15 +36,15 @@
   ([q limit]
    (when-let [repo (state/get-current-repo)]
      (let [q (fuzzy/clean-str q)]
-      (when-not (string/blank? q)
-        (p/let [mldoc-exts (set (map name common-config/mldoc-support-formats))
-                result (db-async/<get-files repo)
-                files (->> result
-                           (map first)
-                           (remove (fn [file]
-                                     (mldoc-exts (util/get-file-ext file)))))]
-          (when (seq files)
-            (fuzzy/fuzzy-search files q :limit limit))))))))
+       (when-not (string/blank? q)
+         (p/let [mldoc-exts (set (map name common-config/mldoc-support-formats))
+                 result (db-async/<get-files repo)
+                 files (->> result
+                            (map first)
+                            (remove (fn [file]
+                                      (mldoc-exts (util/get-file-ext file)))))]
+           (when (seq files)
+             (fuzzy/fuzzy-search files q :limit limit))))))))
 
 (defn template-search
   ([q]
@@ -51,11 +52,16 @@
   ([q limit]
    (when-let [repo (state/get-current-repo)]
      (when q
-       (p/let [q (fuzzy/clean-str q)
-               templates (db-async/<get-all-templates repo)]
-         (when (seq templates)
-           (let [result (fuzzy/fuzzy-search (keys templates) q {:limit limit})]
-             (vec (select-keys templates result)))))))))
+       (let [db-based? (config/db-based-graph?)]
+         (p/let [q (fuzzy/clean-str q)
+                 templates (if db-based?
+                             (db-async/<get-tag-objects repo (:db/id (db/entity :logseq.class/Template)))
+                             (p/let [result (db-async/<get-all-templates repo)]
+                               (vals result)))]
+           (when (seq templates)
+             (let [extract-fn (if db-based? :block/title :template)]
+               (fuzzy/fuzzy-search templates q {:limit limit
+                                                :extract-fn extract-fn})))))))))
 
 (defn property-search
   ([q]
@@ -78,13 +84,13 @@
   ([property q limit]
    (when-let [repo (state/get-current-repo)]
      (when q
-      (p/let [q (fuzzy/clean-str q)
-              result (db-async/<file-get-property-values repo (keyword property))]
-        (when (seq result)
-          (if (string/blank? q)
-            result
-            (let [result (fuzzy/fuzzy-search result q :limit limit)]
-              (vec result)))))))))
+       (p/let [q (fuzzy/clean-str q)
+               result (db-async/<file-get-property-values repo (keyword property))]
+         (when (seq result)
+           (if (string/blank? q)
+             result
+             (let [result (fuzzy/fuzzy-search result q :limit limit)]
+               (vec result)))))))))
 
 (defn rebuild-indices!
   ([]

+ 6 - 5
src/main/frontend/ui.cljs

@@ -908,8 +908,9 @@
 (rum/defc with-shortcut < rum/reactive
   < {:key-fn (fn [key pos] (str "shortcut-" key pos))}
   [shortcut-key position content]
-  (let [tooltip? (state/sub :ui/shortcut-tooltip?)]
-    (if tooltip?
+  (let [shortcut-tooltip? (state/sub :ui/shortcut-tooltip?)
+        enabled-tooltip? (state/enable-tooltip?)]
+    (if (and enabled-tooltip? shortcut-tooltip?)
       (tippy
        {:html [:div.text-sm.font-medium (keyboard-shortcut-from-config shortcut-key)]
         :interactive true
@@ -1017,9 +1018,9 @@
 (rum/defc tooltip
   [trigger tooltip-content & {:keys [trigger-props]}]
   (shui/tooltip-provider
-    (shui/tooltip
-      (shui/tooltip-trigger trigger-props trigger)
-      (shui/tooltip-content tooltip-content))))
+   (shui/tooltip
+    (shui/tooltip-trigger trigger-props trigger)
+    (shui/tooltip-content tooltip-content))))
 
 (rum/defc DelDateButton
   [on-delete]

+ 3 - 1
src/main/frontend/worker/db/migrate.cljs

@@ -755,7 +755,9 @@
    ["64.1" {:properties [:logseq.property.view/group-by-property]
             :fix add-view-icons}]
    ["64.2" {:properties [:logseq.property.view/feature-type]
-            :fix migrate-views}]])
+            :fix migrate-views}]
+   ["64.3" {:properties [:logseq.property/used-template :logseq.property/template-applied-to]
+            :classes [:logseq.class/Template]}]])
 
 (let [[major minor] (last (sort (map (comp (juxt :major :minor) db-schema/parse-schema-version first)
                                      schema-version->updates)))

+ 43 - 9
src/main/frontend/worker/pipeline.cljs

@@ -9,8 +9,8 @@
             [logseq.common.defkeywords :refer [defkeywords]]
             [logseq.db :as ldb]
             [logseq.db.frontend.validate :as db-validate]
-            [logseq.db.sqlite.util :as sqlite-util]
             [logseq.db.sqlite.export :as sqlite-export]
+            [logseq.db.sqlite.util :as sqlite-util]
             [logseq.graph-parser.exporter :as gp-exporter]
             [logseq.outliner.core :as outliner-core]
             [logseq.outliner.datascript-report :as ds-report]
@@ -46,6 +46,35 @@
                            :block/refs refs})))))
             blocks)))
 
+(defn- insert-tag-templates
+  [repo conn tx-report]
+  (let [db (:db-after tx-report)
+        tx-data (some->> (:tx-data tx-report)
+                         (filter (fn [d] (and (= (:a d) :block/tags) (:added d))))
+                         (group-by :e)
+                         (mapcat (fn [[e datoms]]
+                                   (let [object (d/entity db e)
+                                         template-blocks (->> (mapcat (fn [id]
+                                                                        (let [tag (d/entity db id)
+                                                                              parents (ldb/get-page-parents tag {:node-class? true})
+                                                                              templates (mapcat :logseq.property/_template-applied-to (conj parents tag))]
+                                                                          templates))
+                                                                      (set (map :v datoms)))
+                                                              distinct
+                                                              (sort-by :block/created-at)
+                                                              (mapcat (fn [template]
+                                                                        (let [template-blocks (rest (ldb/get-block-and-children db (:block/uuid template)
+                                                                                                                                {:include-property-block? true}))
+                                                                              blocks (->>
+                                                                                      (cons (assoc (first template-blocks) :logseq.property/used-template (:db/id template))
+                                                                                            (rest template-blocks))
+                                                                                      (map (fn [e] (assoc (into {} e) :db/id (:db/id e)))))]
+                                                                          blocks))))]
+                                     (when (seq template-blocks)
+                                       (let [result (outliner-core/insert-blocks repo conn template-blocks object {:sibling? false})]
+                                         (:tx-data result)))))))]
+    tx-data))
+
 (defkeywords
   ::skip-validate-db? {:doc "tx-meta option, default = false"}
   ::skip-store-conn {:doc "tx-meta option, skip `d/store` on conn. default = false"})
@@ -121,9 +150,12 @@
           commands-tx (when-not (or (:undo? tx-meta) (:redo? tx-meta) (:rtc-tx? tx-meta))
                         (commands/run-commands tx-report))
           ;; :block/refs relies on those changes
-          tx-before-refs (concat display-blocks-tx-data commands-tx)
+          ;; idea: implement insert-templates using a command?
+          insert-templates-tx (insert-tag-templates repo conn tx-report)
+          tx-before-refs (concat display-blocks-tx-data commands-tx insert-templates-tx)
           tx-report* (if (seq tx-before-refs)
-                       (let [result (ldb/transact! conn tx-before-refs {:pipeline-replace? true})]
+                       (let [result (ldb/transact! conn tx-before-refs {:pipeline-replace? true
+                                                                        :outliner-op :pre-hook-invoke})]
                          (assoc tx-report
                                 :tx-data (concat (:tx-data tx-report) (:tx-data result))
                                 :db-after (:db-after result)))
@@ -134,17 +166,19 @@
                 (doseq [page-id page-ids]
                   (when (d/entity @conn page-id)
                     (file/sync-to-file repo page-id tx-meta)))))
-          deleted-block-uuids (set (outliner-pipeline/filter-deleted-blocks (:tx-data tx-report*)))
+          deleted-blocks (outliner-pipeline/filter-deleted-blocks (:tx-data tx-report*))
+          deleted-block-ids (set (map :db/id deleted-blocks))
+          deleted-block-uuids (set (map :block/uuid deleted-blocks))
           deleted-assets (keep (fn [id]
-                                 (let [e (d/entity (:db-before tx-report*) [:block/uuid id])]
+                                 (let [e (d/entity (:db-before tx-report*) id)]
                                    (when (ldb/asset? e)
                                      {:block/uuid (:block/uuid e)
-                                      :ext (:logseq.property.asset/type e)}))) deleted-block-uuids)
-          blocks' (remove (fn [b] (deleted-block-uuids (:block/uuid b))) blocks)
+                                      :ext (:logseq.property.asset/type e)}))) deleted-block-ids)
+          blocks' (remove (fn [b] (deleted-block-ids (:db/id b))) blocks)
           block-refs (when (seq blocks')
                        (rebuild-block-refs repo tx-report* blocks'))
           refs-tx-report (when (seq block-refs)
-                           (ldb/transact! conn block-refs {:pipeline-replace? true}))
+                           (ldb/transact! conn (concat insert-templates-tx block-refs) {:pipeline-replace? true}))
           replace-tx (let [db-after (or (:db-after refs-tx-report) (:db-after tx-report*))]
                        (concat
                       ;; block path refs
@@ -153,7 +187,7 @@
                             (compute-block-path-refs-tx tx-report* blocks')))
 
                        ;; update block/tx-id
-                        (let [updated-blocks (remove (fn [b] (contains? (set deleted-block-uuids) (:block/uuid b)))
+                        (let [updated-blocks (remove (fn [b] (contains? deleted-block-ids (:db/id b)))
                                                      (concat pages blocks))
                               tx-id (get-in (or refs-tx-report tx-report*) [:tempids :db/current-tx])]
                           (keep (fn [b]

+ 47 - 48
src/test/frontend/modules/outliner/core_test.cljs

@@ -1,21 +1,21 @@
 (ns frontend.modules.outliner.core-test
   (:require [cljs.test :refer [deftest is use-fixtures testing] :as test]
+            [clojure.set :as set]
             [clojure.test.check.generators :as gen]
-            [frontend.test.fixtures :as fixtures]
-            [logseq.outliner.core :as outliner-core]
-            [frontend.modules.outliner.tree :as tree]
-            [logseq.outliner.transaction :as outliner-tx]
-            [frontend.db :as db]
-            [frontend.db.model :as db-model]
             [clojure.walk :as walk]
-            [logseq.graph-parser.block :as gp-block]
             [datascript.core :as d]
-            [frontend.test.helper :as test-helper :refer [load-test-files]]
-            [frontend.state :as state]
-            [clojure.set :as set]
+            [frontend.db :as db]
             [frontend.db.conn :as conn]
+            [frontend.db.model :as db-model]
+            [frontend.modules.outliner.tree :as tree]
+            [frontend.state :as state]
+            [frontend.test.fixtures :as fixtures]
+            [frontend.test.helper :as test-helper :refer [load-test-files]]
             [frontend.worker.db-listener :as worker-db-listener]
-            [logseq.db :as ldb]))
+            [logseq.db :as ldb]
+            [logseq.graph-parser.block :as gp-block]
+            [logseq.outliner.core :as outliner-core]
+            [logseq.outliner.transaction :as outliner-tx]))
 
 (def test-db test-helper/test-db)
 
@@ -165,10 +165,10 @@
    "
     (transact-tree! tree)
     (outliner-tx/transact!
-      (transact-opts)
-      (outliner-core/move-blocks! test-db
-                                  (db/get-db test-db false)
-                                  [(get-block 3)] (get-block 14) true))
+     (transact-opts)
+     (outliner-core/move-blocks! test-db
+                                 (db/get-db test-db false)
+                                 [(get-block 3)] (get-block 14) true))
     (is (= [6 9] (get-children 2)))
     (is (= [13 14 3 15] (get-children 12))))
 
@@ -188,10 +188,10 @@
    "
       (transact-tree! tree)
       (outliner-tx/transact!
-        (transact-opts)
-        (outliner-core/move-blocks! test-db
-                                  (db/get-db test-db false)
-                                  [(get-block 3)] (get-block 12) false))
+       (transact-opts)
+       (outliner-core/move-blocks! test-db
+                                   (db/get-db test-db false)
+                                   [(get-block 3)] (get-block 12) false))
       (is (= [6 9] (get-children 2)))
       (is (= [3 13 14 15] (get-children 12))))))
 
@@ -254,8 +254,8 @@
   "
     (transact-tree! tree)
     (outliner-tx/transact!
-      (transact-opts)
-      (outliner-core/indent-outdent-blocks! test-db (db/get-db test-db false) [(get-block 6) (get-block 9)] true))
+     (transact-opts)
+     (outliner-core/indent-outdent-blocks! test-db (db/get-db test-db false) [(get-block 6) (get-block 9)] true))
     (is (= [4 5 6 9] (get-children 3)))))
 
 (deftest test-indent-blocks-regression-5604
@@ -273,8 +273,8 @@
   "
     (transact-tree! tree)
     (outliner-tx/transact!
-      (transact-opts)
-      (outliner-core/indent-outdent-blocks! test-db (db/get-db test-db false) [(get-block 13) (get-block 14) (get-block 15)] false))
+     (transact-opts)
+     (outliner-core/indent-outdent-blocks! test-db (db/get-db test-db false) [(get-block 13) (get-block 14) (get-block 15)] false))
     (is (= [2 12 13 14 15 16] (get-children 22))))
   (testing "
   [22 [[2 [[3
@@ -290,8 +290,8 @@
   "
     (transact-tree! tree)
     (outliner-tx/transact!
-      (transact-opts)
-      (outliner-core/indent-outdent-blocks! test-db (db/get-db test-db false) [(get-block 13) (get-block 14)] false))
+     (transact-opts)
+     (outliner-core/indent-outdent-blocks! test-db (db/get-db test-db false) [(get-block 13) (get-block 14)] false))
     (is (= [2 12 13 14 16] (get-children 22)))))
 
 (deftest test-outdent-blocks
@@ -309,8 +309,8 @@
   "
     (transact-tree! tree)
     (outliner-tx/transact!
-      (transact-opts)
-      (outliner-core/indent-outdent-blocks! test-db (db/get-db test-db false) [(get-block 4) (get-block 5)] false))
+     (transact-opts)
+     (outliner-core/indent-outdent-blocks! test-db (db/get-db test-db false) [(get-block 4) (get-block 5)] false))
     (is (= [3 4 5 6 9] (get-children 2)))))
 
 (deftest test-delete-blocks
@@ -328,10 +328,10 @@
 "
     (transact-tree! tree)
     (outliner-tx/transact!
-      (transact-opts)
-      (outliner-core/delete-blocks! test-db (db/get-db test-db false)
-                                    (state/get-date-formatter)
-                                    [(get-block 6) (get-block 9)] {}))
+     (transact-opts)
+     (outliner-core/delete-blocks! test-db (db/get-db test-db false)
+                                   (state/get-date-formatter)
+                                   [(get-block 6) (get-block 9)] {}))
     (is (= [3] (get-children 2)))))
 
 (deftest test-delete-non-consecutive-blocks
@@ -370,8 +370,8 @@
   "
     (transact-tree! tree)
     (outliner-tx/transact!
-      (transact-opts)
-      (outliner-core/move-blocks-up-down! test-db (db/get-db test-db false) [(get-block 9)] true))
+     (transact-opts)
+     (outliner-core/move-blocks-up-down! test-db (db/get-db test-db false) [(get-block 9)] true))
     (is (= [3 9 6] (get-children 2)))))
 
 (deftest test-insert-blocks
@@ -393,7 +393,7 @@
                                     [21]])
           target-block (get-block 6)]
       (outliner-tx/transact!
-        (transact-opts)
+       (transact-opts)
        (outliner-core/insert-blocks!
         test-db
         (db/get-db test-db false)
@@ -419,13 +419,14 @@
                             :block/title ""}])
     (let [target-block (get-block 22)]
       (outliner-tx/transact!
-        (transact-opts)
+       (transact-opts)
        (outliner-core/insert-blocks!
         test-db
         (db/get-db test-db false)
         [{:block/title "test"
           :block/parent 1
-          :block/page 1}]
+          :block/page 1
+          :block/uuid (random-uuid)}]
         target-block
         {:sibling? false
          :outliner-op :paste
@@ -594,13 +595,13 @@ tags:: tag1, tag2
 (defn insert-blocks!
   [blocks target]
   (outliner-tx/transact! (transact-opts)
-    (outliner-core/insert-blocks! test-db
-                                  (db/get-db test-db false)
-                                  blocks
-                                  target
-                                  {:sibling? (gen/generate gen/boolean)
-                                   :keep-uuid? (gen/generate gen/boolean)
-                                   :replace-empty-target? (gen/generate gen/boolean)})))
+                         (outliner-core/insert-blocks! test-db
+                                                       (db/get-db test-db false)
+                                                       blocks
+                                                       target
+                                                       {:sibling? (gen/generate gen/boolean)
+                                                        :keep-uuid? (gen/generate gen/boolean)
+                                                        :replace-empty-target? (gen/generate gen/boolean)})))
 
 (defn transact-random-tree!
   []
@@ -688,7 +689,7 @@ tags:: tag1, tag2
           (when (seq blocks)
             (let [target (get-random-block)]
               (outliner-tx/transact! (transact-opts)
-                (outliner-core/move-blocks! test-db (db/get-db test-db false) blocks target (gen/generate gen/boolean)))
+                                     (outliner-core/move-blocks! test-db (db/get-db test-db false) blocks target (gen/generate gen/boolean)))
               (let [total (get-blocks-count)]
                 (is (= total (count @*random-blocks)))))))))))
 
@@ -725,7 +726,7 @@ tags:: tag1, tag2
                 indent? (gen/generate gen/boolean)]
             (when (seq blocks)
               (outliner-tx/transact! (transact-opts)
-                (outliner-core/indent-outdent-blocks! test-db (db/get-db test-db false) blocks indent?))
+                                     (outliner-core/indent-outdent-blocks! test-db (db/get-db test-db false) blocks indent?))
               (let [total (get-blocks-count)]
                 (is (= total (count @*random-blocks)))))))))))
 
@@ -861,6 +862,4 @@ tags:: tag1, tag2
 
   (do
     (frontend.test.fixtures/reset-datascript test-db)
-    (cljs.test/test-vars [#'test-paste-first-empty-block]))
-
-  )
+    (cljs.test/test-vars [#'test-paste-first-empty-block])))