From 20a477c35b057b3e852b8ea164180c49bab64799 Mon Sep 17 00:00:00 2001 From: Tienson Qin Date: Thu, 25 Sep 2025 21:34:45 +0800 Subject: [PATCH] refactor: move db graph page operations to outliner dep --- .../outliner/src/logseq/outliner}/page.cljs | 67 ++++++++++++++- .../src/logseq/outliner/property.cljs | 21 +++-- .../test/logseq/outliner}/page_test.cljs | 34 ++++---- .../logseq/tasks/dev/db_and_file_graphs.clj | 3 +- src/main/frontend/worker/commands.cljs | 4 +- src/main/frontend/worker/handler/page.cljs | 85 ++----------------- .../handler/page/file_based/delete.cljs | 39 +++++++++ src/main/logseq/api.cljs | 7 +- 8 files changed, 151 insertions(+), 109 deletions(-) rename {src/main/frontend/worker/handler/page/db_based => deps/outliner/src/logseq/outliner}/page.cljs (76%) rename {src/test/frontend/worker/handler/page/db_based => deps/outliner/test/logseq/outliner}/page_test.cljs (76%) create mode 100644 src/main/frontend/worker/handler/page/file_based/delete.cljs diff --git a/src/main/frontend/worker/handler/page/db_based/page.cljs b/deps/outliner/src/logseq/outliner/page.cljs similarity index 76% rename from src/main/frontend/worker/handler/page/db_based/page.cljs rename to deps/outliner/src/logseq/outliner/page.cljs index a646f13fd2..41e33cbe8d 100644 --- a/src/main/frontend/worker/handler/page/db_based/page.cljs +++ b/deps/outliner/src/logseq/outliner/page.cljs @@ -1,5 +1,5 @@ -(ns frontend.worker.handler.page.db-based.page - "Page operations for DB graphs" +(ns logseq.outliner.page + "Page-related fns" (:require [clojure.string :as string] [datascript.core :as d] [datascript.impl.entity :as de] @@ -10,6 +10,7 @@ [logseq.db.common.entity-plus :as entity-plus] [logseq.db.common.order :as db-order] [logseq.db.frontend.class :as db-class] + [logseq.db.frontend.content :as db-content] [logseq.db.frontend.entity-util :as entity-util] [logseq.db.frontend.malli-schema :as db-malli-schema] [logseq.db.frontend.property :as db-property] @@ -18,6 +19,68 @@ [logseq.graph-parser.text :as text] [logseq.outliner.validate :as outliner-validate])) +(defn db-refs->page + "Replace [[page name]] with page name" + [page-entity] + (let [refs (:block/_refs page-entity) + id-ref->page #(db-content/content-id-ref->page % [page-entity])] + (when (seq refs) + (let [tx-data (mapcat (fn [{:block/keys [raw-title] :as ref}] + ;; block content + (let [content' (id-ref->page raw-title) + content-tx (when (not= raw-title content') + {:db/id (:db/id ref) + :block/title content'}) + tx content-tx] + (concat + [[:db/retract (:db/id ref) :block/refs (:db/id page-entity)]] + (when tx [tx])))) refs)] + tx-data)))) + +(defn delete! + "Deletes a page. Returns true if able to delete page. If unable to delete, + calls error-handler fn and returns false" + [conn page-uuid & {:keys [persist-op? rename? error-handler] + :or {persist-op? true + error-handler (fn [{:keys [msg]}] (js/console.error msg))}}] + (assert (uuid? page-uuid) (str "frontend.worker.handler.page/delete! srong page-uuid: " (if page-uuid page-uuid "nil"))) + (when page-uuid + (when-let [page (d/entity @conn [:block/uuid page-uuid])] + (let [blocks (:block/_page page) + truncate-blocks-tx-data (mapv + (fn [block] + [:db.fn/retractEntity [:block/uuid (:block/uuid block)]]) + blocks)] + ;; TODO: maybe we should add $$$favorites to built-in pages? + (if (or (ldb/built-in? page) (ldb/hidden? page)) + (do + (error-handler {:msg "Built-in page cannot be deleted"}) + false) + (let [delete-property-tx (when (ldb/property? page) + (concat + (let [datoms (d/datoms @conn :avet (:db/ident page))] + (map (fn [d] [:db/retract (:e d) (:a d)]) datoms)) + (map (fn [d] [:db/retractEntity (:e d)]) + (d/datoms @conn :avet :logseq.property.history/property (:db/ident page))))) + delete-page-tx (concat (db-refs->page page) + delete-property-tx + [[:db.fn/retractEntity (:db/id page)]]) + restore-class-parent-tx (->> (filter ldb/class? (:logseq.property.class/_extends page)) + (map (fn [p] + {:db/id (:db/id p) + :logseq.property.class/extends :logseq.class/Root}))) + tx-data (concat truncate-blocks-tx-data + restore-class-parent-tx + delete-page-tx)] + + (ldb/transact! conn tx-data + (cond-> {:outliner-op :delete-page + :deleted-page (str (:block/uuid page)) + :persist-op? persist-op?} + rename? + (assoc :real-outliner-op :rename-page))) + true)))))) + (defn- build-page-tx [db properties page {:keys [whiteboard? class? tags]}] (when (:block/uuid page) (let [type-tag (cond class? :logseq.class/Tag diff --git a/deps/outliner/src/logseq/outliner/property.cljs b/deps/outliner/src/logseq/outliner/property.cljs index 488d496b25..aa0dba345d 100644 --- a/deps/outliner/src/logseq/outliner/property.cljs +++ b/deps/outliner/src/logseq/outliner/property.cljs @@ -307,13 +307,24 @@ a property ref value for a string value if necessary" [conn property-id v property-type] (let [number-property? (= property-type :number)] - (if (and (integer? v) - (or (not number-property?) + (cond + (and (integer? v) + (or (not number-property?) ;; Allows :number property to use number as a ref (for closed value) or value - (and number-property? - (or (= property-id (:db/ident (:logseq.property/created-from-property (d/entity @conn v)))) - (= :logseq.property/empty-placeholder (:db/ident (d/entity @conn v))))))) + (and number-property? + (or (= property-id (:db/ident (:logseq.property/created-from-property (d/entity @conn v)))) + (= :logseq.property/empty-placeholder (:db/ident (d/entity @conn v))))))) v + + (= property-type :page) + (if (or (string/blank? v) (not (string? v))) + (throw (ex-info "Value should be non-empty string" {:property-id property-id + :property-type property-type + :v v})) + + ;; TODO: create page + nil) + :else ;; only value-ref-property types should call this (when-let [v' (if (and number-property? (string? v)) (parse-double v) diff --git a/src/test/frontend/worker/handler/page/db_based/page_test.cljs b/deps/outliner/test/logseq/outliner/page_test.cljs similarity index 76% rename from src/test/frontend/worker/handler/page/db_based/page_test.cljs rename to deps/outliner/test/logseq/outliner/page_test.cljs index 2515343c3f..8f9022b02e 100644 --- a/src/test/frontend/worker/handler/page/db_based/page_test.cljs +++ b/deps/outliner/test/logseq/outliner/page_test.cljs @@ -1,16 +1,16 @@ -(ns frontend.worker.handler.page.db-based.page-test +(ns logseq.outliner.page-test (:require [cljs.test :refer [deftest is testing]] [datascript.core :as d] - [frontend.worker.handler.page.db-based.page :as worker-db-page] [logseq.common.config :as common-config] [logseq.db :as ldb] [logseq.db.frontend.db :as db-db] - [logseq.db.test.helper :as db-test])) + [logseq.db.test.helper :as db-test] + [logseq.outliner.page :as outliner-page])) (deftest create-class (let [conn (db-test/create-conn) - _ (worker-db-page/create! conn "movie" {:class? true}) - _ (worker-db-page/create! conn "Movie" {:class? true}) + _ (outliner-page/create! conn "movie" {:class? true}) + _ (outliner-page/create! conn "Movie" {:class? true}) movie-class (ldb/get-case-page @conn "movie") Movie-class (ldb/get-case-page @conn "Movie")] @@ -25,13 +25,13 @@ :pages-and-blocks [{:page {:block/title "page1"}}]})] (testing "Basic valid workflows" - (let [[_ child-uuid] (worker-db-page/create! conn "foo/bar/baz" {:split-namespace? true}) + (let [[_ child-uuid] (outliner-page/create! conn "foo/bar/baz" {:split-namespace? true}) child-page (d/entity @conn [:block/uuid child-uuid]) ;; Create a 2nd child page using existing parent pages - [_ child-uuid2] (worker-db-page/create! conn "foo/bar/baz2" {:split-namespace? true}) + [_ child-uuid2] (outliner-page/create! conn "foo/bar/baz2" {:split-namespace? true}) child-page2 (d/entity @conn [:block/uuid child-uuid2]) ;; Create a child page for a class - [_ child-uuid3] (worker-db-page/create! conn "c1/c2" {:split-namespace? true :class? true}) + [_ child-uuid3] (outliner-page/create! conn "c1/c2" {:split-namespace? true :class? true}) child-page3 (d/entity @conn [:block/uuid child-uuid3]) library (ldb/get-built-in-page @conn common-config/library-page-name) bar (ldb/get-page @conn "bar")] @@ -48,7 +48,7 @@ (is (= #{"Root Tag" "c1"} (set (map :block/title (ldb/get-classes-parents [child-page3])))) "Child class with new parent has correct parents") - (worker-db-page/create! conn "foo/class1/baz3" {:split-namespace? true}) + (outliner-page/create! conn "foo/class1/baz3" {:split-namespace? true}) (is (= #{"Tag" "Page"} (set (d/q '[:find [?tag-title ...] :where @@ -58,8 +58,8 @@ "Using an existing class page in a multi-parent namespace doesn't allow a page to have a class parent and instead creates a new page"))) (testing "Child pages with same name and different parents" - (let [_ (worker-db-page/create! conn "vim/keys" {:split-namespace? true}) - _ (worker-db-page/create! conn "emacs/keys" {:split-namespace? true})] + (let [_ (outliner-page/create! conn "vim/keys" {:split-namespace? true}) + _ (outliner-page/create! conn "emacs/keys" {:split-namespace? true})] (is (= #{"vim" "emacs"} (->> (d/q '[:find [(pull ?b [{:block/parent [:block/title]}]) ...] :where [?b :block/title "keys"]] @conn) (map #(get-in % [:block/parent :block/title])) @@ -70,34 +70,34 @@ (is (thrown-with-msg? js/Error #"Cannot create" - (worker-db-page/create! conn "class1/page" {:split-namespace? true})) + (outliner-page/create! conn "class1/page" {:split-namespace? true})) "Page can't have a class parent") (is (thrown-with-msg? js/Error #"Cannot create" - (worker-db-page/create! conn "property1/page" {:split-namespace? true})) + (outliner-page/create! conn "property1/page" {:split-namespace? true})) "Page can't have a property parent") (is (thrown-with-msg? js/Error #"Cannot create" - (worker-db-page/create! conn "property1/class" {:split-namespace? true :class? true})) + (outliner-page/create! conn "property1/class" {:split-namespace? true :class? true})) "Class can't have a property parent")))) (deftest create-page (let [conn (db-test/create-conn) - [_ page-uuid] (worker-db-page/create! conn "fooz" {})] + [_ page-uuid] (outliner-page/create! conn "fooz" {})] (is (= "fooz" (:block/title (d/entity @conn [:block/uuid page-uuid]))) "Page created correctly") (is (thrown-with-msg? js/Error #"can't include \"/" - (worker-db-page/create! conn "foo/bar" {})) + (outliner-page/create! conn "foo/bar" {})) "Page can't have '/'n title"))) (deftest create-journal (let [conn (db-test/create-conn) - [_ page-uuid] (worker-db-page/create! conn "Dec 16th, 2024" {})] + [_ page-uuid] (outliner-page/create! conn "Dec 16th, 2024" {})] (is (= "Dec 16th, 2024" (:block/title (d/entity @conn [:block/uuid page-uuid]))) "Journal created correctly") diff --git a/scripts/src/logseq/tasks/dev/db_and_file_graphs.clj b/scripts/src/logseq/tasks/dev/db_and_file_graphs.clj index 83389b171c..64c724c5a6 100644 --- a/scripts/src/logseq/tasks/dev/db_and_file_graphs.clj +++ b/scripts/src/logseq/tasks/dev/db_and_file_graphs.clj @@ -44,8 +44,7 @@ "DB graph paths with :block/name" ["deps/db/src/logseq/db/frontend" "deps/db/src/logseq/db/sqlite" - "deps/outliner/src/logseq/outliner/property.cljs" - "src/main/frontend/worker/handler/page/db_based"]) + "deps/outliner/src/logseq/outliner/property.cljs"]) (def db-graph-paths "Paths _only_ for DB graphs" diff --git a/src/main/frontend/worker/commands.cljs b/src/main/frontend/worker/commands.cljs index 87936cff83..07626b4749 100644 --- a/src/main/frontend/worker/commands.cljs +++ b/src/main/frontend/worker/commands.cljs @@ -3,13 +3,13 @@ (:require [cljs-time.coerce :as tc] [cljs-time.core :as t] [datascript.core :as d] - [frontend.worker.handler.page.db-based.page :as worker-db-page] [logseq.common.util.date-time :as date-time-util] [logseq.db :as ldb] [logseq.db.frontend.property :as db-property] [logseq.db.frontend.property.build :as db-property-build] [logseq.db.frontend.property.type :as db-property-type] [logseq.db.sqlite.util :as sqlite-util] + [logseq.outliner.page :as outliner-page] [logseq.outliner.pipeline :as outliner-pipeline])) ;; TODO: allow users to add command or configure it through #Command (which parent should be #Code) @@ -172,7 +172,7 @@ {:page-uuid (:block/uuid (d/entity db journal-day))} (let [formatter (:logseq.property.journal/title-format (d/entity db :logseq.class/Journal)) title (date-time-util/format (t/to-default-time-zone (tc/to-date-time next-time-long)) formatter)] - (worker-db-page/create db title {}))) + (outliner-page/create db title {}))) value (if date? [:block/uuid page-uuid] next-time-long)] (concat tx-data diff --git a/src/main/frontend/worker/handler/page.cljs b/src/main/frontend/worker/handler/page.cljs index 4935898854..0c3b2ee305 100644 --- a/src/main/frontend/worker/handler/page.cljs +++ b/src/main/frontend/worker/handler/page.cljs @@ -1,21 +1,18 @@ (ns frontend.worker.handler.page "Page operations" - (:require [datascript.core :as d] - [frontend.worker.handler.page.db-based.page :as db-worker-page] + (:require [frontend.worker.handler.page.file-based.delete :as file-worker-page-delete] [frontend.worker.handler.page.file-based.page :as file-worker-page] [logseq.common.config :as common-config] [logseq.common.util :as common-util] [logseq.db :as ldb] - [logseq.db.frontend.content :as db-content] - [logseq.db.sqlite.util :as sqlite-util] [logseq.graph-parser.block :as gp-block] - [logseq.graph-parser.db :as gp-db])) + [logseq.outliner.page :as outliner-page])) (defn rtc-create-page! [conn config title {:keys [uuid old-db-id]}] (assert (uuid? uuid) (str "rtc-create-page! `uuid` is not a uuid " uuid)) (let [date-formatter (common-config/get-date-formatter config) - title (db-worker-page/sanitize-title title) + title (outliner-page/sanitize-title title) page-name (common-util/page-name-sanity-lc title) page (cond-> (gp-block/page-name->map title @conn true date-formatter {:page-uuid uuid @@ -39,79 +36,13 @@ TODO: Add other options" [repo conn config title & {:as options}] (if (ldb/db-based-graph? @conn) - (db-worker-page/create! conn title options) + (outliner-page/create! conn title options) (file-worker-page/create! repo conn config title options))) -(defn db-refs->page - "Replace [[page name]] with page name" - [repo page-entity] - (when (sqlite-util/db-based-graph? repo) - (let [refs (:block/_refs page-entity) - id-ref->page #(db-content/content-id-ref->page % [page-entity])] - (when (seq refs) - (let [tx-data (mapcat (fn [{:block/keys [raw-title] :as ref}] - ;; block content - (let [content' (id-ref->page raw-title) - content-tx (when (not= raw-title content') - {:db/id (:db/id ref) - :block/title content'}) - tx content-tx] - (concat - [[:db/retract (:db/id ref) :block/refs (:db/id page-entity)]] - (when tx [tx])))) refs)] - tx-data))))) - (defn delete! "Deletes a page. Returns true if able to delete page. If unable to delete, calls error-handler fn and returns false" - [repo conn page-uuid & {:keys [persist-op? rename? error-handler] - :or {persist-op? true - error-handler (fn [{:keys [msg]}] (js/console.error msg))}}] - (assert (uuid? page-uuid) (str "frontend.worker.handler.page/delete! srong page-uuid: " (if page-uuid page-uuid "nil"))) - (when (and repo page-uuid) - (when-let [page (d/entity @conn [:block/uuid page-uuid])] - (let [page-name (:block/name page) - blocks (:block/_page page) - truncate-blocks-tx-data (mapv - (fn [block] - [:db.fn/retractEntity [:block/uuid (:block/uuid block)]]) - blocks) - db-based? (sqlite-util/db-based-graph? repo)] - ;; TODO: maybe we should add $$$favorites to built-in pages? - (if (or (ldb/built-in? page) (ldb/hidden? page)) - (do - (error-handler {:msg "Built-in page cannot be deleted"}) - false) - (let [db @conn - file (when-not db-based? (gp-db/get-page-file db page-name)) - file-path (:file/path file) - delete-file-tx (when file - [[:db.fn/retractEntity [:file/path file-path]]]) - delete-property-tx (when (ldb/property? page) - (concat - (let [datoms (d/datoms @conn :avet (:db/ident page))] - (map (fn [d] [:db/retract (:e d) (:a d)]) datoms)) - (map (fn [d] [:db/retractEntity (:e d)]) - (d/datoms @conn :avet :logseq.property.history/property (:db/ident page))))) - delete-page-tx (concat (db-refs->page repo page) - delete-property-tx - [[:db.fn/retractEntity (:db/id page)]]) - restore-class-parent-tx (when db-based? - (->> (filter ldb/class? (:logseq.property.class/_extends page)) - (map (fn [p] - {:db/id (:db/id p) - :logseq.property.class/extends :logseq.class/Root})))) - tx-data (concat truncate-blocks-tx-data - restore-class-parent-tx - delete-page-tx - delete-file-tx)] - - (ldb/transact! conn tx-data - (cond-> {:outliner-op :delete-page - :deleted-page (str (:block/uuid page)) - :persist-op? persist-op?} - rename? - (assoc :real-outliner-op :rename-page) - file-path - (assoc :file-path file-path))) - true)))))) + [repo conn page-uuid & {:as options}] + (if (ldb/db-based-graph? @conn) + (outliner-page/delete! conn page-uuid options) + (file-worker-page-delete/delete! repo conn page-uuid options))) diff --git a/src/main/frontend/worker/handler/page/file_based/delete.cljs b/src/main/frontend/worker/handler/page/file_based/delete.cljs new file mode 100644 index 0000000000..3d7a0e98c6 --- /dev/null +++ b/src/main/frontend/worker/handler/page/file_based/delete.cljs @@ -0,0 +1,39 @@ +(ns frontend.worker.handler.page.file-based.delete + "File graph page delete" + (:require [datascript.core :as d] + [logseq.db :as ldb] + [logseq.graph-parser.db :as gp-db])) + +(defn delete! + "Deletes a page. Returns true if able to delete page. If unable to delete, + calls error-handler fn and returns false" + [repo conn page-uuid & {:keys [persist-op? rename?] + :or {persist-op? true}}] + (assert (uuid? page-uuid) (str "frontend.worker.handler.page/delete! requires page-uuid: " (if page-uuid page-uuid "nil"))) + (when (and repo page-uuid) + (when-let [page (d/entity @conn [:block/uuid page-uuid])] + (let [page-name (:block/name page) + blocks (:block/_page page) + truncate-blocks-tx-data (mapv + (fn [block] + [:db.fn/retractEntity [:block/uuid (:block/uuid block)]]) + blocks) + db @conn + file (gp-db/get-page-file db page-name) + file-path (:file/path file) + delete-file-tx (when file + [[:db.fn/retractEntity [:file/path file-path]]]) + delete-page-tx [[:db.fn/retractEntity (:db/id page)]] + tx-data (concat truncate-blocks-tx-data + delete-page-tx + delete-file-tx)] + + (ldb/transact! conn tx-data + (cond-> {:outliner-op :delete-page + :deleted-page (str (:block/uuid page)) + :persist-op? persist-op?} + rename? + (assoc :real-outliner-op :rename-page) + file-path + (assoc :file-path file-path))) + true)))) diff --git a/src/main/logseq/api.cljs b/src/main/logseq/api.cljs index c5a7d7f044..bc6ca33e93 100644 --- a/src/main/logseq/api.cljs +++ b/src/main/logseq/api.cljs @@ -760,7 +760,7 @@ :properties (when (not db-base?) (merge properties (when custom-uuid {:id custom-uuid})))}) - _ (when (and db-base? (some? properties)) + _ (when (and db-base? (seq properties)) (api-block/save-db-based-block-properties! new-block properties this))] (bean/->js (sdk-utils/normalize-keyword-for-json new-block))))))) @@ -800,7 +800,7 @@ opts (bean/->clj opts)] (when block (p/do! - (when (and db-base? (some? (:properties opts))) + (when (and db-base? (seq (:properties opts))) (api-block/save-db-based-block-properties! block (:properties opts))) (editor-handler/save-block! repo (sdk-utils/uuid-or-throw-error block-uuid) content @@ -947,8 +947,7 @@ value (bean/->clj value)] (when block (if db-base? - (p/do! - (api-block/save-db-based-block-properties! block {key' value} this)) + (api-block/save-db-based-block-properties! block {key' value} this) (property-handler/set-block-property! repo block-uuid key' value)))))) (defn ^:export remove_block_property