Просмотр исходного кода

Merge pull request #11517 from logseq/feat/namespace

feat: support namespace pages and tags
Tienson Qin 1 год назад
Родитель
Сommit
582e3a9bbe

+ 32 - 4
deps/db/src/logseq/db/frontend/entity_plus.cljc

@@ -25,15 +25,40 @@
 
 (defn- get-block-title
   [^Entity e k default-value]
-  (let [db (.-db e)]
-    (if (and (db-based-graph? db) (= "journal" (:block/type e)))
+  (let [db (.-db e)
+        db-based? (db-based-graph? db)]
+    (if (and db-based? (= "journal" (:block/type e)))
       (get-journal-title db e)
       (or
        (get (.-kv e) k)
-       (let [result (lookup-entity e k default-value)]
+       (let [result (lookup-entity e k default-value)
+             parent-title? (:block.temp/parent-title? e)]
+         (or
+          (let [result' (if (string? result)
+                          (db-content/special-id-ref->page-ref result
+                                                               (:block/refs e))
+                          result)
+                parent (when (= (:block/type e) "page")
+                         (:logseq.property/parent e))]
+            (if (and db-based? parent parent-title?)
+              (str (:block/title parent) "/" result')
+              result'))
+          default-value))))))
+
+(defn- get-block-title-parent-refs
+  "Add parent to block ref titles"
+  [^Entity e k default-value]
+  (let [db (.-db e)
+        db-based? (db-based-graph? db)]
+    (if (and db-based? (= "journal" (:block/type e)))
+      (get-journal-title db e)
+      (or
+       (get (.-kv e) k)
+       (let [result (lookup-entity e :block/title default-value)]
          (or
           (if (string? result)
-            (db-content/special-id-ref->page-ref result (:block/refs e))
+            (db-content/special-id-ref->page-ref result
+                                                 (map (fn [e] (assoc e :block.temp/parent-title? true)) (:block/refs e)))
             result)
           default-value))))))
 
@@ -61,6 +86,9 @@
          :block/title
          (get-block-title e k default-value)
 
+         :block/title-with-refs-parent
+         (get-block-title-parent-refs e k default-value)
+
          :block/_parent
          (->> (lookup-entity e k default-value)
               (remove (fn [e] (or (:logseq.property/created-from-property e)

+ 2 - 1
deps/db/src/logseq/db/sqlite/util.cljs

@@ -115,7 +115,8 @@
    (cond-> (merge block
                   {:block/type "class"
                    :block/format :markdown})
-     (not= (:db/ident block) :logseq.class/Root)
+     (and (not= (:db/ident block) :logseq.class/Root)
+          (nil? (:logseq.property/parent block)))
      (assoc :logseq.property/parent :logseq.class/Root))))
 
 (defn build-new-page

+ 105 - 6
deps/outliner/src/logseq/outliner/core.cljs

@@ -21,7 +21,9 @@
             [logseq.outliner.batch-tx :include-macros true :as batch-tx]
             [logseq.db.frontend.order :as db-order]
             [logseq.outliner.pipeline :as outliner-pipeline]
-            [logseq.common.util.macro :as macro-util]))
+            [logseq.graph-parser.text :as text]
+            [logseq.common.util.macro :as macro-util]
+            [logseq.db.frontend.class :as db-class]))
 
 (def ^:private block-map
   (mu/optional-keys
@@ -246,10 +248,104 @@
          (map (fn [tag]
                 [:db/retract (:db/id block) :block/tags (:db/id tag)])))))
 
+(defn- get-page-by-parent-name
+  [db parent-title child-title]
+  (some->>
+   (d/q
+    '[:find [?b ...]
+      :in $ ?parent-name ?child-name
+      :where
+      [?b :logseq.property/parent ?p]
+      [?b :block/name ?child-name]
+      [?p :block/name ?parent-name]]
+    db
+     (common-util/page-name-sanity-lc parent-title)
+     (common-util/page-name-sanity-lc child-title))
+   first
+   (d/entity db)))
+
+(defn ^:api split-namespace-pages
+  [db page-or-pages tags date-formatter & {:keys [*changed-uuids]}]
+  (let [pages (if (map? page-or-pages) [page-or-pages] page-or-pages)
+        tags-set (set (map :block/uuid tags))]
+    (->>
+     (mapcat
+      (fn [{:block/keys [title] :as page}]
+        (let [block-uuid (:block/uuid page)
+              block-type (if (contains? tags-set (:block/uuid page))
+                           "class"
+                           (:block/type page))]
+          (if (and (contains? #{"page" "class"} block-type) (text/namespace-page? title))
+            (let [class? (= block-type "class")
+                  parts (->> (string/split title #"/")
+                             (map string/trim)
+                             (remove string/blank?))
+                  pages (doall
+                         (map-indexed
+                          (fn [idx part]
+                            (let [last-part? (= idx (dec (count parts)))
+                                  page (if (zero? idx)
+                                         (ldb/get-page db part)
+                                         (get-page-by-parent-name db (nth parts (dec idx)) part))
+                                  result (or page
+                                             (when last-part? (ldb/get-page db part))
+                                             (-> (gp-block/page-name->map part db true date-formatter
+                                                                          {:page-uuid (when last-part? block-uuid)})
+                                                 (assoc :block/format :markdown)))]
+                              (when (and last-part? (not= (:block/uuid result) block-uuid)
+                                         *changed-uuids)
+                                (swap! *changed-uuids assoc block-uuid (:block/uuid result)))
+                              result))
+                          parts))]
+              (map-indexed
+               (fn [idx page]
+                 (let [parent-eid (when (> idx 0)
+                                    (when-let [id (:block/uuid (nth pages (dec idx)))]
+                                      [:block/uuid id]))]
+                   (if class?
+                     (cond
+                       (and (de/entity? page) (ldb/class? page))
+                       page
+                       (de/entity? page) ; page exists but not a class, avoid converting here becuase this could be troublesome.
+                       nil
+                       (zero? idx)
+                       (db-class/build-new-class db page)
+                       :else
+                       (db-class/build-new-class db (assoc page :logseq.property/parent parent-eid)))
+                     (if (or (de/entity? page) (zero? idx))
+                       page
+                       (assoc page :logseq.property/parent parent-eid)))))
+               pages))
+            [page])))
+      pages)
+     (remove nil?))))
+
+(defn- build-page-parents
+  [db m date-formatter raw-title]
+  (let [refs (:block/refs m)
+        tags (:block/tags m)
+        *changed-uuids (atom {})
+        refs' (split-namespace-pages db refs tags date-formatter
+                                     {:*changed-uuids *changed-uuids})
+        tags' (map (fn [tag]
+                     (or (first (filter (fn [ref] (= (:block/uuid ref) (:block/uuid tag))) refs')) tag))
+                   tags)
+        raw-title' (if (seq @*changed-uuids)
+                     (reduce
+                      (fn [raw-content [old-id new-id]]
+                        (string/replace raw-content (str old-id) (str new-id)))
+                      raw-title
+                      @*changed-uuids)
+                     raw-title)]
+    (assoc m
+           :block/refs refs'
+           :block/title raw-title'
+           :block/tags tags')))
+
 (extend-type Entity
   otree/INode
-  (-save [this txs-state conn repo _date-formatter {:keys [retract-attributes? retract-attributes]
-                                                    :or {retract-attributes? true}}]
+  (-save [this txs-state conn repo date-formatter {:keys [retract-attributes? retract-attributes]
+                                                   :or {retract-attributes? true}}]
     (assert (ds/outliner-txs-state? txs-state)
             "db should be satisfied outliner-tx-state?")
     (let [data this
@@ -261,7 +357,7 @@
                   db-based?
                   (dissoc :block/properties))
           m* (-> data'
-                 (dissoc :block/children :block/meta :block.temp/top? :block.temp/bottom? :block/unordered
+                 (dissoc :block/children :block/meta :block.temp/top? :block.temp/bottom? :block/unordered :block.temp/parent-title?
                          :block.temp/ast-title :block.temp/ast-body :block/level :block.temp/fully-loaded?)
                  common-util/remove-nils
                  block-with-updated-at
@@ -283,14 +379,17 @@
                  (assoc m* :block/name page-name))
                m*)
           _ (when (and db-based?
-                      ;; page or object changed?
+                       ;; page or object changed?
                        (and (or (ldb/page? block-entity) (ldb/object? block-entity))
                             (:block/title m*)
                             (not= (:block/title m*) (:block/title block-entity))))
               (outliner-validate/validate-block-title db (:block/title m*) block-entity))
           m (cond-> m*
               db-based?
-              (dissoc :block/pre-block? :block/priority :block/marker :block/properties-order))]
+              (dissoc :block/pre-block? :block/priority :block/marker :block/properties-order))
+          m (if db-based?
+              (build-page-parents db m date-formatter (:block/title m))
+              m)]
       ;; Ensure block UUID never changes
       (let [e (d/entity db db-id)]
         (when (and e block-uuid)

+ 2 - 1
scripts/src/logseq/tasks/dev/db_and_file_graphs.clj

@@ -98,7 +98,8 @@
                               ;; anything org mode
                               "org"
                               "pre-block"
-                              "namespace"
+                              ;; TODO: rename split-namespace?
+                              ;; "namespace"
                               "db/get-page"
                               "/page-name-sanity-lc"]))
         res (apply shell {:out :string :continue true}

+ 56 - 48
src/main/frontend/components/block.cljs

@@ -553,8 +553,10 @@
 
    All page-names are sanitized except page-name-in-block"
   [state
-   {:keys [contents-page? whiteboard-page? html-export? meta-click? show-unique-title? stop-click-event?]
-    :or {stop-click-event? true}
+   {:keys [contents-page? whiteboard-page? html-export? meta-click? show-unique-title? stop-click-event?
+           display-parent?]
+    :or {stop-click-event? true
+         display-parent? true}
     :as config}
    page-entity children label]
   (let [*hover? (::hover? state)
@@ -610,55 +612,61 @@
             (let [{:keys [content children]} (last child)
                   page-name (subs content 2 (- (count content) 2))]
               (rum/with-key (page-reference html-export? page-name (assoc config :children children) nil) page-name))))
-        (cond
-          (and label
-               (string? label)
-               (not (string/blank? label)))                    ; alias
-          label
+        (let [page-component (cond
+                               (and label
+                                    (string? label)
+                                    (not (string/blank? label)))                    ; alias
+                               label
 
-          (coll? label)
-          (->elem :span (map-inline config label))
+                               (coll? label)
+                               (->elem :span (map-inline config label))
 
-          show-unique-title?
-          (title/block-unique-title page-entity)
+                               show-unique-title?
+                               (title/block-unique-title page-entity)
 
-          :else
-          (let [title (:block/title page-entity)
-                s (cond untitled?
-                        (t :untitled)
+                               :else
+                               (let [title (:block/title page-entity)
+                                     s (cond untitled?
+                                             (t :untitled)
 
                         ;; The page-name-in-block generated by the auto-complete is not page-name-sanitized
-                        (pdf-utils/hls-file? page-name)
-                        (pdf-utils/fix-local-asset-pagename page-name)
-
-                        (not= (util/safe-page-name-sanity-lc title) page-name)
-                        page-name                  ;; page-name-in-block might be overridden (legacy))
-
-                        title
-                        (util/trim-safe title)
-
-                        :else
-                        (util/trim-safe page-name))
-                _ (when-not page-entity (js/console.warn "page-inner's page-entity is nil, given page-name: " page-name))
-                s (cond
-                    (not (string? s))
-                    (do
-                      (prn :debug :unknown-title-error :title s
-                           :data (db/pull (:db/id page-entity)))
-                      (db/transact! [{:db/id (:db/id page-entity)
-                                      :block/title "FIX unknown page"
-                                      :block/name "fix unknown page"}])
-                      "Unknown title")
-                    (re-find db-content/special-id-ref-pattern s)
-                    (db-content/special-id-ref->page s (:block/refs page-entity))
-                    :else
-                    s)
-                s (if tag? (str "#" s) s)]
-            (if (ldb/page? page-entity)
-              s
-              (let [inline-list (gp-mldoc/inline->edn (first (string/split-lines s))
-                                                      (mldoc/get-default-config (get page-entity :block/format :markdown)))]
-                (->elem :span (map-inline config inline-list)))))))]]))
+                                             (pdf-utils/hls-file? page-name)
+                                             (pdf-utils/fix-local-asset-pagename page-name)
+
+                                             (not= (util/safe-page-name-sanity-lc title) page-name)
+                                             page-name                  ;; page-name-in-block might be overridden (legacy))
+
+                                             title
+                                             (util/trim-safe title)
+
+                                             :else
+                                             (util/trim-safe page-name))
+                                     _ (when-not page-entity (js/console.warn "page-inner's page-entity is nil, given page-name: " page-name))
+                                     s (cond
+                                         (not (string? s))
+                                         (do
+                                           (prn :debug :unknown-title-error :title s
+                                                :data (db/pull (:db/id page-entity)))
+                                           (db/transact! [{:db/id (:db/id page-entity)
+                                                           :block/title "FIX unknown page"
+                                                           :block/name "fix unknown page"}])
+                                           "Unknown title")
+                                         (re-find db-content/special-id-ref-pattern s)
+                                         (db-content/special-id-ref->page s (:block/refs page-entity))
+                                         :else
+                                         s)
+                                     s (if tag? (str "#" s) s)]
+                                 (if (ldb/page? page-entity)
+                                   s
+                                   (let [inline-list (gp-mldoc/inline->edn (first (string/split-lines s))
+                                                                           (mldoc/get-default-config (get page-entity :block/format :markdown)))]
+                                     (->elem :span (map-inline config inline-list))))))]
+          (let [parent (:logseq.property/parent page-entity)]
+            (if (and display-parent? parent (not (ldb/class? page-entity)))
+              [:span
+               (str (:block/title parent) "/")
+               page-component]
+              page-component))))]]))
 
 (rum/defc popup-preview-impl
   [children {:keys [*timer *timer1 visible? set-visible! render *el-popup]}]
@@ -2250,7 +2258,7 @@
        {:on-pointer-down (fn [e]
                            (util/stop e)
                            (state/clear-editor-action!)
-                           (editor-handler/escape-editing false)
+                           (editor-handler/escape-editing)
                            (reset! *show-datapicker? true)
                            (reset! commands/*current-command typ)
                            (state/set-timestamp-block! {:block block
@@ -2329,7 +2337,7 @@
                                                   util/caret-range)
                              {:block/keys [title format]} block
                              content (if (config/db-based-graph? (state/get-current-repo))
-                                       (or (:block/title block) title)
+                                       (:block/title-with-refs-parent block)
                                        (->> title
                                             (property-file/remove-built-in-properties-when-file-based
                                              (state/get-current-repo) format)

+ 2 - 1
src/main/frontend/components/class.cljs

@@ -13,7 +13,8 @@
     (when (seq children)
       [:ul
        (for [child (sort-by :block/title children)]
-         (let [title [:li.ml-2 (block/page-reference false (:block/title child) {:show-brackets? false} nil)]]
+         (let [title [:li.ml-2 (block/page-reference false (:block/title child) {:show-brackets? false
+                                                                                 :display-parent? false} nil)]]
            (if (seq (:logseq.property/_parent child))
              (ui/foldable
               title

+ 13 - 14
src/main/frontend/components/cmdk/core.cljs

@@ -106,7 +106,7 @@
                             (take 5 items))))
         node-exists? (let [blocks-result (keep :source-block (get-in results [:nodes :items]))]
                        (when-not (string/blank? input)
-                         (or (db/get-page (string/trim input))
+                         (or (db/get-page (string/trim (last (string/split input "/"))))
                              (some (fn [block]
                                      (and
                                       (:block/tags block)
@@ -223,7 +223,7 @@
                 new-result)))]))
 
 (defn- page-item
-  [repo page]
+  [repo page q]
   (let [entity (db/entity [:block/uuid (:block/uuid page)])
         source-page (model/get-alias-source-page repo (:db/id entity))
         icon (cond
@@ -235,7 +235,7 @@
                "whiteboard"
                :else
                "page")
-        title (title/block-unique-title page)
+        title (highlight-content-query (title/block-unique-title page) q)
         title' (if source-page (str title " -> alias: " (:block/title source-page)) title)]
     (hash-map :icon icon
               :icon-theme :gray
@@ -269,7 +269,7 @@
             blocks (remove nil? blocks)
             items (keep (fn [block]
                           (if (:page? block)
-                            (page-item repo block)
+                            (page-item repo block !input)
                             (block-item repo block current-page !input))) blocks)]
       (if (= group :current-page)
         (let [items-on-current-page (filter :current-page? items)]
@@ -492,17 +492,16 @@
         create-whiteboard? (= :whiteboard (:source-create item))
         create-page? (= :page (:source-create item))
         class (when create-class? (get-class-from-input @!input))]
-    (p/do!
-      (cond
-        create-class?
-        (db-page-handler/<create-class! class
-                                        {:redirect? false
-                                         :create-first-block? false})
-        create-whiteboard? (whiteboard-handler/<create-new-whiteboard-and-redirect! @!input)
-        create-page? (page-handler/<create! @!input {:redirect? true}))
+    (p/let [result (cond
+                     create-class?
+                     (db-page-handler/<create-class! class
+                                                     {:redirect? false
+                                                      :create-first-block? false})
+                     create-whiteboard? (whiteboard-handler/<create-new-whiteboard-and-redirect! @!input)
+                     create-page? (page-handler/<create! @!input {:redirect? true}))]
       (state/close-modal!)
-      (when create-class?
-        (state/pub-event! [:class/configure (db/get-case-page class)])))))
+      (when (and create-class? result)
+        (state/pub-event! [:class/configure result])))))
 
 (defn- get-filter-user-input
   [input]

+ 5 - 1
src/main/frontend/components/container.cljs

@@ -141,7 +141,11 @@
         (not (db/page? page))
         (block/inline-text :markdown (:block/title page))
         untitled? (t :untitled)
-        :else (pdf-utils/fix-local-asset-pagename title))]
+        :else (let [title' (pdf-utils/fix-local-asset-pagename title)
+                    parent (:logseq.property/parent page)]
+                (if (and parent (not (ldb/class? page)))
+                  (str (:block/title parent) "/" title')
+                  title')))]
 
      ;; dots trigger
      (shui/button

+ 1 - 1
src/main/frontend/components/editor.cljs

@@ -731,7 +731,7 @@
         (when-let [container (gdom/getElement "app-container")]
           (dom/remove-class! container "blocks-selection-mode"))
         (p/do!
-         (editor-handler/escape-editing select?)
+         (editor-handler/escape-editing {:select? select?})
          (some-> config :on-escape-editing
                  (apply [(str uuid) (= type :esc)])))))))
 

+ 24 - 21
src/main/frontend/components/property/value.cljs

@@ -712,6 +712,7 @@
   [property type value {:keys [page-cp inline-text other-position? _icon?] :as opts}]
   (let [closed-values? (seq (:property/closed-values property))
         tag? (or (:tag? opts) (= (:db/ident property) :block/tags))
+        parent? (= (:db/ident property) :logseq.property/parent)
         inline-text-cp (fn [content]
                          [:div.flex.flex-row.items-center
                           (inline-text {} :markdown (macro-util/expand-value-if-macro content (state/get-macros)))
@@ -731,10 +732,11 @@
                 ;; support this case and maybe other complex cases.
                 (not (string/includes? (:block/title value) "[["))))
        (when value
-          (rum/with-key
-            (page-cp {:disable-preview? true
-                      :tag? tag?
-                      :meta-click? other-position?} value)
+         (rum/with-key
+           (page-cp {:disable-preview? true
+                     :tag? tag?
+                     :meta-click? other-position?
+                     :display-parent? (not parent?)} value)
            (:db/id value)))
 
        (contains? #{:node :class :property :page} type)
@@ -880,21 +882,21 @@
         *el (rum/use-ref nil)
         items (if (de/entity? v) #{v} v)]
     (rum/use-effect!
-      (fn []
-        (when editing?
-          (.click (rum/deref *el))))
-      [editing?])
+     (fn []
+       (when editing?
+         (.click (rum/deref *el))))
+     [editing?])
     (let [select-cp (fn [select-opts]
                       (let [select-opts (merge {:multiple-choices? true
                                                 :on-chosen (fn []
                                                              (when on-chosen (on-chosen)))}
-                                          select-opts
-                                          {:dropdown? false})]
+                                               select-opts
+                                               {:dropdown? false})]
                         [:div.property-select
                          (if (contains? #{:node :page :class :property} type)
                            (property-value-select-node block property
-                             select-opts
-                             opts)
+                                                       select-opts
+                                                       opts)
                            (select block property select-opts opts))]))]
       (let [toggle-fn shui/popup-hide!
             content-fn (fn [{:keys [_id content-props]}]
@@ -906,22 +908,23 @@
                       (let [target (.-target e)]
                         (when-not (or (util/link? target) (.closest target "a") config/publishing?)
                           (shui/popup-show! (rum/deref *el) content-fn
-                            {:as-dropdown? true :as-content? false
-                             :align "start" :auto-focus? true}))))
+                                            {:as-dropdown? true :as-content? false
+                                             :align "start" :auto-focus? true}))))
           :on-key-down (fn [^js e]
                          (case (.-key e)
                            (" " "Enter")
                            (do (some-> (rum/deref *el) (.click))
-                             (util/stop e))
+                               (util/stop e))
                            :dune))
           :class "flex flex-1 flex-row items-center flex-wrap gap-x-2 gap-y-2 pr-4"}
          (let [not-empty-value? (not= (map :db/ident items) [:logseq.property/empty-placeholder])]
            (if (and (seq items) not-empty-value?)
              (concat
-               (for [item items]
-                 (rum/with-key (select-item property type item opts) (or (:block/uuid item) (str item))))
-               (when date?
-                 [(property-value-date-picker block property nil {:toggle-fn toggle-fn})]))
+              (->> (for [item items]
+                     (rum/with-key (select-item property type item opts) (or (:block/uuid item) (str item))))
+                   (interpose [:span.opacity-50.-ml-2 ","]))
+              (when date?
+                [(property-value-date-picker block property nil {:toggle-fn toggle-fn})]))
              (if date?
                (property-value-date-picker block property nil {:toggle-fn toggle-fn})
                (property-empty-text-value))))]))))
@@ -979,12 +982,12 @@
                      (multiple-values block property opts schema)
 
                      :else
-                     (let [value-cp (property-scalar-value block property v
+                     (let [parent? (= (:db/ident property) :logseq.property/parent)
+                           value-cp (property-scalar-value block property v
                                       (merge
                                         opts
                                         {:editor-id editor-id
                                          :dom-id dom-id}))
-                           parent? (= (:db/ident property) :logseq.property/parent)
                            page-ancestors (when parent?
                                             (let [ancestor-pages (loop [parents [block]]
                                                                    (if-let [parent (:logseq.property/parent (last parents))]

+ 2 - 2
src/main/frontend/handler/block.cljs

@@ -200,8 +200,8 @@
              db-graph? (config/db-based-graph? repo)
              block (or (db/entity [:block/uuid block-id]) block)
              content (if (and db-graph? (:block/name block))
-                       (:block/title block)
-                       (or custom-content (:block/title block) ""))
+                       (:block/title-with-refs-parent block)
+                       (or custom-content (:block/title-with-refs-parent block) ""))
              content-length (count content)
              text-range (cond
                           (vector? pos)

+ 5 - 1
src/main/frontend/handler/common/page.cljs

@@ -62,7 +62,10 @@
          (notification/show! "Page name can't include \"#\"." :warning)
          (when-not (string/blank? title')
            (p/let [options' (if db-based?
-                              (update options :tags concat (:block/tags parsed-result))
+                              (cond->
+                               (update options :tags concat (:block/tags parsed-result))
+                                (nil? (:split-namespace? options))
+                                (assoc :split-namespace? true))
                               options)
                    result (ui-outliner-tx/transact!
                            {:outliner-op :create-page}
@@ -75,6 +78,7 @@
                  (block-handler/edit-block! first-block :max {:container-id :unknown-container}))
                page))))))))
 
+
 ;; favorite fns
 ;; ============
 (defn file-favorited?

+ 12 - 13
src/main/frontend/handler/editor.cljs

@@ -290,9 +290,9 @@
          block-id (when (and (not (config/db-based-graph? repo)) (map? properties))
                     (get properties :id))
          content (if (config/db-based-graph? repo)
-                   title
+                   (:block/title-with-refs-parent (db/entity (:db/id block)))
                    (-> (property-file/remove-built-in-properties-when-file-based repo format title)
-                     (drawer/remove-logbook)))]
+                       (drawer/remove-logbook)))]
      (cond
        (another-block-with-same-id-exists? uuid block-id)
        (notification/show!
@@ -3747,17 +3747,16 @@
             (select-all-blocks! {})))))))
 
 (defn escape-editing
-  ([]
-   (escape-editing true))
-  ([select?]
-   (let [edit-block (state/get-edit-block)]
-     (p/do!
-      (save-current-block!)
-      (if select?
-        (when-let [node (some-> (state/get-input) (util/rec-get-node "ls-block"))]
-          (state/exit-editing-and-set-selected-blocks! [node]))
-        (when (= (:db/id edit-block) (:db/id (state/get-edit-block)))
-          (state/clear-edit!)))))))
+  [& {:keys [select? save-block?]
+      :or {save-block? true}}]
+  (let [edit-block (state/get-edit-block)]
+    (p/do!
+     (when save-block? (save-current-block!))
+     (if select?
+       (when-let [node (some-> (state/get-input) (util/rec-get-node "ls-block"))]
+         (state/exit-editing-and-set-selected-blocks! [node]))
+       (when (= (:db/id edit-block) (:db/id (state/get-edit-block)))
+         (state/clear-edit!))))))
 
 (defn replace-block-reference-with-content-at-point
   []

+ 8 - 2
src/main/frontend/handler/page.cljs

@@ -371,8 +371,14 @@
                      (when-not (de/entity? chosen-result)
                        (<create! chosen'
                                  {:redirect? false
-                                  :create-first-block? false})))
-            ref-text' (if result (page-ref/->page-ref (:block/title result)) ref-text)]
+                                  :create-first-block? false
+                                  :split-namespace? true})))
+            ref-text' (if result
+                        (let [title (if-let [parent (:logseq.property/parent result)]
+                                      (str (:block/title parent) "/" (:block/title result))
+                                      (:block/title result))]
+                          (page-ref/->page-ref title))
+                        ref-text)]
       (p/do!
        (editor-handler/insert-command! id
                                        ref-text'

+ 2 - 2
src/main/frontend/modules/shortcut/config.cljs

@@ -32,7 +32,7 @@
 
 (defn- search
   [mode]
-  (editor-handler/escape-editing true)
+  (editor-handler/escape-editing {:select? true})
   (if (state/get-search-mode)
     (js/setTimeout #(route-handler/go-to-search! mode) 128)
     (route-handler/go-to-search! mode)))
@@ -462,7 +462,7 @@
    :command/run                             {:binding  "mod+shift+1"
                                              :inactive (not (util/electron?))
                                              :fn       #(do
-                                                          (editor-handler/escape-editing true)
+                                                          (editor-handler/escape-editing {:select? true})
                                                           (state/pub-event! [:command/run]))}
 
    :go/home                                 {:binding "g h"

+ 30 - 22
src/main/frontend/worker/handler/page/db_based/page.cljs

@@ -10,7 +10,8 @@
             [logseq.db.frontend.order :as db-order]
             [logseq.db.frontend.property.util :as db-property-util]
             [logseq.db.frontend.property.build :as db-property-build]
-            [logseq.db.frontend.class :as db-class]))
+            [logseq.db.frontend.class :as db-class]
+            [logseq.outliner.core :as outliner-core]))
 
 (defn- build-page-tx [conn properties page {:keys [whiteboard? class? tags]}]
   (when (:block/uuid page)
@@ -69,13 +70,14 @@
 
 (defn create!
   [conn title*
-   {:keys [create-first-block? properties uuid persist-op? whiteboard? class? today-journal?]
+   {:keys [create-first-block? properties uuid persist-op? whiteboard? class? today-journal? split-namespace?]
     :or   {create-first-block?      true
            properties               nil
            uuid                     nil
            persist-op?              true}
     :as options}]
-  (let [date-formatter (:logseq.property.journal/title-format (d/entity @conn :logseq.class/Journal))
+  (let [db @conn
+        date-formatter (:logseq.property.journal/title-format (d/entity db :logseq.class/Journal))
         title (sanitize-title title*)
         type (cond class?
                    "class"
@@ -85,28 +87,34 @@
                    "journal"
                    :else
                    "page")]
-    (when-not (ldb/page-exists? @conn title type)
+    (when-not (ldb/page-exists? db title type)
       (let [format    :markdown
             page      (-> (gp-block/page-name->map title @conn true date-formatter
                                                    {:class? class?
                                                     :page-uuid (when (uuid? uuid) uuid)
                                                     :skip-existing-page-check? true})
                           (assoc :block/format format))
-            page-uuid (:block/uuid page)
-            page-txs  (build-page-tx conn properties page (select-keys options [:whiteboard? :class? :tags]))
-            first-block-tx (when (and
-                                  (nil? (d/entity @conn [:block/uuid page-uuid]))
-                                  create-first-block?
-                                  (not (or whiteboard? class?))
-                                  page-txs)
-                             (build-first-block-tx (:block/uuid (first page-txs)) format))
-            txs      (concat
-                      page-txs
-                      first-block-tx)]
-        (when (seq txs)
-          (ldb/transact! conn txs (cond-> {:persist-op? persist-op?
-                                           :outliner-op :create-page}
-                                    today-journal?
-                                    (assoc :create-today-journal? true
-                                           :today-journal-name title))))
-        [title page-uuid]))))
+            [page parents] (if (and (text/namespace-page? title) split-namespace?)
+                             (let [pages (outliner-core/split-namespace-pages db page nil date-formatter)]
+                               [(last pages) (butlast pages)])
+                             [page nil])]
+        (when page
+          (let [page-uuid (:block/uuid page)
+                page-txs  (build-page-tx conn properties page (select-keys options [:whiteboard? :class? :tags]))
+                first-block-tx (when (and
+                                      (nil? (d/entity @conn [:block/uuid page-uuid]))
+                                      create-first-block?
+                                      (not (or whiteboard? class?))
+                                      page-txs)
+                                 (build-first-block-tx (:block/uuid (first page-txs)) format))
+                txs      (concat
+                          parents
+                          page-txs
+                          first-block-tx)]
+            (when (seq txs)
+              (ldb/transact! conn txs (cond-> {:persist-op? persist-op?
+                                               :outliner-op :create-page}
+                                        today-journal?
+                                        (assoc :create-today-journal? true
+                                               :today-journal-name title))))
+            [title page-uuid]))))))

+ 47 - 31
src/main/frontend/worker/search.cljs

@@ -10,7 +10,8 @@
             [logseq.db.sqlite.util :as sqlite-util]
             [logseq.common.util :as common-util]
             [logseq.db :as ldb]
-            [clojure.set :as set]))
+            [clojure.set :as set]
+            [logseq.graph-parser.text :as text]))
 
 ;; TODO: use sqlite for fuzzy search
 ;; maybe https://github.com/nalgeon/sqlean/blob/main/docs/fuzzy.md?
@@ -126,16 +127,37 @@ DROP TRIGGER IF EXISTS blocks_au;
                   (snippet-by snippet max-snippet-length))]
     snippet))
 
+(defn- get-match-input
+  [q]
+  (let [match-input (-> q
+                        (string/replace " and " " AND ")
+                        (string/replace " & " " AND ")
+                        (string/replace " or " " OR ")
+                        (string/replace " | " " OR ")
+                        (string/replace " not " " NOT "))]
+    (if (not= q match-input)
+      (string/replace match-input "," "")
+      (str "\"" match-input "\"*"))))
+
 (defn- search-blocks-aux
-  [db sql input page limit enable-snippet?]
+  [db sql q input page limit enable-snippet?]
   (try
-    (p/let [result (if page
-                     (.exec db #js {:sql sql
-                                    :bind #js [page input limit]
-                                    :rowMode "array"})
-                     (.exec db #js {:sql sql
-                                    :bind #js [input limit]
-                                    :rowMode "array"}))
+    (p/let [namespace? (text/namespace-page? q)
+            last-part (when namespace?
+                        (get-match-input (last (string/split q "/"))))
+            bind (cond
+                   (and namespace? page)
+                   [page input last-part limit]
+                   page
+                   [page input limit]
+                   namespace?
+                   [input last-part limit]
+                   :else
+                   [input limit])
+            result (.exec db (bean/->js
+                              {:sql sql
+                               :bind bind
+                               :rowMode "array"}))
             blocks (bean/->clj result)]
       (map (fn [block]
              (let [[id page title snippet] (if enable-snippet?
@@ -149,18 +171,6 @@ DROP TRIGGER IF EXISTS blocks_au;
       (prn :debug "Search blocks failed: ")
       (js/console.error e))))
 
-(defn- get-match-input
-  [q]
-  (let [match-input (-> q
-                        (string/replace " and " " AND ")
-                        (string/replace " & " " AND ")
-                        (string/replace " or " " OR ")
-                        (string/replace " | " " OR ")
-                        (string/replace " not " " NOT "))]
-    (if (not= q match-input)
-      (string/replace match-input "," "")
-      (str "\"" match-input "\"*"))))
-
 (defn exact-matched?
   "Check if two strings points toward same search result"
   [q match]
@@ -209,10 +219,14 @@ DROP TRIGGER IF EXISTS blocks_au;
       ;; (let [content (if (and db-based? (seq (:block/properties block)))
       ;;                 (str content (when (not= content "") "\n") (get-db-properties-str db properties))
       ;;                 content)])
-    (when uuid
-      {:id (str uuid)
-       :page (str (or (:block/uuid page) uuid))
-       :title (if (page-or-object? block) title (sanitize title))})))
+    (let [parent (:logseq.property/parent block)
+          title (if (and parent (= "page" (:block/type block)))
+                  (str (:block/title parent) "/" title)
+                  title)]
+      (when uuid
+        {:id (str uuid)
+         :page (str (or (:block/uuid page) uuid))
+         :title (if (page-or-object? block) title (sanitize title))}))))
 
 (defn build-fuzzy-search-indice
   "Build a block title indice from scratch.
@@ -271,10 +285,10 @@ DROP TRIGGER IF EXISTS blocks_au;
                      (str "select id, page, title, " snippet-aux " from blocks_fts where ")
                      "select id, page, title from blocks_fts where ")
             pg-sql (if page "page = ? and" "")
-            match-sql (str select
-                           pg-sql
-                           " title match ? order by rank limit ?")
-            matched-result (search-blocks-aux search-db match-sql match-input page limit enable-snippet?)
+            match-sql (if (text/namespace-page? q)
+                        (str select pg-sql " title match ? or title match ? order by rank limit ?")
+                        (str select pg-sql " title match ? order by rank limit ?"))
+            matched-result (search-blocks-aux search-db match-sql q match-input page limit enable-snippet?)
             fuzzy-result (when-not page (fuzzy-search repo @conn q option))]
       (let [result (->> (concat fuzzy-result matched-result)
                         (common-util/distinct-by :id)
@@ -286,8 +300,10 @@ DROP TRIGGER IF EXISTS blocks_au;
                                             true
                                             (if built-in?
                                               (or (not (ldb/built-in? block))
-                                                  (not (ldb/private-built-in-page? block)))
-                                              (not (ldb/built-in? block))))
+                                                  (not (ldb/private-built-in-page? block))
+                                                  (ldb/class? block))
+                                              (or (not (ldb/built-in? block))
+                                                  (ldb/class? block))))
                                       {:db/id (:db/id block)
                                        :block/uuid block-id
                                        :block/title (or snippet title)

+ 1 - 1
src/main/logseq/api.cljs

@@ -543,7 +543,7 @@
 
 (def ^:export exit_editing_mode
   (fn [select?]
-    (editor-handler/escape-editing select?)
+    (editor-handler/escape-editing {:select? select?})
     nil))
 
 (def ^:export insert_at_editing_cursor