Kaynağa Gözat

Merge pull request #11774 from logseq/perf/app-start

perf: app start time && large table
Tienson Qin 6 ay önce
ebeveyn
işleme
fb724ca130
100 değiştirilmiş dosya ile 3608 ekleme ve 2888 silme
  1. 9 4
      .clj-kondo/config.edn
  2. 2 1
      .cljfmt.edn
  3. 1 1
      deps/common/package.json
  4. 1 2
      deps/common/src/logseq/common/defkeywords.cljc
  5. 25 4
      deps/common/src/logseq/common/util.cljs
  6. 3 3
      deps/common/yarn.lock
  7. 4 0
      deps/db/.carve/ignore
  8. 1 0
      deps/db/.clj-kondo/config.edn
  9. 1 1
      deps/db/bb.edn
  10. 1 1
      deps/db/package.json
  11. 0 1
      deps/db/script/create_graph.cljs
  12. 86 39
      deps/db/src/logseq/db.cljs
  13. 15 1
      deps/db/src/logseq/db/common/property_util.cljs
  14. 161 114
      deps/db/src/logseq/db/common/sqlite.cljs
  15. 577 0
      deps/db/src/logseq/db/common/view.cljs
  16. 1 1
      deps/db/src/logseq/db/file_based/rules.cljc
  17. 14 0
      deps/db/src/logseq/db/frontend/class.cljs
  18. 1 1
      deps/db/src/logseq/db/frontend/content.cljs
  19. 6 6
      deps/db/src/logseq/db/frontend/entity_plus.cljc
  20. 5 0
      deps/db/src/logseq/db/frontend/entity_util.cljs
  21. 1 1
      deps/db/src/logseq/db/frontend/schema.cljs
  22. 1 1
      deps/db/src/logseq/db/sqlite/export.cljs
  23. 20 34
      deps/db/test/logseq/db/common/sqlite_test.cljs
  24. 3 3
      deps/db/yarn.lock
  25. 1 1
      deps/graph-parser/package.json
  26. 6 6
      deps/graph-parser/src/logseq/graph_parser/exporter.cljs
  27. 2 2
      deps/graph-parser/test/logseq/graph_parser/exporter_test.cljs
  28. 3 3
      deps/graph-parser/yarn.lock
  29. 1 1
      deps/outliner/package.json
  30. 6 4
      deps/outliner/src/logseq/outliner/core.cljs
  31. 2 2
      deps/outliner/src/logseq/outliner/tree.cljs
  32. 3 3
      deps/outliner/yarn.lock
  33. 1 1
      deps/publishing/package.json
  34. 3 3
      deps/publishing/yarn.lock
  35. 18 1
      deps/shui/src/logseq/shui/hooks.cljs
  36. 13 14
      deps/shui/src/logseq/shui/table/core.cljc
  37. 0 41
      deps/shui/src/logseq/shui/table/impl.cljc
  38. 1 0
      packages/ui/src/colors.css
  39. 1 1
      scripts/package.json
  40. 1 1
      scripts/src/logseq/tasks/db_graph/create_graph_with_properties.cljs
  41. 3 3
      scripts/yarn.lock
  42. 48 48
      src/electron/electron/core.cljs
  43. 6 6
      src/main/electron/listener.cljs
  44. 1 1
      src/main/frontend/common.css
  45. 19 0
      src/main/frontend/common/cache.cljs
  46. 3 3
      src/main/frontend/common/file/core.cljs
  47. 240 0
      src/main/frontend/common/graph_view.cljs
  48. 19 5
      src/main/frontend/common/missionary.cljs
  49. 5 1
      src/main/frontend/common/thread_api.cljc
  50. 7 34
      src/main/frontend/components/all_pages.cljs
  51. 389 311
      src/main/frontend/components/block.cljs
  52. 1 1
      src/main/frontend/components/block.css
  53. 6 7
      src/main/frontend/components/class.cljs
  54. 15 18
      src/main/frontend/components/cmdk/core.cljs
  55. 8 4
      src/main/frontend/components/container.cljs
  56. 1 1
      src/main/frontend/components/content.cljs
  57. 22 22
      src/main/frontend/components/editor.cljs
  58. 42 35
      src/main/frontend/components/export.cljs
  59. 4 4
      src/main/frontend/components/file_based/hierarchy.cljs
  60. 4 4
      src/main/frontend/components/file_based/query.cljs
  61. 3 3
      src/main/frontend/components/header.cljs
  62. 0 1
      src/main/frontend/components/imports.cljs
  63. 34 39
      src/main/frontend/components/journal.cljs
  64. 3 3
      src/main/frontend/components/journal.css
  65. 31 20
      src/main/frontend/components/lazy_editor.cljs
  66. 36 118
      src/main/frontend/components/objects.cljs
  67. 186 179
      src/main/frontend/components/page.cljs
  68. 2 2
      src/main/frontend/components/page_menu.cljs
  69. 31 4
      src/main/frontend/components/profiler.cljs
  70. 4 20
      src/main/frontend/components/property.cljs
  71. 36 46
      src/main/frontend/components/property/config.cljs
  72. 201 199
      src/main/frontend/components/property/value.cljs
  73. 59 65
      src/main/frontend/components/query.cljs
  74. 44 53
      src/main/frontend/components/query/builder.cljs
  75. 21 17
      src/main/frontend/components/query/result.cljs
  76. 17 39
      src/main/frontend/components/query/view.cljs
  77. 86 246
      src/main/frontend/components/reference.cljs
  78. 10 9
      src/main/frontend/components/reference_filters.cljs
  79. 174 150
      src/main/frontend/components/right_sidebar.cljs
  80. 15 16
      src/main/frontend/components/rtc/indicator.cljs
  81. 69 50
      src/main/frontend/components/select.cljs
  82. 5 6
      src/main/frontend/components/selection.cljs
  83. 12 1
      src/main/frontend/components/table.css
  84. 468 448
      src/main/frontend/components/views.cljs
  85. 8 0
      src/main/frontend/components/views.css
  86. 21 19
      src/main/frontend/components/whiteboard.cljs
  87. 3 4
      src/main/frontend/db.cljs
  88. 60 95
      src/main/frontend/db/async.cljs
  89. 4 3
      src/main/frontend/db/async/util.cljs
  90. 39 147
      src/main/frontend/db/model.cljs
  91. 5 4
      src/main/frontend/db/query_react.cljs
  92. 21 11
      src/main/frontend/db/react.cljs
  93. 1 6
      src/main/frontend/db/restore.cljs
  94. 11 13
      src/main/frontend/db/rtc/debug_ui.cljs
  95. 14 11
      src/main/frontend/extensions/code.cljs
  96. 1 5
      src/main/frontend/extensions/code.css
  97. 4 3
      src/main/frontend/extensions/fsrs.cljs
  98. 1 1
      src/main/frontend/extensions/handbooks/core.cljs
  99. 21 18
      src/main/frontend/extensions/pdf/assets.cljs
  100. 3 3
      src/main/frontend/extensions/pdf/core.cljs

+ 9 - 4
.clj-kondo/config.edn

@@ -1,10 +1,17 @@
-{:ns-groups [{:pattern "frontend.components.*" :name all-components}]
+{:ns-groups [{:pattern "frontend.components.*" :name all-components}
+             {:pattern "frontend.*" :name all-frontend}]
 
  :config-in-ns
  ;; :used-underscored-binding is turned off for components because of false positive
  ;; for rum/defcs and _state.
  {all-components
   {:linters {:used-underscored-binding {:level :off}}}
+
+  all-frontend
+  {:linters {:discouraged-namespace
+             {logseq.db.sqlite.cli {:message "frontend should not depend on CLI namespace with sqlite3 dependency"}
+              logseq.outliner.cli {:message "frontend should not depend on CLI namespace with sqlite3 dependency"}}}}
+
   ;; false positive with match/match and _
   frontend.handler.paste {:linters {:used-underscored-binding {:level :off}}}
   frontend.db {:linters {:aliased-namespace-symbol
@@ -32,9 +39,6 @@
                                 ;; TODO:lint: Fix when fixing all type hints
                                 object]}
 
-  :discouraged-namespace
-  {logseq.db.sqlite.cli {:message "frontend should not depend on CLI namespace with sqlite3 dependency"}
-   logseq.outliner.cli {:message "frontend should not depend on CLI namespace with sqlite3 dependency"}}
   :discouraged-var
   {rum.core/use-effect! {:message "Use frontend.hooks/use-effect! instead" :level :info}
    rum.core/use-memo {:message "Use frontend.hooks/use-memo instead" :level :info}
@@ -172,6 +176,7 @@
              logseq.db.common.order db-order
              logseq.db.common.property-util db-property-util
              logseq.db.common.sqlite sqlite-common-db
+             logseq.db.common.view db-view
              logseq.db.file-based.rules file-rules
              logseq.db.file-based.schema file-schema
              logseq.db.file-based.entity-util file-entity-util

+ 2 - 1
.cljfmt.edn

@@ -1,3 +1,4 @@
  {:extra-indents {missionary.core/sp [[:block 0]]
-                  missionary.core/ap [[:block 0]]}
+                  missionary.core/ap [[:block 0]]
+                  frontend.common.missionary/run-task [[:inner 0]]}
   :sort-ns-references? true}

+ 1 - 1
deps/common/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v18"
+    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v19"
   },
   "scripts": {
     "test": "yarn nbb-logseq -cp test -m nextjournal.test-runner"

+ 1 - 2
deps/common/src/logseq/common/defkeywords.cljc

@@ -37,5 +37,4 @@
 (comment
   "update anything here to trigger this ns to be recompiled,
 so macro get-all-defined-kw->config's result will be updated."
-  1
-  )
+  1)

+ 25 - 4
deps/common/src/logseq/common/util.cljs

@@ -1,14 +1,14 @@
 (ns logseq.common.util
   "Util fns shared between the app. Util fns only rely on
   clojure standard libraries."
-  (:require [cljs.reader :as reader]
+  (:require [cljs-time.coerce :as tc]
+            [cljs-time.core :as t]
+            [cljs.reader :as reader]
             [clojure.edn :as edn]
             [clojure.string :as string]
             [clojure.walk :as walk]
-            [logseq.common.log :as log]
             [goog.string :as gstring]
-            [cljs-time.coerce :as tc]
-            [cljs-time.core :as t]))
+            [logseq.common.log :as log]))
 
 (defn safe-decode-uri-component
   [uri]
@@ -364,3 +364,24 @@ return: [{:id 3} {:id 2 :depend-on 3} {:id 1 :depend-on 2}]"
                 (nil? (:block/created-at block))
                 (assoc :block/created-at updated-at))]
     block))
+
+(defn get-timestamp
+  [value]
+  (let [now (t/now)
+        f t/minus]
+    (if (string? value)
+      (case value
+        "1 day ago"
+        (tc/to-long (f now (t/days 1)))
+        "3 days ago"
+        (tc/to-long (f now (t/days 3)))
+        "1 week ago"
+        (tc/to-long (f now (t/weeks 1)))
+        "1 month ago"
+        (tc/to-long (f now (t/months 1)))
+        "3 months ago"
+        (tc/to-long (f now (t/months 3)))
+        "1 year ago"
+        (tc/to-long (f now (t/years 1)))
+        nil)
+      (tc/to-long (tc/to-date value)))))

+ 3 - 3
deps/common/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v18":
-  version "1.2.173-feat-db-v18"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/1cd15bf5beb77a1bc5c943a438681cb072eabf2c"
+"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v19":
+  version "1.2.173-feat-db-v19"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/ca32553da41aff783d89264b2e1a753372721f2e"
   dependencies:
     import-meta-resolve "^2.1.0"
 

+ 4 - 0
deps/db/.carve/ignore

@@ -24,3 +24,7 @@ logseq.db.sqlite.build/create-blocks
 logseq.db.sqlite.export/build-export
 ;; API
 logseq.db.sqlite.export/build-import
+;; API
+logseq.db.common.view/get-property-values
+;; API
+logseq.db.common.view/get-view-data

+ 1 - 0
deps/db/.clj-kondo/config.edn

@@ -13,6 +13,7 @@
              logseq.db.common.order db-order
              logseq.db.common.property-util db-property-util
              logseq.db.common.sqlite sqlite-common-db
+             logseq.db.common.view db-view
              logseq.db.frontend.content db-content
              logseq.db.frontend.class db-class
              logseq.db.frontend.db-ident db-ident

+ 1 - 1
deps/db/bb.edn

@@ -43,4 +43,4 @@
  :tasks/config
  {:large-vars
   {:max-lines-count 50
-   :metadata-exceptions #{:large-vars/doc-var}}}}
+   :metadata-exceptions #{:large-vars/doc-var :large-vars/cleanup-todo}}}}

+ 1 - 1
deps/db/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v18"
+    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v19"
   },
   "dependencies": {
     "better-sqlite3": "9.3.0"

+ 0 - 1
deps/db/script/create_graph.cljs

@@ -9,7 +9,6 @@
             [clojure.string :as string]
             [datascript.core :as d]
             [logseq.db.sqlite.export :as sqlite-export]
-            #_:clj-kondo/ignore
             [logseq.outliner.cli :as outliner-cli]
             [nbb.classpath :as cp]
             [nbb.core :as nbb]

+ 86 - 39
deps/db/src/logseq/db.cljs

@@ -232,12 +232,14 @@
 
 (defn get-page
   "Get a page given its unsanitized name"
-  [db page-name-or-uuid]
+  [db page-id-name-or-uuid]
   (when db
-    (if-let [id (if (uuid? page-name-or-uuid) page-name-or-uuid
-                    (parse-uuid page-name-or-uuid))]
-      (d/entity db [:block/uuid id])
-      (d/entity db (get-first-page-by-name db (name page-name-or-uuid))))))
+    (if (number? page-id-name-or-uuid)
+      (d/entity db page-id-name-or-uuid)
+      (if-let [id (if (uuid? page-id-name-or-uuid) page-id-name-or-uuid
+                      (parse-uuid page-id-name-or-uuid))]
+        (d/entity db [:block/uuid id])
+        (d/entity db (get-first-page-by-name db (name page-id-name-or-uuid)))))))
 
 (defn get-case-page
   "Case sensitive version of get-page. For use with DB graphs"
@@ -301,7 +303,8 @@
 
 (defn has-children?
   [db block-id]
-  (some? (:block/_parent (d/entity db [:block/uuid block-id]))))
+  (let [eid (if (uuid? block-id) [:block/uuid block-id] block-id)]
+    (some? (:block/_parent (d/entity db eid)))))
 
 (defn- collapsed-and-has-children?
   [db block]
@@ -435,15 +438,37 @@
     (:alias rules/rules))
    distinct))
 
+(defn page-alias-set
+  [db page-id]
+  (->>
+   (get-block-alias db page-id)
+   (set)
+   (set/union #{page-id})))
+
 (defn get-block-refs
   [db id]
-  (let [alias (->> (get-block-alias db id)
+  (let [entity (d/entity db id)
+        db-based? (db-based-graph? db)
+        alias (->> (get-block-alias db id)
                    (cons id)
                    distinct)
-        refs (->> (mapcat (fn [id] (:block/_path-refs (d/entity db id))) alias)
-                  distinct)]
-    (when (seq refs)
-      (d/pull-many db '[*] (map :db/id refs)))))
+        ref-ids (->> (mapcat (fn [id]
+                               (cond->> (:block/_refs (d/entity db id))
+                                 db-based?
+                                 (remove (fn [ref]
+                                           ;; remove refs that have the block as either tag or property
+                                           (or (and
+                                                (class? entity)
+                                                (d/datom db :eavt (:db/id ref) :block/tags (:db/id entity)))
+                                               (and
+                                                (property? entity)
+                                                (d/datom db :eavt (:db/id ref) (:db/ident entity))))))
+                                 true
+                                 (map :db/id)))
+                             alias)
+                     distinct)]
+    (when (seq ref-ids)
+      (d/pull-many db '[*] ref-ids))))
 
 (defn get-block-refs-count
   [db id]
@@ -451,21 +476,9 @@
           :block/_refs
           count))
 
-(defn get-page-unlinked-refs
-  "Get unlinked refs from search result"
-  [db page-id search-result-eids]
-  (let [alias (->> (get-block-alias db page-id)
-                   (cons page-id)
-                   set)
-        eids (remove
-              (fn [eid]
-                (when-let [e (d/entity db eid)]
-                  (or (some alias (map :db/id (:block/refs e)))
-                      (:block/link e)
-                      (nil? (:block/title e)))))
-              search-result-eids)]
-    (when (seq eids)
-      (d/pull-many db '[*] eids))))
+(defn hidden-or-internal-tag?
+  [e]
+  (or (entity-util/hidden? e) (db-class/internal-tags (:db/ident e))))
 
 (defn get-all-pages
   [db]
@@ -473,13 +486,12 @@
    (d/datoms db :avet :block/name)
    (keep (fn [d]
            (let [e (d/entity db (:e d))]
-             (when-not (or (hidden? e) (internal-tags (:db/ident e)))
+             (when-not (or (hidden-or-internal-tag? e)
+                           ;; Why this happened?
+                           (nil? (:block/title e)))
                e))))))
 
-(defn built-in?
-  "Built-in page or block"
-  [entity]
-  (:logseq.property/built-in? entity))
+(def built-in? entity-util/built-in?)
 
 (defn built-in-class-property?
   "Whether property a built-in property for the specific class"
@@ -602,12 +614,47 @@
     :logseq.class/Quote-block :quote
     nil))
 
-(defn get-recent-updated-pages
+(def get-recent-updated-pages sqlite-common-db/get-recent-updated-pages)
+
+(def get-latest-journals sqlite-common-db/get-latest-journals)
+
+(defn get-all-namespace-relation
+  [db]
+  (d/q '[:find ?page ?parent
+         :where
+         [?page :block/namespace ?parent]]
+       db))
+
+(defn get-pages-relation
+  [db with-journal?]
+  (if (entity-plus/db-based-graph? db)
+    (let [q (if with-journal?
+              '[:find ?p ?ref-page
+                :where
+                [?block :block/page ?p]
+                [?block :block/refs ?ref-page]]
+              '[:find ?p ?ref-page
+                :where
+                [?block :block/page ?p]
+                [?p :block/tags]
+                (not [?p :block/tags :logseq.class/Journal])
+                [?block :block/refs ?ref-page]])]
+      (d/q q db))
+    (let [q (if with-journal?
+              '[:find ?p ?ref-page
+                :where
+                [?block :block/page ?p]
+                [?block :block/refs ?ref-page]]
+              '[:find ?p ?ref-page
+                :where
+                [?block :block/page ?p]
+                (not [?p :block/type "journal"])
+                [?block :block/refs ?ref-page]])]
+      (d/q q db))))
+
+(defn get-all-tagged-pages
   [db]
-  (->> (d/datoms db :avet :block/updated-at)
-       (reverse)
-       (keep (fn [datom]
-               (let [e (d/entity db (:e datom))]
-                 (when (and (page? e) (not (hidden? e)))
-                   e))))
-       (take 30)))
+  (d/q '[:find ?page ?tag
+         :where
+         [?page :block/tags ?tag]]
+       db))

+ 15 - 1
deps/db/src/logseq/db/common/property_util.cljs

@@ -1,17 +1,31 @@
 (ns logseq.db.common.property-util
   "Property related util fns. Fns used in both DB and file graphs should go here"
   (:require [datascript.core :as d]
+            [logseq.db.frontend.entity-plus :as entity-plus]
             [logseq.db.frontend.property :as db-property]
             [logseq.db.frontend.property.type :as db-property-type]
             [logseq.db.sqlite.util :as sqlite-util]))
 
+(defn- get-file-pid-by-ident
+  [db-ident]
+  (get-in db-property/built-in-properties [db-ident :name] (name db-ident)))
+
+;; TODO: refactor later to remove this fn
 (defn get-pid
   "Get a built-in property's id (keyword name for file graph and db-ident for db
   graph) given its db-ident. No need to use this fn in a db graph only context"
   [repo db-ident]
   (if (sqlite-util/db-based-graph? repo)
     db-ident
-    (get-in db-property/built-in-properties [db-ident :name] (name db-ident))))
+    (get-file-pid-by-ident db-ident)))
+
+(defn get-pid-2
+  "Get a built-in property's id (keyword name for file graph and db-ident for db
+  graph) given its db-ident. No need to use this fn in a db graph only context"
+  [db db-ident]
+  (if (entity-plus/db-based-graph? db)
+    db-ident
+    (get-file-pid-by-ident db-ident)))
 
 (defn built-in-has-ref-value?
   "Given a built-in's db-ident, determine if its property value is a ref"

+ 161 - 114
deps/db/src/logseq/db/common/sqlite.cljs

@@ -5,13 +5,14 @@
             [clojure.set :as set]
             [clojure.string :as string]
             [datascript.core :as d]
+            [datascript.impl.entity :as de]
             [logseq.common.config :as common-config]
             [logseq.common.util :as common-util]
             [logseq.common.util.date-time :as date-time-util]
-            [logseq.db.frontend.entity-plus :as entity-plus]
-            [logseq.db.frontend.entity-util :as entity-util]
             [logseq.db.common.entity-util :as common-entity-util]
             [logseq.db.common.order :as db-order]
+            [logseq.db.frontend.entity-plus :as entity-plus]
+            [logseq.db.frontend.entity-util :as entity-util]
             [logseq.db.sqlite.util :as sqlite-util]))
 
 (defn- get-pages-by-name
@@ -53,12 +54,6 @@
   [db block]
   (update block :block/refs (fn [refs] (map (fn [ref] (d/pull db '[*] (:db/id ref))) refs))))
 
-(defn- with-block-link
-  [db block]
-  (if (:block/link block)
-    (update block :block/link (fn [link] (d/pull db '[*] (:db/id link))))
-    block))
-
 (defn with-parent
   [db block]
   (cond
@@ -73,6 +68,13 @@
     :else
     block))
 
+(comment
+  (defn- with-block-link
+    [db block]
+    (if (:block/link block)
+      (update block :block/link (fn [link] (d/pull db '[*] (:db/id link))))
+      block)))
+
 (defn- mark-block-fully-loaded
   [b]
   (assoc b :block.temp/fully-loaded? true))
@@ -80,37 +82,42 @@
 (comment
   (defn- property-without-db-attrs
     [property]
-    (dissoc property :db/index :db/valueType :db/cardinality)))
+    (dissoc property :db/index :db/valueType :db/cardinality))
 
-(defn- property-with-values
-  [db block]
-  (when (entity-plus/db-based-graph? db)
-    (let [block (d/entity db (:db/id block))]
-      (->> (:block/properties block)
-           vals
-           (mapcat
-            (fn [property-values]
-              (let [values (->>
-                            (if (and (coll? property-values)
-                                     (map? (first property-values)))
-                              property-values
-                              #{property-values})
-                            (remove entity-util/page?))
-                    value-ids (when (every? map? values)
-                                (->> (map :db/id values)
-                                     (filter (fn [id] (or (int? id) (keyword? id))))))
-                    value-blocks (->>
-                                  (when (seq value-ids)
-                                    (map
-                                     (fn [id] (d/pull db '[*] id))
-                                     value-ids))
-                             ;; FIXME: why d/pull returns {:db/id db-ident} instead of {:db/id number-eid}?
-                                  (keep (fn [block]
-                                          (let [from-property-id (get-in block [:logseq.property/created-from-property :db/id])]
-                                            (if (keyword? from-property-id)
-                                              (assoc-in block [:logseq.property/created-from-property :db/id] (:db/id (d/entity db from-property-id)))
-                                              block)))))]
-                value-blocks)))))))
+  (defn- property-with-values
+    [db block properties]
+    (when (entity-plus/db-based-graph? db)
+      (let [block (d/entity db (:db/id block))
+            property-vals (if properties
+                            (map block properties)
+                            (vals (:block/properties block)))]
+        (->> property-vals
+             (mapcat
+              (fn [property-values]
+                (let [values (->>
+                              (if (and (coll? property-values)
+                                       (map? (first property-values)))
+                                property-values
+                                #{property-values}))
+                      value-ids (when (every? map? values)
+                                  (->> (map :db/id values)
+                                       (filter (fn [id] (or (int? id) (keyword? id))))))
+                      value-blocks (->>
+                                    (when (seq value-ids)
+                                      (map
+                                       (fn [id] (d/pull db '[:db/id :block/uuid
+                                                             :block/name :block/title
+                                                             :logseq.property/value
+                                                             :block/tags :block/page
+                                                             :logseq.property/created-from-property] id))
+                                       value-ids))
+                                  ;; FIXME: why d/pull returns {:db/id db-ident} instead of {:db/id number-eid}?
+                                    (keep (fn [block]
+                                            (let [from-property-id (get-in block [:logseq.property/created-from-property :db/id])]
+                                              (if (keyword? from-property-id)
+                                                (assoc-in block [:logseq.property/created-from-property :db/id] (:db/id (d/entity db from-property-id)))
+                                                block)))))]
+                  value-blocks))))))))
 
 (defn get-block-children-ids
   "Returns children UUIDs"
@@ -139,80 +146,93 @@
       (let [ids' (map (fn [id] [:block/uuid id]) ids)]
         (d/pull-many db '[*] ids')))))
 
-(defn get-block-and-children
-  [db id {:keys [children? nested-children?]}]
+(defn- with-raw-title
+  [m entity]
+  (if-let [raw-title (:block/raw-title entity)]
+    (assoc m :block/title raw-title)
+    m))
+
+(defn- entity->map
+  [entity]
+  (-> (into {} entity)
+      (with-raw-title entity)
+      (assoc :db/id (:db/id entity))))
+
+(defn ^:large-vars/cleanup-todo get-block-and-children
+  [db id {:keys [children? children-only? nested-children? including-property-vals? properties children-props]
+          :or {including-property-vals? true}}]
   (let [block (d/entity db (if (uuid? id)
                              [:block/uuid id]
                              id))
-        page? (common-entity-util/page? block)
-        get-children (fn [block children]
-                       (let [long-page? (and (> (count children) 500) (not (common-entity-util/whiteboard? block)))]
-                         (if long-page?
-                           (->> (map (fn [e]
-                                       (select-keys e [:db/id :block/uuid :block/page :block/order :block/parent :block/collapsed? :block/link]))
-                                     children)
-                                (map #(with-block-link db %)))
-                           (->> (d/pull-many db '[*] (map :db/id children))
-                                (map #(with-block-refs db %))
-                                (map #(with-block-link db %))
-                                (mapcat (fn [block]
-                                          (let [e (d/entity db (:db/id block))]
-                                            (conj
-                                             (if (seq (:block/properties e))
-                                               (vec (property-with-values db e))
-                                               [])
-                                             block))))))))]
+        block-refs-count? (some #{:block.temp/refs-count} properties)
+        whiteboard? (common-entity-util/whiteboard? block)]
     (when block
-      (let [block' (->> (d/pull db '[*] (:db/id block))
-                        (with-parent db)
-                        (with-block-refs db)
-                        (with-block-link db))
-            block' (if (or children? nested-children?)
-                     (mark-block-fully-loaded block')
-                     block')]
-        (cond->
-         {:block block'
-          :properties (property-with-values db block)}
-          children?
-          (assoc :children (get-children block
-                                         (if (and nested-children? (not page?))
-                                           (get-block-children db (:block/uuid block))
-                                           (if page?
+      (let [children (when (or children? children-only?)
+                       (let [page? (common-entity-util/page? block)
+                             children (->>
+                                       (cond
+                                         (and nested-children? (not page?))
+                                         (get-block-children db (:block/uuid block))
+                                         nested-children?
+                                         (:block/_page block)
+                                         :else
+                                         (let [short-page? (when page?
+                                                             (<= (count (:block/_page block)) 100))]
+                                           (if short-page?
                                              (:block/_page block)
-                                             (:block/_parent block))))))))))
+                                             (:block/_parent block))))
+                                       (remove (fn [e] (or (:logseq.property/created-from-property e)
+                                                           (:block/closed-value-property e)))))
+                             children-props (if whiteboard?
+                                              '[*]
+                                              (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]))]
+                         (map
+                          (fn [block]
+                            (if (= children-props '[*])
+                              (entity->map block)
+                              (-> (select-keys block children-props)
+                                  (with-raw-title block))))
+                          children)))]
+        (if children-only?
+          {:children children}
+          (let [block' (if (seq properties)
+                         (-> (select-keys block properties)
+                             (with-raw-title block)
+                             (assoc :db/id (:db/id block)))
+                         (entity->map block))
+                block' (cond->
+                        (mark-block-fully-loaded block')
+                         including-property-vals?
+                         (update-vals (fn [v]
+                                        (cond
+                                          (de/entity? v)
+                                          (entity->map v)
+                                          (and (coll? v) (every? de/entity? v))
+                                          (map entity->map v)
 
-(defn get-latest-journals
-  [db n]
-  (let [today (date-time-util/date->int (js/Date.))]
-    (->>
-     (d/q '[:find [(pull ?page [:db/id :block/journal-day]) ...]
-            :in $ ?today
-            :where
-            [?page :block/name ?page-name]
-            [?page :block/journal-day ?journal-day]
-            [(<= ?journal-day ?today)]]
-          db
-          today)
-     (sort-by :block/journal-day)
-     (reverse)
-     (take n)
-     (mapcat (fn [p]
-               (d/datoms db :eavt (:db/id p)))))))
+                                          :else
+                                          v)))
+                         block-refs-count?
+                         (assoc :block.temp/refs-count (count (:block/_refs block))))]
+            (cond->
+             {:block block'}
+              children?
+              (assoc :children children))))))))
 
-(defn get-all-pages
-  "Get all pages including property page's default value"
+(defn get-latest-journals
   [db]
-  (let [datoms (d/datoms db :avet :block/name)]
-    (mapcat (fn [d]
-              (let [datoms (d/datoms db :eavt (:e d))]
-                (mapcat
-                 (fn [d]
-                   (if (keyword-identical? (:a d) :logseq.property/default-value)
-                     (concat
-                      (d/datoms db :eavt (:v d))
-                      datoms)
-                     datoms))
-                 datoms))) datoms)))
+  (let [today (date-time-util/date->int (js/Date.))]
+    (->> (d/datoms db :avet :block/journal-day)
+         vec
+         rseq
+         (keep (fn [d]
+                 (and (<= (:v d) today)
+                      (let [e (d/entity db (:e d))]
+                        (when (and (common-entity-util/journal? e) (:db/id e))
+                          e))))))))
 
 (defn get-page->refs-count
   [db]
@@ -225,9 +245,19 @@
 
 (defn get-structured-datoms
   [db]
-  (->> (d/datoms db :avet :block/closed-value-property)
-       (mapcat (fn [d]
-                 (d/datoms db :eavt (:e d))))))
+  (let [class-property-id (:db/id (d/entity db :logseq.class/Property))]
+    (->> (concat
+          (d/datoms db :avet :block/tags :logseq.class/Tag)
+          (d/datoms db :avet :block/tags :logseq.class/Property)
+          (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)
+                       block-datoms)))))))
 
 (defn get-favorites
   "Favorites page and its blocks"
@@ -248,8 +278,21 @@
   (let [page-id (get-first-page-by-name db common-config/views-page-name)
         children (when page-id (:block/_parent (d/entity db page-id)))]
     (when (seq children)
-      (mapcat (fn [b] (d/datoms db :eavt (:db/id b)))
-              children))))
+      (into
+       (mapcat (fn [b] (d/datoms db :eavt (:db/id b)))
+               children)
+       (d/datoms db :eavt page-id)))))
+
+(defn get-recent-updated-pages
+  [db]
+  (->> (d/datoms db :avet :block/updated-at)
+       vec
+       rseq
+       (keep (fn [datom]
+               (let [e (d/entity db (:e datom))]
+                 (when (and (common-entity-util/page? e) (not (entity-util/hidden? e)))
+                   e))))
+       (take 30)))
 
 (defn get-initial-data
   "Returns current database schema and initial data.
@@ -270,19 +313,23 @@
                         :logseq.property/empty-placeholder])
         favorites (when db-graph? (get-favorites db))
         views (when db-graph? (get-views-data db))
-        latest-journals (get-latest-journals db 1)
         all-files (get-all-files db)
-        all-pages (get-all-pages db)
         structured-datoms (when db-graph?
                             (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])))
         data (distinct
               (concat idents
-                      all-pages
                       structured-datoms
                       favorites
+                      recent-updated-pages
                       views
-                      latest-journals
-                      all-files))]
+                      all-files
+                      pages-datoms))]
     {:schema schema
      :initial-data data}))
 

+ 577 - 0
deps/db/src/logseq/db/common/view.cljs

@@ -0,0 +1,577 @@
+(ns logseq.db.common.view
+  "Main namespace for view fns."
+  (:require [cljs.reader :as reader]
+            [clojure.set :as set]
+            [clojure.string :as string]
+            [datascript.core :as d]
+            [datascript.impl.entity :as de]
+            [logseq.common.log :as log]
+            [logseq.common.util :as common-util]
+            [logseq.db :as ldb]
+            [logseq.db.frontend.class :as db-class]
+            [logseq.db.frontend.entity-plus :as entity-plus]
+            [logseq.db.frontend.entity-util :as entity-util]
+            [logseq.db.frontend.property :as db-property]
+            [logseq.db.frontend.property.type :as db-property-type]
+            [logseq.db.frontend.rules :as rules]))
+
+(def valid-type-for-sort? (some-fn number? string? boolean?))
+
+(defn get-property-value-for-search
+  [block property]
+  (let [v (get block (:db/ident property))]
+    (if (valid-type-for-sort? v)        ;fast path
+      v
+      (let [typ (:logseq.property/type property)
+            many? (keyword-identical? :db.cardinality/many (get property :db/cardinality))
+            number-type? (or (keyword-identical? :number typ)
+                             (keyword-identical? :datetime typ))]
+        (if many?
+          (let [col (->> (if (db-property-type/all-ref-property-types typ) (map db-property/property-value-content v) v)
+                         (remove nil?))]
+            (if number-type?
+              (reduce + (filter number? col))
+              (string/join ", " col)))
+          (let [v' (if (db-property-type/all-ref-property-types typ) (db-property/property-value-content v) v)]
+            (cond
+              (and number-type? (number? v')) v'
+              :else v')))))))
+
+(defn- get-value-for-sort
+  [property]
+  (let [db-ident (or (:db/ident property) (:id property))
+        closed-values (seq (:property/closed-values property))
+        closed-value->sort-number (when closed-values
+                                    (->> (zipmap (map :db/id closed-values)
+                                                 (if (every? :block/order closed-values)
+                                                   (map :block/order closed-values)
+                                                   (range 0 (count closed-values))))
+                                         (into {})))
+        get-property-value-fn (fn [entity]
+                                (if (de/entity? property)
+                                  (get-property-value-for-search entity property)
+                                  (get entity db-ident)))]
+    (fn [entity]
+      (cond
+        closed-values
+        (closed-value->sort-number (:db/id (get entity db-ident)))
+        :else
+        (let [v (get-property-value-fn entity)]
+          (when (valid-type-for-sort? v)
+            v))))))
+
+(defn- by-sorting
+  [sorting]
+  (let [get-value+cmp
+        (map
+         (fn [{:keys [get-value asc?]}]
+           [get-value
+            (if asc? compare #(compare %2 %1))])
+         sorting)]
+    (fn [a b]
+      (reduce
+       (fn [order [get-value cmp]]
+         (if (zero? order)
+           (cmp (get-value a) (get-value b))
+           (reduced order)))
+       0 get-value+cmp))))
+
+(defn- by-one-sorting
+  [{:keys [asc? get-value]}]
+  (let [cmp (if asc? compare #(compare %2 %1))]
+    (fn [a b]
+      (cmp (get-value a) (get-value b)))))
+
+(defn- sort-ref-entities-by-single-property
+  "get all entities sorted by `major-sorting`"
+  [entities {:keys [_id asc?]} get-value-fn]
+  (let [sorting {:asc? asc?
+                 :get-value get-value-fn}
+        sort-cmp (by-one-sorting sorting)]
+    (sort sort-cmp entities)))
+
+(defn- sort-by-single-property
+  [db {:keys [id asc?] :as sorting} entities partition?]
+  (let [property (or (d/entity db id) {:db/ident id})
+        get-value-fn (memoize (get-value-for-sort property))
+        sorted-entities (cond
+                          (= id :block.temp/refs-count)
+                          (cond-> (sort-by :block.temp/refs-count entities)
+                            (not asc?)
+                            reverse)
+
+                          (not (ldb/db-based-graph? db)) ; file graph properties don't support index
+                          (sort (by-sorting
+                                 [{:get-value get-value-fn
+                                   :asc? asc?}]) entities)
+
+                          :else
+                          (let [ref-type? (= :db.type/ref (:db/valueType property))]
+                            (if ref-type?
+                              (sort-ref-entities-by-single-property entities sorting get-value-fn)
+                              (let [datoms (cond->
+                                            (->> (d/datoms db :avet id)
+                                                 (common-util/distinct-by :e)
+                                                 vec)
+                                             (not asc?)
+                                             rseq)
+                                    row-ids (set (map :db/id entities))
+                                    id->row (zipmap (map :db/id entities) entities)]
+                                (keep
+                                 (fn [d]
+                                   (when (row-ids (:e d))
+                                     (id->row (:e d))))
+                                 datoms)))))]
+    (if partition?
+      (partition-by get-value-fn sorted-entities)
+      sorted-entities)))
+
+(defn- sort-entities-by-minor-sorting
+  "minor-sorting - [{:keys [id asc?]} ...]"
+  [db partitioned-entities-by-major-sorting minor-sorting]
+  (let [sorting
+        (map (fn [{:keys [id asc?]}]
+               (let [property (d/entity db id)]
+                 {:asc? asc?
+                  :get-value (memoize (get-value-for-sort property))}))
+             minor-sorting)
+        sort-cmp (by-sorting sorting)]
+    (mapcat (fn [entities] (sort sort-cmp entities)) partitioned-entities-by-major-sorting)))
+
+(defn sort-entities
+  [db sorting entities]
+  (let [major-sorting (or (first sorting)
+                          {:id :block/updated-at :asc? false})
+        minor-sorting (seq (rest sorting))
+        major-sorted-entities
+        (sort-by-single-property db major-sorting entities (not-empty minor-sorting))]
+    (if minor-sorting
+      (sort-entities-by-minor-sorting db major-sorted-entities minor-sorting)
+      major-sorted-entities)))
+
+(defn get-property-value-content
+  [db value]
+  (when value
+    (cond
+      (uuid? value)
+      (db-property/property-value-content (d/entity db [:block/uuid value]))
+      (de/entity? value)
+      (db-property/property-value-content value)
+      (keyword? value)
+      (str value)
+      :else
+      value)))
+
+(defn- ^:large-vars/cleanup-todo row-matched?
+  [db row filters input]
+  (let [or? (:or? filters)
+        check-f (if or? some every?)]
+    (and
+     (if (string/blank? input)
+       true
+       (string/includes? (string/lower-case (:block/title row)) (string/lower-case input)))
+     (check-f
+      (fn [[property-ident operator match]]
+        (if (nil? match)
+          true
+          (let [value (get row property-ident)
+                value' (cond
+                         (set? value) value
+                         (nil? value) #{}
+                         :else #{value})
+                entity? (de/entity? (first value'))
+                result
+                (case operator
+                  :is
+                  (if (boolean? match)
+                    (= (boolean (get-property-value-content db (get row property-ident))) match)
+                    (cond
+                      (empty? match)
+                      true
+                      (and (empty? match) (empty? value'))
+                      true
+                      :else
+                      (if entity?
+                        (boolean (seq (set/intersection (set (map :block/uuid value')) match)))
+                        (boolean (seq (set/intersection (set value') match))))))
+
+                  :is-not
+                  (if (boolean? match)
+                    (not= (boolean (get-property-value-content db (get row property-ident))) match)
+                    (cond
+                      (and (empty? match) (seq value'))
+                      true
+                      (and (seq match) (empty? value'))
+                      true
+                      :else
+                      (if entity?
+                        (boolean (empty? (set/intersection (set (map :block/uuid value')) match)))
+                        (boolean (empty? (set/intersection (set value') match))))))
+
+                  :text-contains
+                  (some (fn [v]
+                          (if-let [property-value (get-property-value-content db v)]
+                            (string/includes? (string/lower-case property-value) (string/lower-case match))
+                            false))
+                        value')
+
+                  :text-not-contains
+                  (not-any? #(string/includes? (str (get-property-value-content db %)) match) value')
+
+                  :number-gt
+                  (if match (some #(> (get-property-value-content db %) match) value') true)
+                  :number-gte
+                  (if match (some #(>= (get-property-value-content db %) match) value') true)
+                  :number-lt
+                  (if match (some #(< (get-property-value-content db %) match) value') true)
+                  :number-lte
+                  (if match (some #(<= (get-property-value-content db %) match) value') true)
+
+                  :between
+                  (if (seq match)
+                    (some (fn [value-entity]
+                            (let [[start end] match
+                                  value (get-property-value-content db value-entity)
+                                  conditions [(if start (<= start value) true)
+                                              (if end (<= value end) true)]]
+                              (if (seq match) (every? true? conditions) true))) value')
+                    true)
+
+                  :date-before
+                  (if match (some #(< (:block/journal-day %) (:block/journal-day match)) value') true)
+
+                  :date-after
+                  (if match (some #(> (:block/journal-day %) (:block/journal-day match)) value') true)
+
+                  :before
+                  (let [search-value (common-util/get-timestamp match)]
+                    (if search-value (<= (get row property-ident) search-value) true))
+
+                  :after
+                  (let [search-value (common-util/get-timestamp match)]
+                    (if search-value (>= (get row property-ident) search-value) true))
+
+                  true)]
+            result)))
+      (:filters filters)))))
+
+(defn filter-blocks
+  [filters ref-blocks]
+  (let [exclude-ids (set (map :db/id (:excluded filters)))
+        include-ids (set (map :db/id (:included filters)))
+        get-ids (fn [block]
+                  (set (map :db/id (:block/path-refs block))))]
+    (cond->> ref-blocks
+      (seq exclude-ids)
+      (remove (fn [block]
+                (let [ids (get-ids block)]
+                  (seq (set/intersection exclude-ids ids)))))
+
+      (seq include-ids)
+      (filter (fn [block]
+                (let [ids (get-ids block)]
+                  (set/subset? include-ids ids)))))))
+
+(defn get-filters
+  [db page]
+  (let [db-based? (entity-plus/db-based-graph? db)]
+    (if db-based?
+      (let [included-pages (:logseq.property.linked-references/includes page)
+            excluded-pages (:logseq.property.linked-references/excludes page)]
+        (when (or (seq included-pages) (seq excluded-pages))
+          {:included included-pages
+           :excluded excluded-pages}))
+      (let [k :filters
+            properties (:block/properties page)
+            properties-str (or (get properties k) "{}")]
+        (try (let [result (reader/read-string properties-str)]
+               (when (seq result)
+                 (let [excluded-pages (->> (filter #(false? (second %)) result)
+                                           (keep first)
+                                           (keep #(ldb/get-page db %)))
+                       included-pages (->> (filter #(true? (second %)) result)
+                                           (keep first)
+                                           (keep #(ldb/get-page db %)))]
+                   {:included included-pages
+                    :excluded excluded-pages})))
+             (catch :default e
+               (log/error :syntax/filters e)))))))
+
+(defn- get-linked-references
+  [db id]
+  (let [db-based? (ldb/db-based-graph? db)
+        entity (d/entity db id)
+        ids (set (cons id (ldb/get-block-alias db id)))
+        refs (mapcat (fn [id] (:block/_refs (d/entity db id))) ids)
+        page-filters (get-filters db entity)
+        full-ref-blocks (->> refs
+                             (remove (fn [block]
+                                       (if db-based?
+                                         (or
+                                          (= (:db/id block) id)
+                                          (= id (: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))))
+                                         (or
+                                          (= (:db/id block) id)
+                                          (= id (:db/id (:block/page block)))))))
+                             (common-util/distinct-by :db/id))
+        ref-blocks (cond->> full-ref-blocks
+                     (seq page-filters)
+                     (filter-blocks page-filters))
+        ref-pages-count (->> (mapcat (fn [block]
+                                       (->>
+                                        (cons
+                                         (:block/title (:block/page block))
+                                         (map (fn [b]
+                                                (when (and (ldb/page? b) (not= (:db/id b) id))
+                                                  (:block/title b)))
+                                              (:block/refs block)))
+                                        distinct))
+                                     full-ref-blocks)
+                             (remove nil?)
+                             (frequencies))]
+    {:ref-pages-count ref-pages-count
+     :ref-blocks ref-blocks}))
+
+(defn- get-unlinked-references
+  [db id]
+  (let [entity (d/entity db id)
+        title (string/lower-case (:block/title entity))]
+    (when-not (string/blank? title)
+      (let [ids (->> (d/datoms db :avet :block/title)
+                     (keep (fn [d]
+                             (when (and (not= id (:e d)) (string/includes? (string/lower-case (:v d)) title))
+                               (:e d)))))]
+        (keep
+         (fn [eid]
+           (let [e (d/entity db eid)]
+             (when-not (or (some #(= id %) (map :db/id (:block/refs e)))
+                           (:block/link e)
+                           (ldb/page? e))
+               e)))
+         ids)))))
+
+(defn- get-exclude-page-ids
+  [db]
+  (->>
+   (concat
+    (d/datoms db :avet :logseq.property/hide? true)
+    (d/datoms db :avet :logseq.property/built-in? true)
+    (d/datoms db :avet :block/tags (:db/id (d/entity db :logseq.class/Property))))
+   (map :e)
+   set))
+
+(defn- get-entities-for-all-pages [db sorting property-ident {:keys [db-based?]}]
+  (let [refs-count? (and (coll? sorting) (some (fn [m] (= (:id m) :block.temp/refs-count)) sorting))
+        exclude-ids (when db-based? (get-exclude-page-ids db))]
+    (keep (fn [d]
+            (let [e (d/entity db (:e d))]
+              (when-not (if db-based?
+                          (exclude-ids (:db/id e))
+                          (or (ldb/hidden-or-internal-tag? e)
+                              (entity-util/property? e)
+                              (entity-util/built-in? e)
+                              (nil? (:block/title e))))
+                (cond-> e
+                  refs-count?
+                  (assoc :block.temp/refs-count (count (:block/_refs e)))))))
+          (d/datoms db :avet property-ident))))
+
+(defn- get-entities
+  [db view feat-type property-ident view-for-id* sorting]
+  (let [view-for (:logseq.property/view-for view)
+        view-for-id (or (:db/id view-for) view-for-id*)
+        non-hidden-e (fn [id] (let [e (d/entity db id)]
+                                (when-not (entity-util/hidden? e)
+                                  e)))
+        db-based? (entity-plus/db-based-graph? db)]
+    (case feat-type
+      :all-pages
+      (get-entities-for-all-pages db sorting property-ident {:db-based? db-based?})
+
+      :class-objects
+      (let [class-id view-for-id
+            class-children (db-class/get-structured-children db class-id)
+            class-ids (distinct (conj class-children class-id))
+            datoms (mapcat (fn [id] (d/datoms db :avet :block/tags id)) class-ids)]
+        (keep (fn [d] (non-hidden-e (:e d))) datoms))
+
+      :property-objects
+      (->>
+       (d/q
+        '[:find [?b ...]
+          :in $ % ?prop
+          :where
+          (has-property-or-default-value? ?b ?prop)]
+        db
+        (rules/extract-rules rules/db-query-dsl-rules [:has-property-or-default-value]
+                             {:deps rules/rules-dependencies})
+        property-ident)
+       (keep (fn [id] (non-hidden-e id))))
+
+      :linked-references
+      (get-linked-references db view-for-id)
+
+      :unlinked-references
+      (get-unlinked-references db view-for-id)
+
+      :query-result
+      nil
+
+      nil)))
+
+(defn- get-view-entities
+  [db view-id & {:keys [view-for-id view-feature-type sorting]}]
+  (let [view (d/entity db view-id)
+        feat-type (or view-feature-type (:logseq.property.view/feature-type view))
+        sorting (or sorting (:logseq.property.table/sorting view))
+        index-attr (case feat-type
+                     :all-pages
+                     :block/name
+                     :class-objects
+                     :block/tags
+                     :property-objects
+                     (let [view-for (:logseq.property/view-for view)]
+                       (:db/ident view-for))
+                     nil)]
+    (get-entities db view feat-type index-attr view-for-id sorting)))
+
+(defn- get-view-property-values
+  [db property-ident {:keys [view-id query-entity-ids]}]
+  (let [empty-id (:db/id (d/entity db :logseq.property/empty-placeholder))
+        entities-result (get-view-entities db view-id)
+        entities (cond
+                   query-entity-ids
+                   (keep #(d/entity db %) query-entity-ids)
+                   (map? entities-result)
+                   (:ref-blocks entities-result)
+                   :else
+                   entities-result)]
+    (->> (mapcat (fn [entity]
+                   (let [v (get entity property-ident)]
+                     (if (set? v) v #{v})))
+                 entities)
+         (remove nil?)
+         (keep (fn [e]
+                 (when-let [label (get-property-value-content db e)]
+                   (when-not (or (string/blank? (str label))
+                                 (= empty-id (:db/id e)))
+                     {:label (str label)
+                      :value (if (de/entity? e)
+                               (select-keys e [:db/id :block/uuid])
+                               e)}))))
+         (common-util/distinct-by :label))))
+
+(defn ^:api get-property-values
+  [db property-ident {:keys [view-id _query-entity-ids] :as option}]
+  (let [property (d/entity db property-ident)
+        default-value (:logseq.property/default-value property)
+        ref-type? (= :db.type/ref (:db/valueType property))
+        values (if view-id
+                 (get-view-property-values db property-ident option)
+                 ;; get all values
+                 (->> (d/datoms db :avet property-ident)
+                      (map (fn [d]
+                             (:v d)))
+                      distinct
+                      (map (fn [v]
+                             (let [e (when ref-type? (d/entity db v))
+                                   [label value] (cond ref-type?
+                                                       [(db-property/property-value-content e)
+                                                        (select-keys e [:db/id :block/uuid])]
+                                                       (= :datetime (:logseq.property/type property))
+                                                       [v v]
+                                                       :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)))
+
+(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]
+               :as opts}]
+  ;; TODO: create a view for journals maybe?
+  (cond
+    journals?
+    (let [ids (->> (ldb/get-latest-journals db)
+                   (mapv :db/id))]
+      {:count (count ids)
+       :data ids})
+    :else
+    (let [view (d/entity db view-id)
+          db-based? (ldb/db-based-graph? db)
+          group-by-property (:logseq.property.view/group-by-property view)
+          group-by-property-ident (if db-based?
+                                    (:db/ident group-by-property)
+                                    (when (contains? #{:linked-references :unlinked-references} view-feature-type)
+                                      :block/page))
+          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)
+          list-view? (= :logseq.property.view/type.list (:db/ident (:logseq.property.view/type view)))
+          feat-type (or view-feature-type (:logseq.property.view/feature-type view))
+          query? (= feat-type :query-result)
+          entities-result (if query?
+                            (keep #(d/entity db %) query-entity-ids)
+                            (get-view-entities db view-id opts))
+          entities (if (= feat-type :linked-references)
+                     (:ref-blocks entities-result)
+                     entities-result)
+          sorting (let [sorting* (:logseq.property.table/sorting view)]
+                    (if (or (= sorting* :logseq.property/empty-placeholder) (empty? sorting*))
+                      (or sorting [{:id :block/updated-at, :asc? false}])
+                      sorting*))
+          filtered-entities (if (or (seq filters) (not (string/blank? input)))
+                              (filter (fn [row] (row-matched? db row filters input)) entities)
+                              entities)
+          group-by-page? (= group-by-property-ident :block/page)
+          result (if group-by-property-ident
+                   (->> filtered-entities
+                        (group-by group-by-property-ident)
+                        (seq)
+                        (sort-by (fn [[by-value _]]
+                                   (cond
+                                     group-by-page?
+                                     (:block/updated-at by-value)
+                                     group-by-closed-values?
+                                     (:block/order by-value)
+                                     ref-property?
+                                     (db-property/property-value-content by-value)
+                                     :else
+                                     by-value))
+                                 (if group-by-page? #(compare %2 %1) compare)))
+                   (sort-entities db sorting filtered-entities))
+          data' (if group-by-property-ident
+                  (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])
+                                       by-value)
+                           pages? (not (some :block/page entities))
+                           group (if (and list-view? (not pages?))
+                                   (let [parent-groups (->> entities
+                                                            (group-by :block/parent)
+                                                            (sort-by (fn [[parent _]] (:block/order parent))))]
+                                     (map
+                                      (fn [[_parent blocks]]
+                                        [(:block/uuid (first blocks))
+                                         (map (fn [b]
+                                                {:db/id (:db/id b)
+                                                 :block/parent (:block/uuid (:block/parent b))}) blocks)])
+                                      parent-groups))
+                                   (map :db/id entities))]
+                       [by-value' group]))
+                   result)
+                  (map :db/id result))]
+      (cond->
+       {:count (count filtered-entities)
+        :data data'}
+        (= feat-type :linked-references)
+        (assoc :ref-pages-count (:ref-pages-count entities-result))))))

+ 1 - 1
deps/db/src/logseq/db/file_based/rules.cljc

@@ -90,4 +90,4 @@
    :page-ref
    '[(page-ref ?b ?page-name)
      [?b :block/path-refs ?br]
-     [?br :block/name ?page-name]]})
+     [?br :block/name ?page-name]]})

+ 14 - 0
deps/db/src/logseq/db/frontend/class.cljs

@@ -1,9 +1,11 @@
 (ns logseq.db.frontend.class
   "Class related fns for DB graphs and frontend/datascript usage"
   (:require [clojure.set :as set]
+            [datascript.core :as d]
             [flatland.ordered.map :refer [ordered-map]]
             [logseq.common.defkeywords :refer [defkeywords]]
             [logseq.db.frontend.db-ident :as db-ident]
+            [logseq.db.frontend.rules :as rules]
             [logseq.db.sqlite.util :as sqlite-util]))
 
 ;; Main class vars
@@ -115,6 +117,18 @@
   "Built-in classes that are hidden in a few contexts like property values"
   #{:logseq.class/Page :logseq.class/Root :logseq.class/Asset})
 
+(defn get-structured-children
+  [db eid]
+  (->>
+   (d/q '[:find [?children ...]
+          :in $ ?parent %
+          :where
+          (parent ?parent ?children)]
+        db
+        eid
+        (:parent rules/rules))
+   (remove #{eid})))
+
 ;; Helper fns
 ;; ==========
 

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

@@ -161,4 +161,4 @@
             (common-util/replace-ignore-case (str "#" id-ref) id-ref))))
     content
     (sort-refs tags))
-   (string/trim)))
+   (string/trim)))

+ 6 - 6
deps/db/src/logseq/db/frontend/entity_plus.cljc

@@ -20,7 +20,7 @@
     ;; File graph only attributes. Can these be removed if this is only called in db graphs?
     :block/pre-block? :block/scheduled :block/deadline :block/type :block/name :block/marker
 
-    :block.temp/ast-title :block.temp/top? :block.temp/bottom? :block.temp/search?
+    :block.temp/ast-title :block.temp/search?
     :block.temp/fully-loaded? :block.temp/ast-body
 
     :db/valueType :db/cardinality :db/ident :db/index
@@ -77,7 +77,7 @@
   "Whether the current graph is db-only"
   [db]
   (when db
-    (= "db" (:kv/value (entity-memoized db :logseq.kv/db-type)))))
+    (identical? "db" (:kv/value (entity-memoized db :logseq.kv/db-type)))))
 
 (defn- get-journal-title
   [db e]
@@ -92,7 +92,7 @@
       (get-journal-title db e)
       (let [search? (get (.-kv e) :block.temp/search?)]
         (or
-         (when-not (and search? (= k :block/title))
+         (when-not (and search? (keyword-identical? k :block/title))
            (get (.-kv e) k))
          (let [result (lookup-entity e k default-value)
                refs (:block/refs e)
@@ -115,7 +115,7 @@
        (when (qualified-keyword? k)
          (when-let [property (entity-memoized db k)]
            (let [property-type (lookup-entity property :logseq.property/type nil)]
-             (if (= :checkbox property-type)
+             (if (keyword-identical? :checkbox property-type)
                (lookup-entity property :logseq.property/scalar-default-value nil)
                (lookup-entity property :logseq.property/default-value nil)))))))))
 
@@ -177,8 +177,8 @@
            (lookup-entity e :block/_parent default-value)
 
            :property/closed-values
-           (->> (lookup-entity e :block/_closed-value-property default-value)
-                (sort-by :block/order))
+           (some->> (lookup-entity e :block/_closed-value-property default-value)
+                    (sort-by :block/order))
 
            (lookup-kv-with-default-value db e k default-value))))
      (catch :default e

+ 5 - 0
deps/db/src/logseq/db/frontend/entity_util.cljs

@@ -81,3 +81,8 @@
                      :logseq.class/Whiteboard :whiteboard
                      :logseq.class/Page :page}]
     (set (map #(ident->type (:db/ident %)) (:block/tags entity)))))
+
+(defn built-in?
+  "Built-in page or block"
+  [entity]
+  (:logseq.property/built-in? entity))

+ 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.4"))
+(def version (parse-schema-version "64.5"))
 
 (defn major-version
   "Return a number.

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

@@ -920,4 +920,4 @@
         (-> (sqlite-build/build-blocks-tx (dissoc export-map'' ::graph-files ::kv-values ::export-type))
             (assoc :misc-tx (vec (concat (::graph-files export-map'')
                                          (::kv-values export-map'')))))
-        (sqlite-build/build-blocks-tx export-map'')))))
+        (sqlite-build/build-blocks-tx export-map'')))))

+ 20 - 34
deps/db/test/logseq/db/common/sqlite_test.cljs

@@ -1,12 +1,15 @@
 (ns logseq.db.common.sqlite-test
-  (:require [cljs.test :refer [deftest async use-fixtures is testing]]
-            ["fs" :as fs]
+  "This ns is the only one to test against file based datascript connections.
+   These are useful integration tests"
+  (:require ["fs" :as fs]
             ["path" :as node-path]
+            [cljs.test :refer [deftest async use-fixtures is testing]]
             [datascript.core :as d]
             [logseq.db.common.sqlite :as sqlite-common-db]
-            [logseq.common.util.date-time :as date-time-util]
+            [logseq.db.sqlite.build :as sqlite-build]
             [logseq.db.sqlite.cli :as sqlite-cli]
-            [clojure.string :as string]))
+            [logseq.db.sqlite.create-graph :as sqlite-create-graph]
+            [logseq.db.test.helper :as db-test]))
 
 (use-fixtures
   :each
@@ -41,34 +44,17 @@
           "Correct file with content is found"))))
 
 (deftest restore-initial-data
-  (testing "Restore a journal page"
-    (create-graph-dir "tmp/graphs" "test-db")
-    (let [conn* (sqlite-cli/open-db! "tmp/graphs" "test-db")
-          page-uuid (random-uuid)
-          block-uuid (random-uuid)
-          created-at (js/Date.now)
-          date-int (date-time-util/date->int (js/Date.))
-          date-title (date-time-util/int->journal-title date-int "MMM do, yyyy")
-          blocks [{:db/id 100001
-                   :block/uuid page-uuid
-                   :block/journal-day date-int
-                   :block/name (string/lower-case date-title)
-                   :block/title date-title
-                   :block/created-at created-at
-                   :block/updated-at created-at}
-                  {:db/id 100002
-                   :block/title "test"
-                   :block/uuid block-uuid
-                   :block/page {:db/id 100001}
-                   :block/created-at created-at
-                   :block/updated-at created-at}]
-          _ (d/transact! conn* blocks)
+  (create-graph-dir "tmp/graphs" "test-db")
+  (let [conn* (sqlite-cli/open-db! "tmp/graphs" "test-db")
+        _ (d/transact! conn* (sqlite-create-graph/build-db-initial-data "{}"))
+        {:keys [init-tx]}
+        (sqlite-build/build-blocks-tx
+         {:pages-and-blocks
+          [{:page {:block/title "page1"}
+            :blocks [{:block/title "b1"}]}]})
+        _ (d/transact! conn* init-tx)
           ;; Simulate getting data from sqlite and restoring it for frontend
-          {:keys [schema initial-data]} (sqlite-common-db/get-initial-data @conn*)
-          conn (sqlite-common-db/restore-initial-data initial-data schema)]
-      (is (= (take 1 blocks)
-             (->> (d/q '[:find (pull ?b [*])
-                         :where [?b :block/created-at]]
-                       @conn)
-                  (map first)))
-          "Journal page is included in initial restore while its block is not"))))
+        {:keys [schema initial-data]} (sqlite-common-db/get-initial-data @conn*)
+        conn (sqlite-common-db/restore-initial-data initial-data schema)]
+    (is (some? (db-test/find-page-by-title @conn "page1"))
+        "Restores recently updated page")))

+ 3 - 3
deps/db/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v18":
-  version "1.2.173-feat-db-v18"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/1cd15bf5beb77a1bc5c943a438681cb072eabf2c"
+"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v19":
+  version "1.2.173-feat-db-v19"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/ca32553da41aff783d89264b2e1a753372721f2e"
   dependencies:
     import-meta-resolve "^2.1.0"
 

+ 1 - 1
deps/graph-parser/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v18",
+    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v19",
     "better-sqlite3": "9.3.0"
   },
   "dependencies": {

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

@@ -927,12 +927,12 @@
                            (throw (ex-info (str "No parent page found for " (pr-str (:block/uuid (:logseq.property/parent n))))
                                            {:node n})))))]
     (when-let [parent (get-parent node)]
-     (loop [current-parent parent
-            parents' []]
-       (if (and current-parent (not (contains? parents' current-parent)))
-         (recur (get-parent current-parent)
-                (conj parents' current-parent))
-         (vec (reverse parents')))))))
+      (loop [current-parent parent
+             parents' []]
+        (if (and current-parent (not (contains? parents' current-parent)))
+          (recur (get-parent current-parent)
+                 (conj parents' current-parent))
+          (vec (reverse parents')))))))
 
 (defn- get-all-existing-page-uuids
   "Returns a map of unique page names mapped to their uuids. The page names

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

@@ -10,6 +10,7 @@
             [logseq.common.util.date-time :as date-time-util]
             [logseq.db :as ldb]
             [logseq.db.frontend.content :as db-content]
+            [logseq.db.frontend.entity-plus :as entity-plus]
             [logseq.db.frontend.malli-schema :as db-malli-schema]
             [logseq.db.frontend.rules :as rules]
             [logseq.db.frontend.validate :as db-validate]
@@ -19,8 +20,7 @@
             [logseq.graph-parser.test.docs-graph-helper :as docs-graph-helper]
             [logseq.graph-parser.test.helper :as test-helper :include-macros true :refer [deftest-async]]
             [logseq.outliner.db-pipeline :as db-pipeline]
-            [promesa.core :as p]
-            [logseq.db.frontend.entity-plus :as entity-plus]))
+            [promesa.core :as p]))
 
 ;; Helpers
 ;; =======

+ 3 - 3
deps/graph-parser/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v18":
-  version "1.2.173-feat-db-v18"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/1cd15bf5beb77a1bc5c943a438681cb072eabf2c"
+"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v19":
+  version "1.2.173-feat-db-v19"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/ca32553da41aff783d89264b2e1a753372721f2e"
   dependencies:
     import-meta-resolve "^2.1.0"
 

+ 1 - 1
deps/outliner/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v18"
+    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v19"
   },
   "dependencies": {
     "better-sqlite3": "9.3.0",

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

@@ -138,7 +138,9 @@
 
 (defn- file-rebuild-block-refs
   [repo db date-formatter {:block/keys [properties] :as block}]
-  (let [property-key-refs (keys properties)
+  (let [property-key-refs (->> (keys properties)
+                               (keep (fn [property-id]
+                                       (:block/uuid (ldb/get-page db (name property-id))))))
         property-value-refs (->> (vals properties)
                                  (mapcat (fn [v]
                                            (cond
@@ -160,7 +162,6 @@
         property-refs (->> (concat property-key-refs property-value-refs)
                            (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))]
     (concat property-refs content-refs)))
@@ -233,7 +234,7 @@
                   db-based?
                   (dissoc :block/properties))
           m* (-> data'
-                 (dissoc :block/children :block/meta :block.temp/top? :block.temp/bottom? :block/unordered
+                 (dissoc :block/children :block/meta :block/unordered
                          :block.temp/ast-title :block.temp/ast-body :block/level :block.temp/fully-loaded?)
                  common-util/remove-nils
                  block-with-updated-at
@@ -711,7 +712,8 @@
                               (map (fn [uuid'] {:block/uuid uuid'})))
                 tx (assign-temp-id blocks-tx replace-empty-target? target-block)
                 from-property (:logseq.property/created-from-property target-block)
-                property-values-tx (when (and sibling? from-property)
+                many? (= :db.cardinality/many (:db/cardinality from-property))
+                property-values-tx (when (and sibling? from-property many?)
                                      (let [top-level-blocks (filter #(= 1 (:block/level %)) blocks')]
                                        (mapcat (fn [block]
                                                  (when-let [new-id (or (id->new-uuid (:db/id block)) (:block/uuid block))]

+ 2 - 2
deps/outliner/src/logseq/outliner/tree.cljs

@@ -53,7 +53,7 @@
 ;; TODO: entity can already be used as a tree
 (defn blocks->vec-tree
   "`blocks` need to be in the same page."
-  [repo db blocks root-id]
+  [repo db blocks root-id & {:as option}]
   (let [blocks (map (fn [b] (if (de/entity? b)
                               (assoc (into {} b) :db/id (:db/id b))
                               b)) blocks)
@@ -61,7 +61,7 @@
     (if-not root ; custom query
       blocks
       (let [result (blocks->vec-tree-aux repo db blocks root)]
-        (if page?
+        (if (and page? (not (:link option)))
           result
           ;; include root block
           (let [root-block (some #(when (= (:db/id %) (:db/id root)) %) blocks)

+ 3 - 3
deps/outliner/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v18":
-  version "1.2.173-feat-db-v18"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/1cd15bf5beb77a1bc5c943a438681cb072eabf2c"
+"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v19":
+  version "1.2.173-feat-db-v19"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/ca32553da41aff783d89264b2e1a753372721f2e"
   dependencies:
     import-meta-resolve "^2.1.0"
 

+ 1 - 1
deps/publishing/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v18",
+    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v19",
     "mldoc": "^1.5.9"
   },
   "dependencies": {

+ 3 - 3
deps/publishing/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v18":
-  version "1.2.173-feat-db-v18"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/1cd15bf5beb77a1bc5c943a438681cb072eabf2c"
+"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v19":
+  version "1.2.173-feat-db-v19"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/ca32553da41aff783d89264b2e1a753372721f2e"
   dependencies:
     import-meta-resolve "^2.1.0"
 

+ 18 - 1
deps/shui/src/logseq/shui/hooks.cljs

@@ -1,7 +1,9 @@
 (ns logseq.shui.hooks
   "React custom hooks."
   (:refer-clojure :exclude [ref deref])
-  (:require [goog.functions :as gfun]
+  (:require [frontend.common.missionary :as c.m]
+            [goog.functions :as gfun]
+            [missionary.core :as m]
             [rum.core :as rum]))
 
 (defn- memo-deps
@@ -24,6 +26,7 @@
   "setup-fn will be invoked every render of component when no deps arg provided"
   ([setup-fn] (rum/use-effect! setup-fn))
   ([setup-fn deps & {:keys [equal-fn]}]
+   (assert (fn? setup-fn) "use-effect! setup-fn should be a function")
    (rum/use-effect! (fn [& deps]
                       (let [result (apply setup-fn deps)]
                         (when (fn? result) result)))
@@ -35,6 +38,7 @@
 (defn use-layout-effect!
   ([setup-fn] (rum/use-layout-effect! setup-fn))
   ([setup-fn deps & {:keys [equal-fn]}]
+   (assert (fn? setup-fn) "use-layout-effect! setup-fn should be a function")
    (rum/use-layout-effect! (fn [& deps]
                              (let [result (apply setup-fn deps)]
                                (when (fn? result) result)))
@@ -67,3 +71,16 @@
         cb (use-callback (gfun/debounce set-value! msec) [])]
     (use-effect! #(cb value) [value])
     debounced-value))
+
+(defn use-flow-state
+  "Return values from `flow`, default init-value is nil"
+  ([flow] (use-flow-state nil flow))
+  ([init-value flow]
+   (let [[value set-value!] (use-state init-value)]
+     (use-effect!
+      #(c.m/run-task*
+        (m/reduce
+         (constantly nil)
+         (m/ap (set-value! (m/?> flow)))))
+      [])
+     value)))

+ 13 - 14
deps/shui/src/logseq/shui/table/core.cljc

@@ -16,7 +16,7 @@
 
 (defn- row-selected?
   [row row-selection]
-  (let [id (:id row)]
+  (let [id (:db/id row)]
     (or
      (and (:selected-all? row-selection)
           ;; exclude ids
@@ -30,14 +30,14 @@
   (boolean
    (or
     (and (seq (:selected-ids row-selection))
-         (some (:selected-ids row-selection) (map :db/id rows)))
+         (some (:selected-ids row-selection) rows))
     (and (seq (:exclude-ids row-selection))
          (not= (count rows) (count (:exclude-ids row-selection)))))))
 
 (defn- select-all?
   [row-selection rows]
   (and (seq (:selected-ids row-selection))
-       (set/subset? (set (map :db/id rows))
+       (set/subset? (set rows)
                     (:selected-ids row-selection))))
 
 (defn- toggle-selected-all!
@@ -48,7 +48,7 @@
       (and group-by-property value)
       (let [new-selection (update row-selection :selected-ids
                                   (fn [ids]
-                                    (set/union (set ids) (set (map :db/id (:rows table))))))]
+                                    (set/union (set ids) (set (:rows table)))))]
         (set-row-selection! new-selection))
 
       value
@@ -57,7 +57,7 @@
       group-by-property
       (let [new-selection (update row-selection :selected-ids
                                   (fn [ids]
-                                    (set/difference (set ids) (set (map :db/id (:rows table))))))]
+                                    (set/difference (set ids) (set (:rows table)))))]
         (set-row-selection! new-selection))
 
       :else
@@ -71,7 +71,7 @@
 
 (defn- row-toggle-selected!
   [row value set-row-selection! row-selection]
-  (let [id (:id row)
+  (let [id (:db/id row)
         new-selection (if (:selected-all? row-selection)
                         (update row-selection :excluded-ids (if value disj set-conj) id)
                         (update row-selection :selected-ids (if value set-conj disj) id))]
@@ -86,7 +86,7 @@
                        (remove (fn [item] (= (:id item) id)) sorting)
                        (map (fn [item] (if (= (:id item) id) (assoc item :asc? asc?) item)) sorting))
                      (when-not (nil? asc?)
-                       (conj (if (vector? sorting) sorting (vec sorting)) {:id id :asc? asc?})))
+                       (into [{:id id :asc? asc?}] sorting)))
                    (remove nil?)
                    vec)]
     (set-sorting! value)
@@ -97,11 +97,11 @@
   (if (:selected-all? row-selection)
     (let [excluded-ids (:excluded-ids row-selection)]
       (if (seq excluded-ids)
-        (remove #(excluded-ids (:id %)) rows)
+        (remove #(excluded-ids %) rows)
         rows))
     (let [selected-ids (:selected-ids row-selection)]
       (when (seq selected-ids)
-        (filter #(selected-ids (:id %)) rows)))))
+        (filter #(selected-ids %) rows)))))
 
 (defn table-option
   [{:keys [data columns state data-fns]
@@ -276,14 +276,15 @@
 (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 (merge {:class "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted bg-gray-01 items-stretch"}
-                                                         prop)
+    [:div.ls-table-row.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]))
 
 (rum/defc table-cell < rum/static
   [& prop-and-children]
   (let [[prop children] (get-prop-and-children prop-and-children)]
-    [:div.flex.relative prop
+    [:div.ls-table-cell.flex.relative.h-full (dissoc prop :select? :add-property?)
      [:div {:class (str "flex align-middle w-full overflow-x-clip items-center"
                         (cond
                           (:select? prop)
@@ -305,5 +306,3 @@
              :style {:z-index 101}}
             prop)
      children]))
-
-(def table-sort-rows impl/sort-rows)

+ 0 - 41
deps/shui/src/logseq/shui/table/impl.cljc

@@ -15,47 +15,6 @@
   [columns visible-columns']
   (filter #(column-visible? % visible-columns') columns))
 
-(defn sort-rows
-  "Support multiple sorts"
-  [rows sorting columns]
-  (let [column-id->get-value (zipmap (map column-id columns)
-                                     (map :get-value-for-sort columns))]
-    (loop [[sorting-item & other-sorting] (reverse sorting)
-           rows rows]
-      (if sorting-item
-        (let [{:keys [id asc?]} sorting-item
-              rows' (sort-by
-                     (fn [row]
-                       (let [sort-value (or (get column-id->get-value id)
-                                            (let [valid-type? (some-fn number? string? boolean?)]
-                                              ;; need to check value type, otherwise `compare` can be failed,
-                                              ;; then crash the UI.
-                                              (fn [row]
-                                                (let [v (get row id)]
-                                                  (when (valid-type? v)
-                                                    v)))))]
-                         (sort-value row)))
-                     (if asc? compare #(compare %2 %1))
-                     rows)]
-          (recur other-sorting rows'))
-        rows))))
-
-(comment
-  (def columns [{:id :author}
-                {:id :published-year}])
-  (def sorting [{:id :published-year :asc? true}
-                {:id :author :asc? false}])
-  (def rows [{:id :author-1
-              :author "Charlie"
-              :published-year 2014}
-             {:id :author-2
-              :author "Tienson"
-              :published-year 2014}
-             {:id :author-2
-              :author "Zhiyuan"
-              :published-year 2020}])
-  (sort-rows rows sorting columns))
-
 (defn rows
   [{:keys [row-filter]
     :as opts}]

+ 1 - 0
packages/ui/src/colors.css

@@ -35,6 +35,7 @@ html {
     }
 
     &[data-theme=dark] {
+      --lx-gray-01: var(--ls-primary-background-color, hsl(var(--background)));
       --background: 192 100% 11%;
       --foreground: 0 0% 95%;
       --accent: 192 80% 10%;

+ 1 - 1
scripts/package.json

@@ -3,7 +3,7 @@
   "version": "1.0.0",
   "private": true,
   "devDependencies": {
-    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v18"
+    "@logseq/nbb-logseq": "logseq/nbb-logseq#feat-db-v19"
   },
   "dependencies": {
     "better-sqlite3": "9.3.0",

+ 1 - 1
scripts/src/logseq/tasks/db_graph/create_graph_with_properties.cljs

@@ -7,10 +7,10 @@
             ["os" :as os]
             ["path" :as node-path]
             [babashka.cli :as cli]
+            [cljs.pprint :as pprint]
             [clojure.edn :as edn]
             [clojure.set :as set]
             [clojure.string :as string]
-            [cljs.pprint :as pprint]
             [datascript.core :as d]
             [logseq.common.util :as common-util]
             [logseq.common.util.date-time :as date-time-util]

+ 3 - 3
scripts/yarn.lock

@@ -2,9 +2,9 @@
 # yarn lockfile v1
 
 
-"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v18":
-  version "1.2.173-feat-db-v18"
-  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/1cd15bf5beb77a1bc5c943a438681cb072eabf2c"
+"@logseq/nbb-logseq@logseq/nbb-logseq#feat-db-v19":
+  version "1.2.173-feat-db-v19"
+  resolved "https://codeload.github.com/logseq/nbb-logseq/tar.gz/ca32553da41aff783d89264b2e1a753372721f2e"
   dependencies:
     import-meta-resolve "^2.1.0"
 

+ 48 - 48
src/electron/electron/core.cljs

@@ -1,26 +1,26 @@
 (ns electron.core
-  (:require [electron.handler :as handler]
+  (:require ["/electron/utils" :as js-utils]
+            ["electron" :refer [BrowserWindow Menu app protocol ipcMain dialog shell] :as electron]
+            ["electron-deeplink" :refer [Deeplink]]
+            ["os" :as os]
+            ["path" :as node-path]
+            [cljs-bean.core :as bean]
+            [clojure.string :as string]
             [electron.db :as db]
+            [electron.exceptions :as exceptions]
+            [electron.fs-watcher :as fs-watcher]
+            [electron.git :as git]
+            [electron.handler :as handler]
+            [electron.logger :as logger]
+            [electron.server :as server]
             [electron.updater :refer [init-updater] :as updater]
+            [electron.url :refer [logseq-url-handler]]
             [electron.utils :refer [*win mac? linux? dev? get-win-from-sender
                                     decode-protected-assets-schema-path send-to-renderer]
              :as utils]
-            [electron.url :refer [logseq-url-handler]]
-            [electron.logger :as logger]
-            [electron.server :as server]
-            [clojure.string :as string]
-            [promesa.core :as p]
-            [cljs-bean.core :as bean]
-            [electron.fs-watcher :as fs-watcher]
-            ["path" :as node-path]
-            ["os" :as os]
-            ["electron" :refer [BrowserWindow Menu app protocol ipcMain dialog shell] :as electron]
-            ["electron-deeplink" :refer [Deeplink]]
-            [electron.git :as git]
             [electron.window :as win]
-            [electron.exceptions :as exceptions]
-            ["/electron/utils" :as js-utils]
-            [logseq.publishing.export :as publish-export]))
+            [logseq.publishing.export :as publish-export]
+            [promesa.core :as p]))
 
 ;; Keep same as main/frontend.util.url
 (defonce LSP_SCHEME "logseq")
@@ -105,15 +105,15 @@
   (p/let [app-path (. app getAppPath)
           asset-filenames (->> (js->clj asset-filenames) (remove nil?))
           root-dir (or output-path (handler/open-dir-dialog))]
-         (when root-dir
-           (publish-export/create-export
-            html
-            app-path
-            repo-path
-            root-dir
-            {:asset-filenames asset-filenames
-             :log-error-fn logger/error
-             :notification-fn #(send-to-renderer :notification %)}))))
+    (when root-dir
+      (publish-export/create-export
+       html
+       app-path
+       repo-path
+       root-dir
+       {:asset-filenames asset-filenames
+        :log-error-fn logger/error
+        :notification-fn #(send-to-renderer :notification %)}))))
 
 (defn setup-app-manager!
   [^js win]
@@ -250,7 +250,7 @@
          ;; Add React developer tool
          (when-let [^js devtoolsInstaller (and dev? (js/require "electron-devtools-installer"))]
            (-> (.default devtoolsInstaller (.-REACT_DEVELOPER_TOOLS devtoolsInstaller))
-             (.then #(js/console.log "Added Extension:" (.-REACT_DEVELOPER_TOOLS devtoolsInstaller)))))
+               (.then #(js/console.log "Added Extension:" (.-REACT_DEVELOPER_TOOLS devtoolsInstaller)))))
 
          (let [t0 (setup-interceptor! app')
                ^js win (win/create-main-window!)
@@ -281,30 +281,30 @@
 
            ;; main window events
            (.on win "close" (fn [e]
-                                  (git/before-graph-close-hook!)
-                                  (when @*quit-dirty? ;; when not updating
-                                    (.preventDefault e)
-
-                                    (let [windows (win/get-all-windows)
-                                          window @*win
-                                          multiple-windows? (> (count windows) 1)]
-                                      (cond
-                                        (or multiple-windows? (not mac?) @win/*quitting?)
-                                        (when window
-                                          (win/close-handler win handler/close-watcher-when-orphaned! e)
-                                          (reset! *win nil))
-
-                                        (and mac? (not multiple-windows?))
+                              (git/before-graph-close-hook!)
+                              (when @*quit-dirty? ;; when not updating
+                                (.preventDefault e)
+
+                                (let [windows (win/get-all-windows)
+                                      window @*win
+                                      multiple-windows? (> (count windows) 1)]
+                                  (cond
+                                    (or multiple-windows? (not mac?) @win/*quitting?)
+                                    (when window
+                                      (win/close-handler win handler/close-watcher-when-orphaned! e)
+                                      (reset! *win nil))
+
+                                    (and mac? (not multiple-windows?))
                                         ;; Just hiding - don't do any actual closing operation
-                                        (do (.preventDefault ^js/Event e)
-                                            (if (and mac? (.isFullScreen win))
-                                              (do (.once win "leave-full-screen" #(.hide win))
-                                                  (.setFullScreen win false))
-                                              (.hide win)))
-                                        :else
-                                        nil)))))
+                                    (do (.preventDefault ^js/Event e)
+                                        (if (and mac? (.isFullScreen win))
+                                          (do (.once win "leave-full-screen" #(.hide win))
+                                              (.setFullScreen win false))
+                                          (.hide win)))
+                                    :else
+                                    nil)))))
            (.on app' "before-quit" (fn [_e]
-                                    (reset! win/*quitting? true)))
+                                     (reset! win/*quitting? true)))
 
            (.on app' "activate" #(when @*win (.show win)))))))
 

+ 6 - 6
src/main/electron/listener.cljs

@@ -5,23 +5,23 @@
             [clojure.string :as string]
             [dommy.core :as dom]
             [electron.ipc :as ipc]
+            [frontend.db :as db]
+            [frontend.db.async :as db-async]
             [frontend.db.model :as db-model]
             [frontend.fs.sync :as sync]
             [frontend.fs.watcher-handler :as watcher-handler]
             [frontend.handler.file-sync :as file-sync-handler]
             [frontend.handler.notification :as notification]
+            [frontend.handler.property.util :as pu]
             [frontend.handler.route :as route-handler]
+            [frontend.handler.search :as search-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.user :as user]
-            [frontend.handler.search :as search-handler]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [logseq.common.path :as path]
             [logseq.common.util :as common-util]
-            [promesa.core :as p]
-            [frontend.handler.property.util :as pu]
-            [frontend.db :as db]
-            [frontend.db.async :as db-async]))
+            [promesa.core :as p]))
 
 (defn- safe-api-call
   "Force the callback result to be nil, otherwise, ipc calls could lead to
@@ -87,7 +87,7 @@
                          (route-handler/redirect-to-page! page-name {:block-id block-id}))
 
                        block-id
-                       (p/let [block (db-async/<get-block (state/get-current-repo) block-id)]
+                       (p/let [block (db-async/<get-block (state/get-current-repo) block-id {:children? false})]
                          (if block
                            (if (pu/shape-block? block)
                              (route-handler/redirect-to-page! (get-in block [:block/page :block/uuid]) {:block-id block-id})

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

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

+ 19 - 0
src/main/frontend/common/cache.cljs

@@ -0,0 +1,19 @@
+(ns frontend.common.cache
+  "Utils about cache"
+  (:require [cljs.cache :as cache]))
+
+#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
+;; (def *profile (volatile! {}))
+
+(defn cache-fn
+  "Return a cached version of `f`.
+  cache-key&f-args-fn: return [<cache-key> <args-list-to-f>]"
+  [*cache cache-key&f-args-fn f]
+  (fn [& args]
+    (let [[cache-k f-args] (apply cache-key&f-args-fn args)
+          through-value-fn #(apply f f-args)
+          ;; hit? (cache/has? @*cache cache-k)
+          ;; _ (vswap! *profile update-in [[*cache (.-limit ^js @*cache)] (if hit? :hit :miss)] inc)
+          ;; _ (prn (if hit? :hit :miss) cache-k)
+          cache (vreset! *cache (cache/through through-value-fn @*cache cache-k))]
+      (cache/lookup cache cache-k))))

+ 3 - 3
src/main/frontend/common/file/core.cljs

@@ -99,7 +99,7 @@
                                   (-> (string/replace content #"^\s?#+\s+" "")
                                       (string/replace #"^\s?#+\s?$" ""))
                                   content)
-                        content (content-with-collapsed-state repo format content collapsed?)
+                        content (if db-based? content (content-with-collapsed-state repo format content collapsed?))
                         new-content (indented-block-content (string/trim content) spaces-tabs)
                         sep (if (or markdown-top-heading?
                                     (string/blank? new-content))
@@ -111,13 +111,13 @@
       content)))
 
 (defn- tree->file-content-aux
-  [repo db tree {:keys [init-level] :as opts} context]
+  [repo db tree {:keys [init-level link] :as opts} context]
   (let [block-contents (transient [])]
     (loop [[f & r] tree level init-level]
       (if (nil? f)
         (->> block-contents persistent! flatten (remove nil?))
         (let [page? (nil? (:block/page f))
-              content (if page? nil (transform-content repo db f level opts context))
+              content (if (and page? (not link)) nil (transform-content repo db f level opts context))
               new-content
               (if-let [children (seq (:block/children f))]
                 (cons content (tree->file-content-aux repo db children {:init-level (inc level)} context))

+ 240 - 0
src/main/frontend/common/graph_view.cljs

@@ -0,0 +1,240 @@
+(ns frontend.common.graph-view
+  "Main namespace for graph view fns."
+  (:require [clojure.set :as set]
+            [clojure.string :as string]
+            [datascript.core :as d]
+            [logseq.common.util :as common-util]
+            [logseq.db :as ldb]
+            [logseq.db.common.property-util :as db-property-util]
+            [logseq.db.frontend.entity-plus :as entity-plus]
+            [logseq.db.sqlite.create-graph :as sqlite-create-graph]
+            [logseq.graph-parser.db :as gp-db]))
+
+(defn- build-links
+  [links]
+  (keep (fn [[from to]]
+          (when (and from to)
+            {:source (str from)
+             :target (str to)}))
+        links))
+
+(defn- build-nodes
+  [dark? current-page page-links tags nodes namespaces]
+  (let [page-parents (set (map last namespaces))
+        current-page (or current-page "")
+        pages (common-util/distinct-by :db/id nodes)]
+    (->>
+     pages
+     (remove ldb/hidden?)
+     (remove nil?)
+     (mapv (fn [p]
+             (let [page-title (:block/title p)
+                   current-page? (= page-title current-page)
+                   color (case [dark? current-page?] ; FIXME: Put it into CSS
+                           [false false] "#999"
+                           [false true]  "#045591"
+                           [true false]  "#93a1a1"
+                           [true true]   "#ffffff")
+                   color (if (contains? tags (:db/id p))
+                           (if dark? "orange" "green")
+                           color)
+                   n (get page-links page-title 1)
+                   size (int (* 8 (max 1.0 (js/Math.cbrt n))))]
+               (cond->
+                {:id (str (:db/id p))
+                 :label page-title
+                 :size size
+                 :color color
+                 :block/created-at (:block/created-at p)}
+                 (contains? page-parents (:db/id p))
+                 (assoc :parent true))))))))
+
+                  ;; slow
+(defn- uuid-or-asset?
+  [label]
+  (or (common-util/uuid-string? label)
+      (string/starts-with? label "../assets/")
+      (= label "..")
+      (string/starts-with? label "assets/")
+      (string/ends-with? label ".gif")
+      (string/ends-with? label ".jpg")
+      (string/ends-with? label ".png")))
+
+(defn- remove-uuids-and-files!
+  [nodes]
+  (remove
+   (fn [node] (uuid-or-asset? (:label node)))
+   nodes))
+
+(defn- normalize-page-name
+  [{:keys [nodes links]}]
+  (let [nodes' (->> (remove-uuids-and-files! nodes)
+                    (common-util/distinct-by (fn [node] (:id node)))
+                    (remove nil?))]
+    {:nodes nodes'
+     :links links}))
+
+(defn- build-global-graph
+  [db {:keys [theme journal? orphan-pages? builtin-pages? excluded-pages? created-at-filter]}]
+  (let [dark? (= "dark" theme)
+        relation (ldb/get-pages-relation db journal?)
+        tagged-pages (ldb/get-all-tagged-pages db)
+        namespaces (ldb/get-all-namespace-relation db)
+        tags (set (map second tagged-pages))
+        full-pages (ldb/get-all-pages db)
+        db-based? (entity-plus/db-based-graph? db)
+        created-ats (map :block/created-at full-pages)
+
+            ;; build up nodes
+        full-pages'
+        (cond->> full-pages
+          created-at-filter
+          (filter #(<= (:block/created-at %) (+ (apply min created-ats) created-at-filter)))
+          (not journal?)
+          (remove ldb/journal?)
+          (not excluded-pages?)
+          (remove (fn [p] (true?
+                           (get p (db-property-util/get-pid-2 db :logseq.property/exclude-from-graph-view))))))
+        links (concat relation tagged-pages namespaces)
+        linked (set (mapcat identity links))
+        build-in-pages (->> (if db-based? sqlite-create-graph/built-in-pages-names gp-db/built-in-pages-names)
+                            (map string/lower-case)
+                            set)
+        nodes (cond->> full-pages'
+                (not builtin-pages?)
+                (remove #(contains? build-in-pages (:block/name %)))
+                (not orphan-pages?)
+                (filter #(contains? linked (:db/id %))))
+        links (map (fn [[x y]] [(str x) (str y)]) links)
+        page-links (reduce (fn [m [k v]] (-> (update m k inc)
+                                             (update v inc))) {} links)
+        links (build-links links)
+        nodes (build-nodes dark? nil page-links tags nodes namespaces)]
+    (-> {:nodes nodes
+         :links links}
+        normalize-page-name
+        (assoc :all-pages
+               {:created-at-min (apply min created-ats)
+                :created-at-max (apply max created-ats)}))))
+
+(defn get-pages-that-mentioned-page
+  [db page-id include-journals?]
+  (let [pages (ldb/page-alias-set db page-id)
+        mentioned-pages (->>
+                         (mapcat
+                          (fn [id]
+                            (let [page (d/entity db id)]
+                              (->> (:block/_refs page)
+                                   (keep (fn [ref]
+                                           (if (ldb/page? ref)
+                                             page
+                                             (:block/page ref)))))))
+                          pages)
+                         (common-util/distinct-by :db/id))]
+    (keep (fn [page]
+            (when-not (and (not include-journals?) (ldb/journal? page))
+              (:db/id page)))
+          mentioned-pages)))
+
+(defn get-page-referenced-pages
+  [db page-id]
+  (let [pages (ldb/page-alias-set db page-id)
+        ref-pages (d/q
+                   '[:find [?ref-page ...]
+                     :in $ ?pages
+                     :where
+                     [(untuple ?pages) [?page ...]]
+                     [?block :block/page ?page]
+                     [?block :block/refs ?ref-page]]
+                   db
+                   pages)]
+    ref-pages))
+
+(defn- build-page-graph-other-page-links [db other-pages* show-journal]
+  (let [other-pages (->> other-pages*
+                         (remove nil?)
+                         (set))]
+    (mapcat
+     (fn [page-id]
+       (let [ref-pages (-> (get-page-referenced-pages db page-id)
+                           (set)
+                           (set/intersection other-pages))
+             mentioned-pages (-> (get-pages-that-mentioned-page db page-id show-journal)
+                                 (set)
+                                 (set/intersection other-pages))]
+         (concat
+          (map (fn [p] [page-id p]) ref-pages)
+          (map (fn [p] [p page-id]) mentioned-pages))))
+     other-pages)))
+
+(defn- build-page-graph
+  [db page-uuid theme show-journal]
+  (let [dark? (= "dark" theme)
+        page-entity (d/entity db [:block/uuid page-uuid])
+        db-based? (entity-plus/db-based-graph? db)
+        page-id (:db/id page-entity)
+        tags (when db-based?
+               (set (map :db/id (:block/tags page-entity))))
+        tags (set (remove #(= page-id %) tags))
+        ref-pages (get-page-referenced-pages db page-id)
+        mentioned-pages (get-pages-that-mentioned-page db page-id show-journal)
+        namespaces (ldb/get-all-namespace-relation db)
+        links (concat
+               namespaces
+               (map (fn [ref-page]
+                      [page-id ref-page]) ref-pages)
+               (map (fn [page]
+                      [page-id page]) mentioned-pages)
+               (map (fn [tag]
+                      [page-id tag])
+                    tags))
+        other-pages-links (build-page-graph-other-page-links db (concat ref-pages mentioned-pages) show-journal)
+        links (->> (concat links other-pages-links)
+                   (remove nil?)
+                   (distinct)
+                   (build-links))
+        nodes (->> (concat
+                    [page-id]
+                    ref-pages
+                    mentioned-pages
+                    tags)
+                   (remove nil?)
+                   (map #(d/entity db %))
+                   (common-util/distinct-by :db/id))
+        nodes (build-nodes dark? (:block/title page-entity) links tags nodes namespaces)]
+    (normalize-page-name
+     {:nodes nodes
+      :links links})))
+
+(defn- build-block-graph
+  "Builds a citation/reference graph for a given block uuid."
+  [db block-uuid theme]
+  (when-let [block (and (uuid? block-uuid) (d/entity db [:block/uuid block-uuid]))]
+    (let [dark? (= "dark" theme)
+          ref-blocks (->> (concat (:block/_refs block) (:block/refs block))
+                          (map (fn [b]
+                                 (if (ldb/page? b) b (:block/page b))))
+                          (remove (fn [node] (= (:db/id block) (:db/id node))))
+                          (common-util/distinct-by :db/id))
+          namespaces (ldb/get-all-namespace-relation db)
+          links (->> (concat
+                      namespaces
+                      (map (fn [p] [(:db/id block) (:db/id p)]) ref-blocks))
+                     (remove nil?)
+                     (distinct)
+                     (build-links))
+          nodes (->> (cons block ref-blocks)
+                     distinct
+                       ;; FIXME: get block tags
+                     )
+          nodes (build-nodes dark? block links #{} nodes namespaces)]
+      (normalize-page-name
+       {:nodes nodes
+        :links links}))))
+
+(defn build-graph
+  [db opts]
+  (case (:type opts)
+    :global (build-global-graph db opts)
+    :block (build-block-graph db (:block/uuid opts) (:theme opts))
+    :page (build-page-graph db (:block/uuid opts) (:theme opts) (:show-journal? opts))))

+ 19 - 5
src/main/frontend/common/missionary.cljs

@@ -85,14 +85,28 @@
     (let [x (m/?> (m/relieve {} >in))]
       (m/amb x (do (m/? (m/sleep dur-ms)) (m/amb))))))
 
+(defn- fail-case-default-handler
+  [e]
+  (when-not (instance? Cancelled e)
+    (log/error :run-task*-failed e)))
+
 (defn run-task
   "Return the canceler"
-  [task key & {:keys [succ fail]}]
-  (task (or succ #(log/info :key key :succ %)) (or fail #(log/info :key key :stopped %))))
+  [key' task & {:keys [succ fail]}]
+  (let [cancel (task (or succ #(log/info :key key' :succ %)) (or fail fail-case-default-handler))]
+    #(cancel)))
+
+(defn run-task*
+  "Return the canceler"
+  [task & {:keys [succ fail]}]
+  (let [cancel (task (or succ (constantly nil)) (or fail fail-case-default-handler))]
+    #(cancel)))
 
 (defn run-task-throw
-  [task key & {:keys [succ]}]
-  (task (or succ #(log/info :key key :succ %)) #(throw (ex-info "task stopped" {:key key :e %}))))
+  "Return the canceler"
+  [key' task & {:keys [succ]}]
+  (let [cancel (task (or succ #(log/info :key key' :succ %)) #(throw (ex-info "task stopped" {:key key' :e %})))]
+    #(cancel)))
 
 (defonce ^:private *background-task-cancelers ; key -> canceler
   (volatile! {}))
@@ -105,7 +119,7 @@
     (canceler)
     (vswap! *background-task-cancelers assoc key' nil))
   (prn :run-background-task key')
-  (let [canceler (run-task task key')]
+  (let [canceler (run-task key' task)]
     (vswap! *background-task-cancelers assoc key' canceler)
     nil))
 

+ 5 - 1
src/main/frontend/common/thread_api.cljc

@@ -18,13 +18,17 @@
   (assert (vector? params) params)
   `(vswap! *thread-apis assoc
            ~qualified-keyword-name
-           (fn ~params ~@body)))
+           (fn ~(symbol (str "thread-api--" (name qualified-keyword-name))) ~params ~@body)))
+
+
+#?(:cljs (def *profile (volatile! {})))
 
 #?(:cljs
    (defn remote-function
      "Return a promise whose value is transit-str."
      [qualified-kw-str args-direct-passthrough? args-transit-str-or-args-array]
      (let [qkw (keyword qualified-kw-str)]
+       (vswap! *profile update qkw inc)
        (if-let [f (@*thread-apis qkw)]
          (let [result (apply f (cond-> args-transit-str-or-args-array
                                  (not args-direct-passthrough?) ldb/read-transit-str))

+ 7 - 34
src/main/frontend/components/all_pages.cljs

@@ -1,17 +1,12 @@
 (ns frontend.components.all-pages
   "All pages"
   (:require [frontend.components.block :as component-block]
-            [frontend.components.page :as component-page]
             [frontend.components.views :as views]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
-            [frontend.handler.page :as page-handler]
             [frontend.state :as state]
             [logseq.common.config :as common-config]
-            [logseq.shui.hooks :as hooks]
-            [logseq.shui.ui :as shui]
-            [promesa.core :as p]
             [rum.core :as rum]))
 
 (defn- columns
@@ -19,7 +14,8 @@
   (->> [{:id :block/title
          :name (t :block/name)
          :cell (fn [_table row _column]
-                 (component-block/page-cp {} row))
+                 (component-block/page-cp {:show-non-exists-page? true
+                                           :skip-async-load? true} row))
          :type :string}
         (when (not (config/db-based-graph? (state/get-current-repo)))
           {:id :block/type
@@ -27,46 +23,23 @@
            :cell (fn [_table row _column]
                    (let [type (get row :block/type)]
                      [:div.capitalize type]))
-           :get-value (fn [row] (get row :block/type))
            :type :string})
         {:id :block.temp/refs-count
          :name (t :page/backlinks)
-         :cell (fn [_table row _column] (:block.temp/refs-count row))
+         :cell (fn [_table row _column]
+                 (:block.temp/refs-count row))
          :type :number}]
        (remove nil?)
        vec))
 
-(defn- get-all-pages
-  []
-  (->> (page-handler/get-all-pages (state/get-current-repo))
-       (map (fn [p] (assoc p :id (:db/id p))))))
-
 (rum/defc all-pages < rum/static
   []
-  (let [[data set-data!] (rum/use-state nil)
-        columns' (views/build-columns {} (columns)
+  (let [columns' (views/build-columns {} (columns)
                                       {:with-object-name? false
                                        :with-id? false})]
-    (hooks/use-effect!
-     (fn []
-       (p/let [result (state/<invoke-db-worker :thread-api/get-page-refs-count (state/get-current-repo))
-               data (get-all-pages)
-               data (map (fn [row] (assoc row :block.temp/refs-count (get result (:db/id row) 0))) data)]
-         (set-data! data)))
-     [])
     [:div.ls-all-pages.w-full.mx-auto
-     (views/view {:data data
-                  :set-data! set-data!
-                  :view-parent (db/get-page common-config/views-page-name)
+     (views/view {:view-parent (db/get-page common-config/views-page-name)
                   :view-feature-type :all-pages
                   :show-items-count? true
                   :columns columns'
-                  :title-key :all-pages/table-title
-                  :on-delete-rows (fn [table selected-rows]
-                                    (shui/dialog-open!
-                                     (component-page/batch-delete-dialog
-                                      selected-rows false
-                                      (fn []
-                                        (when-let [f (get-in table [:data-fns :set-row-selection!])]
-                                          (f {}))
-                                        (set-data! (get-all-pages))))))})]))
+                  :title-key :all-pages/table-title})]))

+ 389 - 311
src/main/frontend/components/block.cljs

@@ -678,7 +678,8 @@
                        (editor-handler/block->data-transfer! page-name e true))
       :on-mouse-over #(reset! *hover? true)
       :on-mouse-leave #(reset! *hover? false)
-      :on-click (fn [e] (when stop-click-event? (util/stop e)))
+      :on-click (fn [e]
+                  (when stop-click-event? (util/stop e)))
       :on-pointer-down (fn [^js e]
                          (cond
                            (and meta-click? (util/meta-key? e))
@@ -697,7 +698,8 @@
       :on-pointer-up (fn [e]
                        (when @*mouse-down?
                          (state/clear-edit!)
-                         (when-not (:disable-click? config)
+                         (when-not (or (:disable-click? config)
+                                       (:disable-redirect? config))
                            (open-page-ref config page-entity e page-name contents-page?))
                          (reset! *mouse-down? false)))
       :on-key-up (fn [e] (when (and e (= (.-key e) "Enter") (not meta-click?))
@@ -895,7 +897,7 @@
   (let [db-based? (config/db-based-graph? (state/get-current-repo))
         ->ref (if db-based? page-ref/->page-ref block-ref/->block-ref)]
     [:span.warning.mr-1 {:title "Node ref invalid"}
-     (->ref id)]))
+     (->ref (str id))]))
 
 (defn inline-text
   ([format v]
@@ -907,27 +909,32 @@
 
 (rum/defcs page-cp-inner < db-mixins/query rum/reactive
   {:init (fn [state]
-           (let [page (last (:rum/args state))
+           (let [args (:rum/args state)
+                 [config page] args
                  *result (atom nil)
-                 page-name (or (:block/uuid page)
-                               (when-let [s (:block/name page)]
-                                 (string/trim s)))
-                 page-entity (if (e/entity? page) page (db/get-page page-name))]
-             (if page-entity
+                 page-id-or-name (or (:db/id page)
+                                     (:block/uuid page)
+                                     (when-let [s (:block/name page)]
+                                       (string/trim s)))
+                 page-entity (if (e/entity? page) page (db/get-page page-id-or-name))]
+             (cond
+               page-entity
                (reset! *result page-entity)
-               (p/let [query-result (db-async/<get-block (state/get-current-repo) page-name {:children? false})
-                       result (if (e/entity? query-result)
-                                query-result
-                                (:block query-result))]
+               (or (:skip-async-load? config) (:table-view? config))
+               (reset! *result page)
+               :else
+               (p/let [result (db-async/<get-block (state/get-current-repo) page-id-or-name {:children? false
+                                                                                             :skip-refresh? true
+                                                                                             :including-property-vals? false})]
                  (reset! *result result)))
 
              (assoc state :*entity *result)))}
   "Component for a page. `page` argument contains :block/name which can be (un)sanitized page name.
    Keys for `config`:
    - `:preview?`: Is this component under preview mode? (If true, `page-preview-trigger` won't be registered to this `page-cp`)"
-  [state {:keys [label children preview? disable-preview? show-non-exists-page? tag?] :as config} page]
+  [state {:keys [label children preview? disable-preview? show-non-exists-page? table-view? tag? _skip-async-load?] :as config} page]
   (when-let [entity' (rum/react (:*entity state))]
-    (let [entity (db/sub-block (:db/id entity'))]
+    (let [entity (or (db/sub-block (:db/id entity')) entity')]
       (cond
         entity
         (if (or (ldb/page? entity) (:block/tags entity))
@@ -954,9 +961,11 @@
         (and (:block/name page) (util/uuid-string? (:block/name page)))
         (invalid-node-ref (:block/name page))
 
-        (and (:block/name page) show-non-exists-page?)
-        (page-inner config {:block/title (:block/name page)
-                            :block/name (:block/name page)} children label)
+        (and (:block/name page) (or show-non-exists-page? table-view?))
+        (page-inner config (merge
+                            {:block/title (:block/name page)
+                             :block/name (:block/name page)}
+                            page) children label)
 
         (:block/name page)
         [:span (str (when tag? "#")
@@ -970,7 +979,7 @@
 (rum/defc page-cp
   [config page]
   (rum/with-key (page-cp-inner config page)
-    (or (str (:block/uuid page)) (:block/name page))))
+    (or (str (:db/id page)) (str (:block/uuid page)) (:block/name page))))
 
 (rum/defc asset-reference
   [config title path]
@@ -1124,63 +1133,73 @@
 
 (declare block-container)
 
-(rum/defc block-embed < rum/reactive
-  {:init (fn [state]
-           (let [block-id (second (:rum/args state))]
-             (db-async/<get-block (state/get-current-repo) block-id))
-           state)}
-  [config uuid]
-  (if (state/sub-async-query-loading (str uuid))
-    [:span "Loading..."]
-    (when-let [block (db/entity [:block/uuid uuid])]
+(rum/defc block-embed
+  [config block-uuid]
+  (let [[block set-block!] (hooks/use-state (db/entity [:block/uuid block-uuid]))]
+    (hooks/use-effect!
+     (fn []
+       (p/let [block (db-async/<get-block (state/get-current-repo)
+                                          block-uuid
+                                          {:children? false
+                                           :skip-refresh? true})]
+         (set-block! block)))
+     [])
+    (when block
       [:div.color-level.embed-block.bg-base-2
        {:style {:z-index 2}
         :on-pointer-down (fn [e] (.stopPropagation e))}
        [:div.px-3.pt-1.pb-2
         (let [config' (assoc config
                              :db/id (:db/id block)
-                             :id (str uuid)
-                             :embed-id uuid
+                             :id (str block-uuid)
+                             :embed-id block-uuid
                              :embed? true
                              :embed-parent (:block config)
                              :ref? false)]
-          (blocks-container config' [block]))]])))
+          (block-container config' block))]])))
 
-(rum/defc page-embed < rum/reactive db-mixins/query
-  {:init (fn [state]
-           (let [page-name (second (:rum/args state))
-                 page-name' (util/page-name-sanity-lc (string/trim page-name))]
-             (db-async/<get-block (state/get-current-repo) page-name'))
-           state)}
+(rum/defc page-embed-aux < rum/reactive db-mixins/query
+  [config block]
+  (let [current-page (state/get-current-page)
+        block (db/sub-block (:db/id block))
+        whiteboard-page? (model/whiteboard-page? block)
+        page-name (:block/name block)]
+    [:div.color-level.embed.embed-page.bg-base-2
+     {:class (when (:sidebar? config) "in-sidebar")
+      :on-pointer-down #(.stopPropagation %)}
+     [:section.flex.items-center.p-1.embed-header
+      [:div.mr-3 svg/page]
+      (page-cp config block)]
+     (when (and
+            (not= (util/page-name-sanity-lc (or current-page ""))
+                  page-name)
+            (not= (util/page-name-sanity-lc (get config :id ""))
+                  page-name))
+       (if whiteboard-page?
+         ((state/get-component :whiteboard/tldraw-preview) (:block/uuid block))
+         (let [blocks (ldb/get-children block)
+               config' (assoc config
+                              :db/id (:db/id block)
+                              :id page-name
+                              :embed? true
+                              :page-embed? true
+                              :ref? false)]
+           (blocks-container config' blocks))))]))
+
+(rum/defc page-embed
   [config page-name]
-  (let [page-name (util/page-name-sanity-lc (string/trim page-name))
-        current-page (state/get-current-page)]
-    (if (and page-name (state/sub-async-query-loading page-name))
-      (ui/loading "embed")
-      (let [block (model/get-page page-name)
-            block (db/sub-block (:db/id block))
-            whiteboard-page? (model/whiteboard-page? block)]
-        [:div.color-level.embed.embed-page.bg-base-2
-         {:class (when (:sidebar? config) "in-sidebar")
-          :on-pointer-down #(.stopPropagation %)}
-         [:section.flex.items-center.p-1.embed-header
-          [:div.mr-3 svg/page]
-          (page-cp config block)]
-         (when (and
-                (not= (util/page-name-sanity-lc (or current-page ""))
-                      page-name)
-                (not= (util/page-name-sanity-lc (get config :id ""))
-                      page-name))
-           (if whiteboard-page?
-             ((state/get-component :whiteboard/tldraw-preview) (:block/uuid block))
-             (let [blocks (ldb/get-children block)
-                   config' (assoc config
-                                  :db/id (:db/id block)
-                                  :id page-name
-                                  :embed? true
-                                  :page-embed? true
-                                  :ref? false)]
-               (blocks-container config' blocks))))]))))
+  (let [page-name (util/page-name-sanity-lc (string/trim page-name))]
+    (let [[block set-block!] (hooks/use-state nil)]
+      (hooks/use-effect!
+       (fn []
+         (p/let [block (db-async/<get-block (state/get-current-repo)
+                                            page-name
+                                            {:children? true
+                                             :skip-refresh? true})]
+           (set-block! block)))
+       [])
+      (when block
+        (page-embed-aux config block)))))
 
 (defn- get-label-text
   [label]
@@ -1230,85 +1249,95 @@
                          :*timer *timer :*timer1 *timer1
                          :render render})))
 
-(rum/defc block-reference < rum/reactive db-mixins/query
-  {:init (fn [state]
-           (let [block-id (second (:rum/args state))]
-             (db-async/<get-block (state/get-current-repo) block-id :children? false))
-           state)}
-  [config id label]
-  (if (= (:block/uuid (:block config)) id)
-    [:span.warning.text-sm "Self reference"]
-    (if-let [block-id (and id (if (uuid? id) id (parse-uuid id)))]
-      (if (state/sub-async-query-loading (str block-id))
-        [:span "Loading..."]
-        (let [block (db/entity [:block/uuid block-id])
-              db-id (:db/id block)
-              block (when db-id (db/sub-block db-id))
-              block-type (keyword (pu/lookup block :logseq.property/ls-type))
-              hl-type (pu/lookup block :logseq.property.pdf/hl-type)
-              repo (state/get-current-repo)
-              stop-inner-events? (= block-type :whiteboard-shape)]
-          (if (and block (:block/title block))
-            (let [content-cp (block-content (assoc config :block-ref? true :stop-events? stop-inner-events?)
-                                            block nil (:block/uuid block)
-                                            (:slide? config))
-                  display-type (:logseq.property.node/display-type block)]
-              (if (and display-type (not (contains? #{:quote :math} display-type)))
-                content-cp
-                (let [title [:span.block-ref content-cp]
-                      inner (cond
-                              (seq label)
-                              (->elem
-                               :span.block-ref
-                               (map-inline config label))
-                              :else
-                              title)]
-                  [:div.block-ref-wrap.inline
-                   {:data-type    (name (or block-type :default))
-                    :data-hl-type hl-type
-                    :on-pointer-down
-                    (fn [^js/MouseEvent e]
-                      (if (util/right-click? e)
-                        (state/set-state! :block-ref/context {:block (:block config)
-                                                              :block-ref block-id})
-                        (when (and
-                               (or (gobj/get e "shiftKey")
-                                   (not (.. e -target (closest ".blank"))))
-                               (not (util/right-click? e)))
-                          (util/stop e)
-
-                          (cond
-                            (gobj/get e "shiftKey")
-                            (state/sidebar-add-block!
-                             (state/get-current-repo)
-                             (:db/id block)
-                             :block-ref)
-
-                            (and (util/meta-key? e) (whiteboard-handler/inside-portal? (.-target e)))
-                            (whiteboard-handler/add-new-block-portal-shape!
-                             (:block/uuid block)
-                             (whiteboard-handler/closest-shape (.-target e)))
-
-                            :else
-                            (match [block-type (util/electron?)]
+(rum/defc block-reference-aux < rum/reactive db-mixins/query
+  [config block-id label]
+  (let [block (db/entity [:block/uuid block-id])
+        db-id (:db/id block)
+        block (when db-id (db/sub-block db-id))
+        block-type (keyword (pu/lookup block :logseq.property/ls-type))
+        hl-type (pu/lookup block :logseq.property.pdf/hl-type)
+        repo (state/get-current-repo)
+        stop-inner-events? (= block-type :whiteboard-shape)]
+    (if (and block (:block/title block))
+      (let [content-cp (block-content (assoc config :block-ref? true :stop-events? stop-inner-events?)
+                                      block nil (:block/uuid block)
+                                      (:slide? config))
+            display-type (:logseq.property.node/display-type block)]
+        (if (and display-type (not (contains? #{:quote :math} display-type)))
+          content-cp
+          (let [title [:span.block-ref content-cp]
+                inner (cond
+                        (seq label)
+                        (->elem
+                         :span.block-ref
+                         (map-inline config label))
+                        :else
+                        title)]
+            [:div.block-ref-wrap.inline
+             {:data-type    (name (or block-type :default))
+              :data-hl-type hl-type
+              :on-pointer-down
+              (fn [^js/MouseEvent e]
+                (if (util/right-click? e)
+                  (state/set-state! :block-ref/context {:block (:block config)
+                                                        :block-ref block-id})
+                  (when (and
+                         (or (gobj/get e "shiftKey")
+                             (not (.. e -target (closest ".blank"))))
+                         (not (util/right-click? e)))
+                    (util/stop e)
+
+                    (cond
+                      (gobj/get e "shiftKey")
+                      (state/sidebar-add-block!
+                       (state/get-current-repo)
+                       (:db/id block)
+                       :block-ref)
+
+                      (and (util/meta-key? e) (whiteboard-handler/inside-portal? (.-target e)))
+                      (whiteboard-handler/add-new-block-portal-shape!
+                       (:block/uuid block)
+                       (whiteboard-handler/closest-shape (.-target e)))
+
+                      :else
+                      (match [block-type (util/electron?)]
                           ;; pdf annotation
-                              [:annotation true] (pdf-assets/open-block-ref! block)
+                        [:annotation true] (pdf-assets/open-block-ref! block)
 
-                              [:whiteboard-shape true] (route-handler/redirect-to-page!
-                                                        (get-in block [:block/page :block/uuid]) {:block-id block-id})
+                        [:whiteboard-shape true] (route-handler/redirect-to-page!
+                                                  (get-in block [:block/page :block/uuid]) {:block-id block-id})
 
                           ;; default open block page
-                              :else (route-handler/redirect-to-page! id))))))}
-
-                   (if (and (not (util/mobile?))
-                            (not (:preview? config))
-                            (not (shui-dialog/has-modal?))
-                            (nil? block-type))
-                     (block-reference-preview inner
-                                              {:repo repo :config config :id block-id})
-                     inner)])))
-            (invalid-node-ref id))))
-      (invalid-node-ref id))))
+                        :else (route-handler/redirect-to-page! block-id))))))}
+
+             (if (and (not (util/mobile?))
+                      (not (:preview? config))
+                      (not (shui-dialog/has-modal?))
+                      (nil? block-type))
+               (block-reference-preview inner
+                                        {:repo repo :config config :id block-id})
+               inner)])))
+      (do
+        (log/warn :invalid-node block)
+        (invalid-node-ref block-id)))))
+
+(rum/defc block-reference
+  [config id label]
+  (let [block-id (and id (if (uuid? id) id (parse-uuid id)))
+        [block set-block!] (hooks/use-state (db/entity [:block/uuid block-id]))]
+    (hooks/use-effect!
+     (fn []
+       (p/let [block (db-async/<get-block (state/get-current-repo)
+                                          block-id
+                                          {:children? false
+                                           :skip-refresh? true})]
+         (set-block! block)))
+     [])
+    (if (and block-id (= (:block/uuid (:block config)) block-id))
+      [:span.warning.text-sm "Self reference"]
+      (if block
+        (block-reference-aux config block-id label)
+        (invalid-node-ref block-id)))))
 
 (defn- render-macro
   [config name arguments macro-content format]
@@ -2073,21 +2102,12 @@
 
 (defn- block-content-empty?
   [block]
-  (let [ast-title (:block.temp/ast-title block)
-        ast-body (:block.temp/ast-body block)
-        db-based? (config/db-based-graph? (state/get-current-repo))]
-    (and
-     (or db-based?
-         (property-file/properties-hidden? (:block/properties block)))
+  (string/blank? (:block/title block)))
 
-     (empty? ast-title)
-
-     (every? #(= % ["Horizontal_Rule"]) ast-body))))
-
-(rum/defcs block-control < rum/reactive
+(rum/defcs ^:large-vars/cleanup-todo block-control < rum/reactive
   (rum/local false ::dragging?)
-  [state config block {:keys [uuid block-id collapsed? *control-show? edit? selected?]}]
-  (let [*dragging?         (::dragging? state)
+  [state config block {:keys [uuid block-id collapsed? *control-show? edit? selected? top? bottom?]}]
+  (let [*bullet-dragging?         (::dragging? state)
         doc-mode?          (state/sub :document/mode?)
         control-show?      (util/react *control-show?)
         ref?               (:ref? config)
@@ -2138,11 +2158,11 @@
                       {:id (str "dot-" uuid)
                        :draggable true
                        :on-drag-start (fn [event]
-                                        (reset! *dragging? true)
+                                        (reset! *bullet-dragging? true)
                                         (util/stop-propagation event)
                                         (bullet-drag-start event block uuid block-id))
                        :on-drag-end (fn [_]
-                                      (reset! *dragging? false))
+                                      (reset! *bullet-dragging? false))
                        :blockid (str uuid)
                        :class (str (when collapsed? "bullet-closed")
                                    (when (and (:document/mode? config)
@@ -2171,8 +2191,8 @@
                        (or
                         (and empty-content?
                              (not edit?)
-                             (not (:block.temp/top? block))
-                             (not (:block.temp/bottom? block))
+                             (not top?)
+                             (not bottom?)
                              (not (util/react *control-show?))
                              (not (:logseq.property/created-from-property  block)))
                         (and doc-mode?
@@ -2182,7 +2202,7 @@
 
                        :else
                        bullet)]
-         (if (and (config/db-based-graph?) (not @*dragging?))
+         (if (and (config/db-based-graph?) (not @*bullet-dragging?))
            (ui/tooltip
             bullet'
             [:div.flex.flex-col.gap-1.p-2
@@ -2465,7 +2485,7 @@
       :else
       nil)))
 
-(rum/defcs db-properties-cp <
+(rum/defcs db-properties-cp < rum/static
   {:init (fn [state]
            (let [container-id (or (:container-id (first (:rum/args state)))
                                   (state/get-next-container-id))]
@@ -2556,25 +2576,29 @@
             (let [block (or (db/entity [:block/uuid (:block/uuid block)]) block)]
               (editor-handler/clear-selection!)
               (editor-handler/unhighlight-blocks!)
-              (let [f #(let [cursor-range (some-> (gdom/getElement block-id)
-                                                  (dom/by-class "block-content-inner")
-                                                  first
-                                                  util/caret-range)
-                             {:block/keys [title format]} block
-                             content (if (config/db-based-graph? (state/get-current-repo))
-                                       (:block/title block)
-                                       (->> title
-                                            (property-file/remove-built-in-properties-when-file-based
-                                             (state/get-current-repo) format)
-                                            (drawer/remove-logbook)))]
-                         (state/set-editing!
-                          edit-input-id
-                          content
-                          block
-                          cursor-range
-                          {:db (db/get-db)
-                           :move-cursor? false
-                           :container-id (:container-id config)}))]
+              (let [f #(p/do!
+                        (when-not (:block.temp/fully-loaded? (db/entity (:db/id block)))
+                          (db-async/<get-block (state/get-current-repo) (:db/id block) {:children? false}))
+                        (let [cursor-range (some-> (gdom/getElement block-id)
+                                                   (dom/by-class "block-content-inner")
+                                                   first
+                                                   util/caret-range)
+                              block (db/entity (:db/id block))
+                              {:block/keys [title format]} block
+                              content (if (config/db-based-graph? (state/get-current-repo))
+                                        (:block/title block)
+                                        (->> title
+                                             (property-file/remove-built-in-properties-when-file-based
+                                              (state/get-current-repo) format)
+                                             (drawer/remove-logbook)))]
+                          (state/set-editing!
+                           edit-input-id
+                           content
+                           block
+                           cursor-range
+                           {:db (db/get-db)
+                            :move-cursor? false
+                            :container-id (:container-id config)})))]
                 ;; wait a while for the value of the caret range
                 (p/do!
                  (state/pub-event! [:editor/save-code-editor])
@@ -2882,7 +2906,8 @@
        (merge attrs))
 
      [:<>
-      (when (> (count content) (state/block-content-max-length (state/get-current-repo)))
+      (when (and (> (count content) (state/block-content-max-length (state/get-current-repo)))
+                 (not (contains? #{:code} (:logseq.property.node/display-type block))))
         [:div.warning.text-sm
          "Large block will not be editable or searchable to not slow down the app, please use another editor to edit this block."])
       [:div.flex.flex-row.justify-between.block-content-inner
@@ -3088,7 +3113,9 @@
 
 (rum/defc breadcrumb-fragment
   [config block label opts]
-  [:a {:on-pointer-up
+  [:a {:on-pointer-down (fn [e]
+                          (when (some? (:sidebar-key config)) (util/stop e)))
+       :on-pointer-up
        (fn [e]
          (cond
            (gobj/get e "shiftKey")
@@ -3105,13 +3132,7 @@
              (reset! (:navigating-block opts) (:block/uuid block)))
 
            (some? (:sidebar-key config))
-           (do
-             (util/stop e)
-             (state/sidebar-replace-block!
-              (:sidebar-key config)
-              [(state/get-current-repo)
-               (:db/id block)
-               (if (:block/name block) :page :block)]))
+           nil
 
            :else
            (when-let [uuid (:block/uuid block)]
@@ -3125,81 +3146,92 @@
                             :class "opacity-50 mx-1"}))
 
 ;; "block-id - uuid of the target block of breadcrumb. page uuid is also acceptable"
-(rum/defc breadcrumb < rum/reactive
-  {:init (fn [state]
-           (let [args (:rum/args state)
-                 block-id (nth args 2)
-                 depth (:level-limit (last args))]
-             (p/let [id (:db/id (db/entity [:block/uuid block-id]))
-                     _block (db-async/<get-block (state/get-current-repo) block-id
-                                                 {:children? false})]
-               (when id (db-async/<get-block-parents (state/get-current-repo) id depth)))
-             state))}
+(rum/defc breadcrumb-aux < rum/reactive
   [config repo block-id {:keys [show-page? indent? end-separator? level-limit _navigating-block]
                          :or {show-page? true
                               level-limit 3}
                          :as opts}]
-  (when block-id
-    (let [_ (state/sub-async-query-loading (str block-id "-parents"))
-          from-property (when (and block-id (config/db-based-graph? repo))
-                          (:logseq.property/created-from-property (db/entity [:block/uuid block-id])))
-          parents (db/get-block-parents repo block-id {:depth (inc level-limit)})
-          parents (remove nil? (concat parents [from-property]))
-          page (or (db/get-block-page repo block-id) ;; only return for block uuid
-                   (model/query-block-by-uuid block-id)) ;; return page entity when received page uuid
-          page-name (:block/name page)
-          page-title (:block/title page)
-          show? (or (seq parents) show-page? page-name)
-          parents (if (= page-name (:block/name (first parents)))
-                    (rest parents)
-                    parents)
-          more? (> (count parents) level-limit)
-          parents (if more? (take-last level-limit parents) parents)
-          config (assoc config :breadcrumb? true)]
-      (when show?
-        (let [page-name-props (when show-page?
-                                [page
-                                 (page-cp (dissoc config :breadcrumb? true) page)
-                                 {:block/name (or page-title page-name)}])
-              parents-props (doall
-                             (for [{:block/keys [uuid name title] :as block} parents]
-                               (if name
-                                 [block (page-cp {} block)]
-                                 (let [result (block/parse-title-and-body
-                                               uuid
-                                               (get block :block/format :markdown)
-                                               (:block/pre-block? block)
-                                               title)
-                                       ast-body (:block.temp/ast-body result)
-                                       ast-title (:block.temp/ast-title result)
-                                       config (assoc config :block/uuid uuid)]
-                                   [block
-                                    (when ast-title
-                                      (if (seq ast-title)
-                                        (->elem :span (map-inline config ast-title))
-                                        (->elem :div (markup-elements-cp config ast-body))))]))))
-              breadcrumbs (->> (into [] parents-props)
-                               (concat [page-name-props] (when more? [:more]))
-                               (filterv identity)
-                               (map (fn [x]
-                                      (if (and (vector? x) (second x))
-                                        (let [[block label] x]
-                                          (rum/with-key (breadcrumb-fragment config block label opts)
-                                            (str (:block/uuid block))))
-                                        [:span.opacity-70 {:key "dots"} "⋯"])))
-                               (interpose (rum/with-key (breadcrumb-separator) "icon")))]
-          (when (seq breadcrumbs)
-            [:div.breadcrumb.block-parents
-             {:class (when (seq breadcrumbs)
-                       (str (when-not (:search? config)
-                              " my-2")
-                            (when indent?
-                              " ml-4")))}
-             (when (and (false? (:top-level? config))
-                        (seq parents))
-               (breadcrumb-separator))
-             breadcrumbs
-             (when end-separator? (breadcrumb-separator))]))))))
+  (let [from-property (when (and block-id (config/db-based-graph? repo))
+                        (:logseq.property/created-from-property (db/entity [:block/uuid block-id])))
+        parents (db/get-block-parents repo block-id {:depth (inc level-limit)})
+        parents (remove nil? (concat parents [from-property]))
+        page (or (db/get-block-page repo block-id) ;; only return for block uuid
+                 (model/query-block-by-uuid block-id)) ;; return page entity when received page uuid
+        page-name (:block/name page)
+        page-title (:block/title page)
+        show? (or (seq parents) show-page? page-name)
+        parents (if (= page-name (:block/name (first parents)))
+                  (rest parents)
+                  parents)
+        more? (> (count parents) level-limit)
+        parents (if more? (take-last level-limit parents) parents)
+        config (assoc config
+                      :breadcrumb? true
+                      :disable-redirect? true
+                      :disable-preview? true
+                      :stop-click-event? false)]
+    (when show?
+      (let [page-name-props (when show-page?
+                              [page
+                               (page-cp (dissoc config :breadcrumb? true) page)
+                               {:block/name (or page-title page-name)}])
+            parents-props (doall
+                           (for [{:block/keys [uuid name title] :as block} parents]
+                             (if name
+                               [block (page-cp {} block)]
+                               (let [result (block/parse-title-and-body
+                                             uuid
+                                             (get block :block/format :markdown)
+                                             (:block/pre-block? block)
+                                             title)
+                                     ast-body (:block.temp/ast-body result)
+                                     ast-title (:block.temp/ast-title result)
+                                     config (assoc config :block/uuid uuid)]
+                                 [block
+                                  (when ast-title
+                                    (if (seq ast-title)
+                                      (->elem :span (map-inline config ast-title))
+                                      (->elem :div (markup-elements-cp config ast-body))))]))))
+            breadcrumbs (->> (into [] parents-props)
+                             (concat [page-name-props] (when more? [:more]))
+                             (filterv identity)
+                             (map (fn [x]
+                                    (if (and (vector? x) (second x))
+                                      (let [[block label] x]
+                                        (rum/with-key (breadcrumb-fragment config block label opts)
+                                          (str (:block/uuid block))))
+                                      [:span.opacity-70 {:key "dots"} "⋯"])))
+                             (interpose (rum/with-key (breadcrumb-separator) "icon")))]
+        (when (seq breadcrumbs)
+          [:div.breadcrumb.block-parents
+           {:class (when (seq breadcrumbs)
+                     (str (when-not (or (:search? config) (:list-view? config))
+                            " my-2")
+                          (when indent?
+                            " ml-4")))}
+           (when (and (false? (:top-level? config))
+                      (seq parents))
+             (breadcrumb-separator))
+           breadcrumbs
+           (when end-separator? (breadcrumb-separator))])))))
+
+(rum/defc breadcrumb
+  [config repo block-id {:keys [_show-page? _indent? _end-separator? level-limit _navigating-block]
+                         :or {level-limit 3}
+                         :as opts}]
+  (let [[block set-block!] (hooks/use-state (db/entity [:block/uuid block-id]))]
+    (hooks/use-effect!
+     (fn []
+       (p/let [block (db-async/<get-block (state/get-current-repo)
+                                          block-id
+                                          {:children? false
+                                           :skip-refresh? true})
+               _ (when-let [id (:db/id block)]
+                   (db-async/<get-block-parents (state/get-current-repo) id level-limit))]
+         (set-block! block)))
+     [])
+    (when block
+      (breadcrumb-aux config repo block-id opts))))
 
 (defn- block-drag-over
   [event uuid top? block-id *move-to']
@@ -3359,7 +3391,8 @@
     (nil? (:level config))
     (assoc :level 0)))
 
-(defn- build-block [config block* {:keys [navigating-block navigated?]}]
+(defn- build-block
+  [config block* {:keys [navigating-block navigated?]}]
   (let [linked-block (:block/link (db/entity (:db/id block*)))
         block (cond
                 (or (and (:custom-query? config)
@@ -3377,8 +3410,7 @@
 
                 :else
                 block*)
-        result (merge (db/sub-block (:db/id block))
-                      (select-keys block [:block/level :block.temp/top? :block.temp/bottom?]))]
+        result (or (db/sub-block (:db/id block)) block*)]
     (if linked-block
       [block* result]
       [nil result])))
@@ -3402,17 +3434,12 @@
           [:div.my-1 {:style {:margin-left 42}}
            (block-container (assoc config :property? true) query)])))))
 
-(rum/defcs ^:large-vars/cleanup-todo block-container-inner < rum/reactive db-mixins/query
+(rum/defcs ^:large-vars/cleanup-todo block-container-inner-aux < rum/reactive db-mixins/query
   {:init (fn [state]
-           (let [*ref (atom nil)
-                 block (nth (:rum/args state) 3)
-                 block-id (:db/id block)
-                 repo (state/get-current-repo)]
-             (db-async/<get-block repo block-id :children? true)
+           (let [*ref (atom nil)]
              (assoc state ::ref *ref)))}
-  [state container-state repo config* block {:keys [navigating-block navigated?]}]
+  [state container-state repo config* block {:keys [navigating-block navigated? editing? selected?] :as opts}]
   (let [*ref (::ref state)
-        _ (when (:block/uuid block) (state/sub-async-query-loading (:block/uuid block)))
         [original-block block] (build-block config* block {:navigating-block navigating-block :navigated? navigated?})
         config* (if original-block
                   (assoc config* :original-block original-block)
@@ -3424,8 +3451,6 @@
                                (str (:block/uuid block))))
         edit-input-id (str "edit-block-" (:block/uuid block))
         container-id (:container-id config*)
-        editing? (or (state/sub-editing? [container-id (:block/uuid block)])
-                     (state/sub-editing? [:unknown-container (:block/uuid block)]))
         table? (:table? config*)
         sidebar? (:sidebar? config*)
         property? (:property? config*)
@@ -3438,7 +3463,9 @@
         *control-show? (get container-state ::control-show?)
         db-collapsed? (util/collapsed? block)
         collapsed? (cond
-                     (or ref-or-custom-query? (root-block? config block)
+                     (or ref-or-custom-query?
+                         (:view? config)
+                         (root-block? config block)
                          (and (or (ldb/class? block) (ldb/property? block)) (:page-title? config)))
                      (state/sub-block-collapsed uuid)
 
@@ -3462,7 +3489,6 @@
         own-number-list? (:own-order-number-list? config)
         order-list? (boolean own-number-list?)
         children (ldb/get-children block)
-        selected? (when (uuid? (:block/uuid block)) (state/sub-block-selected? (:block/uuid block)))
         db-based? (config/db-based-graph? repo)
         page-icon (when (:page-title? config)
                     (let [icon' (get block (pu/get-pid :logseq.property/icon))]
@@ -3561,11 +3587,12 @@
                           (= uuid (:block/uuid (state/get-edit-block))))]
             (block-control (assoc config :hide-bullet? (:page-title? config))
                            block
-                           {:uuid uuid
-                            :block-id block-id
-                            :collapsed? collapsed?
-                            :*control-show? *control-show?
-                            :edit? edit?})))
+                           (merge opts
+                                  {:uuid uuid
+                                   :block-id block-id
+                                   :collapsed? collapsed?
+                                   :*control-show? *control-show?
+                                   :edit? edit?}))))
 
         (when (and @*show-left-menu? (not in-whiteboard?) (not (or table? property?)))
           (block-left-menu config block))
@@ -3630,32 +3657,64 @@
 
      (when-not (or in-whiteboard? table? property?) (dnd-separator-wrapper block children block-id slide? false false))]))
 
+(rum/defc ^:large-vars/cleanup-todo block-container-inner
+  [container-state repo config* block opts]
+  (let [container-id (:container-id config*)
+        block-id (:block/uuid block)
+        v1 (state/sub-editing? [container-id block-id])
+        v2 (state/sub-editing? [:unknown-container block-id])
+        selected? (state/sub-block-selected? block-id)
+        editing? (or v1 v2)]
+    (block-container-inner-aux container-state repo config* block (assoc opts
+                                                                         :editing? editing?
+                                                                         :selected? selected?))))
+
 (defn- block-changed?
   [old-block new-block]
   (not= (:block/tx-id old-block) (:block/tx-id new-block)))
 
-(rum/defcs block-container < rum/reactive db-mixins/query
+(defn- config-block-should-update?
+  [old-state new-state]
+  (let [config-compare-keys [:show-cloze? :hide-children? :own-order-list-type :own-order-list-index :original-block :edit? :hide-bullet?]
+        b1                  (second (:rum/args old-state))
+        b2                  (second (:rum/args new-state))
+        result              (or
+                             (block-changed? b1 b2)
+                                               ;; config changed
+                             (not= (select-keys (first (:rum/args old-state)) config-compare-keys)
+                                   (select-keys (first (:rum/args new-state)) config-compare-keys)))]
+    (boolean result)))
+
+(defn- set-collapsed-block!
+  [block-id v]
+  (if (false? v)
+    (editor-handler/expand-block! block-id {:skip-db-collpsing? true})
+    (state/set-collapsed-block! block-id v)))
+
+(rum/defcs loaded-block-container < rum/reactive db-mixins/query
   (rum/local false ::show-block-left-menu?)
   (rum/local false ::show-block-right-menu?)
+  {:should-update config-block-should-update?}
   {:init (fn [state]
            (let [[config block] (:rum/args state)
                  block-id (:block/uuid block)
                  linked-block? (or (:block/link block)
                                    (:original-block config))]
-             (cond
-               (and (:page-title? config) (or (ldb/class? block) (ldb/property? block)) (not config/publishing?))
-               (let [collapsed? (state/get-block-collapsed block-id)]
-                 (state/set-collapsed-block! block-id (if (some? collapsed?) collapsed? true)))
+             (when-not (:property-block? config)
+               (cond
+                 (and (:page-title? config) (or (ldb/class? block) (ldb/property? block)) (not config/publishing?))
+                 (let [collapsed? (state/get-block-collapsed block-id)]
+                   (set-collapsed-block! block-id (if (some? collapsed?) collapsed? true)))
 
-               (root-block? config block)
-               (state/set-collapsed-block! block-id false)
+                 (root-block? config block)
+                 (set-collapsed-block! block-id false)
 
-               (or (:ref? config) (:custom-query? config))
-               (state/set-collapsed-block! block-id
-                                           (boolean (editor-handler/block-default-collapsed? block config)))
+                 (or (:view? config) (:ref? config) (:custom-query? config))
+                 (set-collapsed-block! block-id
+                                       (boolean (editor-handler/block-default-collapsed? block config)))
 
-               :else
-               nil)
+                 :else
+                 nil))
              (cond->
               (assoc state
                      ::control-show? (atom false)
@@ -3667,9 +3726,9 @@
                    (let [[config block] (:rum/args state)
                          block-id (:block/uuid block)]
                      (when (root-block? config block)
-                       (state/set-collapsed-block! block-id nil)))
+                       (set-collapsed-block! block-id nil)))
                    state)}
-  [state config block]
+  [state config block & {:as opts}]
   (let [repo (state/get-current-repo)
         *navigating-block (get state ::navigating-block)
         navigating-block (rum/react *navigating-block)
@@ -3685,12 +3744,34 @@
           [:code.flex.p-1.text-red-rx-09 "Block render error: " (.-message error)]])
        (rum/with-key
          (block-container-inner state repo config' block
-                                {:navigating-block navigating-block :navigated? navigated?})
+                                (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}]
+  (let [[block set-block!] (hooks/use-state block*)
+        id (or (:db/id block*) (:block/uuid block*))]
+    (when-not (or (:page-title? config)
+                  (:view? config))
+      (hooks/use-effect!
+       (fn []
+         (p/let [block (db-async/<get-block (state/get-current-repo)
+                                            id
+                                            {:children? (not
+                                                         (if-some [result (state/get-block-collapsed (:block/uuid block))]
+                                                           result
+                                                           (:block/collapsed? block)))
+                                             :skip-refresh? false})]
+           (set-block! block)))
+       []))
+    (when (or (:view? config) (:block/title block))
+      (loaded-block-container config block opts))))
+
 (defn divide-lists
   [[f & l]]
   (loop [l        l
@@ -3845,6 +3926,12 @@
   [config col]
   (map #(inline config %) col))
 
+(rum/defc inline-title
+  [title]
+  (map-inline {}
+              (gp-mldoc/inline->edn title
+                                    (mldoc/get-default-config :markdown))))
+
 (declare ->hiccup)
 
 (defn- get-code-mode-by-lang
@@ -3888,10 +3975,10 @@
             [:div.ui-fenced-code-editor.flex.w-full
              {:ref (fn [el]
                      (set-inside-portal? (and el (whiteboard-handler/inside-portal? el))))
-              :on-mouse-over #(dom/add-class! (hooks/deref *actions-ref) "opacity-100")
+              :on-mouse-over #(dom/add-class! (hooks/deref *actions-ref) "!opacity-100")
               :on-mouse-leave (fn [e]
                                 (when (dom/has-class? (.-target e) "code-editor")
-                                  (dom/remove-class! (hooks/deref *actions-ref) "opacity-100")))}
+                                  (dom/remove-class! (hooks/deref *actions-ref) "!opacity-100")))}
              (cond
                (nil? inside-portal?) nil
 
@@ -3903,7 +3990,7 @@
 
                :else
                [:div.ls-code-editor-wrap
-                [:div.code-block-actions.flex.flex-row.gap-1.opacity-0.transition-opacity.ease-in.duration-300
+                [:div.code-block-actions
                  {:ref *actions-ref}
                  (shui/button
                   {:variant :text
@@ -4128,16 +4215,7 @@
   (map #(markup-element-cp config %) col))
 
 (rum/defc block-item <
-  {:should-update (fn [old-state new-state]
-                    (let [config-compare-keys [:show-cloze? :hide-children? :own-order-list-type :own-order-list-index :original-block :edit? :hide-bullet?]
-                          b1                  (second (:rum/args old-state))
-                          b2                  (second (:rum/args new-state))
-                          result              (or
-                                               (block-changed? b1 b2)
-                                               ;; config changed
-                                               (not= (select-keys (first (:rum/args old-state)) config-compare-keys)
-                                                     (select-keys (first (:rum/args new-state)) config-compare-keys)))]
-                      (boolean result)))}
+  {:should-update config-block-should-update?}
   [config item {:keys [top? bottom?]}]
   (let [original-block item
         linked-block (:block/link item)
@@ -4147,15 +4225,15 @@
                      (update :links (fn [ids] (conj (or ids #{}) (:db/id linked-block)))))
                  config)
         item (or (if loop-linked? item linked-block) item)
-        item (cond-> (dissoc item :block/meta)
-               (not (:block-children? config))
-               (assoc :block.temp/top? top?
-                      :block.temp/bottom? bottom?))
+        item (dissoc item :block/meta)
         config' (assoc config
                        :block/uuid (:block/uuid item)
                        :loop-linked? loop-linked?)]
     (when-not (and loop-linked? (:block/name linked-block))
-      (rum/with-key (block-container config' item)
+      (rum/with-key (block-container config' item
+                                     (when (not (:block-children? config))
+                                       {:top? top?
+                                        :bottom? bottom?}))
         (str (:block/uuid item)
              (when linked-block
                (str "-" (:block/uuid original-block))))))))

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

@@ -1040,7 +1040,7 @@ html.is-mac {
   }
 
   > .code-block-actions {
-    @apply absolute right-1 top-1 select-none z-[1] text-xs;
+    @apply flex flex-row gap-1 opacity-0 transition-opacity ease-in duration-300 absolute right-1 top-1 select-none z-[1] text-xs bg-gray-01;
 
     button {
       @apply !py-0 h-4 text-muted-foreground hover:text-foreground text-xs px-1;

+ 6 - 7
src/main/frontend/components/class.cljs

@@ -27,10 +27,9 @@
     (let [children-pages (set (model/get-structured-children (state/get-current-repo) (:db/id class)))
           ;; Expand children if there are about a pageful of total blocks to display
           default-collapsed? (> (count children-pages) 30)]
-      [:div.my-4
-       (ui/foldable
-        [:div.font-medium.opacity-50
-         (str "Children (" (count children-pages) ")")]
-        [:div.ml-1.mt-2 (class-children-aux class {:default-collapsed? default-collapsed?})]
-        {:default-collapsed? false
-         :title-trigger? true})])))
+      (ui/foldable
+       [:div.font-medium.opacity-50
+        (str "Children (" (count children-pages) ")")]
+       [:div.ml-1.mt-2 (class-children-aux class {:default-collapsed? default-collapsed?})]
+       {:default-collapsed? false
+        :title-trigger? true}))))

+ 15 - 18
src/main/frontend/components/cmdk/core.cljs

@@ -121,7 +121,6 @@
                                       (= input (util/page-name-sanity-lc (:block/title block))))) blocks-result))))
         include-slash? (or (string/includes? input "/")
                            (string/starts-with? input "/"))
-        db-based? (config/db-based-graph?)
         order* (cond
                  (= search-mode :graph)
                  []
@@ -150,8 +149,7 @@
                      ["Create"         :create       (create-items input)])
                    ["Current page"     :current-page   (visible-items :current-page)]
                    ["Nodes"            :nodes         (visible-items :nodes)]
-                   (when (and db-based? (string/blank? input))
-                     ["Recently updated" :recently-updated-pages (visible-items :recently-updated-pages)])
+                   ["Recently updated" :recently-updated-pages (visible-items :recently-updated-pages)]
                    ["Commands"         :commands       (visible-items :commands)]
                    ["Files"            :files          (visible-items :files)]
                    ["Filters"          :filters        (visible-items :filters)]]
@@ -199,18 +197,16 @@
     "page"))
 
 (defmethod load-results :initial [_ state]
-  (let [!results (::results state)]
-    (if (config/db-based-graph?)
-      (let [recent-pages (map (fn [block]
-                                (let [text (block-handler/block-unique-title block)
-                                      icon (get-page-icon block)]
-                                  {:icon icon
-                                   :icon-theme :gray
-                                   :text text
-                                   :source-block block}))
-                              (ldb/get-recent-updated-pages (db/get-db)))]
-        (reset! !results (assoc-in default-results [:recently-updated-pages :items] recent-pages)))
-      !results)))
+  (let [!results (::results state)
+        recent-pages (map (fn [block]
+                            (let [text (block-handler/block-unique-title block)
+                                  icon (get-page-icon block)]
+                              {:icon icon
+                               :icon-theme :gray
+                               :text text
+                               :source-block block}))
+                          (ldb/get-recent-updated-pages (db/get-db)))]
+    (reset! !results (assoc-in default-results [:recently-updated-pages :items] recent-pages))))
 
 ;; The commands search uses the command-palette handler
 (defmethod load-results :commands [group state]
@@ -409,8 +405,9 @@
 
 (defmethod handle-action :open-page [_ state _event]
   (when-let [page-name (get-highlighted-page-uuid-or-name state)]
-    (let [page (db/get-page page-name)]
-      (route-handler/redirect-to-page! (:block/uuid page)))
+    (let [page-uuid (get (db/get-page page-name) :block/uuid
+                         (when (uuid? page-name) page-name))]
+      (route-handler/redirect-to-page! page-uuid))
     (shui/dialog-close! :ls-dialog-cmdk)))
 
 (defmethod handle-action :open-block [_ state _event]
@@ -803,7 +800,7 @@
                                   (handle-input-change state e)
                                   (when-let [on-change (:on-input-change opts)]
                                     (on-change new-value))))
-                              100)
+                              200)
                              [])]
     ;; use-effect [results-ordered input] to check whether the highlighted item is still in the results,
     ;; if not then clear that puppy out!

+ 8 - 4
src/main/frontend/components/container.cljs

@@ -20,6 +20,7 @@
             [frontend.context.i18n :refer [t tt]]
             [frontend.db :as db]
             [frontend.db-mixins :as db-mixins]
+            [frontend.db.async :as db-async]
             [frontend.db.model :as db-model]
             [frontend.extensions.fsrs :as fsrs]
             [frontend.extensions.pdf.utils :as pdf-utils]
@@ -56,6 +57,7 @@
             [logseq.shui.toaster.core :as shui-toaster]
             [logseq.shui.ui :as shui]
             [medley.core :as medley]
+            [promesa.core :as p]
             [react-draggable]
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]))
@@ -707,8 +709,7 @@
   (let [default-home (get-default-home-if-valid)
         current-repo (state/sub :git/current-repo)
         loading-files? (when current-repo (state/sub [:repo/loading-files? current-repo]))
-        journals-length (state/sub :journals-length)
-        latest-journals (db/get-latest-journals (state/get-current-repo) journals-length)
+        latest-journals (db/get-latest-journals (state/get-current-repo) 1)
         graph-parsing-state (state/sub [:graph/parsing-state current-repo])]
     (cond
       (or
@@ -736,7 +737,7 @@
          (ui/loading (t :loading-files))
 
          (seq latest-journals)
-         (journal/journals latest-journals)
+         (journal/all-journals)
 
          ;; FIXME: why will this happen?
          :else
@@ -748,6 +749,7 @@
   (when-not (or (gobj/get e "shiftKey")
                 (util/meta-key? e)
                 (state/get-edit-input-id)
+                (some-> (.-target e) util/input?)
                 (= (shui-dialog/get-last-modal-id) :property-dialog)
                 (some-> (.-target e) (.closest ".ls-block"))
                 (some-> (.-target e) (.closest "[data-keep-selection]")))
@@ -916,7 +918,9 @@
                                   (when block
                                     (state/clear-selection!)
                                     (state/conj-selection-block! block :down))
-                                  (show! (cp-content/block-context-menu-content target (uuid block-id) property-default-value?)))
+                                  (p/do!
+                                   (db-async/<get-block (state/get-current-repo) (uuid block-id) {:children? false})
+                                   (show! (cp-content/block-context-menu-content target (uuid block-id) property-default-value?))))
 
                                 :else
                                 false)]

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

@@ -417,7 +417,7 @@
      hiccup
      [:div.cursor (t :content/click-to-edit)])])
 
-(rum/defc non-hiccup-content < rum/reactive
+(rum/defc non-hiccup-content
   [id content on-click on-hide config format]
   (let [edit? (state/sub-editing? id)]
     (if edit?

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

@@ -1,6 +1,5 @@
 (ns frontend.components.editor
   (:require [clojure.string :as string]
-            [datascript.impl.entity :as de]
             [dommy.core :as dom]
             [frontend.commands :as commands :refer [*matched-commands]]
             [frontend.components.file-based.datetime :as datetime-comp]
@@ -138,16 +137,17 @@
 
 (rum/defc page-search-aux
   [id format embed? db-tag? q current-pos input pos]
-  (let [db? (config/db-based-graph? (state/get-current-repo))
+  (let [db-based? (config/db-based-graph? (state/get-current-repo))
         q (string/trim q)
         [matched-pages set-matched-pages!] (rum/use-state nil)
         search-f (fn []
                    (when-not (string/blank? q)
                      (p/let [result (if db-tag?
                                       (editor-handler/get-matched-classes q)
-                                      (editor-handler/<get-matched-blocks q {:nlp-pages? true}))]
+                                      (editor-handler/<get-matched-blocks q {:nlp-pages? true
+                                                                             :page-only? (not db-based?)}))]
                        (set-matched-pages! result))))]
-    (hooks/use-effect! search-f [(hooks/use-debounced-value q 50)])
+    (hooks/use-effect! search-f [(hooks/use-debounced-value q 150)])
 
     (let [matched-pages' (if (string/blank? q)
                            (if db-tag?
@@ -183,53 +183,53 @@
          :on-enter    (fn []
                         (page-handler/page-not-exists-handler input id q current-pos))
          :item-render (fn [block _chosen?]
-                        (let [block (if (and (:block/uuid block) (not (de/entity? block)))
-                                      (db/entity [:block/uuid (:block/uuid block)])
-                                      block)]
+                        (let [block' (if-let [id (:block/uuid block)]
+                                       (or (db/entity [:block/uuid id]) block)
+                                       block)]
                           [:div.flex.flex-col
-                           (when-not (db/page? block)
+                           (when (and (not (: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) {})]))
+                                (breadcrumb {:search? true} (state/get-current-repo) (:block/uuid block') {})]))
                            [:div.flex.flex-row.items-center.gap-1
-                            (when-not db-tag?
+                            (when-not (or db-tag? (not db-based?))
                               [:div.flex.items-center
                                (cond
-                                 (:nlp-date? block)
+                                 (:nlp-date? block')
                                  (ui/icon "calendar" {:size 14})
 
-                                 (ldb/class? block)
+                                 (ldb/class? block')
                                  (ui/icon "hash" {:size 14})
 
-                                 (ldb/property? block)
+                                 (ldb/property? block')
                                  (ui/icon "letter-p" {:size 14})
 
-                                 (db-model/whiteboard-page? block)
+                                 (db-model/whiteboard-page? block')
                                  (ui/icon "whiteboard" {:extension? true})
 
-                                 (db/page? block)
+                                 (:page? block')
                                  (ui/icon "page" {:extension? true})
 
-                                 (or (string/starts-with? (str (:block/title block)) (t :new-tag))
-                                     (string/starts-with? (str (:block/title block)) (t :new-page)))
+                                 (or (string/starts-with? (str (:block/title block')) (t :new-tag))
+                                     (string/starts-with? (str (:block/title block')) (t :new-page)))
                                  (ui/icon "plus" {:size 14})
 
                                  :else
                                  (ui/icon "letter-n" {:size 14}))])
 
                             (let [title (if db-tag?
-                                          (let [target (first (:block/_alias block))]
+                                          (let [target (first (:block/_alias block'))]
                                             (if (ldb/class? target)
-                                              (str (:block/title block) " -> alias: " (:block/title target))
-                                              (:block/title block)))
-                                          (block-handler/block-unique-title block))]
+                                              (str (:block/title block') " -> alias: " (:block/title target))
+                                              (:block/title block')))
+                                          (block-handler/block-unique-title block'))]
                               (search-handler/highlight-exact-query title q))]]))
          :empty-placeholder [:div.text-gray-500.text-sm.px-4.py-2 (if db-tag?
                                                                     "Search for a tag"
                                                                     "Search for a node")]
          :class "black"})
 
-       (when (and db? db-tag? (not (string/blank? q)))
+       (when (and db-based? db-tag? (not (string/blank? q)))
          [:p.px-1.opacity-50.text-sm
           [:code (if util/mac? "Cmd+Enter" "Ctrl+Enter")]
           [:span " to display this tag inline instead of at the end of this node."]])])))

+ 42 - 35
src/main/frontend/components/export.cljs

@@ -5,6 +5,7 @@
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
+            [frontend.handler.block :as block-handler]
             [frontend.handler.db-based.export :as db-export-handler]
             [frontend.handler.export :as export]
             [frontend.handler.export.html :as export-html]
@@ -144,7 +145,7 @@
                                  :selected false}])
 
 (defn- export-helper
-  [block-uuids-or-page-name]
+  [top-level-ids]
   (let [current-repo (state/get-current-repo)
         text-indent-style (state/get-export-block-text-indent-style)
         text-remove-options (set (state/get-export-block-text-remove-options))
@@ -152,19 +153,19 @@
         tp @*export-block-type]
     (case tp
       :text (export-text/export-blocks-as-markdown
-             current-repo block-uuids-or-page-name
+             current-repo top-level-ids
              {:indent-style text-indent-style :remove-options text-remove-options :other-options text-other-options})
       :opml (export-opml/export-blocks-as-opml
-             current-repo block-uuids-or-page-name {:remove-options text-remove-options :other-options text-other-options})
+             current-repo top-level-ids {:remove-options text-remove-options :other-options text-other-options})
       :html (export-html/export-blocks-as-html
-             current-repo block-uuids-or-page-name {:remove-options text-remove-options :other-options text-other-options})
+             current-repo top-level-ids {:remove-options text-remove-options :other-options text-other-options})
       "")))
 
 (defn- <export-edn-helper
   [root-block-uuids-or-page-uuid export-type]
   (let [export-args (case export-type
                       :page
-                      {:page-id [:block/uuid root-block-uuids-or-page-uuid]}
+                      {:page-id [:block/uuid (first root-block-uuids-or-page-uuid)]}
                       :block
                       {:block-id [:block/uuid (first root-block-uuids-or-page-uuid)]}
                       :selected-nodes
@@ -214,6 +215,11 @@
                                                   (set! (.-src img) img-url)
                                                   (callback blob)))) "image/png"))))))
 
+(defn- get-top-level-uuids
+  [selection-ids]
+  (->> (block-handler/get-top-level-blocks (map #(db/entity [:block/uuid %]) selection-ids))
+       (map :block/uuid)))
+
 (rum/defcs ^:large-vars/cleanup-todo
   export-blocks < rum/static
   (rum/local false ::copied?)
@@ -222,19 +228,21 @@
   (rum/local nil ::text-other-options)
   (rum/local nil ::content)
   {:will-mount (fn [state]
-                 (reset! *export-block-type (if (:whiteboard? (last (:rum/args state))) :png :text))
-                 (if (= @*export-block-type :png)
-                   (do (reset! (::content state) nil)
-                       (get-image-blob (first (:rum/args state))
-                                       (merge (second (:rum/args state)) {:transparent-bg? false})
-                                       (fn [blob] (reset! (::content state) blob))))
-                   (reset! (::content state) (export-helper (first (:rum/args state)))))
-                 (reset! (::text-remove-options state) (set (state/get-export-block-text-remove-options)))
-                 (reset! (::text-indent-style state) (state/get-export-block-text-indent-style))
-                 (reset! (::text-other-options state) (state/get-export-block-text-other-options))
-                 state)}
-  [state root-block-uuids-or-page-uuid {:keys [whiteboard? export-type] :as options}]
-  (let [tp @*export-block-type
+                 (let [top-level-uuids (get-top-level-uuids (first (:rum/args state)))]
+                   (reset! *export-block-type (if (:whiteboard? (last (:rum/args state))) :png :text))
+                   (if (= @*export-block-type :png)
+                     (do (reset! (::content state) nil)
+                         (get-image-blob top-level-uuids
+                                         (merge (second (:rum/args state)) {:transparent-bg? false})
+                                         (fn [blob] (reset! (::content state) blob))))
+                     (reset! (::content state) (export-helper top-level-uuids)))
+                   (reset! (::text-remove-options state) (set (state/get-export-block-text-remove-options)))
+                   (reset! (::text-indent-style state) (state/get-export-block-text-indent-style))
+                   (reset! (::text-other-options state) (state/get-export-block-text-other-options))
+                   (assoc state ::top-level-uuids top-level-uuids)))}
+  [state _selection-ids {:keys [whiteboard? export-type] :as options}]
+  (let [top-level-uuids (::top-level-uuids state)
+        tp @*export-block-type
         *text-other-options (::text-other-options state)
         *text-remove-options (::text-remove-options state)
         *text-indent-style (::text-indent-style state)
@@ -248,30 +256,29 @@
          (ui/button "Text"
                     :class "mr-4 w-20"
                     :on-click #(do (reset! *export-block-type :text)
-                                   (reset! *content (export-helper root-block-uuids-or-page-uuid))))
+                                   (reset! *content (export-helper top-level-uuids))))
          (ui/button "OPML"
                     :class "mr-4 w-20"
                     :on-click #(do (reset! *export-block-type :opml)
-                                   (reset! *content (export-helper root-block-uuids-or-page-uuid))))
+                                   (reset! *content (export-helper top-level-uuids))))
          (ui/button "HTML"
                     :class "mr-4 w-20"
                     :on-click #(do (reset! *export-block-type :html)
-                                   (reset! *content (export-helper root-block-uuids-or-page-uuid))))
-         (when-not (seq? root-block-uuids-or-page-uuid)
+                                   (reset! *content (export-helper top-level-uuids))))
+         (when-not (seq? top-level-uuids)
            (ui/button "PNG"
                       :class "mr-4 w-20"
                       :on-click #(do (reset! *export-block-type :png)
                                      (reset! *content nil)
-                                     (get-image-blob root-block-uuids-or-page-uuid (merge options {:transparent-bg? false}) (fn [blob] (reset! *content blob))))))
+                                     (get-image-blob top-level-uuids (merge options {:transparent-bg? false}) (fn [blob] (reset! *content blob))))))
          (when (config/db-based-graph?)
            (ui/button "EDN"
                       :class "w-20"
                       :on-click #(do (reset! *export-block-type :edn)
-                                     (p/let [result (<export-edn-helper root-block-uuids-or-page-uuid export-type)
+                                     (p/let [result (<export-edn-helper top-level-uuids export-type)
                                              pull-data (with-out-str (pprint/pprint result))]
                                        (when-not (= :export-edn-error result)
                                          (reset! *content pull-data))))))])
-
       (if (= :png tp)
         [:div.flex.items-center.justify-center.relative
          (when (not @*content) [:div.absolute (ui/loading "")])
@@ -285,7 +292,7 @@
          (ui/checkbox {:class "mr-2 ml-4"
                        :on-change (fn [e]
                                     (reset! *content nil)
-                                    (get-image-blob root-block-uuids-or-page-uuid (merge options {:transparent-bg? e.currentTarget.checked}) (fn [blob] (reset! *content blob))))})]
+                                    (get-image-blob top-level-uuids (merge options {:transparent-bg? e.currentTarget.checked}) (fn [blob] (reset! *content blob))))})]
         (let [options (->> text-indent-style-options
                            (mapv (fn [opt]
                                    (if (= @*text-indent-style (:label opt))
@@ -301,7 +308,7 @@
                                 (let [value (util/evalue e)]
                                   (state/set-export-block-text-indent-style! value)
                                   (reset! *text-indent-style value)
-                                  (reset! *content (export-helper root-block-uuids-or-page-uuid))))}
+                                  (reset! *content (export-helper top-level-uuids))))}
                   (for [{:keys [label value selected]} options]
                     [:option (cond->
                               {:key label
@@ -316,7 +323,7 @@
                           :on-change (fn [e]
                                        (state/update-export-block-text-remove-options! e :page-ref)
                                        (reset! *text-remove-options (state/get-export-block-text-remove-options))
-                                       (reset! *content (export-helper root-block-uuids-or-page-uuid)))})
+                                       (reset! *content (export-helper top-level-uuids)))})
             [:div {:style {:visibility (if (#{:text :html :opml} tp) "visible" "hidden")}}
              "[[text]] -> text"]
 
@@ -326,7 +333,7 @@
                           :on-change (fn [e]
                                        (state/update-export-block-text-remove-options! e :emphasis)
                                        (reset! *text-remove-options (state/get-export-block-text-remove-options))
-                                       (reset! *content (export-helper root-block-uuids-or-page-uuid)))})
+                                       (reset! *content (export-helper top-level-uuids)))})
 
             [:div {:style {:visibility (if (#{:text :html :opml} tp) "visible" "hidden")}}
              "remove emphasis"]
@@ -337,7 +344,7 @@
                           :on-change (fn [e]
                                        (state/update-export-block-text-remove-options! e :tag)
                                        (reset! *text-remove-options (state/get-export-block-text-remove-options))
-                                       (reset! *content (export-helper root-block-uuids-or-page-uuid)))})
+                                       (reset! *content (export-helper top-level-uuids)))})
 
             [:div {:style {:visibility (if (#{:text :html :opml} tp) "visible" "hidden")}}
              "remove #tags"]]
@@ -350,7 +357,7 @@
                                        (state/update-export-block-text-other-options!
                                         :newline-after-block (boolean (util/echecked? e)))
                                        (reset! *text-other-options (state/get-export-block-text-other-options))
-                                       (reset! *content (export-helper root-block-uuids-or-page-uuid)))})
+                                       (reset! *content (export-helper top-level-uuids)))})
             [:div {:style {:visibility (if (#{:text} tp) "visible" "hidden")}}
              "newline after block"]
 
@@ -360,7 +367,7 @@
                           :on-change (fn [e]
                                        (state/update-export-block-text-remove-options! e :property)
                                        (reset! *text-remove-options (state/get-export-block-text-remove-options))
-                                       (reset! *content (export-helper root-block-uuids-or-page-uuid)))})
+                                       (reset! *content (export-helper top-level-uuids)))})
             [:div {:style {:visibility (if (#{:text} tp) "visible" "hidden")}}
              "remove properties"]]
 
@@ -375,7 +382,7 @@
                                  level (if (= "all" value) :all (util/safe-parse-int value))]
                              (state/update-export-block-text-other-options! :keep-only-level<=N level)
                              (reset! *text-other-options (state/get-export-block-text-other-options))
-                             (reset! *content (export-helper root-block-uuids-or-page-uuid))))}
+                             (reset! *content (export-helper top-level-uuids))))}
              (for [n (cons "all" (range 1 10))]
                [:option {:key n :value n} n])]]]))
 
@@ -389,8 +396,8 @@
                                   (util/copy-to-clipboard! @*content :html (when (= tp :html) @*content)))
                                 (reset! *copied? true)))
          (ui/button (t :export-save-to-file)
-                    :on-click #(let [file-name (if (uuid? root-block-uuids-or-page-uuid)
-                                                 (-> (db/get-page root-block-uuids-or-page-uuid)
+                    :on-click #(let [file-name (if (uuid? top-level-uuids)
+                                                 (-> (db/get-page top-level-uuids)
                                                      (util/get-page-title))
                                                  (t/now))]
                                  (utils/saveToFile (js/Blob. [@*content]) (str "logseq_" file-name) (if (= tp :text) "txt" (name tp)))))])]]))

+ 4 - 4
src/main/frontend/components/file_based/hierarchy.cljs

@@ -4,11 +4,11 @@
             [frontend.db :as db]
             [frontend.db.model :as db-model]
             [frontend.state :as state]
-            [logseq.graph-parser.text :as text]
             [frontend.ui :as ui]
+            [frontend.util :as util]
+            [logseq.graph-parser.text :as text]
             [medley.core :as medley]
-            [rum.core :as rum]
-            [frontend.util :as util]))
+            [rum.core :as rum]))
 
 (defn- get-relation
   "Get all parent pages along the namespace hierarchy path.
@@ -49,7 +49,7 @@
   [page]
   (let [{:keys [namespaces]} (get-relation page)]
     (when (seq namespaces)
-      [:div.page-hierarchy.mt-6
+      [:div.page-hierarchy
        (ui/foldable
         [:h2.font-bold.opacity-30 "Hierarchy"]
         [:ul.namespaces {:style {:margin "12px 24px"}}

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

@@ -1,6 +1,6 @@
 (ns frontend.components.file-based.query
-  (:require [frontend.components.query.result :as query-result]
-            [frontend.components.file-based.query-table :as query-table]
+  (:require [frontend.components.file-based.query-table :as query-table]
+            [frontend.components.query.result :as query-result]
             [frontend.db.query-dsl :as query-dsl]
             [frontend.handler.property :as property-handler]
             [frontend.state :as state]
@@ -31,7 +31,7 @@
 (rum/defc custom-query-header
   [{:keys [dsl-query?] :as config}
    {:keys [title query] :as q}
-   {:keys [collapsed? result table? current-block view-f page-list? query-error-atom]}]
+   {:keys [collapsed? *result result table? current-block view-f page-list? query-error-atom]}]
   (let [dsl-page-query? (and dsl-query?
                              (false? (:blocks? (query-dsl/parse-query query))))
         full-text-search? (and dsl-query?
@@ -82,4 +82,4 @@
            (query-refresh-button query-time {:full-text-search? full-text-search?
                                              :on-pointer-down (fn [e]
                                                                 (util/stop e)
-                                                                (query-result/trigger-custom-query! config q query-error-atom (fn [])))}))]])]))
+                                                                (query-result/run-custom-query config q *result query-error-atom))}))]])]))

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

@@ -54,9 +54,9 @@
   {:will-mount (fn [state]
                  (reset!
                   (::online-users-canceler state)
-                  (c.m/run-task
-                   (m/reduce (fn [_ v] (reset! (::online-users state) v)) rtc-flows/rtc-online-users-flow)
-                   :fetch-online-users :succ (constantly nil)))
+                  (c.m/run-task :fetch-online-users
+                    (m/reduce (fn [_ v] (reset! (::online-users state) v)) rtc-flows/rtc-online-users-flow)
+                    :succ (constantly nil)))
                  state)
    :will-unmount (fn [state]
                    (when @(::online-users-canceler state) (@(::online-users-canceler state)))

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

@@ -45,7 +45,6 @@
   []
   (notification/show! "Import finished!" :success)
   (shui/dialog-close! :import-indicator)
-  (state/clear-async-query-state!)
   (ui-handler/re-render-root!)
   (route-handler/redirect-to-home!)
   (js/setTimeout ui-handler/re-render-root! 500))

+ 34 - 39
src/main/frontend/components/journal.cljs

@@ -1,50 +1,45 @@
 (ns frontend.components.journal
   (:require [frontend.components.page :as page]
-            [frontend.db :as db]
+            [frontend.components.views :as views]
             [frontend.db-mixins :as db-mixins]
-            [frontend.handler.page :as page-handler]
-            [frontend.mixins :as mixins]
+            [frontend.db.react :as react]
             [frontend.state :as state]
+            [frontend.ui :as ui]
             [frontend.util :as util]
-            [goog.functions :refer [debounce]]
+            [goog.dom :as gdom]
+            [promesa.core :as p]
             [rum.core :as rum]))
 
-(rum/defc journal-cp < rum/reactive
-  [page]
-  [:div.journal-item.content {:key (:db/id page)}
-   (let [repo (state/sub :git/current-repo)]
-     (page/page-cp {:repo repo
-                    :page-name (str (:block/uuid page))}))])
+(rum/defc journal-cp < rum/static
+  [id last?]
+  [:div.journal-item.content
+   (when last?
+     {:class "journal-last-item"})
+   (page/page-cp {:db/id id})])
 
-(defn on-scroll
-  [node {:keys [threshold on-load]
-         :or {threshold 500}}]
-  (when (util/bottom-reached? node threshold)
-    (on-load)))
-
-(defn attach-listeners
-  "Attach scroll and resize listeners."
-  [state]
-  (let [node (js/document.getElementById "main-content-container")
-        opts {:on-load page-handler/load-more-journals!}
-        debounced-on-scroll (debounce #(on-scroll node opts) 100)]
-    (mixins/listen state node :scroll debounced-on-scroll)))
-
-(rum/defc journals < rum/reactive
-  (mixins/event-mixin attach-listeners)
-  {:will-unmount (fn [state]
-                   (state/set-journals-length! 3)
-                   state)}
-  [latest-journals]
-  (when (seq latest-journals)
-    [:div#journals
-     (for [journal latest-journals]
-       (rum/with-key
-         (journal-cp journal)
-         (str "journal-" (:db/id journal))))]))
+(defn- sub-journals
+  []
+  (-> (react/q (state/get-current-repo)
+               [:frontend.worker.react/journals]
+               {:async-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
   []
-  (let [journals-length (state/sub :journals-length)
-        latest-journals (db/get-latest-journals (state/get-current-repo) journals-length)]
-    (journals latest-journals)))
+  (let [data (sub-journals)]
+    (when (seq data)
+      [:div#journals
+       (ui/virtualized-list
+        {:custom-scroll-parent (gdom/getElement "main-content-container")
+         :increase-viewport-by {:top 300 :bottom 300}
+         :compute-item-key (fn [idx]
+                             (let [id (util/nth-safe data idx)]
+                               (str "journal-" id)))
+         :total-count (count data)
+         :item-content (fn [idx]
+                         (let [id (util/nth-safe data idx)
+                               last? (= (inc idx) (count data))]
+                           (journal-cp id last?)))})])))

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

@@ -11,9 +11,9 @@
     &:first-child {
       @apply pt-0 min-h-[500px];
     }
+  }
 
-    &:last-child {
-      @apply border-none;
-    }
+  .journal-last-item {
+    @apply border-none;
   }
 }

+ 31 - 20
src/main/frontend/components/lazy_editor.cljs

@@ -1,12 +1,12 @@
 (ns frontend.components.lazy-editor
   (:require [clojure.string :as string]
-            [rum.core :as rum]
-            [shadow.lazy :as lazy]
-            [frontend.ui :as ui]
             [frontend.config :as config]
-            [frontend.state :as state]
             [frontend.handler.plugin :refer [hook-extensions-enhancers-by-key]]
-            [promesa.core :as p]))
+            [frontend.state :as state]
+            [frontend.ui :as ui]
+            [promesa.core :as p]
+            [rum.core :as rum]
+            [shadow.lazy :as lazy]))
 
 ;; TODO: Why does shadow fail when code is required
 #_:clj-kondo/ignore
@@ -14,28 +14,39 @@
 
 (defonce loaded? (atom false))
 
+(rum/defc editor-aux
+  [config id attr code theme options codemirror-loaded?]
+  (let [^js state (ui/useInView #js {:rootMargin "0px"})
+        in-view? (.-inView state)
+        placeholder [:div
+                     {:style {:height (min
+                                       (* 23.2 (count (string/split-lines code)))
+                                       600)}}]]
+    [:div {:ref (.-ref state)}
+     (if (and codemirror-loaded? in-view?)
+       (@lazy-editor config id attr code theme options)
+       placeholder)]))
+
 (rum/defc editor <
   rum/reactive
   {:will-mount
    (fn [state]
-     (lazy/load lazy-editor
-                (fn []
-                  (if-not @loaded?
-                    (p/finally
-                     (p/all (when-let [enhancers (and config/lsp-enabled?
-                                                      (seq (hook-extensions-enhancers-by-key :codemirror)))]
-                              (for [{f :enhancer} enhancers]
-                                (when (fn? f) (f (. js/window -CodeMirror))))))
-                     (fn []
-                       (-> (p/delay 200)
-                           (p/then #(reset! loaded? true)))))
-                    (reset! loaded? true))))
+     (when-not @loaded?
+       (lazy/load lazy-editor
+                  (fn []
+                    (if-not @loaded?
+                      (p/finally
+                        (p/all (when-let [enhancers (and config/lsp-enabled?
+                                                         (seq (hook-extensions-enhancers-by-key :codemirror)))]
+                                 (for [{f :enhancer} enhancers]
+                                   (when (fn? f) (f (. js/window -CodeMirror))))))
+                        (fn []
+                          (reset! loaded? true)))
+                      (reset! loaded? true)))))
      state)}
   [config id attr code options]
   (let [loaded?' (rum/react loaded?)
         theme   (state/sub :ui/theme)
         code    (or code "")
         code    (string/replace-first code #"\n$" "")]      ;; See-also: #3410
-    (if loaded?'
-      (@lazy-editor config id attr code theme options)
-      (ui/loading "CodeMirror"))))
+    (editor-aux config id attr code theme options loaded?')))

+ 36 - 118
src/main/frontend/components/objects.cljs

@@ -4,34 +4,21 @@
             [frontend.components.views :as views]
             [frontend.db :as db]
             [frontend.db-mixins :as db-mixins]
-            [frontend.db.async :as db-async]
-            [frontend.db.model :as db-model]
-            [frontend.db.react :as react]
             [frontend.handler.editor :as editor-handler]
             [frontend.mixins :as mixins]
-            [frontend.modules.outliner.op :as outliner-op]
-            [frontend.modules.outliner.ui :as ui-outliner-tx]
             [frontend.state :as state]
-            [logseq.db.frontend.entity-util :as entity-util]
             [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]))
 
-(defn- get-class-objects
-  [class]
-  (->> (db-model/get-class-objects (state/get-current-repo) (:db/id class))
-       (map (fn [row] (assoc row :id (:db/id row))))))
-
 (defn- add-new-class-object!
-  [class set-data! properties]
+  [class properties]
   (p/let [block (editor-handler/api-insert-new-block! ""
                                                       {:page (:block/uuid class)
                                                        :properties (merge properties {:block/tags (:db/id class)})
-                                                       :edit-block? false})
-          _ (set-data! (get-class-objects class))]
+                                                       :edit-block? false})]
     (editor-handler/edit-block! (db/entity [:block/uuid (:block/uuid block)]) 0 {:container-id :unknown-container})
     block))
 
@@ -47,13 +34,10 @@
    :disable-hide? true})
 
 (rum/defc class-objects-inner < rum/static
-  [config class objects properties]
-  (let [[loading? set-loading?] (rum/use-state nil)
-        [data set-data!] (rum/use-state objects)
-        ;; Properties can be nil for published private graphs
+  [config class properties]
+  (let [;; Properties can be nil for published private graphs
         properties' (remove nil? properties)
-        columns* (views/build-columns config properties' {:add-tags-column? (or (= (:db/ident class) :logseq.class/Root)
-                                                                                (> (count (distinct (mapcat :block/tags objects))) 1))})
+        columns* (views/build-columns config properties' {:add-tags-column? true})
         columns (cond
                   (= (:db/ident class) :logseq.class/Pdf-annotation)
                   (remove #(contains? #{:logseq.property/ls-type} (:id %)) columns*)
@@ -67,57 +51,31 @@
                     (concat before-cols [(build-asset-file-column config)] after-cols))
                   columns)]
 
-    (hooks/use-effect!
-     (fn []
-       (when (nil? loading?)
-         (set-loading? true)
-         (p/let [_result (db-async/<get-tag-objects (state/get-current-repo) (:db/id class))]
-           (react/refresh! (state/get-current-repo)
-                           [[:frontend.worker.react/objects (:db/id class)]])
-           (set-data! (get-class-objects class))
-           (set-loading? false))))
-     [])
-
     (views/view {:config config
-                 :data data
-                 :set-data! set-data!
                  :view-parent class
                  :view-feature-type :class-objects
                  :columns columns
-                 :add-new-object! (fn [{:keys [properties]}]
-                                    (if (= :logseq.class/Asset (:db/ident class))
-                                      (shui/dialog-open!
-                                       (fn []
-                                         [:div.flex.flex-col.gap-2
-                                          [:div.font-medium "Add assets"]
-                                          (filepicker/picker
-                                           {:on-change (fn [_e files]
-                                                         (p/do!
-                                                          (editor-handler/upload-asset! nil files :markdown editor-handler/*asset-uploading? true)
-                                                          (set-data! (get-class-objects class))
-                                                          (shui/dialog-close!)))})]))
-                                      (add-new-class-object! class set-data! properties)))
+                 :add-new-object! (fn [_view table {:keys [properties]}]
+                                    (let [set-data! (get-in table [:data-fns :set-data!])
+                                          full-data (:full-data table)]
+                                      (if (= :logseq.class/Asset (:db/ident class))
+                                        (shui/dialog-open!
+                                         (fn []
+                                           [:div.flex.flex-col.gap-2
+                                            [:div.font-medium "Add assets"]
+                                            (filepicker/picker
+                                             {:on-change (fn [_e files]
+                                                           (p/let [entities (editor-handler/upload-asset! nil files :markdown editor-handler/*asset-uploading? true)]
+                                                             (shui/dialog-close!)
+                                                             (when (seq entities)
+                                                               (set-data! (concat full-data (map :db/id entities))))))})]))
+                                        (p/let [block (add-new-class-object! class properties)]
+                                          (set-data! (conj (vec full-data) (:db/id block)))))))
                  :show-add-property? true
                  :show-items-count? true
                  :add-property! (fn []
                                   (state/pub-event! [:editor/new-property {:block class
-                                                                           :class-schema? true}]))
-                 :on-delete-rows (fn [table selected-rows]
-                                     ;; Built-in objects must not be deleted e.g. Tag, Property and Root
-                                   (let [pages (->> selected-rows (filter entity-util/page?) (remove :logseq.property/built-in?))
-                                         blocks (->> selected-rows (remove entity-util/page?) (remove :logseq.property/built-in?))]
-                                     (p/do!
-                                      (when-let [f (get-in table [:data-fns :set-row-selection!])]
-                                        (f {}))
-                                      (ui-outliner-tx/transact!
-                                       {:outliner-op :delete-blocks}
-                                       (when (seq blocks)
-                                         (outliner-op/delete-blocks! blocks nil))
-                                       (let [page-ids (map :db/id pages)
-                                             tx-data (map (fn [pid] [:db/retract pid :block/tags (:db/id class)]) page-ids)]
-                                         (when (seq tx-data)
-                                           (outliner-op/transact! tx-data {:outliner-op :save-block}))))
-                                      (set-data! (get-class-objects class)))))})))
+                                                                           :class-schema? true}]))})))
 
 (rum/defcs class-objects < rum/reactive db-mixins/query mixins/container-id
   [state class {:keys [current-page? sidebar?]}]
@@ -126,19 +84,12 @@
           config {:container-id (:container-id state)
                   :current-page? current-page?
                   :sidebar? sidebar?}
-          properties (outliner-property/get-class-properties class)
-          repo (state/get-current-repo)
-          objects (->> (db-model/sub-class-objects repo (:db/id class))
-                       (map (fn [row] (assoc row :id (:db/id row)))))]
+          properties (outliner-property/get-class-properties class)]
       [:div.ml-1
-       (class-objects-inner config class objects properties)])))
-
-(defn- get-property-related-objects [repo property]
-  (->> (db-model/get-property-related-objects repo (:db/id property))
-       (map (fn [row] (assoc row :id (:db/id row))))))
+       (class-objects-inner config class properties)])))
 
 (defn- add-new-property-object!
-  [property set-data! properties]
+  [property properties]
   (p/let [default-value (if (= :checkbox (:logseq.property/type property))
                           false
                           (:db/id (db/entity :logseq.property/empty-placeholder)))
@@ -147,55 +98,24 @@
                                                        :properties (merge
                                                                     {(:db/ident property) default-value}
                                                                     properties)
-                                                       :edit-block? false})
-          _ (set-data! (get-property-related-objects (state/get-current-repo) property))]
+                                                       :edit-block? false})]
     (editor-handler/edit-block! (db/entity [:block/uuid (:block/uuid block)]) 0 {:container-id :unknown-container})
     block))
 
 (rum/defc property-related-objects-inner < rum/static
-  [config property objects properties]
-  (let [[loading? set-loading?] (rum/use-state nil)
-        [data set-data!] (rum/use-state objects)
-        columns (views/build-columns config properties)]
-
-    (hooks/use-effect!
-     (fn []
-       (when (nil? loading?)
-         (set-loading? true)
-         (p/let [result (db-async/<get-property-objects (state/get-current-repo) (:db/ident property))]
-           (set-data! (mapv (fn [m]
-                              (let [e (db/entity (:db/id m))]
-                                (assoc e :id (:db/id m)))) result))
-           (set-loading? false))))
-     [])
-
+  [config property properties]
+  (let [columns (views/build-columns config properties)]
     (views/view {:config config
-                 :data data
                  :view-parent property
                  :view-feature-type :property-objects
-                 :set-data! set-data!
                  :columns columns
-                 :add-new-object! (fn [{:keys [properties]}]
-                                    (add-new-property-object! property set-data! properties))
+                 :add-new-object! (fn [_view table {:keys [properties]}]
+                                    (p/let [set-data! (get-in table [:data-fns :set-data!])
+                                            full-data (:full-data table)
+                                            block (add-new-property-object! property properties)]
+                                      (set-data! (conj (vec full-data) (:db/id block)))))
                  ;; TODO: Add support for adding column
-                 :show-add-property? false
-                 ;; Relationships with built-in properties must not be deleted e.g. built-in? or parent
-                 :on-delete-rows (when-not (:logseq.property/built-in? property)
-                                   (fn [table selected-rows]
-                                     (let [pages (->> selected-rows (filter entity-util/page?) (remove :logseq.property/built-in?))
-                                           blocks (->> selected-rows (remove entity-util/page?) (remove :logseq.property/built-in?))]
-                                       (p/do!
-                                        (set-data! (get-property-related-objects (state/get-current-repo) property))
-                                        (when-let [f (get-in table [:data-fns :set-row-selection!])]
-                                          (f {}))
-                                        (ui-outliner-tx/transact!
-                                         {:outliner-op :delete-blocks}
-                                         (when (seq blocks)
-                                           (outliner-op/delete-blocks! blocks nil))
-                                         (let [page-ids (map :db/id pages)
-                                               tx-data (map (fn [pid] [:db/retract pid (:db/ident property)]) page-ids)]
-                                           (when (seq tx-data)
-                                             (outliner-op/transact! tx-data {:outliner-op :save-block}))))))))})))
+                 :show-add-property? false})))
 
 ;; Show all nodes containing the given property
 (rum/defcs property-related-objects < rum/reactive db-mixins/query mixins/container-id
@@ -205,8 +125,6 @@
           config {:container-id (:container-id state)
                   :current-page? current-page?}
           ;; Show tags to help differentiate property rows
-          properties [property' (db/entity :block/tags)]
-          repo (state/get-current-repo)
-          objects (get-property-related-objects repo property)]
+          properties [property' (db/entity :block/tags)]]
       [:div.ml-1
-       (property-related-objects-inner config property' objects properties)])))
+       (property-related-objects-inner config property' properties)])))

+ 186 - 179
src/main/frontend/components/page.cljs

@@ -73,12 +73,6 @@
            str)))
   (def get-block-uuid-by-block-route-name (constantly nil)))
 
-(defn- get-block
-  [page-name-or-uuid]
-  (when page-name-or-uuid
-    (when-let [block (model/get-page page-name-or-uuid)]
-      (model/sub-block (:db/id block)))))
-
 (defn- open-root-block!
   [state]
   (let [[_ block _ sidebar? preview?] (:rum/args state)]
@@ -94,7 +88,7 @@
   {:did-mount (fn [state]
                 (open-root-block! state)
                 state)}
-  [page-e blocks config sidebar? whiteboard? _block-uuid]
+  [page-e blocks config sidebar? _preview? _block-uuid]
   (when page-e
     (let [hiccup (component-block/->hiccup blocks config {})]
       [:div.page-blocks-inner {:style {:min-height 29}}
@@ -151,7 +145,8 @@
                  ;; The same as .dnd-separator
                 :border-top (if hover
                               "3px solid #ccc"
-                              nil)}
+                              nil)
+                :margin-left 20}
         :ref *el-ref
         :tabIndex 0
         :on-click click-handler-fn
@@ -164,7 +159,7 @@
          [:span.bullet-container.cursor
           [:span.bullet]]]
 
-        [:div.flex.flex-1
+        [:div.flex.flex-1.cursor-text
          {:on-drag-enter #(set-hover! true)
           :on-drag-over #(util/stop %)
           :on-drop drop-handler-fn
@@ -204,12 +199,10 @@
                                   (date/journal-title->int (date/today))))
                      (state/pub-event! [:journal/insert-template page-name])))
                  state)}
-  [state page-e {:keys [sidebar? whiteboard?] :as config}]
-  (when page-e
-    (let [page-name (or (:block/name page-e)
-                        (str (:block/uuid page-e)))
-          block-id (parse-uuid page-name)
-          block (get-block (or (:block/uuid page-e) (:block/name page-e)))
+  [state block* {:keys [sidebar? whiteboard?] :as config}]
+  (when-let [id (:db/id block*)]
+    (let [block (db/sub-block id)
+          block-id (:block/uuid block)
           block? (not (db/page? block))
           children (:block/_parent block)
           children (cond
@@ -224,13 +217,13 @@
       (cond
         (and
          (not block?)
-         (empty? children) page-e)
-        (dummy-block page-e)
+         (empty? children) block)
+        (dummy-block block)
 
         :else
         (let [document-mode? (state/sub :document/mode?)
               hiccup-config (merge
-                             {:id (if block? (str block-id) page-name)
+                             {:id (str (:block/uuid block))
                               :db/id (:db/id block)
                               :block? block?
                               :editor-box editor/box
@@ -245,10 +238,8 @@
                                        (string/blank? (:block/title (or link block'))))))]
             [:div.relative
              {:class (when add-button? "show-add-button")}
-             (page-blocks-inner page-e blocks config sidebar? whiteboard? block-id)
-             (let [args (if block-id
-                          {:block-uuid block-id}
-                          {:page page-name})]
+             (page-blocks-inner block blocks config sidebar? whiteboard? block-id)
+             (let [args {:block-uuid block-id}]
                (add-button args (:container-id config)))]))))))
 
 (rum/defc today-queries < rum/reactive
@@ -256,7 +247,7 @@
   (when (and today? (not sidebar?))
     (let [queries (get-in (state/sub-config repo) [:default-queries :journals])]
       (when (seq queries)
-        [:div#today-queries.mt-10
+        [:div#today-queries
          (for [query queries]
            (let [query' (if (config/db-based-graph?)
                           (assoc query :collapsed? true)
@@ -280,7 +271,7 @@
          (set-pages! result)))
      [tag])
     (when (seq pages)
-      [:div.references.page-tags.mt-6.flex-1.flex-row
+      [:div.references.page-tags.flex-1.flex-row
        [:div.content
         (ui/foldable
          [:h2.font-bold.opacity-50 (util/format "Pages tagged with \"%s\"" tag-title)]
@@ -452,19 +443,9 @@
 
 (rum/defc db-page-title
   [page whiteboard-page? sidebar? container-id]
-  (let [[with-actions? set-with-actions!] (rum/use-state false)
-        *el (rum/use-ref nil)]
-
-    (hooks/use-effect!
-     (fn []
-       (when (and (not config/publishing?)
-                  (some-> (rum/deref *el) (.closest "#main-content-container")))
-         (set-with-actions! true)))
-     [])
-
+  (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")
-      :ref *el
       :on-pointer-down (fn [e]
                          (when (util/right-click? e)
                            (state/set-state! :page-title/context {:page (:block/title page)
@@ -495,10 +476,11 @@
   [e *control-show? *all-collapsed?]
   (util/stop e)
   (reset! *control-show? true)
-  (let [all-collapsed?
-        (->> (editor-handler/all-blocks-with-level {:collapse? true})
-             (filter (fn [b] (editor-handler/collapsable? (:block/uuid b))))
-             (empty?))]
+  (p/let [blocks (editor-handler/<all-blocks-with-level {:collapse? true})
+          all-collapsed?
+          (->> blocks
+               (filter (fn [b] (editor-handler/collapsable? (:block/uuid b))))
+               (empty?))]
     (reset! *all-collapsed? all-collapsed?)))
 
 (defn- page-mouse-leave
@@ -524,7 +506,7 @@
   [state page-name]
   (or page-name
       (get-block-uuid-by-block-route-name state)
-    ;; is page name or uuid
+      ;; is page name or uuid
       (get-page-name state)
       (state/get-current-page)))
 
@@ -623,149 +605,149 @@
   (rum/local false ::control-show?)
   (rum/local nil   ::current-page)
   (rum/local false ::tabs-rendered?)
-  [state {:keys [repo page-name preview? sidebar? linked-refs? unlinked-refs? config] :as option}]
-  (when-let [path-page-name (get-path-page-name state page-name)]
-    (let [current-repo (state/sub :git/current-repo)
-          *tabs-rendered? (::tabs-rendered? state)
-          repo (or repo current-repo)
-          page-name (util/page-name-sanity-lc path-page-name)
-          page (get-page-entity page-name)
-          block-id (:block/uuid page)
-          block? (some? (:block/page page))
-          class-page? (ldb/class? page)
-          property-page? (ldb/property? page)
-          journal? (db/journal-page? page-name)
-          db-based? (config/db-based-graph? repo)
-          fmt-journal? (boolean (date/journal-title->int page-name))
-          whiteboard? (:whiteboard? option) ;; in a whiteboard portal shape?
-          whiteboard-page? (model/whiteboard-page? page) ;; is this page a whiteboard?
-          route-page-name path-page-name
-          page-name (:block/name page)
-          page-title (:block/title page)
-          title (or page-title page-name)
-          today? (and
-                  journal?
-                  (= page-name (util/page-name-sanity-lc (date/journal-name))))
-          *control-show? (::control-show? state)
-          *all-collapsed? (::all-collapsed? state)
-          block-or-whiteboard? (or block? whiteboard?)
-          home? (= :home (state/get-current-route))
-          show-tabs? (and db-based? (or class-page? (ldb/property? page)))
-          tabs-rendered? (rum/react *tabs-rendered?)]
-      (if page
-        (when (or page-name block-or-whiteboard?)
-          [:div.flex-1.page.relative.cp__page-inner-wrap
-           (merge (if (seq (:block/tags page))
-                    (let [page-names (map :block/title (:block/tags page))]
-                      (when (seq page-names)
-                        {:data-page-tags (text-util/build-data-value page-names)}))
-                    {})
-
-                  {:key path-page-name
-                   :class (util/classnames [{:is-journals (or journal? fmt-journal?)
-                                             :is-node-page (or class-page? property-page?)}])})
-
-           (if (and whiteboard-page? (not sidebar?))
-             [:div ((state/get-component :whiteboard/tldraw-preview) (:block/uuid page))] ;; FIXME: this is not reactive
-             [:div.relative.grid.gap-6.page-inner
-              (when-not (or block? sidebar?)
-                [:div.flex.flex-row.space-between
-                 (when (and (or (mobile-util/native-platform?) (util/mobile?)) (not db-based?))
-                   [:div.flex.flex-row.pr-2
-                    {:style {:margin-left -15}
-                     :on-mouse-over (fn [e]
-                                      (page-mouse-over e *control-show? *all-collapsed?))
-                     :on-mouse-leave (fn [e]
-                                       (page-mouse-leave e *control-show?))}
-                    (page-blocks-collapse-control title *control-show? *all-collapsed?)])
-                 (when (and (not whiteboard?) (ldb/page? page))
-                   (if db-based?
-                     (db-page-title page whiteboard-page? sidebar? (:container-id state))
-                     (page-title-cp page {:journal? journal?
-                                          :fmt-journal? fmt-journal?
-                                          :preview? preview?})))
-                 (lsp-pagebar-slot)])
-
-              (when (and db-based? sidebar?)
-                [:div.-mb-8
-                 (sidebar-page-properties config page)])
-
-              (when (and block? (not sidebar?) (not whiteboard?))
-                (let [config (merge config {:id "block-parent"
-                                            :block? true})]
-                  [:div.mb-4
-                   (component-block/breadcrumb config repo block-id {:level-limit 3})]))
-
-              (when show-tabs?
-                (tabs page {:current-page? option :sidebar? sidebar? :*tabs-rendered? *tabs-rendered?}))
-
-              (when (or (not show-tabs?) tabs-rendered?)
-                [:div.ls-page-blocks
-                 {:style {:margin-left (if whiteboard? 0 -20)}}
-                 (page-blocks-cp page (merge option {:sidebar? sidebar?
-                                                     :container-id (:container-id state)
-                                                     :whiteboard? whiteboard?}))])])
-
-           (when (and (not preview?) (or (not show-tabs?) tabs-rendered?))
-             [:div.ml-1
-              (when today?
-                (today-queries repo today? sidebar?))
-
-              (when today?
-                (scheduled/scheduled-and-deadlines page-name))
-
-              (when (and (not block?) (not db-based?))
-                (tagged-pages repo page page-title))
-
-              (when (and (ldb/page? page) (:logseq.property/_parent page))
-                (class-component/class-children page))
+  [state {:keys [repo page preview? sidebar? linked-refs? unlinked-refs? config] :as option}]
+  (let [current-repo (state/sub :git/current-repo)
+        *tabs-rendered? (::tabs-rendered? state)
+        repo (or repo current-repo)
+        block-id (:block/uuid page)
+        block? (some? (:block/page page))
+        class-page? (ldb/class? page)
+        property-page? (ldb/property? page)
+        title (:block/title page)
+        journal? (db/journal-page? title)
+        db-based? (config/db-based-graph? repo)
+        fmt-journal? (boolean (date/journal-title->int title))
+        whiteboard? (:whiteboard? option) ;; in a whiteboard portal shape?
+        whiteboard-page? (model/whiteboard-page? page) ;; is this page a whiteboard?
+        today? (and
+                journal?
+                (= title (date/journal-name)))
+        *control-show? (::control-show? state)
+        *all-collapsed? (::all-collapsed? state)
+        block-or-whiteboard? (or block? whiteboard?)
+        home? (= :home (state/get-current-route))
+        show-tabs? (and db-based? (or class-page? (ldb/property? page)))
+        tabs-rendered? (rum/react *tabs-rendered?)]
+    (if page
+      (when (or title block-or-whiteboard?)
+        [:div.flex-1.page.relative.cp__page-inner-wrap
+         (merge (if (seq (:block/tags page))
+                  (let [page-names (map :block/title (:block/tags page))]
+                    (when (seq page-names)
+                      {:data-page-tags (text-util/build-data-value page-names)}))
+                  {})
+
+                {:key title
+                 :class (util/classnames [{:is-journals (or journal? fmt-journal?)
+                                           :is-node-page (or class-page? property-page?)}])})
+
+         (if (and whiteboard-page? (not sidebar?))
+           [:div ((state/get-component :whiteboard/tldraw-preview) (:block/uuid page))] ;; FIXME: this is not reactive
+           [:div.relative.grid.gap-8.page-inner
+            (when-not (or block? sidebar?)
+              [:div.flex.flex-row.space-between
+               (when (and (or (mobile-util/native-platform?) (util/mobile?)) (not db-based?))
+                 [:div.flex.flex-row.pr-2
+                  {:style {:margin-left -15}
+                   :on-mouse-over (fn [e]
+                                    (page-mouse-over e *control-show? *all-collapsed?))
+                   :on-mouse-leave (fn [e]
+                                     (page-mouse-leave e *control-show?))}
+                  (page-blocks-collapse-control title *control-show? *all-collapsed?)])
+               (when (and (not whiteboard?) (ldb/page? page))
+                 (if db-based?
+                   (db-page-title page whiteboard-page? sidebar? (:container-id state))
+                   (page-title-cp page {:journal? journal?
+                                        :fmt-journal? fmt-journal?
+                                        :preview? preview?})))
+               (lsp-pagebar-slot)])
+
+            (when (and db-based? sidebar? (ldb/page? page))
+              [:div.-mb-8
+               (sidebar-page-properties config page)])
+
+            (when (and block? (not sidebar?) (not whiteboard?))
+              (let [config (merge config {:id "block-parent"
+                                          :block? true})]
+                [:div.mb-4
+                 (component-block/breadcrumb config repo block-id {:level-limit 3})]))
+
+            (when show-tabs?
+              (tabs page {:current-page? option :sidebar? sidebar? :*tabs-rendered? *tabs-rendered?}))
+
+            (when (or (not show-tabs?) tabs-rendered?)
+              [:div.ls-page-blocks
+               {:style {:margin-left (if whiteboard? 0 -20)}}
+               (page-blocks-cp page (merge option {:sidebar? sidebar?
+                                                   :container-id (:container-id state)
+                                                   :whiteboard? whiteboard?}))])])
+
+         (when (and (not preview?) (or (not show-tabs?) tabs-rendered?))
+           [:div.ml-1.flex.flex-col.gap-4
+            (when today?
+              (today-queries repo today? sidebar?))
+
+            (when today?
+              (scheduled/scheduled-and-deadlines title))
+
+            (when (and (not block?) (not db-based?))
+              (tagged-pages repo page title))
+
+            (when (and (ldb/page? page) (:logseq.property/_parent page))
+              (class-component/class-children page))
 
               ;; referenced blocks
-              (when-not (or whiteboard? linked-refs? (and block? (not db-based?)))
-                [:div {:key "page-references"}
-                 (rum/with-key
-                   (reference/references page)
-                   (str route-page-name "-refs"))])
-
-              (when-not block-or-whiteboard?
-                (when (and (not journal?) (not db-based?))
-                  (hierarchy/structures (:block/title page))))
-
-              (when-not (or whiteboard? unlinked-refs?
-                            sidebar?
-                            home?
-                            (or class-page? property-page?)
-                            (and block? (not db-based?)))
-                [:div {:key "page-unlinked-references"}
-                 (reference/unlinked-references page)])])])
-        [:div.opacity-75 "Page not found"]))))
-
-(rum/defcs page-aux < rum/reactive db-mixins/query
+            (when-not (or whiteboard? linked-refs? (and block? (not db-based?)))
+              [:div {:key "page-references"}
+               (rum/with-key
+                 (reference/references page {:sidebar? sidebar?})
+                 (str title "-refs"))])
+
+            (when-not block-or-whiteboard?
+              (when (and (not journal?) (not db-based?))
+                (hierarchy/structures (:block/title page))))
+
+            (when-not (or whiteboard? unlinked-refs?
+                          sidebar?
+                          home?
+                          (or class-page? property-page?)
+                          (and block? (not db-based?)))
+              [:div {:key "page-unlinked-references"}
+               (reference/unlinked-references page {:sidebar? sidebar?})])])])
+      [:div.opacity-75 "Page not found"])))
+
+(rum/defcs page-aux < rum/reactive
   {:init (fn [state]
-           (let [page-name (:page-name (first (:rum/args state)))
+           (let [page* (first (:rum/args state))
+                 page-name (:page-name page*)
+                 page-id-uuid-or-name (or (:db/id page*) (:block/uuid page*)
+                                          (get-sanity-page-name state page-name))
                  option (last (:rum/args state))
                  preview-or-sidebar? (or (:preview? option) (:sidebar? option))
-                 page-name' (get-sanity-page-name state page-name)
-                 page-uuid? (util/uuid-string? page-name')
+                 page-uuid? (when page-name (util/uuid-string? page-name))
                  *loading? (atom true)
-                 page (db/get-page page-name')]
-             (when page (reset! *loading? false))
-             (p/let [page-block (db-async/<get-block (state/get-current-repo) page-name')]
+                 page (db/get-page page-id-uuid-or-name)
+                 *page (atom page)]
+             (when (:block.temp/fully-loaded? page) (reset! *loading? false))
+             (p/let [page-block (db-async/<get-block (state/get-current-repo) page-id-uuid-or-name)]
                (reset! *loading? false)
+               (reset! *page (db/entity (:db/id page-block)))
                (when page-block
                  (when-not preview-or-sidebar?
-                   (if-let [page-uuid (and (not page-uuid?) (:block/uuid page-block))]
+                   (if-let [page-uuid (and (not (:db/id page*)) (not page-uuid?) (:block/uuid page-block))]
                      (route-handler/redirect-to-page! (str page-uuid) {:push false})
                      (route-handler/update-page-title-and-label! (state/get-route-match))))))
              (assoc state
-                    ::page-name page-name'
-                    ::loading? *loading?)))
+                    ::loading? *loading?
+                    ::*page *page)))
    :will-unmount (fn [state]
                    (state/set-state! :editor/virtualized-scroll-fn nil)
                    state)}
   [state option]
-  (when-not (rum/react (::loading? state))
-    (page-inner option)))
+  (let [loading? (rum/react (::loading? state))
+        page (rum/react (::*page state))]
+    (when (and page (not loading?))
+      (page-inner (assoc option :page page)))))
 
 (rum/defcs page-cp
   [state option]
@@ -774,7 +756,8 @@
     (str
      (state/get-current-repo)
      "-"
-     (or (:page-name option)
+     (or (:db/id option)
+         (:page-name option)
          (get-page-name state)))))
 
 (defonce layout (atom [js/window.innerWidth js/window.innerHeight]))
@@ -1096,6 +1079,21 @@
       (filter (fn [node] (some #(re-find % (:label node)) filter-patterns)) nodes))
     nodes))
 
+(rum/defc graph-aux
+  [settings forcesettings theme search-graph-filters]
+  (let [[graph set-graph!] (hooks/use-state nil)]
+    (hooks/use-effect!
+     (fn []
+       (p/let [result (state/<invoke-db-worker :thread-api/build-graph (state/get-current-repo)
+                                               (assoc settings
+                                                      :type :global
+                                                      :theme theme))]
+         (set-graph! result)))
+     [theme settings])
+    (when graph
+      (let [graph' (update graph :nodes #(filter-graph-nodes % search-graph-filters))]
+        (global-graph-inner graph' settings forcesettings theme)))))
+
 (rum/defcs global-graph < rum/reactive
   (mixins/event-mixin
    (fn [state]
@@ -1113,10 +1111,8 @@
         theme (state/sub :ui/theme)
         ;; Needed for query to retrigger after reset
         _reset? (rum/react *graph-reset?)
-        graph (graph-handler/build-global-graph theme settings)
-        search-graph-filters (state/sub :search/graph-filters)
-        graph (update graph :nodes #(filter-graph-nodes % search-graph-filters))]
-    (global-graph-inner graph settings forcesettings theme)))
+        search-graph-filters (state/sub :search/graph-filters)]
+    (graph-aux settings forcesettings theme search-graph-filters)))
 
 (rum/defc page-graph-inner < rum/reactive
   [_page graph dark?]
@@ -1140,6 +1136,18 @@
                       (fn [graph]
                         (graph-register-handlers graph (atom nil) (atom nil) dark?))})]))
 
+(rum/defc page-graph-aux
+  [page opts]
+  (let [[graph set-graph!] (hooks/use-state nil)
+        dark? (= (:theme opts) "dark")]
+    (hooks/use-effect!
+     (fn []
+       (p/let [result (state/<invoke-db-worker :thread-api/build-graph (state/get-current-repo) opts)]
+         (set-graph! result)))
+     [opts])
+    (when (seq (:nodes graph))
+      (page-graph-inner page graph dark?))))
+
 (rum/defc page-graph < db-mixins/query rum/reactive
   []
   (let [page (or
@@ -1147,14 +1155,13 @@
                    (state/sub [:route-match :path-params :name]))
               (date/today))
         theme (:ui/theme @state/state)
-        dark? (= theme "dark")
         show-journals-in-page-graph (rum/react *show-journals-in-page-graph?)
-        page-entity (db/get-page page)
-        graph (if (ldb/page? page-entity)
-                (graph-handler/build-page-graph page theme show-journals-in-page-graph)
-                (graph-handler/build-block-graph (uuid page) theme))]
-    (when (seq (:nodes graph))
-      (page-graph-inner page graph dark?))))
+        page-entity (db/get-page page)]
+    (page-graph-aux page
+                    {:type (if (ldb/page? page-entity) :page :block)
+                     :block/uuid (:block/uuid page-entity)
+                     :theme theme
+                     :show-journals? show-journals-in-page-graph})))
 
 (defn batch-delete-dialog
   [pages orphaned-pages? refresh-fn]

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

@@ -132,8 +132,8 @@
             {:title   (t :export-page)
              :options {:on-click #(shui/dialog-open!
                                    (fn []
-                                     (export/export-blocks (:block/uuid page) {:whiteboard? whiteboard?
-                                                                               :export-type :page}))
+                                     (export/export-blocks [(:block/uuid page)] {:whiteboard? whiteboard?
+                                                                                 :export-type :page}))
                                    {:class "w-auto md:max-w-4xl max-h-[80vh] overflow-y-auto"})}})
 
           (when (util/electron?)

+ 31 - 4
src/main/frontend/components/profiler.cljs

@@ -1,6 +1,7 @@
 (ns frontend.components.profiler
   "Profiler UI"
-  (:require [fipp.edn :as fipp]
+  (:require [clojure.set :as set]
+            [fipp.edn :as fipp]
             [frontend.handler.profiler :as profiler-handler]
             [frontend.util :as util]
             [logseq.shui.ui :as shui]
@@ -8,13 +9,15 @@
 
 (rum/defcs profiler < rum/reactive
   (rum/local nil ::reports)
+  (rum/local nil ::mem-leak-reports)
   (rum/local nil ::register-fn-name)
   [state]
   (let [profiling-fns (keys (rum/react profiler-handler/*fn-symbol->origin-fn))
         *reports (get state ::reports)
+        *mem-leak-reports (get state ::mem-leak-reports)
         *register-fn-name (get state ::register-fn-name)]
     [:div
-     [:b "Profiling fns:"]
+     [:b "Profiling fns(Only support UI thread now):"]
      [:div.pb-4
       (for [f-name profiling-fns]
         [:div.flex.flex-row.items-center.gap-2
@@ -38,7 +41,6 @@
                             (when (= v "input fn name here")
                               (set! (.-value (.-target e)) ""))))
         :placeholder "input fn name here"}]]
-     [:hr]
      [:div.flex.gap-2.flex-wrap.items-center.pb-3
       (shui/button
        {:size :sm
@@ -57,4 +59,29 @@
            (-> @*reports
                (update :time-sum update-time-sum)
                (fipp/pprint {:width 20})
-               with-out-str))]])]))
+               with-out-str))]])
+     [:hr]
+     [:b "Atom/Volatile Mem Leak Detect(Only support UI thread now):"]
+     [:pre "Only check atoms/volatiles with a value type of `coll`.
+The report shows refs with coll-size > 5k and atom's watches-count > 1k.
+`ref` means atom or volatile.
+`ref-hash` means `(hash ref)`."]
+     [:div.flex.flex-row.items-center.gap-2
+      (if (= 2 (count (set/difference #{'cljs.core/reset! 'cljs.core/vreset!} (set profiling-fns))))
+        (shui/button
+         {:on-click (fn []
+                      (profiler-handler/mem-leak-detect))}
+         "Start to detect")
+        (shui/button
+         {:size :sm
+          :on-click (fn [_] (reset! *mem-leak-reports (profiler-handler/mem-leak-report)))}
+         (shui/tabler-icon "refresh") "Refresh reports"))]
+     [:pre.select-text
+      (when @*mem-leak-reports
+        (let [ref-hash->ref (:ref-hash->ref @*mem-leak-reports)]
+          (-> @*mem-leak-reports
+              (dissoc :ref-hash->ref)
+              (assoc :ref-hash->take-3-items (update-vals ref-hash->ref (fn [ref] (take 3 @ref)))
+                     :ref-hash->take-3-watch-keys (update-vals ref-hash->ref (fn [ref] (take 3 (.-watches ^js ref)))))
+              (fipp/pprint {:width 20})
+              with-out-str)))]]))

+ 4 - 20
src/main/frontend/components/property.cljs

@@ -159,7 +159,7 @@
     (hooks/use-effect!
      (fn []
        (p/let [repo (state/get-current-repo)
-               properties (db-async/<db-based-get-all-properties repo)
+               properties (db-async/db-based-get-all-properties repo)
                classes (->> (db-model/get-all-classes repo)
                             (remove ldb/built-in?))]
          (set-classes! classes)
@@ -294,6 +294,7 @@
                                                          (.focus input)))}
                                    :align "start"
                                    :as-dropdown? true}))}
+
    (:block/title property)))
 
 (rum/defc property-key-cp < rum/static
@@ -570,7 +571,7 @@
                                                  {:db/id (:db/id block)})]
                                                {:outliner-op :save-block})))})))
 
-(rum/defc properties-section < rum/reactive db-mixins/query
+(rum/defc properties-section < rum/static
   [block properties opts]
   (when (seq properties)
       ;; Sort properties by :block/order
@@ -580,28 +581,13 @@
                                    (:block/order (db/entity k)))) properties)]
       (ordered-properties block properties' opts))))
 
-(defn- async-load-classes!
-  [block]
-  (let [repo (state/get-current-repo)
-        classes (concat (:block/tags block) (outliner-property/get-classes-parents (:block/tags block)))]
-    (doseq [class classes]
-      (db-async/<get-block repo (:db/id class) :children? false))
-    (when (ldb/class? block)
-      (doseq [property (:logseq.property.class/properties block)]
-        (db-async/<get-block repo (:db/id property) :children? false)))
-    classes))
-
 (rum/defcs ^:large-vars/cleanup-todo properties-area < rum/reactive db-mixins/query
   {:init (fn [state]
            (let [target-block (first (:rum/args state))
                  block (resolve-linked-block-if-exists target-block)]
              (assoc state
                     ::id (str (random-uuid))
-                    ::block block
-                    ::classes (async-load-classes! block))))
-   :will-remount (fn [state]
-                   (let [block (db/entity (:db/id (::block state)))]
-                     (assoc state ::classes (async-load-classes! block))))}
+                    ::block block)))}
   [state _target-block {:keys [sidebar-properties?] :as opts}]
   (let [id (::id state)
         db-id (:db/id (::block state))
@@ -610,8 +596,6 @@
                                             (and show?
                                                  (or (= mode :global)
                                                      (and (set? ids) (contains? ids (:block/uuid block))))))
-        _ (doseq [class (::classes state)]
-            (db/sub-block (:db/id class)))
         class? (ldb/class? block)
         properties (:block/properties block)
         remove-built-in-or-other-position-properties

+ 36 - 46
src/main/frontend/components/property/config.cljs

@@ -363,27 +363,19 @@
 
 (rum/defc add-existing-values
   [property values {:keys [toggle-fn]}]
-  (let [uuid-values? (every? uuid? values)
-        values' (if uuid-values?
-                  (let [values' (map #(db/entity [:block/uuid %]) values)]
-                    (->> values'
-                         (util/distinct-by db-property/closed-value-content)
-                         (map :block/uuid)))
-                  values)]
-    [:div.flex.flex-col.gap-1.w-64.p-4.overflow-y-auto
-     {:class "max-h-[50dvh]"}
-     [:div "Existing values:"]
-     [:ol
-      (for [value values']
-        [:li (if (uuid? value)
-               (let [result (db/entity [:block/uuid value])]
-                 (db-property/closed-value-content result))
-               (str value))])]
-     (shui/button
-      {:on-click (fn []
-                   (p/let [_ (db-property-handler/add-existing-values-to-closed-values! (:db/id property) values')]
-                     (toggle-fn)))}
-      "Add choices")]))
+  [:div.flex.flex-col.gap-1.w-64.p-4.overflow-y-auto
+   {:class "max-h-[50dvh]"}
+   [:div "Existing values:"]
+   [:ol
+    (for [value values]
+      [:li (:label value)])]
+   (shui/button
+    {:on-click (fn []
+                 (p/let [_ (db-property-handler/add-existing-values-to-closed-values! (:db/id property)
+                                                                                      (map (fn [{:keys [value]}]
+                                                                                             (:block/uuid value)) values))]
+                   (toggle-fn)))}
+    "Add choices")])
 
 (rum/defc choices-sub-pane < rum/reactive db-mixins/query
   [property {:keys [disabled?] :as opts}]
@@ -431,29 +423,29 @@
         {:icon :plus :title "Add choice"
          :item-props {:on-click
                       (fn [^js e]
-                        (p/let [values (db-async/<get-block-property-values (state/get-current-repo) (:db/ident property))
+                        (p/let [values (db-async/<get-property-values (:db/ident property) {})
                                 existing-values (seq (:property/closed-values property))
-                                values (if (seq existing-values)
-                                         (let [existing-ids (set (map :db/id existing-values))]
-                                           (remove (fn [id] (existing-ids id)) values))
-                                         values)]
-                          (shui/popup-show! (.-target e)
-                                            (fn [{:keys [id]}]
-                                              (let [opts {:toggle-fn (fn [] (shui/popup-hide! id))}
-                                                    values' (->> (if (contains? db-property-type/all-ref-property-types (:logseq.property/type property))
-                                                                   (->> values
-                                                                        (map db/entity)
-                                                                        (remove (fn [e]
-                                                                                  (let [value (db-property/property-value-content e)]
-                                                                                    (and (string? value) (string/blank? value)))))
-                                                                        (map :block/uuid))
-                                                                   (remove string/blank? values))
-                                                                 distinct)]
-                                                (if (seq values')
-                                                  (add-existing-values property values' opts)
-                                                  (choice-base-edit-form property {:create? true}))))
-                                            {:id :ls-base-edit-form
-                                             :align "start"})))}}))]))
+                                values' (if (seq existing-values)
+                                          (let [existing-ids (set (map :db/id existing-values))
+                                                existing-titles (set (map db-property/property-value-content existing-values))]
+                                            (remove (fn [{:keys [label value]}]
+                                                      (or (existing-ids (:db/id value))
+                                                          (existing-titles label)
+                                                          (string/blank? label))) values))
+                                          (remove (fn [{:keys [label _value]}]
+                                                    (string/blank? label))
+                                                  values))]
+                          (p/do!
+                           (when (seq values')
+                             (db-async/<get-blocks (state/get-current-repo) (map (fn [{:keys [value]}] (:db/id value)) values)))
+                           (shui/popup-show! (.-target e)
+                                             (fn [{:keys [id]}]
+                                               (let [opts {:toggle-fn (fn [] (shui/popup-hide! id))}]
+                                                 (if (seq values')
+                                                   (add-existing-values property values' opts)
+                                                   (choice-base-edit-form property {:create? true}))))
+                                             {:id :ls-base-edit-form
+                                              :align "start"}))))}}))]))
 
 (rum/defc checkbox-state-mapping
   [choices]
@@ -762,11 +754,9 @@
 (rum/defcs dropdown-editor < rum/reactive db-mixins/query
   {:init (fn [state]
            (let [*values (atom :loading)
-                 repo (state/get-current-repo)
                  property (first (:rum/args state))
                  ident (:db/ident property)]
-             (p/let [_ (db-async/<get-block repo (:block/uuid property))
-                     result (db-async/<get-block-property-values repo ident)]
+             (p/let [result (db-async/<get-property-values ident)]
                (reset! *values result))
              (assoc state ::values *values)))}
   [state property* owner-block opts]

+ 201 - 199
src/main/frontend/components/property/value.cljs

@@ -4,7 +4,6 @@
             [cljs-time.local :as local]
             [clojure.set :as set]
             [clojure.string :as string]
-            [datascript.impl.entity :as de]
             [dommy.core :as d]
             [frontend.components.icon :as icon-component]
             [frontend.components.select :as select]
@@ -42,6 +41,10 @@
             [promesa.core :as p]
             [rum.core :as rum]))
 
+(defn- entity-map?
+  [m]
+  (and (map? m) (:db/id m)))
+
 (rum/defc property-empty-btn-value
   [property & opts]
   (let [text (cond
@@ -56,14 +59,15 @@
                    text))))
 
 (rum/defc property-empty-text-value
-  [property {:keys [property-position]}]
-  [:span.inline-flex.items-center.cursor-pointer
+  [property {:keys [property-position table-view?]}]
+  [:span.inline-flex.items-center.cursor-pointer.w-full
    (merge {:class "empty-text-btn" :variant :text})
-   (if property-position
-     (if-let [icon (:logseq.property/icon property)]
-       (icon-component/icon icon {:color? true})
-       (ui/icon "line-dashed"))
-     "Empty")])
+   (when-not table-view?
+     (if property-position
+       (if-let [icon (:logseq.property/icon property)]
+         (icon-component/icon icon {:color? true})
+         (ui/icon "line-dashed"))
+       "Empty"))])
 
 (defn- get-selected-blocks
   []
@@ -235,8 +239,15 @@
 (defn- add-or-remove-property-value
   [block property value selected? {:keys [refresh-result-f] :as opts}]
   (let [many? (db-property/many? property)
-        blocks (get-operating-blocks block)]
+        blocks (get-operating-blocks block)
+        repo (state/get-current-repo)]
     (p/do!
+     (db-async/<get-block repo (:db/id block) {:children? false})
+     (when (and selected?
+                (= :db.type/ref (:db/valueType property))
+                (number? value)
+                (not (db/entity value)))
+       (db-async/<get-block repo value {:children? false}))
      (if selected?
        (<add-property! block (:db/ident property) value
                        {:selected? selected?
@@ -632,10 +643,10 @@
         tags? (= :block/tags (:db/ident property))
         alias? (= :block/alias (:db/ident property))
         tags-or-alias? (or tags? alias?)
-        block (db/entity (:db/id block))
+        block (or (db/entity (:db/id block)) block)
         selected-choices (when block
                            (when-let [v (get block (:db/ident property))]
-                             (if (every? de/entity? v)
+                             (if (every? entity-map? v)
                                (map :db/id v)
                                [(:db/id v)])))
         parent-property? (= (:db/ident property) :logseq.property/parent)
@@ -644,57 +655,51 @@
         get-all-classes-f (fn []
                             (model/get-all-classes repo {:except-root-class? true
                                                          :except-private-tags? (not (contains? #{:logseq.property/template-applied-to} (:db/ident property)))}))
-        nodes
-        (->>
-         (cond
-           parent-property?
-           (let [;; Disallows cyclic hierarchies
-                 exclude-ids (-> (set (map (fn [id] (:block/uuid (db/entity id))) children-pages))
-                                 (conj (:block/uuid block))) ; break cycle
-                 options (if (ldb/class? block)
-                           (model/get-all-classes repo)
-                           (when (ldb/internal-page? block)
-                             (cond->>
-                              (->> (model/get-all-pages repo)
-                                   (filter ldb/internal-page?)
-                                   (remove ldb/built-in?)))))
-                 excluded-options (remove (fn [e] (contains? exclude-ids (:block/uuid e))) options)]
-             excluded-options)
-
-           (= property-type :class)
-           (get-all-classes-f)
-
-           (seq classes)
-           (->>
-            (mapcat
-             (fn [class]
-               (model/get-class-objects repo (:db/id class)))
-             classes)
-            distinct)
-
-           :else
-           (if (empty? result)
-             (let [v (get block (:db/ident property))]
-               (remove #(= :logseq.property/empty-placeholder (:db/ident %))
-                       (if (every? de/entity? v) v [v])))
-             (remove (fn [node]
-                       (or (= (:db/id block) (:db/id node))
+        nodes (cond
+                parent-property?
+                (let [;; Disallows cyclic hierarchies
+                      exclude-ids (-> (set (map (fn [id] (:block/uuid (db/entity id))) children-pages))
+                                      (conj (:block/uuid block))) ; break cycle
+                      options (if (ldb/class? block)
+                                (model/get-all-classes repo)
+                                result)
+                      excluded-options (remove (fn [e] (contains? exclude-ids (:block/uuid e))) options)]
+                  excluded-options)
+
+                (= property-type :class)
+                (get-all-classes-f)
+
+                (seq classes)
+                (->>
+                 (mapcat
+                  (fn [class]
+                    (model/get-class-objects repo (:db/id class)))
+                  classes)
+                 distinct)
+
+                :else
+                (if (empty? result)
+                  (let [v (get block (:db/ident property))]
+                    (remove #(= :logseq.property/empty-placeholder (:db/ident %))
+                            (if (every? entity-map? v) v [v])))
+                  (remove (fn [node]
+                            (or (= (:db/id block) (:db/id node))
                              ;; A page's alias can't be itself
-                           (and alias? (= (or (:db/id (:block/page block))
-                                              (:db/id block))
-                                          (:db/id node)))
-                           (cond
-                             (= property-type :class)
-                             (ldb/private-tags (:db/ident node))
-
-                             (and property-type (not= property-type :node))
-                             (if (= property-type :page)
-                               (not (db/page? node))
-                               (not (contains? (ldb/get-entity-types node) property-type)))
-
-                             :else
-                             false)))
-                     result))))
+                                (and alias? (= (or (:db/id (:block/page block))
+                                                   (:db/id block))
+                                               (:db/id node)))
+                                (cond
+                                  (= property-type :class)
+                                  (ldb/private-tags (:db/ident node))
+
+                                  (and property-type (not= property-type :node))
+                                  (if (= property-type :page)
+                                    (not (db/page? node))
+                                    (not (contains? (ldb/get-entity-types node) property-type)))
+
+                                  :else
+                                  false)))
+                          result)))
 
         options (map (fn [node]
                        (let [id (or (:value node) (:db/id node))
@@ -809,14 +814,27 @@
                            class?
                            (conj (frontend.db/entity :logseq.class/Tag)))
         parent-property? (= (:db/ident property) :logseq.property/parent)]
-    (when (and (not parent-property?) (seq non-root-classes))
-      ;; effect runs once
-      (hooks/use-effect!
-       (fn []
+
+    ;; effect runs once
+    (hooks/use-effect!
+     (fn []
+       (cond
+         (and parent-property? (not (ldb/class? block))
+              (ldb/internal-page? block))
+         (p/let [result (db-async/<get-tag-pages repo (:db/id (db/entity :logseq.class/Page)))
+                 result' (->> result
+                              (remove ldb/built-in?))]
+           (set-result! result'))
+
+         parent-property?
+         nil
+
+         (seq non-root-classes)
          (p/let [result (p/all (map (fn [class] (db-async/<get-tag-objects repo (:db/id class))) non-root-classes))
                  result' (distinct (apply concat result))]
-           (set-result! result')))
-       []))
+           (set-result! result'))))
+     [])
+
     (select-node property opts' result)))
 
 (rum/defcs select < rum/reactive db-mixins/query
@@ -827,8 +845,7 @@
                                       (p/let [property-ident (if (= :logseq.property/default-value (:db/ident property))
                                                                (:db/ident block)
                                                                (:db/ident property))
-                                              result (db-async/<get-block-property-values (state/get-current-repo)
-                                                                                          property-ident)]
+                                              result (db-async/<get-property-values property-ident)]
                                         (reset! *values result))))]
              (refresh-result-f)
              (assoc state
@@ -839,12 +856,10 @@
    {:keys [*show-new-property-config? exit-edit?] :as opts}]
   (let [*values (::values state)
         refresh-result-f (::refresh-result-f state)
-        values (rum/react *values)
-        block (db/sub-block (:db/id block))]
+        values (rum/react *values)]
     (when-not (= :loading values)
       (let [type (:logseq.property/type property)
             closed-values? (seq (:property/closed-values property))
-            ref-type? (db-property-type/all-ref-property-types type)
             items (if closed-values?
                     (let [date? (and
                                  (= (:db/ident property) :logseq.task/recur-unit)
@@ -864,17 +879,9 @@
                                  :label-value value}))
                             values))
                     (->> values
-                         (mapcat (fn [value]
-                                   (if (coll? value)
-                                     (map (fn [v] {:value v}) value)
-                                     [{:value value}])))
-                         (map (fn [{:keys [value]}]
-                                (if (and ref-type? (number? value))
-                                  (when-let [e (db/entity value)]
-                                    {:label (db-property/property-value-content e)
-                                     :value value})
-                                  {:label value
-                                   :value value})))
+                         (map (fn [{:keys [value label]}]
+                                {:label label
+                                 :value (:db/id value)}))
                          (distinct)))
             items (->> (if (= :date type)
                          (map (fn [m] (let [label (:block/title (db/entity (:value m)))]
@@ -888,7 +895,7 @@
                                                         {:exit-edit? exit-edit?
                                                          :refresh-result-f refresh-result-f})))
             selected-choices' (get block (:db/ident property))
-            selected-choices (if (every? de/entity? selected-choices')
+            selected-choices (if (every? #(and (map? %) (:db/id %)) selected-choices')
                                (map :db/id selected-choices')
                                [selected-choices'])]
         (select-aux block property
@@ -918,100 +925,95 @@
 (rum/defcs property-normal-block-value <
   {:init (fn [state]
            (assoc state :container-id (state/get-next-container-id)))}
-  [state block property value-block]
+  [state block property value-block opts]
   (let [container-id (:container-id state)
         multiple-values? (db-property/many? property)
         block-container (state/get-component :block/container)
         blocks-container (state/get-component :block/blocks-container)
-        value-block (if (and (coll? value-block) (every? de/entity? value-block))
+        value-block (if (and (coll? value-block) (every? entity-map? value-block))
                       (set (remove #(= (:db/ident %) :logseq.property/empty-placeholder) value-block))
                       value-block)
         default-value (:logseq.property/default-value property)
         default-value? (and
                         (:db/id default-value)
                         (= (:db/id value-block) (:db/id default-value))
-                        (not= (:db/ident property) :logseq.property/default-value))]
-    (if (seq value-block)
-      [:div.property-block-container.content.w-full
-       (let [config {:id (str (if multiple-values?
-                                (:block/uuid block)
-                                (:block/uuid value-block)))
-                     :container-id container-id
-                     :editor-box (state/get-component :editor/box)
-                     :property-block? true
-                     :on-block-content-pointer-down (when default-value?
-                                                      (fn [_e]
-                                                        (<create-new-block! block property (or (:block/title default-value) ""))))
-                     :p-block (:db/id block)
-                     :p-property (:db/id property)}]
-         (if (set? value-block)
-           (blocks-container config (ldb/sort-by-order value-block))
-           (rum/with-key
-             (block-container (assoc config
-                                     :property-default-value? default-value?) value-block)
-             (str (:db/id property) "-" (:block/uuid value-block)))))]
-      [:div
-       {:tabIndex 0
-        :on-click (fn [] (<create-new-block! block property ""))}
-       (property-empty-btn-value property)])))
+                        (not= (:db/ident property) :logseq.property/default-value))
+        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)
+       {:create-new-block #(<create-new-block! block property "")
+        :property-ident (:db/ident property)})
+      (cond
+        (seq value-block)
+        [:div.property-block-container.content.w-full
+         (let [config {:id (str (if multiple-values?
+                                  (:block/uuid block)
+                                  (:block/uuid value-block)))
+                       :container-id container-id
+                       :editor-box (state/get-component :editor/box)
+                       :property-block? true
+                       :on-block-content-pointer-down (when default-value?
+                                                        (fn [_e]
+                                                          (<create-new-block! block property (or (:block/title default-value) ""))))
+                       :p-block (:db/id block)
+                       :p-property (:db/id property)
+                       :view? (:view? opts)}]
+           (if (set? value-block)
+             (blocks-container config (ldb/sort-by-order value-block))
+             (rum/with-key
+               (block-container (assoc config
+                                       :property-default-value? default-value?) value-block)
+               (str (:db/id block) "-" (:db/id property) "-" (:db/id value-block)))))]
+
+        :else
+        [:div.w-full.h-full
+         {:tabIndex 0
+          :class (if (:table-view? opts) "cursor-pointer" "cursor-text")
+          :on-click #(<create-new-block! block property "")}]))))
 
-(rum/defcs property-block-value < rum/reactive db-mixins/query
-  {:init (fn [state]
-           (let [block (first (:rum/args state))]
-             (when-let [block-id (or (:db/id block) (:block/uuid block))]
-               (db-async/<get-block (state/get-current-repo) block-id :children? true)))
-           state)}
-  [state value block property page-cp]
-  (when value
-    (if (state/sub-async-query-loading (:block/uuid value))
-      [:div.text-sm.opacity-70 "loading"]
-      (if-let [v-block (db/sub-block (:db/id value))]
-        (let [class? (ldb/class? v-block)
-              invalid-warning [:div.warning.text-sm
-                               "Invalid block value, please delete the current property."]]
-          (when v-block
-            (cond
-              (:block/page v-block)
-              (property-normal-block-value block property v-block)
-
-              ;; page/class/etc.
-              (entity-util/page? v-block)
-              (rum/with-key
-                (page-cp {:disable-preview? true
-                          :tag? class?} v-block)
-                (:db/id v-block))
-              :else
-              invalid-warning)))
-        (property-empty-btn-value property)))))
+(rum/defc property-block-value
+  [value block property page-cp opts]
+  (let [v-block value
+        class? (ldb/class? v-block)]
+    (cond
+      (entity-util/page? v-block)
+      (rum/with-key
+        (page-cp {:disable-preview? true
+                  :tag? class?} v-block)
+        (:db/id v-block))
+
+      :else
+      (property-normal-block-value block property v-block opts))))
 
 (rum/defc closed-value-item < rum/reactive db-mixins/query
   [value {:keys [inline-text icon?]}]
   (when value
-    (let [eid (if (de/entity? value) (:db/id value) [:block/uuid value])]
-      (when-let [block (db/sub-block (:db/id (db/entity eid)))]
-        (let [property-block? (db-property/property-created-block? block)
-              value' (db-property/closed-value-content block)
-              icon (pu/get-block-property-value block :logseq.property/icon)]
-          (cond
-            icon
-            (if icon?
-              (icon-component/icon icon {:color? true})
-              [:div.flex.flex-row.items-center.gap-1.h-6
-               (icon-component/icon icon {:color? true})
-               (when value'
-                 [:span value'])])
-
-            property-block?
-            value'
-
-            (= type :number)
-            [:span.number (str value')]
-
-            :else
-            (inline-text {} :markdown (str value'))))))))
+    (let [eid (if (entity-map? value) (:db/id value) [:block/uuid value])
+          block (or (db/sub-block (:db/id (db/entity eid))) value)
+          property-block? (db-property/property-created-block? block)
+          value' (db-property/closed-value-content block)
+          icon (pu/get-block-property-value block :logseq.property/icon)]
+      (cond
+        icon
+        (if icon?
+          (icon-component/icon icon {:color? true})
+          [:div.flex.flex-row.items-center.gap-1.h-6
+           (icon-component/icon icon {:color? true})
+           (when value'
+             [:span value'])])
+
+        property-block?
+        value'
+
+        (= type :number)
+        [:span.number (str value')]
+
+        :else
+        (inline-text {} :markdown (str value'))))))
 
 (rum/defc select-item
-  [property type value {:keys [page-cp inline-text other-position? property-position _icon?] :as opts}]
+  [property type value {:keys [page-cp inline-text other-position? property-position table-view? _icon?] :as opts}]
   (let [closed-values? (seq (:property/closed-values property))
         tag? (or (:tag? opts) (= (:db/ident property) :block/tags))
         inline-text-cp (fn [content]
@@ -1035,15 +1037,16 @@
            (page-cp {:disable-preview? true
                      :tag? tag?
                      :property-position property-position
-                     :meta-click? other-position?} value)
+                     :meta-click? other-position?
+                     :table-view? table-view?} value)
            (:db/id value)))
 
        (contains? #{:node :class :property :page} type)
        (when-let [reference (state/get-component :block/reference)]
-         (reference {} (:block/uuid value)))
+         (reference {:table-view? table-view?} (:block/uuid value)))
 
-       (de/entity? value)
-       (when-some [content (str (db-property/property-value-content value))]
+       (and (map? value) (some? (db-property/property-value-content value)))
+       (let [content (str (db-property/property-value-content value))]
          (inline-text-cp content))
 
        :else
@@ -1093,7 +1096,8 @@
 
 (defn- property-value-inner
   [block property value {:keys [inline-text page-cp
-                                dom-id row?]}]
+                                dom-id row?]
+                         :as opts}]
   (let [multiple-values? (db-property/many? property)
         class (str (when-not row? "flex flex-1 ")
                    (when multiple-values? "property-value-content"))
@@ -1103,37 +1107,28 @@
      {:id (or dom-id (random-uuid))
       :tabIndex 0
       :class (str class " " (when-not text-ref-type? "jtrigger"))
-      :style {:min-height 24}
-      :on-click (fn []
-                  (when (and text-ref-type? (nil? value))
-                    (<create-new-block! block property "")))}
+      :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"]
 
-       (and text-ref-type? (nil? (:block/title value)))
-       [:div.jtrigger (property-empty-btn-value property)]
-
        text-ref-type?
-       (property-block-value value block property page-cp)
+       (property-block-value value block property page-cp opts)
 
        :else
        (inline-text {} :markdown (macro-util/expand-value-if-macro (str value) (state/get-macros))))]))
 
-(rum/defcs property-scalar-value < rum/static rum/reactive
-  [state block property value* {:keys [container-id editing? on-chosen]
+(rum/defcs property-scalar-value-aux < rum/static rum/reactive
+  [state block property value* {:keys [editing? on-chosen]
                                 :as opts}]
   (let [property (model/sub-block (:db/id property))
         type (:logseq.property/type property)
-        editing? (or editing?
-                     (and (state/sub-editing? [container-id (:block/uuid block)])
-                          (= (:db/id property) (:db/id (:property (state/get-editor-action-data))))))
         batch? (batch-operation?)
         closed-values? (seq (:property/closed-values property))
         select-type?' (or (select-type? block property)
                           (and editing? batch? (contains? #{:default :url} type) (not closed-values?)))
         select-opts {:on-chosen on-chosen}
-        value (if (and (de/entity? value*) (= (:db/ident value*) :logseq.property/empty-placeholder))
+        value (if (and (entity-map? value*) (= (:db/ident value*) :logseq.property/empty-placeholder))
                 nil
                 value*)]
     (if (= :logseq.property/icon (:db/ident property))
@@ -1186,12 +1181,21 @@
           [:div.flex.flex-1
            (property-value-inner block property value opts)])))))
 
+(rum/defc property-scalar-value
+  [block property value* {:keys [container-id editing?]
+                          :as opts}]
+  (let [block-editing? (state/sub-editing? [container-id (:block/uuid block)])
+        editing (or editing?
+                    (and block-editing?
+                         (= (:db/id property) (:db/id (:property (state/get-editor-action-data))))))]
+    (property-scalar-value-aux block property value* (assoc opts :editing? editing))))
+
 (rum/defc multiple-values-inner
   [block property v {:keys [on-chosen editing?] :as opts}]
   (let [type (:logseq.property/type property)
         date? (= type :date)
         *el (rum/use-ref nil)
-        items (cond->> (if (de/entity? v) #{v} v)
+        items (cond->> (if (entity-map? v) #{v} v)
                 (= (:db/ident property) :block/tags)
                 (remove (fn [v] (contains? ldb/hidden-tags (:db/ident v)))))
         select-cp (fn [select-opts]
@@ -1233,7 +1237,7 @@
              (concat
               (->> (for [item items]
                      (rum/with-key (select-item property type item opts) (or (:block/uuid item) (str item))))
-                   (interpose [:span.opacity-50.-ml-2 ","]))
+                   (interpose [:span.opacity-50.-ml-1 ","]))
               (when date?
                 [(property-value-date-picker block property nil {:toggle-fn toggle-fn})]))
              (if date?
@@ -1242,8 +1246,7 @@
 
 (rum/defc multiple-values < rum/reactive db-mixins/query
   [block property opts]
-  (let [block (db/sub-block (:db/id block))
-        value (get block (:db/ident property))
+  (let [value (get block (:db/ident property))
         value' (if (coll? value) value
                    (when (some? value) #{value}))]
     (multiple-values-inner block property value' opts)))
@@ -1253,8 +1256,7 @@
                          :as opts}]
   (ui/catch-error
    (ui/block-error "Something wrong" {})
-   (let [block (db/sub-block (:db/id block))
-         block-cp (state/get-component :block/blocks-container)
+   (let [block-cp (state/get-component :block/blocks-container)
          properties-cp (state/get-component :block/properties-cp)
          opts (merge opts
                      {:page-cp (state/get-component :block/page-cp)
@@ -1266,22 +1268,22 @@
          editor-id (str dom-id "-editor")
          type (:logseq.property/type property)
          multiple-values? (db-property/many? property)
-         v (get block (:db/ident property))
-         v (cond
-             (and multiple-values? (or (set? v) (and (coll? v) (empty? v)) (nil? v)))
-             v
-             multiple-values?
-             #{v}
-             (set? v)
-             (first v)
-             :else
-             v)
+         v (let [v (get block (:db/ident property))]
+             (cond
+               (and multiple-values? (or (set? v) (and (coll? v) (empty? v)) (nil? v)))
+               v
+               multiple-values?
+               #{v}
+               (set? v)
+               (first v)
+               :else
+               v))
          self-value-or-embedded? (fn [v]
                                    (or (= (:db/id v) (:db/id block))
                                        ;; property value self embedded
                                        (= (:db/id (:block/link v)) (:db/id block))))]
-     (if (and (or (and (de/entity? v) (self-value-or-embedded? v))
-                  (and (coll? v) (every? de/entity? v)
+     (if (and (or (and (entity-map? v) (self-value-or-embedded? v))
+                  (and (coll? v) (every? entity-map? v)
                        (some self-value-or-embedded? v))
                   (and (= p-block (:db/id block)) (= p-property (:db/id property))))
               (not= :logseq.class/Tag (:db/ident block)))
@@ -1308,7 +1310,7 @@
                                                   :class-schema? true})
 
                          (and multiple-values? (contains? #{:default :url} type) (not closed-values?) (not editing?))
-                         (property-normal-block-value block property v)
+                         (property-normal-block-value block property v opts)
 
                          multiple-values?
                          (multiple-values block property opts)

+ 59 - 65
src/main/frontend/components/query.cljs

@@ -15,7 +15,6 @@
             [frontend.util :as util]
             [lambdaisland.glogi :as log]
             [logseq.db :as ldb]
-            [logseq.shui.hooks :as hooks]
             [rum.core :as rum]))
 
 (defn- built-in-custom-query?
@@ -61,8 +60,10 @@
            (util/hiccup-keywordize result))
 
          (and db-graph? (not (:built-in-query? config)))
-         (query-view/query-result (assoc config :id (str (:block/uuid current-block)))
-                                  current-block result)
+         (when-let [query (:logseq.property/query current-block)]
+           (when-not (string/blank? (:block/title query))
+             (query-view/query-result (assoc config :id (str (:block/uuid current-block)))
+                                      current-block result)))
 
          (and (not db-graph?)
               (or page-list? table?))
@@ -126,70 +127,63 @@
                           (:block/collapsed? current-block)))]
     collapsed?'))
 
-(rum/defc custom-query* < rum/reactive db-mixins/query
-  [{:keys [*query-error db-graph? dsl-query? built-in-query? table? current-block] :as config}
-   {:keys [builder query view collapsed?] :as q}
-   *result]
-  (let [collapsed?' (:collapsed? config)
-        result' (rum/react *result)]
-    (let [result (when *result (query-result/transform-query-result config q result'))
-          ;; Remove hidden pages from result
-          result (if (and (coll? result) (not (map? result)))
-                   (->> result
-                        (remove (fn [b] (when (and (map? b) (:block/title b)) (ldb/hidden? (:block/title b)))))
-                        (remove (fn [b]
-                                  (when (and current-block (:db/id current-block)) (= (:db/id b) (:db/id current-block))))))
-                   result)
-          ;; Args for displaying query header and results
-          view-fn (if (keyword? view) (get-in (state/sub-config) [:query/views view]) view)
-          view-f (and view-fn (sci/eval-string (pr-str view-fn)))
-          page-list? (and (seq result) (some? (:block/name (first result))))
-          opts {:query-error-atom *query-error
-                :current-block current-block
-                :table? table?
-                :view-f view-f
-                :page-list? page-list?
-                :result result
-                :group-by-page? (query-result/get-group-by-page q {:table? table?})}]
-      (if (:custom-query? config)
+(rum/defcs custom-query* < rum/reactive db-mixins/query
+  {:init (fn [state]
+           (assoc state ::result (atom nil)))}
+  [state {:keys [*query-error db-graph? dsl-query? built-in-query? table? current-block] :as config}
+   {:keys [builder query view collapsed?] :as q}]
+  (let [*result (::result state)
+        collapsed?' (:collapsed? config)
+        result' (query-result/run-custom-query config q *result *query-error)
+        result (when result' (query-result/transform-query-result config q result'))
+        ;; Remove hidden pages from result
+        result (if (and (coll? result) (not (map? result)))
+                 (->> result
+                      (remove (fn [b] (when (and (map? b) (:block/title b)) (ldb/hidden? (:block/title b)))))
+                      (remove (fn [b]
+                                (when (and current-block (:db/id current-block)) (= (:db/id b) (:db/id current-block))))))
+                 result)
+        ;; Args for displaying query header and results
+        view-fn (if (keyword? view) (get-in (state/sub-config) [:query/views view]) view)
+        view-f (and view-fn (sci/eval-string (pr-str view-fn)))
+        page-list? (and (seq result) (some? (:block/name (first result))))
+        opts {:query-error-atom *query-error
+              :current-block current-block
+              :table? table?
+              :view-f view-f
+              :page-list? page-list?
+              :result result
+              :group-by-page? (query-result/get-group-by-page q {:table? table?})}]
+    (if (:custom-query? config)
       ;; Don't display recursive results when query blocks are a query result
-        [:code (if dsl-query? (str "Results for " (pr-str query)) "Advanced query results")]
-        (when-not (and built-in-query? (empty? result))
-          [:div.custom-query (get config :attr {})
-           (when (and (not db-graph?) (not built-in-query?))
-             (file-query/custom-query-header config
-                                             q
-                                             {:query-error-atom *query-error
-                                              :current-block current-block
-                                              :table? table?
-                                              :view-f view-f
-                                              :page-list? page-list?
-                                              :result result
-                                              :collapsed? collapsed?'}))
-
-           (when (and dsl-query? builder) builder)
+      [:code (if dsl-query? (str "Results for " (pr-str query)) "Advanced query results")]
+      (when-not (and built-in-query? (empty? result))
+        [:div.custom-query (get config :attr {})
+         (when (and (not db-graph?) (not built-in-query?))
+           (file-query/custom-query-header config
+                                           q
+                                           {:query-error-atom *query-error
+                                            :current-block current-block
+                                            :table? table?
+                                            :view-f view-f
+                                            :page-list? page-list?
+                                            :*result *result
+                                            :result result
+                                            :collapsed? collapsed?'}))
 
-           (if built-in-query?
-             [:div {:style {:margin-left 2}}
-              (ui/foldable
-               (query-title config (:title q) {:result-count (count result)})
-               (fn []
-                 (custom-query-inner config q opts))
-               {:default-collapsed? collapsed?
-                :title-trigger? true})]
-             [:div.bd
-              (when-not collapsed?'
-                (custom-query-inner config q opts))])])))))
+         (when (and dsl-query? builder) builder)
 
-(rum/defc trigger-custom-query
-  [config q]
-  (let [[result set-result!] (rum/use-state nil)]
-    (hooks/use-effect!
-     (fn []
-       (query-result/trigger-custom-query! config q (:*query-error config) set-result!))
-     [q])
-    (when (util/atom? result)
-      (custom-query* config q result))))
+         (if built-in-query?
+           [:div {:style {:margin-left 2}}
+            (ui/foldable
+             (query-title config (:title q) {:result-count (count result)})
+             (fn []
+               (custom-query-inner config q opts))
+             {:default-collapsed? collapsed?
+              :title-trigger? true})]
+           [:div.bd
+            (when-not collapsed?'
+              (custom-query-inner config q opts))])]))))
 
 (rum/defcs custom-query < rum/static
   {:init (fn [state]
@@ -227,4 +221,4 @@
                         :built-in-query? (built-in-custom-query? (:title q))
                         :*query-error *query-error)]
      (when (or built-in-collapsed? (not db-graph?) (not collapsed?'))
-       (trigger-custom-query config' q)))))
+       (custom-query* config' q)))))

+ 44 - 53
src/main/frontend/components/query/builder.cljs

@@ -19,7 +19,6 @@
             [logseq.common.util.page-ref :as page-ref]
             [logseq.db :as ldb]
             [logseq.db.frontend.property :as db-property]
-            [logseq.db.frontend.property.type :as db-property-type]
             [logseq.db.sqlite.util :as sqlite-util]
             [logseq.graph-parser.db :as gp-db]
             [logseq.shui.hooks :as hooks]
@@ -172,55 +171,43 @@
 
 (rum/defc property-value-select-inner
   < rum/reactive db-mixins/query
-  [repo *property *private-property? *find *tree opts loc values {:keys [db-graph? ref-property? property-type]}]
-  (let [;; FIXME: lazy load property values consistently on first call
-        ;; Guard against non ref properties like :logseq.property/icon
-        _ (when (and db-graph? ref-property?)
-            (doseq [id values] (db/sub-block id)))
-        values' (if db-graph?
-                  (if ref-property?
-                    (map #(db-property/property-value-content (db/entity repo %)) values)
-                    (if (contains? #{:checkbox :keyword :raw-number :string} property-type)
-                      values
-                      ;; Don't display non-ref property values as they don't have display and query support
-                      []))
-                  values)
-        values'' (map #(hash-map :value (str %)
-                                   ;; Preserve original-value as some values like boolean do not display in select
-                                 :original-value %)
-                      (cons "Select all" values'))]
-    (select values''
-            (fn [{:keys [original-value]}]
+  [*property *private-property? *find *tree opts loc values {:keys [db-graph?]}]
+  (let [values' (cons {:label "Select all"
+                       :value "Select all"}
+                      values)
+        find' (rum/react *find)]
+    (select values'
+            (fn [{:keys [value]}]
               (let [k (cond
                         db-graph? (if @*private-property? :private-property :property)
-                        (= (rum/react *find) :page) :page-property
+                        (= find' :page) :page-property
                         :else :property)
-                    x (if (= original-value "Select all")
+                    x (if (= value "Select all")
                         [k @*property]
-                        [k @*property original-value])]
+                        [k @*property value])]
                 (reset! *property nil)
                 (append-tree! *tree opts loc x))))))
 
 (rum/defc property-value-select
   [repo *property *private-property? *find *tree opts loc]
   (let [db-graph? (sqlite-util/db-based-graph? repo)
-        property-type (when db-graph? (:logseq.property/type (db/entity repo @*property)))
-        ref-property? (and db-graph? (contains? db-property-type/all-ref-property-types property-type))
         [values set-values!] (rum/use-state nil)]
     (hooks/use-effect!
-     (fn []
+     (fn [_property]
        (p/let [result (if db-graph?
-                        (db-async/<get-block-property-values repo @*property)
-                        (db-async/<file-get-property-values repo @*property))]
-         (when (and db-graph? ref-property?)
-           (doseq [db-id result]
-             (db-async/<get-block repo db-id :children? false)))
+                        (p/let [result (db-async/<get-property-values @*property)]
+                          (map (fn [{:keys [label _value]}]
+                                 {:label label
+                                  :value label})
+                               result))
+                        (p/let [result (db-async/<file-get-property-values repo @*property)]
+                          (map (fn [value]
+                                 {:label (str value)
+                                  :value value}) result)))]
          (set-values! result)))
      [@*property])
-    (property-value-select-inner repo *property *private-property? *find *tree opts loc values
-                                 {:db-graph? db-graph?
-                                  :ref-property? ref-property?
-                                  :property-type property-type})))
+    (property-value-select-inner *property *private-property? *find *tree opts loc values
+                                 {:db-graph? db-graph?})))
 
 (rum/defc tags
   [repo *tree opts loc]
@@ -238,6 +225,19 @@
               (fn [{:keys [value]}]
                 (append-tree! *tree opts loc [(if db-based? :tags :page-tags) value]))))))
 
+(rum/defc page-search
+  [on-chosen]
+  (let [[result set-result!] (hooks/use-state nil)
+        [loading? set-loading!] (hooks/use-state nil)]
+    (hooks/use-effect!
+     (fn []
+       (set-loading! true)
+       (p/let [result (state/<invoke-db-worker :thread-api/get-all-page-titles (state/get-current-repo))]
+         (set-result! result)
+         (set-loading! false)))
+     [])
+    (select result on-chosen {:loading? loading?})))
+
 (defn- db-based-query-filter-picker
   [state *find *tree loc clause opts]
   (let [*mode (::mode state)
@@ -291,18 +291,14 @@
                               (append-tree! *tree opts loc (vec (cons :priority choices)))))})
 
        "page"
-       (let [pages (sort (db-model/get-all-page-titles repo))]
-         (select pages
-                 (fn [{:keys [value]}]
-                   (append-tree! *tree opts loc [:page value]))))
+       (page-search (fn [{:keys [value]}]
+                      (append-tree! *tree opts loc [:page value])))
 
        ;; TODO: replace with node reference
        "page reference"
-       (let [pages (sort (db-model/get-all-page-titles repo))]
-         (select pages
-                 (fn [{:keys [value]}]
-                   (append-tree! *tree opts loc [:page-ref value]))
-                 {}))
+
+       (page-search (fn [{:keys [value]}]
+                      (append-tree! *tree opts loc [:page-ref value])))
 
        "full text search"
        (search (fn [v] (append-tree! *tree opts loc v))
@@ -376,17 +372,12 @@
                               (append-tree! *tree opts loc (vec (cons :priority choices)))))})
 
        "page"
-       (let [pages (sort (db-model/get-all-page-titles repo))]
-         (select pages
-                 (fn [{:keys [value]}]
-                   (append-tree! *tree opts loc [:page value]))))
+       (page-search (fn [{:keys [value]}]
+                      (append-tree! *tree opts loc [:page value])))
 
        "page reference"
-       (let [pages (sort (db-model/get-all-page-titles repo))]
-         (select pages
-                 (fn [{:keys [value]}]
-                   (append-tree! *tree opts loc [:page-ref value]))
-                 {}))
+       (page-search (fn [{:keys [value]}]
+                      (append-tree! *tree opts loc [:page-ref value])))
 
        "full text search"
        (search (fn [v] (append-tree! *tree opts loc v))

+ 21 - 17
src/main/frontend/components/query/result.cljs

@@ -10,12 +10,14 @@
             [frontend.search :as search]
             [frontend.state :as state]
             [frontend.template :as template]
+            [frontend.util :as util]
             [logseq.common.util :as common-util]
+            [logseq.db :as ldb]
             [promesa.core :as p]
-            [logseq.db :as ldb]))
+            [rum.core :as rum]))
 
-(defn trigger-custom-query!
-  [config query *query-error set-result!]
+(defn run-custom-query
+  [config query *result *query-error]
   (let [repo (state/get-current-repo)
         current-block-uuid (or (:block/uuid (:block config))
                                (:block/uuid config))
@@ -27,29 +29,31 @@
               form (common-util/safe-read-string {:log-error? false} q)]
           (cond
             (and (symbol? form)
-                                ;; Queries only containing template should trigger a query
+                 ;; Queries only containing template should trigger a query
                  (not (re-matches template/template-re (string/trim q))))
             nil
 
             (re-matches #"^\".*\"$" q) ; full-text search
-            (p/let [blocks (search/block-search repo (string/trim form) {:limit 30})]
-              (when (seq blocks)
-                (let [result (->> blocks
-                                  (keep (fn [b]
-                                          (when-not (= (:block/uuid b) current-block-uuid)
-                                            (let [entity (db/entity [:block/uuid (:block/uuid b)])]
-                                              (when-not (ldb/hidden? entity)
-                                                entity))))))]
-                  (set-result! (atom result)))))
+            (do
+              (p/let [blocks (search/block-search repo (string/trim form) {:limit 30})]
+                (when (seq blocks)
+                  (let [result (->> blocks
+                                    (keep (fn [b]
+                                            (when-not (= (:block/uuid b) current-block-uuid)
+                                              (let [entity (or (db/entity [:block/uuid (:block/uuid b)]) b)]
+                                                (when-not (ldb/hidden? entity)
+                                                  entity))))))]
+                    (reset! *result result))))
+              (rum/react *result))
 
             :else
             (let [result (query-dsl/query (state/get-current-repo) q {:cards? (:cards? config)})]
-              (set-result! (or result (atom []))))))
+              (when (util/atom? result)
+                (rum/react result)))))
 
         :else
-        (set-result! (query-custom/custom-query query {:current-block-uuid current-block-uuid
-                                                       ;; FIXME: Remove this temporary workaround for reactivity not working
-                                                       :use-cache? false})))
+        (util/react (query-custom/custom-query query {:current-block-uuid current-block-uuid
+                                                      :built-in-query? (:built-in-query? config)})))
       (catch :default e
         (reset! *query-error e)))))
 

+ 17 - 39
src/main/frontend/components/query/view.cljs

@@ -2,13 +2,9 @@
   "DB query result view"
   (:require [frontend.components.views :as views]
             [frontend.db :as db]
-            [frontend.mixins :as mixins]
-            [frontend.modules.outliner.op :as outliner-op]
-            [frontend.modules.outliner.ui :as ui-outliner-tx]
             [frontend.state]
             [logseq.db :as ldb]
-            [logseq.db.frontend.entity-util :as entity-util]
-            [promesa.core :as p]
+            [logseq.shui.hooks :as hooks]
             [rum.core :as rum]))
 
 (defn- columns
@@ -21,8 +17,7 @@
 
 (defn- result->entities
   [result]
-  (map (fn [b]
-         (assoc (db/entity (:db/id b)) :id (:db/id b))) result))
+  (map (fn [b] (or (db/entity (:db/id b)) b)) result))
 
 (defn- init-result
   [result view-entity]
@@ -32,42 +27,25 @@
     (->> (result->entities result')
          (remove (fn [b] (contains?
                           #{(:db/id view-entity) (:db/id (:logseq.property/query view-entity))}
-                          (:db/id b)))))))
+                          (:db/id b))))
+         (remove :logseq.property/view-for))))
 
-(rum/defcs query-result < rum/static mixins/container-id
-  (rum/local nil ::result)
-  {:will-remount (fn [old-state new-state]
-                   (let [*result (::result new-state)
-                         [_config view-entity old-result] (:rum/args old-state)
-                         [_config _view-entity new-result] (:rum/args old-state)]
-                     (when-not (= old-result new-result)
-                       (reset! *result (init-result new-result view-entity))))
-                   new-state)}
-  [state config view-entity result]
-  (let [*result (::result state)
-        result' (or @*result (init-result result view-entity))
-        columns' (columns (assoc config :container-id (::container-id state)) result')
-        set-data! (fn [data] (reset! *result data))]
+(rum/defc query-result
+  [config view-entity result*]
+  (let [[data set-data!] (rum/use-state (init-result result* view-entity))
+        ids (mapv :db/id data)
+        columns' (columns config data)]
+    (hooks/use-effect!
+     (fn []
+       (set-data! (init-result result* view-entity)))
+     [result*])
     [:div.query-result.w-full
      (views/view
-      {:config {:custom-query? true}
+      {:config (assoc {:custom-query? true} :sidebar? (:sidebar? config))
        :title-key :views.table/live-query-title
        :view-entity view-entity
        :view-feature-type :query-result
-       :data result'
+       :data ids
        :set-data! set-data!
-       :columns columns'
-       :on-delete-rows (fn [table selected-rows]
-                         (let [pages (filter entity-util/page? selected-rows)
-                               blocks (remove entity-util/page? selected-rows)
-                               selected (set (map :id (remove :logseq.property/built-in? selected-rows)))
-                               data' (remove (fn [row] (contains? selected (:id row))) (:data table))]
-                           (p/do!
-                            (set-data! data')
-                            (ui-outliner-tx/transact!
-                             {:outliner-op :delete-blocks}
-                             (when (seq blocks)
-                               (outliner-op/delete-blocks! blocks nil))
-                             (doseq [page pages]
-                               (when-let [id (:block/uuid page)]
-                                 (outliner-op/delete-page! id)))))))})]))
+       :query-entity-ids ids
+       :columns columns'})]))

+ 86 - 246
src/main/frontend/components/reference.cljs

@@ -1,77 +1,59 @@
 (ns frontend.components.reference
-  (:require [frontend.components.block :as block]
+  (:require [frontend.common.missionary :as c.m]
+            [frontend.components.block :as block]
             [frontend.components.content :as content]
             [frontend.components.editor :as editor]
             [frontend.components.reference-filters :as filters]
             [frontend.components.views :as views]
             [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.utils :as db-utils]
-            [frontend.handler.block :as block-handler]
-            [frontend.handler.page :as page-handler]
-            [frontend.modules.outliner.tree :as tree]
-            [frontend.search :as search]
             [frontend.state :as state]
             [frontend.ui :as ui]
-            [frontend.util :as util]
-            [logseq.db :as ldb]
+            [logseq.db.common.view :as db-view]
+            [logseq.shui.hooks :as hooks]
             [logseq.shui.ui :as shui]
+            [missionary.core :as m]
             [promesa.core :as p]
             [rum.core :as rum]))
 
-(rum/defc block-linked-references < rum/reactive db-mixins/query
-  {:init (fn [state]
-           (when-let [e (db/entity [:block/uuid (first (:rum/args state))])]
-             (db-async/<get-block-refs (state/get-current-repo) (:db/id e)))
-           state)}
+;; TODO: merge both page and block linked refs
+(rum/defc block-linked-references-aux < rum/reactive db-mixins/query
+  [e]
+  (let [block-id (:block/uuid e)
+        ref-blocks (-> (db/get-referenced-blocks (:db/id e))
+                       db-utils/group-by-page)]
+    (when (> (count ref-blocks) 0)
+      (let [ref-hiccup (block/->hiccup ref-blocks
+                                       {:id (str block-id)
+                                        :ref? true
+                                        :breadcrumb-show? true
+                                        :group-by-page? true
+                                        :editor-box editor/box}
+                                       {})]
+        [:div.references-blocks
+         (content/content block-id
+                          {:hiccup ref-hiccup})]))))
+
+(rum/defc block-linked-references
   [block-id]
   (when-let [e (db/entity [:block/uuid block-id])]
-    (when-not (state/sub-async-query-loading (str (:db/id e) "-refs"))
-      (let [ref-blocks (-> (db/get-referenced-blocks (:db/id e))
-                           db-utils/group-by-page)]
-        (when (> (count ref-blocks) 0)
-          (let [ref-hiccup (block/->hiccup ref-blocks
-                                           {:id (str block-id)
-                                            :ref? true
-                                            :breadcrumb-show? true
-                                            :group-by-page? true
-                                            :editor-box editor/box}
-                                           {})]
-            [:div.references-blocks
-             (content/content block-id
-                              {:hiccup ref-hiccup})]))))))
-
-(rum/defc references-inner
-  [page-entity filters filtered-ref-blocks]
-  (let [*ref (rum/use-ref nil)]
-    [:div.references-blocks.faster.fade-in {:ref *ref}
-     (let [ref-hiccup (block/->hiccup filtered-ref-blocks
-                                      {:id (str (:block/uuid page-entity))
-                                       :ref? true
-                                       :breadcrumb-show? true
-                                       :group-by-page? true
-                                       :editor-box editor/box
-                                       :filters filters}
-                                      {})]
-       (content/content (str (:block/uuid page-entity)) {:hiccup ref-hiccup}))]))
-
-(defn- columns
-  [config result]
-  (->> (mapcat :block.temp/property-keys result)
-       distinct
-       (map db/entity)
-       (ldb/sort-by-order)
-       ((fn [cs] (views/build-columns config cs {:add-tags-column? false})))))
-
-(rum/defc references-cp
-  [page-entity *filters total filter-n filtered-ref-blocks *ref-pages]
-  (let [filters @*filters
-        *collapsed? (atom nil)
-        db-based? (config/db-based-graph?)
-        reference-filter (if db-based?
+    (let [[loading? set-loading!] (hooks/use-state true)]
+      (hooks/use-effect!
+       (fn []
+         (p/do!
+          (db-async/<get-block-refs (state/get-current-repo) (:db/id e))
+          (set-loading! false)))
+       [])
+      (when-not loading?
+        (block-linked-references-aux e)))))
+
+(rum/defc references-aux
+  [page-entity config]
+  (let [filters (db-view/get-filters (db/get-db) page-entity)
+        reference-filter (fn [{:keys [ref-pages-count]}]
                            (shui/button
                             {:title "Page filter"
                              :variant "ghost"
@@ -81,7 +63,7 @@
                                          (shui/popup-show! (.-target e)
                                                            (fn []
                                                              [:div.p-4
-                                                              (filters/filter-dialog page-entity *filters *ref-pages)])
+                                                              (filters/filter-dialog page-entity ref-pages-count)])
                                                            {:align "end"}))}
                             (ui/icon "filter-cog"
                                      {:class (cond
@@ -94,194 +76,52 @@
                                                (and (empty? (:included filters)) (seq (:excluded filters)))
                                                "text-error"
                                                :else
-                                               "text-warning")}))
-                           [:a.filter.fade-link
-                            {:title (t :linked-references/filter-heading)
-                             :on-mouse-over (fn [_e]
-                                              (when @*collapsed? ; collapsed
-                           ;; expand
-                                                (reset! @*collapsed? false)))
-                             :on-pointer-down (fn [e]
-                                                (util/stop-propagation e)
-                                                (shui/popup-show! (.-target e)
-                                                                  (fn []
-                                                                    [:div.p-4
-                                                                     (filters/filter-dialog page-entity *filters *ref-pages)])
-                                                                  {:align "end"}))}
-                            (ui/icon "filter" {:class (cond
-                                                        (and (empty? (:included filters)) (empty? (:excluded filters)))
-                                                        "opacity-60 hover:opacity-100"
-
-                                                        (and (seq (:included filters)) (empty? (:excluded filters)))
-                                                        "text-success"
-
-                                                        (and (empty? (:included filters)) (seq (:excluded filters)))
-                                                        "text-error"
-                                                        :else
-                                                        "text-warning")
-                                               :size  22})])]
-    (if db-based?
-      (let [blocks (->> (mapcat second filtered-ref-blocks)
-                        (map (fn [b] (assoc (db/entity (:db/id b)) :id (:db/id b)))))
-            columns' (columns {} blocks)]
-        (when (or (seq blocks)
-                  (seq (:included filters))
-                  (seq (:excluded filters)))
-          (views/view
-           {:view-parent page-entity
-            :view-feature-type :linked-references
-            :additional-actions [reference-filter]
-            :data blocks
-            :columns columns'})))
-      (let [threshold (state/get-linked-references-collapsed-threshold)
-            default-collapsed? (or (>= total threshold) (ldb/class? page-entity))]
-        (ui/foldable
-         [:div.flex.flex-row.flex-1.justify-between.items-center
-          [:div.font-medium.opacity-50
-           (t :linked-references/reference-count (when (or (seq (:included filters))
-                                                           (seq (:excluded filters))) filter-n) total)]
-          reference-filter]
-
-         (fn []
-           (references-inner page-entity filters filtered-ref-blocks))
-
-         {:default-collapsed? default-collapsed?
-          :title-trigger? true
-          :init-collapsed (fn [collapsed-atom]
-                            (reset! *collapsed? collapsed-atom))})))))
-
-(defn- get-filtered-children
-  [block parent->blocks]
-  (let [children (get parent->blocks (:db/id block))]
-    (set
-     (loop [blocks children
-            result (vec children)]
-       (if (empty? blocks)
-         result
-         (let [fb (first blocks)
-               children (get parent->blocks (:db/id fb))]
-           (recur
-            (concat children (rest blocks))
-            (conj result fb))))))))
-
-(rum/defc references-aux < rum/reactive db-mixins/query
-  {:should-update (fn [old-state new-state]
-                    ;; Re-render if only filters update
-                    (not= (last (:rum/args old-state))
-                          (last (:rum/args new-state))))}
-  [state repo page-entity *filters filters]
-  (let [*ref-pages (::ref-pages state)
-        page-id (:db/id page-entity)
-        ref-blocks (db/get-referenced-blocks page-id)
-        aliases (db/page-alias-set repo page-id)
-        aliases-exclude-self (set (remove #{page-id} aliases))
-        top-level-blocks (filter (fn [b] (some aliases (set (map :db/id (:block/refs b))))) ref-blocks)
-        top-level-blocks-ids (set (map :db/id top-level-blocks))
-        filtered-ref-blocks (->> (block-handler/filter-blocks ref-blocks filters)
-                                 (block-handler/get-filtered-ref-blocks-with-parents ref-blocks))
-        total (count top-level-blocks)
-        filtered-top-blocks (filter (fn [b] (top-level-blocks-ids (:db/id b))) filtered-ref-blocks)
-        filter-n (count filtered-top-blocks)
-        parent->blocks (group-by (fn [x] (:db/id (x :block/parent))) filtered-ref-blocks)
-        result (->> (group-by :block/page filtered-top-blocks)
-                    (map (fn [[page blocks]]
-                           (let [blocks (sort-by (fn [b] (not= (:db/id page) (:db/id (:block/parent b)))) blocks)
-                                 result (map (fn [block]
-                                               (let [filtered-children (get-filtered-children block parent->blocks)
-                                                     refs (when-not (contains? top-level-blocks-ids (:db/id (:block/parent block)))
-                                                            (block-handler/get-blocks-refed-pages aliases (cons block filtered-children)))
-                                                     block' (assoc (tree/block-entity->map block) :block/children filtered-children)]
-                                                 [block' refs])) blocks)
-                                 blocks' (map first result)
-                                 page' (if (contains? aliases-exclude-self (:db/id page))
-                                         {:db/id (:db/id page)
-                                          :block/alias? true
-                                          :block/journal-day (:block/journal-day page)}
-                                         page)]
-                             [[page' blocks'] (mapcat second result)]))))
-        filtered-ref-blocks' (map first result)
-        ref-pages (->>
-                   (mapcat second result)
-                   (map :block/title)
-                   frequencies)]
-    (reset! *ref-pages ref-pages)
-    (when (or (seq (:included filters)) (seq (:excluded filters)) (> filter-n 0))
-      [:div.references.page-linked.flex-1.flex-row
-       [:div.content.pt-2
-        (references-cp page-entity *filters total filter-n filtered-ref-blocks' *ref-pages)]])))
-
-(rum/defcs references* < rum/reactive db-mixins/query
-  (rum/local nil ::ref-pages)
-  {:init (fn [state]
-           (let [page (first (:rum/args state))]
-             (when page (db-async/<get-block-refs (state/get-current-repo) (:db/id page))))
-           (assoc state ::filters (atom nil)))}
-  [state block-entity]
-  (when block-entity
-    (let [repo (state/get-current-repo)
-          *filters (::filters state)]
-      (when block-entity
-        (when-not (state/sub-async-query-loading (str (:db/id block-entity) "-refs"))
-          (let [block-entity (db/sub-block (:db/id block-entity))
-                filters (page-handler/get-filters block-entity)
-                _ (when-not (= filters @*filters)
-                    (reset! *filters filters))]
-            (references-aux state repo block-entity *filters filters)))))))
+                                               "text-warning")})))]
+    (views/view
+     {:view-parent page-entity
+      :view-feature-type :linked-references
+      :additional-actions [reference-filter]
+      :columns (views/build-columns config [] {})
+      :config config})))
+
+(rum/defc references-cp < rum/reactive db-mixins/query
+  [page-entity config]
+  (let [page (db/sub-block (:db/id page-entity))]
+    (references-aux page config)))
 
 (rum/defc references
-  [entity]
-  (ui/catch-error
-   (ui/component-error (if (config/db-based-graph? (state/get-current-repo))
-                         "Linked References: Unexpected error."
-                         "Linked References: Unexpected error. Please re-index your graph first."))
-   (references* entity)))
-
-(rum/defcs unlinked-references-aux
-  < rum/reactive db-mixins/query
-  {:init
-   (fn [state]
-     (let [*result (atom nil)
-           [page *n-ref] (:rum/args state)]
-       (p/let [result (search/get-unlinked-refs (:db/id page))
-               result' (remove nil? result)]
-         (reset! *n-ref (count result'))
-         (reset! *result result'))
-       (assoc state ::result *result)))}
-  [state page _n-ref]
-  (let [ref-blocks (rum/react (::result state))]
-    (when (seq ref-blocks)
-      (if (config/db-based-graph?)
-        (let [blocks (->> (mapcat val ref-blocks)
-                          (map (fn [b] (assoc (db/entity (:db/id b)) :id (:db/id b)))))
-              columns' (columns {} blocks)]
-          (views/view
-           {:view-parent page
-            :view-feature-type :unlinked-references
-            :data blocks
-            :columns columns'
-            :foldable-options {:default-collapsed? true}}))
-        [:div.references-blocks
-         (let [ref-hiccup (block/->hiccup ref-blocks
-                                          {:id (str (:block/title page) "-unlinked-")
-                                           :ref? true
-                                           :group-by-page? true
-                                           :editor-box editor/box}
-                                          {})]
-           (content/content (:block/name page)
-                            {:hiccup ref-hiccup}))]))))
-
-(rum/defcs unlinked-references < rum/reactive
-  (rum/local nil ::n-ref)
-  [state page]
-  (let [n-ref (get state ::n-ref)]
-    (when page
-      [:div.references.page-unlinked.mt-6.flex-1.flex-row.faster.fade-in
-       [:div.content.flex-1
-        (if (config/db-based-graph?)
-          (unlinked-references-aux page n-ref)
-          (ui/foldable
-           [:div.font-medium.opacity-50
-            (t :unlinked-references/reference-count @n-ref)]
-           (fn [] (unlinked-references-aux page n-ref))
-           {:default-collapsed? true
-            :title-trigger? true}))]])))
+  [entity config]
+  (when-let [id (:db/id entity)]
+    (let [[has-references? set-has-references!] (hooks/use-state nil)]
+      (hooks/use-effect!
+       #(c.m/run-task*
+         (m/sp
+           (let [result (c.m/<? (state/<invoke-db-worker :thread-api/block-refs-check
+                                                         (state/get-current-repo) id {}))]
+             (set-has-references! result))))
+       [])
+      (when has-references?
+        (ui/catch-error
+         (ui/component-error (if (config/db-based-graph? (state/get-current-repo))
+                               "Linked References: Unexpected error."
+                               "Linked References: Unexpected error. Please re-index your graph first."))
+         (references-cp entity config))))))
+
+(rum/defc unlinked-references
+  [entity config]
+  (when-let [id (:db/id entity)]
+    (let [[has-references? set-has-references!] (hooks/use-state nil)]
+      (hooks/use-effect!
+       #(c.m/run-task*
+         (m/sp
+           (let [result (c.m/<? (state/<invoke-db-worker :thread-api/block-refs-check
+                                                         (state/get-current-repo) id {:unlinked? true}))]
+             (set-has-references! result))))
+       [])
+      (when has-references?
+        (views/view
+         {:view-parent entity
+          :view-feature-type :unlinked-references
+          :columns (views/build-columns config [] {})
+          :foldable-options {:default-collapsed? true}
+          :config config})))))

+ 10 - 9
src/main/frontend/components/reference_filters.cljs

@@ -1,17 +1,18 @@
 (ns frontend.components.reference-filters
   "References filters"
   (:require [clojure.string :as string]
+            [datascript.impl.entity :as de]
+            [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
+            [frontend.db :as db]
             [frontend.handler.page :as page-handler]
             [frontend.search :as search]
+            [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
+            [logseq.db.common.view :as db-view]
             [promesa.core :as p]
-            [rum.core :as rum]
-            [frontend.config :as config]
-            [frontend.state :as state]
-            [frontend.db :as db]
-            [datascript.impl.entity :as de]))
+            [rum.core :as rum]))
 
 (defn- frequencies-sort
   [references]
@@ -51,11 +52,11 @@
                :variant :outline
                :key ref-name)))))])
 
-(rum/defcs filter-dialog < rum/reactive (rum/local "" ::filterSearch)
-  [state page-entity *filters *references]
-  (let [filters (rum/react *filters)
+(rum/defcs filter-dialog < (rum/local "" ::filterSearch) rum/reactive
+  [state page references]
+  (let [page-entity (db/sub-block (:db/id page))
         filter-search (get state ::filterSearch)
-        references (rum/react *references)
+        filters (db-view/get-filters (db/get-db) page-entity)
         filtered-references  (frequencies-sort
                               (if (= @filter-search "")
                                 references

+ 174 - 150
src/main/frontend/components/right_sidebar.cljs

@@ -11,6 +11,7 @@
             [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
             [frontend.db :as db]
+            [frontend.db.async :as db-async]
             [frontend.db.rtc.debug-ui :as rtc-debug-ui]
             [frontend.extensions.slide :as slide]
             [frontend.handler.editor :as editor-handler]
@@ -23,6 +24,7 @@
             [logseq.shui.hooks :as hooks]
             [logseq.shui.ui :as shui]
             [medley.core :as medley]
+            [promesa.core :as p]
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]))
 
@@ -68,78 +70,84 @@
                          :sidebar-key sidebar-key} repo block-id {:indent? false})]
      (block-cp repo idx block)]))
 
-(defn build-sidebar-item
+(defn- <build-sidebar-item
   [repo idx db-id block-type *db-id init-key]
-  (case (keyword block-type)
-    :contents
-    [[:.flex.items-center (ui/icon "list-details" {:class "text-md mr-2"}) (t :right-side-bar/contents)]
-     (page-cp repo "contents")]
-
-    :help
-    [[:.flex.items-center (ui/icon "help" {:class "text-md mr-2"}) (t :right-side-bar/help)] (onboarding/help)]
-
-    :page-graph
-    [[:.flex.items-center (ui/icon "hierarchy" {:class "text-md mr-2"}) (t :right-side-bar/page-graph)]
-     (page/page-graph)]
-
-    :block-ref
-    #_:clj-kondo/ignore
-    (let [lookup (if (integer? db-id) db-id [:block/uuid db-id])]
-      (when-let [block (db/entity repo lookup)]
-        [(t :right-side-bar/block-ref)
-         (block-with-breadcrumb repo block idx [repo db-id block-type] true)]))
-
-    :block
-    #_:clj-kondo/ignore
-    (let [lookup (if (integer? db-id) db-id [:block/uuid db-id])]
-      (when-let [block (db/entity repo lookup)]
-        (block-with-breadcrumb repo block idx [repo db-id block-type] false)))
-
-    :page
-    (let [lookup (if (integer? db-id) db-id [:block/uuid db-id])
-          page (db/entity repo lookup)]
-      (if (ldb/page? page)
-        [[:.flex.items-center.page-title.gap-1
-          (icon/get-node-icon-cp page {:class "text-md"})
-          [:span.overflow-hidden.text-ellipsis (:block/title page)]]
-         (page-cp repo (str (:block/uuid page)))]
-        (block-with-breadcrumb repo page idx [repo db-id block-type] false)))
-
-    :search
-    [[:.flex.items-center.page-title
-      (ui/icon "search" {:class "text-md mr-2"})
-      (let [input (rum/react *db-id)
-            input' (if (string/blank? input) "Blank input" input)]
-        [:span.overflow-hidden.text-ellipsis input'])]
-     (rum/with-key
-       (cmdk/cmdk-block {:initial-input db-id
-                         :sidebar? true
-                         :on-input-change (fn [new-value]
-                                            (reset! *db-id new-value))
-                         :on-input-blur (fn [new-value]
-                                          (state/sidebar-replace-block! [repo db-id block-type]
-                                                                        [repo new-value block-type]))})
-       (str init-key))]
-
-    :page-slide-view
-    (let [page (db/entity db-id)]
-      [[:a.page-title {:href (rfe/href :page {:name (str (:block/uuid page))})}
-        (:block/title page)]
-       [:div.ml-2.slide.mt-2
-        (slide/slide page)]])
-
-    :shortcut-settings
-    [[:.flex.items-center (ui/icon "command" {:class "text-md mr-2"}) (t :help/shortcuts)]
-     (shortcut-settings)]
-    :rtc
-    [[:.flex.items-center (ui/icon "cloud" {:class "text-md mr-2"}) "(Dev) RTC"]
-     (rtc-debug-ui/rtc-debug-ui)]
-
-    :profiler
-    [[:.flex.items-center (ui/icon "cloud" {:class "text-md mr-2"}) "(Dev) Profiler"]
-     (profiler/profiler)]
-
-    ["" [:span]]))
+  (p/do!
+   (db-async/<get-block repo db-id)
+   (let [lookup (cond
+                  (integer? db-id) db-id
+                  (uuid? db-id) [:block/uuid db-id]
+                  :else nil)
+         entity (when lookup (db/entity repo lookup))
+         page? (ldb/page? entity)
+         block-render (fn []
+                        (when entity
+                          (if page?
+                            [[:.flex.items-center.page-title.gap-1
+                              (icon/get-node-icon-cp entity {:class "text-md"})
+                              [:span.overflow-hidden.text-ellipsis (:block/title entity)]]
+                             (page-cp repo (str (:block/uuid entity)))]
+                            (block-with-breadcrumb repo entity idx [repo db-id block-type] false))))]
+     (case (keyword block-type)
+       :contents
+       (when-let [page (db/get-page "Contents")]
+         [[:.flex.items-center (ui/icon "list-details" {:class "text-md mr-2"}) (t :right-side-bar/contents)]
+          (page-cp repo (str (:block/uuid page)))])
+
+       :help
+       [[:.flex.items-center (ui/icon "help" {:class "text-md mr-2"}) (t :right-side-bar/help)] (onboarding/help)]
+
+       :page-graph
+       [[:.flex.items-center (ui/icon "hierarchy" {:class "text-md mr-2"}) (t :right-side-bar/page-graph)]
+        (page/page-graph)]
+
+       :block-ref
+       (let [lookup (if (integer? db-id) db-id [:block/uuid db-id])]
+         (when-let [block (db/entity repo lookup)]
+           [(t :right-side-bar/block-ref)
+            (block-with-breadcrumb repo block idx [repo db-id block-type] true)]))
+
+       :block
+       (block-render)
+
+       :page
+       (block-render)
+
+       :search
+       [[:.flex.items-center.page-title
+         (ui/icon "search" {:class "text-md mr-2"})
+         (let [input (rum/react *db-id)
+               input' (if (string/blank? input) "Blank input" input)]
+           [:span.overflow-hidden.text-ellipsis input'])]
+        (rum/with-key
+          (cmdk/cmdk-block {:initial-input db-id
+                            :sidebar? true
+                            :on-input-change (fn [new-value]
+                                               (reset! *db-id new-value))
+                            :on-input-blur (fn [new-value]
+                                             (state/sidebar-replace-block! [repo db-id block-type]
+                                                                           [repo new-value block-type]))})
+          (str init-key))]
+
+       :page-slide-view
+       (when entity
+         [[:a.page-title {:href (rfe/href :page {:name (str (:block/uuid entity))})}
+           (:block/title entity)]
+          [:div.ml-2.slide.mt-2
+           (slide/slide entity)]])
+
+       :shortcut-settings
+       [[:.flex.items-center (ui/icon "command" {:class "text-md mr-2"}) (t :help/shortcuts)]
+        (shortcut-settings)]
+       :rtc
+       [[:.flex.items-center (ui/icon "cloud" {:class "text-md mr-2"}) "(Dev) RTC"]
+        (rtc-debug-ui/rtc-debug-ui)]
+
+       :profiler
+       [[:.flex.items-center (ui/icon "cloud" {:class "text-md mr-2"}) "(Dev) Profiler"]
+        (profiler/profiler)]
+
+       ["" [:span]]))))
 
 (defonce *drag-to
   (atom nil))
@@ -150,7 +158,9 @@
 (rum/defc actions-menu-content
   [db-id idx type collapsed? block-count]
   (let [multi-items? (> block-count 1)
-        menu-item shui/dropdown-menu-item]
+        menu-item shui/dropdown-menu-item
+        block (when (integer? db-id) (db/entity db-id))
+        page? (or (contains? #{:page :contents} type) (ldb/page? block))]
     [:<>
      (menu-item {:on-click #(state/sidebar-remove-block! idx)} (t :right-side-bar/pane-close))
      (when multi-items? (menu-item {:on-click #(state/sidebar-remove-rest! db-id)} (t :right-side-bar/pane-close-others)))
@@ -164,11 +174,10 @@
      (when (and collapsed? multi-items?) [:hr.menu-separator])
      (when collapsed? (menu-item {:on-click #(state/sidebar-block-toggle-collapse! db-id)} (t :right-side-bar/pane-expand)))
      (when multi-items? (menu-item {:on-click #(state/sidebar-block-set-collapsed-all! false)} (t :right-side-bar/pane-expand-all)))
-     (when (= type :page) [:hr.menu-separator])
-     (when (= type :page)
-       (let [page  (db/entity db-id)]
-         (menu-item {:on-click (fn [] (route-handler/redirect-to-page! (:block/uuid page)))}
-                    (t :right-side-bar/pane-open-as-page))))]))
+     (when page? [:hr.menu-separator])
+     (when page?
+       (menu-item {:on-click (fn [] (route-handler/redirect-to-page! (:block/uuid block)))}
+                  (t :right-side-bar/pane-open-as-page)))]))
 
 (rum/defc drop-indicator
   [idx drag-to]
@@ -190,85 +199,100 @@
   [component _should-update?]
   component)
 
+(rum/defc sidebar-item-inner
+  [db-id {:keys [repo idx block-type collapsed? drag-from drag-to block-count *db-id init-key]}]
+  (let [[item set-item!] (hooks/use-state nil)]
+    (hooks/use-effect!
+     (fn []
+       (p/let [item (<build-sidebar-item repo idx db-id block-type *db-id init-key)]
+         (set-item! item)))
+     [])
+    (when item
+      [:<>
+       (when (zero? idx) (drop-indicator (dec idx) drag-to))
+       [:div.flex.sidebar-item.content.color-level.rounded-md.shadow-lg
+        {:class [(str "item-type-" (name block-type))
+                 (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
+            {:class         (when collapsed? "rounded-b-md")
+             :draggable     true
+             :on-context-menu (fn [e]
+                                (util/stop e)
+                                (shui/popup-show! e
+                                                  (actions-menu-content db-id idx block-type collapsed? block-count)
+                                                  {:as-dropdown? true
+                                                   :content-props {:on-click (fn [] (shui/popup-hide!))}}))
+             :on-drag-start (fn [event]
+                              (editor-handler/block->data-transfer! (:block/name (db/entity db-id)) event true)
+                              (reset! *drag-from idx))
+             :on-drag-end   (fn [_event]
+                              (when drag-to (state/sidebar-move-block! idx drag-to))
+                              (reset! *drag-to nil)
+                              (reset! *drag-from nil))
+             :on-pointer-up   (fn [event]
+                                (when (= (.-which (.-nativeEvent event)) 2)
+                                  (state/sidebar-remove-block! idx)))}
+
+            [:button.flex.flex-row.p-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)
+              :on-click      (fn [event]
+                               (util/stop event)
+                               (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
+              title]]
+            [:.item-actions.flex.items-center
+             (shui/button
+              {:title (t :right-side-bar/pane-more)
+               :class "px-3"
+               :variant :text
+               :on-click #(shui/popup-show!
+                           (.-target %)
+                           (actions-menu-content db-id idx block-type collapsed? block-count)
+                           {:as-dropdown? true
+                            :content-props {:on-click (fn [] (shui/popup-hide!))}})}
+              (ui/icon "dots"))
+
+             (shui/button
+              {:title (t :right-side-bar/pane-close)
+               :variant :text
+               :class "px-3"
+               :on-click #(state/sidebar-remove-block! idx)}
+              (ui/icon "x"))]]
+
+           [:div {:role "region"
+                  :id (str "sidebar-panel-content-" idx)
+                  :aria-labelledby (str "sidebar-panel-header-" idx)
+                  :class           (util/classnames [{:hidden  collapsed?
+                                                      :initial (not collapsed?)
+                                                      :sidebar-panel-content true
+                                                      :px-2    (not (contains? #{:search :shortcut-settings} block-type))}])}
+            (inner-component component (not drag-from))]
+           (when drag-from (drop-area idx))])]
+       (drop-indicator idx drag-to)])))
+
 (rum/defcs sidebar-item < rum/reactive
   {:init (fn [state] (assoc state
                             ::db-id (atom (nth (:rum/args state) 2))
                             ::init-key (random-uuid)))}
   [state repo idx db-id block-type block-count]
   (let [drag-from (rum/react *drag-from)
-        drag-to (rum/react *drag-to)
-        item (build-sidebar-item repo idx db-id block-type
-                                 (::db-id state)
-                                 (::init-key state))]
-    (when item
-      (let [collapsed? (state/sub [:ui/sidebar-collapsed-blocks db-id])]
-        [:<>
-         (when (zero? idx) (drop-indicator (dec idx) drag-to))
-         [:div.flex.sidebar-item.content.color-level.rounded-md.shadow-lg
-          {:class [(str "item-type-" (name block-type))
-                   (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
-              {:class         (when collapsed? "rounded-b-md")
-               :draggable     true
-               :on-context-menu (fn [e]
-                                  (util/stop e)
-                                  (shui/popup-show! e
-                                                    (actions-menu-content db-id idx block-type collapsed? block-count)
-                                                    {:as-dropdown? true
-                                                     :content-props {:on-click (fn [] (shui/popup-hide!))}}))
-               :on-drag-start (fn [event]
-                                (editor-handler/block->data-transfer! (:block/name (db/entity db-id)) event true)
-                                (reset! *drag-from idx))
-               :on-drag-end   (fn [_event]
-                                (when drag-to (state/sidebar-move-block! idx drag-to))
-                                (reset! *drag-to nil)
-                                (reset! *drag-from nil))
-               :on-pointer-up   (fn [event]
-                                  (when (= (.-which (.-nativeEvent event)) 2)
-                                    (state/sidebar-remove-block! idx)))}
-
-              [:button.flex.flex-row.p-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)
-                :on-click      (fn [event]
-                                 (util/stop event)
-                                 (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
-                title]]
-              [:.item-actions.flex.items-center
-               (shui/button
-                {:title (t :right-side-bar/pane-more)
-                 :class "px-3"
-                 :variant :text
-                 :on-click #(shui/popup-show!
-                             (.-target %)
-                             (actions-menu-content db-id idx block-type collapsed? block-count)
-                             {:as-dropdown? true
-                              :content-props {:on-click (fn [] (shui/popup-hide!))}})}
-                (ui/icon "dots"))
-
-               (shui/button
-                {:title (t :right-side-bar/pane-close)
-                 :variant :text
-                 :class "px-3"
-                 :on-click #(state/sidebar-remove-block! idx)}
-                (ui/icon "x"))]]
-
-             [:div {:role "region"
-                    :id (str "sidebar-panel-content-" idx)
-                    :aria-labelledby (str "sidebar-panel-header-" idx)
-                    :class           (util/classnames [{:hidden  collapsed?
-                                                        :initial (not collapsed?)
-                                                        :sidebar-panel-content true
-                                                        :px-2    (not (contains? #{:search :shortcut-settings} block-type))}])}
-              (inner-component component (not drag-from))]
-             (when drag-from (drop-area idx))])]
-         (drop-indicator idx drag-to)]))))
+        drag-to (rum/react *drag-to)]
+    (let [collapsed? (state/sub [:ui/sidebar-collapsed-blocks db-id])]
+      (sidebar-item-inner db-id {:repo repo
+                                 :idx idx
+                                 :block-type block-type
+                                 :collapsed? collapsed?
+                                 :drag-from drag-from
+                                 :drag-to drag-to
+                                 :block-count block-count
+                                 :*db-id (::db-id state)
+                                 :init-key (::init-key state)}))))
 
 (defn- get-page
   [match]

+ 15 - 16
src/main/frontend/components/rtc/indicator.cljs

@@ -2,12 +2,12 @@
   "RTC state indicator"
   (:require [cljs-time.core :as t]
             [clojure.pprint :as pprint]
+            [frontend.common.missionary :as c.m]
             [frontend.db :as db]
             [frontend.handler.db-based.rtc-flows :as rtc-flows]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
-            [frontend.common.missionary :as c.m]
             [logseq.shui.ui :as shui]
             [missionary.core :as m]
             [rum.core :as rum]))
@@ -38,21 +38,20 @@
                (when log
                  (swap! *detail-info update k (fn [logs] (take 5 (conj logs log))))))
              flow))]
-    (let [canceler (c.m/run-task
-                    (m/join
-                     (constantly nil)
-                     (update-log-task rtc-flows/rtc-download-log-flow :download-logs)
-                     (update-log-task rtc-flows/rtc-upload-log-flow :upload-logs)
-                     (update-log-task rtc-flows/rtc-misc-log-flow :misc-logs)
-                     (m/reduce (fn [_ state]
-                                 (swap! *detail-info assoc
-                                        :pending-local-ops (:unpushed-block-update-count state)
-                                        :graph-uuid (:graph-uuid state)
-                                        :local-tx (:local-tx state)
-                                        :remote-tx (:remote-tx state)
-                                        :rtc-state (if (:rtc-lock state) :open :close)))
-                               rtc-flows/rtc-state-stream-flow))
-                    ::update-detail-info)]
+    (let [canceler (c.m/run-task ::update-detail-info
+                     (m/join
+                      (constantly nil)
+                      (update-log-task rtc-flows/rtc-download-log-flow :download-logs)
+                      (update-log-task rtc-flows/rtc-upload-log-flow :upload-logs)
+                      (update-log-task rtc-flows/rtc-misc-log-flow :misc-logs)
+                      (m/reduce (fn [_ state]
+                                  (swap! *detail-info assoc
+                                         :pending-local-ops (:unpushed-block-update-count state)
+                                         :graph-uuid (:graph-uuid state)
+                                         :local-tx (:local-tx state)
+                                         :remote-tx (:remote-tx state)
+                                         :rtc-state (if (:rtc-lock state) :open :close)))
+                                rtc-flows/rtc-state-stream-flow)))]
       (reset! *update-detail-info-canceler canceler))))
 (run-task--update-detail-info)
 

+ 69 - 50
src/main/frontend/components/select.cljs

@@ -14,6 +14,7 @@
             [frontend.ui :as ui]
             [frontend.util :as util]
             [frontend.util.text :as text-util]
+            [logseq.shui.hooks :as hooks]
             [logseq.shui.ui :as shui]
             [reitit.frontend.easy :as rfe]
             [rum.core :as rum]))
@@ -43,6 +44,27 @@
        row]
       row)))
 
+(rum/defc search-input
+  [*input {:keys [prompt-key input-default-placeholder input-opts on-input]}]
+  (let [[input set-input!] (hooks/use-state @*input)]
+    (hooks/use-effect!
+     (fn []
+       (reset! *input input)
+       (when (fn? on-input) (on-input input)))
+     [(hooks/use-debounced-value input 100)])
+    [:div.input-wrap
+     {:style {:margin-bottom "-2px"}}
+     [:input.cp__select-input.w-full
+      (merge {:type        "text"
+              :class "!p-1.5"
+              :placeholder (or input-default-placeholder (t prompt-key))
+              :auto-focus  true
+              :value       input
+              :on-change   (fn [e]
+                             (let [v (util/evalue e)]
+                               (set-input! v)))}
+             input-opts)]]))
+
 (rum/defcs ^:large-vars/cleanup-todo select
   "Provides a select dropdown powered by a fuzzy search. Takes the following options:
    * :items - Vec of things to select from. Assumes a vec of maps with :value key by default. Required option
@@ -54,6 +76,7 @@
    * :exact-match-exclude-items - A set of strings that can't be added as a new item. Default is #{}
    * :transform-fn - Optional fn to transform search results given results and current input
    * :new-case-sensitive? - Boolean to allow new values to be case sensitive
+   * :loading? - whether it's loading the items
    TODO: Describe more options"
   < rum/reactive
   shortcut/disable-all-shortcuts
@@ -76,7 +99,7 @@
                  item-cp transform-fn tap-*input-val
                  multiple-choices? on-apply new-case-sensitive?
                  dropdown? show-new-when-not-exact-match? exact-match-exclude-items
-                 input-container initial-open?]
+                 input-container initial-open? loading?]
           :or {limit 100
                prompt-key :select/default-prompt
                empty-placeholder (fn [_t] [:div])
@@ -123,54 +146,50 @@
         input-opts' (if (fn? input-opts) (input-opts (empty? search-result)) input-opts)
         input-container (or
                          input-container
-                         [:div.input-wrap
-                          {:style {:margin-bottom "-2px"}}
-                          [:input.cp__select-input.w-full
-                           (merge {:type        "text"
-                                   :class "!p-1.5"
-                                   :placeholder (or input-default-placeholder (t prompt-key))
-                                   :auto-focus  true
-                                   :value       @input
-                                   :on-change   (fn [e]
-                                                  (let [v (util/evalue e)]
-                                                    (reset! input v)
-                                                    (and (fn? on-input) (on-input v))))}
-                                  input-opts')]])
-        results-container [:div
-                           {:class (when (seq search-result) "py-1")}
-                           [:div.item-results-wrap
-                            (ui/auto-complete
-                             search-result
-                             {:grouped? grouped?
-                              :item-render       (or item-cp (fn [result chosen?]
-                                                               (render-item result chosen? multiple-choices? *selected-choices)))
-                              :class             "cp__select-results"
-                              :on-chosen         (fn [raw-chosen e]
-                                                   (reset! input "")
-                                                   (let [chosen (extract-chosen-fn raw-chosen)]
-                                                     (if multiple-choices?
-                                                       (if (selected-choices chosen)
-                                                         (do
-                                                           (swap! *selected-choices disj chosen)
-                                                           (when on-chosen (on-chosen chosen false @*selected-choices e)))
-                                                         (do
-                                                           (swap! *selected-choices conj chosen)
-                                                           (when on-chosen (on-chosen chosen true @*selected-choices e))))
-                                                       (do
-                                                         (when (and close-modal? (not multiple-choices?))
-                                                           (state/close-modal!))
-                                                         (when on-chosen
-                                                           (on-chosen chosen true @*selected-choices e))))))
-                              :empty-placeholder (empty-placeholder t)})]
+                         (search-input input
+                                       {:prompt-key prompt-key
+                                        :input-default-placeholder input-default-placeholder
+                                        :input-opts input-opts'
+                                        :on-input on-input}))
+        results-container-f (fn []
+                              (if loading?
+                                [:div.px-1.py-2
+                                 (ui/loading "Loading ...")]
+                                [:div
+                                 {:class (when (seq search-result) "py-1")}
+                                 [:div.item-results-wrap
+                                  (ui/auto-complete
+                                   search-result
+                                   {:grouped? grouped?
+                                    :item-render       (or item-cp (fn [result chosen?]
+                                                                     (render-item result chosen? multiple-choices? *selected-choices)))
+                                    :class             "cp__select-results"
+                                    :on-chosen         (fn [raw-chosen e]
+                                                         (reset! input "")
+                                                         (let [chosen (extract-chosen-fn raw-chosen)]
+                                                           (if multiple-choices?
+                                                             (if (selected-choices chosen)
+                                                               (do
+                                                                 (swap! *selected-choices disj chosen)
+                                                                 (when on-chosen (on-chosen chosen false @*selected-choices e)))
+                                                               (do
+                                                                 (swap! *selected-choices conj chosen)
+                                                                 (when on-chosen (on-chosen chosen true @*selected-choices e))))
+                                                             (do
+                                                               (when (and close-modal? (not multiple-choices?))
+                                                                 (state/close-modal!))
+                                                               (when on-chosen
+                                                                 (on-chosen chosen true @*selected-choices e))))))
+                                    :empty-placeholder (empty-placeholder t)})]
 
-                           (when (and multiple-choices? (fn? on-apply))
-                             [:div.p-4 (ui/button "Apply"
-                                                  {:small? true
-                                                   :on-pointer-down (fn [e]
-                                                                      (util/stop e)
-                                                                      (when @*toggle (@*toggle))
-                                                                      (on-apply selected-choices)
-                                                                      (when close-modal? (state/close-modal!)))})])]]
+                                 (when (and multiple-choices? (fn? on-apply))
+                                   [:div.p-4 (ui/button "Apply"
+                                                        {:small? true
+                                                         :on-pointer-down (fn [e]
+                                                                            (util/stop e)
+                                                                            (when @*toggle (@*toggle))
+                                                                            (on-apply selected-choices)
+                                                                            (when close-modal? (state/close-modal!)))})])]))]
     (when (fn? tap-*input-val)
       (tap-*input-val input))
     [:div.cp__select
@@ -180,12 +199,12 @@
      (if dropdown?
        (ui/dropdown
         (if (fn? input-container) input-container (fn [] input-container))
-        (fn [] results-container)
+        results-container-f
         {:initial-open? initial-open?
          :*toggle-fn *toggle})
        [:<>
         (if (fn? input-container) (input-container) input-container)
-        results-container])]))
+        (results-container-f)])]))
 
 (defn select-config
   "Config that supports multiple types (uses) of this component. To add a new

+ 5 - 6
src/main/frontend/components/selection.cljs

@@ -1,6 +1,7 @@
 (ns frontend.components.selection
   "Block selection"
   (:require [frontend.config :as config]
+            [frontend.db :as db]
             [frontend.handler.editor :as editor-handler]
             [frontend.state :as state]
             [frontend.ui :as ui]
@@ -11,9 +12,9 @@
 (rum/defc action-bar
   [& {:keys [on-cut on-copy selected-blocks hide-dots? button-border?]
       :or {on-cut #(editor-handler/cut-selection-blocks true)}}]
-  (let [on-copy (if (and selected-blocks (nil? on-copy))
-                  #(editor-handler/copy-selection-blocks true {:selected-blocks selected-blocks
-                                                               :page-title-only? true})
+  (let [selected-blocks (map (fn [block] (if (number? block) (db/entity block) block)) selected-blocks)
+        on-copy (if (and selected-blocks (nil? on-copy))
+                  #(editor-handler/copy-selection-blocks true {:selected-blocks selected-blocks})
                   (or on-copy #(editor-handler/copy-selection-blocks true)))
         button-opts {:variant :outline
                      :size :sm
@@ -80,8 +81,6 @@
                                                        [:div {:on-click #(shui/popup-hide! id)
                                                               :data-keep-selection true}
                                                         ((state/get-component :selection/context-menu))])
-                                                     {:on-before-hide state/dom-clear-selection!
-                                                      :on-after-hide state/state-clear-selection!
-                                                      :content-props {:class "w-[280px] ls-context-menu-content"}
+                                                     {:content-props {:class "w-[280px] ls-context-menu-content"}
                                                       :as-dropdown? true})))
          (ui/icon "dots" {:size 13}))))]))

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

@@ -30,7 +30,18 @@
   }
 
   .ls-table-row {
-    @apply min-h-[33px];
+    @apply h-[33px] min-h-[33px] max-h-[33px];
+    div, span, a {
+      @apply whitespace-nowrap;
+    }
+
+    .table-block-title, .block-head-wrap a {
+      @apply text-ellipsis overflow-hidden;
+    }
+
+    .multi-values {
+      @apply !flex-nowrap;
+    }
   }
 
   .ls-table-header {

Dosya farkı çok büyük olduğundan ihmal edildi
+ 468 - 448
src/main/frontend/components/views.cljs


+ 8 - 0
src/main/frontend/components/views.css

@@ -3,3 +3,11 @@
         width: 107px;
     }
 }
+
+.group-list-view div[data-testid='virtuoso-item-list'] {
+    @apply flex flex-col gap-2;
+}
+
+.group-list-view div[data-testid='virtuoso-item-list'] div[data-testid='virtuoso-item-list'] {
+    @apply gap-0;
+}

+ 21 - 19
src/main/frontend/components/whiteboard.cljs

@@ -4,8 +4,11 @@
             [frontend.components.onboarding.quick-tour :as quick-tour]
             [frontend.components.page :as page]
             [frontend.components.reference :as reference]
+            [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.handler.route :as route-handler]
             [frontend.handler.whiteboard :as whiteboard-handler]
@@ -14,14 +17,12 @@
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
+            [logseq.common.util :as common-util]
+            [logseq.shui.hooks :as hooks]
+            [logseq.shui.ui :as shui]
             [promesa.core :as p]
             [rum.core :as rum]
-            [shadow.loader :as loader]
-            [frontend.config :as config]
-            [frontend.db.async :as db-async]
-            [logseq.common.util :as common-util]
-            [frontend.db :as db]
-            [logseq.shui.ui :as shui]))
+            [shadow.loader :as loader]))
 
 (defonce tldraw-loaded? (atom false))
 (rum/defc tldraw-app < rum/reactive
@@ -36,21 +37,22 @@
     (when draw-component
       (draw-component page-uuid shape-id))))
 
-(rum/defc tldraw-preview < rum/reactive
-  {:init (fn [state]
-           (p/let [_ (loader/load :tldraw)]
-             (reset! tldraw-loaded? true))
-           (let [page-uuid (first (:rum/args state))]
-             (db-async/<get-block (state/get-current-repo) page-uuid))
-           state)}
+(rum/defc tldraw-preview
   [page-uuid]
   (when page-uuid
-    (let [loaded? (rum/react tldraw-loaded?)
-          tldr (whiteboard-handler/get-page-tldr page-uuid)
-          generate-preview (when loaded?
-                             (resolve 'frontend.extensions.tldraw/generate-preview))]
-      (when (and generate-preview (not (state/sub-async-query-loading page-uuid)))
-        (generate-preview tldr)))))
+    (let [[loading? set-loading!] (hooks/use-state true)]
+      (hooks/use-effect!
+       (fn []
+         (p/do!
+          (loader/load :tldraw)
+          (db-async/<get-block (state/get-current-repo) page-uuid)
+          (set-loading! false)))
+       [])
+      (when-not loading?
+        (let [tldr (whiteboard-handler/get-page-tldr page-uuid)
+              generate-preview (resolve 'frontend.extensions.tldraw/generate-preview)]
+          (when generate-preview
+            (generate-preview tldr)))))))
 
 ;; TODO: move to frontend.components.reference
 (rum/defcs references-count < rum/reactive db-mixins/query

+ 3 - 4
src/main/frontend/db.cljs

@@ -35,16 +35,15 @@
   get-files-blocks get-files-full get-journals-length
   get-latest-journals get-page get-case-page get-page-alias-names
   get-page-blocks-count get-page-blocks-no-cache get-page-file get-page-format
-  get-referenced-blocks get-page-referenced-blocks-full get-page-referenced-pages
-  get-all-pages get-pages-relation get-pages-that-mentioned-page
-  journal-page? page? page-alias-set sub-block
+  get-referenced-blocks get-page-referenced-blocks-full
+  journal-page? page? page-alias-set sub-block sub-entity
   page-empty? page-exists? get-alias-source-page
   has-children? whiteboard-page?
   get-namespace-pages get-all-namespace-relation]
 
  [frontend.db.react
   get-current-page
-  remove-q! remove-query-component! add-q! add-query-component! clear-query-state!
+  remove-query-component! add-q! add-query-component! clear-query-state!
   q
   query-state component->query-key set-new-result!])
 

+ 60 - 95
src/main/frontend/db/async.cljs

@@ -17,7 +17,6 @@
             [frontend.util :as util]
             [logseq.db :as ldb]
             [logseq.db.frontend.property :as db-property]
-            [logseq.db.frontend.rules :as rules]
             [promesa.core :as p]))
 
 (def <q db-async-util/<q)
@@ -50,7 +49,7 @@
     (p/let [templates (<get-all-templates repo)]
       (get templates name))))
 
-(defn <db-based-get-all-properties
+(defn db-based-get-all-properties
   "Return seq of all property names except for private built-in properties."
   [graph & {:keys [remove-built-in-property? remove-non-queryable-built-in-property?]
             :or {remove-built-in-property? true
@@ -79,7 +78,7 @@
   [& {:as opts}]
   (when-let [graph (state/get-current-repo)]
     (if (config/db-based-graph? graph)
-      (<db-based-get-all-properties graph opts)
+      (db-based-get-all-properties graph opts)
       (p/let [properties (file-async/<file-based-get-all-properties graph)
               hidden-properties (set (map name (property-util/hidden-properties)))]
         (remove #(hidden-properties (:block/title %)) properties)))))
@@ -90,109 +89,86 @@
   (when-not (config/db-based-graph? graph)
     (file-async/<get-file-based-property-values graph property)))
 
-(defn <get-block-property-values
-  "For db graphs, returns property value ids for given property db-ident.
-   Separate from file version because values are lazy loaded"
-  [graph property-id]
-  (let [default-value-id (:db/id (:logseq.property/default-value (db/entity property-id)))
-        empty-id (:db/id (db/entity :logseq.property/empty-placeholder))]
-    (p/let [result (<q graph {:transact-db? false}
-                       '[:find [?v ...]
-                         :in $ ?property-id ?empty-id
-                         :where
-                         [?b ?property-id ?v]
-                         [(not= ?v ?empty-id)]]
-                       property-id
-                       empty-id)]
-      (if default-value-id
-        ;; put default value the first
-        (concat [default-value-id] result)
-        result))))
+(defn <get-property-values
+  "For db graphs, returns a vec of property value maps for given property
+  db-ident.  The map contains a :label key which can be a string or number (for
+  query builder) and a :value key which contains the entity or scalar property value"
+  [property-id & {:as opts}]
+  (when property-id
+    (state/<invoke-db-worker :thread-api/get-property-values (state/get-current-repo)
+                             (assoc opts :property-ident property-id))))
 
-(comment
-  (defn <get-block-property-value-entity
-    [graph property-id value]
-    (p/let [result (<q graph {}
-                       '[:find [(pull ?vid [*]) ...]
-                         :in $ ?property-id ?value
-                         :where
-                         [?b ?property-id ?vid]
-                         [(not= ?vid :logseq.property/empty-placeholder)]
-                         (or
-                          [?vid :logseq.property/value ?value]
-                          [?vid :block/title ?value])]
-                       property-id
-                       value)]
-      (db/entity (:db/id (first result))))))
-
-;; TODO: batch queries for better performance and UX
 (defn <get-block
-  [graph name-or-uuid & {:keys [children? nested-children?]
-                         :or {children? true
-                              nested-children? false}
-                         :as opts}]
-  (let [name' (str name-or-uuid)
-        *async-queries (:db/async-queries @state/state)
-        async-requested? (get @*async-queries [name' opts])
+  [graph id-uuid-or-name & {:keys [children? nested-children? skip-transact? skip-refresh? children-only? properties]
+                            :or {children? true}
+                            :as opts}]
+
+  ;; (prn :debug :<get-block id-uuid-or-name)
+  ;; (js/console.trace)
+  (let [name' (str id-uuid-or-name)
+        opts (assoc opts :children? children?)
         e (cond
-            (number? name-or-uuid)
-            (db/entity name-or-uuid)
+            (number? id-uuid-or-name)
+            (db/entity id-uuid-or-name)
             (util/uuid-string? name')
             (db/entity [:block/uuid (uuid name')])
             :else
             (db/get-page name'))
         id (or (and (:block/uuid e) (str (:block/uuid e)))
                (and (util/uuid-string? name') name')
-               name-or-uuid)]
-    (if (or (:block.temp/fully-loaded? e) async-requested?)
-      e
-      (do
-        (swap! *async-queries assoc [name' opts] true)
-        (state/update-state! :db/async-query-loading (fn [s] (conj s name')))
-        (p/let [{:keys [properties block children] :as result'}
-                (state/<invoke-db-worker :thread-api/get-block-and-children
-                                         graph id {:children? children?
-                                                   :nested-children? nested-children?})
-                conn (db/get-db graph false)
-                block-and-children (concat properties [block] children)
-                _ (d/transact! conn block-and-children)
-                affected-keys (->> (keep :db/id block-and-children)
-                                   (map #(vector :frontend.worker.react/block %)))]
-          (react/refresh-affected-queries! graph affected-keys)
-          (state/update-state! :db/async-query-loading (fn [s] (disj s name')))
-          (if children?
-            block
-            result'))))))
+               id-uuid-or-name)]
+    (cond
+      (and (:block.temp/fully-loaded? e) ; children may not be fully loaded
+           (not children-only?)
+           (not nested-children?)
+           (not (some #{:block.temp/refs-count} properties)))
+      (p/promise e)
+
+      :else
+      (p/let [result (state/<invoke-db-worker :thread-api/get-blocks graph
+                                              [{:id id :opts opts}])
+              {:keys [block children]} (first result)]
+        (when-not skip-transact?
+          (let [conn (db/get-db graph false)
+                block-and-children (if block (cons block children) children)
+                affected-keys [[:frontend.worker.react/block (:db/id block)]]
+                tx-data (remove (fn [b] (:block.temp/fully-loaded? (db/entity (:db/id b)))) block-and-children)]
+            (when (seq tx-data) (d/transact! conn tx-data))
+            (when-not skip-refresh?
+              (react/refresh-affected-queries! graph affected-keys))))
+
+        (if children-only? children block)))))
+
+(defn <get-blocks
+  [graph ids* & {:as opts}]
+  (let [ids (remove (fn [id] (:block.temp/fully-loaded? (db/entity id))) ids*)]
+    (when (seq ids)
+      (p/let [result (state/<invoke-db-worker :thread-api/get-blocks graph
+                                              (map (fn [id]
+                                                     {:id id :opts (assoc opts :children? false)})
+                                                   ids))]
+        (let [conn (db/get-db graph false)
+              result' (map :block result)]
+          (when (seq result')
+            (let [result'' (map (fn [b] (assoc b :block.temp/fully-loaded? true)) result')]
+              (d/transact! conn result'')))
+          result')))))
 
 (defn <get-block-parents
   [graph id depth]
   (assert (integer? id))
-  (when-let [block-id (:block/uuid (db/entity graph id))]
-    (state/update-state! :db/async-query-loading (fn [s] (conj s (str block-id "-parents"))))
+  (when (:block/uuid (db/entity graph id))
     (p/let [result (state/<invoke-db-worker :thread-api/get-block-parents graph id depth)
             conn (db/get-db graph false)
             _ (d/transact! conn result)]
-      (state/update-state! :db/async-query-loading (fn [s] (disj s (str block-id "-parents"))))
       result)))
 
-(defn <get-page-all-blocks
-  [page-name]
-  (when-let [page (some-> page-name (db-model/get-page))]
-    (p/let [result (state/<invoke-db-worker :thread-api/get-block-and-children
-                                            (state/get-current-repo)
-                                            (:block/uuid page)
-                                            {:children? true
-                                             :nested-children? false})]
-      (:children result))))
-
 (defn <get-block-refs
   [graph eid]
   (assert (integer? eid))
-  (state/update-state! :db/async-query-loading (fn [s] (conj s (str eid "-refs"))))
   (p/let [result (state/<invoke-db-worker :thread-api/get-block-refs graph eid)
           conn (db/get-db graph false)
           _ (d/transact! conn result)]
-    (state/update-state! :db/async-query-loading (fn [s] (disj s (str eid "-refs"))))
     result))
 
 (defn <get-block-refs-count
@@ -271,25 +247,14 @@
 
 (defn <get-tag-pages
   [graph tag-id]
-  (<q graph {:transact-db? true}
-      '[:find [(pull ?page [:db/id :block/uuid :block/name :block/title :block/created-at :block/updated-at])]
+  (<q graph {:transact-db? false}
+      '[:find [(pull ?page [:db/id :block/uuid :block/name :block/title :block/created-at :block/updated-at]) ...]
         :in $ ?tag-id
         :where
         [?page :block/tags ?tag-id]
         [?page :block/name]]
       tag-id))
 
-(defn <get-property-objects
-  [graph property-ident]
-  (<q graph {:transact-db? true}
-      '[:find [(pull ?b [*]) ...]
-        :in $ % ?prop
-        :where
-        (has-property-or-default-value? ?b ?prop)]
-      (rules/extract-rules rules/db-query-dsl-rules [:has-property-or-default-value]
-                           {:deps rules/rules-dependencies})
-      property-ident))
-
 (defn <get-tag-objects
   [graph class-id]
   (let [class-children (db-model/get-structured-children graph class-id)

+ 4 - 3
src/main/frontend/db/async/util.cljs

@@ -12,9 +12,10 @@
   (assert (not-any? fn? inputs) "Async query inputs can't include fns because fn can't be serialized")
   (let [*async-queries (:db/async-queries @state/state)
         async-requested? (get @*async-queries [inputs opts])]
-    (if async-requested?
-      (let [db (db-conn/get-db graph)]
-        (apply d/q (first inputs) db (rest inputs)))
+    (if (and async-requested? transact-db?)
+      (p/promise
+       (let [db (db-conn/get-db graph)]
+         (apply d/q (first inputs) db (rest inputs))))
       (p/let [result (state/<invoke-db-worker :thread-api/q graph inputs)]
         (swap! *async-queries assoc [inputs opts] true)
         (when result

+ 39 - 147
src/main/frontend/db/model.cljs

@@ -7,6 +7,7 @@
             [clojure.walk :as walk]
             [datascript.core :as d]
             [frontend.common.file-based.db :as common-file-db]
+            [frontend.common.graph-view :as graph-view]
             [frontend.config :as config]
             [frontend.date :as date]
             [frontend.db.conn :as conn]
@@ -17,9 +18,11 @@
             [logseq.common.util :as common-util]
             [logseq.common.util.date-time :as date-time-util]
             [logseq.db :as ldb]
+            [logseq.db.frontend.class :as db-class]
             [logseq.db.frontend.content :as db-content]
             [logseq.db.frontend.rules :as rules]
-            [logseq.graph-parser.db :as gp-db]))
+            [logseq.graph-parser.db :as gp-db]
+            [logseq.shui.hooks :as hooks]))
 
 ;; TODO: extract to specific models and move data transform logic to the
 ;; corresponding handlers.
@@ -53,28 +56,11 @@
     :block/heading-level
     :block/file
     :logseq.property/parent
-    {:block/page [:db/id :block/name :block/title :block/journal-day]}
+    {:block/page [:db/id :block/name :block/title :block/uuid :block/journal-day :block/type]}
     {:block/_parent ...}])
 
 (def hidden-page? ldb/hidden?)
 
-(defn get-all-tagged-pages
-  [repo]
-  (d/q '[:find ?page ?tag
-         :where
-         [?page :block/tags ?tag]]
-       (conn/get-db repo)))
-
-(defn get-all-pages
-  [repo]
-  (when-let [db (conn/get-db repo)]
-    (ldb/get-all-pages db)))
-
-(defn get-all-page-titles
-  [repo]
-  (->> (get-all-pages repo)
-       (map :block/title)))
-
 (defn get-alias-source-page
   "return the source page of an alias"
   [repo alias-id]
@@ -214,10 +200,7 @@ independent of format as format specific heading characters are stripped"
 
 (defn page-alias-set
   [repo-url page-id]
-  (->>
-   (ldb/get-block-alias (conn/get-db repo-url) page-id)
-   (set)
-   (set/union #{page-id})))
+  (ldb/page-alias-set (conn/get-db repo-url) page-id))
 
 (defn get-page-alias-names
   [repo page-id]
@@ -244,17 +227,34 @@ independent of format as format specific heading characters are stripped"
 (def sort-by-order ldb/sort-by-order)
 
 (defn sub-block
-  [id]
+  "Used together with rum/reactive db-mixins/query"
+  [id & {:keys [ref?]
+         :or {ref? false}}]
   (when-let [repo (state/get-current-repo)]
     (when id
       (let [ref (react/q repo [:frontend.worker.react/block id]
                          {:query-fn (fn [_]
-                                      (let [e (db-utils/entity id)]
-                                        [e (:block/tx-id e)]))}
-                         nil)
-            e (-> ref react first)]
-        (when-let [id (:db/id e)]
-          (db-utils/entity id))))))
+                                      (db-utils/entity id))}
+                         nil)]
+        (if ref? ref
+            (let [e (-> ref react)]
+              (when-let [id (:db/id e)]
+                (db-utils/entity id))))))))
+
+(defn sub-entity
+  "Used for react function components"
+  [entity* watch-id]
+  (let [id (:db/id entity*)
+        *ref (sub-block id {:ref? true})
+        [entity set-entity!] (hooks/use-state @*ref)]
+    (add-watch *ref watch-id (fn [_ _ _ new-value]
+                               (set-entity! new-value)))
+    (hooks/use-effect!
+     (fn []
+       #(remove-watch *ref watch-id))
+     [])
+
+    [entity set-entity!]))
 
 (defn sort-by-order-recursive
   [form]
@@ -482,9 +482,9 @@ independent of format as format specific heading characters are stripped"
        first))))
 
 (defn get-page
-  [page-name-or-uuid]
-  (when page-name-or-uuid
-    (ldb/get-page (conn/get-db) page-name-or-uuid)))
+  [page-id-name-or-uuid]
+  (when page-id-name-or-uuid
+    (ldb/get-page (conn/get-db) page-id-name-or-uuid)))
 
 (defn get-case-page
   [page-name-or-uuid]
@@ -554,60 +554,14 @@ independent of format as format specific heading characters are stripped"
   ([n]
    (get-latest-journals (state/get-current-repo) n))
   ([repo-url n]
-   (when (conn/get-db repo-url)
-     (let [date (js/Date.)
-           _ (.setDate date (- (.getDate date) (dec n)))
-           today (date-time-util/date->int (js/Date.))]
-       (->>
-        (react/q repo-url [:frontend.worker.react/journals] {:use-cache? false}
-                 '[:find [(pull ?page [*]) ...]
-                   :in $ ?today
-                   :where
-                   [?page :block/name ?page-name]
-                   [?page :block/journal-day ?journal-day]
-                   [(<= ?journal-day ?today)]]
-                 today)
-        (react)
-        (sort-by :block/journal-day)
-        (reverse)
-        (take n))))))
-
-;; get pages that this page referenced
-(defn get-page-referenced-pages
-  [repo page-id]
-  (when-let [db (conn/get-db repo)]
-    (let [pages (page-alias-set repo page-id)
-          ref-pages (d/q
-                     '[:find [?ref-page ...]
-                       :in $ ?pages
-                       :where
-                       [(untuple ?pages) [?page ...]]
-                       [?block :block/page ?page]
-                       [?block :block/refs ?ref-page]]
-                     db
-                     pages)]
-      ref-pages)))
+   (when-let [db (conn/get-db repo-url)]
+     (take n (ldb/get-latest-journals db)))))
 
 ;; get pages who mentioned this page
 (defn get-pages-that-mentioned-page
   [repo page-id include-journals?]
-  (when (conn/get-db repo)
-    (let [pages (page-alias-set repo page-id)
-          mentioned-pages (->>
-                           (mapcat
-                            (fn [id]
-                              (let [page (db-utils/entity repo id)]
-                                (->> (:block/_refs page)
-                                     (keep (fn [ref]
-                                             (if (ldb/page? ref)
-                                               page
-                                               (:block/page ref)))))))
-                            pages)
-                           (util/distinct-by :db/id))]
-      (keep (fn [page]
-              (when-not (and (not include-journals?) (ldb/journal? page))
-                (:db/id page)))
-            mentioned-pages))))
+  (when-let [db (conn/get-db repo)]
+    (graph-view/get-pages-that-mentioned-page db page-id include-journals?)))
 
 (defn get-page-referenced-blocks-full
   ([page-id]
@@ -822,15 +776,7 @@ independent of format as format specific heading characters are stripped"
 
 (defn get-structured-children
   [repo eid]
-  (->>
-   (d/q '[:find [?children ...]
-          :in $ ?parent %
-          :where
-          (parent ?parent ?children)]
-        (conn/get-db repo)
-        eid
-        (:parent rules/rules))
-   (remove #{eid})))
+  (db-class/get-structured-children (conn/get-db repo) eid))
 
 (defn get-class-objects
   [repo class-id]
@@ -845,34 +791,9 @@ independent of format as format specific heading characters are stripped"
        (:block/_tags class))
      (remove ldb/hidden?))))
 
-(defn sub-class-objects
-  [repo class-id]
-  (when class-id
-    (-> (react/q repo [:frontend.worker.react/objects class-id]
-                 {:query-fn (fn [_] (get-class-objects repo class-id))}
-                 nil)
-        react)))
-
-(defn get-property-related-objects
-  [repo property-id]
-  (when-let [property (db-utils/entity repo property-id)]
-    (->> (d/q '[:find [?b ...]
-                :in $ % ?prop
-                :where
-                (has-property-or-default-value? ?b ?prop)]
-              (conn/get-db repo)
-              (rules/extract-rules rules/db-query-dsl-rules [:has-property-or-default-value]
-                                   {:deps rules/rules-dependencies})
-              (:db/ident property))
-         (map #(db-utils/entity repo %))
-         (remove ldb/hidden?))))
-
 (defn get-all-namespace-relation
   [repo]
-  (d/q '[:find ?page ?parent
-         :where
-         [?page :block/namespace ?parent]]
-       (conn/get-db repo)))
+  (ldb/get-all-namespace-relation (conn/get-db repo)))
 
 (defn get-all-namespace-parents
   [repo]
@@ -881,35 +802,6 @@ independent of format as format specific heading characters are stripped"
          (map (fn [[_ ?parent]]
                 (db-utils/entity db ?parent))))))
 
-;; Ignore files with empty blocks for now
-(defn get-pages-relation
-  [repo with-journal?]
-  (when-let [db (conn/get-db repo)]
-    (if (config/db-based-graph?)
-      (let [q (if with-journal?
-                '[:find ?p ?ref-page
-                  :where
-                  [?block :block/page ?p]
-                  [?block :block/refs ?ref-page]]
-                '[:find ?p ?ref-page
-                  :where
-                  [?block :block/page ?p]
-                  [?p :block/tags]
-                  (not [?p :block/tags :logseq.class/Journal])
-                  [?block :block/refs ?ref-page]])]
-        (d/q q db))
-      (let [q (if with-journal?
-                '[:find ?p ?ref-page
-                  :where
-                  [?block :block/page ?p]
-                  [?block :block/refs ?ref-page]]
-                '[:find ?p ?ref-page
-                  :where
-                  [?block :block/page ?p]
-                  (not [?p :block/type "journal"])
-                  [?block :block/refs ?ref-page]])]
-        (d/q q db)))))
-
 (defn get-namespace-pages
   "Accepts both sanitized and unsanitized namespaces"
   [repo namespace]

+ 5 - 4
src/main/frontend/db/query_react.cljs

@@ -3,18 +3,18 @@
   (:require [clojure.string :as string]
             [clojure.walk :as walk]
             [frontend.config :as config]
+            [frontend.date :as date]
             [frontend.db.conn :as conn]
             [frontend.db.model :as model]
             [frontend.db.react :as react]
             [frontend.db.utils :as db-utils]
             [frontend.extensions.sci :as sci]
             [frontend.state :as state]
-            [logseq.db.frontend.inputs :as db-inputs]
-            [logseq.common.util.page-ref :as page-ref]
             [frontend.util :as util]
-            [frontend.date :as date]
             [lambdaisland.glogi :as log]
-            [logseq.db :as ldb]))
+            [logseq.common.util.page-ref :as page-ref]
+            [logseq.db :as ldb]
+            [logseq.db.frontend.inputs :as db-inputs]))
 
 (defn resolve-input
   "Wrapper around db-inputs/resolve-input which provides editor-specific state"
@@ -100,6 +100,7 @@
     (pprint "================")
     (pprint "Use the following to debug your datalog queries:")
     (pprint query')
+
     (let [query (resolve-query query)
           repo (or repo (state/get-current-repo))
           db (conn/get-db repo)

+ 21 - 11
src/main/frontend/db/react.cljs

@@ -42,12 +42,13 @@
   (reset! query-state {}))
 
 (defn add-q!
-  [k query inputs result-atom transform-fn query-fn inputs-fn]
+  [k query inputs result-atom transform-fn query-fn async-query-fn inputs-fn]
   (swap! query-state assoc k {:query query
                               :inputs inputs
                               :result result-atom
                               :transform-fn transform-fn
                               :query-fn query-fn
+                              :async-query-fn async-query-fn
                               :inputs-fn inputs-fn})
   result-atom)
 
@@ -85,14 +86,22 @@
     (:result result)))
 
 (defn- <q-aux
-  [repo db query-fn inputs-fn k query inputs]
+  [repo db query-fn async-query-fn inputs-fn k query inputs built-in-query?]
   (let [kv? (and (vector? k) (= :kv (second k)))
-        journals? (and (vector? k) (= :frontend.worker.react/journals (last k)))
-        q (if (or journals? util/node-test?)
+        q (if util/node-test?
             (fn [query inputs] (apply d/q query db inputs))
-            (fn [query inputs] (apply db-async-util/<q repo {} (cons query inputs))))]
-    (when (or query-fn query kv?)
+            (fn [query inputs]
+              (let [q-f #(apply db-async-util/<q repo {} (cons query inputs))]
+                (if built-in-query?
+                  ;; delay built-in-queries to not block journal rendering
+                  (p/let [_ (p/delay 100)]
+                    (q-f))
+                  (q-f)))))]
+    (when (or query-fn async-query-fn query kv?)
       (cond
+        async-query-fn
+        (async-query-fn)
+
         query-fn
         (query-fn db nil)
 
@@ -110,7 +119,8 @@
         (q query nil)))))
 
 (defn q
-  [repo k {:keys [use-cache? transform-fn query-fn inputs-fn disable-reactive? return-promise?]
+  [repo k {:keys [use-cache? transform-fn query-fn async-query-fn inputs-fn
+                  disable-reactive? return-promise? built-in-query?]
            :or {use-cache? true
                 transform-fn identity}} query & inputs]
   ;; {:pre [(s/valid? :frontend.worker.react/block k)]}
@@ -125,9 +135,9 @@
         (if (and use-cache? result-atom)
           result-atom
           (let [result-atom (or result-atom (atom nil))
-                p-or-value (<q-aux repo db query-fn inputs-fn k query inputs)]
+                p-or-value (<q-aux repo db query-fn async-query-fn inputs-fn k query inputs built-in-query?)]
             (when-not disable-reactive?
-              (add-q! k query inputs result-atom transform-fn query-fn inputs-fn))
+              (add-q! k query inputs result-atom transform-fn query-fn async-query-fn inputs-fn))
             (cond
               return-promise?
               p-or-value
@@ -159,9 +169,9 @@
         (ldb/get-page (conn/get-db) page)))))
 
 (defn- execute-query!
-  [graph db k {:keys [query inputs transform-fn query-fn inputs-fn result]
+  [graph db k {:keys [query inputs transform-fn query-fn async-query-fn inputs-fn result built-in-query?]
                :or {transform-fn identity}}]
-  (p/let [p-or-value (<q-aux graph db query-fn inputs-fn k query inputs)
+  (p/let [p-or-value (<q-aux graph db query-fn async-query-fn inputs-fn k query inputs built-in-query?)
           result' (transform-fn p-or-value)]
     (when-not (= result' result)
       (set-new-result! k result'))))

+ 1 - 6
src/main/frontend/db/restore.cljs

@@ -28,9 +28,4 @@
 
     (state/pub-event! [:graph/restored repo])
     (state/set-state! :graph/loading? false)
-    (state/pub-event! [:ui/re-render-root])
-
-    ;; (async/go
-    ;;   (async/<! (async/timeout 100))
-    ;;   (db-async/<fetch-all-pages repo))
-    ))
+    (state/pub-event! [:ui/re-render-root])))

+ 11 - 13
src/main/frontend/db/rtc/debug_ui.cljs

@@ -28,16 +28,15 @@
   (rum/local nil ::keys-state)
   {:will-mount (fn [state]
                  (let [canceler
-                       (c.m/run-task
-                        (m/reduce
-                         (fn [logs log]
-                           (let [logs* (if log
-                                         (take 10 (conj logs log))
-                                         logs)]
-                             (reset! (get state ::logs) logs*)
-                             logs*))
-                         nil rtc-flows/rtc-log-flow)
-                        ::sub-logs)]
+                       (c.m/run-task ::sub-logs
+                         (m/reduce
+                          (fn [logs log]
+                            (let [logs* (if log
+                                          (take 10 (conj logs log))
+                                          logs)]
+                              (reset! (get state ::logs) logs*)
+                              logs*))
+                          nil rtc-flows/rtc-log-flow))]
                    (reset! (get state ::sub-log-canceler) canceler)
                    state))
    :will-unmount (fn [state]
@@ -83,9 +82,8 @@
        (shui/tabler-icon "download") "graph-list")
       (shui/button
        {:size :sm
-        :on-click #(c.m/run-task
-                    (user/new-task--upload-user-avatar "TEST_AVATAR")
-                    :upload-test-avatar)}
+        :on-click #(c.m/run-task :upload-test-avatar
+                     (user/new-task--upload-user-avatar "TEST_AVATAR"))}
        (shui/tabler-icon "upload") "upload-test-avatar")]
 
      [:div.pb-4

+ 14 - 11
src/main/frontend/extensions/code.cljs

@@ -1,11 +1,9 @@
 (ns frontend.extensions.code
-  (:require [cljs-bean.core :as bean]
-            [clojure.string :as string]
-            ["codemirror" :as CodeMirror]
+  (:require ["codemirror" :as CodeMirror]
             ["codemirror/addon/edit/closebrackets"]
             ["codemirror/addon/edit/matchbrackets"]
-            ["codemirror/addon/selection/active-line"]
             ["codemirror/addon/hint/show-hint"]
+            ["codemirror/addon/selection/active-line"]
             ["codemirror/mode/apl/apl"]
             ["codemirror/mode/asciiarmor/asciiarmor"]
             ["codemirror/mode/asn.1/asn.1"]
@@ -128,20 +126,22 @@
             ["codemirror/mode/yaml-frontmatter/yaml-frontmatter"]
             ["codemirror/mode/yaml/yaml"]
             ["codemirror/mode/z80/z80"]
+            [cljs-bean.core :as bean]
+            [clojure.string :as string]
             [frontend.commands :as commands]
+            [frontend.config :as config]
             [frontend.db :as db]
             [frontend.extensions.calc :as calc]
-            [frontend.handler.editor :as editor-handler]
             [frontend.handler.code :as code-handler]
+            [frontend.handler.editor :as editor-handler]
+            [frontend.schema.handler.common-config :refer [Config-edn]]
             [frontend.state :as state]
             [frontend.util :as util]
-            [frontend.config :as config]
             [goog.dom :as gdom]
             [goog.object :as gobj]
-            [frontend.schema.handler.common-config :refer [Config-edn]]
             [malli.core :as m]
-            [rum.core :as rum]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [rum.core :as rum]))
 
 ;; codemirror
 
@@ -466,11 +466,14 @@
                              (when e (util/stop e))
                              (let [esc? (gobj/get cm "escPressed")]
                                (when (or (= :file (state/get-current-route))
-                                       (not esc?))
+                                         (not esc?))
                                  (code-handler/save-code-editor!))
                                (state/set-block-component-editing-mode! false)
                                (state/set-state! :editor/code-block-context nil)
-                               (when (not esc?) (state/clear-edit!))
+                               (when (and (not esc?)
+                                          (= (:db/id (state/get-edit-block))
+                                             (:db/id edit-block)))
+                                 (state/clear-edit!))
                                (vreset! *cursor-curr nil)
                                (vreset! *cursor-prev nil))))
         (.on editor "focus" (fn [_e]

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

@@ -40,16 +40,12 @@
 
 .CodeMirror {
   height: auto;
+  max-height: 1024px;
   width: 100%;
   font-family: Fira Code, Monaco, Menlo, Consolas, 'COURIER NEW', monospace;
   border-radius: 2px;
   line-height: 1.45em;
 
-  &-scroll {
-    padding-top: 14px;
-    padding-bottom: 62px;
-  }
-
   &:not(.CodeMirror-focused) {
     .CodeMirror-activeline-background {
       background: unset !important;

+ 4 - 3
src/main/frontend/extensions/fsrs.cljs

@@ -1,6 +1,7 @@
 (ns frontend.extensions.fsrs
   "Flashcards functions based on FSRS, only works in db-based graphs"
   (:require [clojure.string :as string]
+            [frontend.common.missionary :as c.m]
             [frontend.components.block :as component-block]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
@@ -16,7 +17,6 @@
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
-            [frontend.common.missionary :as c.m]
             [logseq.db :as ldb]
             [logseq.db.frontend.entity-plus :as entity-plus]
             [logseq.shui.ui :as shui]
@@ -189,7 +189,7 @@
 (rum/defcs ^:private card-view < rum/reactive db-mixins/query
   {:will-mount (fn [state]
                  (when-let [[repo block-id _] (:rum/args state)]
-                   (db-async/<get-block repo block-id))
+                   (db-async/<get-block repo block-id {:children? false}))
                  state)}
   [state repo block-id *card-index *phase]
   (when-let [block-entity (db/sub-block block-id)]
@@ -310,7 +310,8 @@
   (when-let [canceler @*last-update-due-cards-count-canceler]
     (canceler)
     (reset! *last-update-due-cards-count-canceler nil))
-  (let [canceler (c.m/run-task new-task--update-due-cards-count :update-due-cards-count)]
+  (let [canceler (c.m/run-task :update-due-cards-count
+                   new-task--update-due-cards-count)]
     (reset! *last-update-due-cards-count-canceler canceler)
     nil))
 

+ 1 - 1
src/main/frontend/extensions/handbooks/core.cljs

@@ -534,7 +534,7 @@
 
         [scrolled?, set-scrolled!] (rum/use-state false)
         on-scroll (hooks/use-memo
-                   #(util/debounce 100 (fn [^js e] (set-scrolled! (not (< (.. e -target -scrollTop) 10)))))
+                   #(util/debounce (fn [^js e] (set-scrolled! (not (< (.. e -target -scrollTop) 10)))) 100)
                    [])]
 
     ;; load handbooks

+ 21 - 18
src/main/frontend/extensions/pdf/assets.cljs

@@ -1,35 +1,35 @@
 (ns frontend.extensions.pdf.assets
   (:require [cljs.reader :as reader]
             [clojure.string :as string]
+            [fipp.edn :refer [pprint]]
             [frontend.config :as config]
-            [frontend.db.conn :as conn]
+            [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
+            [frontend.db.async :as db-async]
+            [frontend.db.conn :as conn]
             [frontend.db.model :as db-model]
             [frontend.db.utils :as db-utils]
-            [frontend.db.async :as db-async]
+            [frontend.extensions.lightbox :as lightbox]
+            [frontend.extensions.pdf.windows :as pdf-windows]
             [frontend.fs :as fs]
-            [frontend.handler.editor :as editor-handler]
-            [frontend.handler.property :as property-handler]
-            [frontend.handler.page :as page-handler]
             [frontend.handler.assets :as assets-handler]
+            [frontend.handler.editor :as editor-handler]
             [frontend.handler.notification :as notification]
-            [frontend.handler.route :as route-handler]
+            [frontend.handler.page :as page-handler]
+            [frontend.handler.property :as property-handler]
             [frontend.handler.property.util :as pu]
-            [frontend.ui :as ui]
-            [frontend.context.i18n :refer [t]]
-            [frontend.extensions.lightbox :as lightbox]
+            [frontend.handler.route :as route-handler]
             [frontend.state :as state]
+            [frontend.ui :as ui]
             [frontend.util :as util]
-            [logseq.publishing.db :as publish-db]
-            [frontend.extensions.pdf.windows :as pdf-windows]
-            [logseq.common.path :as path]
             [logseq.common.config :as common-config]
+            [logseq.common.path :as path]
             [logseq.common.util.block-ref :as block-ref]
+            [logseq.publishing.db :as publish-db]
             [medley.core :as medley]
             [promesa.core :as p]
             [reitit.frontend.easy :as rfe]
-            [rum.core :as rum]
-            [fipp.edn :refer [pprint]]))
+            [rum.core :as rum]))
 
 (defn get-in-repo-assets-full-filename
   [url]
@@ -190,8 +190,11 @@
 
 (defn construct-highlights-from-hls-page
   [hls-page]
-  (p/let [blocks (db-async/<get-page-all-blocks (:block/uuid hls-page))]
-    {:highlights (keep :logseq.property.pdf/hl-value blocks)}))
+  (p/let [result (db-async/<get-block (state/get-current-repo)
+                                      (:block/uuid hls-page)
+                                      {:children? true
+                                       :nested-children? false})]
+    {:highlights (keep :logseq.property.pdf/hl-value result)}))
 
 (defn file-based-load-hls-data$
   [{:keys [hls-file]}]
@@ -423,7 +426,7 @@
             {:style {:width (if style "100%" "auto")}}
             [:span.asset-action-bar
              (when-let [asset-uuid (and (config/db-based-graph?)
-                                     (some-> asset-block (:block/uuid)))]
+                                        (some-> asset-block (:block/uuid)))]
                [:button.asset-action-btn
                 {:title (t :asset/ref-block)
                  :tabIndex "-1"
@@ -439,7 +442,7 @@
                  :on-click (fn [e]
                              (util/stop e)
                              (-> (util/copy-image-to-clipboard (common-config/remove-asset-protocol @*src))
-                               (p/then #(notification/show! "Copied!" :success))))}
+                                 (p/then #(notification/show! "Copied!" :success))))}
                 (ui/icon "copy")])
 
              [:button.asset-action-btn

+ 3 - 3
src/main/frontend/extensions/pdf/core.cljs

@@ -690,12 +690,12 @@
            (when-let [^js/HTMLDivElement hls-layer (pdf-utils/resolve-hls-layer! viewer page)]
              (let [page-hls (get grouped-hls page)
                    hls-render (pdf-highlights-region-container
-                                   viewer page-hls {:show-ctx-menu! show-ctx-menu!
-                                                    :upd-hl! upd-hl!})
+                               viewer page-hls {:show-ctx-menu! show-ctx-menu!
+                                                :upd-hl! upd-hl!})
                    ^js mounted-root (.-mountedRoot hls-layer)]
                (if (nil? mounted-root)
                  (->> (rum/mount hls-render hls-layer)
-                   (set! (. hls-layer -mountedRoot)))
+                      (set! (. hls-layer -mountedRoot)))
                  (.render mounted-root hls-render))))))
        ;; destroy
        #())

Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor