Browse Source

Merge branch 'feat/db' into feat/capacitor-new

charlie 7 months ago
parent
commit
6e6ba3995c
100 changed files with 1960 additions and 1047 deletions
  1. 8 0
      .github/workflows/clj-e2e.yml
  2. 1 0
      .gitignore
  3. 3 2
      clj-e2e/deps.edn
  4. 19 6
      clj-e2e/dev/user.clj
  5. 5 5
      clj-e2e/src/logseq/e2e/assert.clj
  6. 23 3
      clj-e2e/src/logseq/e2e/block.clj
  7. 17 6
      clj-e2e/src/logseq/e2e/graph.clj
  8. 25 0
      clj-e2e/src/logseq/e2e/page.clj
  9. 45 0
      clj-e2e/src/logseq/e2e/rtc.clj
  10. 33 12
      clj-e2e/src/logseq/e2e/util.clj
  11. 193 18
      clj-e2e/test/logseq/e2e/commands_test.clj
  12. 40 0
      clj-e2e/test/logseq/e2e/custom_report.clj
  13. 20 12
      clj-e2e/test/logseq/e2e/fixtures.clj
  14. 3 3
      clj-e2e/test/logseq/e2e/multi_tabs_test.clj
  15. 29 1
      clj-e2e/test/logseq/e2e/rtc_basic_test.clj
  16. 7 2
      deps/common/resources/templates/config.edn
  17. 1 1
      deps/db/bb.edn
  18. 1 1
      deps/db/src/logseq/db.cljs
  19. 20 12
      deps/db/src/logseq/db/common/initial_data.cljs
  20. 43 19
      deps/db/src/logseq/db/common/view.cljs
  21. 1 1
      deps/db/src/logseq/db/frontend/class.cljs
  22. 34 34
      deps/db/src/logseq/db/frontend/property.cljs
  23. 10 12
      deps/db/src/logseq/db/frontend/rules.cljc
  24. 1 1
      deps/db/src/logseq/db/frontend/schema.cljs
  25. 10 3
      deps/db/src/logseq/db/sqlite/build.cljs
  26. 7 5
      deps/db/src/logseq/db/sqlite/export.cljs
  27. 5 5
      deps/db/test/logseq/db/sqlite/build_test.cljs
  28. 5 5
      deps/db/test/logseq/db/sqlite/create_graph_test.cljs
  29. 1 1
      deps/db/test/logseq/db/sqlite/export_test.cljs
  30. 18 18
      deps/graph-parser/src/logseq/graph_parser/exporter.cljs
  31. 9 9
      deps/graph-parser/test/logseq/graph_parser/exporter_test.cljs
  32. 4 1
      deps/outliner/src/logseq/outliner/core.cljs
  33. 6 6
      deps/outliner/src/logseq/outliner/op.cljs
  34. 53 46
      deps/outliner/src/logseq/outliner/property.cljs
  35. 14 1
      deps/outliner/src/logseq/outliner/validate.cljs
  36. 4 4
      deps/outliner/test/logseq/outliner/property_test.cljs
  37. 1 1
      deps/outliner/test/logseq/outliner/validate_test.cljs
  38. 1 1
      deps/shui/src/logseq/shui/table/core.cljc
  39. 1 1
      packages/tldraw/apps/tldraw-logseq/src/components/ContextBar/ContextBar.tsx
  40. 1 1
      packages/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx
  41. 2 1
      packages/tldraw/apps/tldraw-logseq/src/styles.css
  42. 22 13
      src/electron/electron/window.cljs
  43. 7 6
      src/main/frontend/commands.cljs
  44. 3 1
      src/main/frontend/common.css
  45. 64 47
      src/main/frontend/components/block.cljs
  46. 1 1
      src/main/frontend/components/block.css
  47. 16 8
      src/main/frontend/components/cmdk/core.cljs
  48. 1 1
      src/main/frontend/components/cmdk/list_item.cljs
  49. 1 2
      src/main/frontend/components/container.cljs
  50. 6 10
      src/main/frontend/components/container.css
  51. 15 13
      src/main/frontend/components/editor.cljs
  52. 9 6
      src/main/frontend/components/file_based/git.cljs
  53. 6 5
      src/main/frontend/components/file_based/query_table.cljs
  54. 3 3
      src/main/frontend/components/icon.cljs
  55. 3 2
      src/main/frontend/components/imports.cljs
  56. 8 7
      src/main/frontend/components/journal.cljs
  57. 26 12
      src/main/frontend/components/objects.cljs
  58. 1 0
      src/main/frontend/components/page.cljs
  59. 1 1
      src/main/frontend/components/property/config.cljs
  60. 45 28
      src/main/frontend/components/property/value.cljs
  61. 4 4
      src/main/frontend/components/query/builder.cljs
  62. 2 2
      src/main/frontend/components/repo.cljs
  63. 7 7
      src/main/frontend/components/right_sidebar.cljs
  64. 3 3
      src/main/frontend/components/right_sidebar.css
  65. 3 1
      src/main/frontend/components/rtc/indicator.cljs
  66. 5 1
      src/main/frontend/components/table.css
  67. 379 190
      src/main/frontend/components/views.cljs
  68. 8 7
      src/main/frontend/components/whiteboard.cljs
  69. 33 13
      src/main/frontend/db/async.cljs
  70. 12 25
      src/main/frontend/db/model.cljs
  71. 1 1
      src/main/frontend/extensions/code.css
  72. 23 21
      src/main/frontend/extensions/tldraw.cljs
  73. 1 1
      src/main/frontend/handler/block.cljs
  74. 28 30
      src/main/frontend/handler/common/config_edn.cljs
  75. 25 0
      src/main/frontend/handler/common/editor.cljs
  76. 1 2
      src/main/frontend/handler/db_based/page.cljs
  77. 4 3
      src/main/frontend/handler/db_based/property.cljs
  78. 32 168
      src/main/frontend/handler/editor.cljs
  79. 0 7
      src/main/frontend/handler/events/ui.cljs
  80. 123 1
      src/main/frontend/handler/file_based/editor.cljs
  81. 5 5
      src/main/frontend/handler/property.cljs
  82. 1 1
      src/main/frontend/handler/ui.cljs
  83. 7 7
      src/main/frontend/modules/outliner/op.cljs
  84. 1 1
      src/main/frontend/search.cljs
  85. 8 4
      src/main/frontend/state.cljs
  86. 10 11
      src/main/frontend/ui.cljs
  87. 11 7
      src/main/frontend/util.cljc
  88. 24 23
      src/main/frontend/util/cursor.cljs
  89. 20 20
      src/main/frontend/worker/commands.cljs
  90. 165 9
      src/main/frontend/worker/db/migrate.cljs
  91. 11 8
      src/main/frontend/worker/db_worker.cljs
  92. 2 1
      src/main/frontend/worker/export.cljs
  93. 10 10
      src/main/frontend/worker/handler/page/db_based/page.cljs
  94. 5 3
      src/main/frontend/worker/handler/page/file_based/rename.cljs
  95. 1 1
      src/main/frontend/worker/react.cljs
  96. 5 5
      src/rtc_e2e_test/client_steps.cljs
  97. 4 4
      src/rtc_e2e_test/const.cljs
  98. 0 0
      src/rtc_e2e_test/example.cljs
  99. 3 3
      src/test/frontend/db/query_dsl_test.cljs
  100. 11 11
      src/test/frontend/test/helper.cljs

+ 8 - 0
.github/workflows/clj-e2e.yml

@@ -86,3 +86,11 @@ jobs:
         run: cd clj-e2e && bb dev
         env:
           DEBUG: "pw:api"
