Browse Source

chore: split up page-handler into file-based, db-based and graph

Did this cleanup as I noticed that multiple fns were checking if they are in a
db context when they didn't need to. By organizing these namespaces, the
db and file versions will be easier to maintain.

While doing this refactor, I didn't change any logic except for removing
unnecessary db-based-graph? checks.  Also renamed a few fns to remove
redundant file- and db- prefixes
Gabriel Horner 2 years ago
parent
commit
fceb6ddb72

+ 5 - 0
.clj-kondo/config.edn

@@ -70,6 +70,7 @@
              frontend.handler.common.file file-common-handler
              frontend.handler.common.plugin plugin-common-handler
              frontend.handler.common.developer dev-common-handler
+             frontend.handler.common.page page-common-handler
              frontend.handler.config config-handler
              frontend.handler.editor.property editor-property
              frontend.handler.events events
@@ -77,9 +78,13 @@
              frontend.handler.ui ui-handler
              frontend.handler.notification notification
              frontend.handler.page page-handler
+             frontend.handler.db-based.editor db-editor-handler
+             frontend.handler.db-based.page db-page-handler
              frontend.handler.db-based.property db-property-handler
+             frontend.handler.file-based.page file-page-handler
              frontend.handler.file-based.property file-property
              frontend.handler.file-based.property.util property-util
+             frontend.handler.file-based.recent file-recent-handler
              frontend.handler.plugin plugin-handler
              frontend.handler.plugin-config plugin-config-handler
              frontend.handler.property.util pu

+ 3 - 3
src/main/frontend/components/conversion.cljs

@@ -7,7 +7,7 @@
             [frontend.util :as util]
             [frontend.state :as state]
             [frontend.ui :as ui]
-            [frontend.handler.page :as page-handler]
+            [frontend.handler.file-based.page :as file-page-handler]
             [frontend.handler.conversion :refer [supported-filename-formats write-filename-format! calc-rename-target]]
             [frontend.db :as db]
             [frontend.context.i18n :refer [t]]
@@ -138,7 +138,7 @@
             <rename-all   #(async/go
                              (doseq [{:keys [file target status]} rename-items]
                                (when (not= status :unreachable)
-                                 (async/<! (p->c (page-handler/rename-file! file target (constantly nil) true)))))
+                                 (async/<! (p->c (file-page-handler/rename-file! file target (constantly nil) true)))))
                              (<close-modal-on-done sync? rename-items))]
 
         (if (not-empty rename-items)
@@ -164,7 +164,7 @@
                      src-file-name  (gp-util/path->file-name path)
                      tgt-file-name  (str target "." (gp-util/path->file-ext path))
                      rm-item-fn     #(swap! *pages dissoc path)
-                     rename-fn      #(page-handler/rename-file! file target rm-item-fn)
+                     rename-fn      #(file-page-handler/rename-file! file target rm-item-fn)
                      rename-but     [:a {:on-click rename-fn
                                          :title (t :file-rn/apply-rename)}
                                      [:span (t :file-rn/rename src-file-name tgt-file-name)]]]

+ 358 - 0
src/main/frontend/handler/common/page.cljs

