Browse Source

Merge pull request #11765 from logseq/feat/text-template

feat: text template && auto-apply templates on tags
Tienson Qin 7 months ago
parent
commit
a1f643544f

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

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

+ 12 - 3
deps/db/src/logseq/db/frontend/property.cljs

@@ -74,8 +74,7 @@
                             :attribute :block/tags
                             :schema {:type :class
                                      :cardinality :many
-                                     :public? true
-                                     :classes #{:logseq.class/Root}}
+                                     :public? true}
                             :queryable? true}
      :block/parent         {:title "Node parent"
                             :attribute :block/parent
@@ -575,7 +574,17 @@
                                            ;; - avoid losing this attr when the user-block is deleted
                                            ;; - related user-block maybe not exists yet in graph
                                            :type :string
-                                           :hide? true}})))
+                                           :hide? true}}
+     :logseq.property/used-template {:title "Used template"
+                                     :schema {:type :node
+                                              :public? false
+                                              :hide? true
+                                              :classes #{:logseq.class/Template}}}
+     :logseq.property/template-applied-to {:title "Apply template to tags"
+                                           :schema {:type :class
+                                                    :cardinality :many
+                                                    :public? true}
+                                           :queryable? true})))
 
 (def built-in-properties
   (->> built-in-properties*

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

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

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

@@ -228,7 +228,7 @@
         hidden-pages (concat (build-initial-views) (build-favorites-page))
         ;; These classes bootstrap our tags and properties as they depend on each other e.g.
         ;; Root <-> Tag, classes-tx depends on logseq.property/parent, properties-tx depends on Property
-        bootstrap-class? (fn [c] (contains? #{:logseq.class/Root :logseq.class/Property :logseq.class/Tag} (:db/ident c)))
+        bootstrap-class? (fn [c] (contains? #{:logseq.class/Root :logseq.class/Property :logseq.class/Tag :logseq.class/Template} (:db/ident c)))
         bootstrap-classes (filter bootstrap-class? default-classes)
         bootstrap-class-ids (map #(select-keys % [:db/ident :block/uuid]) bootstrap-classes)
         classes-tx (concat (map #(dissoc % :db/ident) bootstrap-classes)

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

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

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

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

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

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

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

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

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

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

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

@@ -2614,9 +2614,11 @@
       :on-mouse-over #(reset! *hover-container? true)
       :on-mouse-out #(reset! *hover-container? false)}
      (when (not (ldb/private-tags (:db/ident tag)))
-       [:div.absolute.-left-5.bg-gray-01.transition-opacity.duration-300.ease-in-out
+       [:div.absolute.bg-gray-03.transition-opacity.duration-300.ease-in-out
         {:class (if @*hover-container? "!opacity-100" "!opacity-0")
-         :style {:top -2}}
+         :style {:top -2
+                 :z-index (if @*hover-container? 99 -1)
+                 :left -23}}
         (shui/button
          {:size :sm
           :variant :ghost
@@ -2966,7 +2968,7 @@
         repo (state/get-current-repo)
         db-based? (config/db-based-graph? repo)
         refs-count (if (seq (:block/_refs block))
-                     (count (:block/_refs block))
+                     (count (remove :logseq.property/view-for (:block/_refs block)))
                      (rum/react *refs-count))
         table? (:table? config)
         raw-mode-block (state/sub :editor/raw-mode-block)

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

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

+ 22 - 18
src/main/frontend/components/property/value.cljs

@@ -642,6 +642,10 @@
                                [(:db/id v)])))
         parent-property? (= (:db/ident property) :logseq.property/parent)
         children-pages (when parent-property? (model/get-structured-children repo (:db/id block)))
+        property-type (:logseq.property/type property)
+        get-all-classes-f (fn []
+                            (model/get-all-classes repo {:except-root-class? true
+                                                         :except-private-tags? (not (contains? #{:logseq.property/template-applied-to} (:db/ident property)))}))
         nodes
         (->>
          (cond
@@ -659,33 +663,33 @@
                  excluded-options (remove (fn [e] (contains? exclude-ids (:block/uuid e))) options)]
              excluded-options)
 
+           (= property-type :class)
+           (get-all-classes-f)
+
            (seq classes)
            (->>
             (mapcat
              (fn [class]
-               (if (= :logseq.class/Root (:db/ident class))
-                 (model/get-all-classes repo {:except-root-class? true})
-                 (model/get-class-objects repo (:db/id class))))
+               (model/get-class-objects repo (:db/id class)))
              classes)
             distinct)
 
            :else
-           (let [property-type (:logseq.property/type property)]
-             (if (empty? result)
-               (let [v (get block (:db/ident property))]
-                 (remove #(= :logseq.property/empty-placeholder (:db/ident %))
-                         (if (every? de/entity? v) v [v])))
-               (remove (fn [node]
-                         (or (= (:db/id block) (:db/id node))
+           (if (empty? result)
+             (let [v (get block (:db/ident property))]
+               (remove #(= :logseq.property/empty-placeholder (:db/ident %))
+                       (if (every? de/entity? v) v [v])))
+             (remove (fn [node]
+                       (or (= (:db/id block) (:db/id node))
                              ;; A page's alias can't be itself
-                             (and alias? (= (or (:db/id (:block/page block))
-                                                (:db/id block))
-                                            (:db/id node)))
-                             (when (and property-type (not= property-type :node))
-                               (if (= property-type :page)
-                                 (not (db/page? node))
-                                 (not (contains? (ldb/get-entity-types node) property-type))))))
-                       result)))))
+                           (and alias? (= (or (:db/id (:block/page block))
+                                              (:db/id block))
+                                          (:db/id node)))
+                           (when (and property-type (not= property-type :node))
+                             (if (= property-type :page)
+                               (not (db/page? node))
+                               (not (contains? (ldb/get-entity-types node) property-type))))))
+                     result))))
 
         options (map (fn [node]
                        (let [id (or (:value node) (:db/id node))

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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