Browse Source

enhance(ux): set/unset #Page to convert between page and block (#11970)

* enhance(ux): add/remove #Page to toggle page/block

* enhance(ux): "p t" to set tags for selected blocks

Updated "p a" to toggle displaying all properties including hidden ones.

* feat: cmd+k move blocks

* fix: block could be converted to page and create invalid nodes

Pages with block namespace parents is nonsensical and pages in
property values led to multiple validation errors. Also guard
against class and property pages as namespace parents

* fix: warn on failed cut+paste or indent/outdent of page blocks. Better to warn than silent failure which leaves user confused on
what happened. 

---------

Co-authored-by: Gabriel Horner <[email protected]>
Tienson Qin 2 months ago
parent
commit
d84d3f9652
39 changed files with 951 additions and 436 deletions
  1. 8 1
      clj-e2e/dev/user.clj
  2. 4 3
      clj-e2e/src/logseq/e2e/assert.clj
  3. 17 0
      clj-e2e/src/logseq/e2e/block.clj
  4. 8 4
      clj-e2e/src/logseq/e2e/graph.clj
  5. 28 17
      clj-e2e/src/logseq/e2e/util.clj
  6. 96 0
      clj-e2e/test/logseq/e2e/editor_basic_test.clj
  7. 1 0
      clj-e2e/test/logseq/e2e/outliner_basic_test.clj
  8. 18 0
      deps/db/src/logseq/db.cljs
  9. 51 36
      deps/outliner/src/logseq/outliner/core.cljs
  10. 10 3
      deps/outliner/src/logseq/outliner/op.cljs
  11. 22 13
      deps/outliner/src/logseq/outliner/property.cljs
  12. 77 6
      deps/outliner/src/logseq/outliner/validate.cljs
  13. 67 7
      deps/outliner/test/logseq/outliner/validate_test.cljs
  14. 56 21
      src/main/frontend/components/cmdk/core.cljs
  15. 2 1
      src/main/frontend/components/container.cljs
  16. 2 1
      src/main/frontend/components/header.cljs
  17. 2 7
      src/main/frontend/components/library.cljs
  18. 2 0
      src/main/frontend/components/property/dialog.cljs
  19. 20 14
      src/main/frontend/components/property/value.cljs
  20. 1 0
      src/main/frontend/components/select.cljs
  21. 68 66
      src/main/frontend/components/selection.cljs
  22. 6 0
      src/main/frontend/handler/db_based/property.cljs
  23. 5 4
      src/main/frontend/handler/dnd.cljs
  24. 46 18
      src/main/frontend/handler/editor.cljs
  25. 9 9
      src/main/frontend/handler/events/ui.cljs
  26. 3 3
      src/main/frontend/log.cljs
  27. 7 2
      src/main/frontend/modules/outliner/op.cljs
  28. 12 1
      src/main/frontend/modules/shortcut/config.cljs
  29. 1 0
      src/main/frontend/state.cljs
  30. 2 1
      src/main/frontend/ui.cljs
  31. 2 1
      src/main/frontend/worker/handler/page.cljs
  32. 86 7
      src/main/frontend/worker/pipeline.cljs
  33. 16 11
      src/main/frontend/worker/rtc/remote_update.cljs
  34. 7 3
      src/main/frontend/worker/search.cljs
  35. 108 108
      src/main/logseq/api.cljs
  36. 58 58
      src/main/logseq/api/block.cljs
  37. 3 3
      src/main/mobile/components/settings.cljs
  38. 2 0
      src/resources/dicts/en.edn
  39. 18 7
      src/test/frontend/modules/outliner/core_test.cljs

+ 8 - 1
clj-e2e/dev/user.clj

@@ -4,6 +4,7 @@
             [logseq.e2e.block :as b]
             [logseq.e2e.block :as b]
             [logseq.e2e.commands-basic-test]
             [logseq.e2e.commands-basic-test]
             [logseq.e2e.config :as config]
             [logseq.e2e.config :as config]
+            [logseq.e2e.editor-basic-test]
             [logseq.e2e.fixtures :as fixtures]
             [logseq.e2e.fixtures :as fixtures]
             [logseq.e2e.graph :as graph]
             [logseq.e2e.graph :as graph]
             [logseq.e2e.keyboard :as k]
             [logseq.e2e.keyboard :as k]