+          
+      - name: Collect screenshots
+        if: ${{ failure() }}
+        uses: actions/upload-artifact@v4
+        with:
+          name: e2e-screenshots
+          path: clj-e2e/e2e-dump/*
+          retention-days: 1

+ 1 - 0
.gitignore

@@ -70,4 +70,5 @@ deps/shui/.clj-kondo
 tx-log*
 clj-e2e/.wally
 clj-e2e/resources
+clj-e2e/e2e-dump
 .dir-locals.el

+ 3 - 2
clj-e2e/deps.edn

@@ -2,9 +2,10 @@
  :deps {org.clojure/clojure {:mvn/version "1.12.0"}
         ;; io.github.pfeodrippe/wally {:local/root "../../../wally"}
         io.github.pfeodrippe/wally {:git/url "https://github.com/logseq/wally"
-                                    :sha "6b0583701fc64ec5177eec6577e33bb8d9115d61"}
+                                    :sha "80ea665c75704a8ca80cb22caeaec3ae6f39ffdb"}
         ;; io.github.zmedelis/bosquet {:mvn/version "2025.03.28"}
-        org.clj-commons/claypoole          {:mvn/version "1.2.2"}}
+        org.clj-commons/claypoole          {:mvn/version "1.2.2"}
+        clj-time/clj-time                  {:mvn/version "0.15.2"}}
  :aliases
  {:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.5"}}
           :ns-default build}

+ 19 - 6
clj-e2e/dev/user.clj

@@ -5,6 +5,7 @@
             [logseq.e2e.commands-test]
             [logseq.e2e.config :as config]
             [logseq.e2e.fixtures :as fixtures]
+            [logseq.e2e.graph :as graph]
             [logseq.e2e.keyboard :as k]
             [logseq.e2e.multi-tabs-test]
             [logseq.e2e.outliner-test]
@@ -17,6 +18,7 @@
 (reset! config/*port 3001)
 ;; show ui
 (reset! config/*headless false)
+(reset! config/*slow-mo 100)
 
 (def *futures (atom {}))
 
@@ -45,14 +47,23 @@
   (->> (future (run-tests 'logseq.e2e.multi-tabs-test))
        (swap! *futures assoc :multi-tabs-test)))
 
-(comment
+(defn run-all-test
+  []
+  (run-tests 'logseq.e2e.commands-test
+             'logseq.e2e.multi-tabs-test
+             'logseq.e2e.outliner-test
+             'logseq.e2e.rtc-basic-test))
 
+(defn start
+  []
   (future
     (fixtures/open-page
      repl/pause
-     {:headless false}))
+     {:headless false})))
+
+(comment
 
-  ;; You can put `(repl/pause)` in any test to pause the tests,
+  ;; You can call or put `(repl/pause)` in any test to pause the tests,
   ;; this allows us to continue experimenting with the current page.
   (repl/pause)
 
@@ -60,7 +71,7 @@
   (repl/resume)
 
   ;; Run specific test
-  (future (run-test logseq.e2e.editor-test/commands-test))
+  (future (run-test logseq.e2e.commands-test/new-property-test))
 
   ;; after the test has been paused, you can do anything with the current page like this
   (repl/with-page
@@ -74,7 +85,9 @@
 
   (do
     (reset! config/*headless true)
-    (dotimes [i 10]
-      (run-outliner-test)))
+    (reset! config/*slow-mo 100)
+    (dotimes [i 5]
+      (run-multi-tabs-test)))
+
   ;;
   )

+ 5 - 5
clj-e2e/src/logseq/e2e/assert.clj

@@ -5,9 +5,8 @@
 (def assert-that PlaywrightAssertions/assertThat)
 
 (defn assert-is-visible
-  "Multiple elements may match `q`, check and wait for the first element to be visible."
   [q]
-  (-> q w/query first assert-that .isVisible)
+  (-> (w/-query q) .first assert-that .isVisible)
   true)
 
 (defn assert-is-hidden
@@ -20,7 +19,7 @@
   - no action bar
   - no search(cmdk) modal"
   []
-  (assert-is-hidden (w/get-by-label "editing block"))
+  (assert-is-hidden (w/get-by-test-id "block editor"))
   (assert-is-hidden ".selection-action-bar")
   (assert-is-visible "#search-button")
   true)
@@ -28,10 +27,11 @@
 (defn assert-graph-loaded?
   []
   ;; there's some blocks visible now
-  (assert-is-visible "span.block-title-wrap"))
+  (assert-is-visible (w/get-by-test-id "page title")))
 
 (defn assert-editor-mode
   []
   (let [klass ".editor-wrapper textarea"
         editor (w/-query klass)]
-    (w/wait-for editor)))
+    (w/wait-for editor)
+    editor))

+ 23 - 3
clj-e2e/src/logseq/e2e/block.clj

@@ -5,6 +5,19 @@
             [logseq.e2e.util :as util]
             [wally.main :as w]))
 
+(defn- wait-for-new-empty-block-inserted
+  "When inserting a new block, if the operation is too fast,
+  the w/fill operation can begin before k/enter has finished creating the block,
+  leading to the save-block not updating the block contents."
+  [last-edit-block-id]
+  (loop [i 5]
+    (if (zero? i)
+      (throw (ex-info "wait-for-new-empty-block-inserted" {}))
+      (if (not= last-edit-block-id (.getAttribute (w/-query util/editor-q) "id"))
+        :done
+        (do (util/wait-timeout 50)
+            (recur (dec i)))))))
+
 (defn open-last-block
   []
   (util/double-esc)
@@ -13,14 +26,17 @@
 
 (defn save-block
   [text]
-  (w/fill ".editor-wrapper textarea" text))
+  (w/fill util/editor-q text))
 
 (defn new-block
   [title]
-  (let [editor? (util/get-editor)]
-    (when-not editor? (open-last-block))
+  (let [editor (util/get-editor)
+        last-edit-block-id (when editor (.getAttribute editor "id"))]
+    (when-not editor (open-last-block))
     (assert/assert-editor-mode)
     (k/enter)
+    (assert/assert-editor-mode)
+    (when last-edit-block-id (wait-for-new-empty-block-inserted last-edit-block-id))
     (save-block title)))
 
 ;; TODO: support tree
@@ -48,3 +64,7 @@
   [blocks]
   (doseq [block blocks]
     (assert/assert-is-visible (format ".ls-page-blocks .ls-block :text('%s')" block))))
+
+(defn jump-to-block
+  [block-text]
+  (w/click (w/find-one-by-text ".ls-block .block-content" block-text)))

+ 17 - 6
clj-e2e/src/logseq/e2e/graph.clj

@@ -21,23 +21,34 @@
     (w/click "button#rtc-sync"))
   (w/click "button:text(\"Submit\")")
   (when enable-sync?
-    (w/wait-for "button.cloud.on.idle" {:timeout 20000})))
+    (w/wait-for "button.cloud.on.idle" {:timeout 20000}))
+  ;; new graph can blocks the ui because the db need to be created and restored,
+  ;; I have no idea why `search-and-click` failed to auto-wait sometimes.
+  (util/wait-timeout 1000))
 
 (defn wait-for-remote-graph
   [graph-name]
   (goto-all-graphs)
   (util/repeat-until-visible 5
-                             (format "div[aria-label='e2e logseq_db_%s']" graph-name)
+                             (format "div[data-testid='logseq_db_%s']" graph-name)
                              refresh-all-remote-graphs))
 
 (defn remove-remote-graph
   [graph-name]
   (wait-for-remote-graph graph-name)
-  (w/click (format "div[aria-label='e2e logseq_db_%s'] a:has-text(\"Remove (server)\")" graph-name))
-  (w/click "div[role='alertdialog'] button:text('ok')"))
+  (let [local-unlink-button-q
+        (.first (w/-query (format "div[data-testid='logseq_db_%s'] a:has-text(\"Unlink (local)\")" graph-name)))]
+    (if (.isVisible local-unlink-button-q)
+      (do (w/click local-unlink-button-q)
+          (w/click "div[role='alertdialog'] button:text('ok')")
+          (remove-remote-graph graph-name))
+      (do (w/click (format "div[data-testid='logseq_db_%s'] a:has-text(\"Remove (server)\")" graph-name))
+          (w/click "div[role='alertdialog'] button:text('ok')")))))
 
 (defn switch-graph
-  [to-graph-name]
+  [to-graph-name wait-sync?]
   (goto-all-graphs)
-  (w/click (format "div[aria-label='e2e logseq_db_%1$s'] span:text('%1$s')" to-graph-name))
+  (w/click (.last (w/-query (format "div[data-testid='logseq_db_%1$s'] span:has-text('%1$s')" to-graph-name))))
+  (when wait-sync?
+    (w/wait-for "button.cloud.on.idle" {:timeout 20000}))
   (assert/assert-graph-loaded?))

+ 25 - 0
clj-e2e/src/logseq/e2e/page.clj

@@ -0,0 +1,25 @@
+(ns logseq.e2e.page
+  (:require [logseq.e2e.assert :as assert]
+            [logseq.e2e.util :as util]
+            [wally.main :as w]
+            [wally.selectors :as ws]))
+
+(defn goto-page
+  [page-name]
+  (util/search-and-click page-name))
+
+(defn new-page
+  [title]
+  ;; Question: what's the best way to close all the popups?
+  ;; close popup, exit editing
+  ;; (repl/pause)
+  (util/search title)
+  (w/click [(ws/text "Create page") (ws/nth= "0")])
+  (util/wait-editor-visible))
+
+(defn delete-page
+  [page-name]
+  (goto-page page-name)
+  (w/click "button[title='More']")
+  (w/click "[role='menuitem'] div:text('Delete page')")
+  (w/click "div[role='alertdialog'] button:text('ok')"))

+ 45 - 0
clj-e2e/src/logseq/e2e/rtc.clj

@@ -0,0 +1,45 @@
+(ns logseq.e2e.rtc
+  (:require [clojure.edn :as edn]
+            [logseq.e2e.util :as util]
+            [wally.main :as w]))
+
+(defn get-rtc-tx
+  []
+  (let [loc (w/get-by-test-id "rtc-tx")]
+    (edn/read-string (w/text-content loc))))
+
+(defmacro with-wait-tx-updated
+  "exec body, then wait for the rtc-tx update.
+  Return the updated rtc-tx(local-tx and remote-tx)"
+  [& body]
+  `(let [m# (get-rtc-tx)
+         local-tx# (or (:local-tx m#) 0)
+         remote-tx# (or (:remote-tx m#) 0)
+         _# (prn :current-rtc-tx m# local-tx# remote-tx#)
+         tx# (max local-tx# remote-tx#)]
+     ~@body
+     (loop [i# 5]
+       (when (zero? i#) (throw (ex-info "wait-tx-updated failed" {:data m#})))
+       (util/wait-timeout 1000)
+       (let [new-m# (get-rtc-tx)
+             new-local-tx# (or (:local-tx new-m#) 0)
+             new-remote-tx# (or (:remote-tx new-m#) 0)]
+         (if (and (= new-local-tx# new-remote-tx#)
+                  (> new-local-tx# tx#))
+           (do (prn :new-rtc-tx new-m#)
+               {:local-tx new-local-tx# :remote-tx new-remote-tx#})
+           (do (prn :current-rtc-tx new-m#)
+               (recur (dec i#))))))))
+
+(defn wait-tx-update-to
+  [new-tx]
+  (loop [i 5]
+    (when (zero? i) (throw (ex-info "wait-tx-update-to" {:update-to new-tx})))
+    (util/wait-timeout 1000)
+    (let [m (get-rtc-tx)
+          local-tx (or (:local-tx m) 0)
+          ;; remote-tx (or (:remote-tx m) 0)
+          ]
+      (if (>= local-tx new-tx)
+        local-tx
+        (recur (dec i))))))

+ 33 - 12
clj-e2e/src/logseq/e2e/util.clj

@@ -30,11 +30,12 @@
   []
   (w/-query "*:focus"))
 
+(def editor-q ".editor-wrapper textarea")
+
 (defn get-editor
   []
-  (let [klass ".editor-wrapper textarea"
-        editor (w/-query klass)]
-    (when (w/visible? klass)
+  (let [editor (w/-query editor-q)]
+    (when (w/visible? editor-q)
       editor)))
 
 (defn get-edit-block-container
@@ -62,20 +63,22 @@
   (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
   [search-text]
   (search search-text)
-  (w/click (w/get-by-label search-text)))
-
-(defn new-page
-  [title]
-  ;; Question: what's the best way to close all the popups?
-  ;; close popup, exit editing
-  ;; (repl/pause)
-  (search title)
-  (w/click [(ws/text "Create page") (ws/nth= "0")])
+  (w/click (.first (w/get-by-test-id search-text))))
+
+(defn wait-editor-gone
+  ([]
+   (wait-editor-gone ".editor-wrapper textarea"))
+  ([editor]
+   (w/wait-for-not-visible editor)))
+
+(defn wait-editor-visible
+  []
   (w/wait-for ".editor-wrapper textarea"))
 
 (defn count-elements
@@ -169,3 +172,21 @@
 (defn move-cursor-to-start
   []
   (k/press "ControlOrMeta+a" "ArrowLeft"))
+
+(defn input-command
+  [command]
+  (let [content (get-edit-content)]
+    (when (and (not= (str (last content)) " ")
+               (not= content ""))
+      (type " ")))
+  (type "/")
+  (type command)
+  (w/wait-for ".ui__popover-content")
+  (k/enter))
+
+(defn set-tag
+  [tag]
+  (type " #")
+  (type tag)
+  (w/wait-for (w/find-one-by-text "a.menu-link mark" tag))
+  (k/enter))

+ 193 - 18
clj-e2e/test/logseq/e2e/commands_test.clj

@@ -1,12 +1,15 @@
 (ns logseq.e2e.commands-test
   (:require
+   [clj-time.core :as t]
+   [clj-time.local :as tl]
    [clojure.string :as string]
    [clojure.test :refer [deftest testing is use-fixtures]]
    [logseq.e2e.block :as b]
    [logseq.e2e.fixtures :as fixtures]
    [logseq.e2e.keyboard :as k]
    [logseq.e2e.util :as util]
-   [wally.main :as w]))
+   [wally.main :as w]
+   [wally.repl :as repl]))
 
 (use-fixtures :once fixtures/open-page)
 
@@ -21,18 +24,11 @@
     (k/backspace)
     (w/wait-for-not-visible ".ui__popover-content")))
 
-(defn- input-command
-  [command-match]
-  (util/type "/")
-  (util/type command-match)
-  (w/wait-for ".ui__popover-content")
-  (k/enter))
-
 (deftest node-reference-test
   (testing "Node reference"
     (testing "Page reference"
       (b/new-blocks ["b1" ""])
-      (input-command "Node eferen")
+      (util/input-command "Node eferen")
       (util/type "Another page")
       (k/enter)
       (is (= "[[Another page]]" (util/get-edit-content)))
@@ -40,7 +36,7 @@
       (is (= "Another page" (util/get-text "a.page-ref"))))
     (testing "Block reference"
       (b/new-block "")
-      (input-command "Node eferen")
+      (util/input-command "Node eferen")
       (util/type "b1")
       (util/wait-timeout 300)
       (k/enter)
@@ -57,11 +53,11 @@
                             (k/tab)
                             (k/enter))]
       (b/new-block "")
-      (input-command "link")
+      (util/input-command "link")
       (add-logseq-link)
       (is (= "[Logseq](https://logseq.com)" (util/get-edit-content)))
       (util/type " some content ")
-      (input-command "link")
+      (util/input-command "link")
       (add-logseq-link)
       (is (= (str "[Logseq](https://logseq.com)"
                   " some content "
@@ -70,7 +66,7 @@
 (deftest link-image-test
   (testing "/image link"
     (b/new-block "")
-    (input-command "image link")
+    (util/input-command "image link")
     (util/type "https://logseq.com/test.png")
     (k/tab)
     (util/type "Logseq")
@@ -81,7 +77,7 @@
 (deftest underline-test
   (testing "/underline"
     (b/new-block "")
-    (input-command "underline")
+    (util/input-command "underline")
     (is (= "<ins></ins>" (util/get-edit-content)))
     (util/type "test")
     (is (= "<ins>test</ins>" (util/get-edit-content)))
@@ -90,7 +86,7 @@
 (deftest code-block-test
   (testing "/code block"
     (b/new-block "")
-    (input-command "code block")
+    (util/input-command "code block")
     (w/wait-for ".CodeMirror")
     (util/wait-timeout 100)
     ;; create another block
@@ -99,7 +95,7 @@
 (deftest math-block-test
   (testing "/math block"
     (b/new-block "")
-    (input-command "math block")
+    (util/input-command "math block")
     (util/type "1 + 2 = 3")
     (util/exit-edit)
     (w/wait-for ".katex")))
@@ -107,7 +103,7 @@
 (deftest quote-test
   (testing "/quote"
     (b/new-block "")
-    (input-command "quote")
+    (util/input-command "quote")
     (w/wait-for "div[data-node-type='quote']")))
 
 (deftest headings-test
@@ -116,7 +112,186 @@
       (let [heading (str "h" (inc i))
             text (str heading " test ")]
         (b/new-block text)
-        (input-command heading)
+        (util/input-command heading)
         (is (= text (util/get-edit-content)))
         (util/exit-edit)
         (w/wait-for heading)))))
+
+(deftest status-test
+  (testing "task status commands"
+    (let [status->icon {"Doing" "InProgress50"
+                        "In review" "InReview"
+                        "Canceled" "Cancelled"}]
+      (doseq [status ["Backlog" "Todo" "Doing" "In review" "Done" "Canceled"]]
+        (let [text (str status " test ")]
+          (b/new-block text)
+          (util/input-command status)
+          (is (= text (util/get-edit-content)))
+          (util/exit-edit)
+          (w/wait-for (str ".ls-icon-" (get status->icon status status))))))))
+
+(deftest priority-test
+  (testing "task priority commands"
+    (let [priority->icon {"No priority" "line-dashed"}]
+      (doseq [priority ["No priority" "Low" "Medium" "High" "Urgent"]]
+        (let [text (str priority " test ")]
+          (b/new-block text)
+          (util/input-command priority)
+          (is (= text (util/get-edit-content)))
+          (util/exit-edit)
+          (w/wait-for (str ".ls-icon-" (get priority->icon priority
+                                            (str "priorityLvl" priority)))))))))
+
+(deftest scheduled-deadline-test
+  (testing "task scheduled and deadline commands"
+    (doseq [command ["Scheduled" "Deadline"]]
+      (fixtures/create-page)
+      (let [text (str command " test ")]
+        (b/new-block text)
+        (util/input-command command)
+        (k/enter)
+        (k/esc)
+        (util/exit-edit)
+        (is (= command (util/get-text ".property-k")))
+        (is (= "Today" (util/get-text ".ls-datetime a.page-ref")))))))
+
+;; TODO: java "MMMM d, yyyy" vs js "MMM do, yyyy"
+(deftest date-time-test
+  (testing "date time commands"
+    (util/input-command "today")
+    (let [text (util/get-edit-content)]
+      (and (string/starts-with? text "[[")
+           (string/ends-with? text "]]")))
+    (b/new-block "")
+    (util/input-command "yesterday")
+    (let [text (util/get-edit-content)]
+      (and (string/starts-with? text "[[")
+           (string/ends-with? text "]]")))
+    (b/new-block "")
+    (util/input-command "tomorrow")
+    (let [text (util/get-edit-content)]
+      (and (string/starts-with? text "[[")
+           (string/ends-with? text "]]")))
+    ;; FIXME:
+    ;; (b/new-block "")
+    ;; (util/input-command "time")
+    ;; (let [text (util/get-edit-content)
+    ;;       t (tl/local-now)]
+    ;;   (is (= text (str (t/hour t) ":" (t/minute t)))))
+    (b/new-block "")
+    (util/input-command "date picker")
+    (let [text (util/get-edit-content)]
+      (and (string/starts-with? text "[[")
+           (string/ends-with? text "]]")))))
+
+(deftest number-list-test
+  (testing "number list commands"
+    (util/input-command "number list")
+    (b/new-blocks ["a" "b" "c"])
+    (is (= ["1." "2." "3."] (w/all-text-contents "span.typed-list")))
+    ;; double `enter` convert the next block to bullet block
+    (k/enter)
+    (k/enter)
+    (is (= ["1." "2." "3."] (w/all-text-contents "span.typed-list")))))
+
+(deftest number-children-test
+  (testing "number children commands"
+    (b/new-blocks ["a" "a1" "a2" "a3" "b"])
+    (k/arrow-up)
+    (util/repeat-keyboard 3 "Shift+ArrowUp")
+    (k/tab)
+    (b/jump-to-block "a")
+    (util/input-command "number children")
+    (is (= ["1." "2." "3."] (w/all-text-contents "span.typed-list")))))
+
+(deftest query-test
+  (testing "query"
+    (b/new-blocks ["[[foo]] block" "[[foo]] another" ""])
+    (util/input-command "query")
+    (let [btn (w/find-one-by-text "button" "Filter")]
+      (w/click btn)
+      (util/input "page reference")
+      (is (some? (w/find-one-by-text "div" "page reference")))
+      (k/enter)
+      (util/input "foo")
+      (is (some? (w/find-one-by-text "div" "foo")))
+      (k/enter)
+      (is (some? (w/find-one-by-text "div" "Live query (2)"))))))
+
+(deftest advanced-query-test
+  (testing "query"
+    (b/new-blocks ["[[bar]] block" "[[bar]] another" ""])
+    (util/input-command "advanced query")
+    (w/click ".ls-query-setting")
+    (w/click "pre.CodeMirror-line")
+    (util/input "{:query [:find (pull ?b [*])
+:where [?b :block/refs ?r]
+[?r :block/title \"bar\"]]}")
+    (k/esc)
+    (is (some? (w/find-one-by-text "div" "Live query (2)")))))
+
+(deftest calculator-test
+  (testing "calculator"
+    (b/new-block "")
+    (util/input-command "calculator")
+    (util/input "1 + 2")
+    (is (some? (w/find-one-by-text "div.extensions__code-calc-output-line" "3")))))
+
+(deftest template-test
+  (testing "template"
+    (b/new-block "template 1")
+    (util/set-tag "Template")
+    (b/new-blocks ["block 1" "block 2" "block 3" "test"])
+    (k/arrow-up)
+    (util/repeat-keyboard 3 "Shift+ArrowUp")
+    (k/tab)
+    (b/jump-to-block "test")
+    (util/input-command "template")
+    (util/input "template 1")
+    (k/enter)
+    (util/exit-edit)
+    (let [content (w/all-text-contents ".ls-block .block-content")]
+      (doseq [text ["block 1" "block 2" "block 3"]]
+        (is (= 2 (count (filter #(= % text) content))))))))
+
+(deftest embed-html-test
+  (testing "embed html"
+    (b/new-block "")
+    (util/input-command "embed html")
+    (util/type "<div id=\"embed-test\">test</div>")
+    (util/exit-edit)
+    (is (= "test" (util/get-text "#embed-test")))))
+
+(deftest embed-video-test
+  (testing "embed video"
+    (b/new-block "")
+    (util/input-command "embed video")
+    (util/type "https://www.youtube.com/watch?v=7xTGNNLPyMI")
+    (util/exit-edit)
+    (w/wait-for "iframe")))
+
+(deftest embed-tweet-test
+  (testing "embed tweet"
+    (b/new-block "")
+    (util/input-command "embed tweet")
+    (util/type "https://x.com/logseq/status/1784914564083314839")
+    (util/exit-edit)
+    (w/wait-for "iframe")))
+
+(deftest cloze-test
+  (testing "cloze"
+    (b/new-block "")
+    (util/input-command "cloze")
+    (util/type "hidden answer")
+    (util/exit-edit)
+    (w/click "a.cloze")
+    (w/wait-for "a.cloze-revealed")))
+
+(deftest new-property-test
+  (testing "new property"
+    (b/new-block "")
+    (util/input-command "add new property")
+    (util/input "p1")
+    (w/click "a:has-text(\"+ New option: p1\")")
+    (k/enter)
+    (is (= "p1" (util/get-text "a.property-k")))))

+ 40 - 0
clj-e2e/test/logseq/e2e/custom_report.clj

@@ -0,0 +1,40 @@
+(ns logseq.e2e.custom-report
+  (:require [clojure.stacktrace :as stack]
+            [clojure.string :as string]
+            [clojure.test :as t]
+            [logseq.e2e.playwright-page :as pw-page])
+  (:import (com.microsoft.playwright Page$ScreenshotOptions)))
+
+(def ^:dynamic *pw-contexts*
+  "Set of pw-contexts.
+  record all playwright contexts in this dynamic var"
+  nil)
+
+(defn- screenshot
+  [page test-name]
+  (println :screenshot test-name)
+  (.screenshot
+   page
+   (-> (Page$ScreenshotOptions.)
+       (.setPath (java.nio.file.Paths/get "e2e-dump/"
+                                          (into-array [(format "./screenshot-%s-%s.png" test-name (System/currentTimeMillis))]))))))
+
+(defmethod t/report :error
+  [m]
+  ;; copy from default impl
+  (t/with-test-out
+    (t/inc-report-counter :error)
+    (println "\nERROR in" (t/testing-vars-str m))
+    (when (seq t/*testing-contexts*) (println (t/testing-contexts-str)))
+    (when-let [message (:message m)] (println message))
+    (println "expected:" (pr-str (:expected m)))
+    (print "  actual: ")
+    (let [actual (:actual m)]
+      (if (instance? Throwable actual)
+        (stack/print-cause-trace actual t/*stack-trace-depth*)
+        (prn actual))))
+
+  ;; screenshot for all pw pages when :error
+  (when-let [all-contexts (seq *pw-contexts*)]
+    (doseq [page (mapcat pw-page/get-pages all-contexts)]
+      (screenshot page (string/join "-" (map (comp str :name meta) t/*testing-vars*))))))

+ 20 - 12
clj-e2e/test/logseq/e2e/fixtures.clj

@@ -1,7 +1,7 @@
 (ns logseq.e2e.fixtures
   (:require [logseq.e2e.config :as config]
-            [logseq.e2e.playwright-page :as pw-page]
-            [logseq.e2e.util :as util]
+            [logseq.e2e.custom-report :as custom-report]
+            [logseq.e2e.page :as page]
             [wally.main :as w]))
 
 ;; TODO: save trace
@@ -12,8 +12,9 @@
     (w/make-page {:headless (or headless @config/*headless)
                   :persistent false
                   :slow-mo @config/*slow-mo})
-    (w/navigate (str "http://localhost:" (or port @config/*port)))
-    (f)))
+    (binding [custom-report/*pw-contexts* #{(.context (w/get-page))}]
+      (w/navigate (str "http://localhost:" (or port @config/*port)))
+      (f))))
 
 (def *page1 (atom nil))
 (def *page2 (atom nil))
@@ -28,15 +29,17 @@
         p1 (w/make-page page-opts)
         p2 (w/make-page page-opts)
         port' (or port @config/*port)]
-    (run!
-     #(w/with-page %
-        (w/navigate (str "http://localhost:" port')))
-     [p1 p2])
-
     (reset! *page1 p1)
     (reset! *page2 p2)
-    (binding [w/*page* (delay (throw (ex-info "Don't use *page*, use *page1* and *page2* instead" {})))]
+    (binding [custom-report/*pw-contexts* (set [(.context @p1) (.context @p2)])
+              w/*page* (delay (throw (ex-info "Don't use *page*, use *page1* and *page2* instead" {})))]
+      (run!
+       #(w/with-page %
+          (w/navigate (str "http://localhost:" port')))
+       [p1 p2])
       (f))
+
+    ;; use with-page-open to release resources
     (w/with-page-open p1)
     (w/with-page-open p2)))
 
@@ -52,13 +55,18 @@
     ;; context for p is no longer needed
     (.close (.context p))
     (w/with-page-open p)              ; use with-page-open to close playwright instance
-    (binding [*pw-ctx* ctx]
+    (binding [custom-report/*pw-contexts* #{ctx}
+              *pw-ctx* ctx]
       (f)
       (.close (.browser *pw-ctx*)))))
 
 (defonce *page-number (atom 0))
 
+(defn create-page
+  []
+  (page/new-page (str "page " (swap! *page-number inc))))
+
 (defn new-logseq-page
   [f]
-  (util/new-page (str "page " (swap! *page-number inc)))
+  (create-page)
   (f))

+ 3 - 3
clj-e2e/test/logseq/e2e/multi_tabs_test.clj

@@ -34,15 +34,15 @@
                 (w/with-page p2
                   (util/goto-journals)
                   (assert/assert-in-normal-mode?)
-                  (graph/switch-graph graph-name))
+                  (graph/switch-graph graph-name false))
                 (w/with-page p3
                   (util/goto-journals)
                   (assert/assert-in-normal-mode?)
-                  (graph/switch-graph graph-name))
+                  (graph/switch-graph graph-name false))
                 (w/with-page p1
                   (util/goto-journals)
                   (assert/assert-in-normal-mode?)
-                  (graph/switch-graph graph-name))
+                  (graph/switch-graph graph-name false))
                 (let [graph-new-blocks (map #(str graph-name "-b1-" %) (range 5))]
                   (add-blocks-and-check-on-other-tabs graph-new-blocks p1 [p2 p3])))]
         (w/with-page p1

+ 29 - 1
clj-e2e/test/logseq/e2e/rtc_basic_test.clj

@@ -2,8 +2,11 @@
   (:require
    [clojure.test :refer [deftest testing is use-fixtures]]
    [com.climate.claypoole :as cp]
+   [logseq.e2e.assert :as assert]
    [logseq.e2e.fixtures :as fixtures :refer [*page1 *page2]]
    [logseq.e2e.graph :as graph]
+   [logseq.e2e.page :as page]
+   [logseq.e2e.rtc :as rtc]
    [logseq.e2e.util :as util]
    [wally.main :as w]
    [wally.repl :as repl]))
@@ -11,7 +14,8 @@
 (use-fixtures :once fixtures/open-2-pages)
 
 (deftest rtc-basic-test
-  (let [graph-name (str "rtc-graph-" (.toEpochMilli (java.time.Instant/now)))]
+  (let [graph-name (str "rtc-graph-" (.toEpochMilli (java.time.Instant/now)))
+        page-names (map #(str "rtc-test-page" %) (range 4))]
     (testing "open 2 app instances, add a rtc graph, check this graph available on other instance"
       (cp/prun!
        2
@@ -22,4 +26,28 @@
         (graph/new-graph graph-name true))
       (w/with-page @*page2
         (graph/wait-for-remote-graph graph-name)
+        (graph/switch-graph graph-name true)))
+    (testing "logseq pages add/delete"
+      (doseq [page-name page-names]
+        (let [{:keys [_local-tx remote-tx]}
+              (w/with-page @*page1
+                (rtc/with-wait-tx-updated
+                  (page/new-page page-name)))]
+          (w/with-page @*page2
+            (rtc/wait-tx-update-to remote-tx)
+            (util/search-and-click page-name))))
+      (let [*last-remote-tx (atom nil)]
+        (doseq [page-name page-names]
+          (let [{:keys [_local-tx remote-tx]}
+                (w/with-page @*page1
+                  (rtc/with-wait-tx-updated
+                    (page/delete-page page-name)))]
+            (reset! *last-remote-tx remote-tx)))
+        (w/with-page @*page2
+          (rtc/wait-tx-update-to @*last-remote-tx)
+          (doseq [page-name page-names]
+            (util/search page-name)
+            (assert/assert-is-hidden (w/get-by-test-id page-name))))))
+    (testing "cleanup"
+      (w/with-page @*page2
         (graph/remove-remote-graph graph-name)))))

+ 7 - 2
deps/common/resources/templates/config.edn

@@ -136,21 +136,24 @@
  ;; :default-home {:page "home", :sidebar ["Page A" "Page B"]}
 
  ;; Set the default location for storing notes.
+ ;; This is _only_ for file graphs.
  ;; Default value: "pages"
  ;; :pages-directory "pages"
 
  ;; Set the default location for storing journals.
+ ;; This is _only_ for file graphs.
  ;; Default value: "journals"
  ;; :journals-directory "journals"
 
  ;; Set the default location for storing whiteboards.
+ ;; This is _only_ for file graphs.
  ;; Default value: "whiteboards"
  ;; :whiteboards-directory "whiteboards"
 
  ;; Enabling this option converts
- ;; This is _only_ for file graphs.
  ;; [[Grant Ideas]] to [[file:./grant_ideas.org][Grant Ideas]] for org-mode.
  ;; For more information, visit https://github.com/logseq/logseq/issues/672
+ ;; This is _only_ for file graphs.
  ;; :org-mode/insert-file-link? false
 
  ;; Configure custom shortcuts.
@@ -204,7 +207,7 @@
  ;; Example usage for DB graphs:
 ;;  :query/result-transforms
 ;;  {:sort-by-priority
-;;   (fn [result] (sort-by (fn [h] (get h :logseq.task/priority "Z")) result))}
+;;   (fn [result] (sort-by (fn [h] (get h :logseq.property/priority "Z")) result))}
 
 ;; Queries will be displayed at the bottom of today's journal page.
 ;; Example usage:
@@ -314,6 +317,7 @@
  ;; :ignored-page-references-keywords #{:author :website}
 
  ;; logbook configuration.
+ ;; This is _only_ for file graphs.
  ;; :logbook/settings
  ;; {:with-second-support? false ;limit logbook to minutes, seconds will be eliminated
  ;;  :enabled-in-all-blocks true ;display logbook in all blocks after timetracking
@@ -371,6 +375,7 @@
 
  ;; File sync options
  ;; Ignore these files when syncing, regexp is supported.
+ ;; This is _only_ for file graphs.
  ;; :file-sync/ignore-files []
 
  ;; Configure the Enter key behavior for

+ 1 - 1
deps/db/bb.edn

@@ -38,7 +38,7 @@
                                                 :property :simple-query-property :private-property
                                                 :property-scalar-default-value
                                                 :property-missing-value
-                                                :has-property-or-default-value)))))}}
+                                                :has-property-or-object-property)))))}}
 
  :tasks/config
  {:large-vars

+ 1 - 1
deps/db/src/logseq/db.cljs

@@ -586,4 +586,4 @@
   [repo]
   (if (sqlite-util/db-based-graph? repo)
     db-schema/schema
-    file-schema/schema))
+    file-schema/schema))

+ 20 - 12
deps/db/src/logseq/db/common/initial_data.cljs

@@ -6,9 +6,9 @@
             [logseq.common.config :as common-config]
             [logseq.common.util :as common-util]
             [logseq.common.util.date-time :as date-time-util]
+            [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.common.entity-util :as common-entity-util]
             [logseq.db.common.order :as db-order]
-            [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.frontend.entity-util :as entity-util]))
 
 (defn- get-pages-by-name
@@ -183,7 +183,7 @@
                                               (or children-props
                                                   [:db/id :block/uuid :block/parent :block/order :block/collapsed? :block/title
                                                    ;; pre-loading feature-related properties to avoid UI refreshing
-                                                   :logseq.task/status :logseq.property.node/display-type]))]
+                                                   :logseq.property/status :logseq.property.node/display-type]))]
                          (map
                           (fn [block]
                             (if (= children-props '[*])
@@ -238,11 +238,15 @@
           (d/datoms db :avet :block/closed-value-property))
          (mapcat (fn [d]
                    (let [block-datoms (d/datoms db :eavt (:e d))
-                         property-desc-datoms (when (= (:v d) class-property-id)
-                                                (when-let [desc (:logseq.property/description (d/entity db (:e d)))]
-                                                  (d/datoms db :eavt (:db/id desc))))]
-                     (if property-desc-datoms
-                       (concat block-datoms property-desc-datoms)
+                         properties-of-property-datoms
+                         (when (= (:v d) class-property-id)
+                           (concat
+                            (when-let [desc (:logseq.property/description (d/entity db (:e d)))]
+                              (d/datoms db :eavt (:db/id desc)))
+                            (when-let [desc (:logseq.property/default-value (d/entity db (:e d)))]
+                              (d/datoms db :eavt (:db/id desc)))))]
+                     (if (seq properties-of-property-datoms)
+                       (concat block-datoms properties-of-property-datoms)
                        block-datoms)))))))
 
 (defn- get-favorites
@@ -305,10 +309,14 @@
                             (get-structured-datoms db))
         recent-updated-pages (let [pages (get-recent-updated-pages db)]
                                (mapcat (fn [p] (d/datoms db :eavt (:db/id p))) pages))
-        pages-datoms (let [contents-id (get-first-page-by-title db "Contents")
-                           views-id (get-first-page-by-title db common-config/views-page-name)]
-                       (mapcat #(d/datoms db :eavt %)
-                               (remove nil? [contents-id views-id])))
+        pages-datoms (if db-graph?
+                       (let [contents-id (get-first-page-by-title db "Contents")
+                             views-id (get-first-page-by-title db common-config/views-page-name)]
+                         (mapcat #(d/datoms db :eavt %)
+                                 (remove nil? [contents-id views-id])))
+                       ;; load all pages for file graphs
+                       (->> (d/datoms db :avet :block/name)
+                            (mapcat (fn [d] (d/datoms db :eavt (:e d))))))
         data (distinct
               (concat idents
                       structured-datoms
@@ -318,4 +326,4 @@
                       all-files
                       pages-datoms))]
     {:schema schema
-     :initial-data data}))
+     :initial-data data}))

+ 43 - 19
deps/db/src/logseq/db/common/view.cljs

@@ -8,8 +8,8 @@
             [logseq.common.log :as log]
             [logseq.common.util :as common-util]
             [logseq.db :as ldb]
-            [logseq.db.frontend.class :as db-class]
             [logseq.db.common.entity-plus :as entity-plus]
+            [logseq.db.frontend.class :as db-class]
             [logseq.db.frontend.entity-util :as entity-util]
             [logseq.db.frontend.property :as db-property]
             [logseq.db.frontend.property.type :as db-property-type]
@@ -164,6 +164,15 @@
       :else
       value)))
 
+(defn- match-property-value-as-entity?
+  "Determines if the property value entity should be treated as an entity. For some property types
+   like :default, we want match on the entity's content as that is what the user sees and interacts with"
+  [property-value-entity property-entity]
+  ;; Allow pvalue entities with :db/ident e.g. closed values like status OR for any type
+  ;; that aren't text types
+  (or (:db/ident property-value-entity)
+      (not (contains? db-property-type/closed-value-property-types (:logseq.property/type property-entity)))))
+
 (defn- ^:large-vars/cleanup-todo row-matched?
   [db row filters input]
   (let [or? (:or? filters)
@@ -194,7 +203,12 @@
                       true
                       :else
                       (if entity?
-                        (boolean (seq (set/intersection (set (map :block/uuid value')) match)))
+                        (let [property (d/entity db property-ident)]
+                          (if (match-property-value-as-entity? (first value') property)
+                            (boolean (seq (set/intersection (set (map :block/uuid value')) match)))
+                            (boolean (seq (set/intersection (set (map db-property/property-value-content value'))
+                                                            (set (map (comp db-property/property-value-content #(d/entity db [:block/uuid %]))
+                                                                      match)))))))
                         (boolean (seq (set/intersection (set value') match))))))
 
                   :is-not
@@ -207,7 +221,12 @@
                       true
                       :else
                       (if entity?
-                        (boolean (empty? (set/intersection (set (map :block/uuid value')) match)))
+                        (let [property (d/entity db property-ident)]
+                          (if (match-property-value-as-entity? (first value') property)
+                            (boolean (empty? (set/intersection (set (map :block/uuid value')) match)))
+                            (boolean (empty? (set/intersection (set (map db-property/property-value-content value'))
+                                                               (set (map (comp db-property/property-value-content #(d/entity db [:block/uuid %]))
+                                                                         match)))))))
                         (boolean (empty? (set/intersection (set value') match))))))
 
                   :text-contains
@@ -395,9 +414,9 @@
         '[:find [?b ...]
           :in $ % ?prop
           :where
-          (has-property-or-default-value? ?b ?prop)]
+          (has-property-or-object-property? ?b ?prop)]
         db
-        (rules/extract-rules rules/db-query-dsl-rules [:has-property-or-default-value]
+        (rules/extract-rules rules/db-query-dsl-rules [:has-property-or-object-property]
                              {:deps rules/rules-dependencies})
         property-ident)
        (keep (fn [id] (non-hidden-e id))))
@@ -477,13 +496,14 @@
                                                        :else
                                                        [(str v) v])]
                                {:label label
-                                :value value})))
-                      (common-util/distinct-by :label)))]
-    (if default-value
-      (cons {:label (get-property-value-content db default-value)
-             :value (select-keys default-value [:db/id :block/uuid])}
-            values)
-      values)))
+                                :value value})))))]
+    (->>
+     (if default-value
+       (cons {:label (get-property-value-content db default-value)
+              :value (select-keys default-value [:db/id :block/uuid])}
+             values)
+       values)
+     (common-util/distinct-by :label))))
 
 (defn ^:api ^:large-vars/cleanup-todo get-view-data
   [db view-id {:keys [journals? _view-for-id view-feature-type input query-entity-ids filters sorting]
@@ -499,11 +519,7 @@
     (let [view (d/entity db view-id)
           group-by-property (:logseq.property.view/group-by-property view)
           list-view? (= :logseq.property.view/type.list (:db/ident (:logseq.property.view/type view)))
-          group-by-property-ident (or (:db/ident group-by-property)
-                                      (when (and list-view? (nil? group-by-property))
-                                        :block/page)
-                                      (when (contains? #{:linked-references :unlinked-references} view-feature-type)
-                                        :block/page))
+          group-by-property-ident (:db/ident group-by-property)
           group-by-closed-values? (some? (:property/closed-values group-by-property))
           ref-property? (= (:db/valueType group-by-property) :db.type/ref)
           filters (or (:logseq.property.table/filters view) filters)
@@ -523,9 +539,17 @@
                               (filter (fn [row] (row-matched? db row filters input)) entities)
                               entities)
           group-by-page? (= group-by-property-ident :block/page)
+          readable-property-value-or-ent
+          (fn readable-property-value-or-ent [ent]
+            (let [pvalue (get ent group-by-property-ident)]
+              (if (de/entity? pvalue)
+                (if (match-property-value-as-entity? pvalue group-by-property)
+                  pvalue
+                  (db-property/property-value-content pvalue))
+                pvalue)))
           result (if group-by-property-ident
                    (->> filtered-entities
-                        (group-by group-by-property-ident)
+                        (group-by readable-property-value-or-ent)
                         (seq)
                         (sort-by (fn [[by-value _]]
                                    (cond
@@ -543,7 +567,7 @@
                   (map
                    (fn [[by-value entities]]
                      (let [by-value' (if (de/entity? by-value)
-                                       (select-keys by-value [:db/id :block/uuid :block/title :block/name :logseq.property/value :logseq.property/icon :block/tags])
+                                       (select-keys by-value [:db/id :db/ident :block/uuid :block/title :block/name :logseq.property/value :logseq.property/icon :block/tags])
                                        by-value)
                            pages? (not (some :block/page entities))
                            group (if (and list-view? (not pages?))

+ 1 - 1
deps/db/src/logseq/db/frontend/class.cljs

@@ -35,7 +35,7 @@
 
      :logseq.class/Task
      {:title "Task"
-      :schema {:properties [:logseq.task/status :logseq.task/priority :logseq.task/deadline :logseq.task/scheduled]}}
+      :schema {:properties [:logseq.property/status :logseq.property/priority :logseq.property/deadline :logseq.property/scheduled]}}
 
      :logseq.class/Query
      {:title "Query"

+ 34 - 34
deps/db/src/logseq/db/frontend/property.cljs

@@ -277,7 +277,7 @@
                :hide? true}
       :queryable? false}
      ;; Task props
-     :logseq.task/status
+     :logseq.property/status
      {:title "Status"
       :schema
       {:type :default
@@ -291,17 +291,17 @@
                :icon {:type :tabler-icon :id icon}
                :properties (when (some? checkbox-state)
                              {:logseq.property/choice-checkbox-state checkbox-state})})
-            [[:logseq.task/status.backlog "Backlog" "Backlog"]
-             [:logseq.task/status.todo "Todo" "Todo" false]
-             [:logseq.task/status.doing "Doing" "InProgress50"]
-             [:logseq.task/status.in-review "In Review" "InReview"]
-             [:logseq.task/status.done "Done" "Done" true]
-             [:logseq.task/status.canceled "Canceled" "Cancelled"]])
+            [[:logseq.property/status.backlog "Backlog" "Backlog"]
+             [:logseq.property/status.todo "Todo" "Todo" false]
+             [:logseq.property/status.doing "Doing" "InProgress50"]
+             [:logseq.property/status.in-review "In Review" "InReview"]
+             [:logseq.property/status.done "Done" "Done" true]
+             [:logseq.property/status.canceled "Canceled" "Cancelled"]])
       :properties {:logseq.property/hide-empty-value true
-                   :logseq.property/default-value :logseq.task/status.todo
+                   :logseq.property/default-value :logseq.property/status.todo
                    :logseq.property/enable-history? true}
       :queryable? true}
-     :logseq.task/priority
+     :logseq.property/priority
      {:title "Priority"
       :schema
       {:type :default
@@ -313,13 +313,13 @@
                :value value
                :uuid (common-uuid/gen-uuid :db-ident-block-uuid db-ident)
                :icon {:type :tabler-icon :id icon}})
-            [[:logseq.task/priority.low "Low" "priorityLvlLow"]
-             [:logseq.task/priority.medium "Medium" "priorityLvlMedium"]
-             [:logseq.task/priority.high "High" "priorityLvlHigh"]
-             [:logseq.task/priority.urgent "Urgent" "priorityLvlUrgent"]])
+            [[:logseq.property/priority.low "Low" "priorityLvlLow"]
+             [:logseq.property/priority.medium "Medium" "priorityLvlMedium"]
+             [:logseq.property/priority.high "High" "priorityLvlHigh"]
+             [:logseq.property/priority.urgent "Urgent" "priorityLvlUrgent"]])
       :properties {:logseq.property/hide-empty-value true
                    :logseq.property/enable-history? true}}
-     :logseq.task/deadline
+     :logseq.property/deadline
      {:title "Deadline"
       :schema {:type :datetime
                :public? true
@@ -327,7 +327,7 @@
       :properties {:logseq.property/hide-empty-value true
                    :logseq.property/description "Use it to finish something at a specific date(time)."}
       :queryable? true}
-     :logseq.task/scheduled
+     :logseq.property/scheduled
      {:title "Scheduled"
       :schema {:type :datetime
                :public? true
@@ -335,42 +335,42 @@
       :properties {:logseq.property/hide-empty-value true
                    :logseq.property/description "Use it to plan something to start at a specific date(time)."}
       :queryable? true}
-     :logseq.task/recur-frequency
+     :logseq.property.repeat/recur-frequency
      (let [schema {:type :number
                    :public? false}]
-       {:title "Recur frequency"
+       {:title "Repeating recur frequency"
         :schema schema
         :properties {:logseq.property/hide-empty-value true
                      :logseq.property/default-value 1}
         :queryable? true})
-     :logseq.task/recur-unit
-     {:title "Recur unit"
+     :logseq.property.repeat/recur-unit
+     {:title "Repeating recur unit"
       :schema {:type :default
                :public? false}
       :closed-values (mapv (fn [[db-ident value]]
                              {:db-ident db-ident
                               :value value
                               :uuid (common-uuid/gen-uuid :db-ident-block-uuid db-ident)})
-                           [[:logseq.task/recur-unit.minute "Minute"]
-                            [:logseq.task/recur-unit.hour "Hour"]
-                            [:logseq.task/recur-unit.day "Day"]
-                            [:logseq.task/recur-unit.week "Week"]
-                            [:logseq.task/recur-unit.month "Month"]
-                            [:logseq.task/recur-unit.year "Year"]])
+                           [[:logseq.property.repeat/recur-unit.minute "Minute"]
+                            [:logseq.property.repeat/recur-unit.hour "Hour"]
+                            [:logseq.property.repeat/recur-unit.day "Day"]
+                            [:logseq.property.repeat/recur-unit.week "Week"]
+                            [:logseq.property.repeat/recur-unit.month "Month"]
+                            [:logseq.property.repeat/recur-unit.year "Year"]])
       :properties {:logseq.property/hide-empty-value true
-                   :logseq.property/default-value :logseq.task/recur-unit.day}
+                   :logseq.property/default-value :logseq.property.repeat/recur-unit.day}
       :queryable? true}
-     :logseq.task/repeated?
-     {:title "Repeated task?"
+     :logseq.property.repeat/repeated?
+     {:title "Node Repeats?"
       :schema {:type :checkbox
                :hide? true}
       :queryable? true}
-     :logseq.task/scheduled-on-property
-     {:title "Scheduled on property"
+     :logseq.property.repeat/temporal-property
+     {:title "Repeating Temporal Property"
       :schema {:type :property
                :hide? true}}
-     :logseq.task/recur-status-property
-     {:title "Recur status property"
+     :logseq.property.repeat/checked-property
+     {:title "Repeating Checked Property"
       :schema {:type :property
                :hide? true}}
 
@@ -623,9 +623,9 @@
   (set (vals schema-properties-map)))
 
 (def logseq-property-namespaces
-  #{"logseq.property" "logseq.property.tldraw" "logseq.property.pdf" "logseq.property.fsrs" "logseq.task"
+  #{"logseq.property" "logseq.property.tldraw" "logseq.property.pdf" "logseq.property.fsrs"
     "logseq.property.linked-references" "logseq.property.asset" "logseq.property.table" "logseq.property.node"
-    "logseq.property.code"
+    "logseq.property.code" "logseq.property.repeat"
     "logseq.property.journal" "logseq.property.class" "logseq.property.view"
     "logseq.property.user" "logseq.property.history"})
 

+ 10 - 12
deps/db/src/logseq/db/frontend/rules.cljc

@@ -138,21 +138,19 @@
        [(= ?t ?tc)]
        (parent ?t ?tc))]
 
-    :has-property-or-default-value
-    '[(has-property-or-default-value? ?b ?prop)
+    :has-property-or-object-property
+    '[(has-property-or-object-property? ?b ?prop)
       [?prop-e :db/ident ?prop]
       (or
        [?b ?prop _]
-       (and (object-has-class-property? ?b ?prop)
-            (or [?prop-e :logseq.property/default-value _]
-                [?prop-e :logseq.property/scalar-default-value _])))]
+       (object-has-class-property? ?b ?prop))]
 
     ;; Checks if a property exists for simple queries. Supports default values
     :has-simple-query-property
     '[(has-simple-query-property ?b ?prop)
       [?prop-e :db/ident ?prop]
       [?prop-e :block/tags :logseq.class/Property]
-      (has-property-or-default-value? ?b ?prop)
+      (has-property-or-object-property? ?b ?prop)
       (or
        [(missing? $ ?prop-e :logseq.property/public?)]
        [?prop-e :logseq.property/public? true])]
@@ -162,7 +160,7 @@
     '[(has-private-simple-query-property ?b ?prop)
       [?prop-e :db/ident ?prop]
       [?prop-e :block/tags :logseq.class/Property]
-      (has-property-or-default-value? ?b ?prop)]
+      (has-property-or-object-property? ?b ?prop)]
 
     ;; Checks if a property exists for any features that are not simple queries
     :has-property
@@ -221,13 +219,13 @@
     :task
     '[(task ?b ?statuses)
       ;; and needed to avoid binding error
-      (and (simple-query-property ?b :logseq.task/status ?val)
+      (and (simple-query-property ?b :logseq.property/status ?val)
            [(contains? ?statuses ?val)])]
 
     :priority
     '[(priority ?b ?priorities)
       ;; and needed to avoid binding error
-      (and (simple-query-property ?b :logseq.task/priority ?priority)
+      (and (simple-query-property ?b :logseq.property/priority ?priority)
            [(contains? ?priorities ?priority)])]}))
 
 (def rules-dependencies
@@ -237,10 +235,10 @@
   {:task #{:simple-query-property}
    :priority #{:simple-query-property}
    :property-missing-value #{:object-has-class-property}
-   :has-property-or-default-value #{:object-has-class-property}
+   :has-property-or-object-property #{:object-has-class-property}
    :object-has-class-property #{:parent}
-   :has-simple-query-property #{:has-property-or-default-value}
-   :has-private-simple-query-property #{:has-property-or-default-value}
+   :has-simple-query-property #{:has-property-or-object-property}
+   :has-private-simple-query-property #{:has-property-or-object-property}
    :property-default-value #{:existing-property-value :property-missing-value}
    :property-scalar-default-value #{:existing-property-value :property-missing-value}
    :property-value #{:property-default-value :property-scalar-default-value}

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

@@ -37,7 +37,7 @@
          (map (juxt :major :minor)
               [(parse-schema-version x) (parse-schema-version y)])))
 
-(def version (parse-schema-version "64.6"))
+(def version (parse-schema-version "64.8"))
 
 (defn major-version
   "Return a number.

+ 10 - 3
deps/db/src/logseq/db/sqlite/build.cljs

@@ -708,10 +708,17 @@
 
 (defn validate-options
   [{:keys [properties] :as options}]
-  (when-let [errors (->> options (m/explain Options) me/humanize)]
+  (when-let [errors (m/explain Options options)]
     (println "The build-blocks-tx has the following options errors:")
-    (pprint/pprint errors)
-    (throw (ex-info "Options validation failed" {:errors errors})))
+    (pprint/pprint (me/humanize errors))
+    (println "Invalid data for options errors:")
+    (pprint/pprint (reduce (fn [m e]
+                             (assoc-in m
+                                       (:in e)
+                                       (get-in options (:in e))))
+                           {}
+                           (:errors errors)))
+    (throw (ex-info "Options validation failed" {:errors (me/humanize errors)})))
   (when-not (:auto-create-ontology? options)
     (let [used-properties (get-used-properties-from-options options)
           undeclared-properties (-> (set (keys used-properties))

+ 7 - 5
deps/db/src/logseq/db/sqlite/export.cljs

@@ -15,7 +15,8 @@
             [logseq.db.frontend.property :as db-property]
             [logseq.db.sqlite.build :as sqlite-build]
             [medley.core :as medley]
-            [logseq.db.frontend.property.type :as db-property-type]))
+            [logseq.db.frontend.property.type :as db-property-type]
+            [logseq.db.frontend.schema :as db-schema]))
 
 ;; Export fns
 ;; ==========
@@ -743,7 +744,8 @@
         ;; Remove all non-ref uuids after all nodes are built.
         ;; Only way to ensure all pvalue uuids present across block types
         graph-export' (-> (remove-uuids-if-not-ref graph-export all-ref-uuids)
-                          (update :pages-and-blocks sort-pages-and-blocks))]
+                          (update :pages-and-blocks sort-pages-and-blocks)
+                          (assoc ::schema-version db-schema/version))]
     (cond-> graph-export'
       (not exclude-files?)
       (assoc ::graph-files files)
@@ -836,10 +838,10 @@
           (build-graph-export db (:graph-options options)))]
     (if (get-in options [:graph-options :catch-validation-errors?])
       (try
-        (ensure-export-is-valid (dissoc export-map ::block ::graph-files ::kv-values) options)
+        (ensure-export-is-valid (dissoc export-map ::block ::graph-files ::kv-values ::schema-version) options)
         (catch ExceptionInfo e
           (println "Caught error:" e)))
-      (ensure-export-is-valid (dissoc export-map ::block ::graph-files ::kv-values) options))
+      (ensure-export-is-valid (dissoc export-map ::block ::graph-files ::kv-values ::schema-version) options))
     (assoc export-map ::export-type export-type)))
 
 ;; Import fns
@@ -948,7 +950,7 @@
         {:error (str "The following imported properties conflict with the current graph: "
                      (pr-str (mapv :property-id @property-conflicts)))})
       (if (= :graph (::export-type export-map''))
-        (-> (sqlite-build/build-blocks-tx (dissoc export-map'' ::graph-files ::kv-values ::export-type))
+        (-> (sqlite-build/build-blocks-tx (dissoc export-map'' ::graph-files ::kv-values ::export-type ::schema-version))
             (assoc :misc-tx (vec (concat (::graph-files export-map'')
                                          (::kv-values export-map'')))))
         (sqlite-build/build-blocks-tx export-map'')))))

+ 5 - 5
deps/db/test/logseq/db/sqlite/build_test.cljs

@@ -53,12 +53,12 @@
            conn
            [{:page {:block/title "page1"}
              :blocks [{:block/title "some todo"
-                       :build/properties {:logseq.task/status :logseq.task/status.doing}}
+                       :build/properties {:logseq.property/status :logseq.property/status.doing}}
                       {:block/title "some slide"
                        :build/properties {:logseq.property/background-image "https://placekitten.com/200/300"}}]}])]
-    (is (= :logseq.task/status.doing
+    (is (= :logseq.property/status.doing
            (->> (db-test/find-block-by-content @conn "some todo")
-                :logseq.task/status
+                :logseq.property/status
                 :db/ident))
         "built-in property with closed value is created and correctly associated to a block")
 
@@ -103,7 +103,7 @@
         (sqlite-build/build-blocks-tx
          {:pages-and-blocks [{:page (select-keys (:block/page block) [:block/uuid])
                               :blocks [(merge {:block/title "imported task" :block/uuid (:block/uuid block)}
-                                              {:build/properties {:logseq.task/status :logseq.task/status.todo}
+                                              {:build/properties {:logseq.property/status :logseq.property/status.todo}
                                                :build/tags [:logseq.class/Task]})]}]
           :build-existing-tx? true})
         _ (d/transact! conn init-tx)
@@ -129,7 +129,7 @@
           "Tx doesn't try to create new blocks or modify existing idents")
       (is (= "imported task" (:block/title updated-block)))
       (is (= {:block/tags [:logseq.class/Task]
-              :logseq.task/status :logseq.task/status.todo}
+              :logseq.property/status :logseq.property/status.todo}
              (db-test/readable-properties updated-block))
           "Block's properties and tags are updated"))
 

+ 5 - 5
deps/db/test/logseq/db/sqlite/create_graph_test.cljs

@@ -104,23 +104,23 @@
 
     ;; testing :properties config
     (testing "A built-in property that has"
-      (is (= :logseq.task/status.todo
-             (-> (d/entity @conn :logseq.task/status)
+      (is (= :logseq.property/status.todo
+             (-> (d/entity @conn :logseq.property/status)
                  :logseq.property/default-value
                  :db/ident))
           "A property with a :db/ident property value is created correctly")
-      (is (-> (d/entity @conn :logseq.task/deadline)
+      (is (-> (d/entity @conn :logseq.property/deadline)
               :logseq.property/description
               db-property/property-value-content
               str
               (string/includes? "finish something"))
           "A :default property is created correctly")
       (is (= true
-             (-> (d/entity @conn :logseq.task/status)
+             (-> (d/entity @conn :logseq.property/status)
                  :logseq.property/enable-history?))
           "A :checkbox property is created correctly")
       (is (= 1
-             (-> (d/entity @conn :logseq.task/recur-frequency)
+             (-> (d/entity @conn :logseq.property.repeat/recur-frequency)
                  :logseq.property/default-value
                  db-property/property-value-content))
           "A numeric property is created correctly"))))

+ 1 - 1
deps/db/test/logseq/db/sqlite/export_test.cljs

@@ -253,7 +253,7 @@
                     {:block/title "b2"
                      :build/tags [:user.class/MyClass]}
                     {:block/title "some task"
-                     :build/properties {:logseq.task/status :logseq.task/status.doing}
+                     :build/properties {:logseq.property/status :logseq.property/status.doing}
                      :build/tags [:logseq.class/Task]}]}]}
         conn (db-test/create-conn-with-blocks original-data)
         conn2 (db-test/create-conn)

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

@@ -268,22 +268,22 @@
   "If a block has a marker, convert it to a task object"
   [block {:keys [log-fn]}]
   (if-let [marker (:block/marker block)]
-    (let [old-to-new {"TODO" :logseq.task/status.todo
-                      "LATER" :logseq.task/status.todo
-                      "IN-PROGRESS" :logseq.task/status.doing
-                      "NOW" :logseq.task/status.doing
-                      "DOING" :logseq.task/status.doing
-                      "DONE" :logseq.task/status.done
-                      "WAIT" :logseq.task/status.backlog
-                      "WAITING" :logseq.task/status.backlog
-                      "CANCELED" :logseq.task/status.canceled
-                      "CANCELLED" :logseq.task/status.canceled}
+    (let [old-to-new {"TODO" :logseq.property/status.todo
+                      "LATER" :logseq.property/status.todo
+                      "IN-PROGRESS" :logseq.property/status.doing
+                      "NOW" :logseq.property/status.doing
+                      "DOING" :logseq.property/status.doing
+                      "DONE" :logseq.property/status.done
+                      "WAIT" :logseq.property/status.backlog
+                      "WAITING" :logseq.property/status.backlog
+                      "CANCELED" :logseq.property/status.canceled
+                      "CANCELLED" :logseq.property/status.canceled}
           status-ident (or (old-to-new marker)
                            (do
                              (log-fn :invalid-todo (str (pr-str marker) " is not a valid marker so setting it to TODO"))
-                             :logseq.task/status.todo))]
+                             :logseq.property/status.todo))]
       (-> block
-          (assoc :logseq.task/status status-ident)
+          (assoc :logseq.property/status status-ident)
           (update :block/title string/replace-first (re-pattern (str marker "\\s*")) "")
           (update :block/tags (fnil conj []) :logseq.class/Task)
           (dissoc :block/marker)))
@@ -292,15 +292,15 @@
 (defn- update-block-priority
   [block {:keys [log-fn]}]
   (if-let [priority (:block/priority block)]
-    (let [old-to-new {"A" :logseq.task/priority.high
-                      "B" :logseq.task/priority.medium
-                      "C" :logseq.task/priority.low}
+    (let [old-to-new {"A" :logseq.property/priority.high
+                      "B" :logseq.property/priority.medium
+                      "C" :logseq.property/priority.low}
           priority-value (or (old-to-new priority)
                              (do
                                (log-fn :invalid-priority (str (pr-str priority) " is not a valid priority so setting it to low"))
-                               :logseq.task/priority.low))]
+                               :logseq.property/priority.low))]
       (-> block
-          (assoc :logseq.task/priority priority-value)
+          (assoc :logseq.property/priority priority-value)
           (update :block/title string/replace-first (re-pattern (str "\\[#" priority "\\]" "\\s*")) "")
           (dissoc :block/priority)))
     block))
@@ -325,7 +325,7 @@
                                       :block/journal-day date-int)))
                          (assoc :block/tags #{:logseq.class/Journal}))
           time-long (tc/to-long (date-time-util/int->local-date date-int))
-          datetime-property (if (:block/deadline block) :logseq.task/deadline :logseq.task/scheduled)]
+          datetime-property (if (:block/deadline block) :logseq.property/deadline :logseq.property/scheduled)]
       {:block
        (-> block
            (assoc datetime-property time-long)

+ 9 - 9
deps/graph-parser/test/logseq/graph_parser/exporter_test.cljs

@@ -169,7 +169,7 @@
           "Created graph has no validation errors")
 
       ;; Counts
-      ;; Includes journals as property values e.g. :logseq.task/deadline
+      ;; Includes journals as property values e.g. :logseq.property/deadline
       (is (= 25 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Journal]] @conn))))
 
       (is (= 4 (count (d/q '[:find ?b :where [?b :block/tags :logseq.class/Task]] @conn))))
@@ -288,13 +288,13 @@
 
       (is (= 20221126
              (-> (db-test/readable-properties (db-test/find-block-by-content @conn "only deadline"))
-                 :logseq.task/deadline
+                 :logseq.property/deadline
                  date-time-util/ms->journal-day))
           "deadline block has correct journal as property value")
 
       (is (= 20221125
              (-> (db-test/readable-properties (db-test/find-block-by-content @conn "only scheduled"))
-                 :logseq.task/scheduled
+                 :logseq.property/scheduled
                  date-time-util/ms->journal-day))
           "scheduled block converted to correct deadline")
 
@@ -304,17 +304,17 @@
                            @conn "Apr 1st, 2024")))
           "Only one journal page exists when deadline is on same day as journal")
 
-      (is (= {:logseq.task/priority :logseq.task/priority.high}
+      (is (= {:logseq.property/priority :logseq.property/priority.high}
              (db-test/readable-properties (db-test/find-block-by-content @conn "high priority")))
           "priority block has correct property")
 
-      (is (= {:logseq.task/status :logseq.task/status.doing
-              :logseq.task/priority :logseq.task/priority.medium
+      (is (= {:logseq.property/status :logseq.property/status.doing
+              :logseq.property/priority :logseq.property/priority.medium
               :block/tags [:logseq.class/Task]}
              (db-test/readable-properties (db-test/find-block-by-content @conn "status test")))
           "status block has correct task properties and class")
 
-      (is (= #{:logseq.task/status :block/tags}
+      (is (= #{:logseq.property/status :block/tags}
              (set (keys (db-test/readable-properties (db-test/find-block-by-content @conn "old todo block")))))
           "old task properties like 'todo' are ignored")
 
@@ -484,14 +484,14 @@
 
       (let [block (db-test/find-block-by-content @conn "old todo block")]
         (is (set/subset?
-             #{:logseq.task/status :logseq.class/Task}
+             #{:logseq.property/status :logseq.class/Task}
              (->> block
                   :block/refs
                   (map #(:db/ident (d/entity @conn (:db/id %))))
                   set))
             "Block has correct task tag and property :block/refs")
         (is (set/subset?
-             #{:logseq.task/status :logseq.class/Task}
+             #{:logseq.property/status :logseq.class/Task}
              (->> block
                   :block/path-refs
                   (map #(:db/ident (d/entity @conn (:db/id %))))

+ 4 - 1
deps/outliner/src/logseq/outliner/core.cljs

@@ -5,6 +5,7 @@
             [clojure.walk :as walk]
             [datascript.core :as d]
             [datascript.impl.entity :as de :refer [Entity]]
+            [logseq.common.config :as common-config]
             [logseq.common.util :as common-util]
             [logseq.common.util.page-ref :as page-ref]
             [logseq.common.uuid :as common-uuid]
@@ -163,7 +164,9 @@
                            (map (fn [id-or-map] (if (uuid? id-or-map) {:block/uuid id-or-map} id-or-map)))
                            (remove (fn [b] (nil? (d/entity db [:block/uuid (:block/uuid b)])))))
         content-refs (when-let [content (:block/title block)]
-                       (gp-block/extract-refs-from-text repo db content date-formatter))]
+                       (let [format (or (:block/format block) :markdown)
+                             content' (str (common-config/get-block-pattern format) " " content)]
+                         (gp-block/extract-refs-from-text repo db content' date-formatter)))]
     (concat property-refs content-refs)))
 
 (defn ^:api rebuild-block-refs

+ 6 - 6
deps/outliner/src/logseq/outliner/op.cljs

@@ -1,12 +1,12 @@
 (ns logseq.outliner.op
   "Transact outliner ops"
-  (:require [logseq.outliner.transaction :as outliner-tx]
-            [logseq.outliner.core :as outliner-core]
-            [logseq.outliner.property :as outliner-property]
+  (:require [clojure.string :as string]
             [datascript.core :as d]
-            [malli.core :as m]
             [logseq.db :as ldb]
-            [clojure.string :as string]))
+            [logseq.outliner.core :as outliner-core]
+            [logseq.outliner.property :as outliner-property]
+            [logseq.outliner.transaction :as outliner-tx]
+            [malli.core :as m]))
 
 (def ^:private ^:large-vars/data-var op-schema
   [:multi {:dispatch first}
@@ -64,7 +64,7 @@
    [:batch-set-property
     [:catn
      [:op :keyword]
-     [:args [:tuple ::block-ids ::property-id ::value]]]]
+     [:args [:tuple ::block-ids ::property-id ::value ::option]]]]
    [:batch-remove-property
     [:catn
      [:op :keyword]

+ 53 - 46
deps/outliner/src/logseq/outliner/property.cljs

@@ -6,9 +6,9 @@
             [datascript.impl.entity :as de]
             [logseq.common.util :as common-util]
             [logseq.db :as ldb]
+            [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.common.order :as db-order]
             [logseq.db.frontend.db-ident :as db-ident]
-            [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.frontend.entity-util :as entity-util]
             [logseq.db.frontend.malli-schema :as db-malli-schema]
             [logseq.db.frontend.property :as db-property]
@@ -39,7 +39,7 @@
           update-block-tx (cond-> (outliner-core/block-with-updated-at {:db/id (:db/id block)})
                             true
                             (assoc property-id value)
-                            (and (contains? #{:logseq.task/status :logseq.task/scheduled :logseq.task/deadline} property-id)
+                            (and (contains? #{:logseq.property/status :logseq.property/scheduled :logseq.property/deadline} property-id)
                                  (or (empty? (:block/tags block)) (ldb/internal-page? block)))
                             (assoc :block/tags :logseq.class/Task))]
       (cond-> []
@@ -242,15 +242,19 @@
   "Converts a ref property's value whether it's an integer or a string. Creates
    a property ref value for a string value if necessary"
   [conn property-id v property-type]
-  (if (and (integer? v)
-           (or (not= property-type :number)
+  (let [number-property? (= property-type :number)]
+    (if (and (integer? v)
+             (or (not number-property?)
                ;; Allows :number property to use number as a ref (for closed value) or value
-               (and (= property-type :number)
-                    (or (= property-id (:db/ident (:logseq.property/created-from-property (d/entity @conn v))))
-                        (= :logseq.property/empty-placeholder (:db/ident (d/entity @conn v)))))))
-    v
-    ;; only value-ref-property types should call this
-    (find-or-create-property-value conn property-id v)))
+                 (and number-property?
+                      (or (= property-id (:db/ident (:logseq.property/created-from-property (d/entity @conn v))))
+                          (= :logseq.property/empty-placeholder (:db/ident (d/entity @conn v)))))))
+      v
+      ;; only value-ref-property types should call this
+      (let [v' (if (and number-property? (string? v))
+                 (parse-double v)
+                 v)]
+        (find-or-create-property-value conn property-id v')))))
 
 (defn- throw-error-if-self-value
   [block value ref?]
@@ -295,42 +299,45 @@
 (defn batch-set-property!
   "Sets properties for multiple blocks. Automatically handles property value refs.
    Does no validation of property values."
-  [conn block-ids property-id v]
-  (assert property-id "property-id is nil")
-  (throw-error-if-read-only-property property-id)
-  (if (nil? v)
-    (batch-remove-property! conn block-ids property-id)
-    (let [block-eids (map ->eid block-ids)
-          _ (when (= property-id :block/tags)
-              (outliner-validate/validate-tags-property @conn block-eids v))
-          property (d/entity @conn property-id)
-          _ (when (= (:db/ident property) :logseq.property/parent)
-              (outliner-validate/validate-parent-property
-               (if (number? v) (d/entity @conn v) v)
-               (map #(d/entity @conn %) block-eids)))
-          _ (assert (some? property) (str "Property " property-id " doesn't exist yet"))
-          property-type (get property :logseq.property/type :default)
-          _ (assert (some? v) "Can't set a nil property value must be not nil")
-          ref? (db-property-type/value-ref-property-types property-type)
-          default-url-not-closed? (and (contains? #{:default :url} property-type)
-                                       (not (seq (:property/closed-values property))))
-          v' (if ref?
-               (convert-ref-property-value conn property-id v property-type)
-               v)
-          txs (doall
-               (mapcat
-                (fn [eid]
-                  (if-let [block (d/entity @conn eid)]
-                    (let [v' (if default-url-not-closed?
-                               (let [v (if (number? v) (:block/title (d/entity @conn v)) v)]
-                                 (convert-ref-property-value conn property-id v property-type))
-                               v')]
-                      (throw-error-if-self-value block v' ref?)
-                      (build-property-value-tx-data conn block property-id v'))
-                    (js/console.error "Skipping setting a block's property because the block id could not be found:" eid)))
-                block-eids))]
-      (when (seq txs)
-        (ldb/transact! conn txs {:outliner-op :save-block})))))
+  ([conn block-ids property-id v]
+   (batch-set-property! conn block-ids property-id v {}))
+  ([conn block-ids property-id v options]
+   (assert property-id "property-id is nil")
+   (throw-error-if-read-only-property property-id)
+   (if (nil? v)
+     (batch-remove-property! conn block-ids property-id)
+     (let [block-eids (map ->eid block-ids)
+           _ (when (= property-id :block/tags)
+               (outliner-validate/validate-tags-property @conn block-eids v))
+           property (d/entity @conn property-id)
+           _ (when (= (:db/ident property) :logseq.property/parent)
+               (outliner-validate/validate-parent-property
+                (if (number? v) (d/entity @conn v) v)
+                (map #(d/entity @conn %) block-eids)))
+           _ (assert (some? property) (str "Property " property-id " doesn't exist yet"))
+           property-type (get property :logseq.property/type :default)
+           _ (assert (some? v) "Can't set a nil property value must be not nil")
+           ref? (contains? db-property-type/all-ref-property-types property-type)
+           default-url-not-closed? (and (contains? #{:default :url} property-type)
+                                        (not (seq (:property/closed-values property))))
+           entity-id? (and (:entity-id? options) (number? v))
+           v' (if (and ref? (not entity-id?))
+                (convert-ref-property-value conn property-id v property-type)
+                v)
+           txs (doall
+                (mapcat
+                 (fn [eid]
+                   (if-let [block (d/entity @conn eid)]
+                     (let [v' (if default-url-not-closed?
+                                (let [v (if (number? v) (:block/title (d/entity @conn v)) v)]
+                                  (convert-ref-property-value conn property-id v property-type))
+                                v')]
+                       (throw-error-if-self-value block v' ref?)
+                       (build-property-value-tx-data conn block property-id v'))
+                     (js/console.error "Skipping setting a block's property because the block id could not be found:" eid)))
+                 block-eids))]
+       (when (seq txs)
+         (ldb/transact! conn txs {:outliner-op :save-block}))))))
 
 (defn remove-block-property!
   [conn eid property-id]

+ 14 - 1
deps/outliner/src/logseq/outliner/validate.cljs

@@ -69,7 +69,8 @@
   (cond
     (seq tags)
     (when-let [another-id (first
-                           (d/q (if (ldb/property? entity)
+                           (d/q (cond
+                                  (ldb/property? entity)
                                   ;; Property names are unique in that they can
                                   ;; have the same names as built-in property names
                                   '[:find [?b ...]
@@ -79,6 +80,18 @@
                                     [?b :block/tags ?tag-id]
                                     [(missing? $ ?b :logseq.property/built-in?)]
                                     [(not= ?b ?eid)]]
+                                  (:logseq.property/parent entity)
+                                  '[:find [?b ...]
+                                    :in $ ?eid ?title [?tag-id ...]
+                                    :where
+                                    [?b :block/title ?title]
+                                    [?b :block/tags ?tag-id]
+                                    [(not= ?b ?eid)]
+                                    ;; same parent
+                                    [?b :logseq.property/parent ?bp]
+                                    [?eid :logseq.property/parent ?ep]
+                                    [(= ?bp ?ep)]]
+                                  :else
                                   '[:find [?b ...]
                                     :in $ ?eid ?title [?tag-id ...]
                                     :where

+ 4 - 4
deps/outliner/test/logseq/outliner/property_test.cljs

@@ -197,7 +197,7 @@
 
 (deftest status-property-setting-classes
   (let [conn (db-test/create-conn-with-blocks
-              {:classes {:Project {:build/class-properties [:logseq.task/status]}}
+              {:classes {:Project {:build/class-properties [:logseq.property/status]}}
                :pages-and-blocks
                [{:page {:block/title "page1"}
                  :blocks [{:block/title ""}
@@ -206,17 +206,17 @@
         [empty-task project]
         (map #(:block/uuid (db-test/find-block-by-content @conn %)) ["" "project task"])]
 
-    (outliner-property/batch-set-property! conn [empty-task] :logseq.task/status :logseq.task/status.doing)
+    (outliner-property/batch-set-property! conn [empty-task] :logseq.property/status :logseq.property/status.doing)
     (is (= [:logseq.class/Task]
            (mapv :db/ident (:block/tags (d/entity @conn [:block/uuid empty-task]))))
         "Adds Task to block when it is not tagged")
 
-    (outliner-property/batch-set-property! conn [page1] :logseq.task/status :logseq.task/status.doing)
+    (outliner-property/batch-set-property! conn [page1] :logseq.property/status :logseq.property/status.doing)
     (is (= #{:logseq.class/Task :logseq.class/Page}
            (set (map :db/ident (:block/tags (d/entity @conn [:block/uuid page1])))))
         "Adds Task to page without tag")
 
-    (outliner-property/batch-set-property! conn [project] :logseq.task/status :logseq.task/status.doing)
+    (outliner-property/batch-set-property! conn [project] :logseq.property/status :logseq.property/status.doing)
     (is (= [:user.class/Project]
            (mapv :db/ident (:block/tags (d/entity @conn [:block/uuid project]))))
         "Doesn't add Task to block when it is already tagged")))

+ 1 - 1
deps/outliner/test/logseq/outliner/validate_test.cljs

@@ -138,7 +138,7 @@
     (is (thrown-with-msg?
          js/Error
          #"Can't set tag.*Priority"
-         (outliner-validate/validate-tags-property @conn [(:db/id block)] :logseq.task/priority))
+         (outliner-validate/validate-tags-property @conn [(:db/id block)] :logseq.property/priority))
         "Nodes can't be tagged with built-in non tags")))
 
 ;; Try as many of the validations against a new graph to confirm

+ 1 - 1
deps/shui/src/logseq/shui/table/core.cljc

@@ -276,7 +276,7 @@
 (rum/defc table-row < rum/static
   [& prop-and-children]
   (let [[prop children] (get-prop-and-children prop-and-children)]
-    [:div.ls-table-row.flex.flex-row.items-center
+    [:div.ls-table-row.ls-block.flex.flex-row.items-center
      (merge {:class "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted bg-gray-01 items-stretch"}
             prop)
      children]))

+ 1 - 1
packages/tldraw/apps/tldraw-logseq/src/components/ContextBar/ContextBar.tsx

@@ -54,7 +54,7 @@ const _ContextBar: TLContextBarComponent<Shape> = ({ shapes, offsets, hidden })
             <React.Fragment key={idx}>
               <Action />
               {idx < Actions.length - 1 && (
-                <LSUI.Separator orientation="vertical" />
+                <LSUI.Separator className="tl-toolbar-separator" orientation="vertical" />
               )}
             </React.Fragment>
           ))}

+ 1 - 1
packages/tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx

@@ -489,7 +489,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
                 setTimeout(() => {
                   app.api.editShape(this)
                   window.logseq?.api?.edit_block?.(uuid)
-                })
+                }, 128)
               }}
               placeholder="Create or search your graph..."
             />

+ 2 - 1
packages/tldraw/apps/tldraw-logseq/src/styles.css

@@ -859,7 +859,8 @@ html[data-theme='dark'] {
 .tl-toolbar-separator {
   background-color: var(--ls-border-color, var(--rx-gray-06));
   width: 1px;
-  opacity: 0.9;
+  height: 24px;
+  opacity: 0.6;
 
   &[data-orientation='horizontal'] {
     height: 1px;

+ 22 - 13
src/electron/electron/window.cljs

@@ -4,7 +4,7 @@
             [electron.configs :as cfgs]
             [electron.context-menu :as context-menu]
             [electron.logger :as logger]
-            ["electron" :refer [BrowserWindow app session shell] :as electron]
+            ["electron" :refer [BrowserWindow app session shell dialog] :as electron]
             ["path" :as node-path]
             ["url" :as URL]
             [electron.state :as state]
@@ -125,9 +125,18 @@
   [url default-open]
   (let [URL (.-URL URL)
         parsed-url (try (URL. url) (catch :default _ nil))]
-    (if (and parsed-url (contains? #{"https:" "http:" "mailto:"} (.-protocol parsed-url)))
-      (.openExternal shell url)
-      (when default-open (default-open url)))))
+    (when parsed-url
+      (if (contains? #{"https:" "http:" "mailto:"} (.-protocol parsed-url))
+        (.openExternal shell url)
+        (when-let [^js res (and (fn? default-open)
+                             (.showMessageBoxSync dialog
+                               #js {:type "warning"
+                                    :message (str "Are you sure you want to open this link? \n\n" url)
+                                    :defaultId 1
+                                    :cancelId 0
+                                    :buttons #js ["Cancel" "OK"]}))]
+          (when (= res 1)
+            (default-open url)))))))
 
 (defn setup-window-listeners!
   [^js win]
@@ -167,18 +176,18 @@
               (-> (if (= url "about:blank")
                     (merge {:action "allow"
                             :overrideBrowserWindowOptions
-                            {:frame                true
-                             :titleBarStyle        "default"
+                            {:frame true
+                             :titleBarStyle "default"
                              :trafficLightPosition {:x 16 :y 16}
-                             :autoHideMenuBar      (not mac?)
-                             :fullscreenable       (not fullscreen?)
+                             :autoHideMenuBar (not mac?)
+                             :fullscreenable (not fullscreen?)
                              :webPreferences
-                             {:plugins          true
-                              :nodeIntegration  false
-                              :webSecurity      (not dev?)
-                              :preload          (node-path/join js/__dirname "js/preload.js")
+                             {:plugins true
+                              :nodeIntegration false
+                              :webSecurity (not dev?)
+                              :preload (node-path/join js/__dirname "js/preload.js")
                               :nativeWindowOpen true}}}
-                           features)
+                      features)
                     (do (open-external! url) {:action "deny"}))
                   (bean/->js))))]
 

+ 7 - 6
src/main/frontend/commands.cljs

@@ -125,7 +125,7 @@
 (defn db-based-statuses
   []
   (map (fn [e] (:block/title e))
-       (db-pu/get-closed-property-values :logseq.task/status)))
+       (db-pu/get-closed-property-values :logseq.property/status)))
 
 (defn db-based-embed-page
   []
@@ -260,7 +260,7 @@
 (defn db-based-priorities
   []
   (map (fn [e] (:block/title e))
-       (db-pu/get-closed-property-values :logseq.task/priority)))
+       (db-pu/get-closed-property-values :logseq.property/priority)))
 
 (defn get-priorities
   []
@@ -425,7 +425,8 @@
        ["Advanced Query" (advanced-query-steps) "Create an advanced query block" :icon/query]
        (when-not db?
          ["Zotero" (zotero-steps) "Import Zotero journal article" :icon/circle-letter-z])
-       ["Query function" [[:editor/input "{{function }}" {:backward-pos 2}]] "Create a query function" :icon/queryCode]
+       (when-not db?
+         ["Query function" [[:editor/input "{{function }}" {:backward-pos 2}]] "Create a query function" :icon/queryCode])
        ["Calculator"
         (calc-steps)
         "Insert a calculator" :icon/calculator]
@@ -756,7 +757,7 @@
 (defn- db-based-set-status
   [status]
   (when-let [block (state/get-edit-block)]
-    (db-property-handler/batch-set-property-closed-value! [(:block/uuid block)] :logseq.task/status status)))
+    (db-property-handler/batch-set-property-closed-value! [(:block/uuid block)] :logseq.property/status status)))
 
 (defmethod handle-step :editor/set-status [[_ status] format]
   (if (config/db-based-graph? (state/get-current-repo))
@@ -795,8 +796,8 @@
   [priority]
   (when-let [block (state/get-edit-block)]
     (if (nil? priority)
-      (db-property-handler/remove-block-property! (:block/uuid block) :logseq.task/priority)
-      (db-property-handler/batch-set-property-closed-value! [(:block/uuid block)] :logseq.task/priority priority))))
+      (db-property-handler/remove-block-property! (:block/uuid block) :logseq.property/priority)
+      (db-property-handler/batch-set-property-closed-value! [(:block/uuid block)] :logseq.property/priority priority))))
 
 (defmethod handle-step :editor/set-priority [[_ priority] _format]
   (if (config/db-based-graph? (state/get-current-repo))

+ 3 - 1
src/main/frontend/common.css

@@ -326,7 +326,9 @@ h1.title, h1.title input, .ls-page-title-container {
 
 .block-highlight,
 .ls-block.selected,
-.ls-dummy-block.selected {
+.ls-dummy-block.selected,
+.ls-table-cell.selected
+{
   transition: background-color 0.2s cubic-bezier(0, 1, 0, 1);
   background-color: var(--ls-block-highlight-color, var(--rx-gray-04));
 }

+ 64 - 47
src/main/frontend/components/block.cljs

@@ -45,6 +45,7 @@
             [frontend.handler.db-based.property :as db-property-handler]
             [frontend.handler.dnd :as dnd]
             [frontend.handler.editor :as editor-handler]
+            [frontend.handler.file-based.editor :as file-editor-handler]
             [frontend.handler.export.common :as export-common-handler]
             [frontend.handler.file-based.property.util :as property-util]
             [frontend.handler.file-sync :as file-sync]
@@ -79,8 +80,8 @@
             [logseq.common.util.macro :as macro-util]
             [logseq.common.util.page-ref :as page-ref]
             [logseq.db :as ldb]
-            [logseq.db.frontend.content :as db-content]
             [logseq.db.common.entity-plus :as entity-plus]
+            [logseq.db.frontend.content :as db-content]
             [logseq.graph-parser.block :as gp-block]
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.text :as text]
@@ -2371,9 +2372,22 @@
         opacity (if hover? "opacity-100" "opacity-0")
         query (:logseq.property/query block)
         advanced-query? (and query? (= :code (:logseq.property.node/display-type query)))
-        show-query? (and *show-query? @*show-query?)]
+        show-query? (and *show-query? @*show-query?)
+        query-setting (when query?
+                        (ui/tooltip
+                         (shui/button
+                          {:size :sm
+                           :variant :ghost
+                           :class (str "ls-query-setting ls-small-icon text-muted-foreground ml-2 w-6 h-6 transition-opacity ease-in duration-300 " opacity)
+                           :on-pointer-down (fn [e]
+                                              (util/stop e)
+                                              (when *show-query? (swap! *show-query? not)))}
+                          (ui/icon "settings"))
+                         [:div.opacity-75 (if show-query?
+                                            "Hide query"
+                                            "Set query")]))]
     [:div.w-full
-     {:class (if (and query? blank?)
+     {:class (if query?
                "inline-flex"
                "inline")
       :on-mouse-over #(set-hover? true)
@@ -2384,20 +2398,8 @@
        (and query? blank?)
        (query-builder-component/builder query {})
        :else
-       [:div.inline (text-block-title config block)])
-     (when query?
-       (ui/tooltip
-        (shui/button
-         {:size :sm
-          :variant :ghost
-          :class (str "ls-small-icon text-muted-foreground ml-2 w-6 h-6 transition-opacity ease-in duration-300 " opacity)
-          :on-pointer-down (fn [e]
-                             (util/stop e)
-                             (when *show-query? (swap! *show-query? not)))}
-         (ui/icon "settings"))
-        [:div.opacity-75 (if show-query?
-                           "Hide query"
-                           "Set query")]))
+       (text-block-title config block))
+     query-setting
      (when-let [property (:logseq.property/created-from-property block)]
        (when-let [message (when (= :url (:logseq.property/type property))
                             (first (outliner-property/validate-property-value (db/get-db) property (:db/id block))))]
@@ -2847,7 +2849,7 @@
        (fn []
          (p/let [result (db-async/<task-spent-time repo (:db/id block))]
            (set-result! result)))
-       [(:logseq.task/status block)])
+       [(:logseq.property/status block)])
       (when (and time-spent (> time-spent 0))
         [:div.text-sm.time-spent.ml-1
          (shui/button
@@ -3008,6 +3010,37 @@
     (when-not edit?
       [:div.more (ui/icon "dots-circle-horizontal" {:size 18})])]])
 
+(rum/defc block-content-with-error
+  [config block edit-input-id block-id slide? *show-query? editor-box]
+  (let [[editing? set-editing!] (hooks/use-state false)
+        query (:logseq.property/query block)]
+    (ui/catch-error
+     (if query
+       (if editing?
+         (editor-box {:block query
+                      :block-id (:block/uuid query)
+                      :block-parent-id uuid
+                      :format (get block :block/format :markdown)}
+                     (str "edit-block-" (:block/uuid query))
+                     (assoc config :editor-opts {:on-blur #(set-editing! false)}))
+         [:a.text-sm
+          {:on-click (fn []
+                       (set-editing! true)
+                       (editor-handler/edit-block! query :max {:container-id (:container-id config)}))}
+          "Click to fix query: "
+          (:block/title query)])
+       [:div.flex.flex-1.flex-col.w-full.gap-2
+        (ui/block-error "Block Render Error:"
+                        {:content (or (:block/title query)
+                                      (:block/title block))
+                         :section-attrs
+                         {:on-click #(let [content (:block/title block)]
+                                       (editor-handler/clear-selection!)
+                                       (editor-handler/unhighlight-blocks!)
+                                       (state/set-editing! edit-input-id content block "" {:db (db/get-db)
+                                                                                           :container-id (:container-id config)}))}})])
+     (block-content config block edit-input-id block-id slide? *show-query?))))
+
 (rum/defcs ^:large-vars/cleanup-todo block-content-or-editor < rum/reactive
   (rum/local false ::hover?)
   [state config {:block/keys [uuid] :as block} {:keys [edit-input-id block-id edit? hide-block-refs-count? refs-count *hide-block-refs? *show-query?]}]
@@ -3048,17 +3081,7 @@
                        edit-input-id
                        config))]
          [:div.flex.flex-1.w-full.block-content-wrapper {:style {:display (if (:slide? config) "block" "flex")}}
-          (ui/catch-error
-           (ui/block-error "Block Render Error:"
-                           {:content (:block/title block)
-                            :section-attrs
-                            {:on-click #(let [content (or (:block/title block)
-                                                          (:block/title block))]
-                                          (editor-handler/clear-selection!)
-                                          (editor-handler/unhighlight-blocks!)
-                                          (state/set-editing! edit-input-id content block "" {:db (db/get-db)
-                                                                                              :container-id (:container-id config)}))}})
-           (block-content config block edit-input-id block-id slide? *show-query?))
+          (block-content-with-error config block edit-input-id block-id slide? *show-query? editor-box)
 
           (when (and (not hide-block-refs-count?)
                      (not named?)
@@ -3306,7 +3329,7 @@
               (when (and (config/local-file-based-graph? repo) (not (state/editing?)))
                 ;; Basically the same logic as editor-handler/upload-asset,
                 ;; does not require edting
-                (-> (editor-handler/file-based-save-assets! repo (js->clj files))
+                (-> (file-editor-handler/file-based-save-assets! repo (js->clj files))
                     (p/then
                      (fn [res]
                        (when-let [[asset-file-name file-obj asset-file-fpath matched-alias] (first res)]
@@ -3316,7 +3339,7 @@
                                                                                   (str
                                                                                    (if image? "../assets/" "")
                                                                                    "@" (:name matched-alias) "/" asset-file-name)
-                                                                                  (editor-handler/resolve-relative-path (or asset-file-fpath asset-file-name)))
+                                                                                  (file-editor-handler/resolve-relative-path (or asset-file-fpath asset-file-name)))
                                                                                 (if file-obj (.-name file-obj) (if image? "image" "asset"))
                                                                                 image?)]
                            (editor-handler/api-insert-new-block!
@@ -3534,8 +3557,7 @@
        :data-is-property (ldb/property? block)
        :ref #(when (nil? @*ref) (reset! *ref %))
        :data-collapsed (and collapsed? has-child?)
-       :class (str "id" uuid " "
-                   (when selected? " selected")
+       :class (str (when selected? "selected")
                    (when pre-block? " pre-block")
                    (when order-list? " is-order-list")
                    (when (string/blank? title) " is-blank")
@@ -3763,20 +3785,15 @@
                   (assoc config :container-id container-id)
                   config)]
     (when (:block/uuid block)
-      (ui/catch-error
-       (fn [^js error]
-         [:div.flex.flex-col.pl-6.my-1
-          [:code (str "#uuid\"" (:block/uuid block) "\"")]
-          [:code.flex.p-1.text-red-rx-09 "Block render error: " (.-message error)]])
-       (rum/with-key
-         (block-container-inner state repo config' block
-                                (merge
-                                 opts
-                                 {:navigating-block navigating-block :navigated? navigated?}))
-         (str "block-inner-"
-              (:container-id config)
-              "-"
-              (:block/uuid block)))))))
+      (rum/with-key
+        (block-container-inner state repo config' block
+                               (merge
+                                opts
+                                {:navigating-block navigating-block :navigated? navigated?}))
+        (str "block-inner-"
+             (:container-id config)
+             "-"
+             (:block/uuid block))))))
 
 (rum/defc block-container
   [config block* & {:as opts}]

+ 1 - 1
src/main/frontend/components/block.css

@@ -533,7 +533,7 @@
 }
 
 .ls-block {
-  @apply relative py-0.5 transition-[background-color] mx-auto;
+  @apply flex-1 relative py-0.5 transition-[background-color] mx-auto;
 
   &.selected {
     @apply rounded;

+ 16 - 8
src/main/frontend/components/cmdk/core.cljs

@@ -58,7 +58,7 @@
   (let [current-page (state/get-current-page)]
     (->>
      [(when current-page
-        {:filter {:group :current-page} :text "Search only current page" :info "Add filter to search" :icon-theme :gray :icon "page"})
+        {:filter {:group :current-page} :text "Search only current page" :info "Add filter to search" :icon-theme :gray :icon "file"})
       {:filter {:group :nodes} :text "Search only nodes" :info "Add filter to search" :icon-theme :gray :icon "letter-n"}
       {:filter {:group :commands} :text "Search only commands" :info "Add filter to search" :icon-theme :gray :icon "command"}
       {:filter {:group :files} :text "Search only files" :info "Add filter to search" :icon-theme :gray :icon "file"}
@@ -112,9 +112,17 @@
                             (take (get-group-limit group) items))))
         node-exists? (let [blocks-result (keep :source-block (get-in results [:nodes :items]))]
                        (when-not (string/blank? input)
-                         (or (some-> (text/get-namespace-last-part input)
-                                     string/trim
-                                     db/get-page)
+                         (or (let [page (some-> (text/get-namespace-last-part input)
+                                                string/trim
+                                                db/get-page)
+                                   parent-title (:block/title (:logseq.property/parent page))
+                                   namespace? (string/includes? input "/")]
+                               (and page
+                                    (or (not namespace?)
+                                        (and
+                                         parent-title
+                                         (= (util/page-name-sanity-lc parent-title)
+                                            (util/page-name-sanity-lc (nth (reverse (string/split input "/")) 1)))))))
                              (some (fn [block]
                                      (and
                                       (:block/tags block)
@@ -192,9 +200,9 @@
     (ldb/property? entity)
     "letter-p"
     (ldb/whiteboard? entity)
-    "whiteboard"
+    "writing"
     :else
-    "page"))
+    "file"))
 
 (defmethod load-results :initial [_ state]
   (when-let [db (db/get-db)]
@@ -518,7 +526,7 @@
                      create-page? (page-handler/<create! @!input {:redirect? true}))]
       (shui/dialog-close! :ls-dialog-cmdk)
       (when (and create-class? result)
-        (state/pub-event! [:class/configure result])))))
+        (state/sidebar-add-block! (state/get-current-repo) (:db/id result) :block)))))
 
 (defn- get-filter-user-input
   [input]
@@ -622,7 +630,7 @@
       [:div.search-results
        (for [item visible-items
              :let [highlighted? (= item highlighted-item)
-                   page? (= "page" (some-> item :icon))
+                   page? (= "file" (some-> item :icon))
                    text (some-> item :text)
                    source-page (some-> item :source-page)
                    hls-page? (and page? (pdf-utils/hls-file? (:block/title source-page)))]]

+ 1 - 1
src/main/frontend/components/cmdk/list_item.cljs

@@ -40,7 +40,7 @@
             highlighted-text (string/replace normal-text query-re "<:hlmarker>$1<:hlmarker>")
             segs (string/split highlighted-text #"<:hlmarker>")]
         (if (seq segs)
-          (into [:span {:aria-label text-string}]
+          (into [:span {:data-testid text-string}]
                 (map-indexed (fn [i seg]
                                (if (even? i)
                                  [:span seg]

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

@@ -283,8 +283,7 @@
                 :href (rfe/href :whiteboards)
                 :on-click-handler (fn [_e] (whiteboard-handler/onboarding-show))
                 :active (and (not srs-open?) (#{:whiteboard :whiteboards} route-name))
-                :icon "whiteboard"
-                :icon-extension? true
+                :icon "writing"
                 :shortcut :go/whiteboards})))
 
           (= nav :flashcards)

+ 6 - 10
src/main/frontend/components/container.css

@@ -112,16 +112,16 @@
   }
 
   .page-icon {
-    @apply flex items-center mr-2 text-center align-baseline leading-none;
+    @apply flex items-center text-center align-baseline leading-none;
+  }
+
+  .ui__icon {
+    @apply relative flex justify-center w-[16px] text-base mr-2 opacity-70;
   }
 
   a.item {
     @apply flex items-center pl-1.5 pr-0.5 h-8 select-none;
 
-    .ui__icon {
-      @apply relative flex justify-center w-[16px] text-base mr-2;
-    }
-
     .graph-icon {
       @apply ml-[3px] mr-[11px];
 
@@ -132,10 +132,6 @@
 
     &:hover, &.active, > .thumb {
       background-color: var(--lx-gray-04, var(--ls-quaternary-background-color, var(--rx-gray-04)));
-
-      .ui__icon {
-        @apply opacity-100;
-      }
     }
   }
 
@@ -552,7 +548,7 @@
     top: 0;
     left: 0;
     right: 0;
-    background-color: or(--ls-right-sidebar-topbar-color, --lx-gray-01, --ls-secondary-background-color, #d8e1e8);
+    background-color: or(--ls-right-sidebar-topbar-color, --lx-gray-02, --ls-secondary-background-color, #d8e1e8);
     z-index: 999;
     user-select: none;
     -webkit-app-region: drag;

+ 15 - 13
src/main/frontend/components/editor.cljs

@@ -148,12 +148,13 @@
     (hooks/use-effect! search-f [(hooks/use-debounced-value q 150)])
 
     (let [matched-pages' (if (string/blank? q)
-                           (if db-tag?
-                             (db-model/get-all-classes (state/get-current-repo) {:except-root-class? true})
-                             (->> (map (fn [title] {:block/title title
-                                                    :nlp-date? true})
-                                       date/nlp-pages)
-                                  (take 10)))
+                           (when db-based?
+                             (if db-tag?
+                               (db-model/get-all-classes (state/get-current-repo) {:except-root-class? true})
+                               (->> (map (fn [title] {:block/title title
+                                                      :nlp-date? true})
+                                         date/nlp-pages)
+                                    (take 10))))
                            ;; reorder, shortest and starts-with first.
                            (let [matched-pages-with-new-page
                                  (fn [partial-matched-pages]
@@ -185,7 +186,8 @@
                                        (or (db/entity [:block/uuid id]) block)
                                        block)]
                           [:div.flex.flex-col
-                           (when (and (not (or (:page? block) (ldb/page? block))) (:block/uuid block'))
+                           (when (and (not (or db-tag? (:page? block) (ldb/page? block)))
+                                      (:block/uuid block'))
                              (when-let [breadcrumb (state/get-component :block/breadcrumb)]
                                [:div.text-xs.opacity-70.mb-1 {:style {:margin-left 3}}
                                 (breadcrumb {:search? true} (state/get-current-repo) (:block/uuid block') {})]))
@@ -203,10 +205,10 @@
                                  (ui/icon "letter-p" {:size 14})
 
                                  (db-model/whiteboard-page? block')
-                                 (ui/icon "whiteboard" {:extension? true})
+                                 (ui/icon "writing")
 
                                  (:page? block')
-                                 (ui/icon "page" {:extension? true})
+                                 (ui/icon "file")
 
                                  (or (string/starts-with? (str (:block/title block')) (t :new-tag))
                                      (string/starts-with? (str (:block/title block')) (t :new-page)))
@@ -350,11 +352,11 @@
 
 (rum/defc template-search-aux
   [id q]
-  (let [db-based? (config/db-based-graph?)
-        [matched-templates set-matched-templates!] (rum/use-state nil)]
+  (let [[matched-templates set-matched-templates!] (rum/use-state nil)]
     (hooks/use-effect! (fn []
                          (p/let [result (editor-handler/<get-matched-templates q)]
-                           (set-matched-templates! (sort-by :block/title result))))
+                           (set-matched-templates!
+                            (sort-by :block/title result))))
                        [q])
     (ui/auto-complete
      matched-templates
@@ -362,7 +364,7 @@
       :on-enter    (fn [_state] (state/clear-editor-action!))
       :empty-placeholder [:div.text-gray-500.px-4.py-2.text-sm "Search for a template"]
       :item-render (fn [template]
-                     (if db-based? (:block/title template) (:template template)))
+                     (:block/title template))
       :class       "black"})))
 
 (rum/defc template-search < rum/reactive

+ 9 - 6
src/main/frontend/components/file_based/git.cljs

@@ -48,11 +48,14 @@
 
 (rum/defc file-version-selector
   [versions path get-content]
-  (let
-   [[content set-content!] (rum/use-state  nil)
-    [hash  set-hash!] (rum/use-state   "HEAD")]
-    (hooks/use-effect! (fn [] (p/let [c (get-content hash path)] (set-content! c)) [hash path]))
-    [:div.flex
+  (let [[content set-content!] (rum/use-state  nil)
+        [hash  set-hash!] (rum/use-state "HEAD")]
+    (hooks/use-effect!
+     (fn []
+       (p/let [c (get-content hash path)]
+         (set-content! c)))
+     [hash path])
+    [:div.flex.overflow-y-auto {:class "max-h-[calc(85vh_-_4rem)]"}
      [:div.overflow-y-auto {:class "w-48 max-h-[calc(85vh_-_4rem)] "}
       [:div.font-bold "File history - " path]
       (for [line  versions]
@@ -65,7 +68,7 @@
              {:on-click (fn []  (set-hash!  hash))}
              hash]
             title]
-           [:div.opacity-50 time]]))]
+           [:div.opacity-50.text-sm time]]))]
      [:div.flex-1.p-4
       [:div.w-full.sm:max-w-lg {:style {:width 700}}
        [:div.font-bold.mb-4 (str path (util/format " (%s)" hash))]

+ 6 - 5
src/main/frontend/components/file_based/query_table.cljs

@@ -152,13 +152,14 @@
                (date/int->local-time-2 updated-at))]
 
     [:string
-     (if comma-separated-property?
+     (let [value (get-in row [:block/properties column])]
+       (if (or comma-separated-property? (coll? value))
          ;; Return original properties since comma properties need to
          ;; return collections for display purposes
-       (get-in row [:block/properties column])
-       (or (get-in row [:block/properties-text-values column])
-             ;; Fallback to original properties for page blocks
-           (get-in row [:block/properties column])))]))
+         value
+         (or (get-in row [:block/properties-text-values column])
+           ;; Fallback to original properties for page blocks
+             value)))]))
 
 (defn- render-column-value
   [{:keys [row-block row-format cell-format value]} page-cp inline-text]

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

@@ -53,9 +53,9 @@
             (ldb/property? node-entity)
             "letter-p"
             (ldb/whiteboard? node-entity)
-            "whiteboard"
+            "writing"
             (ldb/page? node-entity)
-            "page"
+            "file"
             (= asset-type "pdf")
             "book"
             :else
@@ -67,7 +67,7 @@
         node-icon (if (:own-icon? opts)
                     (get node-entity (pu/get-pid :logseq.property/icon))
                     (get-node-icon node-entity))]
-    (when-not (or (string/blank? node-icon) (and (contains? #{"letter-n" "page"} node-icon) (:not-text-or-page? opts)))
+    (when-not (or (string/blank? node-icon) (and (contains? #{"letter-n" "file"} node-icon) (:not-text-or-page? opts)))
       [:div.icon-cp-container.flex.items-center
        (merge {:style {:color (or (:color node-icon) "inherit")}}
               (select-keys opts [:class]))

+ 3 - 2
src/main/frontend/components/imports.cljs

@@ -45,9 +45,10 @@
   []
   (notification/show! "Import finished!" :success)
   (shui/dialog-close! :import-indicator)
-  (ui-handler/re-render-root!)
   (route-handler/redirect-to-home!)
-  (js/setTimeout ui-handler/re-render-root! 500))
+  (if util/web-platform?
+    (js/window.location.reload)
+    (js/setTimeout ui-handler/re-render-root! 500)))
 
 (defn- roam-import-handler
   [e]

+ 8 - 7
src/main/frontend/components/journal.cljs

@@ -19,13 +19,14 @@
 
 (defn- sub-journals
   []
-  (-> (react/q (state/get-current-repo)
-               [:frontend.worker.react/journals]
-               {:query-fn (fn [_]
-                            (p/let [{:keys [data]} (views/<load-view-data nil {:journals? true})]
-                              (remove nil? data)))}
-               nil)
-      util/react))
+  (when-let [repo (state/get-current-repo)]
+    (some-> (react/q repo
+                     [:frontend.worker.react/journals]
+                     {:query-fn (fn [_]
+                                  (p/let [{:keys [data]} (views/<load-view-data nil {:journals? true})]
+                                    (remove nil? data)))}
+                     nil)
+            util/react)))
 
 (rum/defc all-journals < rum/reactive db-mixins/query
   []

+ 26 - 12
src/main/frontend/components/objects.cljs

@@ -10,6 +10,7 @@
             [logseq.db :as ldb]
             [logseq.db.frontend.property :as db-property]
             [logseq.outliner.property :as outliner-property]
+            [logseq.shui.hooks :as hooks]
             [logseq.shui.ui :as shui]
             [promesa.core :as p]
             [rum.core :as rum]))
@@ -34,9 +35,19 @@
              [:div.block-content (asset-cp (assoc config :disable-resize? true) row)]))
    :disable-hide? true})
 
+(comment
+  (defn- edit-new-object
+    [ref id]
+    (js/setTimeout
+     (fn []
+       (when-let [title-node (d/sel1 ref (util/format ".ls-table-row[data-id='%d'] .table-block-title" id))]
+         (.click title-node)))
+     100)))
+
 (rum/defc class-objects-inner < rum/static
   [config class properties]
-  (let [;; Properties can be nil for published private graphs
+  (let [*ref (hooks/use-ref nil)
+        ;; Properties can be nil for published private graphs
         properties' (remove nil? properties)
         columns* (views/build-columns config properties' {:add-tags-column? true})
         columns (cond
@@ -70,19 +81,22 @@
                                                        (set-data! (concat full-data (map :db/id entities))))))})]))
                                 (p/let [block (add-new-class-object! class properties)]
                                   (when (:db/id block)
+                                    (set-data! (conj (vec full-data) (:db/id block)))
                                     (state/sidebar-add-block! (state/get-current-repo) (:db/id block) :block)
-                                    (set-data! (conj (vec full-data) (:db/id block)))))))))]
+                                    ;; (edit-new-object (rum/deref *ref) (:db/id block))
+                                    ))))))]
 
-    (views/view {:config config
-                 :view-parent class
-                 :view-feature-type :class-objects
-                 :columns columns
-                 :add-new-object! add-new-object!
-                 :show-add-property? true
-                 :show-items-count? true
-                 :add-property! (fn []
-                                  (state/pub-event! [:editor/new-property {:block class
-                                                                           :class-schema? true}]))})))
+    [:div {:ref *ref}
+     (views/view {:config config
+                  :view-parent class
+                  :view-feature-type :class-objects
+                  :columns columns
+                  :add-new-object! add-new-object!
+                  :show-add-property? true
+                  :show-items-count? true
+                  :add-property! (fn []
+                                   (state/pub-event! [:editor/new-property {:block class
+                                                                            :class-schema? true}]))})]))
 
 (rum/defcs class-objects < rum/reactive db-mixins/query mixins/container-id
   [state class {:keys [current-page? sidebar?]}]

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

@@ -446,6 +446,7 @@
   (let [with-actions? (not config/publishing?)]
     [:div.ls-page-title.flex.flex-1.w-full.content.items-start.title
      {:class (when-not whiteboard-page? "title")
+      :data-testid "page title"
       :on-pointer-down (fn [e]
                          (when (util/right-click? e)
                            (state/set-state! :page-title/context {:page (:block/title page)

+ 1 - 1
src/main/frontend/components/property/config.cljs

@@ -33,7 +33,7 @@
 (defn- re-init-commands!
   "Update commands after task status and priority's closed values has been changed"
   [property]
-  (when (contains? #{:logseq.task/status :logseq.task/priority} (:db/ident property))
+  (when (contains? #{:logseq.property/status :logseq.property/priority} (:db/ident property))
     (state/pub-event! [:init/commands])))
 
 (defn- <upsert-closed-value!

+ 45 - 28
src/main/frontend/components/property/value.cljs

@@ -203,7 +203,7 @@
   "If a class and in a class schema context, add the property to its schema.
   Otherwise, add a block's property and its value"
   ([block property-id property-value] (<add-property! block property-id property-value {}))
-  ([block property-id property-value {:keys [selected? exit-edit? class-schema?]
+  ([block property-id property-value {:keys [selected? exit-edit? class-schema? entity-id?]
                                       :or {exit-edit? true}}]
    (let [repo (state/get-current-repo)
          class? (ldb/class? block)
@@ -218,8 +218,18 @@
          (when (ldb/class? property)
            (<set-class-as-property! repo property))
          (db-property-handler/class-add-property! (:db/id block) property-id))
-        (let [block-ids (map :block/uuid blocks)]
-          (property-handler/batch-set-block-property! repo block-ids property-id property-value)))
+        (let [block-ids (map :block/uuid blocks)
+              set-query-list-view? (and (:logseq.property/query block)
+                                        (= property-id :logseq.property.view/type)
+                                        (= property-value (:db/id (db/entity :logseq.property.view/type.list))))]
+          (ui-outliner-tx/transact!
+           {:outliner-op :set-block-property}
+           (property-handler/batch-set-block-property! repo block-ids property-id property-value {:entity-id? entity-id?})
+           (when (and set-query-list-view?
+                      (nil? (:logseq.property.view/group-by-property block)))
+             (property-handler/batch-set-block-property! repo block-ids :logseq.property.view/group-by-property
+                                                         (:db/id (db/entity :block/page))
+                                                         {:entity-id? entity-id?})))))
       (when (seq (:view/selected-blocks @state/state))
         (notification/show! "Property updated!" :success))
       (when-not many?
@@ -238,7 +248,7 @@
                                         :property property}))))))
 
 (defn- add-or-remove-property-value
-  [block property value selected? {:keys [refresh-result-f] :as opts}]
+  [block property value selected? {:keys [refresh-result-f entity-id?] :as opts}]
   (let [many? (db-property/many? property)
         blocks (get-operating-blocks block)
         repo (state/get-current-repo)]
@@ -252,6 +262,7 @@
      (if selected?
        (<add-property! block (:db/ident property) value
                        {:selected? selected?
+                        :entity-id? entity-id?
                         :exit-edit? (if (some? (:exit-edit? opts)) (:exit-edit? opts) (not many?))})
        (p/do!
         (ui-outliner-tx/transact!
@@ -273,16 +284,16 @@
      [:div.mb-4
       [:div.flex.flex-row.items-center.gap-1
        [:div.w-4
-        (property-value block (db/entity :logseq.task/repeated?)
+        (property-value block (db/entity :logseq.property.repeat/repeated?)
                         (assoc opts
                                :on-checked-change (fn [value]
                                                     (if value
                                                       (db-property-handler/set-block-property! (:db/id block)
-                                                                                               :logseq.task/scheduled-on-property
+                                                                                               :logseq.property.repeat/temporal-property
                                                                                                (:db/id property))
                                                       (db-property-handler/remove-block-property! (:db/id block)
-                                                                                                  :logseq.task/scheduled-on-property)))))]
-       (if (#{:logseq.task/deadline :logseq.task/scheduled} (:db/ident property))
+                                                                                                  :logseq.property.repeat/temporal-property)))))]
+       (if (#{:logseq.property/deadline :logseq.property/scheduled} (:db/ident property))
          [:div "Repeat task"]
          [:div "Repeat " (if (= :date (:logseq.property/type property)) "date" "datetime")])]]
      [:div.flex.flex-row.gap-2
@@ -291,24 +302,24 @@
 
       ;; recur frequency
       [:div.w-6
-       (property-value block (db/entity :logseq.task/recur-frequency) opts)]
+       (property-value block (db/entity :logseq.property.repeat/recur-frequency) opts)]
 
       ;; recur unit
       [:div.w-20
-       (property-value block (db/entity :logseq.task/recur-unit) (assoc opts :property property))]]
+       (property-value block (db/entity :logseq.property.repeat/recur-unit) (assoc opts :property property))]]
      (let [properties (->>
                        (outliner-property/get-block-full-properties (db/get-db) (:db/id block))
                        (filter (fn [property]
                                  (and (not (ldb/built-in? property))
                                       (>= (count (:property/closed-values property)) 2))))
-                       (concat [(db/entity :logseq.task/status)])
+                       (concat [(db/entity :logseq.property/status)])
                        (util/distinct-by :db/id))
-           status-property (or (:logseq.task/recur-status-property block)
-                               (db/entity :logseq.task/status))
+           status-property (or (:logseq.property.repeat/checked-property block)
+                               (db/entity :logseq.property/status))
            property-id (:db/id status-property)
            done-choice (or
                         (some (fn [choice] (when (true? (:logseq.property/choice-checkbox-state choice)) choice)) (:property/closed-values status-property))
-                        (db/entity :logseq.task/status.done))]
+                        (db/entity :logseq.property/status.done))]
        [:div.flex.flex-col.gap-2
         [:div.text-muted-foreground
          "When"]
@@ -316,7 +327,7 @@
          (cond->
           {:on-value-change (fn [v]
                               (db-property-handler/set-block-property! (:db/id block)
-                                                                       :logseq.task/recur-status-property
+                                                                       :logseq.property.repeat/checked-property
                                                                        v))}
            property-id
            (assoc :default-value property-id))
@@ -452,7 +463,7 @@
                       (str (util/zero-pad hours)
                            ":"
                            (util/zero-pad minutes))])]]
-      (if (or repeated-task? (contains? #{:logseq.task/deadline :logseq.task/scheduled} property-id))
+      (if (or repeated-task? (contains? #{:logseq.property/deadline :logseq.property/scheduled} property-id))
         (overdue date content)
         content))))
 
@@ -481,7 +492,7 @@
                         (when-not config/publishing?
                           (shui/popup-show! (.-target e) content-fn
                                             {:align "start" :auto-focus? true}))))
-        repeated-task? (:logseq.task/repeated? block)]
+        repeated-task? (:logseq.property.repeat/repeated? block)]
     (if editing?
       (content-fn {:id :date-picker})
       (if multiple-values?
@@ -523,7 +534,7 @@
                                         :meta-click? other-position?
                                         :label (human-date-label (t/to-default-time-zone date))} value)
                               (:db/id value)))]
-              (if (or repeated-task? (contains? #{:logseq.task/deadline :logseq.task/scheduled} (:db/id property)))
+              (if (or repeated-task? (contains? #{:logseq.property/deadline :logseq.property/scheduled} (:db/id property)))
                 (overdue compare-value content)
                 content))
 
@@ -717,6 +728,7 @@
                                   (and alias? (= (or (:db/id (:block/page block))
                                                      (:db/id block))
                                                  (:db/id node)))
+                                  (= :logseq.property/empty-placeholder (:db/ident node))
                                   (cond
                                     (= property-type :class)
                                     (ldb/private-tags (:db/ident node))
@@ -729,7 +741,6 @@
                                     :else
                                     false))))
                           result)))
-
         options (map (fn [node]
                        (let [node (if (:value node)
                                     (assoc (:value node) :block/title (:label node))
@@ -910,11 +921,11 @@
             closed-values? (seq (:property/closed-values property))
             items (if closed-values?
                     (let [date? (and
-                                 (= (:db/ident property) :logseq.task/recur-unit)
+                                 (= (:db/ident property) :logseq.property.repeat/recur-unit)
                                  (= :date (:logseq.property/type (:property opts))))
                           values (cond->> (:property/closed-values property)
                                    date?
-                                   (remove (fn [b] (contains? #{:logseq.task/recur-unit.minute :logseq.task/recur-unit.hour} (:db/ident b)))))]
+                                   (remove (fn [b] (contains? #{:logseq.property.repeat/recur-unit.minute :logseq.property.repeat/recur-unit.hour} (:db/ident b)))))]
                       (keep (fn [block]
                               (let [icon (pu/get-block-property-value block :logseq.property/icon)
                                     value (db-property/closed-value-content block)]
@@ -940,7 +951,8 @@
             on-chosen (fn [chosen selected?]
                         (let [value (if (map? chosen) (:value chosen) chosen)]
                           (add-or-remove-property-value block property value selected?
-                                                        {:exit-edit? exit-edit?
+                                                        {:entity-id? (when (integer? value) true)
+                                                         :exit-edit? exit-edit?
                                                          :refresh-result-f refresh-result-f})))
             selected-choices' (get block (:db/ident property))
             selected-choices (if (every? #(and (map? %) (:db/id %)) selected-choices')
@@ -989,12 +1001,15 @@
         table-text-property-render (:table-text-property-render opts)]
     (if table-text-property-render
       (table-text-property-render
-       (if multiple-values? (first value-block) value-block)
+       value-block
        {:create-new-block #(<create-new-block! block property "")
         :property-ident (:db/ident property)})
       (cond
         (seq value-block)
         [:div.property-block-container.content.w-full
+         {:style (if (= (:db/ident property) :logseq.property/default-value)
+                   {:min-width 300}
+                   {})}
          (let [config {:id (str (if multiple-values?
                                   (:block/uuid block)
                                   (:block/uuid value-block)))
@@ -1015,7 +1030,7 @@
                (str (:db/id block) "-" (:db/id property) "-" (:db/id value-block)))))]
 
         :else
-        [:div.w-full.h-full
+        [:div.w-full.h-full.jtrigger.ls-empty-text-property
          {:tabIndex 0
           :class (if (:table-view? opts) "cursor-pointer" "cursor-text")
           :style {:min-height 20}
@@ -1153,7 +1168,7 @@
                                                (d/has-class? node "tag")))))
                         (show-popup! target))))]
         (shui/trigger-as
-         (if (:other-position? opts) :div.jtrigger :div.jtrigger.flex.flex-1.w-full)
+         (if (:other-position? opts) :div.jtrigger :div.jtrigger.flex.flex-1.w-full.cursor-pointer)
          {:ref *el
           :id trigger-id
           :tabIndex 0
@@ -1190,7 +1205,9 @@
       :style {:min-height 24}}
      (cond
        (and (= :logseq.property/default-value (:db/ident property)) (nil? (:block/title value)))
-       [:div.jtrigger.cursor-pointer.text-sm.px-2 "Set default value"]
+       [:div.jtrigger.cursor-pointer.text-sm.px-2
+        {:on-click #(<create-new-block! block property "")}
+        "Set default value"]
 
        text-ref-type?
        (property-block-value value block property page-cp opts)
@@ -1273,7 +1290,7 @@
       (= :logseq.property/icon (:db/ident property))
       (icon-row block editing?)
 
-      (and (= type :number) (not editing?))
+      (and (= type :number) (not editing?) (not closed-values?))
       (single-number-input block property value (:table-view? opts))
 
       :else
@@ -1422,7 +1439,7 @@
          v (let [v (get block (:db/ident property))]
              (or
               (cond
-                (and multiple-values? (or (set? v) (and (coll? v) (empty? v)) (nil? v)))
+                (and multiple-values? (or (set? v) (coll? v) (nil? v)))
                 v
                 multiple-values?
                 #{v}

+ 4 - 4
src/main/frontend/components/query/builder.cljs

@@ -261,7 +261,7 @@
        (tags repo *tree opts loc)
 
        "task"
-       (let [items (let [values (:property/closed-values (db/entity :logseq.task/status))]
+       (let [items (let [values (:property/closed-values (db/entity :logseq.property/status))]
                      (mapv db-property/property-value-content values))]
          (select items
                  (constantly nil)
@@ -277,7 +277,7 @@
 
        "priority"
        (select (if (config/db-based-graph? repo)
-                 (let [values (:property/closed-values (db/entity :logseq.task/priority))]
+                 (let [values (:property/closed-values (db/entity :logseq.property/priority))]
                    (mapv db-property/property-value-content values))
                  gp-db/built-in-priorities)
                (constantly nil)
@@ -342,7 +342,7 @@
 
        "task"
        (select (if (config/db-based-graph? repo)
-                 (let [values (:property/closed-values (db/entity :logseq.task/status))]
+                 (let [values (:property/closed-values (db/entity :logseq.property/status))]
                    (mapv db-property/property-value-content values))
                  gp-db/built-in-markers)
                (constantly nil)
@@ -358,7 +358,7 @@
 
        "priority"
        (select (if (config/db-based-graph? repo)
-                 (let [values (:property/closed-values (db/entity :logseq.task/priority))]
+                 (let [values (:property/closed-values (db/entity :logseq.property/priority))]
                    (mapv db-property/property-value-content values))
                  gp-db/built-in-priorities)
                (constantly nil)

+ 2 - 2
src/main/frontend/components/repo.cljs

@@ -69,7 +69,7 @@
         :let [only-cloud? (and remote? (nil? root))
               db-based? (config/db-based-graph? url)]]
     [:div.flex.justify-between.mb-4.items-center.group {:key (or url GraphUUID)
-                                                        :aria-label (str "e2e " url)}
+                                                        :data-testid url}
      [:div
       [:span.flex.items-center.gap-1
        (normalized-graph-label repo
@@ -375,7 +375,7 @@
                                      {:as-dropdown? true
                                       :content-props {:class "repos-list"}
                                       :align :start}))}
-      [:span.thumb (shui/tabler-icon (if remote? "cloud" (if db-based? "database" "folder")) {:size 16})]
+      [:span.thumb (shui/tabler-icon (if remote? "cloud" (if db-based? "topology-star" "folder")) {:size 16})]
       [:strong short-repo-name]
       (shui/tabler-icon "selector" {:size 18})]]))
 

+ 7 - 7
src/main/frontend/components/right_sidebar.cljs

@@ -215,7 +215,7 @@
                  (when collapsed? "collapsed")]}
         (let [[title component] item]
           [:div.flex.flex-col.w-full.relative
-           [:.flex.flex-row.justify-between.pr-2.sidebar-item-header.color-level.rounded-t-md
+           [:.flex.flex-row.justify-between.sidebar-item-header.color-level.rounded-t-md
             {:class         (when collapsed? "rounded-b-md")
              :draggable     true
              :on-context-menu (fn [e]
@@ -235,7 +235,7 @@
                                 (when (= (.-which (.-nativeEvent event)) 2)
                                   (state/sidebar-remove-block! idx)))}
 
-            [:button.flex.flex-row.p-2.items-center.w-full.overflow-hidden
+            [:button.flex.flex-row.px-2.items-center.w-full.overflow-hidden
              {:aria-expanded (str (not collapsed?))
               :id            (str "sidebar-panel-header-" idx)
               :aria-controls (str "sidebar-panel-content-" idx)
@@ -244,13 +244,13 @@
                                (state/sidebar-block-toggle-collapse! db-id))}
              [:span.opacity-50.hover:opacity-100.flex.items-center.pr-1
               (ui/rotating-arrow collapsed?)]
-             [:div.ml-1.font-medium.overflow-hidden.whitespace-nowrap
+             [:div.ml-1.font-medium.text-sm.overflow-hidden.whitespace-nowrap
               title]]
             [:.item-actions.flex.items-center
              (shui/button
               {:title (t :right-side-bar/pane-more)
-               :class "px-3"
-               :variant :text
+               :class "px-2 py-2 h-8 w-8 text-muted-foreground"
+               :variant :ghost
                :on-click #(shui/popup-show!
                            (.-target %)
                            (actions-menu-content db-id idx block-type collapsed? block-count)
@@ -260,8 +260,8 @@
 
              (shui/button
               {:title (t :right-side-bar/pane-close)
-               :variant :text
-               :class "px-3"
+               :variant :ghost
+               :class "px-2 py-2 h-8 w-8 text-muted-foreground"
                :on-click #(state/sidebar-remove-block! idx)}
               (ui/icon "x"))]]
 

+ 3 - 3
src/main/frontend/components/right_sidebar.css

@@ -23,16 +23,16 @@ html[data-theme=light] {
 
 .cp__header {
   > .r > div:not(.ui__dropdown-trigger) a, button {
-    color: var(--lx-gray-11, var(--ls-header-button-background, var(--rx-gray-11)));
+    @apply opacity-70;
 
     &:hover {
-      color: var(--lx-gray-12, var(--ls-header-button-background, var(--rx-gray-12)));
+      @apply opacity-100;
     }
   }
 }
 
 .cp__right-sidebar-topbar {
-  @apply px-1 h-12 bg-transparent;
+  @apply px-1 h-12;
 
   button {
     @apply opacity-100;

+ 3 - 1
src/main/frontend/components/rtc/indicator.cljs

@@ -153,8 +153,10 @@
         uploading?'                  (uploading? detail-info)
         downloading?'                (downloading? detail-info)
         rtc-state                   (:rtc-state detail-info)
-        unpushed-block-update-count (:pending-local-ops detail-info)]
+        unpushed-block-update-count (:pending-local-ops detail-info)
+        {:keys [local-tx remote-tx]} detail-info]
     [:div.cp__rtc-sync
+     [:div.hidden {:data-testid "rtc-tx"} (pr-str {:local-tx local-tx :remote-tx remote-tx})]
      [:div.cp__rtc-sync-indicator.flex.flex-row.items-center.gap-1
       (when downloading?'
         (shui/button

+ 5 - 1
src/main/frontend/components/table.css

@@ -29,6 +29,10 @@
     }
   }
 
+  .ls-table-row, .ls-table-cell, .ls-table-cell .jtrigger {
+    @apply focus:ring-0 focus:ring-offset-0 focus-visible:outline-none;
+  }
+
   .ls-table-row {
     @apply h-[33px] min-h-[33px] max-h-[33px];
     div, span, a {
@@ -103,7 +107,7 @@ html.is-resizing-buf {
 }
 
 .query-table, .classic-table {
-  @apply my-2 rounded overflow-hidden;
+  @apply my-2 rounded;
 
   &.force-visible-scrollbar {
     @apply !overflow-x-auto pb-1;

+ 379 - 190
src/main/frontend/components/views.cljs

@@ -21,6 +21,7 @@
             [frontend.db :as db]
             [frontend.db-mixins :as db-mixins]
             [frontend.db.async :as db-async]
+            [frontend.db.react :as react]
             [frontend.handler.db-based.export :as db-export-handler]
             [frontend.handler.db-based.property :as db-property-handler]
             [frontend.handler.editor :as editor-handler]
@@ -46,6 +47,12 @@
             [promesa.core :as p]
             [rum.core :as rum]))
 
+(defn- get-scroll-parent
+  [config]
+  (if (:sidebar? config)
+    (dom/sel1 ".sidebar-item-list")
+    (gdom/getElement "main-content-container")))
+
 (rum/defc header-checkbox < rum/static
   [{:keys [selected-all? selected-some? toggle-selected-all!] :as table}]
   (let [[show? set-show!] (rum/use-state false)]
@@ -190,16 +197,27 @@
 
 (rum/defc block-title
   "Used on table view"
-  [block {:keys [create-new-block width]}]
-  (let [inline-title (state/get-component :block/inline-title)
+  [block* {:keys [create-new-block width row property]}]
+  (let [*ref (hooks/use-ref nil)
         [opacity set-opacity!] (hooks/use-state 0)
-        add-to-sidebar! #(state/sidebar-add-block! (state/get-current-repo) (:db/id block) :block)]
+        [focus-timeout set-focus-timeout!] (hooks/use-state nil)
+        inline-title (state/get-component :block/inline-title)
+        many? (db-property/many? property)
+        block (if many? (first block*) block*)
+        add-to-sidebar! #(state/sidebar-add-block! (state/get-current-repo)
+                                                   (or (and many? (:db/id row)) (:db/id block))
+                                                   :block)
+        redirect! #(some-> (:block/uuid block) route-handler/redirect-to-page!)]
+    (hooks/use-effect!
+     (fn []
+       #(some-> focus-timeout js/clearTimeout))
+     [])
     [:div.table-block-title.relative.flex.items-center.w-full.h-full.cursor-pointer.items-center
-     {:on-mouse-over #(set-opacity! 100)
+     {:ref *ref
+      :on-mouse-over #(set-opacity! 100)
       :on-mouse-out #(set-opacity! 0)
       :on-click (fn [e]
-                  (p/let [block (or block (and (fn? create-new-block) (create-new-block)))
-                          redirect! #(some-> (:block/uuid block) route-handler/redirect-to-page!)]
+                  (p/let [block (or block (and (fn? create-new-block) (create-new-block)))]
                     (when block
                       (cond
                         (util/meta-key? e)
@@ -209,51 +227,68 @@
                         (add-to-sidebar!)
 
                         :else
-                        (p/do!
-                         (shui/popup-show!
-                           (.closest (.-target e) ".ls-table-cell")
-                           (fn []
-                             (let [width (-> (max 160 width)
-                                           (- 18))]
-                               [:div.ls-table-block.flex.flex-row.items-start
-                                {:style {:width width :max-width width :margin-right "6px"}
-                                 :on-click util/stop-propagation}
-                                (block-container {:popup? true
-                                                  :view? true
-                                                  :table-block-title? true} block)
-                                (shui/button
-                                  {:variant :ghost
-                                   :title "Open node"
-                                   :on-click (fn [e]
-                                               (util/stop-propagation e)
-                                               (shui/popup-hide!)
-                                               (redirect!))
-                                   :class (str "h-6 w-6 !p-0 text-muted-foreground transition-opacity duration-100 ease-in bg-gray-01 "
-                                            "opacity-" opacity)}
-                                  (ui/icon "arrow-right"))]))
-                           {:id :ls-table-block-editor
-                            :as-mask? true})
-                          (editor-handler/edit-block! block :max {:container-id :unknown-container}))))))}
+                        (let [popup (fn []
+                                      (let [width (-> (max 160 width) (- 18))]
+                                        (if many?
+                                          [:div.ls-table-block.flex.flex-row.items-start
+                                           {:style {:width width :max-width width :margin-right "6px"}
+                                            :on-click util/stop-propagation}
+                                           (pv/property-value row property {})]
+                                          [:div.ls-table-block.flex.flex-row.items-start
+                                           {:style {:width width :max-width width :margin-right "6px"}
+                                            :on-click util/stop-propagation}
+                                           (block-container {:popup? true
+                                                             :view? true
+                                                             :table-block-title? true} block)])))]
+                          (p/do!
+                           (shui/popup-show!
+                            (.closest (.-target e) ".ls-table-cell")
+                            popup
+                            {:id :ls-table-block-editor
+                             :as-mask? true
+                             :on-after-hide (fn []
+                                              (let [node (rum/deref *ref)
+                                                    cell (util/rec-get-node node "ls-table-cell")]
+                                                (p/do!
+                                                 (editor-handler/save-current-block!)
+                                                 (state/exit-editing-and-set-selected-blocks! [cell])
+                                                 (set-focus-timeout! (js/setTimeout #(.focus cell) 100)))))})
+                           (editor-handler/edit-block! block :max {:container-id :unknown-container})))))))}
      (if block
-       [:div
-        (inline-title
-          (some->> (:block/title block)
-            string/trim
-            string/split-lines
-            first))]
+       [:div.flex.flex-row
+        (let [render (fn [block]
+                       [:div
+                        (inline-title
+                         (some->> (:block/title block)
+                                  string/trim
+                                  string/split-lines
+                                  first))])]
+          (if many?
+            (->> (map render block*)
+                 (interpose [:div.mr-1 ","]))
+            (render block*)))]
        [:div])
 
-     [:div.absolute.right-0.p-1
-      {:on-click (fn [e]
-                   (util/stop-propagation e)
-                   (add-to-sidebar!))}
-      [:div.flex.items-center
-       (shui/button
-         {:variant :ghost
-          :title "Open in sidebar"
-          :class (str "h-5 w-5 !p-0 text-muted-foreground transition-opacity duration-100 ease-in bg-gray-01 "
-                   "opacity-" opacity)}
-         (ui/icon "layout-sidebar-right"))]]]))
+     (let [class (str "h-6 w-6 !p-1 text-muted-foreground transition-opacity duration-100 ease-in bg-gray-01 "
+                      "opacity-" opacity)]
+       [:div.absolute.-right-1
+        [:div.flex.flex-row.items-center
+         (shui/button
+          {:variant :ghost
+           :title "Open"
+           :on-click (fn [e]
+                       (util/stop-propagation e)
+                       (redirect!))
+           :class class}
+          (ui/icon "arrow-right"))
+         (shui/button
+          {:variant :ghost
+           :title "Open in sidebar"
+           :class class
+           :on-click (fn [e]
+                       (util/stop-propagation e)
+                       (add-to-sidebar!))}
+          (ui/icon "layout-sidebar-right"))]])]))
 
 (defn build-columns
   [config properties & {:keys [with-object-name? with-id? add-tags-column?]
@@ -263,30 +298,30 @@
   (let [;; FIXME: Shouldn't file graphs have :block/tags?
         add-tags-column?' (and (config/db-based-graph? (state/get-current-repo)) add-tags-column?)
         properties' (->>
-                      (if (or (some #(= (:db/ident %) :block/tags) properties) (not add-tags-column?'))
-                        properties
-                        (conj properties (db/entity :block/tags)))
-                      (remove nil?))]
+                     (if (or (some #(= (:db/ident %) :block/tags) properties) (not add-tags-column?'))
+                       properties
+                       (conj properties (db/entity :block/tags)))
+                     (remove nil?))]
     (->> (concat
-           [{:id :select
-             :name "Select"
-             :header (fn [table _column] (header-checkbox table))
-             :cell (fn [table row column]
-                     (row-checkbox table row column))
-             :column-list? false
-             :resizable? false}
-            (when with-id?
-              {:id :id
-               :name "ID"
-               :header (fn [_table _column] (header-index))
-               :cell (fn [table row _column]
-                       (inc (.indexOf (:rows table) (:db/id row))))
-               :resizable? false})
-            (when with-object-name?
-              {:id :block/title
-               :name "Name"
-               :type :string
-               :header header-cp
+          [{:id :select
+            :name "Select"
+            :header (fn [table _column] (header-checkbox table))
+            :cell (fn [table row column]
+                    (row-checkbox table row column))
+            :column-list? false
+            :resizable? false}
+           (when with-id?
+             {:id :id
+              :name "ID"
+              :header (fn [_table _column] (header-index))
+              :cell (fn [table row _column]
+                      (inc (.indexOf (:rows table) (:db/id row))))
+              :resizable? false})
+           (when with-object-name?
+             {:id :block/title
+              :name "Name"
+              :type :string
+              :header header-cp
               :cell (fn [_table row _column style]
                       (block-title row {:property-ident :block/title
                                         :sidebar? (:sidebar? config)
@@ -320,6 +355,8 @@
                                                                    :table-text-property-render
                                                                    (fn [block opts]
                                                                      (block-title block (assoc opts
+                                                                                               :row row
+                                                                                               :property property
                                                                                                :width (:width style)
                                                                                                :sidebar? (:sidebar? config))))}))))
                     :get-value get-value
@@ -356,16 +393,18 @@
   [view-entity columns {:keys [column-visible? rows column-toggle-visibility]}]
   (let [display-type (:db/ident (:logseq.property.view/type view-entity))
         table? (= display-type :logseq.property.view/type.table)
-        group-by-columns (concat (filter (fn [column]
+        group-by-columns (concat (when (or
+                                        (contains? #{:linked-references :unlinked-references}
+                                                   (:logseq.property.view/feature-type view-entity))
+                                        (:logseq.property/query view-entity))
+                                   [{:id :block/page
+                                     :name "Block Page"}])
+                                 (filter (fn [column]
                                            (when (:id column)
                                              (when-let [p (db/entity (:id column))]
                                                (and (not (db-property/many? p))
                                                     (contains? #{:default :number :checkbox :url :node :date}
-                                                               (:logseq.property/type p)))))) columns)
-                                 (when (contains? #{:linked-references :unlinked-references}
-                                                  (:logseq.property.view/feature-type view-entity))
-                                   [{:id :block/page
-                                     :name "Block Page"}]))]
+                                                               (:logseq.property/type p)))))) columns))]
     (shui/dropdown-menu
      (shui/dropdown-menu-trigger
       {:asChild true}
@@ -653,14 +692,109 @@
   [cell-render-f cell-placeholder]
   (let [^js state (ui/useInView #js {:rootMargin "0px"})
         in-view? (.-inView state)]
-    [:div.h-full {:ref (.-ref state)}
+    [:div.h-full
+     {:ref (.-ref state)}
      (if in-view?
        (cell-render-f)
        cell-placeholder)]))
 
+(defn- click-cell
+  [node]
+  (when-let [trigger (or (dom/sel1 node ".jtrigger")
+                         (dom/sel1 node ".table-block-title"))]
+    (.click trigger)))
+
+(defn navigate-to-cell
+  [e cell direction]
+  (util/stop e)
+  (let [row (util/rec-get-node cell "ls-table-row")
+        cells (dom/sel row ".ls-table-cell")
+        idx (.indexOf cells cell)
+        rows-container (util/rec-get-node row "ls-table-rows")
+        rows (dom/sel rows-container ".ls-table-row")
+        row-idx (.indexOf rows row)
+        container-left (.-left (.getBoundingClientRect rows-container))
+        next-cell (case direction
+                    :left (if (> idx 1)               ; don't focus on checkbox
+                            (nth cells (dec idx))
+                            ;; last cell in the prev row
+                            (let [prev-row (when (> row-idx 0)
+                                             (nth rows (dec row-idx)))]
+                              (when prev-row
+                                (let [cells (dom/sel prev-row ".ls-table-cell")]
+                                  (last cells)))))
+                    :right (if (< idx (dec (count cells)))
+                             (nth cells (inc idx))
+                             ;; first cell in the next row
+                             (let [next-row (when (< row-idx (dec (count rows)))
+                                              (nth rows (inc row-idx)))]
+                               (when next-row
+                                 (let [cells (dom/sel next-row ".ls-table-cell")]
+                                   (second cells)))))
+                    :up (let [prev-row (when (> row-idx 0)
+                                         (nth rows (dec row-idx)))]
+                          (when prev-row
+                            (let [cells (dom/sel prev-row ".ls-table-cell")]
+                              (nth cells idx))))
+                    :down (let [next-row (when (< row-idx (dec (count rows)))
+                                           (nth rows (inc row-idx)))]
+                            (when next-row
+                              (let [cells (dom/sel next-row ".ls-table-cell")]
+                                (nth cells idx)))))]
+    (when next-cell
+      (let [next-cell-left (.-left (.getBoundingClientRect next-cell))]
+        (state/clear-selection!)
+        (dom/add-class! next-cell "selected")
+        (.focus next-cell)
+        (when (< next-cell-left container-left)
+          (.scrollIntoView next-cell #js {:inline "center"
+                                          :block "nearest"}))))))
+
+(rum/defc table-cell-container
+  [cell-opts body]
+  (let [*ref (hooks/use-ref nil)]
+    (shui/table-cell
+     (assoc cell-opts
+            :tabIndex 0
+            :ref *ref
+            :on-click (fn [] (click-cell (rum/deref *ref)))
+            :on-key-down (fn [e]
+                           (let [container (rum/deref *ref)]
+                             (case (util/ekey e)
+                               "Escape"
+                               (do
+                                 (if (util/input? (.-target e))
+                                   (do
+                                     (state/exit-editing-and-set-selected-blocks! [container])
+                                     (.focus container))
+                                   (do
+                                     (dom/remove-class! container "selected")
+                                     (let [row (util/rec-get-node container "ls-table-row")]
+                                       (state/exit-editing-and-set-selected-blocks! [row]))))
+                                 (util/stop e))
+                               "Enter"
+                               (do
+                                 (if (util/input? (.-target e)) ; number
+                                   (do
+                                     (state/exit-editing-and-set-selected-blocks! [container])
+                                     (.focus container))
+                                   (click-cell container))
+                                 (util/stop e))
+                               "ArrowUp"
+                               (navigate-to-cell e container :up)
+                               "ArrowDown"
+                               (navigate-to-cell e container :down)
+                               "ArrowLeft"
+                               (navigate-to-cell e container :left)
+                               "ArrowRight"
+                               (navigate-to-cell e container :right)
+                               nil))))
+     body)))
+
 (rum/defc table-row-inner < rum/static
   [{:keys [row-selected?] :as table} row props {:keys [show-add-property? scrolling?]}]
-  (let [pinned-columns (get-in table [:state :pinned-columns])
+  (let [*ref (hooks/use-ref nil)
+        pinned-columns (get-in table [:state :pinned-columns])
         unpinned (get-in table [:state :unpinned-columns])
         unpinned-columns (if show-add-property?
                            (conj (vec unpinned)
@@ -678,19 +812,60 @@
                                       :select? select?
                                       :add-property? add-property?
                                       :style style}
-                           cell-placeholder (shui/table-cell cell-opts nil)]
+                           cell-placeholder (table-cell-container cell-opts nil)]
                        (if (and scrolling? (not (:block/title row)))
                          cell-placeholder
                          (when-let [render (get column :cell)]
                            (lazy-table-cell
-                            (fn [] (shui/table-cell cell-opts (render table row column style)))
+                            (fn []
+                              (table-cell-container
+                               cell-opts (render table row column style)))
                             cell-placeholder)))))]
     (shui/table-row
      (merge
       props
       {:key (str (:db/id row))
+       :tabIndex 0
+       :ref *ref
        :data-state (when (row-selected? row) "selected")
-       :on-pointer-down (fn [_e] (db-async/<get-block (state/get-current-repo) (:db/id row) {:children? false}))})
+       :data-id (:db/id row)
+       :blockid (str (:block/uuid row))
+       :on-pointer-down (fn [_e] (db-async/<get-block (state/get-current-repo) (:db/id row) {:children? false}))
+       :on-key-down (fn [e]
+                      (let [container (rum/deref *ref)]
+                        (when (dom/has-class? container "selected")
+                          (case (util/ekey e)
+                            "Enter"
+                            (do
+                              (state/sidebar-add-block! (state/get-current-repo) (:db/id row) :block)
+                              (state/clear-selection!)
+                              (util/stop e))
+                            "ArrowLeft"
+                            (do
+                              (when-let [cell (->> (dom/sel container ".ls-table-cell")
+                                                   (remove (fn [node]
+                                                             (some? (dom/sel1 node ".ui__checkbox"))))
+                                                   first)]
+                                (state/clear-selection!)
+                                (dom/add-class! cell "selected")
+                                (.focus cell))
+                              (util/stop e))
+                            "ArrowRight"
+                            (do
+                              (when-let [cell (->> (dom/sel container ".ls-table-cell")
+                                                   (remove (fn [node]
+                                                             (some? (dom/sel1 node ".ui__checkbox"))))
+                                                   last)]
+                                (state/clear-selection!)
+                                (dom/remove-class! container "selected")
+                                (dom/add-class! cell "selected")
+                                (.focus cell))
+                              (util/stop e))
+                            "Escape"
+                            (do
+                              (state/clear-selection!)
+                              (util/stop e))
+                            nil))))})
      (when (seq pinned-columns)
        [:div.sticky-columns.flex.flex-row
         (map #(row-cell-f % {}) pinned-columns)])
@@ -996,106 +1171,102 @@
 (rum/defc ^:large-vars/cleanup-todo filter-value-select < rum/static
   [view-entity {:keys [data-fns] :as table} property value operator idx opts]
   (let [type (:logseq.property/type property)
-        property-ident (:db/ident property)
-        [values set-values!] (hooks/use-state nil)
-        [dropdown-open? set-dropdown-open!] (hooks/use-state false)]
+        property-ident (:db/ident property)]
     (hooks/use-effect!
      (fn []
-       (p/do!
-        (let [values (if (coll? value) value [value])
-              ids (filter #(and (uuid? %) (nil? (db/entity [:block/uuid %]))) values)]
-          (when (seq ids) (db-async/<get-blocks (state/get-current-repo) ids)))
-        (when (and property-ident dropdown-open?
-                   (not (contains? #{:data :datetime :checkbox} type)))
-          (p/let [data (db-async/<get-property-values property-ident {:view-id (:db/id view-entity)
-                                                                      :query-entity-ids (:query-entity-ids opts)})]
-            (set-values! (map (fn [v] (if (map? (:value v))
-                                        (assoc v :value (:block/uuid (:value v)))
-                                        v)) data))))))
-     [property-ident dropdown-open?])
-    (let [items (cond
-                  (contains? #{:before :after} operator)
-                  timestamp-options
-                  (= type :checkbox)
-                  [{:value true :label "true"} {:value false :label "false"}]
-                  :else
-                  values)
-          filters (get-in table [:state :filters])
+       (let [values (if (coll? value) value [value])
+             ids (filter #(and (uuid? %) (nil? (db/entity [:block/uuid %]))) values)]
+         (when (seq ids) (db-async/<get-blocks (state/get-current-repo) ids))))
+     [])
+    (let [filters (get-in table [:state :filters])
           set-filters! (:set-filters! data-fns)
           many? (if (or (contains? #{:date-before :date-after :before :after} operator)
                         (contains? #{:checkbox} type))
                   false
-                  true)
-          option (cond->
-                  {:input-default-placeholder (:block/title property)
-                   :input-opts {:class "!px-3 !py-1"}
-                   :items items
-                   :extract-fn :label
-                   :extract-chosen-fn :value
-                   :on-chosen (fn [value _selected? selected e]
-                                (when-not many?
-                                  (shui/popup-hide!))
-                                (let [value' (if many? selected value)
-                                      set-filters-fn (fn [value']
-                                                       (let [new-filters (update filters :filters
-                                                                                 (fn [col]
-                                                                                   (update col idx
-                                                                                           (fn [[property operator _value]]
-                                                                                             [property operator value']))))]
-                                                         (set-filters! new-filters)))]
-                                  (if (= value "Custom date")
-                                    (shui/popup-show!
-                                     (.-target e)
-                                     (ui/nlp-calendar
-                                      {:initial-focus true
-                                       :datetime? false
-                                       :on-day-click (fn [value]
-                                                       (set-filters-fn value)
-                                                       (shui/popup-hide!))})
-                                     {})
-                                    (set-filters-fn value'))))}
-                   many?
-                   (assoc
-                    :multiple-choices? true
-                    :selected-choices value))]
-      (shui/dropdown-menu
-       (shui/dropdown-menu-trigger
-        {:asChild true}
-        (shui/button
-         {:class "!px-2 rounded-none border-r"
-          :variant "ghost"
-          :size :sm
-          :on-click #(set-dropdown-open! (not dropdown-open?))}
-         (let [value (cond
-                       (uuid? value)
-                       (db/entity [:block/uuid value])
-                       (instance? js/Date value)
-                       (some->> (tc/to-date value)
-                                (t/to-default-time-zone)
-                                (tf/unparse (tf/formatter "yyyy-MM-dd")))
-                       (and (coll? value) (every? uuid? value))
-                       (keep #(db/entity [:block/uuid %]) value)
-                       :else
-                       value)]
-           [:div.flex.flex-row.items-center.gap-1.text-xs
-            (cond
-              (de/entity? value)
-              [:div (get-property-value-content value)]
-
-              (string? value)
-              [:div value]
-
-              (boolean? value)
-              [:div (str value)]
-
-              (seq value)
-              (->> (map (fn [v] [:div (get-property-value-content v)]) value)
-                   (interpose [:div "or"]))
-              :else
-              "All")])))
-       (shui/dropdown-menu-content
-        {:align "start"}
-        (select/select option))))))
+                  true)]
+      (shui/button
+       {:class "!px-2 rounded-none border-r"
+        :variant "ghost"
+        :size :sm
+        :on-click (fn [e]
+                    (p/let [values (when (and property-ident
+                                              (not (contains? #{:data :datetime :checkbox} type)))
+                                     (p/let [data (db-async/<get-property-values property-ident {:view-id (:db/id view-entity)
+                                                                                                 :query-entity-ids (:query-entity-ids opts)})]
+                                       (map (fn [v] (if (map? (:value v))
+                                                      (assoc v :value (:block/uuid (:value v)))
+                                                      v)) data)))
+                            items (cond
+                                    (contains? #{:before :after} operator)
+                                    timestamp-options
+                                    (= type :checkbox)
+                                    [{:value true :label "true"} {:value false :label "false"}]
+                                    :else
+                                    values)]
+                      (shui/popup-show!
+                       (.-target e)
+                       (fn []
+                         (let [option (cond->
+                                       {:input-default-placeholder (:block/title property)
+                                        :input-opts {:class "!px-3 !py-1"}
+                                        :items items
+                                        :extract-fn :label
+                                        :extract-chosen-fn :value
+                                        :on-chosen (fn [value _selected? selected e]
+                                                     (when-not many?
+                                                       (shui/popup-hide!))
+                                                     (let [value' (if many? selected value)
+                                                           set-filters-fn (fn [value']
+                                                                            (let [new-filters (update filters :filters
+                                                                                                      (fn [col]
+                                                                                                        (update col idx
+                                                                                                                (fn [[property operator _value]]
+                                                                                                                  [property operator value']))))]
+                                                                              (set-filters! new-filters)))]
+                                                       (if (= value "Custom date")
+                                                         (shui/popup-show!
+                                                          (.-target e)
+                                                          (ui/nlp-calendar
+                                                           {:initial-focus true
+                                                            :datetime? false
+                                                            :on-day-click (fn [value]
+                                                                            (set-filters-fn value)
+                                                                            (shui/popup-hide!))})
+                                                          {})
+                                                         (set-filters-fn value'))))}
+                                        many?
+                                        (assoc
+                                         :multiple-choices? true
+                                         :selected-choices value))]
+                           (select/select option)))
+                       {:align :start})))}
+       (let [value (cond
+                     (uuid? value)
+                     (db/entity [:block/uuid value])
+                     (instance? js/Date value)
+                     (some->> (tc/to-date value)
+                              (t/to-default-time-zone)
+                              (tf/unparse (tf/formatter "yyyy-MM-dd")))
+                     (and (coll? value) (every? uuid? value))
+                     (keep #(db/entity [:block/uuid %]) value)
+                     :else
+                     value)]
+         [:div.flex.flex-row.items-center.gap-1.text-xs
+          (cond
+            (de/entity? value)
+            [:div (get-property-value-content value)]
+
+            (string? value)
+            [:div value]
+
+            (boolean? value)
+            [:div (str value)]
+
+            (seq value)
+            (->> (map (fn [v] [:div (get-property-value-content v)]) value)
+                 (interpose [:div "or"]))
+            :else
+            "All")])))))
 
 (rum/defc filter-value < rum/static
   [view-entity table property operator value filters set-filters! idx opts]
@@ -1280,14 +1451,12 @@
 
 (rum/defc table-body < rum/static
   [table option rows *scroller-ref set-items-rendered!]
-  (let [[scrolling? set-scrolling!] (hooks/use-state false)
-        sidebar? (get-in option [:config :sidebar?])]
+  (let [[scrolling? set-scrolling!] (hooks/use-state false)]
     (when (seq rows)
       (ui/virtualized-list
        {:ref #(reset! *scroller-ref %)
-        :custom-scroll-parent (if sidebar?
-                                (first (dom/by-class "sidebar-item-list"))
-                                (gdom/getElement "main-content-container"))
+        :increase-viewport-by {:top 300 :bottom 300}
+        :custom-scroll-parent (get-scroll-parent (:config option))
         :compute-item-key (fn [idx]
                             (let [block-id (util/nth-safe rows idx)]
                               (str "table-row-" block-id)))
@@ -1332,7 +1501,7 @@
                     (ui/virtualized-list
                      {:ref #(reset! *scroller-ref %)
                       :class "content"
-                      :custom-scroll-parent (gdom/getElement "main-content-container")
+                      :custom-scroll-parent (get-scroll-parent config)
                       :increase-viewport-by {:top 64 :bottom 64}
                       :compute-item-key (fn [idx]
                                           (let [block-id (util/nth-safe rows idx)]
@@ -1377,7 +1546,7 @@
        (ui/virtualized-grid
         {:ref #(reset! *scroller-ref %)
          :total-count (count blocks)
-         :custom-scroll-parent (gdom/getElement "main-content-container")
+         :custom-scroll-parent (get-scroll-parent config)
          :skipAnimationFrameInResizeObserver true
          :compute-item-key (fn [idx]
                              (str (:db/id view-entity) "-card-" idx))
@@ -1753,9 +1922,12 @@
                                                      (assoc-in [:data-fns :add-new-object!] add-new-object!)
                                                      (assoc :data group ; data for this group
                                                             )))
-                       readable-property-value #(if (and (map? %) (or (:block/title %) (:logseq.property/value %)))
-                                                  (db-property/property-value-content %)
-                                                  (str %))
+                       readable-property-value #(cond (and (map? %) (or (:block/title %) (:logseq.property/value %)))
+                                                      (db-property/property-value-content %)
+                                                      (= (:db/ident %) :logseq.property/empty-placeholder)
+                                                      "Empty"
+                                                      :else
+                                                      (str %))
                        group-by-page? (or (= :block/page group-by-property-ident)
                                           (and (not db-based?) (contains? #{:linked-references :unlinked-references} display-type)))]
                    (rum/with-key
@@ -1865,7 +2037,8 @@
         (:logseq.property.linked-references/includes view-parent)
         (:logseq.property.linked-references/excludes view-parent)
         (:filters view-parent)
-        query-entity-ids]))
+        query-entity-ids
+        (:data-changes-version option)]))
     (if loading?
       [:div.flex.flex-col.space-2.gap-2.my-2
        (repeat 3 (shui/skeleton {:class "h-6 w-full"}))]
@@ -1889,10 +2062,24 @@
                                           :load-view-data load-view-data
                                           :set-view-entity! set-view-entity!))])))
 
+(defn sub-view-data-changes
+  [view-parent view-feature-type]
+  (when view-parent
+    (when-let [repo (state/get-current-repo)]
+      (when-let [k (case view-feature-type
+                     :class-objects :frontend.worker.react/objects
+                     :linked-references :frontend.worker.react/refs
+                     nil)]
+        (let [*version (atom 0)]
+          (react/q repo [k (:db/id view-parent)]
+                   {:query-fn (fn [_] (swap! *version inc))}
+                   nil))))))
+
 (rum/defc sub-view < rum/reactive db-mixins/query
   [view-entity option]
-  (let [view (or (some-> (:db/id view-entity) db/sub-block) view-entity)]
-    (view-aux view option)))
+  (let [view (or (some-> (:db/id view-entity) db/sub-block) view-entity)
+        data-changes-version (some-> (sub-view-data-changes (:view-parent option) (:view-feature-type option)) rum/react)]
+    (view-aux view (assoc option :data-changes-version data-changes-version))))
 
 (rum/defc view < rum/static
   [{:keys [view-parent view-feature-type view-entity] :as option}]
@@ -1903,7 +2090,7 @@
     (hooks/use-effect!
      #(c.m/run-task*
        (m/sp
-         (when-not query?             ; TODO: move query logic to worker
+         (when-not query?
            (let [repo (state/get-current-repo)]
              (when (and db-based? (not view-entity))
                (c.m/<? (db-async/<get-views repo (:db/id view-parent) view-feature-type))
@@ -1920,6 +2107,8 @@
     (when (if db-based? view-entity (or view-entity view-parent
                                         (= view-feature-type :all-pages)))
       (let [option' (assoc option
+                           :view-feature-type (or view-feature-type
+                                                  (:logseq.property.view/feature-type view-entity))
                            :views views
                            :set-views! set-views!
                            :set-view-entity! set-view-entity!)]

+ 8 - 7
src/main/frontend/components/whiteboard.cljs

@@ -134,11 +134,13 @@
 
 (rum/defc whiteboard-dashboard
   []
-  (if (state/enable-whiteboards?)
-    (let [whiteboards (->> (model/get-all-whiteboards (state/get-current-repo))
-                           (sort-by :block/updated-at)
-                           reverse)
-          [ref rect] (use-bounding-client-rect)
+  (let [[whiteboards set-whiteboards!] (hooks/use-state nil)]
+    (hooks/use-effect!
+     (fn []
+       (p/let [result (db-async/<get-whiteboards (state/get-current-repo))]
+         (set-whiteboards! result)))
+     [])
+    (let [[ref rect] (use-bounding-client-rect)
           [container-width] (when rect [(.-width rect) (.-height rect)])
           cols (cond (< container-width 600) 1
                      (< container-width 900) 2
@@ -187,8 +189,7 @@
                                                                                     (conj checked-page-ids id)
                                                                                     (disj checked-page-ids id))))})]))
          (for [n (range empty-cards)]
-           [:div.dashboard-card.dashboard-bg-card {:key n}])]]])
-    [:div "This feature is not publicly available yet."]))
+           [:div.dashboard-card.dashboard-bg-card {:key n}])]]])))
 
 (rum/defc whiteboard-page
   [page-uuid block-id]

+ 33 - 13
src/main/frontend/db/async.cljs

@@ -42,7 +42,10 @@
                        :where
                        [?b :block/properties ?p]
                        [(get ?p :template) ?t]])]
-    (into {} result)))
+    (->> result
+         (map (fn [[template b]]
+                [template (assoc b :block/title template)]))
+         (into {}))))
 
 (defn <get-template-by-name
   [name]
@@ -219,14 +222,14 @@
                       '[:find [(pull ?block ?block-attrs) ...]
                         :in $ ?start-time ?end-time ?block-attrs
                         :where
-                        (or [?block :logseq.task/scheduled ?n]
-                            [?block :logseq.task/deadline ?n])
+                        (or [?block :logseq.property/scheduled ?n]
+                            [?block :logseq.property/deadline ?n])
                         [(>= ?n ?start-time)]
                         [(<= ?n ?end-time)]
-                        [?block :logseq.task/status ?status]
+                        [?block :logseq.property/status ?status]
                         [?status :db/ident ?status-ident]
-                        [(not= ?status-ident :logseq.task/status.done)]
-                        [(not= ?status-ident :logseq.task/status.canceled)]]
+                        [(not= ?status-ident :logseq.property/status.done)]
+                        [(not= ?status-ident :logseq.property/status.canceled)]]
                       start-time
                       future-time
                       '[*])
@@ -274,6 +277,23 @@
           [?b :block/tags ?class-id]]
         class-ids)))
 
+(defn <get-whiteboards
+  [graph]
+  (p/let [result (if (config/db-based-graph? graph)
+                   (<q graph {:transact-db? false}
+                       '[:find [(pull ?page [:db/id :block/uuid :block/name :block/title :block/created-at :block/updated-at]) ...]
+                         :where
+                         [?page :block/tags :logseq.class/Whiteboard]
+                         [?page :block/name]])
+                   (<q graph {:transact-db? false}
+                       '[:find [(pull ?page [:db/id :block/uuid :block/name :block/title :block/created-at :block/updated-at]) ...]
+                         :where
+                         [?page :block/type "whiteboard"]
+                         [?page :block/name]]))]
+    (->> result
+         (sort-by :block/updated-at)
+         reverse)))
+
 (defn <get-views
   [graph class-id view-feature-type]
   (<q graph {:transact-db? true}
@@ -322,7 +342,7 @@
   [graph block-id]
   (p/let [history (<get-block-properties-history graph block-id)
           status-history (filter
-                          (fn [b] (= :logseq.task/status (:db/ident (:logseq.property.history/property b))))
+                          (fn [b] (= :logseq.property/status (:db/ident (:logseq.property.history/property b))))
                           history)]
     (when (seq status-history)
       (let [time (loop [[last-item item & others] status-history
@@ -330,17 +350,17 @@
                    (if item
                      (let [last-status (:db/ident (:logseq.property.history/ref-value last-item))
                            this-status (:db/ident (:logseq.property.history/ref-value item))]
-                       (if (and (= this-status :logseq.task/status.doing)
+                       (if (and (= this-status :logseq.property/status.doing)
                                 (empty? others))
                          (-> (+ time (- (tc/to-long (t/now)) (:block/created-at item)))
                              (quot 1000))
                          (let [time' (if (or
-                                          (= last-status :logseq.task/status.doing)
+                                          (= last-status :logseq.property/status.doing)
                                           (and
-                                           (not (contains? #{:logseq.task/status.canceled
-                                                             :logseq.task/status.backlog
-                                                             :logseq.task/status.done} last-status))
-                                           (= this-status :logseq.task/status.done)))
+                                           (not (contains? #{:logseq.property/status.canceled
+                                                             :logseq.property/status.backlog
+                                                             :logseq.property/status.done} last-status))
+                                           (= this-status :logseq.property/status.done)))
                                        (+ time (- (:block/created-at item) (:block/created-at last-item)))
                                        time)]
                            (recur (cons item others) time'))))

+ 12 - 25
src/main/frontend/db/model.cljs

@@ -592,31 +592,18 @@ independent of format as format specific heading characters are stripped"
    (when repo
      (when (conn/get-db repo)
        (let [entity (db-utils/entity eid)
-             ids (page-alias-set repo eid)]
-         (->>
-          (react/q repo
-                   [:frontend.worker.react/refs eid]
-                   {:query-fn (fn []
-                                (let [entities (mapcat (fn [id]
-                                                         (:block/_refs (db-utils/entity id))) ids)
-                                      blocks (map (fn [e]
-                                                    {:block/parent (:block/parent e)
-                                                     :block/order (:block/order e)
-                                                     :block/page (:block/page e)
-                                                     :block/collapsed? (:block/collapsed? e)}) entities)]
-                                  {:entities entities
-                                   :blocks blocks}))}
-                   nil)
-          react
-          :entities
-          (remove (fn [block]
-                    (or
-                     (= (:db/id block) eid)
-                     (= eid (:db/id (:block/page block)))
-                     (ldb/hidden? (:block/page block))
-                     (contains? (set (map :db/id (:block/tags block))) (:db/id entity))
-                     (some? (get block (:db/ident entity))))))
-          (util/distinct-by :db/id)))))))
+             ids (page-alias-set repo eid)
+             entities (mapcat (fn [id]
+                                (:block/_refs (db-utils/entity id))) ids)]
+         (->> entities
+              (remove (fn [block]
+                        (or
+                         (= (:db/id block) eid)
+                         (= eid (:db/id (:block/page block)))
+                         (ldb/hidden? (:block/page block))
+                         (contains? (set (map :db/id (:block/tags block))) (:db/id entity))
+                         (some? (get block (:db/ident entity))))))
+              (util/distinct-by :db/id)))))))
 
 (defn get-block-referenced-blocks
   ([block-id]

+ 1 - 1
src/main/frontend/extensions/code.css

@@ -26,7 +26,7 @@
 
   &-calc {
     @apply text-sm;
-    padding: 23px 0.25em 0.25em 0.25em;
+    padding: 0.35em 0.25em 0.25em 0.25em;
     width: max-content;
     text-align: right;
 

+ 23 - 21
src/main/frontend/extensions/tldraw.cljs

@@ -9,11 +9,13 @@
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
+            [frontend.db-mixins :as db-mixins]
             [frontend.db.async :as db-async]
             [frontend.db.model :as model]
             [frontend.extensions.pdf.assets :as pdf-assets]
             [frontend.handler.assets :as assets-handler]
             [frontend.handler.editor :as editor-handler]
+            [frontend.handler.file-based.editor :as file-editor-handler]
             [frontend.handler.history :as history]
             [frontend.handler.notification :as notification]
             [frontend.handler.page :as page-handler]
@@ -84,11 +86,11 @@
 
 (defn save-asset-handler
   [file]
-  (-> (editor-handler/file-based-save-assets! (state/get-current-repo) [(js->clj file)])
+  (-> (file-editor-handler/file-based-save-assets! (state/get-current-repo) [(js->clj file)])
       (p/then
        (fn [res]
          (when-let [[asset-file-name _ full-file-path] (and (seq res) (first res))]
-           (editor-handler/resolve-relative-path (or full-file-path asset-file-name)))))))
+           (file-editor-handler/resolve-relative-path (or full-file-path asset-file-name)))))))
 
 (defn references-count
   [props]
@@ -233,15 +235,29 @@
             :onPersist #(on-persist page-uuid %1 %2)
             :model data})])
 
-(rum/defc tldraw-app-inner
-  [page-uuid block-id loaded-app set-loaded-app]
-  (let [[loading? set-loading!] (hooks/use-state true)]
+(rum/defc tldraw-app-react < rum/reactive db-mixins/query
+  [page-uuid populate-onboarding? loaded-app on-mount]
+  (let [data (whiteboard-handler/get-page-tldr page-uuid)]
+    (when data
+      (tldraw-inner page-uuid data populate-onboarding? loaded-app on-mount))))
+
+(rum/defc tldraw-app
+  [page-uuid block-id]
+  (let [[loading? set-loading!] (hooks/use-state true)
+        [loaded-app set-loaded-app] (rum/use-state nil)]
     (hooks/use-effect!
      (fn []
        (p/do!
         (db-async/<get-block (state/get-current-repo) page-uuid)
         (set-loading! false)))
      [])
+
+    (hooks/use-effect!
+     (fn []
+       (when (and loaded-app block-id)
+         (state/focus-whiteboard-shape loaded-app block-id))
+       #())
+     [block-id loaded-app])
     (when-not loading?
       (let [populate-onboarding? (whiteboard-handler/should-populate-onboarding-whiteboard? page-uuid)
             on-mount (fn [^js tln]
@@ -253,19 +269,5 @@
                                      (whiteboard-handler/populate-onboarding-whiteboard api))
                                    #(do (whiteboard-handler/cleanup! (.-currentPage tln))
                                         (state/focus-whiteboard-shape tln block-id)
-                                        (set-loaded-app tln))))))
-            data (whiteboard-handler/get-page-tldr page-uuid)]
-        (when data
-          (tldraw-inner page-uuid data populate-onboarding? loaded-app on-mount))))))
-
-(rum/defc tldraw-app
-  [page-uuid block-id]
-  (let [page-uuid (str page-uuid)
-        [loaded-app set-loaded-app] (rum/use-state nil)]
-    (hooks/use-effect!
-     (fn []
-       (when (and loaded-app block-id)
-         (state/focus-whiteboard-shape loaded-app block-id))
-       #())
-     [block-id loaded-app])
-    (tldraw-app-inner page-uuid block-id loaded-app set-loaded-app)))
+                                        (set-loaded-app tln))))))]
+        (tldraw-app-react page-uuid populate-onboarding? loaded-app on-mount)))))

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

@@ -66,7 +66,7 @@
 
 (defn select-block!
   [block-uuid]
-  (let [blocks (js/document.getElementsByClassName (str "id" block-uuid))]
+  (let [blocks (util/get-blocks-by-id block-uuid)]
     (when (seq blocks)
       (state/exit-editing-and-set-selected-blocks! blocks))))
 

+ 28 - 30
src/main/frontend/handler/common/config_edn.cljs

@@ -88,36 +88,34 @@ nested keys or positional errors e.g. tuples"
 
 (def file-only-config
   "File only config that is deprecated in DB graphs"
-  {:preferred-format
-   "is not used in DB graphs as there is only markdown mode."
-   :preferred-workflow
-   "is not used in DB graphs"
-   :property/separated-by-commas
-   "is not used in DB graphs"
-   :property-pages/enabled?
-   "is not used in DB graphs as all properties have pages"
-   :property-pages/excludelist
-   "is not used in DB graphs"
-   :hidden
-   "is not used in DB graphs"
-   :org-mode/insert-file-link?
-   "is not used in DB graphs"
-   :block-hidden-properties
-   "is not used in DB graphs as hiding a property is done in its configuration"
-   :ignored-page-references-keywords
-   "is not used in DB graphs"
-   :file/name-format
-   "is not used in DB graphs"
-   :feature/enable-block-timestamps?
-   "is not used in DB graphs as it is always enabled"
-   :favorites
-   "is not stored in config for DB graphs"
-   :journal/page-title-format
-   "is not used in DB graphs"
-   :srs/learning-fraction
-   "is not used in DB graphs"
-   :srs/initial-interval
-   "is not used in DB graphs"})
+  (merge
+   (zipmap
+    [:file/name-format
+     :file-sync/ignore-files
+     :hidden
+     :ignored-page-references-keywords
+     :journal/page-title-format
+     :journals-directory
+     :logbook/settings
+     :org-mode/insert-file-link?
+     :pages-directory
+     :preferred-workflow
+     :property/separated-by-commas
+     :property-pages/excludelist
+     :srs/learning-fraction
+     :srs/initial-interval
+     :whiteboards-directory]
+    (repeat "is not used in DB graphs"))
+   {:preferred-format
+    "is not used in DB graphs as there is only markdown mode."
+    :property-pages/enabled?
+    "is not used in DB graphs as all properties have pages"
+    :block-hidden-properties
+    "is not used in DB graphs as hiding a property is done in its configuration"
+    :feature/enable-block-timestamps?
+    "is not used in DB graphs as it is always enabled"
+    :favorites
+    "is not stored in config for DB graphs"}))
 
 (defn detect-deprecations
   "Detects config keys that will or have been deprecated"

+ 25 - 0
src/main/frontend/handler/common/editor.cljs

@@ -0,0 +1,25 @@
+(ns ^:no-doc frontend.handler.common.editor
+  (:require [frontend.commands :as commands]))
+
+(defn insert-command!
+  [id command-output format {:keys [restore?]
+                             :or {restore? true}
+                             :as option}]
+  (cond
+    ;; replace string
+    (string? command-output)
+    (commands/insert! id command-output option)
+
+    ;; steps
+    (vector? command-output)
+    (commands/handle-steps command-output format)
+
+    (fn? command-output)
+    (let [s (command-output)]
+      (commands/insert! id s option))
+
+    :else
+    nil)
+
+  (when restore?
+    (commands/restore-state)))

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

@@ -83,8 +83,7 @@
 (defn <create-class!
   "Creates a class page and provides class-specific error handling"
   [title options]
-  (-> (page-common-handler/<create! title (assoc options :class? true
-                                                 :skip-existing-page-check? false))
+  (-> (page-common-handler/<create! title (assoc options :class? true))
       (p/catch (fn [e]
                  (when (= :notification (:type (ex-data e)))
                    (notification/show! (get-in (ex-data e) [:payload :message])

+ 4 - 3
src/main/frontend/handler/db_based/property.cljs

@@ -44,10 +44,10 @@
    (outliner-op/create-property-text-block! block-id property-id value opts)))
 
 (defn batch-set-property!
-  [block-ids property-id value]
+  [block-ids property-id value opts]
   (ui-outliner-tx/transact!
    {:outliner-op :batch-set-property}
-   (outliner-op/batch-set-property! block-ids property-id value)))
+   (outliner-op/batch-set-property! block-ids property-id value opts)))
 
 (defn batch-remove-property!
   [block-ids property-id]
@@ -73,7 +73,8 @@
     (if-let [closed-value-entity (db-property/get-closed-value-entity-by-name db db-ident closed-value)]
       (batch-set-property! block-ids
                            db-ident
-                           (:db/id closed-value-entity))
+                           (:db/id closed-value-entity)
+                           {:entity-id? true})
       (js/console.error (str "No entity found for closed value " (pr-str closed-value))))))
 
 (defn upsert-closed-value!

+ 32 - 168
src/main/frontend/handler/editor.cljs

@@ -54,8 +54,8 @@
             [logseq.common.util.block-ref :as block-ref]
             [logseq.common.util.page-ref :as page-ref]
             [logseq.db :as ldb]
-            [logseq.db.file-based.schema :as file-schema]
             [logseq.db.common.entity-plus :as entity-plus]
+            [logseq.db.file-based.schema :as file-schema]
             [logseq.db.frontend.property :as db-property]
             [logseq.graph-parser.block :as gp-block]
             [logseq.graph-parser.mldoc :as gp-mldoc]
@@ -66,7 +66,8 @@
             [logseq.outliner.property :as outliner-property]
             [logseq.shui.popup.core :as shui-popup]
             [promesa.core :as p]
-            [rum.core :as rum]))
+            [rum.core :as rum]
+            [frontend.handler.common.editor :as editor-common-handler]))
 
 ;; FIXME: should support multiple images concurrently uploading
 
@@ -236,7 +237,7 @@
 
 (defn highlight-block!
   [block-uuid]
-  (let [blocks (array-seq (js/document.getElementsByClassName (str "id" block-uuid)))]
+  (let [blocks (util/get-blocks-by-id block-uuid)]
     (doseq [block blocks]
       (dom/add-class! block "block-highlight"))))
 
@@ -658,19 +659,19 @@
 (defn db-based-cycle-todo!
   [block]
   (let [status-value (if (ldb/class-instance? (db/entity :logseq.class/Task) block)
-                       (:logseq.task/status block)
-                       (get block :logseq.task/status {}))
+                       (:logseq.property/status block)
+                       (get block :logseq.property/status {}))
         next-status (case (:db/ident status-value)
-                      :logseq.task/status.todo
-                      :logseq.task/status.doing
-                      :logseq.task/status.doing
-                      :logseq.task/status.done
-                      :logseq.task/status.done
+                      :logseq.property/status.todo
+                      :logseq.property/status.doing
+                      :logseq.property/status.doing
+                      :logseq.property/status.done
+                      :logseq.property/status.done
                       nil
-                      :logseq.task/status.todo)
+                      :logseq.property/status.todo)
         repo (state/get-current-repo)]
     (property-handler/set-block-property! repo (:block/uuid block)
-                                          :logseq.task/status
+                                          :logseq.property/status
                                           (:db/id (db/entity next-status)))))
 
 (defn cycle-todos!
@@ -1049,10 +1050,11 @@
   [copy?]
   (when copy? (copy-selection-blocks true))
   (state/set-block-op-type! :cut)
-  (when-let [blocks (seq (get-selected-blocks))]
+  (when-let [blocks (->> (get-selected-blocks)
+                         (remove #(dom/has-class? % "property-value-container"))
+                         seq)]
     ;; remove queries
-    (let [dom-blocks (remove (fn [block]
-                               (= "true" (dom/attr block "data-query"))) blocks)]
+    (let [dom-blocks (remove (fn [block] (= "true" (dom/attr block "data-query"))) blocks)]
       (when (seq dom-blocks)
         (let [repo (state/get-current-repo)
               block-uuids (distinct (map #(uuid (dom/attr % "blockid")) dom-blocks))
@@ -1378,98 +1380,7 @@
              (property-file/remove-properties-when-file-based repo format)
              string/trim)))
 
-(defn insert-command!
-  [id command-output format {:keys [restore?]
-                             :or {restore? true}
-                             :as option}]
-  (cond
-    ;; replace string
-    (string? command-output)
-    (commands/insert! id command-output option)
-
-    ;; steps
-    (vector? command-output)
-    (commands/handle-steps command-output format)
-
-    (fn? command-output)
-    (let [s (command-output)]
-      (commands/insert! id s option))
-
-    :else
-    nil)
-
-  (when restore?
-    (commands/restore-state)))
-
-(defn file-based-save-assets!
-  "Save incoming(pasted) assets to assets directory.
-
-   Returns: [file-rpath file-obj file-fpath matched-alias]"
-  ([repo files]
-   (p/let [[repo-dir assets-dir] (assets-handler/ensure-assets-dir! repo)]
-     (file-based-save-assets! repo repo-dir assets-dir files
-                              (fn [index file-stem]
-                     ;; TODO: maybe there're other chars we need to handle?
-                                (let [file-base (-> file-stem
-                                                    (string/replace " " "_")
-                                                    (string/replace "%" "_")
-                                                    (string/replace "/" "_"))
-                                      file-name (str file-base "_" (.now js/Date) "_" index)]
-                                  (string/replace file-name #"_+" "_"))))))
-  ([repo repo-dir asset-dir-rpath files gen-filename]
-   (p/all
-    (for [[index ^js file] (map-indexed vector files)]
-      ;; WARN file name maybe fully qualified path when paste file
-      (p/let [file-name (util/node-path.basename (.-name file))
-              [file-stem ext-full ext-base] (if file-name
-                                              (let [ext-base (util/node-path.extname file-name)
-                                                    ext-full (if-not (config/extname-of-supported? ext-base)
-                                                               (util/full-path-extname file-name) ext-base)]
-                                                [(subs file-name 0 (- (count file-name)
-                                                                      (count ext-full))) ext-full ext-base])
-                                              ["" "" ""])
-              filename  (str (gen-filename index file-stem) ext-full)
-              file-rpath  (str asset-dir-rpath "/" filename)
-              matched-alias (assets-handler/get-matched-alias-by-ext ext-base)
-              file-rpath (cond-> file-rpath
-                           (not (nil? matched-alias))
-                           (string/replace #"^[.\/\\]*assets[\/\\]+" ""))
-              dir (or (:dir matched-alias) repo-dir)]
-        (if (util/electron?)
-          (do (js/console.debug "Debug: Copy Asset #" dir file-rpath)
-              (-> (if-let [from (not-empty (.-path file))]
-                    (js/window.apis.copyFileToAssets dir file-rpath from)
-                    (p/let [content (.arrayBuffer file)]
-                      (fs/write-file! repo repo-dir file-rpath content {:skip-compare? true})))
-                  (p/then
-                   (fn [dest]
-                     [file-rpath
-                      (if (string? dest) (js/File. #js[] dest) file)
-                      (path/path-join dir file-rpath)
-                      matched-alias]))
-                  (p/catch #(js/console.error "Debug: Copy Asset Error#" %))))
-
-          (->
-           (p/do! (js/console.debug "Debug: Writing Asset #" dir file-rpath)
-                  (cond
-                    (mobile-util/native-platform?)
-                   ;; capacitor fs accepts Blob, File implements Blob
-                    (p/let [buffer (.arrayBuffer file)
-                            content (base64/encodeByteArray (js/Uint8Array. buffer))
-                            fpath (path/path-join dir file-rpath)]
-                      (capacitor-fs/<write-file-with-base64 fpath content))
-
-                    (config/db-based-graph? repo) ;; memory-fs
-                    (p/let [buffer (.arrayBuffer file)
-                            content (js/Uint8Array. buffer)]
-                      (fs/write-file! repo dir file-rpath content nil))
-
-                    :else                ; nfs
-                    (fs/write-file! repo dir file-rpath (.stream file) nil))
-                  [file-rpath file (path/path-join dir file-rpath) matched-alias])
-           (p/catch (fn [error]
-                      (prn :paste-file-error)
-                      (js/console.error error))))))))))
+(def insert-command! editor-common-handler/insert-command!)
 
 (defn delete-asset-of-block!
   [{:keys [repo asset-block href full-text block-id local? delete-local?] :as _opts}]
@@ -1494,55 +1405,6 @@
                                (path/resolve-relative-path block-file-rpath href)))]
             (fs/unlink! repo asset-fpath nil)))))))
 
-;; assets/journals_2021_02_03_1612350230540_0.png
-(defn resolve-relative-path
-  "Relative path to current file path.
-
-   Requires editing state"
-  [file-path]
-  (if-let [current-file-rpath (or (db-model/get-block-file-path (state/get-edit-block))
-                                  ;; fix dummy file path of page
-                                  (when (config/get-pages-directory)
-                                    (path/path-join (config/get-pages-directory) "_.md"))
-                                  "pages/contents.md")]
-    (let [repo-dir (config/get-repo-dir (state/get-current-repo))
-          current-file-fpath (path/path-join repo-dir current-file-rpath)]
-      (path/get-relative-path current-file-fpath file-path))
-    file-path))
-
-(defn file-upload-assets!
-  "Paste asset and insert link to current editing block"
-  [repo id ^js files format uploading? drop-or-paste?]
-  (when (config/local-file-based-graph? repo)
-    (-> (file-based-save-assets! repo (js->clj files))
-          ;; FIXME: only the first asset is handled
-        (p/then
-         (fn [res]
-           (when-let [[asset-file-name file-obj asset-file-fpath matched-alias] (first res)]
-             (let [image? (config/ext-of-image? asset-file-name)]
-               (insert-command!
-                id
-                (assets-handler/get-asset-file-link format
-                                                    (if matched-alias
-                                                      (str
-                                                       (if image? "../assets/" "")
-                                                       "@" (:name matched-alias) "/" asset-file-name)
-                                                      (resolve-relative-path (or asset-file-fpath asset-file-name)))
-                                                    (if file-obj (.-name file-obj) (if image? "image" "asset"))
-                                                    image?)
-                format
-                {:last-pattern (if drop-or-paste? "" commands/command-trigger)
-                 :restore?     true
-                 :command      :insert-asset})
-               (recur (rest res))))))
-        (p/catch (fn [e]
-                   (js/console.error e)))
-        (p/finally
-          (fn []
-            (reset! uploading? false)
-            (reset! *asset-uploading? false)
-            (reset! *asset-uploading-process 0))))))
-
 (defn- write-file!
   [repo dir file file-rpath file-name]
   (if (util/electron?)
@@ -1623,7 +1485,7 @@
                  (throw (ex-info "Can't save asset" {:files files}))))))))))
 
 (defn db-upload-assets!
-  "Paste asset and insert link to current editing block"
+  "Paste asset for db graph and insert link to current editing block"
   [repo id ^js files format uploading? drop-or-paste?]
   (when (or (config/local-file-based-graph? repo)
             (config/db-based-graph? repo))
@@ -1654,7 +1516,7 @@
   (let [repo (state/get-current-repo)]
     (if (config/db-based-graph? repo)
       (db-upload-assets! repo id ^js files format uploading? drop-or-paste?)
-      (file-upload-assets! repo id ^js files format uploading? drop-or-paste?))))
+      (file-editor-handler/file-upload-assets! repo id ^js files format uploading? *asset-uploading? *asset-uploading-process drop-or-paste?))))
 
 ;; Editor should track some useful information, like editor modes.
 ;; For example:
@@ -2091,12 +1953,12 @@
         (cond->> new-content
           (not keep-uuid?) (property-file/remove-property-when-file-based repo format "id")
           true             (property-file/remove-property-when-file-based repo format "custom_id"))]
-    (merge (apply dissoc block (conj (when-not keep-uuid? [:block/_refs]) :block/pre-block? :block/meta))
+    (merge (apply dissoc block (conj (if-not keep-uuid? [:block/_refs] []) :block/pre-block? :block/meta))
            (cond->
             {:block/page {:db/id (:db/id page)}
              :block/title new-content}
              (not db-based?)
-             (assoc :block/properties (apply dissoc (:block/properties block)
+             (assoc :block/properties (apply dissoc (not-empty (:block/properties block))
                                              (concat
                                               (when-not keep-uuid? [:id])
                                               [:custom_id :custom-id]
@@ -2698,7 +2560,8 @@
   [_current-block sibling-block]
   (when-let [trigger (first (dom/by-class sibling-block "jtrigger"))]
     (state/clear-edit!)
-    (if (dom/has-class? trigger "ls-number")
+    (if (or (dom/has-class? trigger "ls-number")
+            (dom/has-class? trigger "ls-empty-text-property"))
       (.click trigger)
       (.focus trigger))))
 
@@ -2768,9 +2631,10 @@
       (move-cross-boundary-up-down direction move-opts)
 
       :else
-      (if up?
-        (cursor/move-cursor-up input)
-        (cursor/move-cursor-down input)))))
+      (when input
+        (if up?
+          (cursor/move-cursor-up input)
+          (cursor/move-cursor-down input))))))
 
 (defn move-to-block-when-cross-boundary
   [direction {:keys [block]}]
@@ -3449,7 +3313,6 @@
                (not (slide-focused?))
                (not (state/get-timestamp-block)))
       (util/stop e)
-
       (cond
         (or (state/editing?) (active-jtrigger?))
         (keydown-up-down-handler direction {})
@@ -3902,7 +3765,7 @@
                                              :collapse? true})]
        (->> blocks
             (map (fn [b] (or (some-> (:db/id (:block/link b)) db/entity) b)))
-            (map (comp gdom/getElementByClass (fn [b] (str "id" (:block/uuid b)))))
+            (mapcat (fn [b] (util/get-blocks-by-id (:block/uuid b))))
             state/exit-editing-and-set-selected-blocks!)))
    (state/set-state! :selection/selected-all? true)))
 
@@ -3917,7 +3780,7 @@
       (do
         (util/stop e)
         (state/exit-editing-and-set-selected-blocks!
-         [(gdom/getElementByClass (str "id" (:block/uuid edit-block)))]))
+         [(util/get-first-block-by-id (:block/uuid edit-block))]))
 
       edit-block
       nil
@@ -3945,7 +3808,8 @@
                   nil
 
                   (and parent (:block/parent parent))
-                  (state/exit-editing-and-set-selected-blocks! [(gdom/getElementByClass (str "id" (:block/uuid parent)))])
+                  (state/exit-editing-and-set-selected-blocks!
+                   [(util/get-first-block-by-id (:block/uuid parent))])
 
                   (:block/name parent)
                   ;; page block

+ 0 - 7
src/main/frontend/handler/events/ui.cljs

@@ -2,7 +2,6 @@
   "UI events"
   (:require [clojure.core.async :as async]
             [clojure.core.async.interop :refer [p->c]]
-            [frontend.components.block :as block]
             [frontend.components.cmdk.core :as cmdk]
             [frontend.components.file-sync :as file-sync]
             [frontend.components.page :as component-page]
@@ -44,12 +43,6 @@
             [logseq.shui.ui :as shui]
             [promesa.core :as p]))
 
-(defmethod events/handle :class/configure [[_ page]]
-  (shui/dialog-open!
-   #(block/block-container {} page)
-   {:label "page-configure"
-    :align :top}))
-
 (defmethod events/handle :go/search [_]
   (shui/dialog-open!
    cmdk/cmdk-modal

+ 123 - 1
src/main/frontend/handler/file_based/editor.cljs

@@ -5,25 +5,34 @@
             [frontend.config :as config]
             [frontend.date :as date]
             [frontend.db :as db]
+            [frontend.db.model :as db-model]
             [frontend.db.query-dsl :as query-dsl]
             [frontend.format.block :as block]
             [frontend.format.mldoc :as mldoc]
+            [frontend.fs :as fs]
+            [frontend.fs.capacitor-fs :as capacitor-fs]
+            [frontend.handler.assets :as assets-handler]
             [frontend.handler.block :as block-handler]
+            [frontend.handler.common.editor :as editor-common-handler]
             [frontend.handler.file-based.property :as file-property-handler]
             [frontend.handler.file-based.property.util :as property-util]
             [frontend.handler.file-based.repeated :as repeated]
             [frontend.handler.file-based.status :as status]
             [frontend.handler.property.file :as property-file]
+            [frontend.mobile.util :as mobile-util]
             [frontend.modules.outliner.op :as outliner-op]
             [frontend.modules.outliner.ui :as ui-outliner-tx]
             [frontend.state :as state]
             [frontend.util :as util]
             [frontend.util.file-based.clock :as clock]
             [frontend.util.file-based.drawer :as drawer]
+            [goog.crypt.base64 :as base64]
+            [logseq.common.path :as path]
             [logseq.common.util :as common-util]
             [logseq.common.util.block-ref :as block-ref]
             [logseq.db :as ldb]
-            [logseq.db.file-based.schema :as file-schema]))
+            [logseq.db.file-based.schema :as file-schema]
+            [promesa.core :as p]))
 
 (defn- remove-non-existed-refs!
   [refs]
@@ -273,3 +282,116 @@
                                 (date/get-date-time-string-3)))]
       content)
     content))
+
+(defn file-based-save-assets!
+  "Save incoming(pasted) assets to assets directory.
+
+   Returns: [file-rpath file-obj file-fpath matched-alias]"
+  ([repo files]
+   (p/let [[repo-dir assets-dir] (assets-handler/ensure-assets-dir! repo)]
+     (file-based-save-assets! repo repo-dir assets-dir files
+                              (fn [index file-stem]
+                     ;; TODO: maybe there're other chars we need to handle?
+                                (let [file-base (-> file-stem
+                                                    (string/replace " " "_")
+                                                    (string/replace "%" "_")
+                                                    (string/replace "/" "_"))
+                                      file-name (str file-base "_" (.now js/Date) "_" index)]
+                                  (string/replace file-name #"_+" "_"))))))
+  ([repo repo-dir asset-dir-rpath files gen-filename]
+   (p/all
+    (for [[index ^js file] (map-indexed vector files)]
+      ;; WARN file name maybe fully qualified path when paste file
+      (p/let [file-name (util/node-path.basename (.-name file))
+              [file-stem ext-full ext-base] (if file-name
+                                              (let [ext-base (util/node-path.extname file-name)
+                                                    ext-full (if-not (config/extname-of-supported? ext-base)
+                                                               (util/full-path-extname file-name) ext-base)]
+                                                [(subs file-name 0 (- (count file-name)
+                                                                      (count ext-full))) ext-full ext-base])
+                                              ["" "" ""])
+              filename  (str (gen-filename index file-stem) ext-full)
+              file-rpath  (str asset-dir-rpath "/" filename)
+              matched-alias (assets-handler/get-matched-alias-by-ext ext-base)
+              file-rpath (cond-> file-rpath
+                           (not (nil? matched-alias))
+                           (string/replace #"^[.\/\\]*assets[\/\\]+" ""))
+              dir (or (:dir matched-alias) repo-dir)]
+        (if (util/electron?)
+          (do (js/console.debug "Debug: Copy Asset #" dir file-rpath)
+              (-> (if-let [from (not-empty (.-path file))]
+                    (js/window.apis.copyFileToAssets dir file-rpath from)
+                    (p/let [content (.arrayBuffer file)]
+                      (fs/write-file! repo repo-dir file-rpath content {:skip-compare? true})))
+                  (p/then
+                   (fn [dest]
+                     [file-rpath
+                      (if (string? dest) (js/File. #js[] dest) file)
+                      (path/path-join dir file-rpath)
+                      matched-alias]))
+                  (p/catch #(js/console.error "Debug: Copy Asset Error#" %))))
+
+          (->
+           (p/do! (js/console.debug "Debug: Writing Asset #" dir file-rpath)
+                  (cond
+                    (mobile-util/native-platform?)
+                   ;; capacitor fs accepts Blob, File implements Blob
+                    (p/let [buffer (.arrayBuffer file)
+                            content (base64/encodeByteArray (js/Uint8Array. buffer))
+                            fpath (path/path-join dir file-rpath)]
+                      (capacitor-fs/<write-file-with-base64 fpath content))
+
+                    :else                ; nfs
+                    (fs/write-file! repo dir file-rpath (.stream file) nil))
+                  [file-rpath file (path/path-join dir file-rpath) matched-alias])
+           (p/catch (fn [error]
+                      (prn :paste-file-error)
+                      (js/console.error error))))))))))
+
+;; assets/journals_2021_02_03_1612350230540_0.png
+(defn resolve-relative-path
+  "Relative path to current file path.
+
+   Requires editing state"
+  [file-path]
+  (if-let [current-file-rpath (or (db-model/get-block-file-path (state/get-edit-block))
+                                  ;; fix dummy file path of page
+                                  (when (config/get-pages-directory)
+                                    (path/path-join (config/get-pages-directory) "_.md"))
+                                  "pages/contents.md")]
+    (let [repo-dir (config/get-repo-dir (state/get-current-repo))
+          current-file-fpath (path/path-join repo-dir current-file-rpath)]
+      (path/get-relative-path current-file-fpath file-path))
+    file-path))
+
+(defn file-upload-assets!
+  "Paste asset for file graph and insert link to current editing block"
+  [repo id ^js files format uploading? *asset-uploading? *asset-uploading-process drop-or-paste?]
+  (-> (file-based-save-assets! repo (js->clj files))
+      ;; FIXME: only the first asset is handled
+      (p/then
+       (fn [res]
+         (when-let [[asset-file-name file-obj asset-file-fpath matched-alias] (first res)]
+           (let [image? (config/ext-of-image? asset-file-name)]
+             (editor-common-handler/insert-command!
+              id
+              (assets-handler/get-asset-file-link format
+                                                  (if matched-alias
+                                                    (str
+                                                     (if image? "../assets/" "")
+                                                     "@" (:name matched-alias) "/" asset-file-name)
+                                                    (resolve-relative-path (or asset-file-fpath asset-file-name)))
+                                                  (if file-obj (.-name file-obj) (if image? "image" "asset"))
+                                                  image?)
+              format
+              {:last-pattern (if drop-or-paste? "" commands/command-trigger)
+               :restore?     true
+               :command      :insert-asset})
+             (recur (rest res))))))
+      (p/catch (fn [e]
+                 (js/console.error e)))
+      (p/finally
+        (fn []
+          (reset! uploading? false)
+          (reset! *asset-uploading? false)
+          (reset! *asset-uploading-process 0)))))

+ 5 - 5
src/main/frontend/handler/property.cljs

@@ -1,9 +1,9 @@
 (ns frontend.handler.property
   "Property fns for both file and DB graphs"
-  (:require [frontend.handler.db-based.property :as db-property-handler]
-            [frontend.handler.file-based.property :as file-property-handler]
+  (:require [frontend.config :as config]
+            [frontend.handler.db-based.property :as db-property-handler]
             [frontend.handler.file-based.page-property :as file-page-property]
-            [frontend.config :as config]
+            [frontend.handler.file-based.property :as file-property-handler]
             [frontend.state :as state]))
 
 (defn remove-block-property!
@@ -53,12 +53,12 @@
     (file-property-handler/batch-remove-block-property! block-ids key)))
 
 (defn batch-set-block-property!
-  [repo block-ids key value]
+  [repo block-ids key value & {:as opts}]
   (assert (some? key) "key is nil")
   (if (config/db-based-graph? repo)
     (if (nil? value)
       (db-property-handler/batch-remove-property! block-ids key)
-      (db-property-handler/batch-set-property! block-ids key value))
+      (db-property-handler/batch-set-property! block-ids key value opts))
     (file-property-handler/batch-set-block-property! block-ids key value)))
 
 (defn set-block-properties!

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

@@ -98,7 +98,7 @@
             (> (count fragment) 36)
             (subs fragment (- (count fragment) 36)))]
     (if (and id (util/uuid-string? id))
-      (let [elements (array-seq (js/document.getElementsByClassName (str "id" id)))]
+      (let [elements (util/get-blocks-by-id id)]
         (when (first elements)
           (util/scroll-to-element (gobj/get (first elements) "id")))
         (state/exit-editing-and-set-selected-blocks! elements))

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

@@ -25,7 +25,7 @@
   [blocks target-block opts]
   (op-transact!
    (let [id (:db/id target-block)]
-    [:insert-blocks [blocks id opts]])))
+     [:insert-blocks [blocks id opts]])))
 
 (defn delete-blocks!
   [blocks opts]
@@ -38,20 +38,20 @@
   [blocks target-block sibling?]
   (op-transact!
    (let [ids (map :db/id blocks)
-        target-id (:db/id target-block)]
-    [:move-blocks [ids target-id sibling?]])))
+         target-id (:db/id target-block)]
+     [:move-blocks [ids target-id sibling?]])))
 
 (defn move-blocks-up-down!
   [blocks up?]
   (op-transact!
    (let [ids (map :db/id blocks)]
-    [:move-blocks-up-down [ids up?]])))
+     [:move-blocks-up-down [ids up?]])))
 
 (defn indent-outdent-blocks!
   [blocks indent? & {:as opts}]
   (op-transact!
    (let [ids (map :db/id blocks)]
-    [:indent-outdent-blocks [ids indent? opts]])))
+     [:indent-outdent-blocks [ids indent? opts]])))
 
 (defn upsert-property!
   [property-id schema property-opts]
@@ -79,9 +79,9 @@
    [:create-property-text-block [block-id property-id value opts]]))
 
 (defn batch-set-property!
-  [block-ids property-id value]
+  [block-ids property-id value opts]
   (op-transact!
-   [:batch-set-property [block-ids property-id value]]))
+   [:batch-set-property [block-ids property-id value opts]]))
 
 (defn batch-remove-property!
   [block-ids property-id]

+ 1 - 1
src/main/frontend/search.cljs

@@ -55,7 +55,7 @@
                              (p/let [result (db-async/<get-all-templates repo)]
                                (vals result)))]
            (when (seq templates)
-             (let [extract-fn (if db-based? :block/title :template)]
+             (let [extract-fn :block/title]
                (fuzzy/fuzzy-search templates q {:limit limit
                                                 :extract-fn extract-fn})))))))))
 

+ 8 - 4
src/main/frontend/state.cljs

@@ -1201,13 +1201,15 @@ Similar to re-frame subscriptions"
 
 (defn dom-clear-selection!
   []
-  (doseq [node (dom/by-class "ls-block selected")]
+  (doseq [node (dom/by-class "selected")]
     (dom/remove-class! node "selected")))
 
 (defn mark-dom-blocks-as-selected
   [nodes]
   (doseq [node nodes]
-    (dom/add-class! node "selected")))
+    (dom/add-class! node "selected")
+    (when (dom/has-class? node "ls-table-row")
+      (.focus node))))
 
 (defn get-events-chan
   []
@@ -1230,8 +1232,10 @@ Similar to re-frame subscriptions"
         removed (set/difference selected-ids new-ids)]
     (mark-dom-blocks-as-selected blocks)
     (doseq [id removed]
-      (doseq [node (array-seq (gdom/getElementsByClass (str "id" id)))]
-        (dom/remove-class! node "selected")))))
+      (doseq [node (dom/sel (util/format "[blockid='%s']" id))]
+        (dom/remove-class! node "selected")
+        (when (dom/has-class? node "ls-table-row")
+          (.blur node))))))
 
 (defn set-selection-blocks!
   ([blocks]

+ 10 - 11
src/main/frontend/ui.cljs

@@ -104,8 +104,6 @@
   {:did-mount (fn [state]
                 (let [^js el (rum/dom-node state)
                       *mouse-point (volatile! nil)]
-                  ;; Passing aria-label as a prop to TextareaAutosize removes the dash
-                  (.setAttribute el "aria-label" "editing block")
                   (doto el
                     (.addEventListener "select"
                                        #(let [start (util/get-selection-start el)
@@ -136,6 +134,7 @@
                                                 (on-change e))
                              (state/set-editor-in-composition! true))))
         props (assoc props
+                     "data-testid" "block editor"
                      :on-change (fn [e] (when-not (state/editor-in-composition?)
                                           (on-change e)))
                      :on-composition-start on-composition
@@ -709,7 +708,7 @@
 (rum/defc block-error
   "Well styled error message for blocks"
   [title {:keys [content section-attrs]}]
-  [:section.border.mt-1.p-1.cursor-pointer.block-content-fallback-ui
+  [:section.border.mt-1.p-1.cursor-pointer.block-content-fallback-ui.w-full
    section-attrs
    [:div.flex.justify-between.items-center.px-1
     [:h5.text-error.pb-1 title]
@@ -876,8 +875,8 @@
         enabled-tooltip? (state/enable-tooltip?)]
     (if (and enabled-tooltip? shortcut-tooltip?)
       (tooltip content
-        [:div.text-sm.font-medium (keyboard-shortcut-from-config shortcut-key)]
-        {:trigger-props {:as-child true}})
+               [:div.text-sm.font-medium (keyboard-shortcut-from-config shortcut-key)]
+               {:trigger-props {:as-child true}})
       content)))
 
 (rum/defc progress-bar
@@ -977,12 +976,12 @@
 (rum/defc tooltip
   [trigger tooltip-content & {:keys [portal? root-props trigger-props content-props]}]
   (shui/tooltip-provider
-    (shui/tooltip root-props
-      (shui/tooltip-trigger (merge {:as-child true} trigger-props) trigger)
-      (if (not (false? portal?))
-        (shui/tooltip-portal
-          (shui/tooltip-content content-props tooltip-content))
-        (shui/tooltip-content content-props tooltip-content)))))
+   (shui/tooltip root-props
+                 (shui/tooltip-trigger (merge {:as-child true} trigger-props) trigger)
+                 (if (not (false? portal?))
+                   (shui/tooltip-portal
+                    (shui/tooltip-content content-props tooltip-content))
+                   (shui/tooltip-content content-props tooltip-content)))))
 
 (rum/defc DelDateButton
   [on-delete]

+ 11 - 7
src/main/frontend/util.cljc

@@ -828,9 +828,10 @@
 #?(:cljs
    (defn react
      [ref]
-     (if rum/*reactions*
-       (rum/react ref)
-       @ref)))
+     (when ref
+       (if rum/*reactions*
+         (rum/react ref)
+         @ref))))
 
 #?(:cljs
    (def time-ms common-util/time-ms))
@@ -992,13 +993,16 @@
 (defonce linux? #?(:cljs goog.userAgent/LINUX
                    :clj nil))
 
+#?(:cljs
+   (defn get-blocks-by-id
+     [block-id]
+     (when (uuid-string? (str block-id))
+       (d/sel (format "[blockid='%s']" (str block-id))))))
+
 #?(:cljs
    (defn get-first-block-by-id
      [block-id]
-     (when block-id
-       (let [block-id (str block-id)]
-         (when (uuid-string? block-id)
-           (first (array-seq (js/document.getElementsByClassName (str "id" block-id)))))))))
+     (first (get-blocks-by-id block-id))))
 
 #?(:cljs
    (defn url-encode

+ 24 - 23
src/main/frontend/util/cursor.cljs

@@ -209,29 +209,30 @@
   (textarea-cursor-rect-last-row? (get-caret-pos input)))
 
 (defn- next-cursor-pos-up-down [direction cursor]
-  (let [elms  (-> (gdom/getElement "mock-text")
-                  gdom/getChildren
-                  array-seq)
-        chars' (->> elms
-                    (map mock-char-pos)
-                    (group-by :top))
-        tops  (sort (keys chars'))
-        tops-p (partition-by #(== (:top cursor) %) tops)
-        line-next
-        (if (= :up direction)
-          (-> tops-p first last)
-          (-> tops-p last first))
-        lefts
-        (->> (get chars' line-next)
-             (partition-by (fn [char-pos]
-                             (<= (:left char-pos) (:left cursor)))))
-        left-a (-> lefts first last)
-        left-c (-> lefts last first)
-        closer'
-        (if (> 2 (count lefts))
-          left-a
-          (closer left-a cursor left-c))]
-    (:pos closer')))
+  (when-let [mock-text (gdom/getElement "mock-text")]
+    (let [elms  (-> mock-text
+                    gdom/getChildren
+                    array-seq)
+          chars' (->> elms
+                      (map mock-char-pos)
+                      (group-by :top))
+          tops  (sort (keys chars'))
+          tops-p (partition-by #(== (:top cursor) %) tops)
+          line-next
+          (if (= :up direction)
+            (-> tops-p first last)
+            (-> tops-p last first))
+          lefts
+          (->> (get chars' line-next)
+               (partition-by (fn [char-pos]
+                               (<= (:left char-pos) (:left cursor)))))
+          left-a (-> lefts first last)
+          left-c (-> lefts last first)
+          closer'
+          (if (> 2 (count lefts))
+            left-a
+            (closer left-a cursor left-c))]
+      (:pos closer'))))
 
 (defn- move-cursor-up-down
   [input direction]

+ 20 - 20
src/main/frontend/worker/commands.cljs

@@ -15,7 +15,7 @@
   (atom
    [[:repeated-task
      {:title "Repeated task"
-      :entity-conditions [{:property :logseq.task/repeated?
+      :entity-conditions [{:property :logseq.property.repeat/repeated?
                            :value true}]
       :tx-conditions [{:property :status
                        :value :done}]
@@ -32,8 +32,8 @@
   [entity property]
   (if (= property :status)
     (or
-     (:db/ident (:logseq.task/recur-status-property entity))
-     :logseq.task/status)
+     (:db/ident (:logseq.property.repeat/checked-property entity))
+     :logseq.property/status)
     property))
 
 (defn- get-value
@@ -41,7 +41,7 @@
   (cond
     (and (= property :status) (= value :done))
     (or
-     (let [p (:logseq.task/recur-status-property entity)
+     (let [p (:logseq.property.repeat/checked-property entity)
            choices (:property/closed-values p)
            checkbox? (= :checkbox (:logseq.property/type p))]
        (if checkbox?
@@ -49,10 +49,10 @@
          (some (fn [choice]
                  (when (:logseq.property/choice-checkbox-state choice)
                    (:db/id choice))) choices)))
-     :logseq.task/status.done)
+     :logseq.property/status.done)
     (and (= property :status) (= value :todo))
     (or
-     (let [p (:logseq.task/recur-status-property entity)
+     (let [p (:logseq.property.repeat/checked-property entity)
            choices (:property/closed-values p)
            checkbox? (= :checkbox (:logseq.property/type p))]
        (if checkbox?
@@ -60,7 +60,7 @@
          (some (fn [choice]
                  (when (false? (:logseq.property/choice-checkbox-state choice))
                    (:db/id choice))) choices)))
-     :logseq.task/status.todo)
+     :logseq.property/status.todo)
     :else
     value))
 
@@ -130,30 +130,30 @@
   (let [current-date-time (tc/to-date-time current-value)
         default-timezone-time (t/to-default-time-zone current-date-time)
         [recur-unit period-f] (case (:db/ident unit)
-                                :logseq.task/recur-unit.minute [t/minutes t/in-minutes]
-                                :logseq.task/recur-unit.hour [t/hours t/in-hours]
-                                :logseq.task/recur-unit.day [t/days t/in-days]
-                                :logseq.task/recur-unit.week [t/weeks t/in-weeks]
-                                :logseq.task/recur-unit.month [t/months t/in-months]
-                                :logseq.task/recur-unit.year [t/years t/in-years]
+                                :logseq.property.repeat/recur-unit.minute [t/minutes t/in-minutes]
+                                :logseq.property.repeat/recur-unit.hour [t/hours t/in-hours]
+                                :logseq.property.repeat/recur-unit.day [t/days t/in-days]
+                                :logseq.property.repeat/recur-unit.week [t/weeks t/in-weeks]
+                                :logseq.property.repeat/recur-unit.month [t/months t/in-months]
+                                :logseq.property.repeat/recur-unit.year [t/years t/in-years]
                                 nil)]
     (when recur-unit
       (let [delta (recur-unit frequency)
             next-time (case (:db/ident unit)
-                        :logseq.task/recur-unit.year
+                        :logseq.property.repeat/recur-unit.year
                         (repeat-until-future-timestamp default-timezone-time recur-unit frequency period-f false)
-                        :logseq.task/recur-unit.month
+                        :logseq.property.repeat/recur-unit.month
                         (repeat-until-future-timestamp default-timezone-time recur-unit frequency period-f false)
-                        :logseq.task/recur-unit.week
+                        :logseq.property.repeat/recur-unit.week
                         (repeat-until-future-timestamp default-timezone-time recur-unit frequency period-f true)
                         (t/plus (t/now) delta))]
         (tc/to-long next-time)))))
 
 (defmethod handle-command :reschedule [_ db entity _datoms]
-  (let [property-ident (or (:db/ident (:logseq.task/scheduled-on-property entity))
-                           :logseq.task/scheduled)
-        frequency (db-property/property-value-content (:logseq.task/recur-frequency entity))
-        unit (:logseq.task/recur-unit entity)
+  (let [property-ident (or (:db/ident (:logseq.property.repeat/temporal-property entity))
+                           :logseq.property/scheduled)
+        frequency (db-property/property-value-content (:logseq.property.repeat/recur-frequency entity))
+        unit (:logseq.property.repeat/recur-unit entity)
         property (d/entity db property-ident)
         date? (= :date (:logseq.property/type property))
         current-value (cond->

+ 165 - 9
src/main/frontend/worker/db/migrate.cljs

@@ -109,18 +109,98 @@
                               [:db/add id new prop-value]]))))
             old-new-props)))
 
+(defn- rename-properties-aux
+  [db props-to-rename]
+  (let [property-tx (map
+                     (fn [[old new]]
+                       (merge {:db/id (:db/id (d/entity db old))
+                               :db/ident new}
+                              (when-let [new-title (get-in db-property/built-in-properties [new :title])]
+                                {:block/title new-title
+                                 :block/name (common-util/page-name-sanity-lc new-title)})))
+                     props-to-rename)
+        titles-tx (->> (d/datoms db :avet :block/title)
+                       (keep (fn [d]
+                               (when-let [props (seq (filter (fn [[old _new]] (string/includes? (:v d) (str old))) props-to-rename))]
+                                 (let [title' (reduce (fn [title [old new]]
+                                                        (string/replace title (str old) (str new))) (:v d) props)]
+                                   [:db/add (:e d) :block/title title'])))))
+        sorting-tx (->> (d/datoms db :avet :logseq.property.table/sorting)
+                        (keep (fn [d]
+                                (when-let [props (seq (filter (fn [[old _new]]
+                                                                (some (fn [item] (= old (:id item))) (:v d))) props-to-rename))]
+                                  (let [value (reduce
+                                               (fn [sorting [old new]]
+                                                 (mapv
+                                                  (fn [item]
+                                                    (if (= old (:id item))
+                                                      (assoc item :id new)
+                                                      item))
+                                                  sorting))
+                                               (:v d)
+                                               props)]
+                                    [:db/add (:e d) :logseq.property.table/sorting value])))))
+        sized-columns-tx (->> (d/datoms db :avet :logseq.property.table/sized-columns)
+                              (keep (fn [d]
+                                      (when-let [props (seq (filter (fn [[old _new]] (get (:v d) old)) props-to-rename))]
+                                        (let [value (reduce
+                                                     (fn [sizes [old new]]
+                                                       (if-let [size (get sizes old)]
+                                                         (-> sizes
+                                                             (dissoc old)
+                                                             (assoc new size))
+                                                         sizes))
+                                                     (:v d)
+                                                     props)]
+                                          [:db/add (:e d) :logseq.property.table/sized-columns value])))))
+        hidden-columns-tx (mapcat
+                           (fn [[old new]]
+                             (->> (d/datoms db :avet :logseq.property.table/hidden-columns old)
+                                  (mapcat (fn [d]
+                                            [[:db/retract (:e d) :logseq.property.table/hidden-columns old]
+                                             [:db/add (:e d) :logseq.property.table/hidden-columns new]]))))
+                           props-to-rename)
+        ordered-columns-tx (->> (d/datoms db :avet :logseq.property.table/ordered-columns)
+                                (keep (fn [d]
+                                        (when-let [props (seq (filter (fn [[old _new]] ((set (:v d)) old)) props-to-rename))]
+                                          (let [value (reduce
+                                                       (fn [col [old new]]
+                                                         (mapv (fn [v] (if (= old v) new v)) col))
+                                                       (:v d)
+                                                       props)]
+                                            [:db/add (:e d) :logseq.property.table/ordered-columns value])))))
+        filters-tx (->> (d/datoms db :avet :logseq.property.table/filters)
+                        (keep (fn [d]
+                                (let [filters (:filters (:v d))]
+                                  (when-let [props (seq (filter (fn [[old _new]]
+                                                                  (some (fn [item] (and (vector? item)
+                                                                                        (= old (first item)))) filters)) props-to-rename))]
+                                    (let [value (update (:v d) :filters
+                                                        (fn [col]
+                                                          (reduce
+                                                           (fn [col [old new]]
+                                                             (mapv (fn [item]
+                                                                     (if (and (vector? item) (= old (first item)))
+                                                                       (vec (cons new (rest item)))
+                                                                       item))
+                                                                   col))
+                                                           col
+                                                           props)))]
+                                      [:db/add (:e d) :logseq.property.table/filters value]))))))]
+    (concat property-tx
+            titles-tx
+            sorting-tx
+            sized-columns-tx
+            hidden-columns-tx
+            ordered-columns-tx
+            filters-tx)))
+
 (defn- rename-properties
   [props-to-rename]
   (fn [conn _search-db]
     (when (ldb/db-based-graph? @conn)
-      (let [props-tx (mapv (fn [[old new]]
-                             (merge {:db/id (:db/id (d/entity @conn old))
-                                     :db/ident new}
-                                    (when-let [new-title (get-in db-property/built-in-properties [new :title])]
-                                      {:block/title new-title
-                                       :block/name (common-util/page-name-sanity-lc new-title)})))
-                           props-to-rename)]
-       ;; Property changes need to be in their own tx for subsequent uses of properties to take effect
+      (let [props-tx (rename-properties-aux @conn props-to-rename)]
+        ;; Property changes need to be in their own tx for subsequent uses of properties to take effect
         (ldb/transact! conn props-tx {:db-migrate? true})
 
         (mapcat (fn [[old new]]
@@ -684,6 +764,80 @@
       block-ids)
      (remove nil?))))
 
+(defn- rename-repeated-properties
+  [conn search-db]
+  (when (ldb/db-based-graph? @conn)
+    (let [closed-values-tx (mapv (fn [[old new]]
+                                   {:db/id (:db/id (d/entity @conn old))
+                                    :db/ident new})
+                                 {:logseq.task/recur-unit.minute :logseq.property.repeat/recur-unit.minute
+                                  :logseq.task/recur-unit.hour :logseq.property.repeat/recur-unit.hour
+                                  :logseq.task/recur-unit.day :logseq.property.repeat/recur-unit.day
+                                  :logseq.task/recur-unit.week :logseq.property.repeat/recur-unit.week
+                                  :logseq.task/recur-unit.month :logseq.property.repeat/recur-unit.month
+                                  :logseq.task/recur-unit.year :logseq.property.repeat/recur-unit.year})]
+      (ldb/transact! conn closed-values-tx {:db-migrate? true})))
+
+  ;; This needs to be last as the returned tx are used
+  ((rename-properties {:logseq.task/recur-frequency :logseq.property.repeat/recur-frequency
+                       :logseq.task/recur-unit :logseq.property.repeat/recur-unit
+                       :logseq.task/repeated? :logseq.property.repeat/repeated?
+                       :logseq.task/scheduled-on-property :logseq.property.repeat/temporal-property
+                       :logseq.task/recur-status-property :logseq.property.repeat/checked-property})
+   conn search-db))
+
+(defn- rename-task-properties
+  [conn search-db]
+  (when (ldb/db-based-graph? @conn)
+    (let [db @conn
+          new-idents {:logseq.task/status.backlog :logseq.property/status.backlog
+                      :logseq.task/status.todo :logseq.property/status.todo
+                      :logseq.task/status.doing :logseq.property/status.doing
+                      :logseq.task/status.in-review :logseq.property/status.in-review
+                      :logseq.task/status.done :logseq.property/status.done
+                      :logseq.task/status.canceled :logseq.property/status.canceled
+                      :logseq.task/priority.low :logseq.property/priority.low
+                      :logseq.task/priority.medium :logseq.property/priority.medium
+                      :logseq.task/priority.high :logseq.property/priority.high
+                      :logseq.task/priority.urgent :logseq.property/priority.urgent}
+          closed-values-tx (mapv (fn [[old new]]
+                                   {:db/id (:db/id (d/entity @conn old))
+                                    :db/ident new})
+                                 new-idents)
+          filters-tx (->> (d/datoms db :avet :logseq.property.table/filters)
+                          (keep (fn [d]
+                                  (let [filters (:filters (:v d))]
+                                    (when (some (fn [item]
+                                                  (and (vector? item) (contains? #{:logseq.task/status :logseq.task/priority}
+                                                                                 (first item)))) filters)
+                                      (let [value (update (:v d) :filters
+                                                          (fn [col]
+                                                            (reduce
+                                                             (fn [col property]
+                                                               (mapv (fn [item]
+                                                                       (if (and (vector? item) (= property (first item)))
+                                                                         (let [[p o v] item
+                                                                               f (fn [id]
+                                                                                   (let [new-ident (get new-idents (:db/ident (d/entity db [:block/uuid id])))]
+                                                                                     (common-uuid/gen-uuid :db-ident-block-uuid new-ident)))
+                                                                               v' (if (set? v)
+                                                                                    (set (map f v))
+                                                                                    (f v))]
+                                                                           [p o v'])
+                                                                         item))
+                                                                     col))
+                                                             col
+                                                             [:logseq.task/status :logseq.task/priority])))]
+                                        [:db/add (:e d) :logseq.property.table/filters value]))))))]
+      (ldb/transact! conn (concat closed-values-tx filters-tx) {:db-migrate? true})))
+
+  ;; This needs to be last as the returned tx are used
+  ((rename-properties {:logseq.task/status :logseq.property/status
+                       :logseq.task/priority :logseq.property/priority
+                       :logseq.task/deadline :logseq.property/deadline
+                       :logseq.task/scheduled :logseq.property/scheduled})
+   conn search-db))
+
 (def ^:large-vars/cleanup-todo schema-version->updates
   "A vec of tuples defining datascript migrations. Each tuple consists of the
    schema version integer and a migration map. A migration map can have keys of :properties, :classes
@@ -793,7 +947,9 @@
             :classes [:logseq.class/Template]}]
    ["64.4" {:properties [:logseq.property/created-by-ref]}]
    ["64.5" {:fix add-group-by-property-for-list-views}]
-   ["64.6" {:fix cardinality-one-multiple-values}]])
+   ["64.6" {:fix cardinality-one-multiple-values}]
+   ["64.7" {:fix rename-repeated-properties}]
+   ["64.8" {:fix rename-task-properties}]])
 
 (let [[major minor] (last (sort (map (comp (juxt :major :minor) db-schema/parse-schema-version first)
                                      schema-version->updates)))

+ 11 - 8
src/main/frontend/worker/db_worker.cljs

@@ -552,8 +552,10 @@
              (or (= id (:db/id (:logseq.property/view-for ref)))
                  (ldb/hidden? (:block/page ref))
                  (ldb/hidden? ref)
-                 (contains? (set (map :db/id (:block/tags ref))) id)
-                 (some? (get ref (:db/ident block))))))
+                 (and db-based? (contains? (set (map :db/id (:block/tags ref))) id))
+                 (some? (get ref (:db/ident block)))
+                 (= id (:db/id ref))
+                 (= id (:db/id (:block/page ref))))))
           (:block/_refs block)))))))
 
 (def-thread-api :thread-api/get-block-parents
@@ -836,12 +838,13 @@
            (js/console.error (str "DB is not found for " repo))))))))
 
 (defn- on-become-master
-  [repo]
+  [repo config import?]
   (js/Promise.
    (m/sp
      (c.m/<? (init-sqlite-module!))
-     (c.m/<? (start-db! repo {}))
-     (assert (some? (worker-state/get-datascript-conn repo)))
+     (when-not import?
+       (c.m/<? (start-db! repo {:config config}))
+       (assert (some? (worker-state/get-datascript-conn repo))))
      (m/? (rtc.core/new-task--rtc-start true)))))
 
 (def broadcast-data-types
@@ -855,7 +858,7 @@
          :rtc-sync-state])))
 
 (defn- <init-service!
-  [graph import?]
+  [graph config import?]
   (let [[prev-graph service] @*service]
     (some-> prev-graph close-db!)
     (when graph
@@ -863,7 +866,7 @@
         service
         (p/let [service (shared-service/<create-service graph
                                                         (bean/->js fns)
-                                                        #(on-become-master graph)
+                                                        #(on-become-master graph config import?)
                                                         broadcast-data-types
                                                         {:import? import?})]
           (assert (p/promise? (get-in service [:status :ready])))
@@ -886,7 +889,7 @@
                                 ;; because shared-service operates at the graph level,
                                 ;; creating a new database or switching to another one requires re-initializing the service.
                                 (let [[graph opts] (ldb/read-transit-str (last args))]
-                                  (p/let [service (<init-service! graph (some? (:import-type opts)))]
+                                  (p/let [service (<init-service! graph (:config opts) (some? (:import-type opts)))]
                                     (get-in service [:status :ready])
                                     ;; wait for service ready
                                     (js-invoke (:proxy service) k args)))

+ 2 - 1
src/main/frontend/worker/export.cljs

@@ -73,6 +73,7 @@
                            (let [entity (d/entity @conn e)]
                              (and (not (:db/ident entity))
                                   (not (ldb/journal? entity))
-                                  (not (:logseq.property/built-in? entity)))))
+                                  (not (:logseq.property/built-in? entity))
+                                  (not (= :logseq.property/query (:db/ident (:logseq.property/created-from-property entity)))))))
                     (d/datom e a (str "debug " e) t)
                     (d/datom e a v t))))))

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

@@ -6,10 +6,10 @@
             [logseq.common.util :as common-util]
             [logseq.common.util.namespace :as ns-util]
             [logseq.db :as ldb]
+            [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.common.order :as db-order]
             [logseq.db.common.property-util :as db-property-util]
             [logseq.db.frontend.class :as db-class]
-            [logseq.db.common.entity-plus :as entity-plus]
             [logseq.db.frontend.entity-util :as entity-util]
             [logseq.db.frontend.malli-schema :as db-malli-schema]
             [logseq.db.frontend.property.build :as db-property-build]
@@ -168,13 +168,12 @@
 (defn create
   "Pure function without side effects"
   [db title*
-   {:keys [create-first-block? properties uuid persist-op? whiteboard?
-           class? today-journal? split-namespace? skip-existing-page-check?]
+   {:keys [create-first-block? tags properties uuid persist-op? whiteboard?
+           class? today-journal? split-namespace?]
     :or   {create-first-block?      true
            properties               nil
            uuid                     nil
-           persist-op?              true
-           skip-existing-page-check? false}
+           persist-op?              true}
     :as options}]
   (let [date-formatter (:logseq.property.journal/title-format (entity-plus/entity-memoized db :logseq.class/Journal))
         title (sanitize-title title*)
@@ -184,9 +183,12 @@
                     #{:logseq.class/Whiteboard}
                     today-journal?
                     #{:logseq.class/Journal}
+                    (seq tags)
+                    (set (map :db/ident tags))
                     :else
-                    #{:logseq.class/Page})]
-    (if-let [existing-page-id (first (ldb/page-exists? db title types))]
+                    #{:logseq.class/Page})
+        existing-page-id (first (ldb/page-exists? db title types))]
+    (if existing-page-id
       (let [existing-page (d/entity db existing-page-id)
             tx-meta {:persist-op? persist-op?
                      :outliner-op :save-block}]
@@ -202,9 +204,7 @@
       (let [page      (gp-block/page-name->map title db true date-formatter
                                                {:class? class?
                                                 :page-uuid (when (uuid? uuid) uuid)
-                                                :skip-existing-page-check? (if (some? skip-existing-page-check?)
-                                                                             skip-existing-page-check?
-                                                                             true)})
+                                                :skip-existing-page-check? true})
             [page parents] (if (and (text/namespace-page? title) split-namespace?)
                              (let [pages (split-namespace-pages db page date-formatter)]
                                [(last pages) (butlast pages)])

+ 5 - 3
src/main/frontend/worker/handler/page/file_based/rename.cljs

@@ -3,8 +3,8 @@
   (:require [clojure.string :as string]
             [clojure.walk :as walk]
             [datascript.core :as d]
-            [frontend.common.file.util :as wfu]
             [frontend.common.file-based.db :as common-file-db]
+            [frontend.common.file.util :as wfu]
             [frontend.worker.handler.page :as worker-page]
             [logseq.common.config :as common-config]
             [logseq.common.util :as common-util]
@@ -243,7 +243,8 @@
 
         (ldb/transact! conn txs {:outliner-op :rename-page
                                  :data (cond->
-                                        {:old-name old-name
+                                        {:page-id (:db/id page)
+                                         :old-name old-name
                                          :new-name new-name}
                                          (and old-path new-path)
                                          (merge {:old-path old-path
@@ -329,7 +330,8 @@
           (ldb/transact! conn
                          [{:db/id (:db/id page-e)
                            :block/title new-name}]
-                         {:persist-op? persist-op?
+                         {:page-id (:db/id page-e)
+                          :persist-op? persist-op?
                           :outliner-op :rename-page})
 
           (and (not= old-page-name new-page-name)

+ 1 - 1
src/main/frontend/worker/react.cljs

@@ -15,7 +15,7 @@
 ;; ::refs
 ;; get BLOCKS referencing PAGE or BLOCK
 (s/def ::refs (s/tuple #(= ::refs %) int?))
-;; get class's Objects
+;; get class's objects
 (s/def ::objects (s/tuple #(= ::objects %) int?))
 ;; custom react-query
 (s/def ::custom any?)

+ 5 - 5
src/rtc_e2e_test/client_steps.cljs

@@ -112,14 +112,14 @@
       (let [conn (helper/get-downloaded-test-conn)
             block1 (d/pull @conn
                            [{:block/tags [:db/ident]}
-                            {:logseq.task/status [:db/ident]}
-                            {:logseq.task/deadline [:block/journal-day]}]
+                            {:logseq.property/status [:db/ident]}
+                            {:logseq.property/deadline [:block/journal-day]}]
                            [:block/uuid const/block1-uuid])]
-        (when-not (= :logseq.task/status.doing (:db/ident (:logseq.task/status block1)))
+        (when-not (= :logseq.property/status.doing (:db/ident (:logseq.property/status block1)))
           (throw (ex-info "wait block1's task properties to be synced" {:missionary/retry true})))
         (is (= {:block/tags [{:db/ident :logseq.class/Task}],
-                :logseq.task/status {:db/ident :logseq.task/status.doing}
-                :logseq.task/deadline {:block/journal-day 20240907}}
+                :logseq.property/status {:db/ident :logseq.property/status.doing}
+                :logseq.property/deadline {:block/journal-day 20240907}}
                block1)))))})
 (def ^:private step4
   "client1:

+ 4 - 4
src/rtc_e2e_test/const.cljs

@@ -85,14 +85,14 @@
     {:block/uuid block1-uuid
      :block/updated-at 1725454876718
      :block/tags :logseq.class/Task
-     :logseq.task/status :logseq.task/status.done
-     :logseq.task/deadline "id-0907"}]
+     :logseq.property/status :logseq.property/status.done
+     :logseq.property/deadline "id-0907"}]
    :step3-toggle-status-TODO
    [{:block/uuid block1-uuid
-     :logseq.task/status :logseq.task/status.todo}]
+     :logseq.property/status :logseq.property/status.todo}]
    :step3-toggle-status-DOING
    [{:block/uuid block1-uuid
-     :logseq.task/status :logseq.task/status.doing}]
+     :logseq.property/status :logseq.property/status.doing}]
    :move-blocks-concurrently-1
    [{:db/id "page"
      :block/uuid page3-uuid

File diff suppressed because it is too large
+ 0 - 0
src/rtc_e2e_test/example.cljs


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

@@ -447,11 +447,11 @@ prop-d:: [[nada]]"}])
   (load-test-files (if js/process.env.DB_GRAPH
                      [{:page {:block/title "page1"}
                        :blocks [{:block/title "[#A] b1"
-                                 :build/properties {:logseq.task/priority :logseq.task/priority.high}}
+                                 :build/properties {:logseq.property/priority :logseq.property/priority.high}}
                                 {:block/title "[#B] b2"
-                                 :build/properties {:logseq.task/priority :logseq.task/priority.medium}}
+                                 :build/properties {:logseq.property/priority :logseq.property/priority.medium}}
                                 {:block/title "[#A] b3"
-                                 :build/properties {:logseq.task/priority :logseq.task/priority.high}}]}]
+                                 :build/properties {:logseq.property/priority :logseq.property/priority.high}}]}]
 
                      [{:file/path "pages/page1.md"
                        :file/content "foo:: bar

+ 11 - 11
src/test/frontend/test/helper.cljs

@@ -81,16 +81,16 @@
       (assoc :block/created-at (:created-at props)))))
 
 (def file-to-db-statuses
-  {"TODO" :logseq.task/status.todo
-   "LATER" :logseq.task/status.todo
-   "IN-PROGRESS" :logseq.task/status.doing
-   "NOW" :logseq.task/status.doing
-   "DOING" :logseq.task/status.doing
-   "DONE" :logseq.task/status.done
-   "WAIT" :logseq.task/status.backlog
-   "WAITING" :logseq.task/status.backlog
-   "CANCELED" :logseq.task/status.canceled
-   "CANCELLED" :logseq.task/status.canceled})
+  {"TODO" :logseq.property/status.todo
+   "LATER" :logseq.property/status.todo
+   "IN-PROGRESS" :logseq.property/status.doing
+   "NOW" :logseq.property/status.doing
+   "DOING" :logseq.property/status.doing
+   "DONE" :logseq.property/status.done
+   "WAIT" :logseq.property/status.backlog
+   "WAITING" :logseq.property/status.backlog
+   "CANCELED" :logseq.property/status.canceled
+   "CANCELLED" :logseq.property/status.canceled})
 
 (defn- parse-content
   "Given a file's content as markdown, returns blocks and page attributes for the file
@@ -117,7 +117,7 @@
                                        file-to-db-statuses)]
                  (-> %
                      (assoc :block/tags [{:db/ident :logseq.class/Task}])
-                     (update :build/properties merge {:logseq.task/status status}))
+                     (update :build/properties merge {:logseq.property/status status}))
                  %)
               blocks*)]
     {:blocks (mapv (fn [b] (update b :block/title #(string/replace-first % #"^-\s*" "")))

Some files were not shown because too many files changed in this diff