Move page-create to worker

pull/10839/head
Tienson Qin 2024-01-05 01:02:22 +08:00
parent 8c3ed702f7
commit 086a052ee7
10 changed files with 234 additions and 222 deletions

View File

@ -97,6 +97,13 @@
(:date-formatter config)
"MMM do, yyyy"))
(defn get-preferred-format
[config]
(or
(when-let [fmt (:preferred-format config)]
(keyword (string/lower-case (name fmt))))
:markdown))
(defn get-block-pattern
[format]
(let [format' (keyword format)]

View File

@ -1,7 +1,8 @@
(ns logseq.common.util.page-ref
"Core vars and util fns for page-ref. Currently this only handles a logseq
page-ref e.g. [[page name]]"
(:require [clojure.string :as string]))
(:require [clojure.string :as string]
["path" :as path]))
(def left-brackets "Opening characters for page-ref" "[[")
(def right-brackets "Closing characters for page-ref" "]]")
@ -12,6 +13,20 @@
(def page-ref-re "Inner capture and doesn't match nested brackets" #"\[\[(.*?)\]\]")
(def page-ref-without-nested-re "Matches most inner nested brackets" #"\[\[([^\[\]]+)\]\]")
(def page-ref-any-re "Inner capture that matches anything between brackets" #"\[\[(.*)\]\]")
(def org-page-ref-re #"\[\[(file:.*)\]\[.+?\]\]")
(def markdown-page-ref-re #"\[(.*)\]\(file:.*\)")
(defn get-file-basename
"Returns the basename of a file path. e.g. /a/b/c.md -> c.md"
[path]
(when-not (string/blank? path)
(.-base (path/parse (string/replace path "+" "/")))))
(defn get-file-rootname
"Returns the rootname of a file path. e.g. /a/b/c.md -> c"
[path]
(when-not (string/blank? path)
(.-name (path/parse (string/replace path "+" "/")))))
(defn page-ref?
"Determines if string is page-ref. Avoid using with format-specific page-refs e.g. org"
@ -25,12 +40,25 @@
(str left-brackets page-name right-brackets))
(defn get-page-name
"Extracts page-name from page-ref string"
"Extracts page names from format-specific page-refs e.g. org/md specific and
logseq page-refs. Only call in contexts where format-specific page-refs are
used. For logseq page-refs use page-ref/get-page-name"
[s]
(second (re-matches page-ref-any-re s)))
(and (string? s)
(or (when-let [[_ label _path] (re-matches markdown-page-ref-re s)]
(string/trim label))
(when-let [[_ path _label] (re-matches org-page-ref-re s)]
(some-> (get-file-rootname path)
(string/replace "." "/")))
(-> (re-matches page-ref-re s)
second))))
(defn get-page-name!
"Extracts page-name from page-ref and fall back to arg. Useful for when user
input may (not) be a page-ref"
[s]
(or (get-page-name s) s))
(defn page-ref-un-brackets!
[s]
(or (get-page-name s) s))

View File

@ -5,7 +5,8 @@
[clojure.set :as set]
[goog.string :as gstring]
[goog.string.format]
[logseq.graph-parser.mldoc :as gp-mldoc]))
[logseq.graph-parser.mldoc :as gp-mldoc]
[logseq.common.util.page-ref :as page-ref]))
(def colons "Property delimiter for markdown mode" "::")
(defn colons-org
@ -330,3 +331,21 @@
:else
content))
(defn insert-properties
[repo format content kvs]
(reduce
(fn [content [k v]]
(let [k (if (string? k)
(keyword (-> (string/lower-case k)
(string/replace " " "-")))
k)
v (if (coll? v)
(some->>
(seq v)
(distinct)
(map (fn [item] (page-ref/->page-ref (page-ref/page-ref-un-brackets! item))))
(string/join ", "))
v)]
(insert-property repo format content k v)))
content kvs))

View File

@ -1,7 +1,6 @@
(ns logseq.graph-parser.text
"Miscellaneous text util fns for the parser"
(:require ["path" :as path]
[goog.string :as gstring]
(:require [goog.string :as gstring]
[clojure.string :as string]
[clojure.set :as set]
[logseq.graph-parser.property :as gp-property]
@ -9,39 +8,11 @@
[logseq.common.util :as common-util]
[logseq.common.util.page-ref :as page-ref]))
(defn get-file-basename
"Returns the basename of a file path. e.g. /a/b/c.md -> c.md"
[path]
(when-not (string/blank? path)
(.-base (path/parse (string/replace path "+" "/")))))
(def get-file-basename page-ref/get-file-basename)
(defn get-file-rootname
"Returns the rootname of a file path. e.g. /a/b/c.md -> c"
[path]
(when-not (string/blank? path)
(.-name (path/parse (string/replace path "+" "/")))))
(def get-page-name page-ref/get-page-name)
(def page-ref-re-0 #"\[\[(.*)\]\]")
(def org-page-ref-re #"\[\[(file:.*)\]\[.+?\]\]")
(def markdown-page-ref-re #"\[(.*)\]\(file:.*\)")
(defn get-page-name
"Extracts page names from format-specific page-refs e.g. org/md specific and
logseq page-refs. Only call in contexts where format-specific page-refs are
used. For logseq page-refs use page-ref/get-page-name"
[s]
(and (string? s)
(or (when-let [[_ label _path] (re-matches markdown-page-ref-re s)]
(string/trim label))
(when-let [[_ path _label] (re-matches org-page-ref-re s)]
(some-> (get-file-rootname path)
(string/replace "." "/")))
(-> (re-matches page-ref-re-0 s)
second))))
(defn page-ref-un-brackets!
[s]
(or (get-page-name s) s))
(def page-ref-un-brackets! page-ref/page-ref-un-brackets!)
(defn get-nested-page-name
[page-name]

View File

@ -17,12 +17,15 @@
[logseq.db.frontend.property :as db-property]
[datascript.core :as d]
[logseq.graph-parser.whiteboard :as gp-whiteboard]
[frontend.worker.handler.page :as worker-page]
[frontend.worker.state :as worker-state]
[frontend.db.rtc.const :as rtc-const]
[frontend.db.rtc.op-mem-layer :as op-mem-layer]
[frontend.db.rtc.ws :as ws]
[frontend.handler.page :as page-handler]
[frontend.handler.user :as user]))
@ -323,31 +326,32 @@
(defn apply-remote-update-page-ops
[repo conn date-formatter update-page-ops]
(doseq [{:keys [self page-name original-name] :as op-value} update-page-ops]
(let [old-page-original-name (:block/original-name (d/entity @conn [:block/uuid self]))
exist-page (d/entity @conn [:block/name page-name])]
(cond
(let [config (worker-state/get-config repo)]
(doseq [{:keys [self page-name original-name] :as op-value} update-page-ops]
(let [old-page-original-name (:block/original-name (d/entity @conn [:block/uuid self]))
exist-page (d/entity @conn [:block/name page-name])
create-opts {:create-first-block? false
:uuid self :persist-op? false}]
(cond
;; same name but different uuid
;; remote page has same block/name as local's, but they don't have same block/uuid.
;; 1. rename local page's name to '<origin-name>-<ms-epoch>-Conflict'
;; 2. create page, name=<origin-name>, uuid=remote-uuid
(and exist-page (not= (:block/uuid exist-page) self))
(do (page-handler/rename! original-name (common-util/format "%s-%s-CONFLICT" original-name (tc/to-long (t/now))))
(page-handler/create! original-name {:redirect? false :create-first-block? false
:uuid self :persist-op? false}))
(and exist-page (not= (:block/uuid exist-page) self))
(do (page-handler/rename! original-name (common-util/format "%s-%s-CONFLICT" original-name (tc/to-long (t/now))))
(worker-page/create! repo conn config original-name create-opts))
;; a client-page has same uuid as remote but different page-names,
;; then we need to rename the client-page to remote-page-name
(and old-page-original-name (not= old-page-original-name original-name))
(page-handler/rename! old-page-original-name original-name false false)
(and old-page-original-name (not= old-page-original-name original-name))
(page-handler/rename! old-page-original-name original-name false false)
;; no such page, name=remote-page-name, OR, uuid=remote-block-uuid
;; just create-page
:else
(page-handler/create! original-name {:redirect? false :create-first-block? false
:uuid self :persist-op? false}))
:else
(worker-page/create! repo conn config original-name create-opts))
(update-block-attrs repo conn date-formatter self op-value))))
(update-block-attrs repo conn date-formatter self op-value)))))
(defn apply-remote-remove-page-ops
[conn remove-page-ops]
@ -832,6 +836,8 @@
{:*rtc-state (atom :closed :validator rtc-state-validator)
:*graph-uuid (atom nil)
:*repo (atom nil)
:*db-conn (atom nil)
:*date-formatter (atom nil)
:data-from-ws-chan data-from-ws-chan
:data-from-ws-pub (async/pub data-from-ws-chan :req-id)
:toggle-auto-push-client-ops-chan (chan (async/sliding-buffer 1))

View File

@ -9,178 +9,44 @@
[frontend.db.utils :as db-utils]
[frontend.format.block :as block]
[frontend.fs :as fs]
[frontend.handler.common :as common-handler]
[frontend.handler.config :as config-handler]
[frontend.handler.editor :as editor-handler]
[frontend.handler.file-based.editor :as file-editor-handler]
[frontend.handler.file-based.page-property :as file-page-property]
[frontend.handler.route :as route-handler]
[frontend.handler.ui :as ui-handler]
[frontend.util.fs :as fs-util]
[frontend.util :as util]
[logseq.db.frontend.schema :as db-schema]
[logseq.graph-parser.block :as gp-block]
[logseq.common.util :as common-util]
[logseq.graph-parser.text :as text]
[lambdaisland.glogi :as log]
[medley.core :as medley]
[promesa.core :as p]
[clojure.string :as string]))
[clojure.string :as string]
[frontend.worker.handler.page :as worker-page]))
;; create! and its helpers
;; =======================
(defn- build-title [page]
;; Don't wrap `\"` anymore, as title property is not effected by `,` now
;; The previous extract behavior isn't unwrapping the `'"` either. So no need
;; to maintain the compatibility.
(:block/original-name page))
(defn- default-properties-block
([title format page]
(default-properties-block title format page {}))
([title format page properties]
(let [repo (state/get-current-repo)
db-based? (config/db-based-graph? repo)]
(when-not db-based?
(let [p (common-handler/get-page-default-properties title)
ps (merge p properties)
content (if db-based?
""
(file-page-property/insert-properties format "" ps))
refs (gp-block/get-page-refs-from-properties properties
(db/get-db repo)
(state/get-date-formatter)
(state/get-config))]
{:block/uuid (db/new-block-id)
:block/refs refs
:block/left page
:block/format format
:block/content content
:block/parent page
:block/page page
:block/pre-block? true
:block/properties ps
:block/properties-order (keys ps)})))))
(defn- create-title-property?
[repo journal? page-name]
(and (not (config/db-based-graph? repo))
(not journal?)
(= (state/get-filename-format) :legacy) ;; reduce title computation
(fs-util/create-title-property? page-name)))
(defn- build-page-tx [repo format properties page journal? {:keys [whiteboard? class? tags]}]
(when (:block/uuid page)
(let [page-entity [:block/uuid (:block/uuid page)]
title (util/get-page-original-name page)
create-title? (create-title-property? repo journal? title)
page (merge page
(when (seq properties) {:block/properties properties})
(when whiteboard? {:block/type "whiteboard"})
(when class? {:block/type "class"})
(when tags {:block/tags (mapv #(hash-map :db/id
(:db/id (db/entity repo [:block/uuid %])))
tags)}))
page-empty? (db/page-empty? (state/get-current-repo) (:block/name page))
db-based? (config/db-based-graph? (state/get-current-repo))]
(cond
(not page-empty?)
[page]
(and create-title?
(not whiteboard?)
(not db-based?))
(let [properties-block (default-properties-block (build-title page) format page-entity properties)]
[page
properties-block])
(and (seq properties)
(not whiteboard?)
(not db-based?))
[page (file-editor-handler/properties-block repo properties format page-entity)]
:else
[page]))))
;; TODO: Move file graph concerns to file-based-handler ns
(defn create!
"Create page. Has the following options:
* :redirect? - when true, redirect to the created page, otherwise return sanitized page name.
* :split-namespace? - when true, split hierarchical namespace into levels.
* :create-first-block? - when true, create an empty block if the page is empty.
* :uuid - when set, use this uuid instead of generating a new one.
* :class? - when true, adds a :block/type 'class'
* :whiteboard? - when true, adds a :block/type 'whiteboard'
* :tags - tag uuids that are added to :block/tags
* :persist-op? - when true, add an update-page op
TODO: Add other options"
"
([title]
(create! title {}))
([title {:keys [redirect? create-first-block? format properties split-namespace? journal? uuid rename? persist-op? whiteboard? class?]
:or {redirect? true
create-first-block? true
rename? false
format nil
properties nil
split-namespace? true
uuid nil
persist-op? true}
([title {:keys [redirect?]
:or {redirect? true}
:as options}]
(let [title (-> (string/trim title)
(text/page-ref-un-brackets!)
;; remove `#` from tags
(string/replace #"^#+" ""))
title (common-util/remove-boundary-slashes title)
page-name (util/page-name-sanity-lc title)
repo (state/get-current-repo)
with-uuid? (if (uuid? uuid) uuid true)] ;; FIXME: prettier validation
(when (or (db/page-empty? repo page-name) rename?)
(let [pages (if split-namespace?
(common-util/split-namespace-pages title)
[title])
format (or format (state/get-preferred-format))
pages (map (fn [page]
;; only apply uuid to the deepest hierarchy of page to create if provided.
(-> (block/page-name->map page (if (= page title) with-uuid? true))
(assoc :block/format format)))
pages)
txs (->> pages
;; for namespace pages, only last page need properties
drop-last
(mapcat #(build-page-tx repo format nil % journal? {}))
(remove nil?))
txs (map-indexed (fn [i page]
(if (zero? i)
page
(assoc page :block/namespace
[:block/uuid (:block/uuid (nth txs (dec i)))])))
txs)
last-txs (build-page-tx repo format properties (last pages) journal? (select-keys options [:whiteboard? :class? :tags]))
last-txs (if (seq txs)
(update last-txs 0
(fn [p]
(assoc p :block/namespace [:block/uuid (:block/uuid (last txs))])))
last-txs)
txs (concat
(when (and rename? uuid)
(when-let [e (db/entity [:block/uuid uuid])]
[[:db/retract (:db/id e) :block/namespace]
[:db/retract (:db/id e) :block/refs]]))
txs
last-txs)]
(when (seq txs)
(db/transact! repo txs {:persist-op? persist-op?})))
(when (and create-first-block? (not (or whiteboard? class?)))
(when (or
(db/page-empty? repo (:db/id (db/entity [:block/name page-name])))
(create-title-property? repo journal? page-name))
(editor-handler/api-insert-new-block! "" {:page page-name}))))
(when redirect?
(route-handler/redirect-to-page! page-name))
page-name)))
(let [repo (state/get-current-repo)
conn (db/get-db repo false)
config (state/get-config repo)]
(when-let [page-name (worker-page/create! repo conn config title options)]
(when redirect?
(route-handler/redirect-to-page! page-name))
page-name))))
;; favorite fns
;; ============
@ -293,7 +159,7 @@
(defn delete!
"Deletes a page and then either calls the ok-handler or the error-handler if unable to delete"
[page-name ok-handler & {:keys [delete-file? redirect-to-home? persist-op? error-handler]
:or {delete-file? true
:or {delete-file? true
redirect-to-home? true
persist-op? true
error-handler (fn [{:keys [msg]}] (log/error :msg msg))}}]
@ -332,9 +198,8 @@
(unfavorite-page! page-name)
(when redirect-to-home? (route-handler/redirect-to-home!))
(when (fn? ok-handler) (ok-handler))
(when redirect-to-home? (route-handler/redirect-to-home!))
(ui-handler/re-render-root!)))))))

