浏览代码

Bring back namespaces/page merge/nested pages

Tienson Qin 1 年之前
父节点
当前提交
6eb6e60b55
共有 29 个文件被更改,包括 851 次插入131 次删除
  1. 33 0
      deps/db/src/logseq/db.cljs
  2. 3 1
      deps/db/src/logseq/db/frontend/malli_schema.cljs
  3. 13 1
      deps/db/src/logseq/db/frontend/rules.cljc
  4. 4 1
      deps/db/src/logseq/db/frontend/schema.cljs
  5. 19 1
      deps/graph-parser/src/logseq/graph_parser/block.cljs
  6. 1 1
      deps/graph-parser/src/logseq/graph_parser/exporter.cljs
  7. 8 1
      deps/graph-parser/src/logseq/graph_parser/extract.cljc
  8. 14 1
      deps/graph-parser/src/logseq/graph_parser/test/docs_graph_helper.cljs
  9. 8 0
      deps/graph-parser/src/logseq/graph_parser/text.cljs
  10. 28 1
      src/main/frontend/components/block.cljs
  11. 67 0
      src/main/frontend/components/hierarchy.cljs
  12. 6 1
      src/main/frontend/components/page.cljs
  13. 7 1
      src/main/frontend/components/query/builder.cljs
  14. 2 1
      src/main/frontend/db.cljs
  15. 133 24
      src/main/frontend/db/model.cljs
  16. 6 2
      src/main/frontend/db_worker.cljs
  17. 1 0
      src/main/frontend/extensions/pdf/assets.cljs
  18. 48 36
      src/main/frontend/handler/graph.cljs
  19. 1 0
      src/main/frontend/handler/page.cljs
  20. 4 1
      src/main/frontend/handler/query/builder.cljs
  21. 35 7
      src/main/frontend/worker/handler/page.cljs
  22. 2 2
      src/main/frontend/worker/handler/page/db_based/rename.cljs
  23. 287 0
      src/main/frontend/worker/handler/page/file_based/rename.cljs
  24. 1 1
      src/main/frontend/worker/rtc/core.cljs
  25. 6 4
      src/main/logseq/api.cljs
  26. 51 0
      src/test/frontend/db/model_test.cljs
  27. 1 1
      src/test/frontend/db/name_sanity_test.cljs
  28. 15 0
      src/test/frontend/db/query_dsl_test.cljs
  29. 47 42
      src/test/frontend/worker/handler/page/rename_test.cljs

+ 33 - 0
deps/db/src/logseq/db.cljs

@@ -504,3 +504,36 @@
     "Whether the current graph is db-only"
     [db]
     (= "db" (:db/type (d/entity db :logseq.kv.db/type)))))
+
+;; File based fns
+(defn get-namespace-pages
+  "Accepts both sanitized and unsanitized namespaces"
+  [db namespace {:keys [db-graph?]}]
+  (assert (string? namespace))
+  (let [namespace (common-util/page-name-sanity-lc namespace)
+        pull-attrs  (cond-> [:db/id :block/name :block/original-name :block/namespace]
+                      (not db-graph?)
+                      (conj {:block/file [:db/id :file/path]}))]
+    (d/q
+     [:find [(list 'pull '?c pull-attrs) '...]
+      :in '$ '% '?namespace
+      :where
+      ['?p :block/name '?namespace]
+      (list 'namespace '?p '?c)]
+     db
+     (:namespace rules/rules)
+     namespace)))
+
+(defn get-pages-by-name-partition
+  [db partition]
+  (when-not (string/blank? partition)
+    (let [partition (common-util/page-name-sanity-lc (string/trim partition))
+          ids (->> (d/datoms db :aevt :block/name)
+                   (filter (fn [datom]
+                             (let [page (:v datom)]
+                               (string/includes? page partition))))
+                   (map :e))]
+      (when (seq ids)
+        (d/pull-many db
+                     '[:db/id :block/name :block/original-name]
+                     ids)))))

+ 3 - 1
deps/db/src/logseq/db/frontend/malli_schema.cljs

@@ -138,7 +138,9 @@
    [:block/journal? :boolean]
    [:block/alias {:optional true} [:set :int]]
     ;; TODO: Should this be here or in common?
-   [:block/path-refs {:optional true} [:set :int]]])
+   [:block/path-refs {:optional true} [:set :int]]
+   ;; file-based
+   [:block/namespace {:optional true} :int]])
 
 (def property-attrs
   "Common attributes for properties"

+ 13 - 1
deps/db/src/logseq/db/frontend/rules.cljc

@@ -4,7 +4,14 @@
 (def ^:large-vars/data-var rules
   "Rules used mainly in frontend.db.model"
   ;; rule "parent" is optimized for parent node -> child node nesting queries
-  {:class-parent
+  {:namespace
+   '[[(namespace ?p ?c)
+      [?c :block/namespace ?p]]
+     [(namespace ?p ?c)
+      [?t :block/namespace ?p]
+      (namespace ?t ?c)]]
+
+   :class-parent
    '[[(class-parent ?p ?c)
       [?c :class/parent ?p]]
      [(class-parent ?p ?c)
@@ -121,6 +128,11 @@
      [?b :block/page ?bp]
      [?bp :block/name ?page-name]]
 
+   :namespace
+   '[(namespace ?p ?namespace)
+     [?p :block/namespace ?parent]
+     [?parent :block/name ?namespace]]
+
    :property
    '[(property ?b ?key ?val)
      [?b :block/properties ?prop]

+ 4 - 1
deps/db/src/logseq/db/frontend/schema.cljs

@@ -49,6 +49,9 @@
    :block/link {:db/valueType :db.type/ref
                 :db/index true}
 
+   ;; page's namespace
+   :block/namespace {:db/valueType :db.type/ref}
+
    ;; for pages
    :block/alias {:db/valueType :db.type/ref
                  :db/cardinality :db.cardinality/many}
@@ -120,7 +123,7 @@
 (def schema-for-db-based-graph
   (merge
    (dissoc schema
-           :block/properties :block/properties-text-values :block/pre-block? :recent/pages :file/handle :block/file
+           :block/namespace :block/properties :block/properties-text-values :block/pre-block? :recent/pages :file/handle :block/file
            :block/properties-order)
    {:block/name {:db/index true}        ; remove db/unique for :block/name
     ;; class properties

+ 19 - 1
deps/graph-parser/src/logseq/graph_parser/block.cljs

@@ -293,6 +293,8 @@
     (and original-page-name (string? original-page-name))
     (let [original-page-name (common-util/remove-boundary-slashes original-page-name)
           [original-page-name page-name journal-day] (convert-page-if-journal original-page-name date-formatter)
+          namespace? (and (not (boolean (text/get-nested-page-name original-page-name)))
+                          (text/namespace-page? original-page-name))
           page-entity (some-> db (ldb/get-page page-name))
           original-page-name (or from-page (:block/original-name page-entity) original-page-name)]
       (merge
@@ -304,6 +306,10 @@
                                (uuid? with-id?) with-id?)
                          (d/squuid))]
            {:block/uuid new-uuid}))