@@ -73,6 +74,11 @@
   (->> (future (run-tests 'logseq.e2e.rtc-extra-test))
   (->> (future (run-tests 'logseq.e2e.rtc-extra-test))
        (swap! *futures assoc :rtc-extra-test)))
        (swap! *futures assoc :rtc-extra-test)))
 
 
+(defn run-editor-basic-test
+  []
+  (->> (future (run-tests 'logseq.e2e.editor-basic-test))
+       (swap! *futures assoc :editor-basic-test)))
+
 (defn run-tag-basic-test
 (defn run-tag-basic-test
   []
   []
   (->> (future (run-tests 'logseq.e2e.tag-basic-test))
   (->> (future (run-tests 'logseq.e2e.tag-basic-test))
@@ -80,7 +86,8 @@
 
 
 (defn run-all-basic-test
 (defn run-all-basic-test
   []
   []
-  (run-tests 'logseq.e2e.commands-basic-test
+  (run-tests 'logseq.e2e.editor-basic-test
+             'logseq.e2e.commands-basic-test
              'logseq.e2e.multi-tabs-basic-test
              'logseq.e2e.multi-tabs-basic-test
              'logseq.e2e.outliner-basic-test
              'logseq.e2e.outliner-basic-test
              'logseq.e2e.rtc-basic-test
              'logseq.e2e.rtc-basic-test

+ 4 - 3
clj-e2e/src/logseq/e2e/assert.clj

@@ -18,9 +18,10 @@
 
 
 (defn assert-non-editor-mode
 (defn assert-non-editor-mode
   []
   []
-  (assert-is-hidden (loc/or "[data-testid='block editor']"
-                            ;; TODO: remove this when this prop-name fixed on dom
-                            "[datatestid='block editor']")))
+  (w/wait-for-not-visible
+   (loc/or "[data-testid='block editor']"
+           ;; TODO: remove this when this prop-name fixed on dom
+           "[datatestid='block editor']")))
 
 
 (defn assert-in-normal-mode?
 (defn assert-in-normal-mode?
   "- not editing mode
   "- not editing mode

+ 17 - 0
clj-e2e/src/logseq/e2e/block.clj

@@ -97,3 +97,20 @@
 (defn outdent
 (defn outdent
   []
   []
   (indent-outdent false))
   (indent-outdent false))
+
+(defn toggle-property
+  [property-title property-value]
+  (k/press (if util/mac? "ControlOrMeta+p" "Control+Alt+p"))
+  (w/fill ".ls-property-dialog .ls-property-input input" property-title)
+  (w/wait-for (format "#ac-0.menu-link:has-text('%s')" property-title))
+  (k/enter)
+  (util/wait-timeout 100)
+  (w/click (w/-query ".ls-property-dialog .ls-property-input input"))
+  (util/wait-timeout 100)
+  (util/input property-value)
+  (w/wait-for (format "#ac-0.menu-link:has-text('%s')" property-value))
+  (k/enter))
+
+(defn select-blocks
+  [n]
+  (util/repeat-keyboard n "Shift+ArrowUp"))

+ 8 - 4
clj-e2e/src/logseq/e2e/graph.clj

@@ -2,9 +2,11 @@
   (:require [clojure.edn :as edn]
   (:require [clojure.edn :as edn]
             [clojure.string :as string]
             [clojure.string :as string]
             [logseq.e2e.assert :as assert]
             [logseq.e2e.assert :as assert]
+            [logseq.e2e.keyboard :as k]
             [logseq.e2e.locator :as loc]
             [logseq.e2e.locator :as loc]
             [logseq.e2e.util :as util]
             [logseq.e2e.util :as util]
-            [wally.main :as w]))
+            [wally.main :as w]
+            [wally.repl :as repl]))
 
 
 (defn- refresh-all-remote-graphs
 (defn- refresh-all-remote-graphs
   []
   []
@@ -55,9 +57,11 @@
 
 
 (defn validate-graph
 (defn validate-graph
   []
   []
+  (k/esc)
+  (k/esc)
   (util/search-and-click "(Dev) Validate current graph")
   (util/search-and-click "(Dev) Validate current graph")
-  (assert/assert-is-visible (loc/and ".notifications div" (w/get-by-text "Your graph is valid")))
-  (let [content (.textContent (loc/and ".notifications div" (w/get-by-text "Your graph is valid")))
+  (assert/assert-is-visible (loc/and ".notifications div.notification-success div" (w/get-by-text "Your graph is valid")))
+  (let [content (.textContent (loc/and ".notifications div.notification-success div" (w/get-by-text "Your graph is valid")))
         summary (edn/read-string (subs content (string/index-of content "{")))]
         summary (edn/read-string (subs content (string/index-of content "{")))]
-    (w/click ".notifications .ls-icon-x")
+    (w/click ".notifications div.notification-success .ls-icon-x")
     summary))
     summary))

+ 28 - 17
clj-e2e/src/logseq/e2e/util.clj

@@ -63,19 +63,31 @@
     (.pressSequentially input-node text
     (.pressSequentially input-node text
                         (.setDelay (Locator$PressSequentiallyOptions.) delay))))
                         (.setDelay (Locator$PressSequentiallyOptions.) delay))))
 
 
+(defn exit-edit
+  []
+  (when (get-editor)
+    (k/esc))
+  (assert/assert-non-editor-mode))
+
 (defn double-esc
 (defn double-esc
   "Exits editing mode and ensure there's no action bar"
   "Exits editing mode and ensure there's no action bar"
   []
   []
-  (k/esc)
-  (k/esc))
+  (when (w/visible? "div[data-radix-popper-content-wrapper]")
+    (k/esc))
+  (exit-edit)
+  (when (w/visible? "div[data-radix-popper-content-wrapper]")
+    (k/esc)))
 
 
 (defn search
 (defn search
   [text]
   [text]
-  (double-esc)
-  (assert/assert-in-normal-mode?)
-  (w/click :#search-button)
-  (w/wait-for ".cp__cmdk-search-input")
-  (w/fill ".cp__cmdk-search-input" text))
+  (if (w/visible? ".cp__cmdk-search-input")
+    (w/fill ".cp__cmdk-search-input" text)
+    (do
+      (double-esc)
+      (assert/assert-in-normal-mode?)
+      (w/click :#search-button)
+      (w/wait-for ".cp__cmdk-search-input")
+      (w/fill ".cp__cmdk-search-input" text))))
 
 
 (defn search-and-click
 (defn search-and-click
   [search-text]
   [search-text]
@@ -105,11 +117,6 @@
   []
   []
   (count-elements ".ls-page-blocks .page-blocks-inner .ls-block"))
   (count-elements ".ls-page-blocks .page-blocks-inner .ls-block"))
 
 
-(defn exit-edit
-  []
-  (k/esc)
-  (assert/assert-non-editor-mode))
-
 (defn get-text
 (defn get-text
   [locator]
   [locator]
   (if (string? locator)
   (if (string? locator)
@@ -129,7 +136,7 @@
 (defn repeat-keyboard
 (defn repeat-keyboard
   [n shortcut]
   [n shortcut]
   (dotimes [_i n]
   (dotimes [_i n]
-    (k/press shortcut)))
+    (k/press shortcut {:delay 20})))
 
 
 (defn get-page-blocks-contents
 (defn get-page-blocks-contents
   []
   []
@@ -161,11 +168,13 @@
 
 
 (defn move-cursor-to-end
 (defn move-cursor-to-end
   []
   []
-  (k/press "ControlOrMeta+a" "ArrowRight"))
+  (k/press ["ControlOrMeta+a" "ArrowRight"]
+           {:delay 20}))
 
 
 (defn move-cursor-to-start
 (defn move-cursor-to-start
   []
   []
-  (k/press "ControlOrMeta+a" "ArrowLeft"))
+  (k/press ["ControlOrMeta+a" "ArrowLeft"]
+           {:delay 20}))
 
 
 (defn input-command
 (defn input-command
   [command]
   [command]
@@ -179,11 +188,13 @@
   (w/click "a.menu-link.chosen"))
   (w/click "a.menu-link.chosen"))
 
 
 (defn set-tag
 (defn set-tag
-  [tag]
+  "`hidden?`: some tags may be hidden from the UI, e.g. Page"
+  [tag & {:keys [hidden?]
+          :or {hidden? false}}]
   (press-seq " #" {:delay 20})
   (press-seq " #" {:delay 20})
   (press-seq tag)
   (press-seq tag)
   (w/click (first (w/query (format "a.menu-link:has-text(\"%s\")" tag))))
   (w/click (first (w/query (format "a.menu-link:has-text(\"%s\")" tag))))
-  (when (not= (string/lower-case tag) "task")
+  (when (and (not= (string/lower-case tag) "task") (not hidden?))
     ;; wait tag added on ui
     ;; wait tag added on ui
     (assert/assert-is-visible
     (assert/assert-is-visible
      (-> ".ls-block:not(.block-add-button)"
      (-> ".ls-block:not(.block-add-button)"

+ 96 - 0
clj-e2e/test/logseq/e2e/editor_basic_test.clj

@@ -0,0 +1,96 @@
+(ns logseq.e2e.editor-basic-test
+  (:require
+   [clojure.set :as set]
+   [clojure.test :refer [deftest testing is use-fixtures]]
+   [logseq.e2e.assert :as assert]
+   [logseq.e2e.block :as b]
+   [logseq.e2e.fixtures :as fixtures]
+   [logseq.e2e.keyboard :as k]
+   [logseq.e2e.page :as p]
+   [logseq.e2e.util :as util]
+   [wally.main :as w]
+   [wally.repl :as repl]))
+
+(use-fixtures :once fixtures/open-page)
+
+(use-fixtures :each
+  fixtures/new-logseq-page
+  fixtures/validate-graph)
+
+(deftest toggle-between-page-and-block
+  (testing "Convert block to page and back"
+    (b/new-block "b1")
+    (util/set-tag "Page" {:hidden? true})
+    (assert/assert-is-visible ".ls-page-blocks .ls-block .ls-icon-file")
+    (b/toggle-property "Tags" "Page")
+    (assert/assert-is-hidden ".ls-page-blocks .ls-block .ls-icon-file")))
+
+(deftest toggle-between-page-and-block-for-selected-blocks
+  (testing "Convert selected blocks to pages and back"
+    (b/new-blocks ["b1" "b2" "b3"])
+    (b/select-blocks 3)
+    (b/toggle-property "Tags" "Page")
+    (assert/assert-is-visible ".ls-page-blocks .ls-block .ls-icon-file")
+    (w/wait-for (format "#ac-0.menu-link:has-text('%s')" "Page"))
+    (k/enter)
+    (w/wait-for-not-visible ".ls-page-blocks .ls-block .ls-icon-file")))
+
+(deftest disallow-adding-page-tag-to-normal-pages
+  (testing "Disallow adding #Page to normal pages"
+    (k/arrow-up)
+    (util/move-cursor-to-end)
+    (util/press-seq " #" {:delay 20})
+    (util/press-seq "Page")
+    (assert/assert-is-hidden (format "#ac-0.menu-link:has-text('%s')" "Page"))))
+
+(deftest move-blocks-mod+shift+m
+  (testing "move blocks using `mod+shift+m`"
+    (p/new-page "Target page")
+    (p/new-page "Source page")
+    (b/new-blocks ["b1" "b2" "b3"])
+    (b/select-blocks 3)
+    (k/press "ControlOrMeta+Shift+m")
+    (w/fill "input[placeholder=\"Move blocks to\"]" "Target page")
+    (w/wait-for (w/get-by-test-id "Target page"))
+    (.focus (w/-query ".cp__cmdk-search-input"))
+    (k/enter)
+    (assert/assert-have-count ".ls-page-blocks .page-blocks-inner .ls-block" 0)))
+
+(deftest move-blocks-cmdk
+  (testing "move blocks using cmdk"
+    (p/new-page "Target page 2")
+    (p/new-page "Source page 2")
+    (b/new-blocks ["b1" "b2" "b3"])
+    (b/select-blocks 3)
+    (util/search-and-click "Move blocks to")
+    (w/fill "input[placeholder=\"Move blocks to\"]" "Target page 2")
+    (w/wait-for (w/get-by-test-id "Target page 2"))
+    (.focus (w/-query ".cp__cmdk-search-input"))
+    (k/enter)
+    (assert/assert-have-count ".ls-page-blocks .page-blocks-inner .ls-block" 0)))
+
+(deftest move-pages-to-library
+  (testing "move pages using `mod+shift+m`"
+    (p/goto-page "Library")
+    (p/new-page "test page")
+    (b/new-blocks ["block1" "block2" "block3"])
+    (b/select-blocks 3)
+    (k/press "ControlOrMeta+Shift+m")
+    (w/fill "input[placeholder=\"Move blocks to\"]" "Library")
+    (w/wait-for (w/get-by-test-id "Library"))
+    (.focus (w/-query ".cp__cmdk-search-input"))
+    (k/enter)
+    (p/goto-page "Library")
+    (let [contents (set (util/get-page-blocks-contents))]
+      (is (set/subset? (set ["block1" "block2" "block3"]) contents)))
+    (p/goto-page "test page")
+    (b/new-blocks ["block4" "block5"])
+    (b/select-blocks 2)
+    (k/press "ControlOrMeta+Shift+m")
+    (w/fill "input[placeholder=\"Move blocks to\"]" "Library")
+    (w/wait-for (w/get-by-test-id "Library"))
+    (.focus (w/-query ".cp__cmdk-search-input"))
+    (k/enter)
+    (p/goto-page "Library")
+    (let [contents (set (util/get-page-blocks-contents))]
+      (is (set/subset? (set ["block1" "block2" "block3" "block4" "block5"]) contents)))))

+ 1 - 0
clj-e2e/test/logseq/e2e/outliner_basic_test.clj

@@ -100,6 +100,7 @@
     (b/indent)
     (b/indent)
     (k/arrow-up)
     (k/arrow-up)
     (b/delete-blocks)
     (b/delete-blocks)
+    (util/wait-editor-visible)
     (is (= "b1" (util/get-edit-content)))
     (is (= "b1" (util/get-edit-content)))
     (is (= 1 (util/page-blocks-count)))))
     (is (= 1 (util/page-blocks-count)))))
 
 

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

@@ -7,6 +7,7 @@
             [clojure.walk :as walk]
             [clojure.walk :as walk]
             [datascript.core :as d]
             [datascript.core :as d]
             [datascript.impl.entity :as de]
             [datascript.impl.entity :as de]
+            [logseq.common.config :as common-config]
             [logseq.common.util :as common-util]
             [logseq.common.util :as common-util]
             [logseq.common.uuid :as common-uuid]
             [logseq.common.uuid :as common-uuid]
             [logseq.db.common.delete-blocks :as delete-blocks] ;; Load entity extensions
             [logseq.db.common.delete-blocks :as delete-blocks] ;; Load entity extensions
@@ -272,6 +273,9 @@
 (def get-built-in-page db-db/get-built-in-page)
 (def get-built-in-page db-db/get-built-in-page)
 
 
 (def library? db-db/library?)
 (def library? db-db/library?)
+(defn get-library-page
+  [db]
+  (get-built-in-page db common-config/library-page-name))
 
 
 (defn get-case-page
 (defn get-case-page
   "Case sensitive version of get-page. For use with DB graphs"
   "Case sensitive version of get-page. For use with DB graphs"
@@ -575,3 +579,17 @@
   (if (sqlite-util/db-based-graph? repo)
   (if (sqlite-util/db-based-graph? repo)
     db-schema/schema
     db-schema/schema
     file-schema/schema))
     file-schema/schema))
+
+(defn page-in-library?
+  "Check whether a `page` exists on the Library page"
+  [db page]
+  (when (page? page)
+    (when-let [library-page (get-built-in-page db common-config/library-page-name)]
+      (loop [parent (:block/parent page)]
+        (cond
+          (nil? parent)
+          false
+          (= (:db/id parent) (:db/id library-page))
+          true
+          :else
+          (recur (:block/parent parent)))))))

+ 51 - 36
deps/outliner/src/logseq/outliner/core.cljs

@@ -522,22 +522,22 @@
      block)))
      block)))
 
 
 (defn- get-target-block-page
 (defn- get-target-block-page
-  [target-block]
+  [target-block sibling?]
   (or
   (or
-   (when (ldb/page? target-block)
-     (:db/id target-block))
    (:db/id (:block/page target-block))
    (:db/id (:block/page target-block))
    ;; target parent is a page
    ;; target parent is a page
-   (when-let [parent (:block/parent target-block)]
-     (when (ldb/page? parent)
-       (:db/id parent)))
+   (when sibling?
+     (when-let [parent (:block/parent target-block)]
+       (when (ldb/page? parent)
+         (:db/id parent))))
+
    ;; target-block is a page itself
    ;; target-block is a page itself
    (:db/id target-block)))
    (:db/id target-block)))
 
 
 (defn- build-insert-blocks-tx
 (defn- build-insert-blocks-tx
   [db target-block blocks uuids get-new-id {:keys [sibling? outliner-op replace-empty-target? insert-template? keep-block-order?]}]
   [db target-block blocks uuids get-new-id {:keys [sibling? outliner-op replace-empty-target? insert-template? keep-block-order?]}]
   (let [block-ids (set (map :block/uuid blocks))
   (let [block-ids (set (map :block/uuid blocks))
-        target-page (get-target-block-page target-block)
+        target-page (get-target-block-page target-block sibling?)
         orders (get-block-orders blocks target-block sibling? keep-block-order?)]
         orders (get-block-orders blocks target-block sibling? keep-block-order?)]
     (map-indexed (fn [idx {:block/keys [parent] :as block}]
     (map-indexed (fn [idx {:block/keys [parent] :as block}]
                    (when-let [uuid' (get uuids (:block/uuid block))]
                    (when-let [uuid' (get uuids (:block/uuid block))]
@@ -551,28 +551,31 @@
                                                       (let [ref-ids (set (map :block/uuid (:block/refs block)))]
                                                       (let [ref-ids (set (map :block/uuid (:block/refs block)))]
                                                         (->> (set/intersection block-ids ref-ids)
                                                         (->> (set/intersection block-ids ref-ids)
                                                              (remove #{(:block/uuid block)})))))
                                                              (remove #{(:block/uuid block)})))))
-                           m (cond->
-                              {:db/id (:db/id block)
-                               :block/uuid uuid'
-                               :block/parent parent
-                               :block/order order}
-                               (not (ldb/page? block))
-                               (assoc :block/page target-page))
-                           result (->
-                                   (if (de/entity? block)
-                                     (assoc m :block/level (:block/level block))
-                                     (merge block m))
-                                   (update :block/title (fn [value]
-                                                          (if (seq template-ref-block-ids)
-                                                            (reduce
-                                                             (fn [value id]
-                                                               (string/replace value
-                                                                               (page-ref/->page-ref id)
-                                                                               (page-ref/->page-ref (uuids id))))
-                                                             value
-                                                             template-ref-block-ids)
-                                                            value)))
-                                   (dissoc :db/id))]
+                           m {:db/id (:db/id block)
+                              :block/uuid uuid'
+                              :block/parent parent
+                              :block/order order}
+                           result* (->
+                                    (if (de/entity? block)
+                                      (assoc m :block/level (:block/level block))
+                                      (merge block m))
+                                    (update :block/title (fn [value]
+                                                           (if (seq template-ref-block-ids)
+                                                             (reduce
+                                                              (fn [value id]
+                                                                (string/replace value
+                                                                                (page-ref/->page-ref id)
+                                                                                (page-ref/->page-ref (uuids id))))
+                                                              value
+                                                              template-ref-block-ids)
+                                                             value)))
+                                    (dissoc :db/id))
+                           page? (or (ldb/page? block) (:block/name block))
+                           result (cond-> result*
+                                    (not page?)
+                                    (assoc :block/page target-page)
+                                    page?
+                                    (dissoc :block/page))]
                        (update-property-ref-when-paste result uuids))))
                        (update-property-ref-when-paste result uuids))))
                  blocks)))
                  blocks)))
 
 
@@ -608,7 +611,7 @@
      :id->new-uuid id->new-uuid}))
      :id->new-uuid id->new-uuid}))
 
 
 (defn- get-target-block
 (defn- get-target-block
-  [db blocks target-block {:keys [outliner-op indent? sibling? up?]}]
+  [db blocks target-block {:keys [outliner-op bottom? top? indent? sibling? up?]}]
   (when-let [block (if (:db/id target-block)
   (when-let [block (if (:db/id target-block)
                      (d/entity db (:db/id target-block))
                      (d/entity db (:db/id target-block))
                      (when (:block/uuid target-block)
                      (when (:block/uuid target-block)
@@ -636,7 +639,16 @@
                              [block sibling?]
                              [block sibling?]
 
 
                              (contains? #{:insert-blocks :move-blocks} outliner-op)
                              (contains? #{:insert-blocks :move-blocks} outliner-op)
-                             [block sibling?]
+                             (cond
+                               top?
+                               [block false]
+
+                               bottom?
+                               (if-let [last-child (last (ldb/sort-by-order (:block/_parent block)))]
+                                 [last-child true]
+                                 [block false])
+                               :else
+                               [block sibling?])
 
 
                              linked
                              linked
                              (get-last-child-or-self db linked)
                              (get-last-child-or-self db linked)
@@ -683,6 +695,8 @@
     `target-block`: where `blocks` will be inserted.
     `target-block`: where `blocks` will be inserted.
     Options:
     Options:
       `sibling?`: as siblings (true) or children (false).
       `sibling?`: as siblings (true) or children (false).
+      `bottom?`: inserts block to the bottom.
+      `top?`: inserts block to the top.
       `keep-uuid?`: whether to replace `:block/uuid` from the parameter `blocks`.
       `keep-uuid?`: whether to replace `:block/uuid` from the parameter `blocks`.
                     For example, if `blocks` are from internal copy, the uuids
                     For example, if `blocks` are from internal copy, the uuids
                     need to be changed, but there's no need for internal cut or drag & drop.
                     need to be changed, but there's no need for internal cut or drag & drop.
@@ -854,7 +868,7 @@
   (let [target-block (d/entity db (:db/id target-block))
   (let [target-block (d/entity db (:db/id target-block))
         block (d/entity db (:db/id block))
         block (d/entity db (:db/id block))
         first-block-page (:db/id (:block/page block))
         first-block-page (:db/id (:block/page block))
-        target-page (get-target-block-page target-block)
+        target-page (get-target-block-page target-block sibling?)
         not-same-page? (not= first-block-page target-page)
         not-same-page? (not= first-block-page target-page)
         block-order (if sibling?
         block-order (if sibling?
                       (db-order/gen-key (:block/order target-block)
                       (db-order/gen-key (:block/order target-block)
@@ -890,7 +904,7 @@
 
 
 (defn- move-blocks
 (defn- move-blocks
   "Move `blocks` to `target-block` as siblings or children."
   "Move `blocks` to `target-block` as siblings or children."
-  [_repo conn blocks target-block {:keys [_sibling? _up? outliner-op _indent?]
+  [_repo conn blocks target-block {:keys [_sibling? _top? _bottom? _up? outliner-op _indent?]
                                    :as opts}]
                                    :as opts}]
   {:pre [(seq blocks)
   {:pre [(seq blocks)
          (m/validate block-map-or-entity target-block)]}
          (m/validate block-map-or-entity target-block)]}
@@ -1065,9 +1079,10 @@
     (op-transact! f repo conn blocks opts)))
     (op-transact! f repo conn blocks opts)))
 
 
 (defn move-blocks!
 (defn move-blocks!
-  [repo conn blocks target-block sibling?]
-  (op-transact! move-blocks repo conn blocks target-block {:sibling? sibling?
-                                                           :outliner-op :move-blocks}))
+  [repo conn blocks target-block opts]
+  (op-transact! move-blocks repo conn blocks target-block
+                (assoc opts :outliner-op :move-blocks)))
+
 (defn move-blocks-up-down!
 (defn move-blocks-up-down!
   [repo conn blocks up?]
   [repo conn blocks up?]
   (op-transact! move-blocks-up-down repo conn blocks up?))
   (op-transact! move-blocks-up-down repo conn blocks up?))

+ 10 - 3
deps/outliner/src/logseq/outliner/op.cljs

@@ -26,7 +26,7 @@
    [:move-blocks
    [:move-blocks
     [:catn
     [:catn
      [:op :keyword]
      [:op :keyword]
-     [:args [:tuple ::ids ::id :boolean]]]]
+     [:args [:tuple ::ids ::id ::option]]]]
    [:move-blocks-up-down
    [:move-blocks-up-down
     [:catn
     [:catn
      [:op :keyword]
      [:op :keyword]
@@ -53,6 +53,10 @@
     [:catn
     [:catn
      [:op :keyword]
      [:op :keyword]
      [:args [:tuple ::block-id ::property-id ::value]]]]
      [:args [:tuple ::block-id ::property-id ::value]]]]
+   [:batch-delete-property-value
+    [:catn
+     [:op :keyword]
+     [:args [:tuple ::block-ids ::property-id ::value]]]]
    [:create-property-text-block
    [:create-property-text-block
     [:catn
     [:catn
      [:op :keyword]
      [:op :keyword]
@@ -171,11 +175,11 @@
            (outliner-core/delete-blocks! repo conn date-formatter blocks (merge opts opts')))
            (outliner-core/delete-blocks! repo conn date-formatter blocks (merge opts opts')))
 
 
          :move-blocks
          :move-blocks
-         (let [[block-ids target-block-id sibling?] args
+         (let [[block-ids target-block-id opts] args
                blocks (keep #(d/entity @conn %) block-ids)
                blocks (keep #(d/entity @conn %) block-ids)
                target-block (d/entity @conn target-block-id)]
                target-block (d/entity @conn target-block-id)]
            (when (and target-block (seq blocks))
            (when (and target-block (seq blocks))
-             (outliner-core/move-blocks! repo conn blocks target-block sibling?)))
+             (outliner-core/move-blocks! repo conn blocks target-block opts)))
 
 
          :move-blocks-up-down
          :move-blocks-up-down
          (let [[block-ids up?] args
          (let [[block-ids up?] args
@@ -211,6 +215,9 @@
          :batch-remove-property
          :batch-remove-property
          (apply outliner-property/batch-remove-property! conn args)
          (apply outliner-property/batch-remove-property! conn args)
 
 
+         :batch-delete-property-value
+         (apply outliner-property/batch-delete-property-value! conn args)
+
          :class-add-property
          :class-add-property
          (apply outliner-property/class-add-property! conn args)
          (apply outliner-property/class-add-property! conn args)
 
 

+ 22 - 13
deps/outliner/src/logseq/outliner/property.cljs

@@ -520,22 +520,31 @@
                          {:outliner-op :upsert-property}))
                          {:outliner-op :upsert-property}))
         (d/entity @conn db-ident')))))
         (d/entity @conn db-ident')))))
 
 
+(defn batch-delete-property-value!
+  "batch delete value when a property has multiple values"
+  [conn block-eids property-id property-value]
+  (when-let [property (d/entity @conn property-id)]
+    (when (and (db-property/many? property)
+               (not (some #(= property-id (:db/ident (d/entity @conn %))) block-eids)))
+      (when (= property-id :block/tags)
+        (outliner-validate/validate-tags-property-deletion @conn block-eids property-value))
+      (if (= property-id :block/tags)
+        (let [tx-data (map (fn [id] [:db/retract id property-id property-value]) block-eids)]
+          (ldb/transact! conn tx-data {:outliner-op :save-block}))
+        (doseq [block-eid block-eids]
+          (when-let [block (d/entity @conn block-eid)]
+            (let [current-val (get block property-id)
+                  fv (first current-val)]
+              (if (and (= 1 (count current-val)) (or (= property-value fv) (= property-value (:db/id fv))))
+                (remove-block-property! conn (:db/id block) property-id)
+                (ldb/transact! conn
+                               [[:db/retract (:db/id block) property-id property-value]]
+                               {:outliner-op :save-block})))))))))
+
 (defn delete-property-value!
 (defn delete-property-value!
   "Delete value if a property has multiple values"
   "Delete value if a property has multiple values"
   [conn block-eid property-id property-value]
   [conn block-eid property-id property-value]
-  (when-let [property (d/entity @conn property-id)]
-    (let [block (d/entity @conn block-eid)]
-      (when (and block (not= property-id (:db/ident block)) (db-property/many? property))
-        (when (= property-id :block/tags)
-          (outliner-validate/validate-tags-property-deletion @conn [block-eid] property-value))
-
-        (let [current-val (get block property-id)
-              fv (first current-val)]
-          (if (and (= 1 (count current-val)) (or (= property-value fv) (= property-value (:db/id fv))))
-            (remove-block-property! conn (:db/id block) property-id)
-            (ldb/transact! conn
-                           [[:db/retract (:db/id block) property-id property-value]]
-                           {:outliner-op :save-block})))))))
+  (batch-delete-property-value! conn [block-eid] property-id property-value))
 
 
 (defn ^:api get-classes-parents
 (defn ^:api get-classes-parents
   [tags]
   [tags]

+ 77 - 6
deps/outliner/src/logseq/outliner/validate.cljs

@@ -204,11 +204,13 @@
 
 
 (defn- disallow-node-cant-tag-with-private-tags
 (defn- disallow-node-cant-tag-with-private-tags
   [db block-eids v & {:keys [delete?]}]
   [db block-eids v & {:keys [delete?]}]
-  (when (and (ldb/private-tags (:db/ident (d/entity db v)))
-             ;; Allow assets to be tagged
-             (not (and
-                   (every? (fn [id] (ldb/asset? (d/entity db id))) block-eids)
-                   (= :logseq.class/Asset (:db/ident (d/entity db v))))))
+  ;; Skip #Page as it is validated by later fns
+  (when (and (contains? (disj ldb/private-tags :logseq.class/Page) (:db/ident (d/entity db v)))
+             (not
+               ;; Allow assets to be tagged
+              (and
+               (every? (fn [id] (ldb/asset? (d/entity db id))) block-eids)
+               (= :logseq.class/Asset (:db/ident (d/entity db v))))))
     (throw (ex-info (str (if delete? "Can't remove tag" "Can't set tag")
     (throw (ex-info (str (if delete? "Can't remove tag" "Can't set tag")
                          " with built-in #" (:block/title (d/entity db v)))
                          " with built-in #" (:block/title (d/entity db v)))
                     {:type :notification
                     {:type :notification
@@ -229,15 +231,84 @@
                                              " on built-in " (pr-str (:block/title built-in-ent)))
                                              " on built-in " (pr-str (:block/title built-in-ent)))
                                :type :error}}))))
                                :type :error}}))))
 
 
+(defn- disallow-removing-page-tag
+  "Disallow page->block when
+  1. this page doesn't have :block/parent
+  2. its parent is Library
+  3. it has page child"
+  [db eids v]
+  (when (= (:db/ident (d/entity db v)) :logseq.class/Page)
+    (let [library-page (ldb/get-library-page db)]
+      (doseq [eid eids]
+        (let [entity (d/entity db eid)]
+          (when (ldb/internal-page? entity)
+            (cond
+              (not (:block/parent entity))
+              (throw (ex-info "This page cannot be converted to a block"
+                              {:type :notification
+                               :payload
+                               {:message (str "Page " (pr-str (:block/title entity)) " cannot be converted to a block")
+                                :type :error
+                                :entity (into {} entity)
+                                :property :block/tags}}))
+              (= (:db/id library-page) (:db/id (:block/parent entity)))
+              (throw (ex-info "This page cannot be converted to a block"
+                              {:type :notification
+                               :payload
+                               {:message (str "Page " (pr-str (:block/title entity)) " cannot be converted to a block, please move it to another page first")
+                                :type :error
+                                :entity (into {} entity)
+                                :property :block/tags}}))
+              (some entity-util/page? (:block/_parent entity))
+              (throw (ex-info "This page cannot be converted to a block"
+                              {:type :notification
+                               :payload
+                               {:message (str "Page " (pr-str (:block/title entity)) " cannot be converted to a block because it has page children")
+                                :type :error
+                                :entity (into {} entity)
+                                :property :block/tags}})))))))))
+
+(defn- validate-block-can-tag-with-page-tag
+  "Validates block can convert to page by adding #Page for allowed scenarios"
+  [db eids v]
+  (when (= (:db/ident (d/entity db v)) :logseq.class/Page)
+    (doseq [eid eids]
+      (let [block (d/entity db eid)]
+        (when (:block/parent block)
+          (validate-page-title (:block/title block) {:node block})
+          (validate-page-title-characters (:block/title block) {:node block})
+
+          ;; Only allow block to be page when its parent is a page to guard against invalid pages
+          ;; in property values or pages being created with blocks as namespace parents
+          (when (or (not (entity-util/page? (:block/parent block)))
+                    (:logseq.property/created-from-property block))
+            (let [message (if (:logseq.property/created-from-property block)
+                            "Can't convert property value to page."
+                            "Can't convert this block to page since its parent is not a page.")]
+              (throw (ex-info message
+                              {:type :notification
+                               :payload {:message message
+                                         :type :error
+                                         :block (into {} block)}}))))
+          ;; Guard against classes and properties becoming namespace parents
+          (when (or (entity-util/class? (:block/page block)) (entity-util/property? (:block/page block)))
+            (throw (ex-info "Can't convert this block to page when block is in a property or tag."
+                            {:type :notification
+                             :payload {:message "Can't convert this block to page when block is in a property or tag."
+                                       :type :error
+                                       :block (into {} block)}}))))))))
+
 (defn validate-tags-property
 (defn validate-tags-property
   "Validates adding a property value to :block/tags for given blocks"
   "Validates adding a property value to :block/tags for given blocks"
   [db block-eids v]
   [db block-eids v]
   (disallow-tagging-a-built-in-entity db block-eids)
   (disallow-tagging-a-built-in-entity db block-eids)
   (disallow-node-cant-tag-with-private-tags db block-eids v)
   (disallow-node-cant-tag-with-private-tags db block-eids v)
+  (validate-block-can-tag-with-page-tag db block-eids v)
   (disallow-node-cant-tag-with-built-in-non-tags db block-eids v))
   (disallow-node-cant-tag-with-built-in-non-tags db block-eids v))
 
 
 (defn validate-tags-property-deletion
 (defn validate-tags-property-deletion
   "Validates deleting a property value from :block/tags for given blocks"
   "Validates deleting a property value from :block/tags for given blocks"
   [db block-eids v]
   [db block-eids v]
   (disallow-tagging-a-built-in-entity db block-eids {:delete? true})
   (disallow-tagging-a-built-in-entity db block-eids {:delete? true})
-  (disallow-node-cant-tag-with-private-tags db block-eids v {:delete? true}))
+  (disallow-node-cant-tag-with-private-tags db block-eids v {:delete? true})
+  (disallow-removing-page-tag db block-eids v))

+ 67 - 7
deps/outliner/test/logseq/outliner/validate_test.cljs

@@ -158,12 +158,21 @@
                                                         [(entity-plus/entity-memoized @conn :logseq.class/Cards)]))))))
                                                         [(entity-plus/entity-memoized @conn :logseq.class/Cards)]))))))
 
 
 (deftest validate-tags-property
 (deftest validate-tags-property
-  (let [conn (db-test/create-conn-with-blocks
-              {:classes {:SomeTag {}}
+  (let [class-uuid (random-uuid)
+        conn (db-test/create-conn-with-blocks
+              {:classes {:SomeTag {:block/uuid class-uuid :build/keep-uuid? true}}
                :pages-and-blocks
                :pages-and-blocks
                [{:page {:block/title "page1"}
                [{:page {:block/title "page1"}
-                 :blocks [{:block/title "block"}]}]})
-        block (db-test/find-block-by-content @conn "block")]
+                 :blocks [{:block/title "block"
+                           :build/children [{:block/title "block - invalid location"}]}
+                          {:block/title "block / invalid title"}]}
+                {:page {:block/uuid class-uuid}
+                 :blocks [{:block/title "class block"}]}]
+               :build-existing-tx? true})
+        block (db-test/find-block-by-content @conn "block")
+        block-invalid-title (db-test/find-block-by-content @conn #"invalid title")
+        block-invalid-location (db-test/find-block-by-content @conn #"invalid location")
+        block-invalid-in-class (db-test/find-block-by-content @conn "class block")]
 
 
     (is (thrown-with-msg?
     (is (thrown-with-msg?
          js/Error
          js/Error
@@ -185,15 +194,66 @@
 
 
     (is (thrown-with-msg?
     (is (thrown-with-msg?
          js/Error
          js/Error
-         #"Can't set tag.*Page"
-         (outliner-validate/validate-tags-property @conn [(:db/id block)] :logseq.class/Page))
+         #"Can't set tag.*Tag"
+         (outliner-validate/validate-tags-property @conn [(:db/id block)] :logseq.class/Tag))
         "Nodes can't be tagged with built-in private tags")
         "Nodes can't be tagged with built-in private tags")
 
 
     (is (thrown-with-msg?
     (is (thrown-with-msg?
          js/Error
          js/Error
          #"Can't set tag.*Priority"
          #"Can't set tag.*Priority"
          (outliner-validate/validate-tags-property @conn [(:db/id block)] :logseq.property/priority))
          (outliner-validate/validate-tags-property @conn [(:db/id block)] :logseq.property/priority))
-        "Nodes can't be tagged with built-in non tags")))
+        "Nodes can't be tagged with built-in non tags")
+
+    (is (nil? (outliner-validate/validate-tags-property @conn [(:db/id block)] :logseq.class/Page))
+        "Blocks can be tagged with #Page")
+
+    (is (thrown-with-msg?
+         js/Error
+         #"Page name can't.*/"
+         (outliner-validate/validate-tags-property @conn [(:db/id block-invalid-title)] :logseq.class/Page))
+        "Block with invalid title can't be tagged with #Page")
+
+    (is (thrown-with-msg?
+         js/Error
+         #"Can't convert this block to page"
+         (outliner-validate/validate-tags-property @conn [(:db/id block-invalid-location)] :logseq.class/Page))
+        "Block with invalid location can't be tagged with #Page")
+
+    (is (thrown-with-msg?
+         js/Error
+         #"Can't convert this block to page"
+         (outliner-validate/validate-tags-property @conn [(:db/id block-invalid-in-class)] :logseq.class/Page))
+        "Block in class or property can't be tagged with #Page")))
+
+(deftest validate-tags-property-deletion
+  (let [conn (db-test/create-conn-with-blocks
+              {:classes {:SomeTag {}}
+               :pages-and-blocks
+               [{:page {:block/title "page1"}
+                 :blocks [{:block/title "block" :build/tags [:logseq.class/Page]}]}]})
+        page (db-test/find-page-by-title @conn "page1")
+        page-with-parent (db-test/find-block-by-content @conn "block")]
+
+    (is (thrown-with-msg?
+         js/Error
+         #"Can't remove tag.*Task"
+         (outliner-validate/validate-tags-property-deletion @conn [(:db/id (d/entity @conn :logseq.class/Task))] :logseq.class/Tag))
+        "built-in class must not have tag deleted by the user")
+
+    (is (thrown-with-msg?
+         js/Error
+         #"Can't remove tag.*Tag"
+         (outliner-validate/validate-tags-property-deletion @conn [(:db/id (d/entity @conn :user.class/SomeTag))] :logseq.class/Tag))
+        "Node can't have private tag deleted by user")
+
+    (is (nil? (outliner-validate/validate-tags-property-deletion @conn [(:db/id page-with-parent)] :logseq.class/Page))
+        "Page with parent can remove #Page")
+
+    (is (thrown-with-msg?
+         js/Error
+         #"This page cannot be converted"
+         (outliner-validate/validate-tags-property-deletion @conn [(:db/id page)] :logseq.class/Page))
+        "Page without parent can't remove #Page")))
 
 
 ;; Try as many of the validations against a new graph to confirm
 ;; Try as many of the validations against a new graph to confirm
 ;; that validations make sense and are valid for a new graph
 ;; that validations make sense and are valid for a new graph

+ 56 - 21
src/main/frontend/components/cmdk/core.cljs

@@ -40,6 +40,10 @@
             [promesa.core :as p]
             [promesa.core :as p]
             [rum.core :as rum]))
             [rum.core :as rum]))
 
 
+(defn- get-action
+  []
+  (:action (:search/args @state/state)))
+
 (defn translate [t {:keys [id desc]}]
 (defn translate [t {:keys [id desc]}]
   (when id
   (when id
     (let [desc-i18n (t (shortcut-utils/decorate-namespace id))]
     (let [desc-i18n (t (shortcut-utils/decorate-namespace id))]
@@ -197,8 +201,10 @@
                (first))))
                (first))))
 
 
 (defn state->action [state]
 (defn state->action [state]
-  (let [highlighted-item (state->highlighted-item state)]
-    (cond (:source-page highlighted-item) :open
+  (let [highlighted-item (state->highlighted-item state)
+        action (get-action)]
+    (cond (and (:source-page highlighted-item) (= action :move-blocks)) :trigger
+          (:source-page highlighted-item) :open
           (:source-block highlighted-item) :open
           (:source-block highlighted-item) :open
           (:file-path highlighted-item) :open
           (:file-path highlighted-item) :open
           (:source-search highlighted-item) :search
           (:source-search highlighted-item) :search
@@ -327,7 +333,9 @@
         repo (state/get-current-repo)
         repo (state/get-current-repo)
         current-page (when-let [id (page-util/get-current-page-id)]
         current-page (when-let [id (page-util/get-current-page-id)]
                        (db/entity id))
                        (db/entity id))
-        opts {:limit 100 :dev? config/dev? :built-in? true}]
+        opts (cond-> {:limit 100 :dev? config/dev? :built-in? true}
+               (contains? #{:move-blocks} (get-action))
+               (assoc :page-only? true))]
     (swap! !results assoc-in [group :status] :loading)
     (swap! !results assoc-in [group :status] :loading)
     (swap! !results assoc-in [:current-page :status] :loading)
     (swap! !results assoc-in [:current-page :status] :loading)
     (p/let [blocks (search/block-search repo @!input opts)
     (p/let [blocks (search/block-search repo @!input opts)
@@ -548,12 +556,21 @@
       (reset! (::input state) search-query))))
       (reset! (::input state) search-query))))
 
 
 (defmethod handle-action :trigger [_ state _event]
 (defmethod handle-action :trigger [_ state _event]
-  (let [command (some-> state state->highlighted-item :source-command)
-        dont-close-commands #{:graph/open :graph/remove :dev/replace-graph-with-db-file :misc/import-edn-data}]
-    (when-let [action (:action command)]
+  (let [highlighted-item (some-> state state->highlighted-item)
+        command (:source-command highlighted-item)
+        dont-close-commands #{:graph/open :graph/remove :dev/replace-graph-with-db-file :misc/import-edn-data :editor/move-blocks}
+        search-args (:search/args @state/state)
+        action (or (:action command)
+                   (when-let [trigger (:trigger search-args)]
+                     #(trigger highlighted-item)))
+        input-ref @(::input-ref state)]
+    (when action
+      (when input-ref
+        (set! (.-value input-ref) "")
+        (.focus input-ref))
+      (action)
       (when-not (contains? dont-close-commands (:id command))
       (when-not (contains? dont-close-commands (:id command))
-        (shui/dialog-close! :ls-dialog-cmdk))
-      (util/schedule #(action) 32))))
+        (shui/dialog-close! :ls-dialog-cmdk)))))
 
 
 (defmethod handle-action :create [_ state _event]
 (defmethod handle-action :create [_ state _event]
   (let [item (state->highlighted-item state)
   (let [item (state->highlighted-item state)
@@ -692,11 +709,13 @@
                              ;; for some reason, the highlight effect does not always trigger on a
                              ;; for some reason, the highlight effect does not always trigger on a
                              ;; boolean value change so manually pass in the dep
                              ;; boolean value change so manually pass in the dep
                             :on-highlight-dep highlighted-item
                             :on-highlight-dep highlighted-item
-                            :on-click (fn [e]
-                                        (reset! (::highlighted-item state) item)
-                                        (handle-action :default state item)
-                                        (when-let [on-click (:on-click item)]
-                                          (on-click e)))
+                            :on-click
+                            (fn [e]
+                              (util/stop-propagation e)
+                              (reset! (::highlighted-item state) item)
+                              (handle-action :default state item)
+                              (when-let [on-click (:on-click item)]
+                                (on-click e)))
                              ;; :on-mouse-enter (fn [e]
                              ;; :on-mouse-enter (fn [e]
                              ;;                   (when (not highlighted?)
                              ;;                   (when (not highlighted?)
                              ;;                     (reset! (::highlighted-item state) (assoc item :mouse-enter-triggered-highlight true))))
                              ;;                     (reset! (::highlighted-item state) (assoc item :mouse-enter-triggered-highlight true))))
@@ -805,12 +824,20 @@
       (and enter? (not composing?)) (do
       (and enter? (not composing?)) (do
                                       (handle-action :default state e)
                                       (handle-action :default state e)
                                       (util/stop-propagation e))
                                       (util/stop-propagation e))
-      esc? (let [filter' @(::filter state)]
-             (if filter'
+      esc? (let [filter' @(::filter state)
+                 action (get-action)
+                 move-blocks? (= :move-blocks action)]
+             (cond
+               (and move-blocks? (string/blank? input))
+               (state/close-modal!)
+
+               (and filter' (not move-blocks?))
                (do
                (do
                  (util/stop e)
                  (util/stop e)
                  (reset! (::filter state) nil)
                  (reset! (::filter state) nil)
                  (load-results :default state))
                  (load-results :default state))
+
+               :else
                (when-not (string/blank? input)
                (when-not (string/blank? input)
                  (util/stop e)
                  (util/stop e)
                  (handle-input-change state nil ""))))
                  (handle-input-change state nil ""))))
@@ -831,12 +858,15 @@
 (defn- input-placeholder
 (defn- input-placeholder
   [sidebar?]
   [sidebar?]
   (let [search-mode (:search/mode @state/state)
   (let [search-mode (:search/mode @state/state)
-        search-args (:search/args @state/state)]
+        action (get-action)]
     (cond
     (cond
+      (= action :move-blocks)
+      "Move blocks to"
+
       (and (= search-mode :graph) (not sidebar?))
       (and (= search-mode :graph) (not sidebar?))
       "Add graph filter"
       "Add graph filter"
 
 
-      (= search-args :new-page)
+      (= action :new-page)
       "Type a page name to create"
       "Type a page name to create"
 
 
       :else
       :else
@@ -1000,8 +1030,10 @@
        (shortcut/listen-all!))
        (shortcut/listen-all!))
      state)}
      state)}
   {:init (fn [state]
   {:init (fn [state]
-           (let [search-mode (:search/mode @state/state)
+           (let [search-mode (or (:search/mode @state/state) :global)
                  opts (last (:rum/args state))]
                  opts (last (:rum/args state))]
+             (when (nil? search-mode)
+               (state/set-state! :search/mode :global))
              (assoc state
              (assoc state
                     ::ref (atom nil)
                     ::ref (atom nil)
                     ::filter (if (and search-mode
                     ::filter (if (and search-mode
@@ -1032,8 +1064,10 @@
   (rum/local false ::input-changed?)
   (rum/local false ::input-changed?)
   [state {:keys [sidebar?] :as opts}]
   [state {:keys [sidebar?] :as opts}]
   (let [*input (::input state)
   (let [*input (::input state)
-        search-mode (:search/mode @state/state)
-        group-filter (:group (rum/react (::filter state)))
+        search-mode (state/sub :search/mode)
+        group-filter (or (when (and (not (contains? #{:global :graph} search-mode)) (not (:sidebar? opts)))
+                           search-mode)
+                         (:group (rum/react (::filter state))))
         results-ordered (state->results-ordered state search-mode)
         results-ordered (state->results-ordered state search-mode)
         all-items (mapcat last results-ordered)
         all-items (mapcat last results-ordered)
         first-item (first all-items)]
         first-item (first all-items)]
@@ -1072,7 +1106,8 @@
      (when-not sidebar? (hints state))]))
      (when-not sidebar? (hints state))]))
 
 
 (rum/defc cmdk-modal [props]
 (rum/defc cmdk-modal [props]
-  [:div {:class "cp__cmdk__modal rounded-lg w-[90dvw] max-w-4xl relative"}
+  [:div {:class "cp__cmdk__modal rounded-lg w-[90dvw] max-w-4xl relative"
+         :data-keep-selection true}
    (cmdk props)])
    (cmdk props)])
 
 
 (rum/defc cmdk-block [props]
 (rum/defc cmdk-block [props]

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

@@ -1019,7 +1019,8 @@
        :on-pointer-up (fn []
        :on-pointer-up (fn []
                         (when-let [container (gdom/getElement "app-container-wrapper")]
                         (when-let [container (gdom/getElement "app-container-wrapper")]
                           (d/remove-class! container "blocks-selection-mode")
                           (d/remove-class! container "blocks-selection-mode")
-                          (when (> (count (state/get-selection-blocks)) 1)
+                          (when (and (> (count (state/get-selection-blocks)) 1)
+                                     (not (util/input? js/document.activeElement)))
                             (util/clear-selection!))))}
                             (util/clear-selection!))))}
 
 
       [:button#skip-to-main
       [:button#skip-to-main

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

@@ -403,7 +403,8 @@
          (when current-repo
          (when current-repo
            (ui/with-shortcut :go/search "right"
            (ui/with-shortcut :go/search "right"
              [:button.button.icon#search-button
              [:button.button.icon#search-button
-              {:title (t :header/search)
+              {:data-keep-selection true
+               :title (t :header/search)
                :on-click #(do (when (or (mobile-util/native-android?)
                :on-click #(do (when (or (mobile-util/native-android?)
                                         (mobile-util/native-iphone?))
                                         (mobile-util/native-iphone?))
                                 (state/set-left-sidebar-open! false))
                                 (state/set-left-sidebar-open! false))

+ 2 - 7
src/main/frontend/components/library.cljs

@@ -7,7 +7,6 @@
             [frontend.search :as search]
             [frontend.search :as search]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.ui :as ui]
-            [logseq.db :as ldb]
             [logseq.shui.hooks :as hooks]
             [logseq.shui.hooks :as hooks]
             [logseq.shui.ui :as shui]
             [logseq.shui.ui :as shui]
             [promesa.core :as p]
             [promesa.core :as p]
@@ -39,12 +38,8 @@
       :selected-choices selected-choices
       :selected-choices selected-choices
       :on-chosen (fn [chosen selected?]
       :on-chosen (fn [chosen selected?]
                    (if selected?
                    (if selected?
-                     (let [last-child (->> (:block/_parent (db/entity (:db/id library-page)))
-                                           ldb/sort-by-order
-                                           last)
-                           target (or last-child library-page)
-                           chosen-block (db/entity chosen)]
-                       (editor-handler/move-blocks! [chosen-block] target (if last-child true false))
+                     (let [chosen-block (db/entity chosen)]
+                       (editor-handler/move-blocks! [chosen-block] library-page {:bottom? true})
                        (set-selected-choices! (conj selected-choices chosen)))
                        (set-selected-choices! (conj selected-choices chosen)))
                      (do
                      (do
                        (db/transact! (state/get-current-repo)
                        (db/transact! (state/get-current-repo)

+ 2 - 0
src/main/frontend/components/property/dialog.cljs

@@ -14,6 +14,7 @@
                  k (:property-key opts)]
                  k (:property-key opts)]
              (when-let [view-selected-blocks (:selected-blocks opts)]
              (when-let [view-selected-blocks (:selected-blocks opts)]
                (state/set-state! :view/selected-blocks view-selected-blocks))
                (state/set-state! :view/selected-blocks view-selected-blocks))
+             (state/set-state! :ui/show-property-dialog? true)
              (assoc state
              (assoc state
                     ::property-key (atom k)
                     ::property-key (atom k)
                     ::property (atom (when k (db/get-case-page k))))))
                     ::property (atom (when k (db/get-case-page k))))))
@@ -21,6 +22,7 @@
                    (when-let [close-fn (:on-dialog-close (last (:rum/args state)))]
                    (when-let [close-fn (:on-dialog-close (last (:rum/args state)))]
                      (close-fn))
                      (close-fn))
                    (state/set-state! :view/selected-blocks nil)
                    (state/set-state! :view/selected-blocks nil)
+                   (state/set-state! :ui/show-property-dialog? false)
                    state)}
                    state)}
   [state blocks opts]
   [state blocks opts]
   (when (seq blocks)
   (when (seq blocks)

+ 20 - 14
src/main/frontend/components/property/value.cljs

@@ -249,8 +249,7 @@
        (p/do!
        (p/do!
         (ui-outliner-tx/transact!
         (ui-outliner-tx/transact!
          {:outliner-op :save-block}
          {:outliner-op :save-block}
-         (doseq [block blocks]
-           (db-property-handler/delete-property-value! (:db/id block) (:db/ident property) value)))
+         (db-property-handler/batch-delete-property-value! (map :db/id blocks) (:db/ident property) value))
         (when (or (not many?)
         (when (or (not many?)
                   ;; values will be cleared
                   ;; values will be cleared
                   (and many? (<= (count (get block (:db/ident property))) 1)))
                   (and many? (<= (count (get block (:db/ident property))) 1)))
@@ -698,10 +697,13 @@
                   excluded-options)
                   excluded-options)
 
 
                 (contains? #{:class :property} property-type)
                 (contains? #{:class :property} property-type)
-                (let [classes (model/get-all-classes
-                               repo
-                               {:except-root-class? true
-                                :except-private-tags? (not (contains? #{:logseq.property/template-applied-to} (:db/ident property)))})]
+                (let [classes (cond->
+                               (model/get-all-classes
+                                repo
+                                {:except-root-class? true
+                                 :except-private-tags? (not (contains? #{:logseq.property/template-applied-to} (:db/ident property)))})
+                                (not (or (and (entity-util/page? block) (not (ldb/internal-page? block))) (:logseq.property/created-from-property block)))
+                                (conj (db/entity :logseq.class/Page)))]
                   (if (= property-type :class)
                   (if (= property-type :class)
                     classes
                     classes
                     (property-handler/get-class-property-choices)))
                     (property-handler/get-class-property-choices)))
@@ -771,7 +773,8 @@
                                 :label label
                                 :label label
                                 :value id
                                 :value id
                                 :disabled? (and tags? (contains?
                                 :disabled? (and tags? (contains?
-                                                       (set/union #{:logseq.class/Journal :logseq.class/Whiteboard} ldb/internal-tags)
+                                                       (set/union #{:logseq.class/Journal :logseq.class/Whiteboard}
+                                                                  (set/difference ldb/internal-tags #{:logseq.class/Page}))
                                                        (:db/ident node)))))) nodes)
                                                        (:db/ident node)))))) nodes)
         classes' (remove (fn [class] (= :logseq.class/Root (:db/ident class))) classes)
         classes' (remove (fn [class] (= :logseq.class/Root (:db/ident class))) classes)
         opts' (cond->
         opts' (cond->
@@ -788,13 +791,12 @@
                                               "Set alias"
                                               "Set alias"
                                               :else
                                               :else
                                               (str "Set " (:block/title property)))
                                               (str "Set " (:block/title property)))
-                 :show-new-when-not-exact-match? (if (or extends-property?
-                                                         ;; Don't allow creating private tags
-                                                         (and (= :block/tags (:db/ident property))
-                                                              (seq (set/intersection (set (map :db/ident classes'))
-                                                                                     ldb/private-tags))))
-                                                   false
-                                                   true)
+                 :show-new-when-not-exact-match? (not
+                                                  (or (and extends-property? (contains? (set children-pages) (:db/id block)))
+                                                      ;; Don't allow creating private tags
+                                                      (and (= :block/tags (:db/ident property))
+                                                           (seq (set/intersection (set (map :db/ident classes'))
+                                                                                  ldb/private-tags)))))
                  :extract-chosen-fn :value
                  :extract-chosen-fn :value
                  :extract-fn (fn [x] (or (:label-value x) (:label x)))
                  :extract-fn (fn [x] (or (:label-value x) (:label x)))
                  :input-opts input-opts
                  :input-opts input-opts
@@ -825,6 +827,10 @@
                                   (when-not add-tag-property?
                                   (when-not add-tag-property?
                                     (log/error :msg "No :db/id found or created for chosen" :chosen chosen)))))})
                                     (log/error :msg "No :db/id found or created for chosen" :chosen chosen)))))})
 
 
+                (= :block/tags (:db/ident property))
+                (assoc :exact-match-exclude-items
+                       (set (map (fn [ident] (:block/title (db/entity ident))) ldb/private-tags)))
+
                 (and (seq classes') (not tags-or-alias?))
                 (and (seq classes') (not tags-or-alias?))
                 (assoc
                 (assoc
                   ;; Provides additional completion for inline classes on new pages or objects
                   ;; Provides additional completion for inline classes on new pages or objects

+ 1 - 0
src/main/frontend/components/select.cljs

@@ -172,6 +172,7 @@
                                                                      (render-item result chosen? multiple-choices? *selected-choices)))
                                                                      (render-item result chosen? multiple-choices? *selected-choices)))
                                     :class             "cp__select-results"
                                     :class             "cp__select-results"
                                     :on-chosen         (fn [raw-chosen e]
                                     :on-chosen         (fn [raw-chosen e]
+                                                         (util/stop-propagation e)
                                                          (when clear-input-on-chosen?
                                                          (when clear-input-on-chosen?
                                                            (reset! *input ""))
                                                            (reset! *input ""))
                                                          (let [chosen (extract-chosen-fn raw-chosen)]
                                                          (let [chosen (extract-chosen-fn raw-chosen)]

+ 68 - 66
src/main/frontend/components/selection.cljs

@@ -9,79 +9,81 @@
             [logseq.shui.ui :as shui]
             [logseq.shui.ui :as shui]
             [rum.core :as rum]))
             [rum.core :as rum]))
 
 
-(rum/defc action-bar
+(rum/defc action-bar < rum/reactive
   [& {:keys [on-cut on-copy selected-blocks hide-dots? button-border?]
   [& {:keys [on-cut on-copy selected-blocks hide-dots? button-border?]
       :or {on-cut #(editor-handler/cut-selection-blocks true)}}]
       :or {on-cut #(editor-handler/cut-selection-blocks true)}}]
-  (let [selected-blocks (map (fn [block] (if (number? block) (db/entity block) block)) selected-blocks)
-        on-copy (if (and selected-blocks (nil? on-copy))
-                  #(editor-handler/copy-selection-blocks true {:selected-blocks selected-blocks})
-                  (or on-copy #(editor-handler/copy-selection-blocks true)))
-        button-opts {:variant :outline
-                     :size :sm
-                     :class (str "p-2 text-xs h-8"
-                                 (when-not button-border?
-                                   " !border-b-0"))}
-        db-graph? (config/db-based-graph?)]
-    [:div.selection-action-bar
-     (shui/button-group
-      ;; set tag
-      (when db-graph?
+  (when-not (or (state/sub :search/mode)
+                (state/sub :ui/show-property-dialog?))
+    (let [selected-blocks (map (fn [block] (if (number? block) (db/entity block) block)) selected-blocks)
+          on-copy (if (and selected-blocks (nil? on-copy))
+                    #(editor-handler/copy-selection-blocks true {:selected-blocks selected-blocks})
+                    (or on-copy #(editor-handler/copy-selection-blocks true)))
+          button-opts {:variant :outline
+                       :size :sm
+                       :class (str "p-2 text-xs h-8"
+                                   (when-not button-border?
+                                     " !border-b-0"))}
+          db-graph? (config/db-based-graph?)]
+      [:div.selection-action-bar
+       (shui/button-group
+        ;; set tag
+        (when db-graph?
+          (shui/button
+           (assoc button-opts
+                  :on-pointer-down (fn [e]
+                                     (util/stop e)
+                                     (state/pub-event! [:editor/new-property {:target (.-target e)
+                                                                              :selected-blocks selected-blocks
+                                                                              :property-key "Tags"
+                                                                              :on-dialog-close #(state/pub-event! [:editor/hide-action-bar])}])))
+           (ui/tooltip (ui/icon "hash" {:size 13}) "Set tag"
+                       {:trigger-props {:class "flex"}})))
         (shui/button
         (shui/button
          (assoc button-opts
          (assoc button-opts
                 :on-pointer-down (fn [e]
                 :on-pointer-down (fn [e]
                                    (util/stop e)
                                    (util/stop e)
-                                   (state/pub-event! [:editor/new-property {:target (.-target e)
-                                                                            :selected-blocks selected-blocks
-                                                                            :property-key "Tags"
-                                                                            :on-dialog-close #(state/pub-event! [:editor/hide-action-bar])}])))
-         (ui/tooltip (ui/icon "hash" {:size 13}) "Set tag"
-                     {:trigger-props {:class "flex"}})))
-      (shui/button
-       (assoc button-opts
-              :on-pointer-down (fn [e]
-                                 (util/stop e)
-                                 (on-copy)
-                                 (state/clear-selection!)
-                                 (state/pub-event! [:editor/hide-action-bar])))
-       "Copy")
-      (when db-graph?
+                                   (on-copy)
+                                   (state/clear-selection!)
+                                   (state/pub-event! [:editor/hide-action-bar])))
+         "Copy")
+        (when db-graph?
+          (shui/button
+           (assoc button-opts
+                  :on-pointer-down (fn [e]
+                                     (util/stop e)
+                                     (state/pub-event! [:editor/new-property {:target (.-target e)
+                                                                              :selected-blocks selected-blocks
+                                                                              :on-dialog-close #(state/pub-event! [:editor/hide-action-bar])}])))
+           "Set property"))
+        (when db-graph?
+          (shui/button
+           (assoc button-opts
+                  :on-pointer-down (fn [e]
+                                     (util/stop e)
+                                     (state/pub-event! [:editor/new-property {:target (.-target e)
+                                                                              :selected-blocks selected-blocks
+                                                                              :remove-property? true
+                                                                              :select-opts {:show-new-when-not-exact-match? false}
+                                                                              :on-dialog-close #(state/pub-event! [:editor/hide-action-bar])}])))
+           "Unset property"))
         (shui/button
         (shui/button
          (assoc button-opts
          (assoc button-opts
                 :on-pointer-down (fn [e]
                 :on-pointer-down (fn [e]
                                    (util/stop e)
                                    (util/stop e)
-                                   (state/pub-event! [:editor/new-property {:target (.-target e)
-                                                                            :selected-blocks selected-blocks
-                                                                            :on-dialog-close #(state/pub-event! [:editor/hide-action-bar])}])))
-         "Set property"))
-      (when db-graph?
-        (shui/button
-         (assoc button-opts
-                :on-pointer-down (fn [e]
-                                   (util/stop e)
-                                   (state/pub-event! [:editor/new-property {:target (.-target e)
-                                                                            :selected-blocks selected-blocks
-                                                                            :remove-property? true
-                                                                            :select-opts {:show-new-when-not-exact-match? false}
-                                                                            :on-dialog-close #(state/pub-event! [:editor/hide-action-bar])}])))
-         "Unset property"))
-      (shui/button
-       (assoc button-opts
-              :on-pointer-down (fn [e]
-                                 (util/stop e)
-                                 (on-cut)
-                                 (state/pub-event! [:editor/hide-action-bar])))
-       (ui/icon "trash" {:size 13}))
-      (when-not hide-dots?
-        (shui/button
-         (assoc button-opts
-                :on-pointer-down (fn [e]
-                                   (util/stop e)
-                                   (shui/popup-hide!)
-                                   (shui/popup-show! e
-                                                     (fn [{:keys [id]}]
-                                                       [:div {:on-click #(shui/popup-hide! id)
-                                                              :data-keep-selection true}
-                                                        ((state/get-component :selection/context-menu))])
-                                                     {:content-props {:class "w-[280px] ls-context-menu-content"}
-                                                      :as-dropdown? true})))
-         (ui/icon "dots" {:size 13}))))]))
+                                   (on-cut)
+                                   (state/pub-event! [:editor/hide-action-bar])))
+         (ui/icon "trash" {:size 13}))
+        (when-not hide-dots?
+          (shui/button
+           (assoc button-opts
+                  :on-pointer-down (fn [e]
+                                     (util/stop e)
+                                     (shui/popup-hide!)
+                                     (shui/popup-show! e
+                                                       (fn [{:keys [id]}]
+                                                         [:div {:on-click #(shui/popup-hide! id)
+                                                                :data-keep-selection true}
+                                                          ((state/get-component :selection/context-menu))])
+                                                       {:content-props {:class "w-[280px] ls-context-menu-content"}
+                                                        :as-dropdown? true})))
+           (ui/icon "dots" {:size 13}))))])))

+ 6 - 0
src/main/frontend/handler/db_based/property.cljs

@@ -37,6 +37,12 @@
    {:outliner-op :delete-property-value}
    {:outliner-op :delete-property-value}
    (outliner-op/delete-property-value! block-id property-id property-value)))
    (outliner-op/delete-property-value! block-id property-id property-value)))
 
 
+(defn batch-delete-property-value!
+  [block-ids property-id property-value]
+  (ui-outliner-tx/transact!
+   {:outliner-op :batch-delete-property-value}
+   (outliner-op/batch-delete-property-value! block-ids property-id property-value)))
+
 (defn create-property-text-block!
 (defn create-property-text-block!
   [block-id property-id value opts]
   [block-id property-id value opts]
   (ui-outliner-tx/transact!
   (ui-outliner-tx/transact!

+ 5 - 4
src/main/frontend/handler/dnd.cljs

@@ -59,12 +59,13 @@
                     (:block/uuid (ldb/get-left-sibling target-block)))]
                     (:block/uuid (ldb/get-left-sibling target-block)))]
              (if first-child?
              (if first-child?
                (when-let [parent (:block/parent target-block)]
                (when-let [parent (:block/parent target-block)]
-                 (outliner-op/move-blocks! blocks' parent false))
+                 (outliner-op/move-blocks! blocks' parent {:sibling? false}))
                (if-let [before-node (ldb/get-left-sibling target-block)]
                (if-let [before-node (ldb/get-left-sibling target-block)]
-                 (outliner-op/move-blocks! blocks' before-node true)
+                 (outliner-op/move-blocks! blocks' before-node {:sibling? true})
                  (when-let [parent (:block/parent target-block)]
                  (when-let [parent (:block/parent target-block)]
-                   (outliner-op/move-blocks! blocks' parent false)))))
-           (outliner-op/move-blocks! blocks' target-block (not nested?)))))
+                   (outliner-op/move-blocks! blocks' parent {:sibling? false})))))
+           (outliner-op/move-blocks! blocks' target-block
+                                     {:sibling? (not nested?)}))))
 
 
       :else
       :else
       nil)))
       nil)))

+ 46 - 18
src/main/frontend/handler/editor.cljs

@@ -860,7 +860,7 @@
                        (ui-outliner-tx/transact!
                        (ui-outliner-tx/transact!
                         transact-opts
                         transact-opts
                         (when (seq children)
                         (when (seq children)
-                          (outliner-op/move-blocks! children prev-block false))
+                          (outliner-op/move-blocks! children prev-block {:sibling? false}))
                         (delete-block-aux! block)
                         (delete-block-aux! block)
                         (save-block! repo prev-block new-content {})))))
                         (save-block! repo prev-block new-content {})))))
 
 
@@ -870,11 +870,29 @@
                    (delete-block-aux! block)))))))))))
                    (delete-block-aux! block)))))))))))
 
 
 (defn move-blocks!
 (defn move-blocks!
-  [blocks target sibling?]
+  [blocks target opts]
   (when (seq blocks)
   (when (seq blocks)
     (ui-outliner-tx/transact!
     (ui-outliner-tx/transact!
      {:outliner-op :move-blocks}
      {:outliner-op :move-blocks}
-     (outliner-op/move-blocks! blocks target sibling?))))
+     (outliner-op/move-blocks! blocks target opts))))
+
+(defn move-selected-blocks
+  [e]
+  (util/stop e)
+  (let [block-ids (or (seq (state/get-selection-block-ids))
+                      (when-let [id (:block/uuid (state/get-edit-block))]
+                        [id]))]
+    (if (seq block-ids)
+      (let [blocks (->> (map (fn [id] (db/entity [:block/uuid id])) block-ids)
+                        block-handler/get-top-level-blocks)]
+        (route-handler/go-to-search! :nodes
+                                     {:action :move-blocks
+                                      :blocks blocks
+                                      :trigger (fn [chosen]
+                                                 (state/pub-event! [:editor/hide-action-bar])
+                                                 (state/clear-selection!)
+                                                 (move-blocks! blocks (:source-page chosen) {:bottom? true}))}))
+      (notification/show! "There's no block selected, please select blocks first." :warning))))
 
 
 (defn delete-block!
 (defn delete-block!
   [repo]
   [repo]
@@ -1284,6 +1302,10 @@
   (some->> (shui-popup/get-popups)
   (some->> (shui-popup/get-popups)
            (some #(some-> % (:id) (str) (string/includes? (str id))))))
            (some #(some-> % (:id) (str) (string/includes? (str id))))))
 
 
+(defn dialog-exists?
+  [id]
+  (shui-dialog/get-modal id))
+
 (defn show-action-bar!
 (defn show-action-bar!
   [& {:keys [delay]
   [& {:keys [delay]
       :or {delay 200}}]
       :or {delay 200}}]
@@ -1644,7 +1666,12 @@
 (defn get-matched-classes
 (defn get-matched-classes
   "Return matched classes except the root tag"
   "Return matched classes except the root tag"
   [q]
   [q]
-  (let [classes (->> (db-model/get-all-classes (state/get-current-repo) {:except-root-class? true})
+  (let [editing-block (some-> (state/get-edit-block) :db/id db/entity)
+        non-page-block? (and editing-block (not (ldb/page? editing-block)))
+        all-classes (cond-> (db-model/get-all-classes (state/get-current-repo) {:except-root-class? true})
+                      non-page-block?
+                      (conj (db/entity :logseq.class/Page)))
+        classes (->> all-classes
                      (mapcat (fn [class]
                      (mapcat (fn [class]
                                (conj (:block/alias class) class)))
                                (conj (:block/alias class) class)))
                      (common-util/distinct-by :db/id)
                      (common-util/distinct-by :db/id)
@@ -3340,18 +3367,19 @@
 
 
 (defn open-selected-block!
 (defn open-selected-block!
   [direction e]
   [direction e]
-  (let [selected-blocks (state/get-selection-blocks)
-        f (case direction :left first :right last)
-        node (some-> selected-blocks f)]
-    (if (some-> node (dom/has-class? "block-add-button"))
-      (.click node)
-      (when-let [block-id (some-> node (dom/attr "blockid") uuid)]
-        (util/stop e)
-        (let [block {:block/uuid block-id}
-              left? (= direction :left)
-              opts {:container-id (some-> node (dom/attr "containerid") (parse-long))
-                    :event e}]
-          (edit-block! block (if left? 0 :max) opts))))))
+  (when-not (auto-complete?)
+    (let [selected-blocks (state/get-selection-blocks)
+          f (case direction :left first :right last)
+          node (some-> selected-blocks f)]
+      (if (some-> node (dom/has-class? "block-add-button"))
+        (.click node)
+        (when-let [block-id (some-> node (dom/attr "blockid") uuid)]
+          (util/stop e)
+          (let [block {:block/uuid block-id}
+                left? (= direction :left)
+                opts {:container-id (some-> node (dom/attr "containerid") (parse-long))
+                      :event e}]
+            (edit-block! block (if left? 0 :max) opts)))))))
 
 
 (defn shortcut-left-right [direction]
 (defn shortcut-left-right [direction]
   (fn [e]
   (fn [e]
@@ -3960,8 +3988,8 @@
         (p/do!
         (p/do!
          (when (seq children)
          (when (seq children)
            (if-let [today-last-child (last (ldb/sort-by-order (:block/_parent today)))]
            (if-let [today-last-child (last (ldb/sort-by-order (:block/_parent today)))]
-             (move-blocks! children today-last-child true)
-             (move-blocks! children today false)))
+             (move-blocks! children today-last-child {:sibling? true})
+             (move-blocks! children today {:sibling? false})))
          (state/close-modal!)
          (state/close-modal!)
          (mobile-state/set-popup! nil)
          (mobile-state/set-popup! nil)
          (when (seq children)
          (when (seq children)

+ 9 - 9
src/main/frontend/handler/events/ui.cljs

@@ -44,13 +44,14 @@
             [promesa.core :as p]))
             [promesa.core :as p]))
 
 
 (defmethod events/handle :go/search [_]
 (defmethod events/handle :go/search [_]
-  (shui/dialog-open!
-   cmdk/cmdk-modal
-   {:id :ls-dialog-cmdk
-    :align :top
-    :content-props {:class "ls-dialog-cmdk"}
-    :close-btn? false
-    :onEscapeKeyDown (fn [e] (.preventDefault e))}))
+  (when-not (editor-handler/dialog-exists? :ls-dialog-cmdk)
+    (shui/dialog-open!
+     cmdk/cmdk-modal
+     {:id :ls-dialog-cmdk
+      :align :top
+      :content-props {:class "ls-dialog-cmdk"}
+      :close-btn? false
+      :onEscapeKeyDown (fn [e] (.preventDefault e))})))
 
 
 (defmethod events/handle :command/run [_]
 (defmethod events/handle :command/run [_]
   (when (util/electron?)
   (when (util/electron?)
@@ -233,8 +234,7 @@
         (if target'
         (if target'
           (shui/popup-show! target'
           (shui/popup-show! target'
                             #(property-dialog/dialog blocks opts')
                             #(property-dialog/dialog blocks opts')
-                            {:align "start"
-                             :auto-focus? true})
+                            {:align "start"})
           (shui/dialog-open! #(property-dialog/dialog blocks opts')
           (shui/dialog-open! #(property-dialog/dialog blocks opts')
                              {:id :property-dialog
                              {:id :property-dialog
                               :align "start"}))))))
                               :align "start"}))))))

+ 3 - 3
src/main/frontend/log.cljs

@@ -1,8 +1,8 @@
 (ns frontend.log
 (ns frontend.log
   "System-component-like ns that encapsulates logging functionality"
   "System-component-like ns that encapsulates logging functionality"
-  (:require [lambdaisland.glogi :as log]
-            [lambdaisland.glogi.console :as glogi-console]
-            [frontend.config :as config]))
+  (:require [frontend.config :as config]
+            [lambdaisland.glogi :as log]
+            [lambdaisland.glogi.console :as glogi-console]))
 
 
 ;; TODO: Move code below into a fn to behave like a system component
 ;; TODO: Move code below into a fn to behave like a system component
 ;; instead of having no control over its behavior at require time
 ;; instead of having no control over its behavior at require time

+ 7 - 2
src/main/frontend/modules/outliner/op.cljs

@@ -35,11 +35,11 @@
        [:delete-blocks [ids opts]]))))
        [:delete-blocks [ids opts]]))))
 
 
 (defn move-blocks!
 (defn move-blocks!
-  [blocks target-block sibling?]
+  [blocks target-block opts]
   (op-transact!
   (op-transact!
    (let [ids (map :db/id blocks)
    (let [ids (map :db/id blocks)
          target-id (:db/id target-block)]
          target-id (:db/id target-block)]
-     [:move-blocks [ids target-id sibling?]])))
+     [:move-blocks [ids target-id opts]])))
 
 
 (defn move-blocks-up-down!
 (defn move-blocks-up-down!
   [blocks up?]
   [blocks up?]
@@ -73,6 +73,11 @@
   (op-transact!
   (op-transact!
    [:delete-property-value [block-eid property-id property-value]]))
    [:delete-property-value [block-eid property-id property-value]]))
 
 
+(defn batch-delete-property-value!
+  [block-eids property-id property-value]
+  (op-transact!
+   [:batch-delete-property-value [block-eids property-id property-value]]))
+
 (defn create-property-text-block!
 (defn create-property-text-block!
   [block-id property-id value opts]
   [block-id property-id value opts]
   (op-transact!
   (op-transact!

+ 12 - 1
src/main/frontend/modules/shortcut/config.cljs

@@ -290,6 +290,8 @@
 
 
    :editor/move-block-down                  {:binding (if mac? "mod+shift+down" "alt+shift+down")
    :editor/move-block-down                  {:binding (if mac? "mod+shift+down" "alt+shift+down")
                                              :fn      (editor-handler/move-up-down false)}
                                              :fn      (editor-handler/move-up-down false)}
+   :editor/move-blocks                      {:binding "mod+shift+m"
+                                             :fn      editor-handler/move-selected-blocks}
 
 
    :editor/open-edit                        {:binding "enter"
    :editor/open-edit                        {:binding "enter"
                                              :fn      (fn [e]
                                              :fn      (fn [e]
@@ -365,6 +367,11 @@
                                              :fn      (fn [e]
                                              :fn      (fn [e]
                                                         (when e (util/stop e))
                                                         (when e (util/stop e))
                                                         (state/pub-event! [:editor/new-property {}]))}
                                                         (state/pub-event! [:editor/new-property {}]))}
+   :editor/set-tags                         {:binding "p t"
+                                             :db-graph? true
+                                             :selection? true
+                                             :fn      (fn []
+                                                        (state/pub-event! [:editor/new-property {:property-key "Tags"}]))}
 
 
    :editor/add-property-deadline            {:binding "p d"
    :editor/add-property-deadline            {:binding "p d"
                                              :db-graph? true
                                              :db-graph? true
@@ -390,7 +397,7 @@
                                              :fn      (fn []
                                              :fn      (fn []
                                                         (state/pub-event! [:editor/new-property {:property-key "Icon"}]))}
                                                         (state/pub-event! [:editor/new-property {:property-key "Icon"}]))}
 
 
-   :editor/toggle-display-all-properties    {:binding "p t"
+   :editor/toggle-display-all-properties    {:binding "p a"
                                              :db-graph? true
                                              :db-graph? true
                                              :fn      ui-handler/toggle-show-empty-hidden-properties!}
                                              :fn      ui-handler/toggle-show-empty-hidden-properties!}
 
 
@@ -792,6 +799,7 @@
           :editor/select-down
           :editor/select-down
           :editor/move-block-up
           :editor/move-block-up
           :editor/move-block-down
           :editor/move-block-down
+          :editor/move-blocks
           :editor/open-edit
           :editor/open-edit
           :editor/open-selected-blocks-in-sidebar
           :editor/open-selected-blocks-in-sidebar
           :editor/select-block-up
           :editor/select-block-up
@@ -862,6 +870,7 @@
           :editor/copy-current-file
           :editor/copy-current-file
           :editor/copy-page-url
           :editor/copy-page-url
           :editor/new-whiteboard
           :editor/new-whiteboard
+          :editor/set-tags
           :editor/add-property-deadline
           :editor/add-property-deadline
           :editor/add-property-status
           :editor/add-property-status
           :editor/add-property-priority
           :editor/add-property-priority
@@ -968,6 +977,7 @@
      :editor/open-link-in-sidebar
      :editor/open-link-in-sidebar
      :editor/move-block-up
      :editor/move-block-up
      :editor/move-block-down
      :editor/move-block-down
+     :editor/move-blocks
      :editor/escape-editing]
      :editor/escape-editing]
 
 
     :shortcut.category/block-command-editing
     :shortcut.category/block-command-editing
@@ -996,6 +1006,7 @@
      :editor/select-block-down
      :editor/select-block-down
      :editor/delete-selection
      :editor/delete-selection
      :editor/add-property
      :editor/add-property
+     :editor/set-tags
      :editor/add-property-deadline
      :editor/add-property-deadline
      :editor/add-property-status
      :editor/add-property-status
      :editor/add-property-priority
      :editor/add-property-priority

+ 1 - 0
src/main/frontend/state.cljs

@@ -103,6 +103,7 @@
 
 
       ;; ui
       ;; ui
       :ui/viewport                           {}
       :ui/viewport                           {}
+      :ui/show-property-dialog?              (atom false)
 
 
       ;; left sidebar
       ;; left sidebar
       :ui/navigation-item-collapsed?         {}
       :ui/navigation-item-collapsed?         {}

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

@@ -295,7 +295,8 @@
               (icon "info-circle" {:class "text-indigo-500" :size "20"}))
               (icon "info-circle" {:class "text-indigo-500" :size "20"}))
             status)]
             status)]
       [:div.ui__notifications-content
       [:div.ui__notifications-content
-       {:style
+       {:class (str "notification-" (name (or status :info)))
+        :style
         (when (or (= state "exiting")
         (when (or (= state "exiting")
                   (= state "exited"))
                   (= state "exited"))
           {:z-index -1})}
           {:z-index -1})}

+ 2 - 1
src/main/frontend/worker/handler/page.cljs

@@ -21,7 +21,8 @@
                                                    {:page-uuid uuid
                                                    {:page-uuid uuid
                                                     :skip-existing-page-check? true})
                                                     :skip-existing-page-check? true})
         result            (ldb/transact! conn [page] {:persist-op? false
         result            (ldb/transact! conn [page] {:persist-op? false
-                                                      :outliner-op :create-page})]
+                                                      :outliner-op :create-page
+                                                      :rtc-op? true})]
     [result page-name (:block/uuid page)]))
     [result page-name (:block/uuid page)]))
 
 
 (defn create!
 (defn create!

+ 86 - 7
src/main/frontend/worker/pipeline.cljs

@@ -12,6 +12,7 @@
             [logseq.common.util :as common-util]
             [logseq.common.util :as common-util]
             [logseq.common.uuid :as common-uuid]
             [logseq.common.uuid :as common-uuid]
             [logseq.db :as ldb]
             [logseq.db :as ldb]
+            [logseq.db.common.order :as db-order]
             [logseq.db.common.sqlite :as common-sqlite]
             [logseq.db.common.sqlite :as common-sqlite]
             [logseq.db.frontend.validate :as db-validate]
             [logseq.db.frontend.validate :as db-validate]
             [logseq.db.sqlite.export :as sqlite-export]
             [logseq.db.sqlite.export :as sqlite-export]
@@ -126,6 +127,62 @@
               (assert (= (count (distinct (map :block/order children))) (count children))
               (assert (= (count (distinct (map :block/order children))) (count children))
                       (str ":block/order is not unique for children blocks, parent id: " (:db/id parent))))))))))
                       (str ":block/order is not unique for children blocks, parent id: " (:db/id parent))))))))))
 
 
+(defn- toggle-page-and-block
+  [conn {:keys [db-before db-after tx-data tx-meta]}]
+  (when-not (:rtc-op? tx-meta)
+    (let [page-tag (d/entity @conn :logseq.class/Page)
+          library-page (ldb/get-library-page db-after)]
+      (mapcat
+       (fn [datom]
+         (let [id (:e datom)
+               page-tag-update? (and (= :block/tags (:a datom))
+                                     (= (:db/id page-tag) (:v datom)))
+               move-to-library? (and (= :block/parent (:a datom))
+                                     (= (:db/id library-page) (:v datom))
+                                     (:added datom))]
+           (when (or page-tag-update? move-to-library?)
+             (let [block-before (d/entity db-before id)
+                   block-after (d/entity db-after id)]
+               (when block-after
+                 (cond
+                   ;; move non-page block to Library
+                   (and move-to-library? (not (ldb/page? block-after)))
+                   [{:db/id id
+                     :block/name (common-util/page-name-sanity-lc (:block/title block-after))
+                     :block/tags :logseq.class/Page}
+                    [:db/retract id :block/page]]
+
+                   ;; block->page
+                   (and (:added datom) block-before (not (ldb/page? block-before))) ; block->page
+                   (let [->page-tx [{:db/id id
+                                     :block/name (common-util/page-name-sanity-lc (:block/title block-after))}
+                                    [:db/retract id :block/page]]
+                         move-parent-to-library-tx (let [block (d/entity db-after (:e datom))
+                                                         block-parent (:block/parent block)]
+                                                     (assert (ldb/page? block-parent))
+                                                     (when (and (nil? (:block/parent block-parent))
+                                                                block-parent
+                                                                (not= (:db/id block-parent) (:db/id library-page))
+                                                                (not (:db/ident block-parent))
+                                                                (not (ldb/built-in? block-parent)))
+                                                       [{:db/id (:db/id block-parent)
+                                                         :block/parent (:db/id (ldb/get-library-page db-after))
+                                                         :block/order (db-order/gen-key)}]))]
+                     (concat ->page-tx move-parent-to-library-tx))
+
+                   ;; page->block
+                   (and block-before (not (:added datom)) (ldb/internal-page? block-before))
+                   (let [parent (:block/parent block-before)
+                         parent-page (when parent
+                                       (loop [parent parent]
+                                         (if (ldb/page? parent)
+                                           parent
+                                           (recur (:block/parent parent)))))]
+                     (when parent-page
+                       [[:db/retract id :block/name]
+                        [:db/add id :block/page (:db/id parent-page)]]))))))))
+       tx-data))))
+
 (defn- add-missing-properties-to-typed-display-blocks
 (defn- add-missing-properties-to-typed-display-blocks
   "Add missing properties for these cases:
   "Add missing properties for these cases:
   1. Add corresponding tag when invoking commands like /code block.
   1. Add corresponding tag when invoking commands like /code block.
@@ -220,22 +277,44 @@
 (defn- compute-extra-tx-data
 (defn- compute-extra-tx-data
   [repo conn tx-report]
   [repo conn tx-report]
   (let [{:keys [db-before db-after tx-data tx-meta]} tx-report
   (let [{:keys [db-before db-after tx-data tx-meta]} tx-report
+        toggle-page-and-block-tx-data (toggle-page-and-block conn tx-report)
         display-blocks-tx-data (add-missing-properties-to-typed-display-blocks db-after tx-data)
         display-blocks-tx-data (add-missing-properties-to-typed-display-blocks db-after tx-data)
         commands-tx (when-not (or (:undo? tx-meta) (:redo? tx-meta) (:rtc-tx? tx-meta))
         commands-tx (when-not (or (:undo? tx-meta) (:redo? tx-meta) (:rtc-tx? tx-meta))
                       (commands/run-commands conn tx-report))
                       (commands/run-commands conn tx-report))
         insert-templates-tx (insert-tag-templates repo tx-report)
         insert-templates-tx (insert-tag-templates repo tx-report)
         created-by-tx (add-created-by-ref-hook db-before db-after tx-data tx-meta)]
         created-by-tx (add-created-by-ref-hook db-before db-after tx-data tx-meta)]
-    (concat display-blocks-tx-data commands-tx insert-templates-tx created-by-tx)))
+    (concat toggle-page-and-block-tx-data display-blocks-tx-data commands-tx insert-templates-tx created-by-tx)))
+
+(defn- undo-tx-data-if-disallowed!
+  [conn {:keys [tx-data]}]
+  (let [db @conn
+        page-has-block-parent? (some (fn [d] (and (:added d)
+                                                  (= :block/parent (:a d))
+                                                  (ldb/page? (d/entity db (:e d)))
+                                                  (not (ldb/page? (d/entity db (:v d)))))) tx-data)]
+    ;; TODO: add other cases that need to be undo
+    (when page-has-block-parent?
+      (let [reversed-tx-data (map (fn [[e a v _tx add?]]
+                                    (let [op (if add? :db/retract :db/add)]
+                                      [op e a v])) tx-data)]
+        (d/transact! conn reversed-tx-data {:op :undo-tx-data}))
+      (throw (ex-info "Page can't have block as parent"
+                      {:type :notification
+                       :payload {:message "Page can't have block as parent"
+                                 :type :warning}
+                       :tx-data tx-data})))))
 
 
 (defn- invoke-hooks-default
 (defn- invoke-hooks-default
   [repo conn {:keys [tx-meta] :as tx-report} context]
   [repo conn {:keys [tx-meta] :as tx-report} context]
+  ;; Notice: don't catch `undo-tx-data-if-disallowed!` since we want it failed immediately
+  (undo-tx-data-if-disallowed! conn tx-report)
   (try
   (try
-    (let [tx-before-refs (when (sqlite-util/db-based-graph? repo)
-                           (compute-extra-tx-data repo conn tx-report))
-          tx-report* (if (seq tx-before-refs)
-                       (let [result (ldb/transact! conn tx-before-refs {:pipeline-replace? true
-                                                                        :outliner-op :pre-hook-invoke
-                                                                        :skip-store? true})]
+    (let [extra-tx-data (when (sqlite-util/db-based-graph? repo)
+                          (compute-extra-tx-data repo conn tx-report))
+          tx-report* (if (seq extra-tx-data)
+                       (let [result (ldb/transact! conn extra-tx-data {:pipeline-replace? true
+                                                                       :outliner-op :pre-hook-invoke
+                                                                       :skip-store? true})]
                          (assoc tx-report
                          (assoc tx-report
                                 :tx-data (concat (:tx-data tx-report) (:tx-data result))
                                 :tx-data (concat (:tx-data tx-report) (:tx-data result))
                                 :db-after (:db-after result)))
                                 :db-after (:db-after result)))

+ 16 - 11
src/main/frontend/worker/rtc/remote_update.cljs

@@ -82,7 +82,8 @@ so need to pull earlier remote-data from websocket."})
                   nil sorted-order+block-uuid-coll)]
                   nil sorted-order+block-uuid-coll)]
           (index/generate-key-between start-order end-order)
           (index/generate-key-between start-order end-order)
           block-order)]
           block-order)]
-    (ldb/transact! conn [{:block/uuid block-uuid :block/order block-order*}])
+    (ldb/transact! conn [{:block/uuid block-uuid :block/order block-order*}]
+                   {:rtc-op? true})
     ;; TODO: add ops when block-order* != block-order
     ;; TODO: add ops when block-order* != block-order
     ))
     ))
 
 
@@ -113,7 +114,8 @@ so need to pull earlier remote-data from websocket."})
                            block-parent (assoc :block/parent [:block/uuid block-parent])))
                            block-parent (assoc :block/parent [:block/uuid block-parent])))
                        block-uuid+parent-coll)
                        block-uuid+parent-coll)
                  {:persist-op? false
                  {:persist-op? false
-                  :gen-undo-ops? false}))
+                  :gen-undo-ops? false
+                  :rtc-op? true}))
 
 
 (defmethod transact-db! :save-block [_ & args]
 (defmethod transact-db! :save-block [_ & args]
   (outliner-tx/transact!
   (outliner-tx/transact!
@@ -128,11 +130,13 @@ so need to pull earlier remote-data from websocket."})
   (ldb/transact! conn
   (ldb/transact! conn
                  (mapv (fn [block-uuid] [:db/retractEntity [:block/uuid block-uuid]]) block-uuids)
                  (mapv (fn [block-uuid] [:db/retractEntity [:block/uuid block-uuid]]) block-uuids)
                  {:persist-op? false
                  {:persist-op? false
-                  :gen-undo-ops? false}))
+                  :gen-undo-ops? false
+                  :rtc-op? true}))
 
 
 (defmethod transact-db! :upsert-whiteboard-block [_ conn blocks]
 (defmethod transact-db! :upsert-whiteboard-block [_ conn blocks]
   (ldb/transact! conn blocks {:persist-op? false
   (ldb/transact! conn blocks {:persist-op? false
-                              :gen-undo-ops? false}))
+                              :gen-undo-ops? false
+                              :rtc-op? true}))
 
 
 (defn- group-remote-remove-ops-by-whiteboard-block
 (defn- group-remote-remove-ops-by-whiteboard-block
   "return {true [<whiteboard-block-ops>], false [<other-ops>]}"
   "return {true [<whiteboard-block-ops>], false [<other-ops>]}"
@@ -174,7 +178,7 @@ so need to pull earlier remote-data from websocket."})
         (when-let [b (d/entity @conn [:block/uuid block-uuid])]
         (when-let [b (d/entity @conn [:block/uuid block-uuid])]
           (when-let [target-b
           (when-let [target-b
                      (d/entity @conn (:db/id (:block/page (d/entity @conn [:block/uuid block-uuid]))))]
                      (d/entity @conn (:db/id (:block/page (d/entity @conn [:block/uuid block-uuid]))))]
-            (transact-db! :move-blocks&persist-op repo conn [b] target-b false))))
+            (transact-db! :move-blocks&persist-op repo conn [b] target-b {:sibling? false}))))
       (doseq [block-uuid block-uuids-to-remove]
       (doseq [block-uuid block-uuids-to-remove]
         (when-let [b (d/entity @conn [:block/uuid block-uuid])]
         (when-let [b (d/entity @conn [:block/uuid block-uuid])]
           (transact-db! :delete-blocks repo conn date-formatter [b] {}))))))
           (transact-db! :delete-blocks repo conn date-formatter [b] {}))))))
@@ -189,7 +193,7 @@ so need to pull earlier remote-data from websocket."})
       (case [whiteboard-page-block? (some? local-parent) (some? remote-block-order)]
       (case [whiteboard-page-block? (some? local-parent) (some? remote-block-order)]
         [false true true]
         [false true true]
         (do (if move?
         (do (if move?
-              (transact-db! :move-blocks repo conn [b] local-parent false)
+              (transact-db! :move-blocks repo conn [b] local-parent {:sibling? false})
               (transact-db! :insert-blocks repo conn
               (transact-db! :insert-blocks repo conn
                             [{:block/uuid block-uuid
                             [{:block/uuid block-uuid
                               :block/title ""}]
                               :block/title ""}]
@@ -198,7 +202,8 @@ so need to pull earlier remote-data from websocket."})
 
 
         [false true false]
         [false true false]
         (if move?
         (if move?
-          (transact-db! :move-blocks repo conn [b] local-parent false)
+          (transact-db! :move-blocks repo conn [b] local-parent
+                        {:sibling? false})
           (transact-db! :insert-no-order-blocks conn [[block-uuid first-remote-parent]]))
           (transact-db! :insert-no-order-blocks conn [[block-uuid first-remote-parent]]))
 
 
         ([true false false] [true false true] [true true false] [true true true])
         ([true false false] [true false true] [true true false] [true true true])
@@ -494,15 +499,15 @@ so need to pull earlier remote-data from websocket."})
       (let [{update-block-order-tx-data :tx-data op-value :op-value} (update-block-order (:db/id ent) op-value)
       (let [{update-block-order-tx-data :tx-data op-value :op-value} (update-block-order (:db/id ent) op-value)
             first-remote-parent (first parents)
             first-remote-parent (first parents)
             local-parent (d/entity @conn [:block/uuid first-remote-parent])
             local-parent (d/entity @conn [:block/uuid first-remote-parent])
-            whiteboard-page-block? (ldb/whiteboard? local-parent)]
+            whiteboard-page-block? (ldb/whiteboard? local-parent)
+            tx-meta {:persist-op? false :gen-undo-ops? false :rtc-op? true}]
         (if whiteboard-page-block?
         (if whiteboard-page-block?
           (upsert-whiteboard-block repo conn op-value)
           (upsert-whiteboard-block repo conn op-value)
           (do (when-let [schema-tx-data (remote-op-value->schema-tx-data block-uuid op-value)]
           (do (when-let [schema-tx-data (remote-op-value->schema-tx-data block-uuid op-value)]
-                (ldb/transact! conn schema-tx-data {:persist-op? false :gen-undo-ops? false}))
+                (ldb/transact! conn schema-tx-data tx-meta))
               (when-let [tx-data (seq (remote-op-value->tx-data @conn ent (dissoc op-value :client/schema)
               (when-let [tx-data (seq (remote-op-value->tx-data @conn ent (dissoc op-value :client/schema)
                                                                 rtc-const/ignore-attrs-when-syncing))]
                                                                 rtc-const/ignore-attrs-when-syncing))]
-                (ldb/transact! conn (concat tx-data update-block-order-tx-data)
-                               {:persist-op? false :gen-undo-ops? false}))))))))
+                (ldb/transact! conn (concat tx-data update-block-order-tx-data) tx-meta))))))))
 
 
 (defn- apply-remote-update-ops
 (defn- apply-remote-update-ops
   [repo conn update-ops]
   [repo conn update-ops]

+ 7 - 3
src/main/frontend/worker/search.cljs

@@ -403,9 +403,13 @@ DROP TRIGGER IF EXISTS blocks_au;
                                 (let [{:keys [id page title snippet]} result
                                 (let [{:keys [id page title snippet]} result
                                       block-id (uuid id)]
                                       block-id (uuid id)]
                                   (when-let [block (d/entity @conn [:block/uuid block-id])]
                                   (when-let [block (d/entity @conn [:block/uuid block-id])]
-                                    (when-not (and library-page-search?
-                                                   (or (:block/parent block)
-                                                       (not (ldb/internal-page? block)))) ; remove pages that already have parents
+                                    (when-not (or
+                                               ;; remove pages that already have parents
+                                               (and library-page-search?
+                                                    (or (ldb/page-in-library? @conn block)
+                                                        (not (ldb/internal-page? block))))
+                                               ;; remove non-page blocks when asking for pages only
+                                               (and page-only? (not (ldb/page? block))))
                                       (when (if dev?
                                       (when (if dev?
                                               true
                                               true
                                               (if built-in?
                                               (if built-in?

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

@@ -718,57 +718,57 @@
 (defn ^:export insert_block
 (defn ^:export insert_block
   [block-uuid-or-page-name content ^js opts]
   [block-uuid-or-page-name content ^js opts]
   (this-as this
   (this-as this
-    (when (string/blank? block-uuid-or-page-name)
-      (throw (js/Error. "Page title or block UUID shouldn't be empty.")))
-
-    (p/let [block? (util/uuid-string? (str block-uuid-or-page-name))
-            block (<pull-block (str block-uuid-or-page-name))]
-      (if (and block? (not block))
-        (throw (js/Error. "Block not exists"))
-        (p/let [{:keys [before sibling focus customUUID properties autoOrderedList]} (bean/->clj opts)
-                [page-name block-uuid] (if (util/uuid-string? block-uuid-or-page-name)
-                                         [nil (uuid block-uuid-or-page-name)]
-                                         [block-uuid-or-page-name nil])
-                page-name (when page-name (util/page-name-sanity-lc page-name))
-                _ (when (and page-name
-                          (nil? (ldb/get-page (db/get-db) page-name)))
-                    (page-handler/<create! block-uuid-or-page-name {}))
-                custom-uuid (or customUUID (:id properties))
-                custom-uuid (when custom-uuid (sdk-utils/uuid-or-throw-error custom-uuid))
-                edit-block? (if (nil? focus) true focus)
-                _ (when (and custom-uuid (db-model/query-block-by-uuid custom-uuid))
-                    (throw (js/Error.
-                             (util/format "Custom block UUID already exists (%s)." custom-uuid))))
-                block-uuid' (if (and (not sibling) before block-uuid)
-                              (let [block (db/entity [:block/uuid block-uuid])
-                                    first-child (ldb/get-first-child (db/get-db) (:db/id block))]
-                                (if first-child
-                                  (:block/uuid first-child)
-                                  block-uuid))
-                              block-uuid)
-                insert-at-first-child? (not= block-uuid' block-uuid)
-                [sibling? before?] (if insert-at-first-child?
-                                     [true true]
-                                     [sibling before])
-                db-base? (db-graph?)
-                before? (if (and (false? sibling?) before? (not insert-at-first-child?))
-                          false
-                          before?)
-                new-block (editor-handler/api-insert-new-block!
-                            content
-                            {:block-uuid block-uuid'
-                             :sibling? sibling?
-                             :before? before?
-                             :edit-block? edit-block?
-                             :page page-name
-                             :custom-uuid custom-uuid
-                             :ordered-list? (if (boolean? autoOrderedList) autoOrderedList false)
-                             :properties (when (not db-base?)
-                                           (merge properties
-                                             (when custom-uuid {:id custom-uuid})))})
-                _ (when (and db-base? (some? properties))
-                    (api-block/save-db-based-block-properties! new-block properties this))]
-          (bean/->js (sdk-utils/normalize-keyword-for-json new-block)))))))
+           (when (string/blank? block-uuid-or-page-name)
+             (throw (js/Error. "Page title or block UUID shouldn't be empty.")))
+
+           (p/let [block? (util/uuid-string? (str block-uuid-or-page-name))
+                   block (<pull-block (str block-uuid-or-page-name))]
+             (if (and block? (not block))
+               (throw (js/Error. "Block not exists"))
+               (p/let [{:keys [before sibling focus customUUID properties autoOrderedList]} (bean/->clj opts)
+                       [page-name block-uuid] (if (util/uuid-string? block-uuid-or-page-name)
+                                                [nil (uuid block-uuid-or-page-name)]
+                                                [block-uuid-or-page-name nil])
+                       page-name (when page-name (util/page-name-sanity-lc page-name))
+                       _ (when (and page-name
+                                    (nil? (ldb/get-page (db/get-db) page-name)))
+                           (page-handler/<create! block-uuid-or-page-name {}))
+                       custom-uuid (or customUUID (:id properties))
+                       custom-uuid (when custom-uuid (sdk-utils/uuid-or-throw-error custom-uuid))
+                       edit-block? (if (nil? focus) true focus)
+                       _ (when (and custom-uuid (db-model/query-block-by-uuid custom-uuid))
+                           (throw (js/Error.
+                                   (util/format "Custom block UUID already exists (%s)." custom-uuid))))
+                       block-uuid' (if (and (not sibling) before block-uuid)
+                                     (let [block (db/entity [:block/uuid block-uuid])
+                                           first-child (ldb/get-first-child (db/get-db) (:db/id block))]
+                                       (if first-child
+                                         (:block/uuid first-child)
+                                         block-uuid))
+                                     block-uuid)
+                       insert-at-first-child? (not= block-uuid' block-uuid)
+                       [sibling? before?] (if insert-at-first-child?
+                                            [true true]
+                                            [sibling before])
+                       db-base? (db-graph?)
+                       before? (if (and (false? sibling?) before? (not insert-at-first-child?))
+                                 false
+                                 before?)
+                       new-block (editor-handler/api-insert-new-block!
+                                  content
+                                  {:block-uuid block-uuid'
+                                   :sibling? sibling?
+                                   :before? before?
+                                   :edit-block? edit-block?
+                                   :page page-name
+                                   :custom-uuid custom-uuid
+                                   :ordered-list? (if (boolean? autoOrderedList) autoOrderedList false)
+                                   :properties (when (not db-base?)
+                                                 (merge properties
+                                                        (when custom-uuid {:id custom-uuid})))})
+                       _ (when (and db-base? (some? properties))
+                           (api-block/save-db-based-block-properties! new-block properties this))]
+                 (bean/->js (sdk-utils/normalize-keyword-for-json new-block)))))))
 
 
 (def ^:export insert_batch_block
 (def ^:export insert_batch_block
   (fn [block-uuid ^js batch-blocks ^js opts]
   (fn [block-uuid ^js batch-blocks ^js opts]
@@ -892,11 +892,11 @@
 (defn ^:export get_property
 (defn ^:export get_property
   [k]
   [k]
   (this-as this
   (this-as this
-    (p/let [prop (-get-property this k)]
-      (some-> prop
-        (assoc :type (:logseq.property/type prop))
-        (sdk-utils/normalize-keyword-for-json)
-        (bean/->js)))))
+           (p/let [prop (-get-property this k)]
+             (some-> prop
+                     (assoc :type (:logseq.property/type prop))
+                     (sdk-utils/normalize-keyword-for-json)
+                     (bean/->js)))))
 
 
 (defn ^:export upsert_property
 (defn ^:export upsert_property
   "schema:
   "schema:
@@ -920,7 +920,7 @@
                        schema (cond-> schema
                        schema (cond-> schema
                                 (string? (:cardinality schema))
                                 (string? (:cardinality schema))
                                 (-> (assoc :db/cardinality (keyword (:cardinality schema)))
                                 (-> (assoc :db/cardinality (keyword (:cardinality schema)))
-                                  (dissoc :cardinality))
+                                    (dissoc :cardinality))
 
 
                                 (string? (:type schema))
                                 (string? (:type schema))
                                 (-> (assoc :logseq.property/type (keyword (:type schema)))
                                 (-> (assoc :logseq.property/type (keyword (:type schema)))
@@ -935,70 +935,70 @@
 (defn ^:export remove_property
 (defn ^:export remove_property
   [k]
   [k]
   (this-as this
   (this-as this
-    (p/let [prop (-get-property this k)]
-      (when-let [uuid (and (api-block/plugin-property-key? (:db/ident prop))
-                        (:block/uuid prop))]
-        (page-common-handler/<delete! uuid nil nil)))))
+           (p/let [prop (-get-property this k)]
+             (when-let [uuid (and (api-block/plugin-property-key? (:db/ident prop))
+                                  (:block/uuid prop))]
+               (page-common-handler/<delete! uuid nil nil)))))
 
 
 ;; block properties
 ;; block properties
 (defn ^:export upsert_block_property
 (defn ^:export upsert_block_property
   [block-uuid key ^js value]
   [block-uuid key ^js value]
   (this-as this
   (this-as this
-    (p/let [keyname (api-block/sanitize-user-property-name key)
-            block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
-            repo (state/get-current-repo)
-            block (db-async/<get-block repo block-uuid :children? false)
-            db-base? (db-graph?)
-            key' (-> (if (keyword? keyname) (name keyname) keyname) (util/trim-safe))
-            value (bean/->clj value)]
-      (when block
-        (if db-base?
-          (p/do!
-            (api-block/save-db-based-block-properties! block {key' value} this))
-          (property-handler/set-block-property! repo block-uuid key' value))))))
+           (p/let [keyname (api-block/sanitize-user-property-name key)
+                   block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
+                   repo (state/get-current-repo)
+                   block (db-async/<get-block repo block-uuid :children? false)
+                   db-base? (db-graph?)
+                   key' (-> (if (keyword? keyname) (name keyname) keyname) (util/trim-safe))
+                   value (bean/->clj value)]
+             (when block
+               (if db-base?
+                 (p/do!
+                  (api-block/save-db-based-block-properties! block {key' value} this))
+                 (property-handler/set-block-property! repo block-uuid key' value))))))
 
 
 (defn ^:export remove_block_property
 (defn ^:export remove_block_property
   [block-uuid key]
   [block-uuid key]
   (this-as this
   (this-as this
-    (p/let [key (api-block/sanitize-user-property-name key)
-            block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
-            _ (db-async/<get-block (state/get-current-repo) block-uuid :children? false)
-            db? (config/db-based-graph? (state/get-current-repo))
-            key-ns? (and (keyword? key) (namespace key))
-            key (if key-ns? key (if (keyword? key) (name key) key))
-            key (if (and db? (not key-ns?))
-                  (api-block/get-db-ident-for-user-property-name
-                    key (api-block/resolve-property-prefix-for-db this))
-                  key)]
-      (property-handler/remove-block-property!
-        (state/get-current-repo)
-        block-uuid key))))
+           (p/let [key (api-block/sanitize-user-property-name key)
+                   block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
+                   _ (db-async/<get-block (state/get-current-repo) block-uuid :children? false)
+                   db? (config/db-based-graph? (state/get-current-repo))
+                   key-ns? (and (keyword? key) (namespace key))
+                   key (if key-ns? key (if (keyword? key) (name key) key))
+                   key (if (and db? (not key-ns?))
+                         (api-block/get-db-ident-for-user-property-name
+                          key (api-block/resolve-property-prefix-for-db this))
+                         key)]
+             (property-handler/remove-block-property!
+              (state/get-current-repo)
+              block-uuid key))))
 
 
 (defn ^:export get_block_property
 (defn ^:export get_block_property
   [block-uuid key]
   [block-uuid key]
   (this-as this
   (this-as this
-    (p/let [block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
-            _ (db-async/<get-block (state/get-current-repo) block-uuid :children? false)]
-      (when-let [properties (some-> block-uuid (db-model/get-block-by-uuid) (:block/properties))]
-        (when (seq properties)
-          (let [key (api-block/sanitize-user-property-name key)
-                property-name (if (keyword? key) (name key) key)
-                ident (api-block/get-db-ident-for-user-property-name
-                        property-name (api-block/resolve-property-prefix-for-db this))
-                property-value (or (get properties key)
-                                 (get properties (keyword property-name))
-                                 (get properties ident))
-                property-value (if-let [property-id (:db/id property-value)]
-                                 (db/pull property-id) property-value)
-                property-value (cond-> property-value
-                                 (map? property-value)
-                                 (assoc
-                                   :value (or (:logseq.property/value property-value)
-                                            (:block/title property-value))
-                                   :ident ident))
-                parsed-value (api-block/parse-property-json-value-if-need ident property-value)]
-            (or parsed-value
-              (bean/->js (sdk-utils/normalize-keyword-for-json property-value)))))))))
+           (p/let [block-uuid (sdk-utils/uuid-or-throw-error block-uuid)
+                   _ (db-async/<get-block (state/get-current-repo) block-uuid :children? false)]
+             (when-let [properties (some-> block-uuid (db-model/get-block-by-uuid) (:block/properties))]
+               (when (seq properties)
+                 (let [key (api-block/sanitize-user-property-name key)
+                       property-name (if (keyword? key) (name key) key)
+                       ident (api-block/get-db-ident-for-user-property-name
+                              property-name (api-block/resolve-property-prefix-for-db this))
+                       property-value (or (get properties key)
+                                          (get properties (keyword property-name))
+                                          (get properties ident))
+                       property-value (if-let [property-id (:db/id property-value)]
+                                        (db/pull property-id) property-value)
+                       property-value (cond-> property-value
+                                        (map? property-value)
+                                        (assoc
+                                         :value (or (:logseq.property/value property-value)
+                                                    (:block/title property-value))
+                                         :ident ident))
+                       parsed-value (api-block/parse-property-json-value-if-need ident property-value)]
+                   (or parsed-value
+                       (bean/->js (sdk-utils/normalize-keyword-for-json property-value)))))))))
 
 
 (def ^:export get_block_properties
 (def ^:export get_block_properties
   (fn [block-uuid]
   (fn [block-uuid]

+ 58 - 58
src/main/logseq/api/block.cljs

@@ -24,7 +24,7 @@
 (defn convert?to-built-in-property-name
 (defn convert?to-built-in-property-name
   [property-name]
   [property-name]
   (if (and (not (qualified-keyword? property-name))
   (if (and (not (qualified-keyword? property-name))
-        (contains? #{:background-color} property-name))
+           (contains? #{:background-color} property-name))
     (keyword :logseq.property property-name)
     (keyword :logseq.property property-name)
     property-name))
     property-name))
 
 
@@ -32,19 +32,19 @@
   [k]
   [k]
   (if (string? k)
   (if (string? k)
     (-> k (string/trim)
     (-> k (string/trim)
-      (string/replace " " "")
-      (string/replace #"^[:_\s]+" "")
-      (#(cond-> %
-          (not (string/includes? % "/"))
-          (string/lower-case))))
+        (string/replace " " "")
+        (string/replace #"^[:_\s]+" "")
+        (#(cond-> %
+            (not (string/includes? % "/"))
+            (string/lower-case))))
     k))
     k))
 
 
 (defn resolve-property-prefix-for-db
 (defn resolve-property-prefix-for-db
   [^js plugin]
   [^js plugin]
   (->> (when (some-> js/window.LSPlugin (.-PluginLocal))
   (->> (when (some-> js/window.LSPlugin (.-PluginLocal))
          (or (some->> plugin (.-id) (sanitize-user-property-name) (str "."))
          (or (some->> plugin (.-id) (sanitize-user-property-name) (str "."))
-           "._api"))
-    (str "plugin.property")))
+             "._api"))
+       (str "plugin.property")))
 
 
 ;; FIXME: This ns should not be creating idents. This allows for ident conflicts
 ;; FIXME: This ns should not be creating idents. This allows for ident conflicts
 ;; and assumes that names directly map to idents which is incorrect and breaks for multiple
 ;; and assumes that names directly map to idents which is incorrect and breaks for multiple
@@ -58,27 +58,27 @@
                           (keyword property-name) property-name)
                           (keyword property-name) property-name)
          property-name' (convert?to-built-in-property-name property-name')]
          property-name' (convert?to-built-in-property-name property-name')]
      (if (qualified-keyword? property-name') property-name'
      (if (qualified-keyword? property-name') property-name'
-       (db-ident/create-db-ident-from-name prefix (name property-name) false)))))
+         (db-ident/create-db-ident-from-name prefix (name property-name) false)))))
 
 
 (defn plugin-property-key?
 (defn plugin-property-key?
   [ident]
   [ident]
   (some-> ident (str)
   (some-> ident (str)
-    (string/starts-with? ":plugin.property.")))
+          (string/starts-with? ":plugin.property.")))
 
 
 (defn into-readable-db-properties
 (defn into-readable-db-properties
   [properties]
   [properties]
   (some-> properties
   (some-> properties
-    (db-pu/readable-properties
-      {:original-key? true :key-fn str})))
+          (db-pu/readable-properties
+           {:original-key? true :key-fn str})))
 
 
 (defn into-properties
 (defn into-properties
   ([block] (into-properties (state/get-current-repo) block))
   ([block] (into-properties (state/get-current-repo) block))
   ([repo block]
   ([repo block]
    (if (some-> repo (config/db-based-graph?))
    (if (some-> repo (config/db-based-graph?))
      (let [props (some->> block
      (let [props (some->> block
-                   (filter (fn [[k _]] (db-property/property? k)))
-                   (into {})
-                   (into-readable-db-properties))
+                          (filter (fn [[k _]] (db-property/property? k)))
+                          (into {})
+                          (into-readable-db-properties))
            block (update block :block/properties merge props)
            block (update block :block/properties merge props)
            block (apply dissoc (concat [block] (keys props)))]
            block (apply dissoc (concat [block] (keys props)))]
        block)
        block)
@@ -87,8 +87,8 @@
 (defn parse-property-json-value-if-need
 (defn parse-property-json-value-if-need
   [ident property-value]
   [ident property-value]
   (when-let [prop (and (string? property-value)
   (when-let [prop (and (string? property-value)
-                    (plugin-property-key? ident)
-                    (some-> ident (db-utils/entity)))]
+                       (plugin-property-key? ident)
+                       (some-> ident (db-utils/entity)))]
     (if (= (:logseq.property/type prop) :string)
     (if (= (:logseq.property/type prop) :string)
       (try
       (try
         (js/JSON.parse property-value)
         (js/JSON.parse property-value)
@@ -108,10 +108,10 @@
                       (let [id (:db/id (ldb/get-case-page (conn/get-db) page))]
                       (let [id (:db/id (ldb/get-case-page (conn/get-db) page))]
                         (if (nil? id)
                         (if (nil? id)
                           (-> (page-handler/<create! page {:redirect? false})
                           (-> (page-handler/<create! page {:redirect? false})
-                            (p/then #(:db/id %)))
+                              (p/then #(:db/id %)))
                           id))))
                           id))))
-                (p/all)
-                (p/then (fn [vs] [ident :logseq.property/empty-placeholder vs true])))
+                  (p/all)
+                  (p/then (fn [vs] [ident :logseq.property/empty-placeholder vs true])))
               (let [value (if as-json? (js/JSON.stringify (bean/->js value)) value)]
               (let [value (if as-json? (js/JSON.stringify (bean/->js value)) value)]
                 [ident value nil false]))))
                 [ident value nil false]))))
         ent (db-utils/entity ident)]
         ent (db-utils/entity ident)]
@@ -124,8 +124,8 @@
             schema {:logseq.property/type type
             schema {:logseq.property/type type
                     :db/cardinality :one}]
                     :db/cardinality :one}]
         (p/chain
         (p/chain
-          (db-property-handler/upsert-property! ident schema {})
-          (fn [] (value-handle type false))))
+         (db-property-handler/upsert-property! ident schema {})
+         (fn [] (value-handle type false))))
       (let [value-multi? (vector? value)
       (let [value-multi? (vector? value)
             ident (:db/ident ent)
             ident (:db/ident ent)
             ent-type (:logseq.property/type ent)
             ent-type (:logseq.property/type ent)
@@ -135,54 +135,54 @@
         (when cardinality-want-illegal-changed?
         (when cardinality-want-illegal-changed?
           (throw (js/Error. "Multiple property type can not be changed.")))
           (throw (js/Error. "Multiple property type can not be changed.")))
         (p/chain
         (p/chain
-          (db-property-handler/upsert-property! ident
-            {:logseq.property/type ent-type
-             :db/cardinality (if (and (not ent-type-str?) value-multi?) :many :one)}
-            {})
-          #(value-handle ent-type ent-multi?))))))
+         (db-property-handler/upsert-property! ident
+                                               {:logseq.property/type ent-type
+                                                :db/cardinality (if (and (not ent-type-str?) value-multi?) :many :one)}
+                                               {})
+         #(value-handle ent-type ent-multi?))))))
 
 
 (defn save-db-based-block-properties!
 (defn save-db-based-block-properties!
   ([block properties] (save-db-based-block-properties! block properties nil))
   ([block properties] (save-db-based-block-properties! block properties nil))
   ([block properties ^js plugin]
   ([block properties ^js plugin]
    (when-let [block-id (and (seq properties) (:db/id block))]
    (when-let [block-id (and (seq properties) (:db/id block))]
      (let [properties (update-keys properties
      (let [properties (update-keys properties
-                        (fn [k]
-                          (let [prefix (resolve-property-prefix-for-db plugin)]
-                            (get-db-ident-for-user-property-name k prefix))))
+                                   (fn [k]
+                                     (let [prefix (resolve-property-prefix-for-db plugin)]
+                                       (get-db-ident-for-user-property-name k prefix))))
            *properties-page-refs (volatile! {})]
            *properties-page-refs (volatile! {})]
        (-> (for [ident (keys properties)]
        (-> (for [ident (keys properties)]
              (p/let [ret (infer-property-value-type-to-save! ident (get properties ident))]
              (p/let [ret (infer-property-value-type-to-save! ident (get properties ident))]
                ret))
                ret))
-         (p/all)
-         (p/chain
-           (fn [props]
-             (->> props
-               (reduce (fn [a [k v vs multi?]]
-                         (if multi?
-                           (do (vswap! *properties-page-refs assoc k vs) a)
-                           (assoc a k v))) {})
-               (db-property-handler/set-block-properties! block-id)))
+           (p/all)
+           (p/chain
+            (fn [props]
+              (->> props
+                   (reduce (fn [a [k v vs multi?]]
+                             (if multi?
+                               (do (vswap! *properties-page-refs assoc k vs) a)
+                               (assoc a k v))) {})
+                   (db-property-handler/set-block-properties! block-id)))
            ;; handle page refs
            ;; handle page refs
-           (fn []
-             (when (seq @*properties-page-refs)
-               (doseq [[ident refs] @*properties-page-refs]
-                 (-> (property-handler/remove-block-property! (state/get-current-repo) block-id ident)
-                   (p/then
-                     (fn []
-                       (if (seq refs)
-                         (ui-outliner-tx/transact!
-                           {:outliner-op :set-block-properties}
-                           (doseq [eid refs]
-                             (when (number? eid)
-                               (property-handler/set-block-property!
+            (fn []
+              (when (seq @*properties-page-refs)
+                (doseq [[ident refs] @*properties-page-refs]
+                  (-> (property-handler/remove-block-property! (state/get-current-repo) block-id ident)
+                      (p/then
+                       (fn []
+                         (if (seq refs)
+                           (ui-outliner-tx/transact!
+                            {:outliner-op :set-block-properties}
+                            (doseq [eid refs]
+                              (when (number? eid)
+                                (property-handler/set-block-property!
                                  (state/get-current-repo) block-id ident eid))))
                                  (state/get-current-repo) block-id ident eid))))
-                         (db-property-handler/set-block-property! block-id ident :logseq.property/empty-placeholder))))))))))))))
+                           (db-property-handler/set-block-property! block-id ident :logseq.property/empty-placeholder))))))))))))))
 
 
 (defn <sync-children-blocks!
 (defn <sync-children-blocks!
   [block]
   [block]
   (when block
   (when block
     (db-async/<get-block (state/get-current-repo)
     (db-async/<get-block (state/get-current-repo)
-      (:block/uuid (:block/parent block)) {:children? true})))
+                         (:block/uuid (:block/parent block)) {:children? true})))
 
 
 (defn get_block
 (defn get_block
   [id-or-uuid ^js opts]
   [id-or-uuid ^js opts]
@@ -190,19 +190,19 @@
                      (db-utils/pull id-or-uuid)
                      (db-utils/pull id-or-uuid)
                      (and id-or-uuid (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error id-or-uuid))))]
                      (and id-or-uuid (db-model/query-block-by-uuid (sdk-utils/uuid-or-throw-error id-or-uuid))))]
     (when (or (true? (some-> opts (.-includePage)))
     (when (or (true? (some-> opts (.-includePage)))
-            (not (contains? block :block/name)))
+              (not (contains? block :block/name)))
       (when-let [uuid (:block/uuid block)]
       (when-let [uuid (:block/uuid block)]
         (let [{:keys [includeChildren]} (bean/->clj opts)
         (let [{:keys [includeChildren]} (bean/->clj opts)
               repo (state/get-current-repo)
               repo (state/get-current-repo)
               block (if includeChildren
               block (if includeChildren
                       ;; nested children results
                       ;; nested children results
                       (let [blocks (->> (db-model/get-block-and-children repo uuid)
                       (let [blocks (->> (db-model/get-block-and-children repo uuid)
-                                     (map (fn [b]
-                                            (dissoc (db-utils/pull (:db/id b)) :block.temp/load-status))))]
+                                        (map (fn [b]
+                                               (dissoc (db-utils/pull (:db/id b)) :block.temp/load-status))))]
                         (first (outliner-tree/blocks->vec-tree blocks uuid)))
                         (first (outliner-tree/blocks->vec-tree blocks uuid)))
                       ;; attached shallow children
                       ;; attached shallow children
                       (assoc block :block/children
                       (assoc block :block/children
-                        (map #(list :uuid (:block/uuid %))
-                          (db/get-block-immediate-children repo uuid))))
+                             (map #(list :uuid (:block/uuid %))
+                                  (db/get-block-immediate-children repo uuid))))
               block (into-properties repo block)]
               block (into-properties repo block)]
           (bean/->js (sdk-utils/normalize-keyword-for-json block)))))))
           (bean/->js (sdk-utils/normalize-keyword-for-json block)))))))

+ 3 - 3
src/main/mobile/components/settings.cljs

@@ -57,6 +57,6 @@
                        [:span.text-muted-foreground {:slot "icon-only"}
                        [:span.text-muted-foreground {:slot "icon-only"}
                         (ion/tabler-icon "dots" {:size 20})])))))
                         (ion/tabler-icon "dots" {:size 20})])))))
      (ion/content {:class "ion-padding"}
      (ion/content {:class "ion-padding"}
-      (user-profile login?)
-      [:div.mt-8
-       (repo/repos-cp)]))))
+                  (user-profile login?)
+                  [:div.mt-8
+                   (repo/repos-cp)]))))

+ 2 - 0
src/resources/dicts/en.edn

@@ -667,6 +667,7 @@
   :editor/select-down             "Select content below"
   :editor/select-down             "Select content below"
   :editor/move-block-up           "Move block up"
   :editor/move-block-up           "Move block up"
   :editor/move-block-down         "Move block down"
   :editor/move-block-down         "Move block down"
+  :editor/move-blocks             "Move blocks to"
   :editor/open-edit               "Edit selected block"
   :editor/open-edit               "Edit selected block"
   :editor/open-selected-blocks-in-sidebar "Open selected block(s) in sidebar"
   :editor/open-selected-blocks-in-sidebar "Open selected block(s) in sidebar"
   :editor/select-block-up         "Select block above"
   :editor/select-block-up         "Select block above"
@@ -690,6 +691,7 @@
   :editor/zoom-out                "Zoom out editing block / Backwards otherwise"
   :editor/zoom-out                "Zoom out editing block / Backwards otherwise"
   :editor/toggle-number-list      "Toggle number list"
   :editor/toggle-number-list      "Toggle number list"
   :editor/add-property            "Add property"
   :editor/add-property            "Add property"
+  :editor/set-tags                "Set tags for selected block(s)"
   :editor/add-property-deadline   "Add task deadline to selected block"
   :editor/add-property-deadline   "Add task deadline to selected block"
   :editor/add-property-status     "Add task status to selected block"
   :editor/add-property-status     "Add task status to selected block"
   :editor/add-property-priority   "Add task priority to selected block"
   :editor/add-property-priority   "Add task priority to selected block"

+ 18 - 7
src/test/frontend/modules/outliner/core_test.cljs

@@ -169,7 +169,8 @@
      (transact-opts)
      (transact-opts)
      (outliner-core/move-blocks! test-db
      (outliner-core/move-blocks! test-db
                                  (db/get-db test-db false)
                                  (db/get-db test-db false)
-                                 [(get-block 3)] (get-block 14) true))
+                                 [(get-block 3)] (get-block 14)
+                                 {:sibling? true}))
     (is (= [6 9] (get-children 2)))
     (is (= [6 9] (get-children 2)))
     (is (= [13 14 3 15] (get-children 12))))
     (is (= [13 14 3 15] (get-children 12))))
 
 
@@ -192,7 +193,8 @@
        (transact-opts)
        (transact-opts)
        (outliner-core/move-blocks! test-db
        (outliner-core/move-blocks! test-db
                                    (db/get-db test-db false)
                                    (db/get-db test-db false)
-                                   [(get-block 3)] (get-block 12) false))
+                                   [(get-block 3)] (get-block 12)
+                                   {:sibling? false}))
       (is (= [6 9] (get-children 2)))
       (is (= [6 9] (get-children 2)))
       (is (= [3 13 14 15] (get-children 12))))))
       (is (= [3 13 14 15] (get-children 12))))))
 
 
@@ -205,7 +207,8 @@
      (transact-opts)
      (transact-opts)
      (outliner-core/move-blocks! test-db
      (outliner-core/move-blocks! test-db
                                  (db/get-db test-db false)
                                  (db/get-db test-db false)
-                                 [(get-block 3)] (get-block 2) true))
+                                 [(get-block 3)] (get-block 2)
+                                 {:sibling? true}))
     (is (= [4] (get-children 2)))
     (is (= [4] (get-children 2)))
     (is (= [2 3 5] (get-children 22)))))
     (is (= [2 3 5] (get-children 22)))))
 
 
@@ -220,7 +223,8 @@
      (transact-opts)
      (transact-opts)
      (outliner-core/move-blocks! test-db
      (outliner-core/move-blocks! test-db
                                  (db/get-db test-db false)
                                  (db/get-db test-db false)
-                                 [(get-block 3) (get-block 6)] (get-block 2) true))
+                                 [(get-block 3) (get-block 6)] (get-block 2)
+                                 {:sibling? true}))
     (is (= [4] (get-children 2)))
     (is (= [4] (get-children 2)))
     (is (= [2 3 6 5 7] (get-children 22)))))
     (is (= [2 3 6 5 7] (get-children 22)))))
 
 
@@ -236,7 +240,8 @@
      (transact-opts)
      (transact-opts)
      (outliner-core/move-blocks! test-db
      (outliner-core/move-blocks! test-db
                                  (db/get-db test-db false)
                                  (db/get-db test-db false)
-                                 [(get-block 3) (get-block 5)] (get-block 2) false))
+                                 [(get-block 3) (get-block 5)] (get-block 2)
+                                 {:sibling? false}))
     (is (= [3 5 4] (get-children 2)))
     (is (= [3 5 4] (get-children 2)))
     (is (= [2 6 7 8] (get-children 22)))))
     (is (= [2 6 7 8] (get-children 22)))))
 
 
@@ -706,7 +711,11 @@ tags:: tag1, tag2
           (when (seq blocks)
           (when (seq blocks)
             (let [target (get-random-block)]
             (let [target (get-random-block)]
               (outliner-tx/transact! (transact-opts)
               (outliner-tx/transact! (transact-opts)
-                                     (outliner-core/move-blocks! test-db (db/get-db test-db false) blocks target (gen/generate gen/boolean)))
+                                     (outliner-core/move-blocks! test-db
+                                                                 (db/get-db test-db false)
+                                                                 blocks
+                                                                 target
+                                                                 {:sibling? (gen/generate gen/boolean)}))
               (let [total (get-blocks-count)]
               (let [total (get-blocks-count)]
                 (is (= total (count @*random-blocks)))))))))))
                 (is (= total (count @*random-blocks)))))))))))
 
 
@@ -774,7 +783,9 @@ tags:: tag1, tag2
                    (outliner-tx/transact! (transact-opts)
                    (outliner-tx/transact! (transact-opts)
                                           (outliner-core/move-blocks! test-db
                                           (outliner-core/move-blocks! test-db
                                                                       (db/get-db test-db false)
                                                                       (db/get-db test-db false)
-                                                                      blocks (get-random-block) (gen/generate gen/boolean))))))
+                                                                      blocks
+                                                                      (get-random-block)
+                                                                      {:sibling? (gen/generate gen/boolean)})))))
 
 
              ;; move up down
              ;; move up down
              (fn []
              (fn []