Browse Source

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

Tienson Qin 9 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
         run: cd deps/db && yarn install --frozen-lockfile
 
 
       - name: Validate created DB graphs
       - 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:
   e2e-test:
     # TODO: Re-enable when ready to enable tests for file graphs
     # 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"
   {:doc "Validate a DB graph's datascript schema"
    :requires ([babashka.fs :as fs])
    :requires ([babashka.fs :as fs])
    :task (apply shell {:dir "deps/db" :extra-env {"ORIGINAL_PWD" (fs/cwd)}}
    :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*)}
                 *command-line-args*)}
 
 
   dev:db-query
   dev:db-query
@@ -81,7 +81,7 @@
   {:doc "Create a DB graph given a sqlite.build EDN file"
   {:doc "Create a DB graph given a sqlite.build EDN file"
    :requires ([babashka.fs :as fs])
    :requires ([babashka.fs :as fs])
    :task (apply shell {:dir "deps/db" :extra-env {"ORIGINAL_PWD" (fs/cwd)}}
    :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
   dev:db-import
   {:doc "Import a file graph to db graph"
   {: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"
   logseq/clj-fractional-indexing        {:git/url "https://github.com/logseq/clj-fractional-indexing"
                                          :sha     "7182b7878410f78536dc2b6df35ed32ef9cd6b61"}
                                          :sha     "7182b7878410f78536dc2b6df35ed32ef9cd6b61"}
   io.github.nextjournal/nbb-test-runner
   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"
     "better-sqlite3": "9.3.0"
   },
   },
   "scripts": {
   "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
 (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.edn :as edn]
+            [clojure.string :as string]
             [datascript.core :as d]
             [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.classpath :as cp]
-            [nbb.core :as nbb]))
+            [nbb.core :as nbb]
+            [validate-db]))
 
 
 (defn- resolve-path
 (defn- resolve-path
   "If relative path, resolve with $ORIGINAL_PWD"
   "If relative path, resolve with $ORIGINAL_PWD"
@@ -17,11 +20,20 @@
     path
     path
     (node-path/join (or js/process.env.ORIGINAL_PWD ".") 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]
 (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 "/")
         [dir db-name] (if (string/includes? graph-dir "/")
                         ((juxt node-path/dirname node-path/basename) graph-dir)
                         ((juxt node-path/dirname node-path/basename) graph-dir)
                         [(node-path/join (os/homedir) "logseq" "graphs") graph-dir])
                         [(node-path/join (os/homedir) "logseq" "graphs") graph-dir])
@@ -34,7 +46,9 @@
     ;; (cljs.pprint/pprint _txs)
     ;; (cljs.pprint/pprint _txs)
     (d/transact! conn init-tx)
     (d/transact! conn init-tx)
     (d/transact! conn block-props-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*))
   (-main *command-line-args*))

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

@@ -1,14 +1,14 @@
   (ns dump-datoms
   (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"
      $ 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]
               [clojure.pprint :as pprint]
+              [datascript.core :as d]
               [logseq.db.sqlite.cli :as sqlite-cli]
               [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
 (defn read-graph
   "The db graph bare version of gp-cli/parse-graph"
   "The db graph bare version of gp-cli/parse-graph"
@@ -28,5 +28,5 @@
     (println "Writing" (count datoms) "datoms to" file)
     (println "Writing" (count datoms) "datoms to" file)
     (fs/writeFileSync file (with-out-str (pprint/pprint datoms)))))
     (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*))
   (-main *command-line-args*))

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

@@ -1,18 +1,18 @@
 (ns query
 (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]]'"
   $ 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]
             [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]
             [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
 (defn- sh
   "Run shell cmd synchronously and print to inherited streams by default. Aims
   "Run shell cmd synchronously and print to inherited streams by default. Aims
@@ -44,8 +44,8 @@
             :desc "Lookup entities instead of query"}})
             :desc "Lookup entities instead of query"}})
 
 
 (defn -main [args]
 (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))
         _ (when (or (nil? graph-dir) (:help options))
             (println (str "Usage: $0 GRAPH-NAME [& ARGS] [OPTIONS]\nOptions:\n"
             (println (str "Usage: $0 GRAPH-NAME [& ARGS] [OPTIONS]\nOptions:\n"
                           (cli/format-opts {:spec spec})))
                           (cli/format-opts {:spec spec})))
@@ -60,7 +60,7 @@
                             (update :block/properties (fn [props] (map (fn [m] (into {} m)) props)))))
                             (update :block/properties (fn [props] (map (fn [m] (into {} m)) props)))))
                        (:entity options))
                        (:entity options))
                   ;; assumes no :in are in queries
                   ;; 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))]
                         res (d/q query @conn (rules/extract-rules rules/db-query-dsl-rules))]
                     ;; Remove nesting for most queries which just have one :find binding
                     ;; Remove nesting for most queries which just have one :find binding
                     (if (= 1 (count (first res))) (mapv first res) res)))]
                     (if (= 1 (count (first res))) (mapv first res) res)))]
@@ -71,5 +71,5 @@
         (sh ["puget"] {:input (pr-str results) :stdio ["pipe" "inherit" "inherit"]})
         (sh ["puget"] {:input (pr-str results) :stdio ["pipe" "inherit" "inherit"]})
         (pprint/pprint results)))))
         (pprint/pprint results)))))
 
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))
   (-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
   "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"
    NOTE: This script is also used in CI to confirm our db's schema is up to date"
   (:require ["os" :as os]
   (:require ["os" :as os]
@@ -14,7 +14,7 @@
             [malli.error :as me]
             [malli.error :as me]
             [nbb.core :as nbb]))
             [nbb.core :as nbb]))
 
 