+       (when namespace?
+         (let [namespace (first (common-util/split-last "/" original-page-name))]
+           (when-not (string/blank? namespace)
+             {:block/namespace {:block/name (common-util/page-name-sanity-lc namespace)}})))
        (when (and with-timestamp? (not page-entity)) ;; Only assign timestamp on creating new entity
          (let [current-ms (common-util/time-ms)]
            {:block/created-at current-ms
@@ -348,7 +354,19 @@
     (let [*name->id (atom {})
           ref->map-fn (fn [*col _tag?]
                         (let [col (remove string/blank? @*col)
-                              col (distinct col)]
+                              children-pages (->> (mapcat (fn [p]
+                                                            (let [p (if (map? p)
+                                                                      (:block/original-name p)
+                                                                      p)]
+                                                              (when (string? p)
+                                                                (let [p (or (text/get-nested-page-name p) p)]
+                                                                  (when (text/namespace-page? p)
+                                                                    (common-util/split-namespace-pages p))))))
+                                                          col)
+                                                  (remove string/blank?)
+                                                  (distinct))
+                              col (->> (distinct (concat col children-pages))
+                                       (remove nil?))]
                           (map
                            (fn [item]
                              (let [macro? (and (map? item)

+ 1 - 1
deps/graph-parser/src/logseq/graph_parser/exporter.cljs

@@ -616,7 +616,7 @@
                                 ;; block/uuid was particularly bad as it actually changed the page's identity across files
                                 disallowed-attributes [:block/name :block/uuid :block/format :block/journal? :block/original-name :block/journal-day
                                                        :block/created-at :block/updated-at]
-                                allowed-attributes [:block/properties :block/tags :block/alias :class/parent :block/type]
+                                allowed-attributes [:block/properties :block/tags :block/alias :class/parent :block/type :block/namespace]
                                 block-changes (select-keys % allowed-attributes)]
                             (when-let [ignored-attrs (not-empty (apply dissoc % (into disallowed-attributes allowed-attributes)))]
                               (notify-user {:msg (str "Import ignored the following attributes on page " (pr-str (:block/original-name %)) ": "

+ 8 - 1
deps/graph-parser/src/logseq/graph_parser/extract.cljc

@@ -252,9 +252,16 @@
              (:block/properties-text-values (first blocks))]
             [properties [] {}])
           page-map (build-page-map properties invalid-properties properties-text-values file page page-name (assoc options' :from-page page))
+          namespace-pages (let [page (:block/original-name page-map)]
+                            (when (text/namespace-page? page)
+                              (->> (common-util/split-namespace-pages page)
+                                   (map (fn [page]
+                                          (-> (gp-block/page-name->map page true db true date-formatter)
+                                              (assoc :block/format format)))))))
           pages (->> (concat
                       [page-map]
-                      @ref-pages)
+                      @ref-pages
+                      namespace-pages)
                      ;; remove block references
                      (remove vector?)
                      (remove nil?))

+ 14 - 1
deps/graph-parser/src/logseq/graph_parser/test/docs_graph_helper.cljs

@@ -106,7 +106,7 @@
                 (into {})))
         "Task marker counts")
 
-    (is (= {:markdown 6100 :org 509} (get-block-format-counts db))
+    (is (= {:markdown 6106 :org 509} (get-block-format-counts db))
         "Block format counts")
 
     (is (= {:description 81, :updated-at 46, :tags 5,
@@ -134,6 +134,19 @@
            (get-counts-for-common-attributes db))
         "Counts for blocks with common block attributes")
 
+    (let [no-name (->> (d/q '[:find (pull ?n [*]) :where [?b :block/namespace ?n]] db)
+                       (filter (fn [x]
+                                 (when-not (:block/original-name (first x))
+                                   x))))
+          all-namespaces (->> (d/q '[:find (pull ?n [*]) :where [?b :block/namespace ?n]] db)
+                              (map (comp :block/original-name first))
+                              set)]
+      (is (= #{"term" "setting" "book" "templates" "Query table" "page"
+               "Whiteboard" "Whiteboard/Tool" "Whiteboard/Tool/Shape" "Whiteboard/Object"
+               "Whiteboard/Property" "Community" "Tweet"}
+             all-namespaces)
+          (str "Has correct namespaces: " no-name)))
+
     (is (empty? (->> (d/q '[:find ?n :where [?b :block/name ?n]] db)
                      (map first)
                      (filter #(string/includes? % "___"))))

+ 8 - 0
deps/graph-parser/src/logseq/graph_parser/text.cljs

@@ -148,3 +148,11 @@
           (if-some [new-val (parse-non-string-property-value v')]
             new-val
             v'))))))
+
+(defn namespace-page?
+  [page-name]
+  (and (string? page-name)
+       (string/includes? page-name "/")
+       (not (string/starts-with? page-name "../"))
+       (not (string/starts-with? page-name "./"))
+       (not (common-util/url? page-name))))

+ 28 - 1
src/main/frontend/components/block.cljs

@@ -1475,6 +1475,27 @@
           format (get-in config [:block :block/format] :markdown)]
       (render-macro config name arguments macro-content format))))
 
+(rum/defc namespace-hierarchy-aux
+  [config namespace children]
+  [:ul
+   (for [child children]
+     [:li {:key (str "namespace-" namespace "-" (:db/id child))}
+      (let [shorten-name (some-> (or (:block/original-name child) (:block/name child))
+                                 (string/split "/")
+                                 last)]
+        (page-cp {:label shorten-name} child))
+      (when (seq (:namespace/children child))
+        (namespace-hierarchy-aux config (:block/name child)
+                                 (:namespace/children child)))])])
+
+(rum/defc namespace-hierarchy
+  [config namespace children]
+  [:div.namespace
+   [:div.font-medium.flex.flex-row.items-center.pb-2
+    [:span.text-sm.mr-1 "Namespace "]
+    (page-cp config {:block/name namespace})]
+   (namespace-hierarchy-aux config namespace children)])
+
 (defn- macro-cp
   [config options]
   (let [{:keys [name arguments]} options
@@ -1493,7 +1514,13 @@
       (macro-function-cp config arguments)
 
       (= name "namespace")
-      [:div.warning "Namespace has been deprecated, use tags instead"]
+      (if (config/db-based-graph? (state/get-current-repo))
+        [:div.warning "Namespace has been deprecated, use tags instead"]
+        (let [namespace (first arguments)]
+          (when-not (string/blank? namespace)
+            (let [namespace (string/lower-case (page-ref/get-page-name! namespace))
+                  children (model/get-namespace-hierarchy (state/get-current-repo) namespace)]
+              (namespace-hierarchy config namespace children)))))
 
       (= name "youtube")
       (when-let [url (first arguments)]

+ 67 - 0
src/main/frontend/components/hierarchy.cljs

@@ -0,0 +1,67 @@
+(ns frontend.components.hierarchy
+  (:require [clojure.string :as string]
+            [frontend.components.block :as block]
+            [frontend.db :as db]
+            [frontend.db.model :as db-model]
+            [frontend.state :as state]
+            [logseq.graph-parser.text :as text]
+            [frontend.ui :as ui]
+            [medley.core :as medley]
+            [rum.core :as rum]
+            [frontend.util :as util]))
+
+(defn- get-relation
+  "Get all parent pages along the namespace hierarchy path.
+   If there're aliases, only use the first namespaced alias."
+  [page]
+  (when-let [page (or (text/get-nested-page-name page) page)]
+    (let [repo (state/get-current-repo)
+          aliases (db/get-page-alias-names repo page)
+          all-page-names (conj aliases page)]
+      (when-let [page (or (first (filter text/namespace-page? all-page-names))
+                          (when (:block/_namespace (db/entity [:block/name (util/page-name-sanity-lc page)]))
+                            page))]
+        (let [namespace-pages (db/get-namespace-pages repo page)
+              parent-routes (db-model/get-page-namespace-routes repo page)
+              pages (->> (concat namespace-pages parent-routes)
+                         (distinct)
+                         (sort-by :block/name)
+                         (map (fn [page]
+                                (or (:block/original-name page) (:block/name page))))
+                         (map #(string/split % "/")))
+              page-namespace (db-model/get-page-namespace repo page)
+              page-namespace (util/get-page-original-name page-namespace)]
+          (cond
+            (seq pages)
+            {:namespaces pages
+             :namespace-pages namespace-pages}
+
+            page-namespace
+            {:namespaces [(string/split page-namespace "/")]
+             :namespace-pages namespace-pages}
+
+            :else
+            nil))))))
+
+(rum/defc structures
+  [page]
+  (let [{:keys [namespaces]} (get-relation page)]
+    (when (seq namespaces)
+      [:div.page-hierarchy.mt-6
+       (ui/foldable
+        [:h2.font-bold.opacity-30 "Hierarchy"]
+        [:ul.namespaces {:style {:margin "12px 24px"}}
+         (for [namespace namespaces]
+           [:li.my-2
+            (->>
+             (for [[idx page] (medley/indexed namespace)]
+               (when (and (string? page) page)
+                 (let [full-page (->> (take (inc idx) namespace)
+                                      util/string-join-path)]
+                   (block/page-reference false
+                                         full-page
+                                         {}
+                                         page))))
+             (interpose [:span.mx-2.opacity-30 "/"]))])]
+        {:default-collapsed? false
+         :title-trigger? true})])))

+ 6 - 1
src/main/frontend/components/page.cljs

@@ -49,7 +49,8 @@
             [frontend.extensions.graph.pixi :as pixi]
             [frontend.db.async :as db-async]
             [logseq.db :as ldb]
-            [frontend.handler.property.util :as pu]))
+            [frontend.handler.property.util :as pu]
+            [frontend.components.hierarchy :as hierarchy]))
 
 (defn- get-page-name
   [state]
@@ -556,6 +557,10 @@
                (when (contains? (:block/type page) "class")
                  (class-component/class-children page))
 
+               (when-not block-or-whiteboard?
+                 (when (and (not journal?) (not db-based?))
+                   (hierarchy/structures route-page-name)))
+
                (when-not (or block-or-whiteboard? sidebar? home?)
                  [:div {:key "page-unlinked-references"}
                   (reference/unlinked-references page)])])))))))

+ 7 - 1
src/main/frontend/components/query/builder.cljs

@@ -156,6 +156,12 @@
         repo (state/get-current-repo)]
     [:div
      (case @*mode
+       "namespace"
+       (let [items (sort (db-model/get-all-namespace-parents repo))]
+         (select items
+                 (fn [{:keys [value]}]
+                   (append-tree! *tree opts loc [:namespace value]))))
+
        "tags"
        (let [items (->> (db-model/get-all-tagged-pages repo)
                         (map second)
@@ -324,7 +330,7 @@
       (str (name f) ": "
            (string/join " | " (rest clause)))
 
-      (contains? #{:page :task} (keyword f))
+      (contains? #{:page :task :namespace} (keyword f))
       (str (name f) ": " (if (vector? (second clause))
                            (second (second clause))
                            (second clause)))

+ 2 - 1
src/main/frontend/db.cljs

@@ -40,7 +40,8 @@
   get-all-pages get-pages-relation get-pages-that-mentioned-page get-tag-pages
   journal-page? page? page-alias-set sub-block
   set-file-last-modified-at! page-empty? page-exists? get-alias-source-page
-  set-file-content! has-children? whiteboard-page?]
+  set-file-content! has-children? whiteboard-page?
+  get-namespace-pages get-all-namespace-relation]
 
  [frontend.db.react
   get-current-page set-key-value

+ 133 - 24
src/main/frontend/db/model.cljs

@@ -19,7 +19,8 @@
             [logseq.common.util :as common-util]
             [logseq.common.util.date-time :as date-time-util]
             [frontend.config :as config]
-            [logseq.db :as ldb]))
+            [logseq.db :as ldb]
+            [logseq.graph-parser.text :as text]))
 
 ;; TODO: extract to specific models and move data transform logic to the
 ;; corresponding handlers.
@@ -616,29 +617,6 @@ independent of format as format specific heading characters are stripped"
                      pages)]
       (mapv (fn [[ref-page ref-page-name]] [ref-page-name (get-page-alias repo ref-page)]) ref-pages))))
 
-;; Ignore files with empty blocks for now
-(defn get-pages-relation
-  [repo with-journal?]
-  (when-let [db (conn/get-db repo)]
-    (let [q (if with-journal?
-              '[:find ?page ?ref-page-name
-                :where
-                [?p :block/name ?page]
-                [?block :block/page ?p]
-                [?block :block/refs ?ref-page]
-                [?ref-page :block/name ?ref-page-name]]
-              '[:find ?page ?ref-page-name
-                :where
-                [?p :block/journal? false]
-                [?p :block/name ?page]
-                [?block :block/page ?p]
-                [?block :block/refs ?ref-page]
-                [?ref-page :block/name ?ref-page-name]])]
-      (->>
-       (d/q q db)
-       (map (fn [[page ref-page-name]]
-              [page ref-page-name]))))))
-
 ;; get pages who mentioned this page
 (defn get-pages-that-mentioned-page
   [repo page-id include-journals?]
@@ -901,6 +879,137 @@ independent of format as format specific heading characters are stripped"
        class-id)
       (map :db/id (:block/_tags class)))))
 
+
+(defn get-all-namespace-relation
+  [repo]
+  (d/q '[:find ?page-name ?parent
+         :where
+         [?page :block/name ?page-name]
+         [?page :block/namespace ?e]
+         [?e :block/original-name ?parent]]
+    (conn/get-db repo)))
+
+(defn get-all-namespace-parents
+  [repo]
+  (->> (get-all-namespace-relation repo)
+       (map second)))
+
+(def ns-char "/")
+(def ns-re #"/")
+
+(defn- get-parents-namespace-list
+  "Return list of parents namespace"
+  [page-namespace & nested-found]
+  (if (text/namespace-page? page-namespace)
+    (let [pre-nested-vec (drop-last (string/split page-namespace ns-re))
+          my-nested-found (if (nil? nested-found)
+                            []
+                            nested-found)]
+      (if (= (count pre-nested-vec) 1)
+        (conj my-nested-found (nth pre-nested-vec 0))
+        (let [pre-nested-str (string/join ns-char pre-nested-vec)]
+          (recur pre-nested-str (conj my-nested-found pre-nested-str)))))
+    []))
+
+(defn- get-unnecessary-namespaces-name
+  "Return unnecessary namespace from a list of page's name"
+  [pages-list]
+  (->> pages-list
+       (remove nil?)
+       (mapcat get-parents-namespace-list)
+       distinct))
+
+(defn- remove-nested-namespaces-link
+  "Remove relations between pages and their nested namespace"
+  [pages-relations]
+  (let [pages-relations-to-return (distinct (mapcat
+                                             identity
+                                             (for [item (for [a-link-from (mapv (fn [a-rel] (first a-rel)) pages-relations)]
+                                                          [a-link-from (mapv
+                                                                        (fn [a-rel] (second a-rel))
+                                                                        (filterv
+                                                                         (fn [link-target] (=  a-link-from (first link-target)))
+                                                                         pages-relations))])
+                                                   :let [list-to (get item 1)
+                                                         page (get item 0)
+                                                         namespaces-to-remove (get-unnecessary-namespaces-name list-to)
+                                                         list-to-without-nested-ns (filterv (fn [elem] (not (some #{elem} namespaces-to-remove))) list-to)
+                                                         node-links (for [item-ok list-to-without-nested-ns]
+                                                                      [page item-ok])]]
+                                               (seq node-links))))]
+    pages-relations-to-return))
+
+;; Ignore files with empty blocks for now
+(defn get-pages-relation
+  [repo with-journal?]
+  (when-let [db (conn/get-db repo)]
+    (let [q (if with-journal?
+              '[:find ?page ?ref-page-name
+                :where
+                [?p :block/name ?page]
+                [?block :block/page ?p]
+                [?block :block/refs ?ref-page]
+                [?ref-page :block/name ?ref-page-name]]
+              '[:find ?page ?ref-page-name
+                :where
+                [?p :block/journal? false]
+                [?p :block/name ?page]
+                [?block :block/page ?p]
+                [?block :block/refs ?ref-page]
+                [?ref-page :block/name ?ref-page-name]])]
+      (->>
+       (d/q q db)
+       (map (fn [[page ref-page-name]]
+              [page ref-page-name]))
+       (remove-nested-namespaces-link)))))
+
+(defn get-namespace-pages
+  "Accepts both sanitized and unsanitized namespaces"
+  [repo namespace]
+  (ldb/get-namespace-pages (conn/get-db repo) namespace {:db-graph? (config/db-based-graph? repo)}))
+
+(defn- tree [flat-col root]
+  (let [sort-fn #(sort-by :block/name %)
+        children (group-by :block/namespace flat-col)
+        namespace-children (fn namespace-children [parent-id]
+                             (map (fn [m]
+                                    (assoc m :namespace/children
+                                           (sort-fn (namespace-children {:db/id (:db/id m)}))))
+                                  (sort-fn (get children parent-id))))]
+    (namespace-children root)))
+
+(defn get-namespace-hierarchy
+  "Unsanitized namespaces"
+  [repo namespace]
+  (let [children (get-namespace-pages repo namespace)
+        namespace-id (:db/id (db-utils/entity [:block/name (util/page-name-sanity-lc namespace)]))
+        root {:db/id namespace-id}
+        col (conj children root)]
+    (tree col root)))
+
+(defn get-page-namespace
+  [repo page]
+  (:block/namespace (db-utils/entity repo [:block/name (util/page-name-sanity-lc page)])))
+
+(defn get-page-namespace-routes
+  [repo page]
+  (assert (string? page))
+  (when-let [db (conn/get-db repo)]
+    (when-not (string/blank? page)
+      (let [page (util/page-name-sanity-lc (string/trim page))
+            page-exist? (db-utils/entity repo [:block/name page])
+            ids (if page-exist?
+                  '()
+                  (->> (d/datoms db :aevt :block/name)
+                       (filter (fn [datom]
+                                 (string/ends-with? (:v datom) (str "/" page))))
+                       (map :e)))]
+        (when (seq ids)
+          (db-utils/pull-many repo
+                              '[:db/id :block/name :block/original-name
+                                {:block/file [:db/id :file/path]}]
+                              ids))))))
+
 (comment
   ;; For debugging
   (defn get-all-blocks

+ 6 - 2
src/main/frontend/db_worker.cljs

@@ -15,7 +15,8 @@
             [frontend.worker.export :as worker-export]
             [frontend.worker.file :as file]
             [frontend.worker.handler.page :as worker-page]
-            [frontend.worker.handler.page.rename :as worker-page-rename]
+            [frontend.worker.handler.page.file-based.rename :as file-worker-page-rename]
+            [frontend.worker.handler.page.db-based.rename :as db-worker-page-rename]
             [frontend.worker.rtc.core :as rtc-core]
             [frontend.worker.rtc.db-listener :as rtc-db-listener]
             [frontend.worker.rtc.full-upload-download-graph :as rtc-updown]
@@ -499,7 +500,10 @@
    (assert (common-util/uuid-string? page-uuid-str))
    (when-let [conn (worker-state/get-datascript-conn repo)]
      (let [config (worker-state/get-config repo)
-           result (worker-page-rename/rename! repo conn config (uuid page-uuid-str) new-name)]
+           f (if (sqlite-util/db-based-graph? repo)
+               db-worker-page-rename/rename!
+               file-worker-page-rename/rename!)
+           result (f repo conn config (uuid page-uuid-str) new-name)]
        (bean/->js {:result result}))))
 
   (page-delete

+ 1 - 0
src/main/frontend/extensions/pdf/assets.cljs

@@ -165,6 +165,7 @@
         (let [label (:filename pdf-current)]
           (p/do!
            (page-handler/<create! page-name {:redirect?        false :create-first-block? false
+                                             :split-namespace? false
                                              :format           format
                                              ;; FIXME: file and file-path properties for db version
                                              :properties       {:file      (case format

+ 48 - 36
src/main/frontend/handler/graph.cljs

@@ -19,8 +19,9 @@
        links))
 
 (defn- build-nodes
-  [dark? current-page page-links tags nodes]
-  (let [current-page (or current-page "")
+  [dark? current-page page-links tags nodes namespaces]
+  (let [parents (set (map last namespaces))
+        current-page (or current-page "")
         pages (set (flatten nodes))]
     (->>
      pages
@@ -38,10 +39,13 @@
                            color)
                    n (get page-links p 1)
                    size (int (* 8 (max 1.0 (js/Math.cbrt n))))]
-               {:id p
-                :label p
-                :size size
-                :color color}))))))
+                (cond->
+                  {:id p
+                   :label p
+                   :size size
+                   :color color}
+                  (contains? parents p)
+                  (assoc :parent true))))))))
 
                   ;; slow
 (defn- uuid-or-asset?
@@ -88,6 +92,7 @@
     (when-let [repo (state/get-current-repo)]
       (let [relation (db/get-pages-relation repo journal?)
             tagged-pages (map (fn [[x y]] [x (common-util/page-name-sanity-lc y)]) (db/get-all-tagged-pages repo))
+            namespaces (map (fn [[x y]] [x (common-util/page-name-sanity-lc y)]) (db/get-all-namespace-relation repo))
             tags (set (map second tagged-pages))
             full-pages (db/get-all-pages repo)
             full-pages-map (into {} (map (juxt :block/name identity) full-pages))
@@ -105,7 +110,8 @@
               (not excluded-pages?)
               (remove (fn [p] (true? (pu/get-block-property-value p :logseq.property/exclude-from-graph-view)))))
             links (concat (seq relation)
-                          (seq tagged-pages))
+                          (seq tagged-pages)
+                          (seq namespaces))
             linked (set (flatten links))
             build-in-pages (->> (if (config/db-based-graph? repo) sqlite-create-graph/built-in-pages-names gp-db/built-in-pages-names)
                                 (map string/lower-case)
@@ -119,7 +125,7 @@
             page-links (reduce (fn [m [k v]] (-> (update m k inc)
                                                  (update v inc))) {} links)
             links (build-links (remove (fn [[_ to]] (nil? to)) links))
-            nodes (build-nodes dark? (string/lower-case current-page) page-links tags nodes)]
+            nodes (build-nodes dark? (string/lower-case current-page) page-links tags nodes namespaces)]
         (-> {:nodes (map #(assoc % :block/created-at (get-in full-pages-map [(:id %) :block/created-at])) nodes)
              :links links
              :page-name->original-name page-name->original-name}
@@ -141,7 +147,9 @@
             tags (remove #(= page %) tags)
             ref-pages (db/get-page-referenced-pages repo page-id)
             mentioned-pages (db/get-pages-that-mentioned-page repo page-id show-journal)
+            namespaces (map (fn [[x y]] [x (common-util/page-name-sanity-lc y)]) (db/get-all-namespace-relation repo))
             links (concat
+                   namespaces
                    (map (fn [[p _aliases]]
                           [page p]) ref-pages)
                    (map (fn [[p _aliases]]
@@ -177,7 +185,7 @@
                         tags)
                        (remove nil?)
                        (distinct))
-            nodes (build-nodes dark? page links (set tags) nodes)
+            nodes (build-nodes dark? page links (set tags) nodes namespaces)
             full-pages (db/get-all-pages repo)
             all-pages (map common-util/get-page-original-name full-pages)
             page-name->original-name (zipmap (map :block/name full-pages) all-pages)]
@@ -189,34 +197,38 @@
 (defn build-block-graph
   "Builds a citation/reference graph for a given block uuid."
   [block theme]
-  (let [dark? (= "dark" theme)
-        ref-blocks (db/get-block-referenced-blocks block)
-        other-blocks (->> (concat (map first ref-blocks))
-                          (remove nil?)
-                          (set))
-        other-blocks-links (mapcat
-                            (fn [block]
-                              (let [ref-blocks (-> (map first (db/get-block-referenced-blocks block))
-                                                   (set)
-                                                   (set/intersection other-blocks))]
-                                (concat
-                                 (map (fn [p] [block p]) ref-blocks))))
-                            other-blocks)
-        links (->> other-blocks-links
-                   (remove nil?)
-                   (distinct)
-                   (build-links))
-        nodes (->> (concat
-                    [block]
-                    (map first ref-blocks))
-                   (remove nil?)
-                   (distinct)
+  (when-let [repo (state/get-current-repo)]
+    (let [dark? (= "dark" theme)
+          ref-blocks (db/get-block-referenced-blocks block)
+          namespaces (map (fn [[x y]] [x (common-util/page-name-sanity-lc y)]) (db/get-all-namespace-relation repo))
+          other-blocks (->> (concat (map first ref-blocks))
+                            (remove nil?)
+                            (set))
+          other-blocks-links (mapcat
+                              (fn [block]
+                                (let [ref-blocks (-> (map first (db/get-block-referenced-blocks block))
+                                                     (set)
+                                                     (set/intersection other-blocks))]
+                                  (concat
+                                   (map (fn [p] [block p]) ref-blocks))))
+                              other-blocks)
+          links (concat
+                 (->> other-blocks-links
+                      (remove nil?)
+                      (distinct)
+                      (build-links))
+                 namespaces)
+          nodes (->> (concat
+                      [block]
+                      (map first ref-blocks))
+                     (remove nil?)
+                     (distinct)
                        ;; FIXME: get block tags
-                   )
-        nodes (build-nodes dark? block links #{} nodes)]
-    (normalize-page-name
-     {:nodes nodes
-      :links links})))
+                     )
+          nodes (build-nodes dark? block links #{} nodes namespaces)]
+      (normalize-page-name
+       {:nodes nodes
+        :links links}))))
 
 (defn n-hops
   "Get all nodes that are n hops from nodes (a collection of node ids)"

+ 1 - 0
src/main/frontend/handler/page.cljs

@@ -374,6 +374,7 @@
               create-f (fn []
                          (p/do!
                           (<create! title {:redirect? false
+                                           :split-namespace? false
                                            :create-first-block? (not template)
                                            :journal? true
                                            :today-journal? true})

+ 4 - 1
src/main/frontend/handler/query/builder.cljs

@@ -9,7 +9,10 @@
 
 (def operators [:and :or :not])
 (def operators-set (set operators))
+
+;; FIXME: remove namespace for db-based graphs
 (def page-filters ["all page tags"
+                   "namespace"
                    "tags"
                    "property"
                    "sample"])
@@ -154,7 +157,7 @@
               (last f))]
       (into [(symbol (first f))] [(second f) l]))
 
-    (and (vector? f) (contains? #{:page :tags} (keyword (first f))))
+    (and (vector? f) (contains? #{:page :tags :namespace} (keyword (first f))))
     (into [(symbol (first f))] (map ->page-ref (rest f)))
 
     :else f))

+ 35 - 7
src/main/frontend/worker/handler/page.cljs

@@ -11,7 +11,8 @@
             [logseq.common.util :as common-util]
             [logseq.common.config :as common-config]
             [logseq.db.frontend.content :as db-content]
-            [medley.core :as medley]))
+            [medley.core :as medley]
+            [frontend.worker.date :as date]))
 
 (defn properties-block
   [repo conn config date-formatter properties format page]
@@ -76,17 +77,44 @@
              persist-op?         true}
       :as options}]
   (let [date-formatter (common-config/get-date-formatter config)
+        split-namespace? (not (or (string/starts-with? title "hls__")
+                                  (date/valid-journal-title? date-formatter title)))
         [title page-name] (get-title-and-pagename title)
         with-uuid? (if (uuid? uuid) uuid true)
         [page-uuid result] (when (ldb/page-empty? @conn page-name)
-                             (let [format   (or format (common-config/get-preferred-format config))
-                                   page (-> (gp-block/page-name->map title with-uuid? @conn true date-formatter)
-                                            (assoc :block/format format))
-                                   page-uuid (:block/uuid page)
-                                   txs      (->> [page]
+                             (let [pages    (if split-namespace?
+                                              (common-util/split-namespace-pages title)
+                                              [title])
+                                   format   (or format (common-config/get-preferred-format config))
+                                   pages    (map (fn [page]
+                             ;; only apply uuid to the deepest hierarchy of page to create if provided.
+                                                   (-> (gp-block/page-name->map page (if (= page title) with-uuid? true) @conn true date-formatter)
+                                                       (assoc :block/format format)))
+                                                 pages)
+                                   txs      (->> pages
+                           ;; for namespace pages, only last page need properties
+                                                 drop-last
                                                  (mapcat #(build-page-tx repo conn config date-formatter format nil % {}))
                                                  (remove nil?))
-                                   page-txs (build-page-tx repo conn config date-formatter format properties page (select-keys options [:whiteboard? :class? :tags]))
+                                   txs      (map-indexed (fn [i page]
+                                                           (if (zero? i)
+                                                             page
+                                                             (assoc page :block/namespace
+                                                                    [:block/uuid (:block/uuid (nth txs (dec i)))])))
+                                                         txs)
+                                   txs      (map-indexed (fn [i page]
+                                                           (if (zero? i)
+                                                             page
+                                                             (assoc page :block/namespace
+                                                                    [:block/uuid (:block/uuid (nth txs (dec i)))])))
+                                                         txs)
+                                   page-uuid (:block/uuid (last pages))
+                                   page-txs (build-page-tx repo conn config date-formatter format properties (last pages) (select-keys options [:whiteboard? :class? :tags]))
+                                   page-txs (if (seq txs)
+                                              (update page-txs 0
+                                                      (fn [p]
+                                                        (assoc p :block/namespace [:block/uuid (:block/uuid (last txs))])))
+                                              page-txs)
                                    first-block-tx (when (and
                                                          create-first-block?
                                                          (not (or whiteboard? class?))

+ 2 - 2
src/main/frontend/worker/handler/page/rename.cljs → src/main/frontend/worker/handler/page/db_based/rename.cljs

@@ -1,5 +1,5 @@
-(ns frontend.worker.handler.page.rename
-  "Page rename"
+(ns frontend.worker.handler.page.db-based.rename
+  "DB based page rename"
   (:require [datascript.core :as d]
             [clojure.string :as string]
             [frontend.worker.file.util :as wfu]

+ 287 - 0
src/main/frontend/worker/handler/page/file_based/rename.cljs

@@ -0,0 +1,287 @@
+(ns frontend.worker.handler.page.file-based.rename
+  "File based page rename"
+  (:require [logseq.outliner.core :as outliner-core]
+            [logseq.outliner.tree :as otree]
+            [frontend.worker.handler.page :as worker-page]
+            [datascript.core :as d]
+            [medley.core :as medley]
+            [clojure.string :as string]
+            [logseq.common.util.page-ref :as page-ref]
+            [frontend.worker.file.util :as wfu]
+            [frontend.worker.file.page-rename :as page-rename]
+            [logseq.db.sqlite.util :as sqlite-util]
+            [logseq.db :as ldb]
+            [logseq.common.util :as common-util]
+            [logseq.graph-parser.text :as text]))
+
+(defn rename-update-namespace!
+  "update :block/namespace of the renamed block"
+  [repo conn config page old-original-name new-name]
+  (let [old-namespace? (text/namespace-page? old-original-name)
+        new-namespace? (text/namespace-page? new-name)]
+    (cond
+      new-namespace?
+      ;; update namespace
+      (let [namespace (first (common-util/split-last "/" new-name))]
+        (when namespace
+          (worker-page/create! repo conn config namespace) ;; create parent page if not exist, creation of namespace ref is handled in `create!`
+          (let [namespace-block (d/entity @conn [:block/name (common-util/page-name-sanity-lc namespace)])
+                page-txs [{:db/id (:db/id page)
+                           :block/namespace (:db/id namespace-block)}]]
+            (ldb/transact! conn page-txs {:persist-op? true}))))
+
+      old-namespace?
+      ;; retract namespace
+      (ldb/transact! conn [[:db/retract (:db/id page) :block/namespace]] {:persist-op? true})
+
+      :else
+      nil)))
+
+(defn- replace-page-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)
+                                             (common-util/remove-nils-non-nested))
+                             tx (merge
+                                 content-tx
+                                 (when (not= (seq properties) (seq properties'))
+                                   {:db/id (:db/id ref)
+                                    ;; FIXME: properties
+                                    :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- rename-update-block-refs!
+  [refs from-id to-id]
+  (->> refs
+       (remove #{{:db/id from-id}})
+       (cons {:db/id to-id})
+       (distinct)
+       (vec)))
+
+(declare rename-page-aux)
+
+(defn- based-merge-pages!
+  [repo conn config from-page-name to-page-name {:keys [old-name new-name]}]
+  (when (and (ldb/page-exists? @conn from-page-name)
+             (ldb/page-exists? @conn to-page-name)
+             (not= from-page-name to-page-name))
+    (let [db @conn
+          to-page (d/entity db [:block/name to-page-name])
+          to-id (:db/id to-page)
+          from-page (d/entity db [:block/name from-page-name])
+          from-id (:db/id from-page)
+          from-first-child (some->> (d/pull db '[*] from-id)
+                                    (outliner-core/block @conn)
+                                    (#(otree/-get-down % conn))
+                                    (outliner-core/get-data))
+          to-last-direct-child-id (ldb/get-block-last-direct-child-id db to-id)
+          db-based? (sqlite-util/db-based-graph? repo)
+          datoms (d/datoms @conn :avet :block/page from-id)
+          block-eids (mapv :e datoms)
+          blocks (d/pull-many db '[: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}
+                                    :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)
+          replace-ref-tx-data (if db-based?
+                                (replace-page-ref from-page to-page)
+                                (page-rename/replace-page-ref db config from-page-name to-page-name))
+          tx-data (concat blocks-tx-data replace-ref-tx-data)]
+
+      (rename-page-aux repo conn config old-name new-name
+                       :merge? true
+                       :other-tx tx-data)
+
+      (worker-page/delete! repo conn (:block/uuid from-page) {:rename? true}))))
+
+(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])]
+    (common-util/string-join-path parts)))
+
+(defn- update-file-tx
+  [db old-page-name new-page-name]
+  (let [page (d/entity db [:block/name old-page-name])
+        file (:block/file page)]
+    (when (and file (not (:block/journal? page)))
+      (let [old-path (:file/path file)
+            new-file-name (wfu/file-name-sanity new-page-name) ;; w/o file extension
+            new-path (compute-new-file-path old-path new-file-name)]
+        {:old-path old-path
+         :new-path new-path
+         :tx-data [{:db/id (:db/id file)
+                    :file/path new-path}]}))))
+
+(defn- rename-page-aux
+  "Only accepts unsanitized page names"
+  [repo conn config old-name new-name & {:keys [merge? other-tx]}]
+  (let [db                  @conn
+        old-page-name       (common-util/page-name-sanity-lc old-name)
+        new-page-name       (common-util/page-name-sanity-lc new-name)
+        db-based?           (sqlite-util/db-based-graph? repo)
+        page                (d/pull @conn '[*] [:block/name old-page-name])]
+    (when (and repo page)
+      (let [old-original-name   (:block/original-name page)
+            page-txs            (when-not merge?
+                                  [{:db/id               (:db/id page)
+                                    :block/uuid          (:block/uuid page)
+                                    :block/name          new-page-name
+                                    :block/original-name new-name}])
+            {:keys [old-path new-path tx-data]} (update-file-tx db old-page-name new-name)
+            txs (concat page-txs
+                        other-tx
+                        (when-not db-based?
+                          (->>
+                           (concat
+                            ;;  update page refes in block content when ref name changes
+                            (page-rename/replace-page-ref db config old-name new-name)
+                            ;; update file path
+                            tx-data)
+
+                           (remove nil?))))]
+
+        (ldb/transact! conn txs {:outliner-op :rename-page
+                                 :data (cond->
+                                        {:old-name old-name
+                                         :new-name new-name}
+                                         (and old-path new-path)
+                                         (merge {:old-path old-path
+                                                 :new-path new-path}))})
+
+        (rename-update-namespace! repo conn config page old-original-name new-name)))))
+
+(defn- rename-namespace-pages!
+  "Original names (unsanitized only)"
+  [repo conn config old-name new-name]
+  (let [pages (ldb/get-namespace-pages @conn old-name {:db-graph? (sqlite-util/db-based-graph? repo)})
+        page (d/pull @conn '[*] [:block/name (common-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 (common-util/replace-first-ignore-case old-page-title old-name new-name)]
+        (when (and old-page-title new-page-title)
+          (rename-page-aux repo conn config old-page-title new-page-title)
+          (println "Renamed " old-page-title " to " new-page-title))))))
+
+(defn- rename-nested-pages
+  "Unsanitized names only"
+  [repo conn config old-ns-name new-ns-name]
+  (let [nested-page-str (page-ref/->page-ref (common-util/page-name-sanity-lc old-ns-name))
+        ns-prefix-format-str (str page-ref/left-brackets "%s/")
+        ns-prefix       (common-util/format ns-prefix-format-str (common-util/page-name-sanity-lc old-ns-name))
+        nested-pages    (ldb/get-pages-by-name-partition @conn nested-page-str)
+        nested-pages-ns (ldb/get-pages-by-name-partition @conn 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)
+            (rename-page-aux repo conn config old-page-title new-page-title)
+            (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
+                              (common-util/format ns-prefix-format-str old-ns-name)
+                              (common-util/format ns-prefix-format-str new-ns-name))]
+          (when (and old-page-title new-page-title)
+            (rename-page-aux repo conn config old-page-title new-page-title)
+            (println "Renamed " old-page-title " to " new-page-title)))))))
+
+(defn rename!
+  [repo conn config page-uuid new-name & {:keys [persist-op?]
+                                         :or {persist-op? true}}]
+  (let [db @conn
+        page-e        (d/entity db [:block/uuid page-uuid])
+        old-name      (:block/original-name page-e)
+        new-name      (string/trim new-name)
+        old-page-name (common-util/page-name-sanity-lc old-name)
+        new-page-name (common-util/page-name-sanity-lc new-name)
+        new-page-e (d/entity db [:block/name new-page-name])
+        name-changed? (not= old-name new-name)]
+    (cond
+      (ldb/built-in? page-e)
+      :built-in-page
+
+      (string/blank? new-name)
+      :invalid-empty-name
+
+      (and page-e new-page-e
+           (or (contains? (:block/type page-e) "whiteboard")
+               (contains? (:block/type new-page-e) "whiteboard")))
+      :merge-whiteboard-pages
+
+      (and old-name new-name name-changed?)
+      (do
+        (cond
+          (= old-page-name new-page-name) ; case changed
+          (ldb/transact! conn
+                         [{:db/id (:db/id page-e)
+                           :block/original-name new-name}]
+                         {:persist-op? persist-op?
+                          :outliner-op :rename-page})
+
+          (and (not= old-page-name new-page-name)
+               (d/entity @conn [:block/name new-page-name])) ; merge page
+          (based-merge-pages! repo conn config old-page-name new-page-name {:old-name old-name
+                                                                            :new-name new-name
+                                                                            :persist-op? persist-op?})
+
+          :else                          ; rename
+          (rename-namespace-pages! repo conn config old-name new-name))
+        (rename-nested-pages repo conn config old-name new-name)))))

+ 1 - 1
src/main/frontend/worker/rtc/core.cljs

@@ -12,7 +12,7 @@
             [frontend.worker.async-util :include-macros true :refer [<? go-try]]
             [frontend.worker.db-metadata :as worker-db-metadata]
             [frontend.worker.handler.page :as worker-page]
-            [frontend.worker.handler.page.rename :as worker-page-rename]
+            [frontend.worker.handler.page.db-based.rename :as worker-page-rename]
             [frontend.worker.react :as worker-react]
             [frontend.worker.rtc.asset-sync :as asset-sync]
             [frontend.worker.rtc.const :as rtc-const]

+ 6 - 4
src/main/logseq/api.cljs

@@ -851,13 +851,15 @@
 
 (defn ^:export get_pages_from_namespace
   [ns]
-  (when-let [_repo (and ns (state/get-current-repo))]
-    (bean/->js nil)))
+  (when-let [repo (and ns (state/get-current-repo))]
+    (when-let [pages (db-model/get-namespace-pages repo ns)]
+      (bean/->js (sdk-utils/normalize-keyword-for-json pages)))))
 
 (defn ^:export get_pages_tree_from_namespace
   [ns]
-  (when-let [_repo (and ns (state/get-current-repo))]
-    (bean/->js nil)))
+  (when-let [repo (and ns (state/get-current-repo))]
+    (when-let [pages (db-model/get-namespace-hierarchy repo ns)]
+      (bean/->js (sdk-utils/normalize-keyword-for-json pages)))))
 
 (defn last-child-of-block
   [block]

+ 51 - 0
src/test/frontend/db/model_test.cljs

@@ -9,6 +9,32 @@
 (use-fixtures :each {:before test-helper/start-test-db!
                      :after test-helper/destroy-test-db!})
 
+(deftest get-namespace-pages
+  (load-test-files [{:file/path "pages/a.b.c.md"
+                     :file/content "foo"}
+                    {:file/path "pages/b.c.md"
+                     :file/content "bar"}
+                    {:file/path "pages/b.d.md"
+                     :file/content "baz"}])
+
+  (is (= ["a/b" "a/b/c"]
+         (map :block/name (model/get-namespace-pages test-helper/test-db "a"))))
+
+  (is (= ["b/c" "b/d"]
+         (map :block/name (model/get-namespace-pages test-helper/test-db "b")))))
+
+(deftest get-page-namespace-routes
+  (load-test-files [{:file/path "pages/a.b.c.md"
+                     :file/content "foo"}
+                    {:file/path "pages/b.c.md"
+                     :file/content "bar"}
+                    {:file/path "pages/b.d.md"
+                     :file/content "baz"}])
+
+  (is (= '()
+         (map :block/name (model/get-page-namespace-routes test-helper/test-db "b/c")))
+      "Empty if page exists"))
+
 (deftest test-page-alias-with-multiple-alias
   (load-test-files [{:file/path "aa.md"
                      :file/content "alias:: ab, ac"}
@@ -47,6 +73,31 @@
       2 (count a-ref-blocks)
       #{"ab" "ac"} (set alias-names))))
 
+(deftest remove-links-for-each-level-of-the-namespaces
+  (load-test-files [{:file/path "pages/generic page.md"
+                     :file/content "tags:: [[one/two/tree]], one/two
+- link to ns [[one]]
+- link to page one [[page ONE]]"}])
+
+  (is (= '("one/two/tree" "page one")
+         (map second (model/get-pages-relation test-helper/test-db true)))
+      "(get-pages-relation) Must be only ns one/two/tree")
+
+  (is (= '("one/two/tree" "page one")
+         (map second (#'model/remove-nested-namespaces-link [["generic page" "one/two/tree"]
+                                                           ["generic page" "one/two"]
+                                                           ["generic page" "one"]
+                                                           ["generic page" "page one"]])))
+      "(model/remove-nested-namespaces-link) Must be only ns one/two/tree")
+
+  (is (= '("one/two/tree" "one/two" "one")
+         (#'model/get-parents-namespace-list "one/two/tree/four"))
+      "Must be one/two/tree one/two one")
+
+  (is (= '("one/two" "one")
+         (#'model/get-unnecessary-namespaces-name '("one/two/tree" "one" "one/two" "non nested tag" "non nested link")))
+      "Must be  one/two one"))
+
 (deftest get-pages-that-mentioned-page-with-show-journal
   (load-test-files [{:file/path "journals/2020_08_15.md"
                      :file/content "link 1 to [[page ONE]] and link to [[generic page]]"}

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

@@ -2,7 +2,7 @@
   (:require [cljs.test :refer [deftest testing is]]
             [clojure.string :as string]
             [logseq.graph-parser.extract :as extract]
-            [frontend.worker.handler.page.rename :as worker-page-rename]
+            [frontend.worker.handler.page.file-based.rename :as worker-page-rename]
             [frontend.util.fs :as fs-util]
             [frontend.worker.file.util :as wfu]))
 

+ 15 - 0
src/test/frontend/db/query_dsl_test.cljs

@@ -621,3 +621,18 @@ created-at:: 1608968448116
          (not [?b :block/priority #{"A"}]
               [(contains? #{"A"} ?priority)]))]
    (frontend.db/get-db test-db))))
+
+(deftest namespace-queries
+  (load-test-files [{:file/path "pages/ns1.page1.md"
+                     :file/content "foo"}
+                    {:file/path "pages/ns1.page2.md"
+                     :file/content "bar"}
+                    {:file/path "pages/ns2.page1.md"
+                     :file/content "baz"}])
+
+  (is (= #{"ns1/page1" "ns1/page2"}
+         (set (map :block/name (dsl-query "(namespace ns1)")))))
+
+  (is (= #{}
+         (set (map :block/name (dsl-query "(namespace blarg)"))))
+      "Correctly returns no results"))

+ 47 - 42
src/test/frontend/worker/handler/page/rename_test.cljs

@@ -2,20 +2,23 @@
   (:require [clojure.test :refer [deftest is testing use-fixtures]]
             [frontend.test.helper :as test-helper]
             [datascript.core :as d]
+            [frontend.handler.page :as page-handler]
             [frontend.db :as db]
-            [frontend.worker.handler.page.rename :as worker-page-rename]))
+            [frontend.worker.db.fix :as db-fix]
+            [frontend.worker.handler.page.file-based.rename :as worker-page-rename]
+            [frontend.handler.editor :as editor-handler]))
 
 ;; FIXME: merge properties from both pages
 
-(def repo test-helper/test-db-name-db-version)
+(def repo test-helper/test-db-name)
 
 (def init-data (test-helper/initial-test-page-and-blocks))
 
-;; (def fbid (:block/uuid (second init-data)))
+(def fbid (:block/uuid (second init-data)))
 
 (defn start-and-destroy-db
   [f]
-  (test-helper/db-based-start-and-destroy-db
+  (test-helper/start-and-destroy-db
    f
    {:init-data (fn [conn] (d/transact! conn init-data))}))
 
@@ -35,43 +38,45 @@
       (page-rename (:block/uuid page) "New name")
       (is (= "New name" (:block/original-name (db/entity (:db/id page)))))))
 
-  ;; (testing "Merge existing page"
-  ;;   (page-handler/create! "Existing page" {:redirect? false :create-first-block? true})
-  ;;   (page-rename "New name" "Existing page")
-  ;;   (let [e1 (db/get-page "new name")
-  ;;         e2 (db/get-page "existing page")]
-  ;;     ;; Old page deleted
-  ;;     (is (nil? e1))
-  ;;     ;; Blocks from both pages have been merged
-  ;;     (is (= (count (:block/_page e2)) (+ 1 (dec (count init-data)))))
-  ;;     ;; Ensure there's no conflicts
-  ;;     (is (empty? (db-fix/get-conflicts (db/get-db) (:db/id e2))))))
-  )
+  (testing "Merge existing page"
+    (page-handler/create! "Existing page" {:redirect? false :create-first-block? true})
+    (let [page (db/get-page "new name")]
+      (page-rename (:block/uuid page) "Existing page"))
+    (let [e1 (db/get-page "new name")
+          e2 (db/get-page "existing page")]
+      ;; Old page deleted
+      (is (nil? e1))
+      ;; Blocks from both pages have been merged
+      (is (= (count (:block/_page e2)) (+ 1 (dec (count init-data)))))
+      ;; Ensure there's no conflicts
+      (is (empty? (db-fix/get-conflicts (db/get-db) (:db/id e2)))))))
 
-;; (deftest merge-with-empty-page
-;;   (page-handler/create! "Existing page" {:redirect? false :create-first-block? false})
-;;   (page-rename "Test" "Existing page")
-;;   (let [e1 (db/get-page "test")
-;;         e2 (db/get-page "existing page")]
-;;       ;; Old page deleted
-;;     (is (nil? e1))
-;;       ;; Blocks from both pages have been merged
-;;     (is (= (count (:block/_page e2)) (dec (count init-data))))
-;;       ;; Ensure there's no conflicts
-;;     (is (empty? (db-fix/get-conflicts (db/get-db) (:db/id e2))))))
+(deftest merge-with-empty-page
+  (page-handler/create! "Existing page" {:redirect? false :create-first-block? false})
+  (let [page (db/get-page "test")]
+    (page-rename (:block/uuid page) "Existing page"))
+  (let [e1 (db/get-page "test")
+        e2 (db/get-page "existing page")]
+      ;; Old page deleted
+    (is (nil? e1))
+      ;; Blocks from both pages have been merged
+    (is (= (count (:block/_page e2)) (dec (count init-data))))
+      ;; Ensure there's no conflicts
+    (is (empty? (db-fix/get-conflicts (db/get-db) (:db/id e2))))))
 
-;; (deftest merge-existing-pages-should-update-ref-ids
-;;   (testing "Merge existing page"
-;;     (editor-handler/save-block! repo fbid "Block 1 [[Test]]")
-;;     (page-handler/create! "Existing page" {:redirect? false :create-first-block? true})
-;;     (page-rename "Test" "Existing page")
-;;     (let [e1 (db/get-page "test")
-;;           e2 (db/get-page "existing page")]
-;;       ;; Old page deleted
-;;       (is (nil? e1))
-;;       ;; Blocks from both pages have been merged
-;;       (is (= (count (:block/_page e2)) (+ 1 (dec (count init-data)))))
-;;       ;; Ensure there's no conflicts
-;;       (is (empty? (db-fix/get-conflicts (db/get-db) (:db/id e2))))
-;;       ;; Content updated
-;;       (is (= "Block 1 [[Existing page]]" (:block/content (db/entity [:block/uuid fbid])))))))
+(deftest merge-existing-pages-should-update-ref-ids
+  (testing "Merge existing page"
+    (editor-handler/save-block! repo fbid "Block 1 [[Test]]")
+    (page-handler/create! "Existing page" {:redirect? false :create-first-block? true})
+    (let [page (db/get-page "test")]
+      (page-rename (:block/uuid page) "Existing page"))
+    (let [e1 (db/get-page "test")
+          e2 (db/get-page "existing page")]
+      ;; Old page deleted
+      (is (nil? e1))
+      ;; Blocks from both pages have been merged
+      (is (= (count (:block/_page e2)) (+ 1 (dec (count init-data)))))
+      ;; Ensure there's no conflicts
+      (is (empty? (db-fix/get-conflicts (db/get-db) (:db/id e2))))
+      ;; Content updated
+      (is (= "Block 1 [[Existing page]]" (:block/content (db/entity [:block/uuid fbid])))))))