@@ -0,0 +1,358 @@
+(ns frontend.handler.common.page
+  "Common fns for file and db based page handlers, including create!, delete!
+  and favorite fns. This ns should be agnostic of file or db concerns but there
+  is still some file-specific tech debt to remove from create!"
+  (:require [frontend.state :as state]
+            [frontend.config :as config]
+            [frontend.db :as db]
+            [frontend.db.model :as model]
+            [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.route :as route-handler]
+            [frontend.handler.ui :as ui-handler]
+            [frontend.util.page-property :as page-property]
+            [frontend.util.fs :as fs-util]
+            [frontend.util :as util]
+            [logseq.db.schema :as db-schema]
+            [logseq.graph-parser.block :as gp-block]
+            [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.text :as text]
+            [lambdaisland.glogi :as log]
+            [medley.core :as medley]
+            [promesa.core :as p]
+            [clojure.string :as string]))
+
+;; 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?
+                       ""
+                       (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?]
+           :or   {redirect?           true
+                  create-first-block? true
+                  rename?             false
+                  format              nil
+                  properties          nil
+                  split-namespace?    true
+                  uuid                nil
+                  persist-op?         true}
+           :as options}]
+   (let [title      (-> (string/trim title)
+                        (text/page-ref-un-brackets!)
+                        ;; remove `#` from tags
+                        (string/replace #"^#+" ""))
+         title      (gp-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?
+                        (gp-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 create-first-block?
+         (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)))
+
+;; favorite fns
+;; ============
+(defn favorited?
+  [page-name]
+  (let [favorites (->> (:favorites (state/get-config))
+                       (filter string?)
+                       (map string/lower-case)
+                       (set))]
+    (contains? favorites page-name)))
+
+(defn favorite-page!
+  [page-name]
+  (when-not (string/blank? page-name)
+    (let [favorites (->
+                     (cons
+                      page-name
+                      (or (:favorites (state/get-config)) []))
+                     (distinct)
+                     (vec))]
+      (config-handler/set-config! :favorites favorites))))
+
+(defn unfavorite-page!
+  [page-name]
+  (when-not (string/blank? page-name)
+    (let [old-favorites (:favorites (state/get-config))
+          new-favorites (->> old-favorites
+                             (remove #(= (string/lower-case %) (string/lower-case page-name)))
+                             (vec))]
+      (when-not (= old-favorites new-favorites)
+        (config-handler/set-config! :favorites new-favorites)))))
+
+;; delete! and its helpers
+;; =======================
+(defn delete-file!
+  [repo page-name unlink-file?]
+  (let [file (db/get-page-file page-name)
+        file-path (:file/path file)]
+    ;; delete file
+    (when-not (string/blank? file-path)
+      (db/transact! [[:db.fn/retractEntity [:file/path file-path]]])
+      (when unlink-file?
+        (-> (fs/unlink! repo (config/get-repo-fpath repo file-path) nil)
+            (p/catch (fn [error] (js/console.error error))))))))
+
+(defn db-refs->page
+  "Replace [[page name]] with page name"
+  [repo page-entity]
+  (when (config/db-based-graph? repo)
+    (let [refs (:block/_refs page-entity)
+          id-ref->page #(db-utils/special-id-ref->page % [page-entity])]
+      (when (seq refs)
+        (let [tx-data (mapcat (fn [{:block/keys [raw-content properties] :as ref}]
+                                ;; block content or properties
+                                (let [content' (id-ref->page raw-content)
+                                      content-tx (when (not= raw-content content')
+                                                   {:db/id (:db/id ref)
+                                                    :block/content content'})
+                                      page-uuid (:block/uuid page-entity)
+                                      properties' (-> (medley/map-vals (fn [v]
+                                                                         (cond
+                                                                           (and (coll? v) (uuid? (first v)))
+                                                                           (vec (remove #{page-uuid} v))
+
+                                                                           (and (uuid? v) (= v page-uuid))
+                                                                           nil
+
+                                                                           (and (coll? v) (string? (first v)))
+                                                                           (mapv id-ref->page v)
+
+                                                                           (string? v)
+                                                                           (id-ref->page v)
+
+                                                                           :else
+                                                                           v)) properties)
+                                                      (util/remove-nils-non-nested))
+                                      tx (merge
+                                          content-tx
+                                          (when (not= (seq properties) (seq properties'))
+                                            {:db/id (:db/id ref)
+                                             :block/properties properties'}))]
+                                  (concat
+                                   [[:db/retract (:db/id ref) :block/refs (:db/id page-entity)]]
+                                   (when tx [tx])))) refs)]
+          tx-data)))))
+
+(defn- page-unable-to-delete
+  "If a page is unable to delete, returns a map with more information. Otherwise returns nil"
+  [repo page]
+  (try
+    (cond
+      (and (contains? (:block/type page) "class")
+           (seq (model/get-tag-blocks repo (:block/name page))))
+      {:msg "Unable to delete this page because blocks are tagged with this page"}
+      (contains? (:block/type page) "property")
+      (cond (seq (model/get-classes-with-property (:block/uuid page)))
+            {:msg "Unable to delete this page because classes use this property"}
+            (->> (model/get-block-property-values (:block/uuid page))
+                 (filter (fn [[_ v]] (if (seq? v) (seq v) (some? v))))
+                 seq)
+            {:msg "Unable to delete this page because blocks use this property"}))
+    (catch :default e
+      (log/error :exception e)
+      (state/pub-event! [:capture-error {:error e}])
+      {:msg (str "An unexpected failure while deleting: " e)})))
+
+(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
+                                redirect-to-home? true
+                                persist-op? true
+                                error-handler (fn [{:keys [msg]}] (log/error :msg msg))}}]
+  (when redirect-to-home? (route-handler/redirect-to-home!))
+  (when page-name
+    (when-let [repo (state/get-current-repo)]
+      (let [page-name (util/page-name-sanity-lc page-name)
+            blocks (db/get-page-blocks-no-cache page-name)
+            truncate-blocks-tx-data (mapv
+                                     (fn [block]
+                                       [:db.fn/retractEntity [:block/uuid (:block/uuid block)]])
+                                     blocks)
+            page (db/entity [:block/name page-name])]
+        (if-let [msg (and (config/db-based-graph? repo)
+                          (page-unable-to-delete repo page))]
+          (error-handler msg)
+          (let [_ (delete-file! repo page-name delete-file?)
+                ;; if other page alias this pagename,
+                ;; then just remove some attrs of this entity instead of retractEntity
+                delete-page-tx (cond
+                                 (not (:block/_namespace page))
+                                 (if (model/get-alias-source-page (state/get-current-repo) page-name)
+                                   (when-let [id (:db/id (db/entity [:block/name page-name]))]
+                                     (mapv (fn [attribute]
+                                             [:db/retract id attribute])
+                                           db-schema/retract-page-attributes))
+                                   (concat (db-refs->page repo page)
+                                           [[:db.fn/retractEntity [:block/name page-name]]]))
+
+                                 :else
+                                 nil)
+                tx-data (concat truncate-blocks-tx-data delete-page-tx)]
+            (db/transact! repo tx-data {:outliner-op :delete-page :persist-op? persist-op?})
+
+            (unfavorite-page! page-name)
+
+            (when (fn? ok-handler) (ok-handler))
+            (ui-handler/re-render-root!)))))))
+
+
+;; other fns
+;; =========
+(defn rename-update-namespace!
+  "update :block/namespace of the renamed block"
+  [page old-original-name new-name]
+  (let [old-namespace? (text/namespace-page? old-original-name)
+        new-namespace? (text/namespace-page? new-name)
+        repo           (state/get-current-repo)]
+    (cond
+      new-namespace?
+      ;; update namespace
+      (let [namespace (first (gp-util/split-last "/" new-name))]
+        (when namespace
+          (create! namespace {:redirect? false}) ;; create parent page if not exist, creation of namespace ref is handled in `create!`
+          (let [namespace-block (db/pull [:block/name (gp-util/page-name-sanity-lc namespace)])
+                page-txs [{:db/id (:db/id page)
+                           :block/namespace (:db/id namespace-block)}]]
+            (db/transact! repo page-txs))))
+
+      old-namespace?
+      ;; retract namespace
+      (db/transact! [[:db/retract (:db/id page) :block/namespace]])
+
+      :else
+      nil)))

+ 143 - 0
src/main/frontend/handler/db_based/page.cljs

@@ -0,0 +1,143 @@
+(ns frontend.handler.db-based.page
+  "Page handlers for DB graphs"
+  (:require [frontend.state :as state]
+            [frontend.db :as db]
+            [frontend.db.model :as model]
+            [frontend.db.conn :as conn]
+            [frontend.db.utils :as db-utils]
+            [frontend.util :as util]
+            [frontend.handler.ui :as ui-handler]
+            [frontend.handler.notification :as notification]
+            [frontend.handler.route :as route-handler]
+            [frontend.modules.outliner.core :as outliner-core]
+            [frontend.modules.outliner.tree :as outliner-tree]
+            [frontend.handler.common.page :as page-common-handler]
+            [datascript.core :as d]
+            [medley.core :as medley]
+            [clojure.string :as string]))
+
+(defn- replace-ref
+  "Replace from-page refs with to-page"
+  [from-page to-page]
+  (let [refs (:block/_refs from-page)
+        from-uuid (:block/uuid from-page)
+        to-uuid (:block/uuid to-page)
+        replace-ref (fn [content] (string/replace content (str from-uuid) (str to-uuid)))]
+    (when (seq refs)
+      (let [tx-data (mapcat
+                     (fn [{:block/keys [raw-content properties] :as ref}]
+                         ;; block content or properties
+                       (let [content' (replace-ref raw-content)
+                             content-tx (when (not= raw-content content')
+                                          {:db/id (:db/id ref)
+                                           :block/content content'})
+                             properties' (-> (medley/map-vals (fn [v]
+                                                                (cond
+                                                                  (and (coll? v) (uuid? (first v)))
+                                                                  (mapv (fn [id] (if (= id from-uuid) to-uuid id)) v)
+
+                                                                  (and (uuid? v) (= v from-uuid))
+                                                                  to-uuid
+
+                                                                  (and (coll? v) (string? (first v)))
+                                                                  (mapv replace-ref v)
+
+                                                                  (string? v)
+                                                                  (replace-ref v)
+
+                                                                  :else
+                                                                  v)) properties)
+                                             (util/remove-nils-non-nested))
+                             tx (merge
+                                 content-tx
+                                 (when (not= (seq properties) (seq properties'))
+                                   {:db/id (:db/id ref)
+                                    :block/properties properties'}))]
+                         (concat
+                          [[:db/add (:db/id ref) :block/refs (:db/id to-page)]
+                           [:db/retract (:db/id ref) :block/refs (:db/id from-page)]]
+                          (when tx [tx]))))
+                     refs)]
+        tx-data))))
+
+(defn based-merge-pages!
+  [from-page-name to-page-name persist-op?]
+  (when (and (db/page-exists? from-page-name)
+             (db/page-exists? to-page-name)
+             (not= from-page-name to-page-name))
+    (let [to-page (db/entity [:block/name to-page-name])
+          to-id (:db/id to-page)
+          from-page (db/entity [:block/name from-page-name])
+          from-id (:db/id from-page)
+          from-first-child (some->> (db/pull from-id)
+                                    (outliner-core/block)
+                                    (outliner-tree/-get-down)
+                                    (outliner-core/get-data))
+          to-last-direct-child-id (model/get-block-last-direct-child (db/get-db) to-id false)
+          repo (state/get-current-repo)
+          conn (conn/get-db repo false)
+          datoms (d/datoms @conn :avet :block/page from-id)
+          block-eids (mapv :e datoms)
+          blocks (db-utils/pull-many repo '[:db/id :block/page :block/refs :block/path-refs :block/left :block/parent] block-eids)
+          blocks-tx-data (map (fn [block]
+                                (let [id (:db/id block)]
+                                  (cond->
+                                   {:db/id id
+                                    :block/page {:db/id to-id}}
+
+                                    (and from-first-child (= id (:db/id from-first-child)))
+                                    (assoc :block/left {:db/id (or to-last-direct-child-id to-id)})
+
+                                    (= (:block/parent block) {:db/id from-id})
+                                    (assoc :block/parent {:db/id to-id})))) blocks)
+          replace-ref-tx-data (replace-ref from-page to-page)
+          tx-data (concat blocks-tx-data replace-ref-tx-data)]
+      (db/transact! repo tx-data {:persist-op? persist-op?})
+      (page-common-handler/rename-update-namespace! from-page
+                                                    (util/get-page-original-name from-page)
+                                                    (util/get-page-original-name to-page)))
+
+
+    (page-common-handler/delete! from-page-name nil :redirect-to-home? false :persist-op? persist-op?)
+
+    (route-handler/redirect! {:to          :page
+                              :push        false
+                              :path-params {:name to-page-name}})))
+
+(defn rename!
+  ([old-name new-name]
+   (rename! old-name new-name true true))
+  ([old-name new-name redirect? persist-op?]
+   (let [repo (state/get-current-repo)
+         old-name      (string/trim old-name)
+         new-name      (string/trim new-name)
+         old-page-name (util/page-name-sanity-lc old-name)
+         page-e (db/entity [:block/name old-page-name])
+         new-page-name (util/page-name-sanity-lc new-name)
+         name-changed? (not= old-name new-name)]
+     (if (and old-name
+              new-name
+              (not (string/blank? new-name))
+              name-changed?)
+       (cond
+         (= old-page-name new-page-name) ; case changed
+         (db/transact! repo
+                       [{:db/id (:db/id page-e)
+                         :block/original-name new-name}]
+                       {:persist-op? persist-op?})
+
+         (and (not= old-page-name new-page-name)
+              (db/entity [:block/name new-page-name])) ; merge page
+         (based-merge-pages! old-page-name new-page-name persist-op?)
+
+         :else                          ; rename
+         (page-common-handler/create! new-name
+                                      {:rename? true
+                                       :uuid (:block/uuid page-e)
+                                       :redirect? redirect?
+                                       :create-first-block? false
+                                       :persist-op? persist-op?}))
+
+       (when (string/blank? new-name)
+         (notification/show! "Please use a valid name, empty name is not allowed!" :error)))
+     (ui-handler/re-render-root!))))

+ 389 - 0
src/main/frontend/handler/file_based/page.cljs

@@ -0,0 +1,389 @@
+(ns frontend.handler.file-based.page
+  "Page handlers for file based graphs"
+  (:require [frontend.config :as config]
+            [frontend.db :as db]
+            [frontend.db.conn :as conn]
+            [frontend.db.utils :as db-utils]
+            [frontend.db.model :as model]
+            [frontend.handler.file-based.property :as file-property]
+            [frontend.handler.file-based.recent :as file-recent-handler]
+            [frontend.handler.config :as config-handler]
+            [frontend.handler.common.page :as page-common-handler]
+            [frontend.handler.notification :as notification]
+            [frontend.handler.route :as route-handler]
+            [frontend.handler.ui :as ui-handler]
+            [frontend.state :as state]
+            [frontend.util :as util]
+            [frontend.util.fs :as fs-util]
+            [frontend.util.page-property :as page-property]
+            [frontend.modules.outliner.core :as outliner-core]
+            [frontend.modules.outliner.file :as outliner-file]
+            [frontend.modules.outliner.tree :as outliner-tree]
+            [frontend.fs :as fs]
+            [logseq.graph-parser.property :as gp-property]
+            [logseq.graph-parser.util.page-ref :as page-ref]
+            [lambdaisland.glogi :as log]
+            [promesa.core :as p]
+            [datascript.core :as d]
+            [clojure.walk :as walk]
+            [clojure.string :as string]))
+
+(defn- replace-page-ref!
+  "Unsanitized names"
+  [content old-name new-name]
+  (let [[original-old-name original-new-name] (map string/trim [old-name new-name])
+        [old-ref new-ref] (map page-ref/->page-ref [old-name new-name])
+        [old-name new-name] (map #(if (string/includes? % "/")
+                                    (string/replace % "/" ".")
+                                    %)
+                                 [original-old-name original-new-name])
+        old-org-ref (and (= :org (state/get-preferred-format))
+                         (:org-mode/insert-file-link? (state/get-config))
+                         (re-find
+                          (re-pattern
+                           (util/format
+                            "\\[\\[file:\\.*/.*%s\\.org\\]\\[(.*?)\\]\\]" old-name))
+                          content))]
+    (-> (if old-org-ref
+          (let [[old-full-ref old-label] old-org-ref
+                new-label (if (= old-label original-old-name)
+                            original-new-name
+                            old-label)
+                new-full-ref (-> (string/replace old-full-ref old-name new-name)
+                                 (string/replace (str "[" old-label "]")
+                                                 (str "[" new-label "]")))]
+            (string/replace content old-full-ref new-full-ref))
+          content)
+        (string/replace old-ref new-ref))))
+
+(defn- replace-tag-ref!
+  [content old-name new-name]
+  (let [old-tag (util/format "#%s" old-name)
+        new-tag (if (re-find #"[\s\t]+" new-name)
+                  (util/format "#[[%s]]" new-name)
+                  (str "#" new-name))]
+    ;; hash tag parsing rules https://github.com/logseq/mldoc/blob/701243eaf9b4157348f235670718f6ad19ebe7f8/test/test_markdown.ml#L631
+    ;; Safari doesn't support look behind, don't use
+    ;; TODO: parse via mldoc
+    (string/replace content
+                    (re-pattern (str "(?i)(^|\\s)(" (util/escape-regex-chars old-tag) ")(?=[,\\.]*($|\\s))"))
+                    ;;    case_insense^    ^lhs   ^_grp2                       look_ahead^         ^_grp3
+                    (fn [[_match lhs _grp2 _grp3]]
+                      (str lhs new-tag)))))
+
+(defn- replace-property-ref!
+  [content old-name new-name format]
+  (let [new-name (keyword (string/replace (string/lower-case new-name) #"\s+" "-"))
+        org-format? (= :org format)
+        old-property (if org-format? (gp-property/colons-org old-name) (str old-name gp-property/colons))
+        new-property (if org-format? (gp-property/colons-org (name new-name)) (str (name new-name) gp-property/colons))]
+    (util/replace-ignore-case content old-property new-property)))
+
+(defn- replace-old-page!
+  "Unsanitized names"
+  [content old-name new-name format]
+  (when (and (string? content) (string? old-name) (string? new-name))
+    (-> content
+        (replace-page-ref! old-name new-name)
+        (replace-tag-ref! old-name new-name)
+        (replace-property-ref! old-name new-name format))))
+
+(defn- walk-replace-old-page!
+  "Unsanitized names"
+  [form old-name new-name format]
+  (walk/postwalk (fn [f]
+                   (cond
+                     (and (vector? f)
+                          (contains? #{"Search" "Label"} (first f))
+                          (string/starts-with? (second f) (str old-name "/")))
+                     [(first f) (string/replace-first (second f)
+                                                      (str old-name "/")
+                                                      (str new-name "/"))]
+
+                     (string? f)
+                     (if (= f old-name)
+                       new-name
+                       (replace-old-page! f old-name new-name format))
+
+                     (and (keyword f) (= (name f) old-name))
+                     (keyword (string/replace (string/lower-case new-name) #"\s+" "-"))
+
+                     :else
+                     f))
+                 form))
+
+(defn- rename-update-block-refs!
+  [refs from-id to-id]
+  (->> refs
+       (remove #{{:db/id from-id}})
+       (cons {:db/id to-id})
+       (distinct)
+       (vec)))
+
+(defn- rename-update-refs!
+  "Unsanitized only"
+  [page old-original-name new-name]
+  ;; update all pages which have references to this page
+  (let [repo (state/get-current-repo)
+        to-page (db/entity [:block/name (util/page-name-sanity-lc new-name)])
+        blocks (:block/_refs (db/entity (:db/id page)))
+        page-ids (->> (map (fn [b]
+                             {:db/id (:db/id (:block/page b))}) blocks)
+                      (set))
+        tx       (->> (map (fn [{:block/keys [uuid content properties format] :as block}]
+                             (let [content    (let [content' (replace-old-page! content old-original-name new-name format)]
+                                                (when-not (= content' content)
+                                                  content'))
+                                   properties (let [properties' (walk-replace-old-page! properties old-original-name new-name format)]
+                                                (when-not (= properties' properties)
+                                                  properties'))]
+                               (when (or content properties)
+                                 (util/remove-nils-non-nested
+                                  {:block/uuid       uuid
+                                   :block/content    content
+                                   :block/properties properties
+                                   :block/properties-order (when (seq properties)
+                                                             (map first properties))
+                                   :block/refs (->> (rename-update-block-refs! (:block/refs block) (:db/id page) (:db/id to-page))
+                                                    (map :db/id)
+                                                    (set))})))) blocks)
+                      (remove nil?))]
+    (db/transact! repo tx)
+    (doseq [page-id page-ids]
+      (outliner-file/sync-to-file page-id))))
+
+(defn- compute-new-file-path
+  "Construct the full path given old full path and the file sanitized body.
+   Ext. included in the `old-path`."
+  [old-path new-file-name-body]
+  (let [result (string/split old-path "/")
+        ext (last (string/split (last result) "."))
+        new-file (str new-file-name-body "." ext)
+        parts (concat (butlast result) [new-file])]
+    (util/string-join-path parts)))
+
+(defn rename-file!
+  "emit file-rename events to :file/rename-event-chan
+   force-fs? - when true, rename file event the db transact is failed."
+  ([file new-file-name-body ok-handler]
+   (rename-file! file new-file-name-body ok-handler false))
+  ([file new-file-name-body ok-handler force-fs?]
+   (let [repo (state/get-current-repo)
+         file (db/pull (:db/id file))
+         old-path (:file/path file)
+         new-path (compute-new-file-path old-path new-file-name-body)
+         transact #(db/transact! repo [{:db/id (:db/id file)
+                                        :file/path new-path}])]
+    ;; update db
+     (if force-fs?
+       (try (transact) ;; capture error and continue FS rename if failed
+            (catch :default e
+              (log/error :rename-file e)))
+       (transact)) ;; interrupted if failed
+
+     (->
+      (p/let [_ (state/offer-file-rename-event-chan! {:repo repo
+                                                      :old-path old-path
+                                                      :new-path new-path})
+              _ (fs/rename! repo old-path new-path)]
+        (ok-handler))
+      (p/catch (fn [error]
+                 (println "file rename failed: " error)))))))
+
+(defn- rename-page-aux
+  "Only accepts unsanitized page names"
+  [old-name new-name redirect?]
+  (let [old-page-name       (util/page-name-sanity-lc old-name)
+        new-file-name-body  (fs-util/file-name-sanity new-name) ;; w/o file extension
+        new-page-name       (util/page-name-sanity-lc new-name)
+        repo                (state/get-current-repo)
+        page                (db/pull [:block/name old-page-name])]
+    (when (and repo page)
+      (let [old-original-name   (:block/original-name page)
+            file                (:block/file page)
+            journal?            (:block/journal? page)
+            properties-block    (:data (outliner-tree/-get-down (outliner-core/block page)))
+            properties-content  (:block/content properties-block)
+            properties-block-tx (when (and properties-block
+                                           properties-content
+                                           (string/includes? (util/page-name-sanity-lc properties-content)
+                                                             old-page-name))
+                                  (let [front-matter? (and (file-property/front-matter?-when-file-based properties-content)
+                                                           (= :markdown (:block/format properties-block)))]
+                                    {:db/id         (:db/id properties-block)
+                                     :block/content (file-property/insert-property
+                                                     (:block/format properties-block)
+                                                     properties-content
+                                                     :title
+                                                     new-name
+                                                     front-matter?)}))
+            page-txs            [{:db/id               (:db/id page)
+                                  :block/uuid          (:block/uuid page)
+                                  :block/name          new-page-name
+                                  :block/original-name new-name}]
+            page-txs            (if properties-block-tx (conj page-txs properties-block-tx) page-txs)]
+
+        (db/transact! repo page-txs)
+
+        (when (and (not (config/db-based-graph? repo))
+                   (fs-util/create-title-property? new-page-name))
+          (page-property/add-property! new-page-name :title new-name))
+
+        (when (and file (not journal?))
+          (rename-file! file new-file-name-body (fn [] nil)))
+
+        (let [home (get (state/get-config) :default-home {})]
+          (when (= old-page-name (util/page-name-sanity-lc (get home :page "")))
+            (config-handler/set-config! :default-home (assoc home :page new-name))))
+
+        (rename-update-refs! page old-original-name new-name)
+
+        (page-common-handler/rename-update-namespace! page old-original-name new-name)
+
+        (outliner-file/sync-to-file page))
+
+      ;; Redirect to the newly renamed page
+      (when redirect?
+        (route-handler/redirect! {:to          (if (model/whiteboard-page? page) :whiteboard :page)
+                                  :push        false
+                                  :path-params {:name new-page-name}}))
+
+      (when (page-common-handler/favorited? old-page-name)
+        (p/do!
+         (page-common-handler/unfavorite-page! old-page-name)
+         (page-common-handler/favorite-page! new-page-name)))
+
+      (file-recent-handler/update-or-add-renamed-page repo old-page-name new-page-name)
+
+      (ui-handler/re-render-root!))))
+
+(defn- rename-nested-pages
+  "Unsanitized names only"
+  [old-ns-name new-ns-name]
+  (let [repo            (state/get-current-repo)
+        nested-page-str (page-ref/->page-ref (util/page-name-sanity-lc old-ns-name))
+        ns-prefix-format-str (str page-ref/left-brackets "%s/")
+        ns-prefix       (util/format ns-prefix-format-str (util/page-name-sanity-lc old-ns-name))
+        nested-pages    (db/get-pages-by-name-partition repo nested-page-str)
+        nested-pages-ns (db/get-pages-by-name-partition repo ns-prefix)]
+    (when nested-pages
+      ;; rename page "[[obsidian]] is a tool" to "[[logseq]] is a tool"
+      (doseq [{:block/keys [name original-name]} nested-pages]
+        (let [old-page-title (or original-name name)
+              new-page-title (string/replace
+                              old-page-title
+                              (page-ref/->page-ref old-ns-name)
+                              (page-ref/->page-ref new-ns-name))]
+          (when (and old-page-title new-page-title)
+            (p/do!
+             (rename-page-aux old-page-title new-page-title false)
+             (println "Renamed " old-page-title " to " new-page-title))))))
+    (when nested-pages-ns
+      ;; rename page "[[obsidian/page1]] is a tool" to "[[logseq/page1]] is a tool"
+      (doseq [{:block/keys [name original-name]} nested-pages-ns]
+        (let [old-page-title (or original-name name)
+              new-page-title (string/replace
+                              old-page-title
+                              (util/format ns-prefix-format-str old-ns-name)
+                              (util/format ns-prefix-format-str new-ns-name))]
+          (when (and old-page-title new-page-title)
+            (p/do!
+             (rename-page-aux old-page-title new-page-title false)
+             (println "Renamed " old-page-title " to " new-page-title))))))))
+
+(defn- rename-namespace-pages!
+  "Original names (unsanitized only)"
+  [repo old-name new-name]
+  (let [pages (db/get-namespace-pages repo old-name)
+        page (db/pull [:block/name (util/page-name-sanity-lc old-name)])
+        pages (cons page pages)]
+    (doseq [{:block/keys [name original-name]} pages]
+      (let [old-page-title (or original-name name)
+            ;; only replace one time, for the case that the namespace is a sub-string of the sub-namespace page name
+            ;; Example: has pages [[work]] [[work/worklog]],
+            ;; we want to rename [[work/worklog]] to [[work1/worklog]] when rename [[work]] to [[work1]],
+            ;; but don't rename [[work/worklog]] to [[work1/work1log]]
+            new-page-title (string/replace-first old-page-title old-name new-name)
+            redirect? (= name (:block/name page))]
+        (when (and old-page-title new-page-title)
+          (p/let [_ (rename-page-aux old-page-title new-page-title redirect?)]
+            (println "Renamed " old-page-title " to " new-page-title)))))))
+
+(defn merge-pages!
+  "Only accepts sanitized page names"
+  [from-page-name to-page-name]
+  (when (and (db/page-exists? from-page-name)
+             (db/page-exists? to-page-name)
+             (not= from-page-name to-page-name))
+    (let [to-page (db/entity [:block/name to-page-name])
+          to-id (:db/id to-page)
+          from-page (db/entity [:block/name from-page-name])
+          from-id (:db/id from-page)
+          from-first-child (some->> (db/pull from-id)
+                                    (outliner-core/block)
+                                    (outliner-tree/-get-down)
+                                    (outliner-core/get-data))
+          to-last-direct-child-id (model/get-block-last-direct-child (db/get-db) to-id false)
+          repo (state/get-current-repo)
+          conn (conn/get-db repo false)
+          datoms (d/datoms @conn :avet :block/page from-id)
+          block-eids (mapv :e datoms)
+          blocks (db-utils/pull-many repo '[:db/id :block/page :block/refs :block/path-refs :block/left :block/parent] block-eids)
+          tx-data (map (fn [block]
+                         (let [id (:db/id block)]
+                           (cond->
+                            {:db/id id
+                             :block/page {:db/id to-id}
+                             :block/refs (rename-update-block-refs! (:block/refs block) from-id to-id)}
+
+                             (and from-first-child (= id (:db/id from-first-child)))
+                             (assoc :block/left {:db/id (or to-last-direct-child-id to-id)})
+
+                             (= (:block/parent block) {:db/id from-id})
+                             (assoc :block/parent {:db/id to-id})))) blocks)]
+      (db/transact! repo tx-data)
+      (outliner-file/sync-to-file {:db/id to-id})
+
+      (rename-update-refs! from-page
+                           (util/get-page-original-name from-page)
+                           (util/get-page-original-name to-page))
+
+      (page-common-handler/rename-update-namespace! from-page
+                                                    (util/get-page-original-name from-page)
+                                                    (util/get-page-original-name to-page)))
+
+
+    (page-common-handler/delete! from-page-name nil)
+
+    (route-handler/redirect! {:to          :page
+                              :push        false
+                              :path-params {:name to-page-name}})))
+
+(defn rename!
+  "Accepts unsanitized page names"
+  ([old-name new-name] (rename! old-name new-name true))
+  ([old-name new-name redirect?]
+   (let [repo          (state/get-current-repo)
+         old-name      (string/trim old-name)
+         new-name      (string/trim new-name)
+         old-page-name (util/page-name-sanity-lc old-name)
+         new-page-name (util/page-name-sanity-lc new-name)
+         name-changed? (not= old-name new-name)]
+     (if (and old-name
+              new-name
+              (not (string/blank? new-name))
+              name-changed?)
+       (do
+         (cond
+           (= old-page-name new-page-name)
+           (rename-page-aux old-name new-name redirect?)
+
+           (db/pull [:block/name new-page-name])
+           (merge-pages! old-page-name new-page-name)
+
+           :else
+           (rename-namespace-pages! repo old-name new-name))
+         (rename-nested-pages old-name new-name))
+       (when (string/blank? new-name)
+         (notification/show! "Please use a valid name, empty name is not allowed!" :error)))
+     (ui-handler/re-render-root!))))

+ 10 - 827
src/main/frontend/handler/page.cljs

@@ -2,56 +2,46 @@
   "Provides util handler fns for pages"
   (:require [cljs.reader :as reader]
             [clojure.string :as string]
-            [clojure.walk :as walk]
-            [datascript.core :as d]
             [frontend.commands :as commands]
             [frontend.config :as config]
             [frontend.date :as date]
             [frontend.db :as db]
-            [frontend.db.conn :as conn]
             [frontend.db.model :as model]
-            [frontend.db.utils :as db-utils]
-            [frontend.format.block :as block]
             [frontend.fs :as fs]
             [frontend.handler.common :as common-handler]
+            [frontend.handler.common.page :as page-common-handler]
             [frontend.handler.reorder :as reorder-handler]
             [frontend.handler.config :as config-handler]
             [frontend.handler.editor :as editor-handler]
-            [frontend.handler.file-based.editor :as file-editor-handler]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.notification :as notification]
-            [frontend.handler.file-based.recent :as recent-handler]
-            [frontend.handler.route :as route-handler]
+            [frontend.handler.db-based.page :as db-page-handler]
+            [frontend.handler.file-based.page :as file-page-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.web.nfs :as web-nfs]
             [frontend.mobile.util :as mobile-util]
-            [frontend.modules.outliner.core :as outliner-core]
-            [frontend.modules.outliner.file :as outliner-file]
-            [frontend.modules.outliner.tree :as outliner-tree]
             [frontend.state :as state]
             [frontend.util :as util]
             [frontend.util.cursor :as cursor]
-            [frontend.util.fs :as fs-util]
             [frontend.util.page-property :as page-property]
             [frontend.util.page :as page-util]
-            [frontend.handler.file-based.property :as file-property]
             [frontend.util.url :as url-util]
             [goog.functions :refer [debounce]]
             [goog.object :as gobj]
             [lambdaisland.glogi :as log]
-            [logseq.db.schema :as db-schema]
             [logseq.db.property :as db-property]
-            [logseq.graph-parser.block :as gp-block]
             [logseq.graph-parser.config :as gp-config]
-            [logseq.graph-parser.property :as gp-property]
-            [logseq.graph-parser.text :as text]
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.util.page-ref :as page-ref]
             [promesa.core :as p]
             [logseq.common.path :as path]
-            [medley.core :as medley]
             [frontend.handler.property.util :as pu]))
 
+(def create! page-common-handler/create!)
+(def delete! page-common-handler/delete!)
+(def unfavorite-page! page-common-handler/unfavorite-page!)
+(def favorite-page! page-common-handler/favorite-page!)
+
 ;; FIXME: add whiteboard
 (defn- get-directory
   [journal?]
@@ -68,320 +58,6 @@
     ;; Win10 file path has a length limit of 260 chars
     (gp-util/safe-subs s 0 200)))
 
-(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?
-                       ""
-                       (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]))))
-
-(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?]
-           :or   {redirect?           true
-                  create-first-block? true
-                  rename?             false
-                  format              nil
-                  properties          nil
-                  split-namespace?    true
-                  uuid                nil
-                  persist-op?         true}
-           :as options}]
-   (let [title      (-> (string/trim title)
-                        (text/page-ref-un-brackets!)
-                        ;; remove `#` from tags
-                        (string/replace #"^#+" ""))
-         title      (gp-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?
-                        (gp-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 create-first-block?
-         (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)))
-
-(defn delete-file!
-  [repo page-name unlink-file?]
-  (let [file (db/get-page-file page-name)
-        file-path (:file/path file)]
-    ;; delete file
-    (when-not (string/blank? file-path)
-      (db/transact! [[:db.fn/retractEntity [:file/path file-path]]])
-      (when unlink-file?
-        (-> (fs/unlink! repo (config/get-repo-fpath repo file-path) nil)
-            (p/catch (fn [error] (js/console.error error))))))))
-
-(defn- compute-new-file-path
-  "Construct the full path given old full path and the file sanitized body.
-   Ext. included in the `old-path`."
-  [old-path new-file-name-body]
-  (let [result (string/split old-path "/")
-        ext (last (string/split (last result) "."))
-        new-file (str new-file-name-body "." ext)
-        parts (concat (butlast result) [new-file])]
-    (util/string-join-path parts)))
-
-(defn rename-file!
-  "emit file-rename events to :file/rename-event-chan
-   force-fs? - when true, rename file event the db transact is failed."
-  ([file new-file-name-body ok-handler]
-   (rename-file! file new-file-name-body ok-handler false))
-  ([file new-file-name-body ok-handler force-fs?]
-   (let [repo (state/get-current-repo)
-         file (db/pull (:db/id file))
-         old-path (:file/path file)
-         new-path (compute-new-file-path old-path new-file-name-body)
-         transact #(db/transact! repo [{:db/id (:db/id file)
-                                        :file/path new-path}])]
-    ;; update db
-     (if force-fs?
-       (try (transact) ;; capture error and continue FS rename if failed
-            (catch :default e
-              (log/error :rename-file e)))
-       (transact)) ;; interrupted if failed
-
-     (->
-      (p/let [_ (state/offer-file-rename-event-chan! {:repo repo
-                                                      :old-path old-path
-                                                      :new-path new-path})
-              _ (fs/rename! repo old-path new-path)]
-        (ok-handler))
-      (p/catch (fn [error]
-                 (println "file rename failed: " error)))))))
-
-(defn- replace-page-ref!
-  "Unsanitized names"
-  [content old-name new-name]
-  (let [[original-old-name original-new-name] (map string/trim [old-name new-name])
-        [old-ref new-ref] (map page-ref/->page-ref [old-name new-name])
-        [old-name new-name] (map #(if (string/includes? % "/")
-                                    (string/replace % "/" ".")
-                                    %)
-                                 [original-old-name original-new-name])
-        old-org-ref (and (= :org (state/get-preferred-format))
-                         (:org-mode/insert-file-link? (state/get-config))
-                         (re-find
-                          (re-pattern
-                           (util/format
-                            "\\[\\[file:\\.*/.*%s\\.org\\]\\[(.*?)\\]\\]" old-name))
-                          content))]
-    (-> (if old-org-ref
-          (let [[old-full-ref old-label] old-org-ref
-                new-label (if (= old-label original-old-name)
-                            original-new-name
-                            old-label)
-                new-full-ref (-> (string/replace old-full-ref old-name new-name)
-                                 (string/replace (str "[" old-label "]")
-                                                 (str "[" new-label "]")))]
-            (string/replace content old-full-ref new-full-ref))
-          content)
-        (string/replace old-ref new-ref))))
-
-(defn- replace-tag-ref!
-  [content old-name new-name]
-  (let [old-tag (util/format "#%s" old-name)
-        new-tag (if (re-find #"[\s\t]+" new-name)
-                  (util/format "#[[%s]]" new-name)
-                  (str "#" new-name))]
-    ;; hash tag parsing rules https://github.com/logseq/mldoc/blob/701243eaf9b4157348f235670718f6ad19ebe7f8/test/test_markdown.ml#L631
-    ;; Safari doesn't support look behind, don't use
-    ;; TODO: parse via mldoc
-    (string/replace content
-                    (re-pattern (str "(?i)(^|\\s)(" (util/escape-regex-chars old-tag) ")(?=[,\\.]*($|\\s))"))
-                    ;;    case_insense^    ^lhs   ^_grp2                       look_ahead^         ^_grp3
-                    (fn [[_match lhs _grp2 _grp3]]
-                      (str lhs new-tag)))))
-
-(defn- replace-property-ref!
-  [content old-name new-name format]
-  (let [new-name (keyword (string/replace (string/lower-case new-name) #"\s+" "-"))
-        org-format? (= :org format)
-        old-property (if org-format? (gp-property/colons-org old-name) (str old-name gp-property/colons))
-        new-property (if org-format? (gp-property/colons-org (name new-name)) (str (name new-name) gp-property/colons))]
-    (util/replace-ignore-case content old-property new-property)))
-
-(defn- replace-old-page!
-  "Unsanitized names"
-  [content old-name new-name format]
-  (when (and (string? content) (string? old-name) (string? new-name))
-    (-> content
-        (replace-page-ref! old-name new-name)
-        (replace-tag-ref! old-name new-name)
-        (replace-property-ref! old-name new-name format))))
-
-(defn- walk-replace-old-page!
-  "Unsanitized names"
-  [form old-name new-name format]
-  (walk/postwalk (fn [f]
-                   (cond
-                     (and (vector? f)
-                          (contains? #{"Search" "Label"} (first f))
-                          (string/starts-with? (second f) (str old-name "/")))
-                     [(first f) (string/replace-first (second f)
-                                                      (str old-name "/")
-                                                      (str new-name "/"))]
-
-                     (string? f)
-                     (if (= f old-name)
-                       new-name
-                       (replace-old-page! f old-name new-name format))
-
-                     (and (keyword f) (= (name f) old-name))
-                     (keyword (string/replace (string/lower-case new-name) #"\s+" "-"))
-
-                     :else
-                     f))
-                 form))
-
-(defn favorited?
-  [page-name]
-  (let [favorites (->> (:favorites (state/get-config))
-                       (filter string?)
-                       (map string/lower-case)
-                       (set))]
-    (contains? favorites page-name)))
-
-(defn favorite-page!
-  [page-name]
-  (when-not (string/blank? page-name)
-    (let [favorites (->
-                     (cons
-                      page-name
-                      (or (:favorites (state/get-config)) []))
-                     (distinct)
-                     (vec))]
-      (config-handler/set-config! :favorites favorites))))
-
-(defn unfavorite-page!
-  [page-name]
-  (when-not (string/blank? page-name)
-    (let [old-favorites (:favorites (state/get-config))
-          new-favorites (->> old-favorites
-                             (remove #(= (string/lower-case %) (string/lower-case page-name)))
-                             (vec))]
-      (when-not (= old-favorites new-favorites)
-        (config-handler/set-config! :favorites new-favorites)))))
-
 (defn toggle-favorite! []
   ;; NOTE: in journals or settings, current-page is nil
   (when-let [page-name (state/get-current-page)]
@@ -392,506 +68,13 @@
         (unfavorite-page! page-name)
         (favorite-page! page-name)))))
 
-(defn db-refs->page
-  "Replace [[page name]] with page name"
-  [repo page-entity]
-  (when (config/db-based-graph? repo)
-    (let [refs (:block/_refs page-entity)
-          id-ref->page #(db-utils/special-id-ref->page % [page-entity])]
-      (when (seq refs)
-        (let [tx-data (mapcat (fn [{:block/keys [raw-content properties] :as ref}]
-                                ;; block content or properties
-                                (let [content' (id-ref->page raw-content)
-                                      content-tx (when (not= raw-content content')
-                                                   {:db/id (:db/id ref)
-                                                    :block/content content'})
-                                      page-uuid (:block/uuid page-entity)
-                                      properties' (-> (medley/map-vals (fn [v]
-                                                                         (cond
-                                                                           (and (coll? v) (uuid? (first v)))
-                                                                           (vec (remove #{page-uuid} v))
-
-                                                                           (and (uuid? v) (= v page-uuid))
-                                                                           nil
-
-                                                                           (and (coll? v) (string? (first v)))
-                                                                           (mapv id-ref->page v)
-
-                                                                           (string? v)
-                                                                           (id-ref->page v)
-
-                                                                           :else
-                                                                           v)) properties)
-                                                      (util/remove-nils-non-nested))
-                                      tx (merge
-                                          content-tx
-                                          (when (not= (seq properties) (seq properties'))
-                                            {:db/id (:db/id ref)
-                                             :block/properties properties'}))]
-                                  (concat
-                                   [[:db/retract (:db/id ref) :block/refs (:db/id page-entity)]]
-                                   (when tx [tx])))) refs)]
-          tx-data)))))
-
-(defn db-replace-ref
-  "Replace from-page refs with to-page"
-  [repo from-page to-page]
-  (when (config/db-based-graph? repo)
-    (let [refs (:block/_refs from-page)
-          from-uuid (:block/uuid from-page)
-          to-uuid (:block/uuid to-page)
-          replace-ref (fn [content] (string/replace content (str from-uuid) (str to-uuid)))]
-      (when (seq refs)
-        (let [tx-data (mapcat
-                       (fn [{:block/keys [raw-content properties] :as ref}]
-                         ;; block content or properties
-                         (let [content' (replace-ref raw-content)
-                               content-tx (when (not= raw-content content')
-                                            {:db/id (:db/id ref)
-                                             :block/content content'})
-                               properties' (-> (medley/map-vals (fn [v]
-                                                                  (cond
-                                                                    (and (coll? v) (uuid? (first v)))
-                                                                    (mapv (fn [id] (if (= id from-uuid) to-uuid id)) v)
-
-                                                                    (and (uuid? v) (= v from-uuid))
-                                                                    to-uuid
-
-                                                                    (and (coll? v) (string? (first v)))
-                                                                    (mapv replace-ref v)
-
-                                                                    (string? v)
-                                                                    (replace-ref v)
-
-                                                                    :else
-                                                                    v)) properties)
-                                               (util/remove-nils-non-nested))
-                               tx (merge
-                                   content-tx
-                                   (when (not= (seq properties) (seq properties'))
-                                     {:db/id (:db/id ref)
-                                      :block/properties properties'}))]
-                           (concat
-                            [[:db/add (:db/id ref) :block/refs (:db/id to-page)]
-                             [:db/retract (:db/id ref) :block/refs (:db/id from-page)]]
-                            (when tx [tx]))))
-                       refs)]
-          tx-data)))))
-
-(defn- page-unable-to-delete
-  "If a page is unable to delete, returns a map with more information. Otherwise returns nil"
-  [repo page]
-  (try
-    (cond
-      (and (contains? (:block/type page) "class")
-           (seq (model/get-tag-blocks repo (:block/name page))))
-      {:msg "Unable to delete this page because blocks are tagged with this page"}
-      (contains? (:block/type page) "property")
-      (cond (seq (model/get-classes-with-property (:block/uuid page)))
-            {:msg "Unable to delete this page because classes use this property"}
-            (->> (model/get-block-property-values (:block/uuid page))
-                 (filter (fn [[_ v]] (if (seq? v) (seq v) (some? v))))
-                 seq)
-            {:msg "Unable to delete this page because blocks use this property"}))
-    (catch :default e
-      (log/error :exception e)
-      (state/pub-event! [:capture-error {:error e}])
-      {:msg (str "An unexpected failure while deleting: " e)})))
-
-(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
-                                redirect-to-home? true
-                                persist-op? true
-                                error-handler (fn [{:keys [msg]}] (log/error :msg msg))}}]
-  (when redirect-to-home? (route-handler/redirect-to-home!))
-  (when page-name
-    (when-let [repo (state/get-current-repo)]
-      (let [page-name (util/page-name-sanity-lc page-name)
-            blocks (db/get-page-blocks-no-cache page-name)
-            truncate-blocks-tx-data (mapv
-                                     (fn [block]
-                                       [:db.fn/retractEntity [:block/uuid (:block/uuid block)]])
-                                     blocks)
-            page (db/entity [:block/name page-name])]
-        (if-let [msg (and (config/db-based-graph? repo)
-                          (page-unable-to-delete repo page))]
-          (error-handler msg)
-          (let [_ (delete-file! repo page-name delete-file?)
-                ;; if other page alias this pagename,
-                ;; then just remove some attrs of this entity instead of retractEntity
-                delete-page-tx (cond
-                                 (not (:block/_namespace page))
-                                 (if (model/get-alias-source-page (state/get-current-repo) page-name)
-                                   (when-let [id (:db/id (db/entity [:block/name page-name]))]
-                                     (mapv (fn [attribute]
-                                             [:db/retract id attribute])
-                                           db-schema/retract-page-attributes))
-                                   (concat (db-refs->page repo page)
-                                           [[:db.fn/retractEntity [:block/name page-name]]]))
-
-                                 :else
-                                 nil)
-                tx-data (concat truncate-blocks-tx-data delete-page-tx)]
-            (db/transact! repo tx-data {:outliner-op :delete-page :persist-op? persist-op?})
-
-            (unfavorite-page! page-name)
-
-            (when (fn? ok-handler) (ok-handler))
-            (ui-handler/re-render-root!)))))))
-
-(defn- rename-update-block-refs!
-  [refs from-id to-id]
-  (->> refs
-       (remove #{{:db/id from-id}})
-       (cons {:db/id to-id})
-       (distinct)
-       (vec)))
-
-(defn- rename-update-refs!
-  "Unsanitized only"
-  [page old-original-name new-name]
-  ;; update all pages which have references to this page
-  (let [repo (state/get-current-repo)
-        to-page (db/entity [:block/name (util/page-name-sanity-lc new-name)])
-        blocks (:block/_refs (db/entity (:db/id page)))
-        page-ids (->> (map (fn [b]
-                             {:db/id (:db/id (:block/page b))}) blocks)
-                      (set))
-        tx       (->> (map (fn [{:block/keys [uuid content properties format] :as block}]
-                             (let [content    (let [content' (replace-old-page! content old-original-name new-name format)]
-                                                (when-not (= content' content)
-                                                  content'))
-                                   properties (let [properties' (walk-replace-old-page! properties old-original-name new-name format)]
-                                                (when-not (= properties' properties)
-                                                  properties'))]
-                               (when (or content properties)
-                                 (util/remove-nils-non-nested
-                                  {:block/uuid       uuid
-                                   :block/content    content
-                                   :block/properties properties
-                                   :block/properties-order (when (seq properties)
-                                                             (map first properties))
-                                   :block/refs (->> (rename-update-block-refs! (:block/refs block) (:db/id page) (:db/id to-page))
-                                                    (map :db/id)
-                                                    (set))})))) blocks)
-                      (remove nil?))]
-    (db/transact! repo tx)
-    (doseq [page-id page-ids]
-      (outliner-file/sync-to-file page-id))))
-
-(defn- rename-update-namespace!
-  "update :block/namespace of the renamed block"
-  [page old-original-name new-name]
-  (let [old-namespace? (text/namespace-page? old-original-name)
-        new-namespace? (text/namespace-page? new-name)
-        repo           (state/get-current-repo)]
-    (cond
-      new-namespace?
-      ;; update namespace
-      (let [namespace (first (gp-util/split-last "/" new-name))]
-        (when namespace
-          (create! namespace {:redirect? false}) ;; create parent page if not exist, creation of namespace ref is handled in `create!`
-          (let [namespace-block (db/pull [:block/name (gp-util/page-name-sanity-lc namespace)])
-                page-txs [{:db/id (:db/id page)
-                           :block/namespace (:db/id namespace-block)}]]
-            (db/transact! repo page-txs))))
-
-      old-namespace?
-      ;; retract namespace
-      (db/transact! [[:db/retract (:db/id page) :block/namespace]])
-
-      :else
-      nil)))
-
-(defn- rename-page-aux
-  "Only accepts unsanitized page names"
-  [old-name new-name redirect?]
-  (let [old-page-name       (util/page-name-sanity-lc old-name)
-        new-file-name-body  (fs-util/file-name-sanity new-name) ;; w/o file extension
-        new-page-name       (util/page-name-sanity-lc new-name)
-        repo                (state/get-current-repo)
-        page                (db/pull [:block/name old-page-name])]
-    (when (and repo page)
-      (let [old-original-name   (:block/original-name page)
-            file                (:block/file page)
-            journal?            (:block/journal? page)
-            properties-block    (:data (outliner-tree/-get-down (outliner-core/block page)))
-            properties-content  (:block/content properties-block)
-            properties-block-tx (when (and properties-block
-                                           properties-content
-                                           (string/includes? (util/page-name-sanity-lc properties-content)
-                                                             old-page-name))
-                                  (let [front-matter? (and (file-property/front-matter?-when-file-based properties-content)
-                                                           (= :markdown (:block/format properties-block)))]
-                                    {:db/id         (:db/id properties-block)
-                                     :block/content (file-property/insert-property
-                                                     (:block/format properties-block)
-                                                     properties-content
-                                                     :title
-                                                     new-name
-                                                     front-matter?)}))
-            page-txs            [{:db/id               (:db/id page)
-                                  :block/uuid          (:block/uuid page)
-                                  :block/name          new-page-name
-                                  :block/original-name new-name}]
-            page-txs            (if properties-block-tx (conj page-txs properties-block-tx) page-txs)]
-
-        (db/transact! repo page-txs)
-
-        (when (and (not (config/db-based-graph? repo))
-                   (fs-util/create-title-property? new-page-name))
-          (page-property/add-property! new-page-name :title new-name))
-
-        (when (and file (not journal?))
-          (rename-file! file new-file-name-body (fn [] nil)))
-
-        (let [home (get (state/get-config) :default-home {})]
-          (when (= old-page-name (util/page-name-sanity-lc (get home :page "")))
-            (config-handler/set-config! :default-home (assoc home :page new-name))))
-
-        (rename-update-refs! page old-original-name new-name)
-
-        (rename-update-namespace! page old-original-name new-name)
-
-        (outliner-file/sync-to-file page))
-
-      ;; Redirect to the newly renamed page
-      (when redirect?
-        (route-handler/redirect! {:to          (if (model/whiteboard-page? page) :whiteboard :page)
-                                  :push        false
-                                  :path-params {:name new-page-name}}))
-
-      (when (favorited? old-page-name)
-        (p/do!
-         (unfavorite-page! old-page-name)
-         (favorite-page! new-page-name)))
-
-      (recent-handler/update-or-add-renamed-page repo old-page-name new-page-name)
-
-      (ui-handler/re-render-root!))))
-
-(defn- rename-nested-pages
-  "Unsanitized names only"
-  [old-ns-name new-ns-name]
-  (let [repo            (state/get-current-repo)
-        nested-page-str (page-ref/->page-ref (util/page-name-sanity-lc old-ns-name))
-        ns-prefix-format-str (str page-ref/left-brackets "%s/")
-        ns-prefix       (util/format ns-prefix-format-str (util/page-name-sanity-lc old-ns-name))
-        nested-pages    (db/get-pages-by-name-partition repo nested-page-str)
-        nested-pages-ns (db/get-pages-by-name-partition repo ns-prefix)]
-    (when nested-pages
-      ;; rename page "[[obsidian]] is a tool" to "[[logseq]] is a tool"
-      (doseq [{:block/keys [name original-name]} nested-pages]
-        (let [old-page-title (or original-name name)
-              new-page-title (string/replace
-                              old-page-title
-                              (page-ref/->page-ref old-ns-name)
-                              (page-ref/->page-ref new-ns-name))]
-          (when (and old-page-title new-page-title)
-            (p/do!
-             (rename-page-aux old-page-title new-page-title false)
-             (println "Renamed " old-page-title " to " new-page-title))))))
-    (when nested-pages-ns
-      ;; rename page "[[obsidian/page1]] is a tool" to "[[logseq/page1]] is a tool"
-      (doseq [{:block/keys [name original-name]} nested-pages-ns]
-        (let [old-page-title (or original-name name)
-              new-page-title (string/replace
-                              old-page-title
-                              (util/format ns-prefix-format-str old-ns-name)
-                              (util/format ns-prefix-format-str new-ns-name))]
-          (when (and old-page-title new-page-title)
-            (p/do!
-             (rename-page-aux old-page-title new-page-title false)
-             (println "Renamed " old-page-title " to " new-page-title))))))))
-
-(defn- rename-namespace-pages!
-  "Original names (unsanitized only)"
-  [repo old-name new-name]
-  (let [pages (db/get-namespace-pages repo old-name)
-        page (db/pull [:block/name (util/page-name-sanity-lc old-name)])
-        pages (cons page pages)]
-    (doseq [{:block/keys [name original-name]} pages]
-      (let [old-page-title (or original-name name)
-            ;; only replace one time, for the case that the namespace is a sub-string of the sub-namespace page name
-            ;; Example: has pages [[work]] [[work/worklog]],
-            ;; we want to rename [[work/worklog]] to [[work1/worklog]] when rename [[work]] to [[work1]],
-            ;; but don't rename [[work/worklog]] to [[work1/work1log]]
-            new-page-title (string/replace-first old-page-title old-name new-name)
-            redirect? (= name (:block/name page))]
-        (when (and old-page-title new-page-title)
-          (p/let [_ (rename-page-aux old-page-title new-page-title redirect?)]
-            (println "Renamed " old-page-title " to " new-page-title)))))))
-
-(defn file-based-merge-pages!
-  "Only accepts sanitized page names"
-  [from-page-name to-page-name]
-  (when (and (db/page-exists? from-page-name)
-             (db/page-exists? to-page-name)
-             (not= from-page-name to-page-name))
-    (let [to-page (db/entity [:block/name to-page-name])
-          to-id (:db/id to-page)
-          from-page (db/entity [:block/name from-page-name])
-          from-id (:db/id from-page)
-          from-first-child (some->> (db/pull from-id)
-                                    (outliner-core/block)
-                                    (outliner-tree/-get-down)
-                                    (outliner-core/get-data))
-          to-last-direct-child-id (model/get-block-last-direct-child (db/get-db) to-id false)
-          repo (state/get-current-repo)
-          conn (conn/get-db repo false)
-          datoms (d/datoms @conn :avet :block/page from-id)
-          block-eids (mapv :e datoms)
-          blocks (db-utils/pull-many repo '[:db/id :block/page :block/refs :block/path-refs :block/left :block/parent] block-eids)
-          tx-data (map (fn [block]
-                         (let [id (:db/id block)]
-                           (cond->
-                            {:db/id id
-                             :block/page {:db/id to-id}
-                             :block/refs (rename-update-block-refs! (:block/refs block) from-id to-id)}
-
-                             (and from-first-child (= id (:db/id from-first-child)))
-                             (assoc :block/left {:db/id (or to-last-direct-child-id to-id)})
-
-                             (= (:block/parent block) {:db/id from-id})
-                             (assoc :block/parent {:db/id to-id})))) blocks)]
-      (db/transact! repo tx-data)
-      (outliner-file/sync-to-file {:db/id to-id})
-
-      (rename-update-refs! from-page
-                           (util/get-page-original-name from-page)
-                           (util/get-page-original-name to-page))
-
-      (rename-update-namespace! from-page
-                                (util/get-page-original-name from-page)
-                                (util/get-page-original-name to-page)))
-
-
-    (delete! from-page-name nil)
-
-    (route-handler/redirect! {:to          :page
-                              :push        false
-                              :path-params {:name to-page-name}})))
-
-(defn db-based-merge-pages!
-  [from-page-name to-page-name persist-op?]
-  (when (and (db/page-exists? from-page-name)
-             (db/page-exists? to-page-name)
-             (not= from-page-name to-page-name))
-    (let [to-page (db/entity [:block/name to-page-name])
-          to-id (:db/id to-page)
-          from-page (db/entity [:block/name from-page-name])
-          from-id (:db/id from-page)
-          from-first-child (some->> (db/pull from-id)
-                                    (outliner-core/block)
-                                    (outliner-tree/-get-down)
-                                    (outliner-core/get-data))
-          to-last-direct-child-id (model/get-block-last-direct-child (db/get-db) to-id false)
-          repo (state/get-current-repo)
-          conn (conn/get-db repo false)
-          datoms (d/datoms @conn :avet :block/page from-id)
-          block-eids (mapv :e datoms)
-          blocks (db-utils/pull-many repo '[:db/id :block/page :block/refs :block/path-refs :block/left :block/parent] block-eids)
-          blocks-tx-data (map (fn [block]
-                                (let [id (:db/id block)]
-                                  (cond->
-                                      {:db/id id
-                                       :block/page {:db/id to-id}}
-
-                                    (and from-first-child (= id (:db/id from-first-child)))
-                                    (assoc :block/left {:db/id (or to-last-direct-child-id to-id)})
-
-                                    (= (:block/parent block) {:db/id from-id})
-                                    (assoc :block/parent {:db/id to-id})))) blocks)
-          replace-ref-tx-data (db-replace-ref repo from-page to-page)
-          tx-data (concat blocks-tx-data replace-ref-tx-data)]
-      (db/transact! repo tx-data {:persist-op? persist-op?})
-      (rename-update-namespace! from-page
-                                (util/get-page-original-name from-page)
-                                (util/get-page-original-name to-page)))
-
-
-    (delete! from-page-name nil :redirect-to-home? false :persist-op? persist-op?)
-
-    (route-handler/redirect! {:to          :page
-                              :push        false
-                              :path-params {:name to-page-name}})))
-
-(defn db-based-rename!
-  ([old-name new-name]
-   (db-based-rename! old-name new-name true true))
-  ([old-name new-name redirect? persist-op?]
-   (let [repo (state/get-current-repo)
-         old-name      (string/trim old-name)
-         new-name      (string/trim new-name)
-         old-page-name (util/page-name-sanity-lc old-name)
-         page-e (db/entity [:block/name old-page-name])
-         new-page-name (util/page-name-sanity-lc new-name)
-         name-changed? (not= old-name new-name)]
-     (if (and old-name
-              new-name
-              (not (string/blank? new-name))
-              name-changed?)
-       (cond
-         (= old-page-name new-page-name) ; case changed
-         (db/transact! repo
-                       [{:db/id (:db/id page-e)
-                         :block/original-name new-name}]
-                       {:persist-op? persist-op?})
-
-         (and (not= old-page-name new-page-name)
-              (db/entity [:block/name new-page-name])) ; merge page
-         (db-based-merge-pages! old-page-name new-page-name persist-op?)
-
-         :else                          ; rename
-         (create! new-name
-                  {:rename? true
-                   :uuid (:block/uuid page-e)
-                   :redirect? redirect?
-                   :create-first-block? false
-                   :persist-op? persist-op?}))
-
-       (when (string/blank? new-name)
-         (notification/show! "Please use a valid name, empty name is not allowed!" :error)))
-     (ui-handler/re-render-root!))))
-
-(defn file-based-rename!
-  "Accepts unsanitized page names"
-  ([old-name new-name] (file-based-rename! old-name new-name true))
-  ([old-name new-name redirect?]
-   (let [repo          (state/get-current-repo)
-         old-name      (string/trim old-name)
-         new-name      (string/trim new-name)
-         old-page-name (util/page-name-sanity-lc old-name)
-         new-page-name (util/page-name-sanity-lc new-name)
-         name-changed? (not= old-name new-name)]
-     (if (and old-name
-              new-name
-              (not (string/blank? new-name))
-              name-changed?)
-       (do
-         (cond
-           (= old-page-name new-page-name)
-           (rename-page-aux old-name new-name redirect?)
-
-            (db/pull [:block/name new-page-name])
-            (file-based-merge-pages! old-page-name new-page-name)
-
-           :else
-           (rename-namespace-pages! repo old-name new-name))
-         (rename-nested-pages old-name new-name))
-       (when (string/blank? new-name)
-         (notification/show! "Please use a valid name, empty name is not allowed!" :error)))
-     (ui-handler/re-render-root!))))
-
 (defn rename!
   ([old-name new-name] (rename! old-name new-name true))
   ([old-name new-name redirect?] (rename! old-name new-name redirect? true))
   ([old-name new-name redirect? persist-op?]
    (if (config/db-based-graph? (state/get-current-repo))
-     (db-based-rename! old-name new-name redirect? persist-op?)
-     (file-based-rename! old-name new-name redirect?))))
+     (db-page-handler/rename! old-name new-name redirect? persist-op?)
+     (file-page-handler/rename! old-name new-name redirect?))))
 
 (defn reorder-favorites!
   [{:keys [to up?]}]

+ 3 - 3
src/main/frontend/handler/recent.cljs

@@ -1,7 +1,7 @@
 (ns frontend.handler.recent
   "Fns related to recent pages feature"
   (:require [frontend.handler.db-based.recent :as db-based]
-            [frontend.handler.file-based.recent :as file-based]
+            [frontend.handler.file-based.recent :as file-recent-handler]
             [frontend.config :as config]
             [frontend.state :as state]))
 
@@ -9,11 +9,11 @@
   [repo page click-from-recent?]
   (if (config/db-based-graph? repo)
     (db-based/add-page-to-recent! repo page click-from-recent?)
-    (file-based/add-page-to-recent! repo page click-from-recent?)))
+    (file-recent-handler/add-page-to-recent! repo page click-from-recent?)))
 
 (defn get-recent-pages
   []
   (let [repo (state/get-current-repo)]
     (if (config/db-based-graph? repo)
       (db-based/get-recent-pages)
-      (file-based/get-recent-pages))))
+      (file-recent-handler/get-recent-pages))))

+ 3 - 3
src/test/frontend/db/name_sanity_test.cljs

@@ -2,7 +2,7 @@
   (:require [cljs.test :refer [deftest testing is are]]
             [clojure.string :as string]
             [logseq.graph-parser.util :as gp-util]
-            [frontend.handler.page :as page-handler]
+            [frontend.handler.file-based.page :as file-page-handler]
             [frontend.handler.conversion :as conversion-handler]
             [frontend.util.fs :as fs-util]))
 
@@ -46,8 +46,8 @@
   (mapv test-page-name fs-util/windows-reserved-filebodies))
 
 (deftest new-path-computation-tests
-  (is (= (#'page-handler/compute-new-file-path "/data/app/dsal dsalfjk aldsaf.jkl" "ddd") "/data/app/ddd.jkl"))
-  (is (= (#'page-handler/compute-new-file-path "c://data/a sdfpp/dsal dsalf% * _ dsaf.mnk" "c d / f") "c://data/a sdfpp/c d / f.mnk")))
+  (is (= (#'file-page-handler/compute-new-file-path "/data/app/dsal dsalfjk aldsaf.jkl" "ddd") "/data/app/ddd.jkl"))
+  (is (= (#'file-page-handler/compute-new-file-path "c://data/a sdfpp/dsal dsalf% * _ dsaf.mnk" "c d / f") "c://data/a sdfpp/c d / f.mnk")))
 
 (deftest break-change-conversion-tests
   (let [conv-legacy #(:target (#'conversion-handler/calc-previous-name :legacy :triple-lowbar %))]

+ 4 - 4
src/test/frontend/handler/page_test.cljs → src/test/frontend/handler/file_based/page_test.cljs

@@ -1,10 +1,10 @@
-(ns frontend.handler.page-test
+(ns frontend.handler.file-based.page-test
   ;; namespace local config for private function tests
   {:clj-kondo/config {:linters {:private-call {:level :off}}}}
   (:require [cljs.test :refer [deftest are]]
             [clojure.string :as string]
             [frontend.util :as util]
-            [frontend.handler.page :as page-handler]))
+            [frontend.handler.file-based.page :as file-page-handler]))
 
 (defn- replace-page-ref!
   [content old-name new-name]
@@ -36,7 +36,7 @@
   (when (and (string? content) (string? old-name) (string? new-name))
     (-> content
         (replace-page-ref! old-name new-name)
-        (page-handler/replace-tag-ref! old-name new-name))))
+        (file-page-handler/replace-tag-ref! old-name new-name))))
 
 (deftest test-replace-page-ref!
   (are [x y] (= (let [[content old-name new-name] x]
@@ -66,7 +66,7 @@
 
 (deftest test-replace-tag-ref!
   (are [x y] (= (let [[content old-name new-name] x]
-                  (page-handler/replace-tag-ref! content old-name new-name))
+                  (file-page-handler/replace-tag-ref! content old-name new-name))
                 y)
     ["#foo" "foo" "bar"] "#bar"
     ["#foo" "foo" "new bar"] "#[[new bar]]"

+ 2 - 2
src/test/frontend/handler/repo_conversion_test.cljs

@@ -7,7 +7,7 @@
             [logseq.graph-parser.test.docs-graph-helper :as docs-graph-helper]
             [logseq.graph-parser.config :as gp-config]
             [frontend.test.helper :as test-helper]
-            [frontend.handler.page :as page-handler]
+            [frontend.handler.file-based.page :as file-page-handler]
             [frontend.handler.conversion :as conversion-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.db.conn :as conn]
@@ -123,7 +123,7 @@
     (if rename-target
       #_:clj-kondo/ignore
       (do #_(prn "conversion triple-lowbar: " original-body " -> " rename-target)
-       (#'page-handler/compute-new-file-path path rename-target))
+       (#'file-page-handler/compute-new-file-path path rename-target))
       path)))
 
 (defn- convert-graph-files-path