mirror of
https://github.com/logseq/logseq.git
synced 2026-05-16 08:52:20 +00:00
fix(template): resolve dynamic variables in template insertion
This commit is contained in:
11
deps/outliner/src/logseq/outliner/op.cljs
vendored
11
deps/outliner/src/logseq/outliner/op.cljs
vendored
@@ -6,6 +6,7 @@
|
||||
[logseq.db :as ldb]
|
||||
[logseq.db.sqlite.export :as sqlite-export]
|
||||
[logseq.outliner.core :as outliner-core]
|
||||
[logseq.outliner.template :as outliner-template]
|
||||
[logseq.outliner.page :as outliner-page]
|
||||
[logseq.outliner.property :as outliner-property]
|
||||
[logseq.outliner.recycle :as outliner-recycle]
|
||||
@@ -227,15 +228,19 @@
|
||||
{:include-property-block? true})
|
||||
rest)]
|
||||
(when (seq template-blocks)
|
||||
(cons (assoc (first template-blocks)
|
||||
(cons (assoc (into {} (first template-blocks))
|
||||
:db/id (:db/id (first template-blocks))
|
||||
:logseq.property/used-template (:db/id template))
|
||||
(rest template-blocks))))))
|
||||
(map (fn [block]
|
||||
(assoc (into {} block) :db/id (:db/id block)))
|
||||
(rest template-blocks)))))))
|
||||
|
||||
(defn- apply-template-op!
|
||||
[conn *result [template-id target-block-id opts]]
|
||||
(when-let [target (d/entity @conn target-block-id)]
|
||||
(let [blocks (or (some-> (:template-blocks opts) seq vec)
|
||||
(template-children-blocks @conn template-id))]
|
||||
(template-children-blocks @conn template-id))
|
||||
blocks (outliner-template/resolve-dynamic-template-blocks @conn target blocks)]
|
||||
(when (seq blocks)
|
||||
(let [sibling? (:sibling? opts)
|
||||
sibling?' (cond
|
||||
|
||||
119
deps/outliner/src/logseq/outliner/template.cljs
vendored
Normal file
119
deps/outliner/src/logseq/outliner/template.cljs
vendored
Normal file
@@ -0,0 +1,119 @@
|
||||
(ns logseq.outliner.template
|
||||
(:require [clojure.string :as string]
|
||||
[datascript.core :as d]
|
||||
[logseq.common.util.page-ref :as page-ref]
|
||||
[logseq.db :as ldb]))
|
||||
|
||||
(def ^:private template-re #"<%([^%].*?)%>")
|
||||
|
||||
(defn- current-time
|
||||
[]
|
||||
(let [d (js/Date.)
|
||||
locale (some-> js/globalThis (aget "navigator") (aget "language"))]
|
||||
(.toLocaleTimeString d locale (clj->js {:hour "2-digit"
|
||||
:minute "2-digit"
|
||||
:hourCycle "h23"}))))
|
||||
|
||||
(defn- date-with-day-offset
|
||||
[offset-days]
|
||||
(let [d (js/Date.)]
|
||||
(.setHours d 0 0 0 0)
|
||||
(.setDate d (+ (.getDate d) offset-days))
|
||||
d))
|
||||
|
||||
(defn- date->journal-day
|
||||
[^js/Date date]
|
||||
(let [year (.getFullYear date)
|
||||
month (inc (.getMonth date))
|
||||
day (.getDate date)
|
||||
month' (if (< month 10) (str "0" month) (str month))
|
||||
day' (if (< day 10) (str "0" day) (str day))]
|
||||
(js/parseInt (str year month' day') 10)))
|
||||
|
||||
(defn- journal-title
|
||||
[db offset-days]
|
||||
(let [journal-day (date->journal-day (date-with-day-offset offset-days))]
|
||||
(or (d/q '[:find ?title .
|
||||
:in $ ?journal-day
|
||||
:where
|
||||
[?p :block/journal-day ?journal-day]
|
||||
[?p :block/title ?title]]
|
||||
db journal-day)
|
||||
(str journal-day))))
|
||||
|
||||
(defn- target-page-title
|
||||
[target]
|
||||
(cond
|
||||
(ldb/page? target)
|
||||
(:block/title target)
|
||||
|
||||
:else
|
||||
(get-in target [:block/page :block/title])))
|
||||
|
||||
(defn- variable-rules
|
||||
[db target]
|
||||
(let [today (journal-title db 0)
|
||||
current-page (or (target-page-title target) today)]
|
||||
{"today" (page-ref/->page-ref today)
|
||||
"yesterday" (page-ref/->page-ref (journal-title db -1))
|
||||
"tomorrow" (page-ref/->page-ref (journal-title db 1))
|
||||
"time" (current-time)
|
||||
"current page" (page-ref/->page-ref current-page)}))
|
||||
|
||||
(defn- resolve-string
|
||||
[content rules]
|
||||
(string/replace content template-re
|
||||
(fn [[_ match]]
|
||||
(let [match' (string/trim match)
|
||||
lowered (string/lower-case match')]
|
||||
(cond
|
||||
(string/blank? match')
|
||||
""
|
||||
|
||||
(contains? rules lowered)
|
||||
(or (get rules lowered) "")
|
||||
|
||||
:else
|
||||
match')))))
|
||||
|
||||
(defn- normalize-block
|
||||
[block]
|
||||
(cond-> (into {} block)
|
||||
(:db/id block)
|
||||
(assoc :db/id (:db/id block))))
|
||||
|
||||
(defn- resolve-field
|
||||
[value rules]
|
||||
(if (string? value)
|
||||
(resolve-string value rules)
|
||||
value))
|
||||
|
||||
(defn- resolve-properties-text-values
|
||||
[value rules]
|
||||
(if (map? value)
|
||||
(reduce-kv (fn [m k v]
|
||||
(assoc m k (resolve-field v rules)))
|
||||
{}
|
||||
value)
|
||||
value))
|
||||
|
||||
(defn- resolve-block
|
||||
[block rules]
|
||||
(cond-> block
|
||||
(contains? block :block/title)
|
||||
(update :block/title resolve-field rules)
|
||||
|
||||
(contains? block :block/raw-title)
|
||||
(update :block/raw-title resolve-field rules)
|
||||
|
||||
(contains? block :block/properties-text-values)
|
||||
(update :block/properties-text-values resolve-properties-text-values rules)))
|
||||
|
||||
(defn resolve-dynamic-template-blocks
|
||||
[db target blocks]
|
||||
(let [rules (variable-rules db target)]
|
||||
(mapv (fn [block]
|
||||
(-> block
|
||||
normalize-block
|
||||
(resolve-block rules)))
|
||||
blocks)))
|
||||
50
deps/outliner/test/logseq/outliner/op_test.cljs
vendored
50
deps/outliner/test/logseq/outliner/op_test.cljs
vendored
@@ -1,5 +1,6 @@
|
||||
(ns logseq.outliner.op-test
|
||||
(:require [cljs.test :refer [deftest is testing]]
|
||||
(:require [clojure.string :as string]
|
||||
[cljs.test :refer [deftest is testing]]
|
||||
[datascript.core :as d]
|
||||
[logseq.db :as ldb]
|
||||
[logseq.db.test.helper :as db-test]
|
||||
@@ -116,3 +117,50 @@
|
||||
(is (= #{"page y" "page z"}
|
||||
(set (map :block/name
|
||||
(:plugin.property._test_plugin/x7 (d/entity @conn block-id)))))))))
|
||||
|
||||
(deftest apply-template-op-resolves-dynamic-variables-test
|
||||
(testing "apply-template resolves dynamic variables in block title and property values"
|
||||
(let [conn (db-test/create-conn-with-blocks
|
||||
{:pages-and-blocks
|
||||
[{:page {:block/title "Target Page"}
|
||||
:blocks [{:block/title "target block"}]}
|
||||
{:page {:block/title "Templates"}
|
||||
:blocks [{:block/title "template root"
|
||||
:build/children [{:block/title "page is <% current page %>"}
|
||||
{:block/title "time block"
|
||||
:build/properties {:log-time "<%time%>"}}]}]}]
|
||||
:properties {:log-time {:logseq.property/type :default}}})
|
||||
template-root (db-test/find-block-by-content @conn "template root")
|
||||
target-block (db-test/find-block-by-content @conn "target block")
|
||||
template-blocks (->> (ldb/get-block-and-children @conn (:block/uuid template-root)
|
||||
{:include-property-block? true})
|
||||
rest)
|
||||
blocks-to-insert (cons (assoc (into {} (first template-blocks))
|
||||
:db/id (:db/id (first template-blocks))
|
||||
:logseq.property/used-template (:db/id template-root))
|
||||
(map (fn [block]
|
||||
(assoc (into {} block) :db/id (:db/id block)))
|
||||
(rest template-blocks)))
|
||||
_ (outliner-op/apply-ops! conn
|
||||
[[:apply-template [(:db/id template-root)
|
||||
(:db/id target-block)
|
||||
{:template-blocks blocks-to-insert}]]]
|
||||
{})
|
||||
page-var-block (db-test/find-block-by-content @conn "page is [[Target Page]]")
|
||||
time-block (some->> (d/q '[:find [?b ...]
|
||||
:in $ ?title ?page-title
|
||||
:where
|
||||
[?b :block/title ?title]
|
||||
[?b :block/page ?page]
|
||||
[?page :block/title ?page-title]]
|
||||
@conn "time block" "Target Page")
|
||||
first
|
||||
(d/entity @conn))
|
||||
time-value (some (fn [[property-id value]]
|
||||
(when (= "log-time" (name property-id))
|
||||
value))
|
||||
(db-test/readable-properties time-block))]
|
||||
(is (some? page-var-block))
|
||||
(is (string? time-value))
|
||||
(is (not (string/blank? time-value)))
|
||||
(is (not (string/includes? time-value "<%"))))))
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
[logseq.graph-parser.exporter :as gp-exporter]
|
||||
[logseq.outliner.core :as outliner-core]
|
||||
[logseq.outliner.datascript-report :as ds-report]
|
||||
[logseq.outliner.template :as outliner-template]
|
||||
[logseq.outliner.pipeline :as outliner-pipeline]))
|
||||
|
||||
(def ^:private rtc-tx-or-download-graph?
|
||||
@@ -67,43 +68,51 @@
|
||||
journal-page (some (fn [d] (when (and (= :block/journal-day (:a d)) (:added d))
|
||||
(d/entity db (:e d))))
|
||||
(:tx-data tx-report))
|
||||
journal-template? (some (fn [d] (and (:added d) (= (:a d) :block/tags) (= (:v d) journal-id))) (:tx-data 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-class-extends tag)
|
||||
templates (mapcat :logseq.property/_template-applied-to (conj parents tag))]
|
||||
(cond->> templates
|
||||
journal-page
|
||||
(map (fn [t] (assoc t :journal journal-page))))))
|
||||
(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]
|
||||
(cond->
|
||||
(assoc (into {} e) :db/id (:db/id e))
|
||||
(:journal template)
|
||||
(assoc :block/uuid
|
||||
(common-uuid/gen-journal-template-block (:block/uuid (:journal template))
|
||||
(:block/uuid e)))))))]
|
||||
blocks))))]
|
||||
(when (seq template-blocks)
|
||||
(let [result (outliner-core/insert-blocks
|
||||
db template-blocks object
|
||||
{:sibling? false
|
||||
:keep-uuid? journal-template?
|
||||
:outliner-op :insert-template-blocks})]
|
||||
(:tx-data result)))))))]
|
||||
journal-template? (some (fn [d] (and (:added d)
|
||||
(= (:a d) :block/tags)
|
||||
(= (:v d) journal-id)))
|
||||
(:tx-data tx-report))
|
||||
tag->templates (fn [id]
|
||||
(let [tag (d/entity db id)
|
||||
parents (ldb/get-class-extends tag)
|
||||
templates (mapcat :logseq.property/_template-applied-to (conj parents tag))]
|
||||
(cond->> templates
|
||||
journal-page
|
||||
(map (fn [t] (assoc t :journal journal-page))))))
|
||||
template->blocks (fn [object template]
|
||||
(let [template-children (rest (ldb/get-block-and-children db (:block/uuid template)
|
||||
{:include-property-block? true}))
|
||||
blocks (->> (cons (assoc (first template-children)
|
||||
:logseq.property/used-template (:db/id template))
|
||||
(rest template-children))
|
||||
(map (fn [block]
|
||||
(cond->
|
||||
(assoc (into {} block) :db/id (:db/id block))
|
||||
(:journal template)
|
||||
(assoc :block/uuid
|
||||
(common-uuid/gen-journal-template-block
|
||||
(:block/uuid (:journal template))
|
||||
(:block/uuid block)))))))]
|
||||
(outliner-template/resolve-dynamic-template-blocks db object blocks)))
|
||||
tag-additions (->> (:tx-data tx-report)
|
||||
(filter (fn [d] (and (= (:a d) :block/tags) (:added d))))
|
||||
(group-by :e))
|
||||
tx-data (mapcat
|
||||
(fn [[e datoms]]
|
||||
(let [object (d/entity db e)
|
||||
templates (->> (set (map :v datoms))
|
||||
(mapcat tag->templates)
|
||||
distinct
|
||||
(sort-by :block/created-at))
|
||||
blocks-to-insert (mapcat (partial template->blocks object) templates)]
|
||||
(when (seq blocks-to-insert)
|
||||
(let [result (outliner-core/insert-blocks
|
||||
db blocks-to-insert object
|
||||
{:sibling? false
|
||||
:keep-uuid? journal-template?
|
||||
:outliner-op :insert-template-blocks})]
|
||||
(:tx-data result)))))
|
||||
tag-additions)]
|
||||
tx-data))
|
||||
|
||||
(defn- fix-page-tags
|
||||
|
||||
@@ -187,3 +187,26 @@
|
||||
|
||||
;; return global fn back to previous behavior
|
||||
(ldb/register-transact-pipeline-fn! identity)))
|
||||
|
||||
(deftest tag-template-insertion-resolves-dynamic-variable-test
|
||||
(let [conn (db-test/create-conn-with-blocks
|
||||
{:pages-and-blocks
|
||||
[{:page {:block/title "Target Page"}
|
||||
:blocks [{:block/title "target block"}]}
|
||||
{:page {:block/title "Templates"}
|
||||
:blocks [{:block/title "tag template root"
|
||||
:build/children [{:block/title "auto <% current page %>"}]}]}]
|
||||
:classes {:DiaryEntry {}}})
|
||||
target-block (db-test/find-block-by-content @conn "target block")
|
||||
template-root (db-test/find-block-by-content @conn "tag template root")
|
||||
diary-entry (ldb/get-page @conn "DiaryEntry")]
|
||||
(ldb/transact! conn [[:db/add (:db/id template-root)
|
||||
:logseq.property/template-applied-to
|
||||
(:db/id diary-entry)]])
|
||||
(ldb/register-transact-pipeline-fn! worker-pipeline/transact-pipeline)
|
||||
(try
|
||||
(ldb/transact! conn [[:db/add (:db/id target-block) :block/tags (:db/id diary-entry)]])
|
||||
(is (some? (db-test/find-block-by-content @conn "auto [[Target Page]]")))
|
||||
(finally
|
||||
;; return global fn back to previous behavior
|
||||
(ldb/register-transact-pipeline-fn! identity)))))
|
||||
|
||||
Reference in New Issue
Block a user