View File

@ -5,9 +5,7 @@
[clojure.set :as set]
[frontend.config :as config]
[logseq.graph-parser.property :as gp-property :refer [properties-start properties-end]]
[logseq.common.util.page-ref :as page-ref]
[frontend.format.mldoc :as mldoc]
[logseq.graph-parser.text :as text]
[frontend.db :as db]
[frontend.state :as state]
[frontend.util.cursor :as cursor]))
@ -165,21 +163,8 @@
(defn insert-properties
[format content kvs]
(reduce
(fn [content [k v]]
(let [k (if (string? k)
(keyword (-> (string/lower-case k)
(string/replace " " "-")))
k)
v (if (coll? v)
(some->>
(seq v)
(distinct)
(map (fn [item] (page-ref/->page-ref (text/page-ref-un-brackets! item))))
(string/join ", "))
v)]
(insert-property format content k v)))
content kvs))
(let [repo (state/get-current-repo)]
(gp-property/insert-properties repo format content kvs)))
(def remove-property gp-property/remove-property)

View File

@ -454,10 +454,8 @@ should be done through this fn in order to get global config and config defaults
([repo-url]
(keyword
(or
(when-let [fmt (:preferred-format (get-config repo-url))]
(string/lower-case (name fmt)))
(get-in @state [:me :preferred_format] "markdown")))))
(common-config/get-preferred-format (get-config repo-url))
(get-in @state [:me :preferred_format] "markdown")))))
(defn markdown?
[]

View File

@ -0,0 +1,126 @@
(ns frontend.worker.handler.page
(:require [logseq.db :as ldb]
[logseq.graph-parser.block :as gp-block]
[logseq.graph-parser.property :as gp-property]
[logseq.db.sqlite.util :as sqlite-util]
[datascript.core :as d]
[clojure.string :as string]
[frontend.worker.date :as date]
[logseq.graph-parser.text :as text]
[logseq.common.util :as common-util]
[logseq.common.config :as common-config]))
(defn properties-block
[repo conn config date-formatter properties format page]
(let [content (gp-property/insert-properties repo format "" properties)
refs (gp-block/get-page-refs-from-properties properties @conn date-formatter config)]
{:block/pre-block? true
:block/uuid (ldb/new-block-id)
:block/properties properties
:block/properties-order (keys properties)
:block/refs refs
:block/left page
:block/format format
:block/content content
:block/parent page
:block/page page}))
(defn- build-page-tx [repo conn config date-formatter format properties page {:keys [whiteboard? class? tags]}]
(when (:block/uuid page)
(let [page-entity [:block/uuid (:block/uuid page)]
page (merge page
(when (seq properties) {:block/properties properties})
(when whiteboard? {:block/type "whiteboard"})
(when class? {:block/type "class"})
(when tags {:block/tags (mapv #(hash-map :db/id
(:db/id (d/entity @conn [:block/uuid %])))
tags)}))
page-empty? (ldb/page-empty? @conn (:block/name page))
db-based? (sqlite-util/db-based-graph? repo)]
(if (and (seq properties)
(not whiteboard?)
(not db-based?)
page-empty?)
[page (properties-block repo conn config date-formatter properties format page-entity)]
[page]))))
(defn create!
"Create page. Has the following options:
* :create-first-block? - when true, create an empty block if the page is empty.
* :uuid - when set, use this uuid instead of generating a new one.
* :class? - when true, adds a :block/type 'class'
* :whiteboard? - when true, adds a :block/type 'whiteboard'
* :tags - tag uuids that are added to :block/tags
* :persist-op? - when true, add an update-page op
TODO: Add other options"
[repo conn config title
& {:keys [create-first-block? format properties uuid rename? persist-op? whiteboard? class?]
:or {create-first-block? true
rename? false
format nil
properties nil
uuid nil
persist-op? true}
:as options}]
(let [date-formatter (common-config/get-date-formatter config)
split-namespace? (not (or (string/starts-with? title "hls__")
(date/valid-journal-title? date-formatter title)))
title (-> (string/trim title)
(text/page-ref-un-brackets!)
;; remove `#` from tags
(string/replace #"^#+" ""))
title (common-util/remove-boundary-slashes title)
page-name (common-util/page-name-sanity-lc title)
with-uuid? (if (uuid? uuid) uuid true)] ;; FIXME: prettier validation
(when (or (ldb/page-empty? @conn page-name) rename?)
(let [pages (if split-namespace?
(common-util/split-namespace-pages title)
[title])
format (or format (common-config/get-preferred-format config))
pages (map (fn [page]
;; only apply uuid to the deepest hierarchy of page to create if provided.
(-> (gp-block/page-name->map page (if (= page title) with-uuid? true) @conn true date-formatter)
(assoc :block/format format)))
pages)
txs (->> pages
;; for namespace pages, only last page need properties
drop-last
(mapcat #(build-page-tx repo conn config date-formatter format nil % {}))
(remove nil?))
txs (map-indexed (fn [i page]
(if (zero? i)
page
(assoc page :block/namespace
[:block/uuid (:block/uuid (nth txs (dec i)))])))
txs)
last-txs (build-page-tx repo conn config date-formatter format properties (last pages) (select-keys options [:whiteboard? :class? :tags]))
last-txs (if (seq txs)
(update last-txs 0
(fn [p]
(assoc p :block/namespace [:block/uuid (:block/uuid (last txs))])))
last-txs)
first-block-tx (when (and
create-first-block?
(not (or whiteboard? class?))
(ldb/page-empty? @conn (:db/id (d/entity @conn [:block/name page-name])))
(seq txs))
(let [page-id [:block/uuid (:block/uuid (last txs))]]
[(sqlite-util/block-with-timestamps
{:block/uuid (ldb/new-block-id)
:block/page page-id
:block/parent page-id
:block/left page-id
:block/content ""
:block/format format})]))
txs (concat
(when (and rename? uuid)
(when-let [e (d/entity @conn [:block/uuid uuid])]
[[:db/retract (:db/id e) :block/namespace]
[:db/retract (:db/id e) :block/refs]]))
txs
last-txs
first-block-tx)]
(when (seq txs)
(d/transact! conn txs {:persist-op? persist-op?})
page-name)))))

View File

@ -3,7 +3,10 @@
(:require [logseq.common.util :as common-util]))
(defonce *state (atom {:db/latest-transact-time {}
:worker/context {}}))
:worker/context {}
;; FIXME load graph config when fetch-initial-data
:config {}}))
(defonce *sqlite (atom nil))
;; repo -> {:db conn :search conn}
@ -50,3 +53,7 @@
(defn set-context!
[context]
(swap! *state assoc :worker/context context))
(defn get-config
[repo]
(get-in @*state [:config repo]))