-(defn validate-client-db
+(defn validate-db*
   "Validate datascript db as a vec of entity maps"
   "Validate datascript db as a vec of entity maps"
   [db ent-maps* {:keys [verbose group-errors humanize closed-maps]}]
   [db ent-maps* {:keys [verbose group-errors humanize closed-maps]}]
   (let [ent-maps (db-malli-schema/update-properties-in-ents db ent-maps*)
   (let [ent-maps (db-malli-schema/update-properties-in-ents db ent-maps*)
@@ -66,6 +66,14 @@
                   :default true
                   :default true
                   :desc "Groups errors by their entity id"}})
                   :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]
 (defn- validate-graph [graph-dir options]
   (let [[dir db-name] (if (string/includes? graph-dir "/")
   (let [[dir db-name] (if (string/includes? graph-dir "/")
                         (let [graph-dir'
                         (let [graph-dir'
@@ -75,13 +83,8 @@
         conn (try (sqlite-cli/open-db! dir db-name)
         conn (try (sqlite-cli/open-db! dir db-name)
                   (catch :default e
                   (catch :default e
                     (println "Error: For graph" (str (pr-str graph-dir) ":") (str 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]
 (defn -main [argv]
   (let [{:keys [args opts]} (cli/parse-args argv {:spec spec})
   (let [{:keys [args opts]} (cli/parse-args argv {:spec spec})
@@ -92,5 +95,5 @@
     (doseq [graph-dir args]
     (doseq [graph-dir args]
       (validate-graph graph-dir opts))))
       (validate-graph graph-dir opts))))
 
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))
   (-main *command-line-args*))

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

@@ -334,7 +334,7 @@
      (sort-by-order (:block/_parent parent)))))
      (sort-by-order (:block/_parent parent)))))
 
 
 (defn get-block-parents
 (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
   (loop [block-id block-id
          parents' (list)
          parents' (list)
          d 1]
          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]
                             :logseq.property.pdf/hl-type :logseq.property.pdf/hl-image]
                :required-properties [:logseq.property/ls-type :logseq.property.pdf/hl-color :logseq.property/asset
                :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.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)
      ;; TODO: Add more classes such as :book, :paper, :movie, :music, :project)
      )))
      )))
 
 
@@ -133,4 +138,4 @@
 (defn logseq-class?
 (defn logseq-class?
   "Determines if keyword is a logseq class"
   "Determines if keyword is a logseq class"
   [kw]
   [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
                             :attribute :block/tags
                             :schema {:type :class
                             :schema {:type :class
                                      :cardinality :many
                                      :cardinality :many
-                                     :public? true
-                                     :classes #{:logseq.class/Root}}
+                                     :public? true}
                             :queryable? true}
                             :queryable? true}
      :block/parent         {:title "Node parent"
      :block/parent         {:title "Node parent"
                             :attribute :block/parent
                             :attribute :block/parent
@@ -154,7 +153,9 @@
                               :schema {:type :node
                               :schema {:type :node
                                        :public? true
                                        :public? true
                                        :view-context :page}
                                        :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"
      :logseq.property/default-value {:title "Default value"
                                      :schema {:type :entity
                                      :schema {:type :entity
                                               :public? false
                                               :public? false
@@ -173,7 +174,9 @@
      :logseq.property/hide-empty-value {:title "Hide empty value"
      :logseq.property/hide-empty-value {:title "Hide empty value"
                                         :schema {:type :checkbox
                                         :schema {:type :checkbox
                                                  :public? true
                                                  :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"
      :logseq.property.class/hide-from-node {:title "Hide from Node"
                                             :schema {:type :checkbox
                                             :schema {:type :checkbox
                                                      :public? true
                                                      :public? true
@@ -187,7 +190,9 @@
                                  :schema {:type :page
                                  :schema {:type :page
                                           :public? true
                                           :public? true
                                           :view-context :page
                                           :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"
      :logseq.property/background-color {:title "Background color"
                                         :schema {:type :default :hide? true}}
                                         :schema {:type :default :hide? true}}
      :logseq.property/background-image {:title "Background image"
      :logseq.property/background-image {:title "Background image"
@@ -549,7 +554,9 @@
      :logseq.property/enable-history? {:title "Enable property history"
      :logseq.property/enable-history? {:title "Enable property history"
                                        :schema {:type :checkbox
                                        :schema {:type :checkbox
                                                 :public? true
                                                 :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"
      :logseq.property.history/block {:title "History block"
                                      :schema {:type :entity
                                      :schema {:type :entity
                                               :hide? true}}
                                               :hide? true}}
@@ -567,7 +574,17 @@
                                            ;; - avoid losing this attr when the user-block is deleted
                                            ;; - avoid losing this attr when the user-block is deleted
                                            ;; - related user-block maybe not exists yet in graph
                                            ;; - related user-block maybe not exists yet in graph
                                            :type :string
                                            :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
 (def built-in-properties
   (->> built-in-properties*
   (->> built-in-properties*

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

@@ -37,7 +37,7 @@
          (map (juxt :major :minor)
          (map (juxt :major :minor)
               [(parse-schema-version x) (parse-schema-version y)])))
               [(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
 (defn major-version
   "Return a number.
   "Return a number.

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

@@ -183,14 +183,14 @@
              {:property-attributes
              {:property-attributes
               (merge {:db/id (or (property-db-ids prop-name)
               (merge {:db/id (or (property-db-ids prop-name)
                                  (throw (ex-info "No :db/id for property" {:property 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)
           [(merge (sqlite-util/build-new-property (get-ident all-idents prop-name)
                                                   (db-property/get-property-schema prop-m)
                                                   (db-property/get-property-schema prop-m)
                                                   {:block-uuid (:block/uuid prop-m)
                                                   {:block-uuid (:block/uuid prop-m)
                                                    :title (:block/title prop-m)})
                                                    :title (:block/title prop-m)})
                   {:db/id (or (property-db-ids prop-name)
                   {:db/id (or (property-db-ids prop-name)
                               (throw (ex-info "No :db/id for property" {:property 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
         pvalue-tx-m
         (->property-value-tx-m new-block (:build/properties prop-m) properties all-idents)]
         (->property-value-tx-m new-block (:build/properties prop-m) properties all-idents)]
     (cond-> []
     (cond-> []
@@ -344,7 +344,7 @@
                                              (mapcat (fn [m]
                                              (mapcat (fn [m]
                                                        (if-let [pvalue-pages
                                                        (if-let [pvalue-pages
                                                                 (->> (vals (:build/properties m))
                                                                 (->> (vals (:build/properties m))
-                                                                     (mapcat #(if (set? %) % [%]) )
+                                                                     (mapcat #(if (set? %) % [%]))
                                                                      (filter page-prop-value?)
                                                                      (filter page-prop-value?)
                                                                      (map second)
                                                                      (map second)
                                                                      seq)]
                                                                      seq)]
@@ -415,8 +415,8 @@
   (vec
   (vec
    (mapcat
    (mapcat
     (fn [{:keys [page blocks]}]
     (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
                     page
                     (merge
                     (merge
                      ;; TODO: Use sqlite-util/build-new-page
                      ;; TODO: Use sqlite-util/build-new-page
@@ -430,8 +430,8 @@
                           page-id-fn)]
                           page-id-fn)]
         (into
         (into
          ;; page tx
          ;; 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))
            (build-page-tx page' all-idents page-uuids properties))
          ;; blocks tx
          ;; blocks tx
          (reduce (fn [acc m]
          (reduce (fn [acc m]
@@ -618,12 +618,18 @@
                                  m))
                                  m))
                              properties-tx)
                              properties-tx)
         pages-and-blocks-tx (build-pages-and-blocks-tx pages-and-blocks' all-idents page-uuids
         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
 ;; Public API
 ;; ==========
 ;; ==========
@@ -701,8 +707,8 @@
     See auto-create-ontology for more details
     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
   * :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.
      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]`
   * :page-id-fn - custom fn that returns ent lookup id for page refs e.g. `[:block/uuid X]`
     Default is :db/id
     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"
   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
   ;; 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)
         bootstrap-properties (map build-bootstrap-property bootstrap-idents)
         ;; First tx bootstrap properties so they can take affect. Then tx the bootstrap properties on themselves
         ;; 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)
         bootstrap-properties-tx (into (mapv #(apply dissoc % bootstrap-idents) bootstrap-properties)
@@ -226,7 +228,7 @@
         hidden-pages (concat (build-initial-views) (build-favorites-page))
         hidden-pages (concat (build-initial-views) (build-favorites-page))
         ;; These classes bootstrap our tags and properties as they depend on each other e.g.
         ;; 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
         ;; 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-classes (filter bootstrap-class? default-classes)
         bootstrap-class-ids (map #(select-keys % [:db/ident :block/uuid]) bootstrap-classes)
         bootstrap-class-ids (map #(select-keys % [:db/ident :block/uuid]) bootstrap-classes)
         classes-tx (concat (map #(dissoc % :db/ident) 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.content :as db-content]
             [logseq.db.frontend.entity-plus :as entity-plus]
             [logseq.db.frontend.entity-plus :as entity-plus]
             [logseq.db.frontend.entity-util :as entity-util]
             [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.frontend.property :as db-property]
             [logseq.db.sqlite.build :as sqlite-build]))
             [logseq.db.sqlite.build :as sqlite-build]))
 
 
@@ -48,8 +49,11 @@
         [:build/page {:build/journal (:block/journal-day pvalue)}]
         [:build/page {:build/journal (:block/journal-day pvalue)}]
         :else
         :else
         (if (= :node (:logseq.property/type property-ent))
         (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)
           (or (:db/ident pvalue)
               ;; nbb-compatible version of db-property/property-value-content
               ;; nbb-compatible version of db-property/property-value-content
               (or (block-title pvalue)
               (or (block-title pvalue)
@@ -177,16 +181,16 @@
 (defn- build-node-export
 (defn- build-node-export
   "Given a block/page entity and optional existing properties, build an export map of its
   "Given a block/page entity and optional existing properties, build an export map of its
    tags and properties"
    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)}}]
               :or {include-uuid-fn (constantly false)}}]
   (let [ent-properties (dissoc (db-property/properties entity) :block/tags)
   (let [ent-properties (dissoc (db-property/properties entity) :block/tags)
         build-tags (when (seq (:block/tags entity)) (->build-tags (:block/tags entity)))
         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))
         new-properties (when-not shallow-copy? (build-node-properties db entity ent-properties properties))
         build-node (cond-> {:block/title (block-title entity)}
         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))
                      (include-uuid-fn (:block/uuid entity))
                      (assoc :block/uuid (:block/uuid entity) :build/keep-uuid? true)
                      (assoc :block/uuid (:block/uuid entity) :build/keep-uuid? true)
-                     keep-uuid?
-                     (assoc :build/keep-uuid? true)
                      (and (not shallow-copy?) (seq build-tags))
                      (and (not shallow-copy?) (seq build-tags))
                      (assoc :build/tags build-tags)
                      (assoc :build/tags build-tags)
                      (and (not shallow-copy?) (seq ent-properties))
                      (and (not shallow-copy?) (seq ent-properties))
@@ -247,12 +251,16 @@
       classes (assoc :classes classes))))
       classes (assoc :classes classes))))
 
 
 (defn- build-content-ref-export
 (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"
    entities found in block refs include their uuid in order to preserve the relationship to the blocks"
   [db blocks*]
   [db blocks*]
   (let [;; Remove property value blocks that can't have content refs
   (let [;; Remove property value blocks that can't have content refs
         blocks (remove :logseq.property/value blocks*)
         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-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)
         content-ref-pages (filter #(or (entity-util/internal-page? %) (entity-util/journal? %)) content-ref-ents)
         {:keys [properties classes]}
         {:keys [properties classes]}
@@ -326,7 +334,6 @@
                       (merge (build-blocks-export db
                       (merge (build-blocks-export db
                                                   (sort-by :block/order blocks)
                                                   (sort-by :block/order blocks)
                                                   {:include-uuid-fn (constantly true)
                                                   {:include-uuid-fn (constantly true)
-                                                   :keep-uuid? true
                                                    ;; shallow copy to disallow failing pvalues
                                                    ;; shallow copy to disallow failing pvalues
                                                    :shallow-copy? true})
                                                    :shallow-copy? true})
                              {:page (shallow-copy-page parent-page-ent)})))))]
                              {: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)))
                      (mapcat #(sqlite-build/extract-from-blocks (:blocks %) (fn [m] (some-> m :block/uuid vector)))
                              pages-and-blocks))
                              pages-and-blocks))
              set)
              set)
+        ;; only looks one-level deep in properties e.g. not inside :build/page
         ref-uuids
         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)]
     (set/difference ref-uuids known-uuids)))
     (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
                                        :block/order ;; TODO: block/order should be same as well
                                        ))
                                        ))
                      init-data*)))]
                      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 ""))
             (data/diff (remove-ignored-attrs&entities (sqlite-create-graph/build-db-initial-data ""))
                        (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)
         (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))
                                       (into (vec (remove #(= "hola" (:block/title %)) bs))
                                             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
 (deftest import-page-with-different-page-and-classes
   (let [original-data
   (let [original-data
         {:properties {:user.property/p1 {:logseq.property/type :default}
         {:properties {:user.property/p1 {:logseq.property/type :default}
@@ -392,7 +413,8 @@
                     {:block/title "node block"
                     {:block/title "node block"
                      :build/properties {:user.property/node #{[:build/page {:block/title "page object"
                      :build/properties {:user.property/node #{[:build/page {:block/title "page object"
                                                                             :build/tags [:user.class/MyClass]}]
                                                                             :build/tags [:user.class/MyClass]}]
-                                                              [:block/uuid block-object-uuid]}}}]}
+                                                              [:block/uuid block-object-uuid]
+                                                              :logseq.class/Task}}}]}
           {:page {:block/title "Blocks"}
           {:page {:block/title "Blocks"}
            :blocks [{:block/title "myclass object"
            :blocks [{:block/title "myclass object"
                      :build/tags [:user.class/MyClass]
                      :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"))
       (when (:verbose options') (println "Transacted" (count (d/datoms @conn :eavt)) "datoms"))
       (println "Created graph" (str db-name "!")))))
       (println "Created graph" (str db-name "!")))))
 
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))
   (-main *command-line-args*))

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

@@ -561,7 +561,7 @@
 (defn- macro->block
 (defn- macro->block
   "macro: {:name \"\" arguments [\"\"]}"
   "macro: {:name \"\" arguments [\"\"]}"
   [macro]
   [macro]
-  {:block/uuid (random-uuid)
+  {:block/uuid (common-uuid/gen-uuid)
    :block/type "macro"
    :block/type "macro"
    :block/properties {:logseq.macro-name (:name macro)
    :block/properties {:logseq.macro-name (:name macro)
                       :logseq.macro-arguments (:arguments 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]
             [clojure.string :as string]
             [datascript.core :as d]
             [datascript.core :as d]
             [logseq.common.util :as common-util]
             [logseq.common.util :as common-util]
+            [logseq.common.uuid :as common-uuid]
             [logseq.db :as ldb]
             [logseq.db :as ldb]
             [logseq.db.file-based.schema :as file-schema]))
             [logseq.db.file-based.schema :as file-schema]))
 
 
@@ -23,7 +24,7 @@
   [title]
   [title]
   {:block/name (string/lower-case title)
   {:block/name (string/lower-case title)
    :block/title title
    :block/title title
-   :block/uuid (random-uuid)
+   :block/uuid (common-uuid/gen-uuid)
    :block/type "page"})
    :block/type "page"})
 
 
 (def built-in-pages
 (def built-in-pages

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

@@ -1,14 +1,14 @@
 (ns transact
 (ns transact
   "This script generically runs transactions against the queried blocks"
   "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.edn :as edn]
             [clojure.string :as string]
             [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]
 (defn -main [args]
   (when (< (count args) 3)
   (when (< (count args) 3)
@@ -37,5 +37,5 @@
         (d/transact! conn update-tx)
         (d/transact! conn update-tx)
         (println "Updated" (count update-tx) "block(s) for graph" (str db-name "!"))))))
         (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*))
   (-main *command-line-args*))

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

@@ -2,9 +2,12 @@
   "Provides the primary outliner operations and fns"
   "Provides the primary outliner operations and fns"
   (:require [clojure.set :as set]
   (:require [clojure.set :as set]
             [clojure.string :as string]
             [clojure.string :as string]
+            [clojure.walk :as walk]
             [datascript.core :as d]
             [datascript.core :as d]
             [datascript.impl.entity :as de :refer [Entity]]
             [datascript.impl.entity :as de :refer [Entity]]
             [logseq.common.util :as common-util]
             [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 :as ldb]
             [logseq.db.common.order :as db-order]
             [logseq.db.common.order :as db-order]
             [logseq.db.file-based.schema :as file-schema]
             [logseq.db.file-based.schema :as file-schema]
@@ -497,22 +500,62 @@
      {}
      {}
      block)))
      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
 (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)
   (let [block-uuids (map :block/uuid blocks)
         uuids (zipmap block-uuids
         uuids (zipmap block-uuids
                       (if keep-uuid?
                       (if keep-uuid?
                         block-uuids
                         block-uuids
-                        (repeatedly random-uuid)))
+                        (repeatedly common-uuid/gen-uuid)))
         uuids (if (and (not keep-uuid?) replace-empty-target?)
         uuids (if (and (not keep-uuid?) replace-empty-target?)
                 (assoc uuids (:block/uuid (first blocks)) (:block/uuid target-block))
                 (assoc uuids (:block/uuid (first blocks)) (:block/uuid target-block))
                 uuids)
                 uuids)
         id->new-uuid (->> (map (fn [block] (when-let [id (:db/id block)]
         id->new-uuid (->> (map (fn [block] (when-let [id (:db/id block)]
                                              [id (get uuids (:block/uuid block))])) blocks)
                                              [id (get uuids (:block/uuid block))])) blocks)
                           (into {}))
                           (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]
         get-new-id (fn [block lookup]
                      (cond
                      (cond
                        (or (map? lookup) (vector? lookup) (de/entity? lookup))
                        (or (map? lookup) (vector? lookup) (de/entity? lookup))
@@ -526,26 +569,9 @@
 
 
                        :else
                        :else
                        (throw (js/Error. (str "[insert-blocks] illegal lookup: " lookup ", block: " block)))))
                        (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
 (defn- get-target-block
   [db blocks target-block {:keys [outliner-op indent? sibling? up?]}]
   [db blocks target-block {:keys [outliner-op indent? sibling? up?]}]
@@ -614,7 +640,7 @@
               m' (vec (conj m block))]
               m' (vec (conj m block))]
           (recur m' (rest blocks)))))))
           (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.
   "Insert blocks as children (or siblings) of target-node.
   Args:
   Args:
     `conn`: db connection.
     `conn`: db connection.
@@ -634,12 +660,27 @@
     ``"
     ``"
   [repo conn blocks target-block {:keys [_sibling? keep-uuid? keep-block-order?
   [repo conn blocks target-block {:keys [_sibling? keep-uuid? keep-block-order?
                                          outliner-op replace-empty-target? update-timestamps?
                                          outliner-op replace-empty-target? update-timestamps?
-                                         created-by]
+                                         created-by insert-template?]
                                   :as opts
                                   :as opts
                                   :or {update-timestamps? true}}]
                                   :or {update-timestamps? true}}]
   {:pre [(seq blocks)
   {:pre [(seq blocks)
          (m/validate block-map-or-entity target-block)]}
          (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))
         _ (assert (some? target-block) (str "Invalid target: " target-block))
         sibling? (if (ldb/page? target-block) false sibling?)
         sibling? (if (ldb/page? target-block) false sibling?)
         replace-empty-target? (if (and (some? replace-empty-target?)
         replace-empty-target? (if (and (some? replace-empty-target?)
@@ -650,48 +691,60 @@
                                      (:block/title target-block)
                                      (:block/title target-block)
                                      (string/blank? (:block/title target-block))
                                      (string/blank? (:block/title target-block))
                                      (> (count blocks) 1)))
                                      (> (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
 (defn- sort-non-consecutive-blocks
   [db blocks]
   [db blocks]

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

@@ -15,7 +15,8 @@
   (keep
   (keep
    (fn [d]
    (fn [d]
      (when (and (= :block/uuid (:a d)) (false? (:added d)))
      (when (and (= :block/uuid (:a d)) (false? (:added d)))
-       (:v d)))
+       {:db/id (:e d)
+        :block/uuid (:v d)}))
    datoms))
    datoms))
 
 
 (defn- calculate-children-refs
 (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
     ;; only value-ref-property types should call this
     (find-or-create-property-value conn property-id v)))
     (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!
 (defn set-block-property!
   "Updates a block property's value for an existing property-id and block.  If
   "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
   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)
       (let [property (d/entity @conn property-id)
             _ (assert (some? property) (str "Property " property-id " doesn't exist yet"))
             _ (assert (some? property) (str "Property " property-id " doesn't exist yet"))
             property-type (get property :logseq.property/type :default)
             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)
                         (convert-ref-property-value conn property-id v property-type)
                         v)
                         v)
             existing-value (get block property-id)]
             existing-value (get block property-id)]
+        (throw-error-if-self-value block new-value ref?)
         (when-not (= existing-value new-value)
         (when-not (= existing-value new-value)
           (raw-set-block-property! conn block property property-type 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"))
         _ (assert (some? property) (str "Property " property-id " doesn't exist yet"))
         property-type (get property :logseq.property/type :default)
         property-type (get property :logseq.property/type :default)
         _ (assert (some? v) "Can't set a nil property value must be not nil")
         _ (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)
              (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!
 (defn batch-remove-property!
   [conn block-ids property-id]
   [conn block-ids property-id]

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

@@ -1,13 +1,13 @@
 (ns publishing
 (ns publishing
   "Basic script for publishing from CLI"
   "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]
             ["path" :as node-path]
             [clojure.edn :as edn]
             [clojure.edn :as edn]
             [datascript.core :as d]
             [datascript.core :as d]
+            [logseq.db.sqlite.cli :as sqlite-cli]
             [logseq.db.sqlite.util :as sqlite-util]
             [logseq.db.sqlite.util :as sqlite-util]
+            [logseq.graph-parser.cli :as gp-cli]
+            [logseq.publishing :as publishing]
             [nbb.core :as nbb]))
             [nbb.core :as nbb]))
 
 
 (defn- get-db [graph-dir]
 (defn- get-db [graph-dir]
@@ -60,5 +60,5 @@
       (publish-db-graph static-dir graph-dir output-path options)
       (publish-db-graph static-dir graph-dir output-path options)
       (publish-file-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*))
   (-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)))
   ([] (when-let [id (some-> (get-popups) (last) :id)] (hide! id 0)))
   ([id] (hide! id 0 {}))
   ([id] (hide! id 0 {}))
   ([id delay] (hide! id delay {}))
   ([id delay] (hide! id delay {}))
-  ([id delay {:keys [all?]}]
+  ([id delay {:keys [_all?]}]
    (when-let [popup (get-popup id)]
    (when-let [popup (get-popup id)]
      (let [config (last popup)
      (let [config (last popup)
            target (:target config)
            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-> (:on-before-hide config) (apply []))
        (some-> target (d/remove-attr! "data-popup-active"))
        (some-> target (d/remove-attr! "data-popup-active"))
        (if (and (number? delay) (> delay 0))
        (if (and (number? delay) (> delay 0))
@@ -147,20 +146,6 @@
            auto-side? _auto-focus? _target root-props content-props
            auto-side? _auto-focus? _target root-props content-props
            _on-before-hide _on-after-hide]
            _on-before-hide _on-after-hide]
     :as _props}]
     :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]
   (when-let [[x y _ height] position]
     (let [popup-root (if (not force-popover?) dropdown-menu popover)
     (let [popup-root (if (not force-popover?) dropdown-menu popover)
           popup-trigger (if (not force-popover?) dropdown-menu-trigger popover-trigger)
           popup-trigger (if (not force-popover?) dropdown-menu-trigger popover-trigger)
@@ -175,7 +160,12 @@
           auto-side? (if (boolean? auto-side?) auto-side? true)
           auto-side? (if (boolean? auto-side?) auto-side? true)
           content-props (cond-> content-props
           content-props (cond-> content-props
                           auto-side? (assoc :side (auto-side-fn)))
                           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
       (popup-root
        (merge root-props {:open open?})
        (merge root-props {:open open?})
        (popup-trigger
        (popup-trigger
@@ -187,10 +177,9 @@
                          :width 1
                          :width 1
                          :top y
                          :top y
                          :left x}} ""))
                          :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?)
                              (and (not force-popover?)
                                   (not as-dropdown?))
                                   (not as-dropdown?))
                              (assoc :on-key-down (fn [^js e]
                              (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;
   @apply overflow-y-auto;
 
 
   &[data-side=top] {
   &[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] {
   &[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 {
   &.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
 (ns logseq.tasks.db-graph.create-graph-with-large-sizes
   "Script that generates graphs at 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]
             [clojure.string :as string]
             [datascript.core :as d]
             [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.classpath :as cp]
             [nbb.core :as nbb]))
             [nbb.core :as nbb]))
 
 
@@ -83,5 +83,5 @@
     #_(d/transact! conn blocks-tx)
     #_(d/transact! conn blocks-tx)
     (println "Created graph" (str db-name " with " (count (d/datoms @conn :eavt)) " datoms!"))))
     (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*))
   (-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)
     (d/transact! conn block-props-tx)
     (println "Created graph" (str db-name " with " (count (d/datoms @conn :eavt)) " datoms!"))))
     (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*))
   (-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))
     (when (:debug options) (write-debug-file @conn))
     (println "Created graph" (str db-name "!"))))
     (println "Created graph" (str db-name "!"))))
 
 
-(when (= nbb/*file* (:file (meta #'-main)))
+(when (= nbb/*file* (nbb/invoked-file))
   (-main *command-line-args*))
   (-main *command-line-args*))

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

@@ -435,10 +435,9 @@
           "Upload file types like image, pdf, docx, etc.)"
           "Upload file types like image, pdf, docx, etc.)"
           :icon/upload])
           :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]
        ["Embed HTML " (->inline "html") "" :icon/htmlEmbed]
 
 

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

@@ -2606,51 +2606,61 @@
 
 
 (rum/defcs block-tag <
 (rum/defcs block-tag <
   (rum/local false ::hover?)
   (rum/local false ::hover?)
+  (rum/local false ::hover-container?)
   [state block tag config popup-opts]
   [state block tag config popup-opts]
   (let [*hover? (::hover? state)
   (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))
      {: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
 (rum/defc tags-cp
   "Tags without inline or hidden tags"
   "Tags without inline or hidden tags"
@@ -2959,7 +2969,7 @@
         repo (state/get-current-repo)
         repo (state/get-current-repo)
         db-based? (config/db-based-graph? repo)
         db-based? (config/db-based-graph? repo)
         refs-count (if (seq (:block/_refs block))
         refs-count (if (seq (:block/_refs block))
-                     (count (:block/_refs block))
+                     (count (remove :logseq.property/view-for (:block/_refs block)))
                      (rum/react *refs-count))
                      (rum/react *refs-count))
         table? (:table? config)
         table? (:table? config)
         raw-mode-block (state/sub :editor/raw-mode-block)
         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;
   @apply w-full inline;
 
 
   > .ui__checkbox {
   > .ui__checkbox {
-    @apply relative top-[2px];
+    @apply relative top-0.5;
+
+    &.checked {
+      @apply top-1;
+    }
 
 
     > span {
     > span {
       @apply h-full;
       @apply h-full;

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

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

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

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

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

@@ -428,13 +428,13 @@
 
 
 (rum/defc db-page-title-actions
 (rum/defc db-page-title-actions
   [page]
   [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
    [:div.flex.flex-row.items-center.gap-2
     (when-not (:logseq.property/icon (db/entity (:db/id page)))
     (when-not (:logseq.property/icon (db/entity (:db/id page)))
       (shui/button
       (shui/button
        {:variant :outline
        {:variant :outline
         :size :sm
         :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]
         :on-click (fn [e]
                     (state/pub-event! [:editor/new-property {:property-key "Icon"
                     (state/pub-event! [:editor/new-property {:property-key "Icon"
                                                              :block page
                                                              :block page
@@ -444,11 +444,11 @@
     (shui/button
     (shui/button
      {:variant :outline
      {:variant :outline
       :size :sm
       :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]
       :on-click (fn [e]
                   (state/pub-event! [:editor/new-property {:block page
                   (state/pub-event! [:editor/new-property {:block page
                                                            :target (.-target e)}]))}
                                                            :target (.-target e)}]))}
-     "Set page property")]])
+     "Set property")]])
 
 
 (rum/defc db-page-title
 (rum/defc db-page-title
   [page whiteboard-page? sidebar? container-id]
   [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-property-handler/remove-block-property!
                         (:db/id block)
                         (:db/id block)
                         :logseq.property/icon))
                         :logseq.property/icon))
-                     (clear-overlay!))]
+                     (clear-overlay!)
+                     (when editing?
+                       (editor-handler/restore-last-saved-cursor!)))]
 
 
     (hooks/use-effect!
     (hooks/use-effect!
      (fn []
      (fn []
@@ -92,12 +94,15 @@
             (fn []
             (fn []
               (when-let [^js target (some-> (.querySelector container (str "#ls-block-" (str (:block/uuid block))))
               (when-let [^js target (some-> (.querySelector container (str "#ls-block-" (str (:block/uuid block))))
                                             (.querySelector ".block-main-container"))]
                                             (.querySelector ".block-main-container"))]
+                (state/set-editor-action! :property-icon-picker)
                 (shui/popup-show! target
                 (shui/popup-show! target
                                   #(icon-component/icon-search
                                   #(icon-component/icon-search
                                     {:on-chosen on-chosen!
                                     {:on-chosen on-chosen!
                                      :icon-value icon
                                      :icon-value icon
                                      :del-btn? (some? icon)})
                                      :del-btn? (some? icon)})
                                   {:id :ls-icon-picker
                                   {: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})))))))
                                    :align :start})))))))
      [editing?])
      [editing?])
 
 
@@ -632,6 +637,10 @@
                                [(:db/id v)])))
                                [(:db/id v)])))
         parent-property? (= (:db/ident property) :logseq.property/parent)
         parent-property? (= (:db/ident property) :logseq.property/parent)
         children-pages (when parent-property? (model/get-structured-children repo (:db/id block)))
         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
         nodes
         (->>
         (->>
          (cond
          (cond
@@ -649,40 +658,40 @@
                  excluded-options (remove (fn [e] (contains? exclude-ids (:block/uuid e))) options)]
                  excluded-options (remove (fn [e] (contains? exclude-ids (:block/uuid e))) options)]
              excluded-options)
              excluded-options)
 
 
+           (= property-type :class)
+           (get-all-classes-f)
+
            (seq classes)
            (seq classes)
            (->>
            (->>
             (mapcat
             (mapcat
              (fn [class]
              (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)
              classes)
             distinct)
             distinct)
 
 
            :else
            :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
                              ;; 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]
         options (map (fn [node]
                        (let [id (or (:value node) (:db/id node))
                        (let [id (or (:value node) (:db/id node))
@@ -929,11 +938,14 @@
                      :property-block? true
                      :property-block? true
                      :on-block-content-pointer-down (when default-value?
                      :on-block-content-pointer-down (when default-value?
                                                       (fn [_e]
                                                       (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)
          (if (set? value-block)
            (blocks-container config (ldb/sort-by-order value-block))
            (blocks-container config (ldb/sort-by-order value-block))
            (rum/with-key
            (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)))))]
              (str (:db/id property) "-" (:block/uuid value-block)))))]
       [:div
       [:div
        {:tabIndex 0
        {:tabIndex 0
@@ -1231,7 +1243,7 @@
     (multiple-values-inner block property value' opts)))
     (multiple-values-inner block property value' opts)))
 
 
 (rum/defcs property-value < rum/reactive db-mixins/query
 (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}]
                          :as opts}]
   (ui/catch-error
   (ui/catch-error
    (ui/block-error "Something wrong" {})
    (ui/block-error "Something wrong" {})
@@ -1258,55 +1270,74 @@
              (first v)
              (first v)
              :else
              :else
              v)
              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;
     @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 {
   .property-value-inner {

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

@@ -1291,7 +1291,8 @@
   (when-let [->hiccup (state/get-component :block/->hiccup)]
   (when-let [->hiccup (state/get-component :block/->hiccup)]
     (let [group-by-page? (not (every? db/page? result))
     (let [group-by-page? (not (every? db/page? result))
           result (if group-by-page?
           result (if group-by-page?
-                   (group-by :block/page result)
+                   (-> (group-by :block/page result)
+                       (update-vals ldb/sort-by-order))
                    result)
                    result)
           config' (cond-> (assoc config
           config' (cond-> (assoc config
                                  :current-block (:db/id view-entity)
                                  :current-block (:db/id view-entity)
@@ -1480,7 +1481,8 @@
                            ""))
                            ""))
             result (editor-handler/api-insert-new-block! view-title
             result (editor-handler/api-insert-new-block! view-title
                                                          {:page (:block/uuid page)
                                                          {:page (:block/uuid page)
-                                                          :properties properties})]
+                                                          :properties properties
+                                                          :edit-block? false})]
       (db/entity [:block/uuid (:block/uuid result)]))))
       (db/entity [:block/uuid (:block/uuid result)]))))
 
 
 (rum/defc views-tab < rum/reactive db-mixins/query
 (rum/defc views-tab < rum/reactive db-mixins/query
@@ -1528,7 +1530,7 @@
           (if (= title "")
           (if (= title "")
             "New view"
             "New view"
             title))
             title))
-        (when show-items-count?
+        (when (and show-items-count? (> (count data) 0))
           [:span.text-muted-foreground.text-xs
           [:span.text-muted-foreground.text-xs
            (count data)]))))
            (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'))))))
           (db-utils/pull-many repo '[*] ids'))))))
 
 
 (defn get-block-and-children
 (defn get-block-and-children
-  [repo block-uuid]
+  [repo block-uuid & {:as opts}]
   (let [db (conn/get-db repo)]
   (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
 (defn get-file-page
   ([file-path]
   ([file-path]
@@ -651,7 +651,7 @@ independent of format as format specific heading characters are stripped"
                    [:frontend.worker.react/refs eid]
                    [:frontend.worker.react/refs eid]
                    {:query-fn (fn []
                    {:query-fn (fn []
                                 (let [entities (mapcat (fn [id]
                                 (let [entities (mapcat (fn [id]
-                                                         (:block/_path-refs (db-utils/entity id))) ids)
+                                                         (:block/_refs (db-utils/entity id))) ids)
                                       blocks (map (fn [e]
                                       blocks (map (fn [e]
                                                     {:block/parent (:block/parent e)
                                                     {:block/parent (:block/parent e)
                                                      :block/order (:block/order 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'))
                            (outliner-op/create-page! title' options'))
                    [_page-name page-uuid] (ldb/read-transit-str result)]
                    [_page-name page-uuid] (ldb/read-transit-str result)]
              (when redirect?
              (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
 ;; favorite fns
 ;; ============
 ;; ============

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

@@ -1897,6 +1897,12 @@
 
 
   (handle-command-input-close id))
   (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
 (defn- close-autocomplete-if-outside
   [input]
   [input]
   (when (and input
   (when (and input
@@ -2219,73 +2225,86 @@
   ([element-id db-id {:keys [target] :as opts}]
   ([element-id db-id {:keys [target] :as opts}]
    (let [repo (state/get-current-repo)
    (let [repo (state/get-current-repo)
          db? (config/db-based-graph? 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)
                                 (-> (first blocks)
                                     (update :block/properties-text-values dissoc :template)
                                     (update :block/properties-text-values dissoc :template)
                                     (update :block/properties-order (fn [keys]
                                     (update :block/properties-order (fn [keys]
                                                                       (vec (remove #{:template} 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))
                                   (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
 (defn template-on-chosen-handler
   [element-id]
   [element-id]
-  (fn [[_template template-block] _click?]
+  (fn [template-block]
     (when-let [db-id (:db/id template-block)]
     (when-let [db-id (:db/id template-block)]
       (insert-template! element-id db-id
       (insert-template! element-id db-id
                         {:replace-empty-target? true}))))
                         {:replace-empty-target? true}))))

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

@@ -8,6 +8,7 @@
             [frontend.format.block :as block]
             [frontend.format.block :as block]
             [frontend.format.mldoc :as mldoc]
             [frontend.format.mldoc :as mldoc]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.editor :as editor-handler]
+            [frontend.handler.notification :as notification]
             [frontend.mobile.util :as mobile-util]
             [frontend.mobile.util :as mobile-util]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.util :as util]
             [frontend.util :as util]
@@ -189,32 +190,38 @@
   ;; todo: logseq/whiteboard-shapes is now text/html
   ;; todo: logseq/whiteboard-shapes is now text/html
   [input text e html]
   [input text e html]
   (util/stop e)
   (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
        ;; 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
 (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
   "Provides search functionality for a number of features including Cmd-K
   search. Most of these fns depend on the search protocol"
   search. Most of these fns depend on the search protocol"
   (:require [clojure.string :as string]
   (: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.agency :as search-agency]
             [frontend.search.protocol :as protocol]
             [frontend.search.protocol :as protocol]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.util :as util]
             [frontend.util :as util]
-            [promesa.core :as p]
-            [frontend.common.search-fuzzy :as fuzzy]
             [logseq.common.config :as common-config]
             [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]
             [logseq.db :as ldb]
-            [datascript.core :as d]))
+            [promesa.core :as p]))
 
 
 (def fuzzy-search fuzzy/fuzzy-search)
 (def fuzzy-search fuzzy/fuzzy-search)
 
 
@@ -35,15 +36,15 @@
   ([q limit]
   ([q limit]
    (when-let [repo (state/get-current-repo)]
    (when-let [repo (state/get-current-repo)]
      (let [q (fuzzy/clean-str q)]
      (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
 (defn template-search
   ([q]
   ([q]
@@ -51,11 +52,16 @@
   ([q limit]
   ([q limit]
    (when-let [repo (state/get-current-repo)]
    (when-let [repo (state/get-current-repo)]
      (when q
      (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
 (defn property-search
   ([q]
   ([q]
@@ -78,13 +84,13 @@
   ([property q limit]
   ([property q limit]
    (when-let [repo (state/get-current-repo)]
    (when-let [repo (state/get-current-repo)]
      (when q
      (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!
 (defn rebuild-indices!
   ([]
   ([]

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

@@ -908,8 +908,9 @@
 (rum/defc with-shortcut < rum/reactive
 (rum/defc with-shortcut < rum/reactive
   < {:key-fn (fn [key pos] (str "shortcut-" key pos))}
   < {:key-fn (fn [key pos] (str "shortcut-" key pos))}
   [shortcut-key position content]
   [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
       (tippy
        {:html [:div.text-sm.font-medium (keyboard-shortcut-from-config shortcut-key)]
        {:html [:div.text-sm.font-medium (keyboard-shortcut-from-config shortcut-key)]
         :interactive true
         :interactive true
@@ -1017,9 +1018,9 @@
 (rum/defc tooltip
 (rum/defc tooltip
   [trigger tooltip-content & {:keys [trigger-props]}]
   [trigger tooltip-content & {:keys [trigger-props]}]
   (shui/tooltip-provider
   (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
 (rum/defc DelDateButton
   [on-delete]
   [on-delete]

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

@@ -755,7 +755,9 @@
    ["64.1" {:properties [:logseq.property.view/group-by-property]
    ["64.1" {:properties [:logseq.property.view/group-by-property]
             :fix add-view-icons}]
             :fix add-view-icons}]
    ["64.2" {:properties [:logseq.property.view/feature-type]
    ["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)
 (let [[major minor] (last (sort (map (comp (juxt :major :minor) db-schema/parse-schema-version first)
                                      schema-version->updates)))
                                      schema-version->updates)))

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

@@ -9,8 +9,8 @@
             [logseq.common.defkeywords :refer [defkeywords]]
             [logseq.common.defkeywords :refer [defkeywords]]
             [logseq.db :as ldb]
             [logseq.db :as ldb]
             [logseq.db.frontend.validate :as db-validate]
             [logseq.db.frontend.validate :as db-validate]
-            [logseq.db.sqlite.util :as sqlite-util]
             [logseq.db.sqlite.export :as sqlite-export]
             [logseq.db.sqlite.export :as sqlite-export]
+            [logseq.db.sqlite.util :as sqlite-util]
             [logseq.graph-parser.exporter :as gp-exporter]
             [logseq.graph-parser.exporter :as gp-exporter]
             [logseq.outliner.core :as outliner-core]
             [logseq.outliner.core :as outliner-core]
             [logseq.outliner.datascript-report :as ds-report]
             [logseq.outliner.datascript-report :as ds-report]
@@ -46,6 +46,35 @@
                            :block/refs refs})))))
                            :block/refs refs})))))
             blocks)))
             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
 (defkeywords
   ::skip-validate-db? {:doc "tx-meta option, default = false"}
   ::skip-validate-db? {:doc "tx-meta option, default = false"}
   ::skip-store-conn {:doc "tx-meta option, skip `d/store` on conn. 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-tx (when-not (or (:undo? tx-meta) (:redo? tx-meta) (:rtc-tx? tx-meta))
                         (commands/run-commands tx-report))
                         (commands/run-commands tx-report))
           ;; :block/refs relies on those changes
           ;; :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)
           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
                          (assoc tx-report
                                 :tx-data (concat (:tx-data tx-report) (:tx-data result))
                                 :tx-data (concat (:tx-data tx-report) (:tx-data result))
                                 :db-after (:db-after result)))
                                 :db-after (:db-after result)))
@@ -134,17 +166,19 @@
                 (doseq [page-id page-ids]
                 (doseq [page-id page-ids]
                   (when (d/entity @conn page-id)
                   (when (d/entity @conn page-id)
                     (file/sync-to-file repo page-id tx-meta)))))
                     (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]
           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)
                                    (when (ldb/asset? e)
                                      {:block/uuid (:block/uuid 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')
           block-refs (when (seq blocks')
                        (rebuild-block-refs repo tx-report* blocks'))
                        (rebuild-block-refs repo tx-report* blocks'))
           refs-tx-report (when (seq block-refs)
           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*))]
           replace-tx (let [db-after (or (:db-after refs-tx-report) (:db-after tx-report*))]
                        (concat
                        (concat
                       ;; block path refs
                       ;; block path refs
@@ -153,7 +187,7 @@
                             (compute-block-path-refs-tx tx-report* blocks')))
                             (compute-block-path-refs-tx tx-report* blocks')))
 
 
                        ;; update block/tx-id
                        ;; 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))
                                                      (concat pages blocks))
                               tx-id (get-in (or refs-tx-report tx-report*) [:tempids :db/current-tx])]
                               tx-id (get-in (or refs-tx-report tx-report*) [:tempids :db/current-tx])]
                           (keep (fn [b]
                           (keep (fn [b]

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

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