refactor: move db graph page operations to outliner dep

This commit is contained in:
Tienson Qin
2025-09-25 21:34:45 +08:00
parent 9705f57053
commit 20a477c35b
8 changed files with 151 additions and 109 deletions

View File

@@ -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

View File

@@ -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)
(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)))))))
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)

View File

@@ -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")

View File

@@ -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"

View File

@@ -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

View File

@@ -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)))

View File

@@ -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))))

View File

@@ -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