From c50511a7b5f6c494fa37d84f1487d4d234f5866c Mon Sep 17 00:00:00 2001 From: Dasemu Date: Wed, 26 Aug 2026 19:02:44 +0200 Subject: [PATCH] Fase B1: chain journal pages with previous/next graph links Adds :logseq.property.journal/previous and /next (built-in, hidden, single-cardinality node refs) and maintains them in logseq.outliner.page/create whenever a new journal page is created. Filling a gap between two already-linked journals re-links both neighbors around the new page instead of just appending. A migration backfills the chain across all existing journal pages ordered by day. --- deps/db/src/logseq/db/frontend/property.cljs | 13 ++++++ deps/db/src/logseq/db/frontend/schema.cljs | 2 +- deps/outliner/src/logseq/outliner/page.cljs | 39 +++++++++++++++- .../test/logseq/outliner/page_test.cljs | 45 +++++++++++++++++++ src/main/frontend/worker/db/migrate.cljs | 21 ++++++++- 5 files changed, 116 insertions(+), 4 deletions(-) diff --git a/deps/db/src/logseq/db/frontend/property.cljs b/deps/db/src/logseq/db/frontend/property.cljs index 35e5cbb..6631eac 100644 --- a/deps/db/src/logseq/db/frontend/property.cljs +++ b/deps/db/src/logseq/db/frontend/property.cljs @@ -265,11 +265,24 @@ :public? false :hide? true}} + :logseq.property.citation/target {:title "Cited page or block" + :schema {:type :node}} + :logseq.property.citation/context {:title "Citation context" + :schema {:type :string}} + ;; Journal props :logseq.property.journal/title-format {:title "Title Format" :schema {:type :string :public? false}} + :logseq.property.journal/previous {:title "Previous journal" + :schema {:type :node + :hide? true + :public? false}} + :logseq.property.journal/next {:title "Next journal" + :schema {:type :node + :hide? true + :public? false}} :logseq.property/choice-checkbox-state {:title "Choice checkbox state" :schema {:type :checkbox diff --git a/deps/db/src/logseq/db/frontend/schema.cljs b/deps/db/src/logseq/db/frontend/schema.cljs index 27bf22a..aa2d865 100644 --- a/deps/db/src/logseq/db/frontend/schema.cljs +++ b/deps/db/src/logseq/db/frontend/schema.cljs @@ -30,7 +30,7 @@ (map (juxt :major :minor) [(parse-schema-version x) (parse-schema-version y)]))) -(def version (parse-schema-version "65.33")) +(def version (parse-schema-version "65.36")) (defn major-version "Return a number. diff --git a/deps/outliner/src/logseq/outliner/page.cljs b/deps/outliner/src/logseq/outliner/page.cljs index 908696d..cf68c7c 100644 --- a/deps/outliner/src/logseq/outliner/page.cljs +++ b/deps/outliner/src/logseq/outliner/page.cljs @@ -299,6 +299,40 @@ [page]) (remove nil?)))) +(defn- journal-neighbors + "Returns [prev-entity next-entity], the closest existing journal pages + immediately before/after journal-day (by day order, not necessarily + adjacent calendar days)." + [db journal-day] + (let [entries (->> (d/datoms db :avet :block/journal-day) + (map (fn [datom] [(:v datom) (:e datom)])) + (remove (fn [[day]] (= day journal-day)))) + prev (->> entries (filter #(< (first %) journal-day)) (sort-by first) last) + next (->> entries (filter #(> (first %) journal-day)) (sort-by first) first)] + [(some->> prev second (d/entity db)) + (some->> next second (d/entity db))])) + +(defn- journal-relink-tx + "Tx-data linking a newly created journal page (identified by its uuid) to + its nearest existing journal neighbors by day. Re-links around the new + page when it fills a gap between two already-linked journals." + [db page-uuid journal-day] + (let [[prev next] (journal-neighbors db journal-day) + page-ref [:block/uuid page-uuid]] + (concat + (when prev + (cond-> [[:db/add page-ref :logseq.property.journal/previous (:db/id prev)] + [:db/add (:db/id prev) :logseq.property.journal/next page-ref]] + (:logseq.property.journal/next prev) + (conj [:db/retract (:db/id prev) :logseq.property.journal/next + (:db/id (:logseq.property.journal/next prev))]))) + (when next + (cond-> [[:db/add page-ref :logseq.property.journal/next (:db/id next)] + [:db/add (:db/id next) :logseq.property.journal/previous page-ref]] + (:logseq.property.journal/previous next) + (conj [:db/retract (:db/id next) :logseq.property.journal/previous + (:db/id (:logseq.property.journal/previous next))])))))) + (defn ^:large-vars/cleanup-todo ^:api create "Pure function without side effects" [db title* @@ -403,10 +437,13 @@ (:block/uuid page)) page (assoc page :block/uuid page-uuid) page-txs (build-page-tx db properties page (select-keys options [:class? :tags :class-ident-namespace])) + journal-link-txs (when-let [journal-day (:block/journal-day page)] + (journal-relink-tx db page-uuid journal-day)) txs (concat ;; transact doesn't support entities (remove de/entity? parents') - page-txs) + page-txs + journal-link-txs) tx-meta (cond-> (outliner-tx-meta/ensure-outliner-ops {:persist-op? persist-op? :outliner-op :create-page} diff --git a/deps/outliner/test/logseq/outliner/page_test.cljs b/deps/outliner/test/logseq/outliner/page_test.cljs index 7520dca..e1f591f 100644 --- a/deps/outliner/test/logseq/outliner/page_test.cljs +++ b/deps/outliner/test/logseq/outliner/page_test.cljs @@ -204,3 +204,48 @@ "Journal title is not split into a month namespace page") (is (nil? (ldb/get-page @conn "18")) "Journal title is not split into a day namespace page"))) + +(deftest create-journal-links-previous-and-next + (let [conn (db-test/create-conn) + [_ day1-uuid] (outliner-page/create! conn "Dec 16th, 2024" {}) + [_ day2-uuid] (outliner-page/create! conn "Dec 17th, 2024" {}) + day1 (d/entity @conn [:block/uuid day1-uuid]) + day2 (d/entity @conn [:block/uuid day2-uuid])] + (is (nil? (:logseq.property.journal/previous day1)) + "First journal has no previous") + (is (= day2-uuid (:block/uuid (:logseq.property.journal/next day1))) + "First journal links forward to the second") + (is (= day1-uuid (:block/uuid (:logseq.property.journal/previous day2))) + "Second journal links back to the first") + (is (nil? (:logseq.property.journal/next day2)) + "Second journal has no next yet"))) + +(deftest create-journal-fills-gap-relinks-neighbors + (let [conn (db-test/create-conn) + [_ day1-uuid] (outliner-page/create! conn "Dec 16th, 2024" {}) + [_ day3-uuid] (outliner-page/create! conn "Dec 18th, 2024" {}) + [_ day2-uuid] (outliner-page/create! conn "Dec 17th, 2024" {}) + day1 (d/entity @conn [:block/uuid day1-uuid]) + day2 (d/entity @conn [:block/uuid day2-uuid]) + day3 (d/entity @conn [:block/uuid day3-uuid])] + (is (= day2-uuid (:block/uuid (:logseq.property.journal/next day1))) + "Day1 now points to the newly created day2, not day3") + (is (= day1-uuid (:block/uuid (:logseq.property.journal/previous day2))) + "Day2 points back to day1") + (is (= day3-uuid (:block/uuid (:logseq.property.journal/next day2))) + "Day2 points forward to day3") + (is (= day2-uuid (:block/uuid (:logseq.property.journal/previous day3))) + "Day3 now points back to day2, not day1"))) + +(deftest create-namespace-page-under-contents + (let [conn (db-test/create-conn) + [_ child-uuid] (outliner-page/create! conn (str common-config/contents-page-name "/rama") {:split-namespace? true}) + child-page (d/entity @conn [:block/uuid child-uuid]) + contents (ldb/get-contents-page @conn) + library (ldb/get-built-in-page @conn common-config/library-page-name)] + (is (= "rama" (:block/title child-page))) + (is (= (:db/id contents) (:db/id (:block/parent child-page))) + "Contents can be used as an explicit parent root, alongside Library") + (is (empty? (:block/_parent library)) + "Library is untouched when a page is nested under Contents instead") + (is (ldb/contents? contents)))) diff --git a/src/main/frontend/worker/db/migrate.cljs b/src/main/frontend/worker/db/migrate.cljs index 97b6f29..a175a91 100644 --- a/src/main/frontend/worker/db/migrate.cljs +++ b/src/main/frontend/worker/db/migrate.cljs @@ -120,6 +120,18 @@ [[:db/retract (:db/id class) :block/order (:block/order class)]])))) [:logseq.class/Comments :logseq.class/Comment])))) +(defn- link-existing-journals + "Backfill :logseq.property.journal/previous and /next across all existing + journal pages, ordered by :block/journal-day." + [db] + (let [journal-pages (->> (d/datoms db :avet :block/journal-day) + (map (fn [datom] (d/entity db (:e datom)))) + (sort-by :block/journal-day))] + (->> (partition 2 1 journal-pages) + (mapcat (fn [[a b]] + [[:db/add (:db/id a) :logseq.property.journal/next (:db/id b)] + [:db/add (:db/id b) :logseq.property.journal/previous (:db/id a)]]))))) + (def schema-version->updates "A vec of tuples defining datascript migrations. Each tuple consists of the schema version integer and a migration map. A migration map can have keys of :properties, :classes @@ -162,7 +174,12 @@ :logseq.property.view/gallery-display-properties :logseq.property.view/gallery-card-size :logseq.property.view/gallery-card-width - :logseq.property.view/gallery-card-height]}]]) + :logseq.property.view/gallery-card-height]}] + ["65.34" {:properties [:logseq.property.journal/previous :logseq.property.journal/next] + :fix link-existing-journals}] + ["65.35" {:classes [:logseq.class/Project :logseq.class/Area]}] + ["65.36" {:classes [:logseq.class/Citation] + :properties [:logseq.property.citation/target :logseq.property.citation/context]}]]) (let [[major minor] (last (sort (map (comp (juxt :major :minor) db-schema/parse-schema-version first) schema-version->updates)))] @@ -185,7 +202,7 @@ (= "logseq.kv" (some-> (:db/ident data) namespace)) nil - (= (:block/title data) "Contents") + (= (:block/title data) common-config/contents-page-name) nil (:file/path data)