Browse Source

Fix: broken linked references (#6105)

* enhance: clicking the refresh button if a query is slow

* fix: skip running slow queries if there's no need to refresh

* fix: linked reference filtering does not work on TASK items

close #1669

* fix: each block should have its own page as a reference when query page references

* fix: references

* fix: non consecutive blocks in query result and filtered linked references

* simplify filters logic

* fix: ref numbers

* Save both :block/refs and :block/path-refs for different usage

* fix: block refs

* enhance: move scheduled and deadlines to its own ns

* linked references performance tweaks

* mouse hover filters icon to expand the collapsed linked refs

* perf: react/refresh! once instead twice

* enhance: compute flashcards every hour instead of every 5s

* feat: macros as blocks

* feat: flashcards list

* fix: refed pages count

* fix: can't select in linked references

* fix: block editing on linked refs

* perf: editing in linked references

* enhance: update srs cards number when clicking flashcards

* Add a test for the case fixed in #6100

* Address feedbacks from Gabriel

* fix: Block Ref Indicator is missing from the references-blocks class

close #5375

* fix: referenced pages

* fix: page refs

* fix: Using filters pushed the title property to the second block

close #5845

Co-authored-by: Gabriel Horner <[email protected]>
Tienson Qin 3 years ago
parent
commit
a1ca6820df
47 changed files with 1194 additions and 943 deletions
  1. 2 2
      deps/db/src/logseq/db/rules.cljc
  2. 3 1
      deps/db/src/logseq/db/schema.cljs
  3. 4 3
      deps/graph-parser/src/logseq/graph_parser.cljs
  4. 145 117
      deps/graph-parser/src/logseq/graph_parser/block.cljs
  5. 15 13
      deps/graph-parser/src/logseq/graph_parser/extract.cljc
  6. 1 1
      deps/graph-parser/src/logseq/graph_parser/property.cljs
  7. 2 2
      deps/graph-parser/src/logseq/graph_parser/test/docs_graph_helper.cljs
  8. 95 62
      src/main/frontend/components/block.cljs
  9. 20 4
      src/main/frontend/components/command_palette.css
  10. 1 1
      src/main/frontend/components/content.cljs
  11. 7 1
      src/main/frontend/components/journal.cljs
  12. 7 3
      src/main/frontend/components/page.cljs
  13. 119 131
      src/main/frontend/components/reference.cljs
  14. 1 1
      src/main/frontend/components/reference.css
  15. 34 0
      src/main/frontend/components/scheduled_deadlines.cljs
  16. 7 6
      src/main/frontend/components/search.cljs
  17. 21 12
      src/main/frontend/components/select.cljs
  18. 7 5
      src/main/frontend/components/sidebar.cljs
  19. 2 5
      src/main/frontend/db.cljs
  20. 107 124
      src/main/frontend/db/model.cljs
  21. 13 10
      src/main/frontend/db/query_custom.cljs
  22. 10 10
      src/main/frontend/db/query_dsl.cljs
  23. 2 14
      src/main/frontend/db/query_react.cljs
  24. 81 64
      src/main/frontend/db/react.cljs
  25. 172 148
      src/main/frontend/extensions/srs.cljs
  26. 5 14
      src/main/frontend/format/block.cljs
  27. 1 2
      src/main/frontend/handler.cljs
  28. 42 54
      src/main/frontend/handler/block.cljs
  29. 10 12
      src/main/frontend/handler/editor.cljs
  30. 1 1
      src/main/frontend/handler/external.cljs
  31. 38 34
      src/main/frontend/handler/file.cljs
  32. 2 3
      src/main/frontend/handler/page.cljs
  33. 1 1
      src/main/frontend/handler/paste.cljs
  34. 6 6
      src/main/frontend/handler/recent.cljs
  35. 13 14
      src/main/frontend/handler/route.cljs
  36. 10 6
      src/main/frontend/modules/editor/undo_redo.cljs
  37. 8 5
      src/main/frontend/modules/outliner/core.cljs
  38. 1 2
      src/main/frontend/modules/outliner/datascript.cljc
  39. 71 11
      src/main/frontend/modules/outliner/pipeline.cljs
  40. 37 0
      src/main/frontend/modules/outliner/tree.cljs
  41. 5 5
      src/main/frontend/state.cljs
  42. 23 12
      src/main/frontend/ui.cljs
  43. 8 14
      src/main/frontend/ui.css
  44. 9 0
      src/main/frontend/util.cljc
  45. 1 1
      src/main/logseq/api.cljs
  46. 13 0
      src/test/frontend/db/query_dsl_test.cljs
  47. 11 6
      src/test/frontend/modules/outliner/core_test.cljs

+ 2 - 2
deps/db/src/logseq/db/rules.cljc

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

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

@@ -84,7 +84,9 @@
    :block/journal-day {}
    ;; page's namespace
    :block/namespace {:db/valueType :db.type/ref}
-
+   ;; macros in block
+   :block/macros {:db/valueType :db.type/ref
+                  :db/cardinality :db.cardinality/many}
    ;; block's file
    :block/file {:db/valueType :db.type/ref}
 

+ 4 - 3
deps/graph-parser/src/logseq/graph_parser.cljs

@@ -51,9 +51,10 @@
         tx (concat tx [(cond-> {:file/path file}
                                new?
                                ;; TODO: use file system timestamp?
-                               (assoc :file/created-at (date-time-util/time-ms)))])]
-    {:tx
-     (d/transact! conn (gp-util/remove-nils tx) (select-keys options [:new-graph? :from-disk?]))
+                         (assoc :file/created-at (date-time-util/time-ms)))])
+        tx' (gp-util/remove-nils tx)
+        result (d/transact! conn tx' (select-keys options [:new-graph? :from-disk?]))]
+    {:tx result
      :ast ast}))
 
 (defn filter-files

+ 145 - 117
deps/graph-parser/src/logseq/graph_parser/block.cljs

@@ -395,53 +395,6 @@
            block))
        blocks))
 
-(defn- with-path-refs
-  [blocks]
-  (loop [blocks blocks
-         acc []
-         parents []]
-    (if (empty? blocks)
-      acc
-      (let [block (first blocks)
-            cur-level (:block/level block)
-            level-diff (- cur-level
-                          (get (last parents) :block/level 0))
-            [path-refs parents]
-            (cond
-              (zero? level-diff)            ; sibling
-              (let [path-refs (mapcat :block/refs (drop-last parents))
-                    parents (conj (vec (butlast parents)) block)]
-                [path-refs parents])
-
-              (> level-diff 0)              ; child
-              (let [path-refs (mapcat :block/refs parents)]
-                [path-refs (conj parents block)])
-
-              (< level-diff 0)              ; new parent
-              (let [parents (vec (take-while (fn [p] (< (:block/level p) cur-level)) parents))
-                    path-refs (mapcat :block/refs parents)]
-                [path-refs (conj parents block)]))
-            path-ref-pages (->> path-refs
-                                (concat (:block/refs block))
-                                (map (fn [ref]
-                                       (cond
-                                         (map? ref)
-                                         (:block/name ref)
-
-                                         :else
-                                         ref)))
-                                (remove string/blank?)
-                                (map (fn [ref]
-                                       (if (string? ref)
-                                         {:block/name (gp-util/page-name-sanity-lc ref)}
-                                         ref)))
-                                (remove vector?)
-                                (remove nil?)
-                                (distinct))]
-        (recur (rest blocks)
-               (conj acc (assoc block :block/path-refs path-ref-pages))
-               parents)))))
-
 (defn- block-tags->pages
   [{:keys [tags] :as block}]
   (if (seq tags)
@@ -491,6 +444,74 @@
           block-tags->pages
           (update :refs (fn [col] (remove nil? col)))))
 
+(defn- with-path-refs
+  [blocks]
+  (loop [blocks blocks
+         acc []
+         parents []]
+    (if (empty? blocks)
+      acc
+      (let [block (first blocks)
+            cur-level (:block/level block)
+            level-diff (- cur-level
+                          (get (last parents) :block/level 0))
+            [path-refs parents]
+            (cond
+              (zero? level-diff)            ; sibling
+              (let [path-refs (mapcat :block/refs (drop-last parents))
+                    parents (conj (vec (butlast parents)) block)]
+                [path-refs parents])
+
+              (> level-diff 0)              ; child
+              (let [path-refs (mapcat :block/refs parents)]
+                [path-refs (conj parents block)])
+
+              (< level-diff 0)              ; new parent
+              (let [parents (vec (take-while (fn [p] (< (:block/level p) cur-level)) parents))
+                    path-refs (mapcat :block/refs parents)]
+                [path-refs (conj parents block)]))
+            path-ref-pages (->> path-refs
+                                (concat (:block/refs block))
+                                (map (fn [ref]
+                                       (cond
+                                         (map? ref)
+                                         (:block/name ref)
+
+                                         :else
+                                         ref)))
+                                (remove string/blank?)
+                                (map (fn [ref]
+                                       (if (string? ref)
+                                         {:block/name (gp-util/page-name-sanity-lc ref)}
+                                         ref)))
+                                (remove vector?)
+                                (remove nil?)
+                                (distinct))]
+        (recur (rest blocks)
+               (conj acc (assoc block :block/path-refs path-ref-pages))
+               parents)))))
+
+(defn- macro->block
+  "macro: {:name \"\" arguments [\"\"]}"
+  [macro]
+  {:db/ident (str (:name macro) " " (string/join " " (:arguments macro)))
+   :block/type "macro"
+   :block/properties {:logseq.macro-name (:name macro)
+                      :logseq.macro-arguments (:arguments macro)}})
+
+(defn extract-macros-from-ast
+  [ast]
+  (let [*result (atom #{})]
+    (walk/postwalk
+     (fn [f]
+       (if (and (vector? f) (= (first f) "Macro"))
+         (do
+           (swap! *result conj (second f))
+           nil)
+         f))
+     ast)
+    (mapv macro->block @*result)))
+
 (defn- with-pre-block-if-exists
   [blocks body pre-block-properties encoded-content {:keys [supported-formats db date-formatter user-config]}]
   (let [first-block (first blocks)
@@ -515,6 +536,7 @@
                                 :refs property-refs
                                 :pre-block? true
                                 :unordered true
+                                :macros (extract-macros-from-ast body)
                                 :body body}
                          block (with-page-block-refs block false supported-formats db date-formatter)]
                      (block-keywordize block))
@@ -577,7 +599,7 @@
     `with-id?`: If `with-id?` equals to true, all the referenced pages will have new db ids.
     `format`: content's format, it could be either :markdown or :org-mode.
     `options`: Options supported are :user-config, :block-pattern :supported-formats,
-     :date-formatter and :db"
+               :extract-macros, :date-formatter and :db"
   [blocks content with-id? format {:keys [user-config] :as options}]
   {:pre [(seq blocks) (string? content) (boolean? with-id?) (contains? #{:markdown :org} format)]}
   (let [encoded-content (utf8/encode content)
@@ -605,8 +627,9 @@
                   (recur headings (rest blocks) timestamps properties body))
 
                 (heading-block? block)
-                (let [block (construct-block block properties timestamps body encoded-content format pos-meta with-id? options)]
-                  (recur (conj headings block) (rest blocks) {} {} []))
+                (let [block' (construct-block block properties timestamps body encoded-content format pos-meta with-id? options)
+                      block'' (assoc block' :macros (extract-macros-from-ast (cons block body)))]
+                  (recur (conj headings block'') (rest blocks) {} {} []))
 
                 :else
                 (recur headings (rest blocks) timestamps properties (conj body block))))
@@ -619,70 +642,75 @@
 
 (defn with-parent-and-left
   [page-id blocks]
-  (loop [blocks (map (fn [block] (assoc block :block/level-spaces (:block/level block))) blocks)
-         parents [{:page/id page-id     ; db id or a map {:block/name "xxx"}
-                   :block/level 0
-                   :block/level-spaces 0}]
-         result []]
-    (if (empty? blocks)
-      (map #(dissoc % :block/level-spaces) result)
-      (let [[block & others] blocks
-            level-spaces (:block/level-spaces block)
-            {:block/keys [uuid level parent] :as last-parent} (last parents)
-            parent-spaces (:block/level-spaces last-parent)
-            [blocks parents result]
-            (cond
-              (= level-spaces parent-spaces)        ; sibling
-              (let [block (assoc block
-                                 :block/parent parent
-                                 :block/left [:block/uuid uuid]
-                                 :block/level level)
-                    parents' (conj (vec (butlast parents)) block)
-                    result' (conj result block)]
-                [others parents' result'])
-
-              (> level-spaces parent-spaces)         ; child
-              (let [parent (if uuid [:block/uuid uuid] (:page/id last-parent))
-                    block (cond->
-                            (assoc block
-                                  :block/parent parent
-                                  :block/left parent)
-                            ;; fix block levels with wrong order
-                            ;; For example:
-                            ;;   - a
-                            ;; - b
-                            ;; What if the input indentation is two spaces instead of 4 spaces
-                            (>= (- level-spaces parent-spaces) 1)
-                            (assoc :block/level (inc level)))
-                    parents' (conj parents block)
-                    result' (conj result block)]
-                [others parents' result'])
-
-              (< level-spaces parent-spaces)
-              (cond
-                (some #(= (:block/level-spaces %) (:block/level-spaces block)) parents) ; outdent
-                (let [parents' (vec (filter (fn [p] (<= (:block/level-spaces p) level-spaces)) parents))
-                      left (last parents')
-                      blocks (cons (assoc (first blocks)
-                                          :block/level (dec level)
-                                          :block/left [:block/uuid (:block/uuid left)])
-                                   (rest blocks))]
-                  [blocks parents' result])
-
-                :else
-                (let [[f r] (split-with (fn [p] (<= (:block/level-spaces p) level-spaces)) parents)
-                      left (first r)
-                      parent-id (if-let [block-id (:block/uuid (last f))]
-                                  [:block/uuid block-id]
-                                  page-id)
-                      block (cond->
-                              (assoc block
-                                     :block/parent parent-id
-                                     :block/left [:block/uuid (:block/uuid left)]
-                                     :block/level (:block/level left)
-                                     :block/level-spaces (:block/level-spaces left)))
-
-                      parents' (->> (concat f [block]) vec)
-                      result' (conj result block)]
-                  [others parents' result'])))]
-        (recur blocks parents result)))))
+  (let [[blocks other-blocks] (split-with
+                               (fn [b]
+                                 (not= "macro" (:block/type b)))
+                                blocks)
+        result (loop [blocks (map (fn [block] (assoc block :block/level-spaces (:block/level block))) blocks)
+                      parents [{:page/id page-id     ; db id or a map {:block/name "xxx"}
+                                :block/level 0
+                                :block/level-spaces 0}]
+                      result []]
+                 (if (empty? blocks)
+                   (map #(dissoc % :block/level-spaces) result)
+                   (let [[block & others] blocks
+                         level-spaces (:block/level-spaces block)
+                         {:block/keys [uuid level parent] :as last-parent} (last parents)
+                         parent-spaces (:block/level-spaces last-parent)
+                         [blocks parents result]
+                         (cond
+                           (= level-spaces parent-spaces)        ; sibling
+                           (let [block (assoc block
+                                              :block/parent parent
+                                              :block/left [:block/uuid uuid]
+                                              :block/level level)
+                                 parents' (conj (vec (butlast parents)) block)
+                                 result' (conj result block)]
+                             [others parents' result'])
+
+                           (> level-spaces parent-spaces)         ; child
+                           (let [parent (if uuid [:block/uuid uuid] (:page/id last-parent))
+                                 block (cond->
+                                         (assoc block
+                                                :block/parent parent
+                                                :block/left parent)
+                                         ;; fix block levels with wrong order
+                                         ;; For example:
+                                         ;;   - a
+                                         ;; - b
+                                         ;; What if the input indentation is two spaces instead of 4 spaces
+                                         (>= (- level-spaces parent-spaces) 1)
+                                         (assoc :block/level (inc level)))
+                                 parents' (conj parents block)
+                                 result' (conj result block)]
+                             [others parents' result'])
+
+                           (< level-spaces parent-spaces)
+                           (cond
+                             (some #(= (:block/level-spaces %) (:block/level-spaces block)) parents) ; outdent
+                             (let [parents' (vec (filter (fn [p] (<= (:block/level-spaces p) level-spaces)) parents))
+                                   left (last parents')
+                                   blocks (cons (assoc (first blocks)
+                                                       :block/level (dec level)
+                                                       :block/left [:block/uuid (:block/uuid left)])
+                                                (rest blocks))]
+                               [blocks parents' result])
+
+                             :else
+                             (let [[f r] (split-with (fn [p] (<= (:block/level-spaces p) level-spaces)) parents)
+                                   left (first r)
+                                   parent-id (if-let [block-id (:block/uuid (last f))]
+                                               [:block/uuid block-id]
+                                               page-id)
+                                   block (cond->
+                                           (assoc block
+                                                  :block/parent parent-id
+                                                  :block/left [:block/uuid (:block/uuid left)]
+                                                  :block/level (:block/level left)
+                                                  :block/level-spaces (:block/level-spaces left)))
+
+                                   parents' (->> (concat f [block]) vec)
+                                   result' (conj result block)]
+                               [others parents' result'])))]
+                     (recur blocks parents result))))]
+    (concat result other-blocks)))

+ 15 - 13
deps/graph-parser/src/logseq/graph_parser/extract.cljc

@@ -97,19 +97,21 @@
           ref-pages (atom #{})
           ref-tags (atom #{})
           blocks (map (fn [block]
-                        (let [block-ref-pages (seq (:block/refs block))
-                              page-lookup-ref [:block/name page-name]
-                              block-path-ref-pages (->> (cons page-lookup-ref (seq (:block/path-refs block)))
-                                                        (remove nil?))]
-                          (when block-ref-pages
-                            (swap! ref-pages set/union (set block-ref-pages)))
-                          (-> block
-                              (dissoc :ref-pages)
-                              (assoc :block/format format
-                                     :block/page [:block/name page-name]
-                                     :block/refs block-ref-pages
-                                     :block/path-refs block-path-ref-pages))))
-                      blocks)
+                        (if (contains? #{"macro"} (:block/type block))
+                          block
+                          (let [block-ref-pages (seq (:block/refs block))
+                                page-lookup-ref [:block/name page-name]
+                                block-path-ref-pages (->> (cons page-lookup-ref (seq (:block/path-refs block)))
+                                                          (remove nil?))]
+                            (when block-ref-pages
+                              (swap! ref-pages set/union (set block-ref-pages)))
+                            (-> block
+                                (dissoc :ref-pages)
+                                (assoc :block/format format
+                                       :block/page [:block/name page-name]
+                                       :block/refs block-ref-pages
+                                       :block/path-refs block-path-ref-pages)))))
+                   blocks)
           page-entity (build-page-entity properties file page-name page ref-tags options)
           namespace-pages (let [page (:block/original-name page-entity)]
                             (when (text/namespace-page? page)

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

@@ -45,7 +45,7 @@
    #{:id :custom-id :background-color :background_color :heading :collapsed
      :created-at :updated-at :last-modified-at :created_at :last_modified_at
      :query-table :query-properties :query-sort-by :query-sort-desc :ls-type
-     :hl-type :hl-page :hl-stamp}
+     :hl-type :hl-page :hl-stamp :logseq.macro-name :logseq.macro-arguments}
    (set (map keyword markers))
    @built-in-extended-properties))
 

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

@@ -100,7 +100,7 @@
             :updated-at 47 :created-at 47
             :card-last-score 6 :card-repeats 6 :card-next-schedule 6
             :card-last-interval 6 :card-ease-factor 6 :card-last-reviewed 6
-            :alias 6}
+            :alias 6 :logseq.macro-arguments 94 :logseq.macro-name 94}
            (get-top-block-properties db))
         "Counts for top block properties")
 
@@ -142,7 +142,7 @@
   ;; only increase over time as the docs graph rarely has deletions
   (testing "Counts"
     (is (= 211 (count files)) "Correct file count")
-    (is (= 41290 (count (d/datoms db :eavt))) "Correct datoms count")
+    (is (= 41672 (count (d/datoms db :eavt))) "Correct datoms count")
 
     (is (= 3600
            (ffirst

+ 95 - 62
src/main/frontend/components/block.cljs

@@ -22,6 +22,7 @@
             [frontend.db :as db]
             [frontend.db-mixins :as db-mixins]
             [frontend.db.model :as model]
+            [frontend.db.react :as react]
             [frontend.db.query-dsl :as query-dsl]
             [frontend.db.utils :as db-utils]
             [frontend.extensions.highlight :as highlight]
@@ -728,7 +729,8 @@
   db-mixins/query
   [config id label]
   (when-let [block-id (parse-uuid id)]
-    (let [block (db/pull-block block-id)
+    (let [db-id (:db/id (db/pull [:block/uuid block-id]))
+          block (when db-id (db/pull-block db-id))
           block-type (keyword (get-in block [:block/properties :ls-type]))
           hl-type (get-in block [:block/properties :hl-type])
           repo (state/get-current-repo)]
@@ -1528,7 +1530,8 @@
   [config children collapsed?]
   (let [ref? (:ref? config)
         query? (:custom-query? config)
-        children (and (coll? children) (filter map? children))]
+        children (when (coll? children)
+                   (remove nil? children))]
     (when (and (coll? children)
                (seq children)
                (not collapsed?))
@@ -2424,6 +2427,9 @@
                       tree (tree/blocks->vec-tree blocks (:block/uuid (first blocks)))]
                   (first tree))
                 block)
+        block (if ref?
+                (merge block (db/pull-block (:db/id block)))
+                block)
         {:block/keys [uuid children pre-block? top? refs heading-level level type format content]} block
         config (if navigated? (assoc config :id (str navigating-block)) config)
         block (merge block (block/parse-title-and-body uuid format pre-block? content))
@@ -2442,13 +2448,6 @@
 
                      :else
                      db-collapsed?)
-        children (if (and ref-or-custom-query?
-                          (not collapsed?))
-                   (map
-                     (fn [b] (assoc b
-                                    :block/level (inc (:block/level block))))
-                     (model/sub-block-direct-children repo uuid))
-                   children)
         breadcrumb-show? (:breadcrumb-show? config)
         *show-left-menu? (::show-block-left-menu? state)
         *show-right-menu? (::show-block-right-menu? state)
@@ -2465,7 +2464,9 @@
         edit-input-id (str "edit-block-" blocks-container-id "-" uuid)
         edit? (state/sub [:editor/editing? edit-input-id])
         card? (string/includes? data-refs-self "\"card\"")
-        review-cards? (:review-cards? config)]
+        review-cards? (:review-cards? config)
+        selected-blocks (set (state/get-selection-block-ids))
+        selected? (contains? selected-blocks uuid)]
     [:div.ls-block
      (cond->
        {:id block-id
@@ -2475,7 +2476,7 @@
         :class (str uuid
                     (when pre-block? " pre-block")
                     (when (and card? (not review-cards?)) " shadow-md")
-                    (when (:ui/selected? block) " selected noselect"))
+                    (when selected? " selected noselect"))
         :blockid (str uuid)
         :haschild (str has-child?)}
 
@@ -2549,7 +2550,7 @@
                     ::navigating-block (atom (:block/uuid block)))))
    :should-update (fn [old-state new-state]
                     (let [compare-keys [:block/uuid :block/content :block/parent :block/collapsed?
-                                        :block/properties :block/left :block/children :block/_refs :ui/selected?]
+                                        :block/properties :block/left :block/children :block/_refs]
                           config-compare-keys [:show-cloze?]
                           b1 (second (:rum/args old-state))
                           b2 (second (:rum/args new-state))
@@ -2762,23 +2763,49 @@
            :query-atom query-atom
            :full-text-search? full-text-search?)))
 
+(defn- clear-custom-query!
+  [dsl? query]
+  (let [query (if dsl? (:query query) query)]
+    (state/remove-custom-query-component! query)
+    (db/remove-custom-query! (state/get-current-repo) query)))
+
+(rum/defc query-refresh-button
+  [state query-time {:keys [on-mouse-down]}]
+  (ui/tippy
+   {:html  [:div
+            [:p
+             (when (and query-time (> query-time 80))
+               [:span (str "This query takes " (int query-time) "ms to finish, it's a bit slow so that auto refresh is disabled.")])
+             (when (:full-text-search? state)
+               [:span "Full-text search results will not be refreshed automatically."])]
+            [:p
+             "Click the refresh button instead if you want to see the latest result."]]
+    :interactive     true
+    :popperOptions   {:modifiers {:preventOverflow
+                                  {:enabled           true
+                                   :boundariesElement "viewport"}}}
+    :arrow true}
+   [:a.control.fade-link.ml-1.inline-flex
+    {:style {:margin-top 7}
+     :on-mouse-down on-mouse-down}
+    (ui/icon "refresh" {:style {:font-size 20}})]))
+
 (rum/defcs ^:large-vars/cleanup-todo custom-query* < rum/reactive
   {:will-mount trigger-custom-query!
    :did-mount (fn [state]
                 (when-let [query (last (:rum/args state))]
                   (state/add-custom-query-component! query (:rum/react-component state)))
                 state)
-   :did-remount (fn [_old_state state]
-                  (trigger-custom-query! state))
    :will-unmount (fn [state]
                    (when-let [query (last (:rum/args state))]
-                     (state/remove-custom-query-component! query)
-                     (db/remove-custom-query! (state/get-current-repo) query))
+                     (clear-custom-query! (:dsl-query? (first (:rum/args state)))
+                                          query))
                    state)}
   [state config {:keys [title query view collapsed? children? breadcrumb-show? table-view?] :as q}]
   (let [dsl-query? (:dsl-query? config)
         query-atom (:query-atom state)
         repo (state/get-current-repo)
+        query-time (react/get-query-time query)
         view-fn (if (keyword? view) (state/sub [:config repo :query/views view]) view)
         current-block-uuid (or (:block/uuid (:block config))
                                (:block/uuid config))
@@ -2821,13 +2848,6 @@
          (ui/foldable
           [:div.custom-query-title.flex.justify-between.w-full
            [:div.flex.items-center
-            (when (:full-text-search? state)
-              [:a.control.fade-link.mr-1.inline-flex
-               {:title "Refresh query result"
-                :on-mouse-down (fn [e]
-                                 (util/stop e)
-                                 (trigger-custom-query! state))}
-               (ui/icon "refresh" {:style {:font-size 20}})])
             [:span.title-text (cond
                                 (vector? title) title
                                 (string? title) (inline-text config
@@ -2835,15 +2855,23 @@
                                                              title)
                                 :else title)]
            [:span.opacity-60.text-sm.ml-2.results-count
-            (str (count transformed-query-result) " results")]]
+            (str (count result) " results")]]
 
            ;;insert an "edit" button in the query view
-           (when-not built-in?
-             [:a.opacity-70.hover:opacity-100.svg-small.inline
-              {:on-mouse-down (fn [e]
-                                (util/stop e)
-                                (editor-handler/edit-block! current-block :max (:block/uuid current-block)))}
-              svg/edit])]
+           [:div.flex.items-center
+            (when-not built-in?
+              [:a.opacity-70.hover:opacity-100.svg-small.inline
+               {:on-mouse-down (fn [e]
+                                 (util/stop e)
+                                 (editor-handler/edit-block! current-block :max (:block/uuid current-block)))}
+               svg/edit])
+
+            (when (or (:full-text-search? state)
+                      (and query-time (> query-time 80)))
+              (query-refresh-button state query-time
+                                    {:on-mouse-down (fn [e]
+                                                      (util/stop e)
+                                                      (trigger-custom-query! state))}))]]
           (fn []
             [:div
              (when (and current-block (not view-f) (nil? table-view?))
@@ -2908,7 +2936,10 @@
                :else
                [:div.text-sm.mt-2.ml-2.font-medium.opacity-50 "Empty"])])
           {:default-collapsed? collapsed?
-           :title-trigger? true})]))))
+           :title-trigger? true
+           :on-mouse-down (fn [collapsed?]
+                            (when collapsed?
+                              (clear-custom-query! dsl-query? q)))})]))))
 
 (rum/defc custom-query
   [config q]
@@ -2916,7 +2947,7 @@
    (ui/block-error "Query Error:" {:content (:query q)})
    (ui/lazy-visible
     (fn [] (custom-query* config q))
-    "custom-query")))
+    {:debug-id q})))
 
 (defn admonition
   [config type result]
@@ -3201,11 +3232,6 @@
            (assoc state ::id (str (random-uuid))))}
   [state config flat-blocks blocks->vec-tree]
   (let [db-id (:db/id config)
-        selected-blocks (set (state/get-selection-block-ids))
-        flat-blocks (if (seq selected-blocks)
-                      (map (fn [b]
-                             (assoc b :ui/selected? (contains? selected-blocks (:block/uuid b)))) flat-blocks)
-                      flat-blocks)
         blocks (blocks->vec-tree flat-blocks)
         *loading? (::loading? state)]
     (if-not db-id
@@ -3296,41 +3322,48 @@
    (cond-> option
      (:document/mode? config) (assoc :class "doc-mode"))
    (cond
-     (and (:custom-query? config)
+     (and (or (:ref? config) (:custom-query? config))
           (:group-by-page? config))
      [:div.flex.flex-col
       (let [blocks (sort-by (comp :block/journal-day first) > blocks)]
         (for [[page blocks] blocks]
-          (let [alias? (:block/alias? page)
-                page (db/entity (:db/id page))
-                parent-blocks (group-by :block/parent blocks)]
-            [:div.my-2 (cond-> {:key (str "page-" (:db/id page))}
-                         (:ref? config)
-                         (assoc :class "color-level px-2 sm:px-7 py-2 rounded"))
-             (ui/foldable
-              [:div
-               (page-cp config page)
-               (when alias? [:span.text-sm.font-medium.opacity-50 " Alias"])]
-              (for [[_parent blocks] parent-blocks]
-                (breadcrumb-with-container blocks config))
-              {})])))]
+          (ui/lazy-visible
+           (fn []
+             (let [alias? (:block/alias? page)
+                   page (db/entity (:db/id page))
+                   blocks (tree/non-consecutive-blocks->vec-tree blocks)
+                   parent-blocks (group-by :block/parent blocks)]
+               [:div.my-2 (cond-> {:key (str "page-" (:db/id page))}
+                            (:ref? config)
+                            (assoc :class "color-level px-2 sm:px-7 py-2 rounded"))
+                (ui/foldable
+                 [:div
+                  (page-cp config page)
+                  (when alias? [:span.text-sm.font-medium.opacity-50 " Alias"])]
+                 (for [[parent blocks] parent-blocks]
+                   (rum/with-key
+                     (breadcrumb-with-container blocks config)
+                     (:db/id parent)))
+                 {:debug-id page})])))))]
 
      (and (:group-by-page? config)
           (vector? (first blocks)))
      [:div.flex.flex-col
       (let [blocks (sort-by (comp :block/journal-day first) > blocks)]
         (for [[page blocks] blocks]
-          (let [alias? (:block/alias? page)
-                page (db/entity (:db/id page))]
-            [:div.my-2 (cond-> {:key (str "page-" (:db/id page))}
-                         (:ref? config)
-                         (assoc :class "color-level px-2 sm:px-7 py-2 rounded"))
-             (ui/foldable
-              [:div
-               (page-cp config page)
-               (when alias? [:span.text-sm.font-medium.opacity-50 " Alias"])]
-              (blocks-container blocks config)
-              {})])))]
+          (let [blocks (remove nil? blocks)]
+            (when (seq blocks)
+              (let [alias? (:block/alias? page)
+                    page (db/entity (:db/id page))]
+                [:div.my-2 (cond-> {:key (str "page-" (:db/id page))}
+                             (:ref? config)
+                             (assoc :class "color-level px-2 sm:px-7 py-2 rounded"))
+                 (ui/foldable
+                  [:div
+                   (page-cp config page)
+                   (when alias? [:span.text-sm.font-medium.opacity-50 " Alias"])]
+                  (blocks-container blocks config)
+                  {})])))))]
 
      :else
      (blocks-container blocks config))])

+ 20 - 4
src/main/frontend/components/command_palette.css

@@ -9,10 +9,8 @@
     display: flex;
     flex-direction: column;
 
-    /* width: 80vw; */
-
     @screen lg {
-      width: 820px;
+      width: var(--ls-main-content-max-width);
     }
 
     .menu-link {
@@ -54,5 +52,23 @@
 html.is-ios {
     .cp__palette-main {
         margin-bottom: 0px;
-    } 
+    }
+}
+
+.cards-review .cp__select {
+    &-main {
+        margin: 0;
+        @screen lg {
+            width: 240px;
+        }
+    }
+
+    .input-wrap {
+        height: initial;
+    }
+
+    &-input {
+        padding: 16px;
+        font-size: 16px;
+    }
 }

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

@@ -238,7 +238,7 @@
           (if (srs/card-block? block)
             (ui/menu-link
              {:key      "Preview Card"
-              :on-click #(srs/preview block-id)}
+              :on-click #(srs/preview (:db/id block))}
              "Preview Card")
             (ui/menu-link
              {:key      "Make a Card"

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

@@ -2,6 +2,7 @@
   (:require [clojure.string :as string]
             [frontend.components.page :as page]
             [frontend.components.reference :as reference]
+            [frontend.components.scheduled-deadlines :as scheduled]
             [frontend.date :as date]
             [frontend.db :as db]
             [frontend.db-mixins :as db-mixins]
@@ -56,12 +57,17 @@
 
       (if today?
         (blocks-cp repo page format)
-        (ui/lazy-visible (fn [] (blocks-cp repo page format)) page))
+        (ui/lazy-visible
+         (fn [] (blocks-cp repo page format))
+         {:debug-id (str "journal-blocks " page)}))
 
       {})
 
      (page/today-queries repo today? false)
 
+     (when today?
+       (scheduled/scheduled-and-deadlines page))
+
      (rum/with-key
        (reference/references title)
        (str title "-refs"))]))

+ 7 - 3
src/main/frontend/components/page.cljs

@@ -7,6 +7,7 @@
             [frontend.components.plugins :as plugins]
             [frontend.components.reference :as reference]
             [frontend.components.svg :as svg]
+            [frontend.components.scheduled-deadlines :as scheduled]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
@@ -305,7 +306,7 @@
     (ui/rotating-arrow @*all-collapsed?)]])
 
 ;; A page is just a logical block
-(rum/defcs page < rum/reactive
+(rum/defcs ^:large-vars/cleanup-todo page < rum/reactive
   (rum/local false ::all-collapsed?)
   (rum/local false ::control-show?)
   [state {:keys [repo page-name] :as option}]
@@ -383,9 +384,12 @@
                       page)]
            (page-blocks-cp repo page {:sidebar? sidebar?}))]]
 
-       (when-not block?
+       (when today?
          (today-queries repo today? sidebar?))
 
+       (when today?
+         (scheduled/scheduled-and-deadlines page-name))
+
        (when-not block?
          (tagged-pages repo page-name))
 
@@ -521,7 +525,7 @@
                              (let [value (not excluded-pages?)]
                                (reset! *excluded-pages? value)
                                (set-setting! :excluded-pages? value)))
-                           true)]]              
+                           true)]]
               (when (seq focus-nodes)
                 [:div.flex.flex-col.mb-2
                  [:p {:title "N hops from selected nodes"}

+ 119 - 131
src/main/frontend/components/reference.cljs

@@ -4,7 +4,6 @@
             [frontend.components.content :as content]
             [frontend.components.editor :as editor]
             [frontend.context.i18n :refer [t]]
-            [frontend.date :as date]
             [frontend.db :as db]
             [frontend.db-mixins :as db-mixins]
             [frontend.db.model :as model-db]
@@ -27,7 +26,7 @@
                               (if (= @filter-search "")
                                 references
                                 (search/fuzzy-search references @filter-search :limit 500 :extract-fn first)))]
-    [:div.filters
+    [:div.ls-filters.filters
      [:div.sm:flex.sm:items-start
       [:div.mx-auto.flex-shrink-0.flex.items-center.justify-center.h-12.w-12.rounded-full.bg-gray-200.text-gray-500.sm:mx-0.sm:h-10.sm:w-10
        (ui/icon "filter" {:style {:fontSize 20}})]
@@ -35,28 +34,32 @@
        [:h3#modal-headline.text-lg.leading-6.font-medium "Filter"]
        [:span.text-xs
         "Click to include and shift-click to exclude. Click again to remove."]]]
-     [:div.cp__filters-input-panel.flex (ui/icon "search") [:input.cp__filters-input.w-full
-                                                            {:placeholder (t :linked-references/filter-search)
-                                                             :auto-focus true
-                                                             :on-change (fn [e]
-                                                                          (reset! filter-search (util/evalue e)))}]]
+     [:div.cp__filters-input-panel.flex
+      (ui/icon "search")
+      [:input.cp__filters-input.w-full
+       {:placeholder (t :linked-references/filter-search)
+        :auto-focus true
+        :on-change (fn [e]
+                     (reset! filter-search (util/evalue e)))}]]
      (when (seq filtered-references)
        (let [filters (rum/react filters-atom)]
          [:div.mt-5.sm:mt-4.sm:flex.sm.gap-1.flex-wrap
           (for [[ref-name ref-count] filtered-references]
-            (let [lc-reference (string/lower-case ref-name)
-                  filtered (get filters lc-reference)
-                  color (condp = filtered
-                          true "text-green-400"
-                          false "text-red-400"
-                          nil)]
-              [:button.border.rounded.px-1.mb-1.mr-1.select-none {:key ref-name :class color :style {:border-color "currentColor"}
-                                                                  :on-click (fn [e]
-                                                                              (swap! filters-atom #(if (nil? (get filters lc-reference))
-                                                                                                     (assoc % lc-reference (not (.-shiftKey e)))
-                                                                                                     (dissoc % lc-reference)))
-                                                                              (page-handler/save-filter! page-name @filters-atom))}
-               ref-name [:sub " " ref-count]]))]))]))
+            (when ref-name
+              (let [lc-reference (string/lower-case ref-name)
+                    filtered (get filters lc-reference)
+                    color (condp = filtered
+                            true "text-green-400"
+                            false "text-red-400"
+                            nil)]
+                [:button.border.rounded.px-1.mb-1.mr-1.select-none
+                 {:key ref-name :class color :style {:border-color "currentColor"}
+                  :on-click (fn [e]
+                              (swap! filters-atom #(if (nil? (get filters lc-reference))
+                                                     (assoc % lc-reference (not (.-shiftKey e)))
+                                                     (dissoc % lc-reference)))
+                              (page-handler/save-filter! page-name @filters-atom))}
+                 ref-name [:sub " " ref-count]])))]))]))
 
 (defn filter-dialog
   [filters-atom references page-name]
@@ -65,133 +68,118 @@
 
 (rum/defc block-linked-references < rum/reactive db-mixins/query
   [block-id]
-  (let [refed-blocks-ids (model-db/get-referenced-blocks-ids (str block-id))]
-    (when (seq refed-blocks-ids)
-      (let [ref-blocks (db/get-block-referenced-blocks block-id)
-            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})]))))
+  (let [ref-blocks (db/get-block-referenced-blocks block-id)
+        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})]))
 
-(defn- scheduled-or-deadlines?
-  [page-name]
-  (and (date/valid-journal-title? (string/capitalize page-name))
-       (not (true? (state/scheduled-deadlines-disabled?)))
-       (= (string/lower-case page-name) (string/lower-case (date/journal-name)))))
+(rum/defc references-inner < rum/reactive db-mixins/query
+  [page-name block-id filters *filtered-ref-blocks ref-pages]
+  [:div.references-blocks
+   (let [ref-blocks (if block-id
+                      (db/get-block-referenced-blocks block-id)
+                      (db/get-page-referenced-blocks page-name))
+         filtered-ref-blocks (if block-id
+                               ref-blocks
+                               (block-handler/get-filtered-ref-blocks ref-blocks filters ref-pages))
+         ref-hiccup (block/->hiccup filtered-ref-blocks
+                                    {:id page-name
+                                     :ref? true
+                                     :breadcrumb-show? true
+                                     :group-by-page? true
+                                     :editor-box editor/box
+                                     :filters filters}
+                                    {})]
+     (reset! *filtered-ref-blocks filtered-ref-blocks)
+     (content/content page-name {:hiccup ref-hiccup}))])
 
-(rum/defcs references* < rum/reactive db-mixins/query
-  (rum/local nil ::n-ref)
+(rum/defc references-cp
+  [repo page-entity page-name block-id filters-atom filter-state n-ref]
+  (let [threshold (state/get-linked-references-collapsed-threshold)
+        default-collapsed? (>= n-ref threshold)
+        filters (when (seq filter-state)
+                  (-> (group-by second filter-state)
+                      (update-vals #(map first %))))
+        *filtered-ref-blocks (atom nil)
+        *collapsed? (atom nil)
+        ref-pages (when-not block-id
+                    (block-handler/get-blocks-refed-pages repo page-entity))]
+    (ui/foldable
+     [:div.flex.flex-row.flex-1.justify-between.items-center
+      [:h2.font-bold.opacity-50 (str n-ref " Linked Reference"
+                                     (when (> n-ref 1) "s"))]
+      [:a.filter.fade-link
+       {:title "Filter"
+        :on-mouse-over (fn [_e]
+                         (when @*collapsed? ; collapsed
+                           ;; expand
+                           (reset! @*collapsed? false)))
+        :on-mouse-down (fn [e]
+                         (util/stop-propagation e))
+        :on-click (fn []
+                    (let [ref-pages (map :block/original-name ref-pages)
+                          references (frequencies ref-pages)]
+                      (state/set-modal! (filter-dialog filters-atom references page-name)
+                                        {:center? true})))}
+       (ui/icon "filter" {:class (cond
+                                   (empty? filter-state)
+                                   ""
+                                   (every? true? (vals filter-state))
+                                   "text-green-400"
+                                   (every? false? (vals filter-state))
+                                   "text-red-400"
+                                   :else
+                                   "text-yellow-400")
+                          :style {:fontSize 24}})]]
+
+     (fn []
+       (references-inner page-name block-id filters *filtered-ref-blocks ref-pages))
+
+     {:default-collapsed? default-collapsed?
+      :title-trigger? true
+      :init-collapsed (fn [collapsed-atom]
+                        (reset! *collapsed? collapsed-atom))})))
+
+(rum/defcs references* < rum/reactive
   {:init (fn [state]
            (let [page-name (first (:rum/args state))
                  filters (when page-name
                            (atom (page-handler/get-filters (string/lower-case page-name))))]
              (assoc state ::filters filters)))}
-  [state page-name refed-blocks-ids]
+  [state page-name]
   (when page-name
     (let [page-name (string/lower-case page-name)
+          page-entity (db/entity [:block/name page-name])
           repo (state/get-current-repo)
-          threshold (state/get-linked-references-collapsed-threshold)
-          *n-ref (::n-ref state)
-          n-ref (or (rum/react *n-ref) (count refed-blocks-ids))
-          default-collapsed? (>= (count refed-blocks-ids) threshold)
           filters-atom (get state ::filters)
           filter-state (rum/react filters-atom)
           block-id (parse-uuid page-name)
-          page-name (string/lower-case page-name)
-          scheduled-or-deadlines (when (scheduled-or-deadlines? page-name)
-                                   (db/get-date-scheduled-or-deadlines (string/capitalize page-name)))]
-      (when (or (seq refed-blocks-ids)
-                (seq scheduled-or-deadlines)
-                (seq filter-state))
+          id (if block-id
+               (:db/id (db/pull [:block/uuid block-id]))
+               (:db/id page-entity))
+          n-ref (model-db/get-linked-references-count id)]
+      (when (or (seq filter-state) (> n-ref 0))
         [:div.references.flex-1.flex-row
          [:div.content.pt-6
-          (when (seq scheduled-or-deadlines)
-            (ui/foldable
-             [:h2.font-bold.opacity-50 "SCHEDULED AND DEADLINE"]
-             [:div.references-blocks.mb-6
-              (let [ref-hiccup (block/->hiccup scheduled-or-deadlines
-                                               {:id (str page-name "-agenda")
-                                                :ref? true
-                                                :group-by-page? true
-                                                :editor-box editor/box}
-                                               {})]
-                (content/content page-name {:hiccup ref-hiccup}))]
-             {:title-trigger? true}))
-
-          (when (seq refed-blocks-ids)
-            (ui/foldable
-             [:div.flex.flex-row.flex-1.justify-between.items-center
-              [:h2.font-bold.opacity-50 (str n-ref " Linked Reference"
-                                             (when (> n-ref 1) "s"))]
-              [:a.filter.fade-link
-               {:title "Filter"
-                :on-mouse-down (fn [e] (util/stop-propagation e))
-                :on-click (fn []
-                            (let [ref-blocks (if block-id
-                                               (db/get-block-referenced-blocks block-id {:filter? true})
-                                               (db/get-page-referenced-blocks page-name {:filter? true}))
-                                  references (db/get-page-linked-refs-refed-pages repo page-name)
-                                  references (->>
-                                              (concat ref-blocks references)
-                                              (remove nil?)
-                                              (frequencies))]
-                              (state/set-modal! (filter-dialog filters-atom references page-name)
-                                                {:center? true})))}
-               (ui/icon "filter" {:class (cond
-                                           (empty? filter-state)
-                                           ""
-                                           (every? true? (vals filter-state))
-                                           "text-green-400"
-                                           (every? false? (vals filter-state))
-                                           "text-red-400"
-                                           :else
-                                           "text-yellow-400")
-                                  :style {:fontSize 24}})]]
-
-             (fn []
-               (let [ref-blocks (if block-id
-                                  (db/get-block-referenced-blocks block-id)
-                                  (db/get-page-referenced-blocks page-name))
-                     filters (when (seq filter-state)
-                               (-> (group-by second filter-state)
-                                   (update-vals #(map first %))))
-                     filtered-ref-blocks (block-handler/filter-blocks repo ref-blocks filters true)
-                     n-ref (apply +
-                                  (for [[_ rfs] filtered-ref-blocks]
-                                    (count rfs)))]
-                 (reset! *n-ref n-ref)
-                 [:div.references-blocks
-                  (let [ref-hiccup (block/->hiccup filtered-ref-blocks
-                                                   {:id page-name
-                                                    :ref? true
-                                                    :breadcrumb-show? true
-                                                    :group-by-page? true
-                                                    :editor-box editor/box
-                                                    :filters filters}
-                                                   {})]
-                    (content/content page-name
-                                     {:hiccup ref-hiccup}))]))
-
-             {:default-collapsed? default-collapsed?
-              :title-trigger? true}))]]))))
+          (references-cp repo page-entity page-name block-id
+                         filters-atom filter-state n-ref)]]))))
 
-(rum/defc references < rum/reactive db-mixins/query
+(rum/defc references
   [page-name]
-  (let [refed-blocks-ids (when page-name (model-db/get-referenced-blocks-ids page-name))]
-    (when (or (seq refed-blocks-ids)
-              (scheduled-or-deadlines? page-name))
-      (ui/catch-error
-       (ui/component-error "Linked References: Unexpected error")
-       (ui/lazy-visible
-        (fn []
-          (references* page-name refed-blocks-ids))
-        (str "ref-" page-name))))))
+  (ui/catch-error
+   (ui/component-error "Linked References: Unexpected error")
+   (ui/lazy-visible
+    (fn []
+      (references* page-name))
+    {:trigger-once? true
+     :debug-id (str page-name " references")})))
 
 (rum/defcs unlinked-references-aux
   < rum/reactive db-mixins/query
@@ -233,4 +221,4 @@
               "Unlinked References")]
            (fn [] (unlinked-references-aux page-name n-ref))
            {:default-collapsed? true
-            :title-trigger? true})]]))))
+            :title-trigger? true})]]))))

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

@@ -9,4 +9,4 @@
   border-radius: 0.25rem;
   padding-left: 0.5rem;
   align-items: center;
-}
+}

+ 34 - 0
src/main/frontend/components/scheduled_deadlines.cljs

@@ -0,0 +1,34 @@
+(ns frontend.components.scheduled-deadlines
+  (:require [frontend.date :as date]
+            [frontend.state :as state]
+            [frontend.ui :as ui]
+            [frontend.components.content :as content]
+            [frontend.components.block :as block]
+            [clojure.string :as string]
+            [frontend.components.editor :as editor]
+            [rum.core :as rum]
+            [frontend.db :as db]
+            [frontend.db-mixins :as db-mixins]))
+
+(defn- scheduled-or-deadlines?
+  [page-name]
+  (and (date/valid-journal-title? (string/capitalize page-name))
+       (not (true? (state/scheduled-deadlines-disabled?)))
+       (= (string/lower-case page-name) (string/lower-case (date/journal-name)))))
+
+(rum/defc scheduled-and-deadlines < rum/reactive db-mixins/query
+  [page-name]
+  (let [scheduled-or-deadlines (when (scheduled-or-deadlines? page-name)
+                                 (db/get-date-scheduled-or-deadlines (string/capitalize page-name)))]
+    (when (seq scheduled-or-deadlines)
+      (ui/foldable
+       [:h2.font-bold.opacity-50 "SCHEDULED AND DEADLINE"]
+       [:div.scheduled-deadlines.references-blocks.mb-6
+        (let [ref-hiccup (block/->hiccup scheduled-or-deadlines
+                                         {:id (str page-name "-agenda")
+                                          :ref? true
+                                          :group-by-page? true
+                                          :editor-box editor/box}
+                                         {})]
+          (content/content page-name {:hiccup ref-hiccup}))]
+       {:title-trigger? true}))))

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

@@ -138,7 +138,7 @@
       (if page
         (if (or collapsed? long-page?)
           (route/redirect-to-page! block-uuid)
-          (route/redirect-to-page! (:block/name page) (str "ls-block-" (:block/uuid data))))
+          (route/redirect-to-page! (:block/name page) {:anchor (str "ls-block-" (:block/uuid data))}))
         ;; search indice outdated
         (println "[Error] Block page missing: "
                  {:block-id block-uuid
@@ -354,7 +354,8 @@
         timeout 300
         in-page-search? (= search-mode :page)]
     [:div.cp__palette.cp__palette-main
-     [:div.input-wrap
+     [:div.ls-search
+      [:div.input-wrap
       [:input.cp__palette-input.w-full
        {:type          "text"
         :auto-focus    true
@@ -387,10 +388,10 @@
                                             (search-handler/search (state/get-current-repo) value opts)
                                             (search-handler/search (state/get-current-repo) value)))
                                         timeout))))))}]]
-     [:div.search-results-wrap
-      (if (seq search-result)
-        (search-auto-complete search-result search-q false)
-        (recent-search-and-pages in-page-search?))]]))
+      [:div.search-results-wrap
+       (if (seq search-result)
+         (search-auto-complete search-result search-q false)
+         (recent-search-and-pages in-page-search?))]]]))
 
 (rum/defc more < rum/reactive
   [route]

+ 21 - 12
src/main/frontend/components/select.cljs

@@ -17,13 +17,18 @@
             [reitit.frontend.easy :as rfe]))
 
 (rum/defc render-item
-  [{:keys [id value]} chosen?]
-  [:div.inline-grid.grid-cols-4.gap-x-4.w-full
-   {:class (when chosen? "chosen")}
-   [:span.col-span-3 value]
-   [:div.col-span-1.justify-end.tip.flex
-    (when id
-      [:code.opacity-20.bg-transparent id])]])
+  [result chosen?]
+  (if (map? result)
+    (let [{:keys [id value]} result]
+      [:div.inline-grid.grid-cols-4.gap-x-4.w-full
+       {:class (when chosen? "chosen")}
+       [:span.col-span-3 value]
+       [:div.col-span-1.justify-end.tip.flex
+        (when id
+          [:code.opacity-20.bg-transparent id])]])
+    [:div.inline-grid.grid-cols-4.gap-x-4.w-full
+     {:class (when chosen? "chosen")}
+     [:span.col-span-3 result]]))
 
 (rum/defcs select <
   (shortcut/disable-all-shortcuts)
@@ -31,27 +36,31 @@
   {:will-unmount (fn [state]
                    (state/set-state! [:ui/open-select] nil)
                    state)}
-  [state {:keys [items limit on-chosen empty-placeholder prompt-key]
+  [state {:keys [items limit on-chosen empty-placeholder
+                 prompt-key input-default-placeholder close-modal?
+                 extract-fn]
           :or {limit 100
                prompt-key :select/default-prompt
-               empty-placeholder (fn [_t] [:div])}}]
+               empty-placeholder (fn [_t] [:div])
+               close-modal? true
+               extract-fn :value}}]
   (let [input (::input state)]
     [:div.cp__select.cp__select-main
      [:div.input-wrap
       [:input.cp__select-input.w-full
        {:type        "text"
-        :placeholder (t prompt-key)
+        :placeholder (or input-default-placeholder (t prompt-key))
         :auto-focus  true
         :value       @input
         :on-change   (fn [e] (reset! input (util/evalue e)))}]]
 
      [:div.item-results-wrap
       (ui/auto-complete
-       (search/fuzzy-search items @input :limit limit :extract-fn :value)
+       (search/fuzzy-search items @input :limit limit :extract-fn extract-fn)
        {:item-render render-item
         :class       "cp__select-results"
         :on-chosen   (fn [x]
-                       (state/close-modal!)
+                       (when close-modal? (state/close-modal!))
                        (on-chosen x))
         :empty-placeholder (empty-placeholder t)})]]))
 

+ 7 - 5
src/main/frontend/components/sidebar.cljs

@@ -67,7 +67,7 @@
     (< delta 14)))
 
 (rum/defc page-name
-  [name icon]
+  [name icon recent?]
   (let [original-name (db-model/get-page-original-name name)]
     [:a {:on-click (fn [e]
                      (let [name (util/safe-page-name-sanity-lc name)]
@@ -77,7 +77,7 @@
                             (state/get-current-repo)
                             (:db/id page-entity)
                             :page))
-                         (route-handler/redirect-to-page! name))))}
+                         (route-handler/redirect-to-page! name {:click-from-recent? recent?}))))}
      [:span.page-icon icon]
      (pdf-assets/fix-local-asset-filename original-name)]))
 
@@ -117,7 +117,7 @@
                                                    :up? (move-up? e)})
                  (reset! up? nil)
                  (reset! dragging-over nil))}
-     (page-name name icon)]))
+     (page-name name icon false)]))
 
 (rum/defc favorites < rum/reactive
   [t]
@@ -167,7 +167,7 @@
            {:key name
             :title name
             :data-ref name}
-           (page-name name (get-page-icon entity))]))])))
+           (page-name name (get-page-icon entity) true)]))])))
 
 (rum/defcs flashcards < db-mixins/query rum/reactive
   {:did-mount (fn [state]
@@ -177,7 +177,9 @@
   (let [num (state/sub :srs/cards-due-count)]
     [:a.item.group.flex.items-center.px-2.py-2.text-sm.font-medium.rounded-md
      {:class (util/classnames [{:active srs-open?}])
-      :on-click #(state/pub-event! [:modal/show-cards])}
+      :on-click #(do
+                   (srs/update-cards-due-count!)
+                   (state/pub-event! [:modal/show-cards]))}
      (ui/icon "infinity")
      [:span.flex-1 (t :right-side-bar/flashcards)]
      (when (and num (not (zero? num)))

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

@@ -46,9 +46,9 @@
   get-custom-css get-date-scheduled-or-deadlines
   get-file-blocks get-file-last-modified-at get-file get-file-page get-file-page-id file-exists?
   get-files get-files-blocks get-files-full get-journals-length
-  get-latest-journals get-page get-page-alias get-page-alias-names get-paginated-blocks get-page-linked-refs-refed-pages
+  get-latest-journals get-page get-page-alias get-page-alias-names get-paginated-blocks
   get-page-blocks-count get-page-blocks-no-cache get-page-file get-page-format get-page-properties
-  get-page-referenced-blocks get-page-referenced-pages get-page-unlinked-references get-page-referenced-blocks-no-cache
+  get-page-referenced-blocks get-page-referenced-blocks-full get-page-referenced-pages get-page-unlinked-references get-page-referenced-blocks-no-cache
   get-all-pages get-pages get-pages-relation get-pages-that-mentioned-page get-public-pages get-tag-pages
   journal-page? page-alias-set pull-block
   set-file-last-modified-at! page-empty? page-exists? page-empty-or-dummy? get-alias-source-page
@@ -121,9 +121,6 @@
   [repo conn]
   (d/listen! conn :persistence
              (fn [tx-report]
-               ;; reactive components
-               (react/refresh! repo tx-report)
-
                (when (and
                       (not config/publishing?)
                       (not (:new-graph? (:tx-meta tx-report)))) ; skip initial txs

+ 107 - 124
src/main/frontend/db/model.cljs

@@ -59,18 +59,13 @@
 
 (defn pull-block
   [id]
-  (let [repo (state/get-current-repo)]
-    (when (conn/get-db repo)
-      (->
-       (react/q repo [:frontend.db.react/block id] {}
-                '[:find [(pull ?block ?block-attrs) ...]
-                  :in $ ?id ?block-attrs
-                  :where
-                  [?block :block/uuid ?id]]
-                id
-                block-attrs)
-       react
-       first))))
+  (when-let [repo (state/get-current-repo)]
+    (->
+     (react/q repo [:frontend.db.react/block id]
+       {:query-fn (fn [_]
+                    (db-utils/pull (butlast block-attrs) id))}
+       nil)
+     react)))
 
 (defn get-original-name
   [page-entity]
@@ -865,14 +860,14 @@
   (when-let [db (conn/get-db repo)]
     (let [eid (:db/id (db-utils/entity repo [:block/uuid block-uuid]))]
       (->> (d/q
-            '[:find ?id
-              :in $ ?p %
-              :where
-              (child ?p ?c)
-              [?c :block/uuid ?id]]
-            db
-            eid
-            rules)
+             '[:find ?id
+               :in $ ?p %
+               :where
+               (child ?p ?c)
+               [?c :block/uuid ?id]]
+             db
+             eid
+             rules)
            (apply concat)))))
 
 (defn get-block-immediate-children
@@ -889,27 +884,13 @@
          block-uuid)
         (sort-by-left (db-utils/entity [:block/uuid block-uuid])))))
 
-(defn sub-block-direct-children
-  "Doesn't include nested children."
-  [repo block-uuid]
-  (when-let [db (conn/get-db repo)]
-    (-> (react/q repo [:frontend.db.react/block-direct-children block-uuid] {}
-                 '[:find [(pull ?b [*]) ...]
-                   :in $ ?parent-id
-                   :where
-                   [?parent :block/uuid ?parent-id]
-                   [?b :block/parent ?parent]]
-                 block-uuid)
-        react
-        (sort-by-left (db-utils/entity [:block/uuid block-uuid])))))
-
 (defn get-block-children
   "Including nested children."
   [repo block-uuid]
-  (let [ids (get-block-children-ids repo block-uuid)
-        ids (map (fn [id] [:block/uuid id]) ids)]
+  (let [ids (get-block-children-ids repo block-uuid)]
     (when (seq ids)
-      (db-utils/pull-many repo '[*] ids))))
+      (let [ids' (map (fn [id] [:block/uuid id]) ids)]
+        (db-utils/pull-many repo '[*] ids')))))
 
 ;; TODO: use the tree directly
 (defn- flatten-tree
@@ -1077,24 +1058,6 @@
                      pages)]
       (mapv (fn [page] [page (get-page-alias repo page)]) ref-pages))))
 
-(defn get-page-linked-refs-refed-pages
-  [repo page]
-  (when-let [db (conn/get-db repo)]
-    (->
-     (d/q
-      '[:find [?ref-page ...]
-        :in $ % ?page
-        :where
-        [?p :block/name ?page]
-        [?b :block/path-refs ?p]
-        [?b :block/refs ?other-p]
-        [(not= ?p ?other-p)]
-        [?other-p :block/original-name ?ref-page]]
-      db
-      rules
-      (util/safe-page-name-sanity-lc page))
-     (distinct))))
-
 (def ns-char "/")
 (def ns-re #"/")
 
@@ -1202,59 +1165,89 @@
           page-id)
      (flatten))))
 
-(defn get-page-referenced-blocks
+(defn get-page-referenced-blocks-full
   ([page]
-   (get-page-referenced-blocks (state/get-current-repo) page {:filter? false}))
+   (get-page-referenced-blocks-full (state/get-current-repo) page nil))
   ([page options]
-   (get-page-referenced-blocks (state/get-current-repo) page options))
+   (get-page-referenced-blocks-full (state/get-current-repo) page options))
   ([repo page options]
    (when repo
      (when (conn/get-db repo)
        (let [page-id (:db/id (db-utils/entity [:block/name (util/safe-page-name-sanity-lc page)]))
              pages (page-alias-set repo page)
-             aliases (set/difference pages #{page-id})
-             query-result (react/q repo
-                                   [:frontend.db.react/page<-blocks-or-block<-blocks page-id]
-                                   {}
-                                   '[:find [(pull ?block ?block-attrs) ...]
-                                     :in $ [?ref-page ...] ?block-attrs
-                                     :where
-                                     [?block :block/refs ?ref-page]]
-                                   pages
-                                   (butlast block-attrs))
-             result (if (not (options :filter?)) (->> query-result
-                                           react
-                                           (remove (fn [block]
-                                                     (= page-id (:db/id (:block/page block)))))
-                                           (sort-by-left-recursive)
-                                           db-utils/group-by-page
-                                           (map (fn [[k blocks]]
-                                                  (let [k (if (contains? aliases (:db/id k))
-                                                            (assoc k :block/alias? true)
-                                                            k)]
-                                                    [k blocks]))))
-
-                        (map (comp :block/original-name :block/page) (->> query-result
-                                                                          react
-                                                                          (remove (fn [block]
-                                                                                    (= page-id (:db/id (:block/page block))))) (sort-by-left-recursive))))]
-         result)))))
-
-(defn get-page-referenced-blocks-ids
-  "Faster and can be used for pagination later."
+             aliases (set/difference pages #{page-id})]
+         (->>
+          (react/q repo
+            [:frontend.db.react/page<-blocks-or-block<-blocks page-id]
+            {}
+            '[:find [(pull ?block ?block-attrs) ...]
+              :in $ [?ref-page ...] ?block-attrs
+              :where
+              [?block :block/path-refs ?ref-page]]
+            pages
+            (butlast block-attrs))
+          react
+          (remove (fn [block] (= page-id (:db/id (:block/page block)))))
+          db-utils/group-by-page
+          (map (fn [[k blocks]]
+                 (let [k (if (contains? aliases (:db/id k))
+                           (assoc k :block/alias? true)
+                           k)]
+                   [k blocks])))))))))
+
+(defn get-page-referenced-blocks
   ([page]
-   (get-page-referenced-blocks-ids (state/get-current-repo) page))
-  ([repo page]
+   (get-page-referenced-blocks (state/get-current-repo) page nil))
+  ([page options]
+   (get-page-referenced-blocks (state/get-current-repo) page options))
+  ([repo page options]
    (when repo
-     (when-let [db (conn/get-db repo)]
-       (let [pages (page-alias-set repo page)]
-         (d/q
-          '[:find ?block
-            :in $ [?ref-page ...]
-            :where
-            [?block :block/refs ?ref-page]]
-          db
-          pages))))))
+     (when (conn/get-db repo)
+       (let [page-id (:db/id (db-utils/entity [:block/name (util/safe-page-name-sanity-lc page)]))
+             pages (page-alias-set repo page)
+             aliases (set/difference pages #{page-id})]
+         (->>
+          (react/q repo
+            [:frontend.db.react/page<-blocks-or-block<-blocks page-id]
+            {:use-cache? false
+             :query-fn (fn []
+                         (let [entities (mapcat (fn [id]
+                                                  (:block/_path-refs (db-utils/entity id))) pages)
+                               blocks (map (fn [e] {:block/parent (:block/parent e)
+                                                    :block/left (:block/left e)
+                                                    :block/page (:block/page e)}) entities)]
+                           {:entities entities
+                            :blocks blocks}))}
+            nil)
+          react
+          :entities
+          (remove (fn [block] (= page-id (:db/id (:block/page block)))))
+          db-utils/group-by-page
+          (map (fn [[k blocks]]
+                 (let [k (if (contains? aliases (:db/id k))
+                           (assoc k :block/alias? true)
+                           k)]
+                   [k blocks])))))))))
+
+(defn get-linked-references-count
+  [id]
+  (when-let [block (db-utils/entity id)]
+    (let [repo (state/get-current-repo)
+          page? (:block/name block)
+          result (if page?
+                   (let [pages (page-alias-set repo (:block/name block))]
+                     (d/q
+                       '[:find [?block ...]
+                         :in $ [?ref-page ...] ?id
+                         :where
+                         [?block :block/refs ?ref-page]
+                         [?block :block/page ?p]
+                         [(not= ?p ?id)]]
+                       (conn/get-db repo)
+                       pages
+                       id))
+                   (:block/_refs block))]
+      (count result))))
 
 (defn get-date-scheduled-or-deadlines
   [journal-title]
@@ -1320,7 +1313,7 @@
 ;; see https://github.com/tonsky/datascript/issues/130#issuecomment-169520434
 (defn get-block-referenced-blocks
   ([block-uuid]
-   (get-block-referenced-blocks block-uuid {:filter? false}))
+   (get-block-referenced-blocks block-uuid {}))
   ([block-uuid options]
    (when-let [repo (state/get-current-repo)]
      (when (conn/get-db repo)
@@ -1337,30 +1330,7 @@
                                         block-attrs)
                                react
                                (sort-by-left-recursive))]
-         (if (options :filter?)
-           (map (comp :block/original-name :block/page) query-result)
-           (db-utils/group-by-page query-result)))))))
-
-(defn get-block-referenced-blocks-ids
-  [block-uuid]
-  (when-let [repo (state/get-current-repo)]
-    (let [block (db-utils/entity [:block/uuid block-uuid])]
-      (->> (react/q repo [:frontend.db.react/block<-block-ids
-                          (:db/id block)] {}
-                    '[:find ?ref-block
-                      :in $ ?block-uuid ?block-attrs
-                      :where
-                      [?block :block/uuid ?block-uuid]
-                      [?ref-block :block/refs ?block]]
-                    block-uuid
-                    block-attrs)
-           react))))
-
-(defn get-referenced-blocks-ids
-  [page-name-or-block-uuid]
-  (if-let [id (parse-uuid (str page-name-or-block-uuid))]
-    (get-block-referenced-blocks-ids id)
-    (get-page-referenced-blocks-ids page-name-or-block-uuid)))
+         (db-utils/group-by-page query-result))))))
 
 (defn journal-page?
   "sanitized page-name only"
@@ -1702,3 +1672,16 @@
                         (remove false?)
                         (remove nil?))]
     orphaned-pages))
+
+(defn get-macro-blocks
+  [repo macro-name]
+  (d/q
+    '[:find [(pull ?b [*]) ...]
+      :in $ ?macro-name
+      :where
+      [?b :block/type "macro"]
+      [?b :block/properties ?properties]
+      [(get ?properties :logseq.macro-name) ?name]
+      [(= ?name ?macro-name)]]
+    (conn/get-db repo)
+    macro-name))

+ 13 - 10
src/main/frontend/db/query_custom.cljs

@@ -11,15 +11,16 @@
 ;; FIXME: what if users want to query other attributes than block-attrs?
 (defn- replace-star-with-block-attrs!
   [l]
-  (walk/postwalk
-   (fn [f]
-     (if (and (list? f)
-                (= 'pull (first f))
-                (= '?b (second f))
-                (= '[*] (nth f 2)))
-       `(~'pull ~'?b ~model/block-attrs)
-       f))
-   l))
+  (let [block-attrs (butlast model/block-attrs)]
+    (walk/postwalk
+    (fn [f]
+      (if (and (list? f)
+               (= 'pull (first f))
+               (= '?b (second f))
+               (= '[*] (nth f 2)))
+        `(~'pull ~'?b ~block-attrs)
+        f))
+    l)))
 
 (defn- add-rules-to-query
   "Searches query's :where for rules and adds them to query if used"
@@ -58,7 +59,9 @@
   ([query query-opts]
    (custom-query (state/get-current-repo) query query-opts))
   ([repo query query-opts]
-   (let [query' (replace-star-with-block-attrs! query)]
+   (let [query' (replace-star-with-block-attrs! query)
+         query-opts (if (:query-string query-opts) query-opts
+                        (assoc query-opts :query-string (str query)))]
      (if (or (list? (:query query'))
              (not= :find (first (:query query')))) ; dsl query
        (query-dsl/custom-query repo query' query-opts)

+ 10 - 10
src/main/frontend/db/query_dsl.cljs

@@ -528,18 +528,18 @@ Some bindings in this fn:
 ;; ========
 
 (defn query-wrapper
-  [where blocks?]
-  (let [q (if blocks?                   ; FIXME: it doesn't need to be either blocks or pages
-            `[:find (~'pull ~'?b ~model/block-attrs)
+  [where {:keys [blocks? block-attrs]}]
+  (let [block-attrs (or block-attrs (butlast model/block-attrs))
+        q (if blocks?                   ; FIXME: it doesn't need to be either blocks or pages
+            `[:find (~'pull ~'?b ~block-attrs)
               :in ~'$ ~'%
               :where]
             '[:find (pull ?p [*])
               :in $ %
-              :where])
-        result (if (coll? (first where))
-                 (apply conj q where)
-                 (conj q where))]
-    result))
+              :where])]
+    (if (coll? (first where))
+      (apply conj q where)
+      (conj q where))))
 
 (defn query
   "Runs a dsl query with query as a string. Primary use is from '{{query }}'"
@@ -547,7 +547,7 @@ Some bindings in this fn:
   (when (and (string? query-string) (not= "\"\"" query-string))
     (let [query-string' (template/resolve-dynamic-template! query-string)
           {:keys [query rules sort-by blocks? sample]} (parse query-string')]
-      (when-let [query' (some-> query (query-wrapper blocks?))]
+      (when-let [query' (some-> query (query-wrapper {:blocks? blocks?}))]
         (let [sort-by (or sort-by identity)
               random-samples (if @sample
                                (fn [col]
@@ -567,7 +567,7 @@ Some bindings in this fn:
   (when (seq (:query query-m))
     (let [query-string (template/resolve-dynamic-template! (pr-str (:query query-m)))
           {:keys [query sort-by blocks? rules]} (parse query-string)]
-      (when-let [query' (some-> query (query-wrapper blocks?))]
+      (when-let [query' (some-> query (query-wrapper {:blocks? blocks?}))]
         (query-react/react-query repo
                            (merge
                             query-m

+ 2 - 14
src/main/frontend/db/query_react.cljs

@@ -51,15 +51,6 @@
     :else
     input))
 
-(defn- remove-nested-children-blocks
-  [blocks]
-  (let [ids (set (map :db/id blocks))]
-    (->> blocks
-         (remove
-          (fn [block]
-            (let [id (:db/id (:block/parent block))]
-              (contains? ids id)))))))
-
 (defn custom-query-result-transform
   [query-result remove-blocks q]
   (try
@@ -72,10 +63,7 @@
                                               (contains? remove-blocks (:block/uuid h)))
                                             result))
                                   result)]
-                     (some->> result
-                              remove-nested-children-blocks
-                              (model/sort-by-left-recursive)
-                              (model/with-pages)))
+                     (model/with-pages result))
                    result)
           result-transform-fn (:result-transform q)
           repo (state/get-current-repo)]
@@ -135,7 +123,7 @@
                          rules
                          (conj rules))
           repo (or repo (state/get-current-repo))
-          k [:custom query']]
+          k [:custom (or (:query-string query') query')]]
       (pprint "inputs (post-resolution):" resolved-inputs)
       (pprint "query-opts:" query-opts)
       (pprint (str "time elapsed: " (.toFixed (- (.now js/performance) start-time) 2) "ms"))

+ 81 - 64
src/main/frontend/db/react.cljs

@@ -95,6 +95,11 @@
     (let [new-result' (f @result-atom)]
       (reset! result-atom new-result'))))
 
+(defn get-query-time
+  [q]
+  (let [k [(state/get-current-repo) :custom q]]
+    (get-in @query-state [k :query-time])))
+
 (defn kv
   [key value]
   {:db/id -1
@@ -119,8 +124,9 @@
     (reset! query-state state)))
 
 (defn add-q!
-  [k query inputs result-atom transform-fn query-fn inputs-fn]
+  [k query time inputs result-atom transform-fn query-fn inputs-fn]
   (swap! query-state assoc k {:query query
+                              :query-time time
                               :inputs inputs
                               :result result-atom
                               :transform-fn transform-fn
@@ -169,29 +175,30 @@
           (add-query-component! k component))
         (if (and use-cache? result-atom)
           result-atom
-          (let [result (cond
-                         query-fn
-                         (query-fn db nil nil)
+          (let [{:keys [result time]} (util/with-time
+                                        (-> (cond
+                                              query-fn
+                                              (query-fn db nil nil)
 
-                         inputs-fn
-                         (let [inputs (inputs-fn)]
-                           (apply d/q query db inputs))
+                                              inputs-fn
+                                              (let [inputs (inputs-fn)]
+                                                (apply d/q query db inputs))
 
-                         kv?
-                         (d/entity db (last k))
+                                              kv?
+                                              (d/entity db (last k))
 
-                         (seq inputs)
-                         (apply d/q query db inputs)
+                                              (seq inputs)
+                                              (apply d/q query db inputs)
 
-                         :else
-                         (d/q query db))
-                result (transform-fn result)
+                                              :else
+                                              (d/q query db))
+                                            transform-fn))
                 result-atom (or result-atom (atom nil))]
             ;; Don't notify watches now
             (set! (.-state result-atom) result)
             (if disable-reactive?
               result-atom
-              (add-q! k query inputs result-atom transform-fn query-fn inputs-fn))))))))
+              (add-q! k query time inputs result-atom transform-fn query-fn inputs-fn))))))))
 
 
 ;; TODO: Extract several parts to handlers
@@ -236,7 +243,7 @@
                               (let [page-id (or
                                              (when (:block/name block) (:db/id block))
                                              (:db/id (:block/page block)))
-                                    blocks [[::block (:block/uuid block)]]
+                                    blocks [[::block (:db/id block)]]
                                     others (when page-id
                                              (let [db-after-parent-uuid (:block/uuid (:block/parent block))
                                                    db-before-parent-uuid (:block/uuid (:block/parent (d/entity db-before
@@ -274,60 +281,70 @@
      set)))
 
 (defn- execute-query!
-  [graph db k tx {:keys [query inputs transform-fn query-fn inputs-fn result]}]
-  (let [new-result (->
-                    (cond
-                      query-fn
-                      (let [result (query-fn db tx result)]
-                        (if (coll? result)
-                          (doall result)
-                          result))
-
-                      inputs-fn
-                      (let [inputs (inputs-fn)]
-                        (apply d/q query db inputs))
-
-                      (keyword? query)
-                      (db-utils/get-key-value graph query)
-
-                      (seq inputs)
-                      (apply d/q query db inputs)
-
-                      :else
-                      (d/q query db))
-                    transform-fn)]
-    (when-not (= new-result result)
-      (set-new-result! k new-result tx))))
+  [graph db k tx {:keys [query query-time inputs transform-fn query-fn inputs-fn result]}
+   {:keys [skip-query-time-check?]}]
+  (when (or skip-query-time-check?
+            (<= (or query-time 0) 80))
+    (let [new-result (->
+                     (cond
+                       query-fn
+                       (let [result (query-fn db tx result)]
+                         (if (coll? result)
+                           (doall result)
+                           result))
+
+                       inputs-fn
+                       (let [inputs (inputs-fn)]
+                         (apply d/q query db inputs))
+
+                       (keyword? query)
+                       (db-utils/get-key-value graph query)
+
+                       (seq inputs)
+                       (apply d/q query db inputs)
+
+                       :else
+                       (d/q query db))
+                     transform-fn)]
+     (when-not (= new-result result)
+       (set-new-result! k new-result tx)))))
+
+(defn path-refs-need-recalculated?
+  [tx-meta]
+  (when-let [outliner-op (:outliner-op tx-meta)]
+    (not (or
+          (contains? #{:collapse-expand-blocks :delete-blocks} outliner-op)
+          ;; ignore move up/down since it doesn't affect the refs for any blocks
+          (contains? #{:move-blocks-up-down} (:move-op tx-meta))
+          (:undo? tx-meta) (:redo? tx-meta)))))
 
 (defn refresh!
   "Re-compute corresponding queries (from tx) and refresh the related react components."
   [repo-url {:keys [tx-data tx-meta] :as tx}]
   (when (and repo-url
-             (seq tx-data)
              (not (:skip-refresh? tx-meta)))
-    (let [db (conn/get-db repo-url)
-          affected-keys (get-affected-queries-keys tx)]
-      (doseq [[k cache] @query-state]
-        (let [custom? (= :custom (second k))
-              kv? (= :kv (second k))]
-          (when (and
-                 (= (first k) repo-url)
-                 (or (get affected-keys (vec (rest k)))
-                     custom?
-                     kv?))
-            (let [{:keys [query query-fn]} cache]
-              (when (or query query-fn)
-                (try
-                  (let [f #(execute-query! repo-url db k tx cache)]
-                    ;; Detects whether user is editing in a custom query, if so, execute the query immediately
-                    (if (and custom?
-                             ;; modifying during cards review need to be executed immediately
-                             (not (:cards-query? (meta query)))
-                             (not (state/edit-in-query-component)))
-                      (async/put! (state/get-reactive-custom-queries-chan) [f query])
-                      (f)))
-                  (catch js/Error e
-                    (js/console.error e)))))))))))
+    (when (seq tx-data)
+      (let [db (conn/get-db repo-url)
+            affected-keys (get-affected-queries-keys tx)]
+        (doseq [[k cache] @query-state]
+          (let [custom? (= :custom (second k))
+                kv? (= :kv (second k))]
+            (when (and
+                   (= (first k) repo-url)
+                   (or (get affected-keys (vec (rest k)))
+                       custom?
+                       kv?))
+              (let [{:keys [query query-fn]} cache
+                    query-or-refs? (state/edit-in-query-or-refs-component)]
+                (when (or query query-fn)
+                  (try
+                    (let [f #(execute-query! repo-url db k tx cache {:skip-query-time-check? query-or-refs?})]
+                      ;; Detects whether user is editing in a custom query, if so, execute the query immediately
+                      (if (or query-or-refs? (not custom?))
+                        (f)
+                        (async/put! (state/get-reactive-custom-queries-chan) [f query])))
+                    (catch js/Error e
+                      (js/console.error e))))))))))))
 
 (defn set-key-value
   [repo-url key value]

+ 172 - 148
src/main/frontend/extensions/srs.cljs

@@ -8,11 +8,14 @@
             [frontend.util.drawer :as drawer]
             [frontend.util.persist-var :as persist-var]
             [frontend.db :as db]
+            [frontend.db.model :as db-model]
             [frontend.db-mixins :as db-mixins]
             [frontend.state :as state]
             [frontend.handler.editor :as editor-handler]
             [frontend.components.block :as component-block]
             [frontend.components.macro :as component-macro]
+            [frontend.components.select :as component-select]
+            [frontend.components.svg :as svg]
             [frontend.ui :as ui]
             [frontend.date :as date]
             [frontend.commands :as commands]
@@ -21,7 +24,6 @@
             [cljs-time.local :as tl]
             [cljs-time.coerce :as tc]
             [clojure.string :as string]
-            [goog.object :as gobj]
             [rum.core :as rum]
             [frontend.modules.shortcut.core :as shortcut]
             [medley.core :as medley]))
@@ -215,16 +217,18 @@
 
 (deftype Sided-Cloze-Card [block]
   ICard
-  (get-root-block [_this] (db/pull [:block/uuid (:block/uuid block)]))
+  (get-root-block [_this] (db/pull [:block/uuid block]))
   ICardShow
   (show-cycle [_this phase]
-    (let [blocks (-> (db/get-block-and-children (state/get-current-repo) (:block/uuid block))
+    (let [block-id (:db/id block)
+          blocks (-> (db/get-paginated-blocks (state/get-current-repo) block-id
+                                              {:scoped-block-id block-id})
                      clear-collapsed-property)
           cloze? (has-cloze? blocks)]
       (case phase
         1
         (let [blocks-count (count blocks)]
-          {:value [block] :next-phase (if (or (> blocks-count 1) (nil? cloze?)) 2 3)})
+          {:value [(first blocks)] :next-phase (if (or (> blocks-count 1) (nil? cloze?)) 2 3)})
         2
         {:value blocks :next-phase (if cloze? 3 1)}
         3
@@ -240,8 +244,8 @@
       {:show-cloze? true})))
 
 (defn- ->card [block]
-  {:pre [(map? block)]}
-  (->Sided-Cloze-Card block))
+  (let [block' (db/pull (:db/id block))]
+    (->Sided-Cloze-Card block')))
 
 ;;; ================================================================
 ;;;
@@ -251,37 +255,38 @@
   Add an extra condition: block's :block/refs contains `#card or [[card]]'"
   ([repo query-string]
    (query repo query-string {}))
-  ([repo query-string {:keys [disable-reactive? use-cache?]
+  ([repo query-string {:keys [use-cache?]
                        :or {use-cache? true}}]
    (when (string? query-string)
-     (let [query-string (template/resolve-dynamic-template! query-string)
-           query-string (if-not (or (string/blank? query-string)
-                                    (string/starts-with? query-string "(")
-                                    (string/starts-with? query-string "["))
-                          (util/format "[[%s]]" (string/trim query-string))
-                          query-string)
-           {:keys [query sort-by rules]} (query-dsl/parse query-string)
-           query* (concat [['?b :block/refs '?bp] ['?bp :block/name card-hash-tag]]
-                          (if (coll? (first query))
-                            query
-                            [query]))]
-       (when-let [query (query-dsl/query-wrapper query* true)]
-         (let [result (query-react/react-query repo
-                                               {:query (with-meta query {:cards-query? true})
-                                                :rules (or rules [])}
-                                               (merge
-                                                {:use-cache? use-cache?}
-                                                (cond->
-                                                 (when sort-by
-                                                   {:transform-fn sort-by})
-                                                  disable-reactive?
-                                                  (assoc :disable-reactive? true))))]
-           (when result
-             (flatten (util/react result)))))))))
+     (let [result (if (string/blank? query-string)
+                    (:block/_refs (db/entity [:block/name card-hash-tag]))
+                    (let [query-string (template/resolve-dynamic-template! query-string)
+                          query-string (if-not (or (string/blank? query-string)
+                                                   (string/starts-with? query-string "(")
+                                                   (string/starts-with? query-string "["))
+                                         (util/format "[[%s]]" (string/trim query-string))
+                                         query-string)
+                          {:keys [query sort-by rules]} (query-dsl/parse query-string)
+                          query* (util/concat-without-nil
+                                  [['?b :block/refs '?br] ['?br :block/name card-hash-tag]]
+                                  (if (coll? (first query)) query [query]))]
+                      (when-let [query (query-dsl/query-wrapper query*
+                                                                {:blocks? true
+                                                                 :block-attrs [:db/id :block/properties]})]
+                        (let [result (query-react/react-query repo
+                                                              {:query (with-meta query {:cards-query? true})
+                                                               :rules (or rules [])}
+                                                              (merge
+                                                               {:use-cache? use-cache?}
+                                                               (when sort-by
+                                                                 {:transform-fn sort-by})))]
+                          (when result
+                            (flatten (util/react result)))))))]
+       (vec result)))))
 
 (defn- query-scheduled
   "Return blocks scheduled to 'time' or before"
-  [_repo blocks time]
+  [blocks time]
   (let [filtered-result (filterv (fn [b]
                                    (let [props (:block/properties b)
                                          next-sched (get props card-next-schedule-property)
@@ -333,7 +338,7 @@
         result (get-next-interval card score)
         next-of-matrix (:next-of-matrix result)]
     (reset! of-matrix next-of-matrix)
-    (save-block-card-properties! (db/pull [:block/uuid (:block/uuid block)])
+    (save-block-card-properties! (db/pull (:db/id block))
                                  (select-keys result
                                               [card-last-interval-property
                                                card-repeats-property
@@ -346,23 +351,24 @@
   [card]
   {:pre [(satisfies? ICard card)]}
   (let [block (.-block card)]
-    (reset-block-card-properties! (db/pull [:block/uuid (:block/uuid block)]))))
+    (reset-block-card-properties! (db/pull (:db/id block)))))
 
 (defn- operation-card-info-summary!
   [review-records review-cards card-query-block]
   (when card-query-block
     (let [review-count (count (flatten (vals review-records)))
           review-cards-count (count review-cards)
-          score-5-count (count (get review-records 5))
-          score-1-count (count (get review-records 1))]
+          score-remembered-count (+ (count (get review-records 5))
+                                    (count (get review-records 3)))
+          score-forgotten-count (count (get review-records 1))]
       (editor-handler/insert-block-tree-after-target
        (:db/id card-query-block) false
        [{:content (util/format "Summary: %d items, %d review counts [[%s]]"
                                review-cards-count review-count (date/today))
          :children [{:content
-                     (util/format "Remembered:   %d (%d%%)" score-5-count (* 100 (/ score-5-count review-count)))}
+                     (util/format "Remembered:   %d (%d%%)" score-remembered-count (* 100 (/ score-remembered-count review-count)))}
                     {:content
-                     (util/format "Forgotten :   %d (%d%%)" score-1-count (* 100 (/ score-1-count review-count)))}]}]
+                     (util/format "Forgotten :   %d (%d%%)" score-forgotten-count (* 100 (/ score-forgotten-count review-count)))}]}]
        (:block/format card-query-block)))))
 
 ;;; ================================================================
@@ -376,24 +382,20 @@
                            (dec n)
                            n))))
 
-(defn- review-finished?
-  [cards]
-  (<= (count cards) 1))
-
-(defn- score-and-next-card [score card *card-index cards *phase *review-records cb]
+(defn- score-and-next-card [score card *card-index finished? *phase *review-records cb]
   (operation-score! card score)
   (swap! *review-records #(update % score (fn [ov] (conj ov card))))
-  (if (review-finished? cards)
+  (if finished?
     (when cb (cb @*review-records))
     (reset! *phase 1))
   (swap! *card-index inc)
   (when @global-cards-mode?
     (dec-cards-due-count!)))
 
-(defn- skip-card [card *card-index cards *phase *review-records cb]
+(defn- skip-card [card *card-index finished? *phase *review-records cb]
   (swap! *review-records #(update % "skip" (fn [ov] (conj ov card))))
   (swap! *card-index inc)
-  (if (review-finished? cards)
+  (if finished?
     (when cb (cb @*review-records))
     (reset! *phase 1)))
 
@@ -402,42 +404,31 @@
 
 (defn- btn-with-shortcut [{:keys [shortcut id btn-text background on-click class]}]
   (ui/button
-   [:span btn-text (when-not (util/sm-breakpoint?)
-                     [" " (ui/render-keyboard-shortcut shortcut)])]
-   :id id
-   :class (str id " " class)
-   :background background
-   :on-click (fn [e]
-               (when-let [elem (gobj/get e "target")]
-                 (.add (.-classList elem) "opacity-25"))
-               (js/setTimeout #(on-click) 10))))
-
-(rum/defcs view
-  < rum/reactive
-  db-mixins/query
+    [:span btn-text (when-not (util/sm-breakpoint?)
+                      [" " (ui/render-keyboard-shortcut shortcut)])]
+    :id id
+    :class (str id " " class)
+    :background background
+    :on-mouse-down (fn [e] (util/stop-propagation e))
+    :on-click (fn [_e]
+                (js/setTimeout #(on-click) 10))))
+
+(rum/defcs view < rum/reactive db-mixins/query
   (rum/local 1 ::phase)
   (rum/local {} ::review-records)
-  {:will-mount (fn [state]
-                 (state/set-state! :srs/mode? true)
-                 state)
-   :will-unmount (fn [state]
-                   (state/set-state! :srs/mode? false)
-                   state)}
   [state blocks {preview? :preview?
+                 cards? :cards?
                  modal? :modal?
                  cb :callback}
    card-index]
-  (let [blocks (if (fn? blocks) (blocks) blocks)
-        cards (map ->card blocks)
-        review-records (::review-records state)
-        ;; TODO: needs refactor
-        card (if preview?
-               (when card-index (util/nth-safe cards @card-index))
-               (first cards))]
-    (if-not card
+  (let [review-records (::review-records state)
+        current-block (util/nth-safe blocks @card-index)
+        card (when current-block (->card current-block))
+        finished? (= (inc @card-index) (count blocks))]
+    (if (nil? card)
       review-finished
       (let [phase (::phase state)
-            {blocks :value next-phase :next-phase} (show-cycle card @phase)
+            {current-blocks :value next-phase :next-phase} (show-cycle card @phase)
             root-block (.-block card)
             root-block-id (:block/uuid root-block)]
         [:div.ls-card.content
@@ -448,7 +439,7 @@
            [:div {:style {:margin-top 20}}
             (component-block/breadcrumb {} repo root-block-id {})])
          (component-block/blocks-container
-          blocks
+          current-blocks
           (merge (show-cycle-config card @phase)
                  {:id (str root-block-id)
                   :editor-box editor/box
@@ -464,14 +455,16 @@
                                   :id "card-answers"
                                   :class "mr-2"
                                   :on-click #(reset! phase next-phase)}))
-            (when (and (> (count cards) 1) preview?)
+            (when (and (not= @card-index (count blocks))
+                       cards?
+                       preview?)
               (btn-with-shortcut {:btn-text "Next"
                                   :shortcut "n"
                                   :id       "card-next"
                                   :class    "mr-2"
                                   :on-click (fn [e]
                                               (util/stop e)
-                                              (skip-card card card-index cards phase review-records cb))}))
+                                              (skip-card card card-index finished? phase review-records cb))}))
 
             (when (and (not preview?) (= 1 next-phase))
               [:<>
@@ -480,20 +473,20 @@
                                    :id         "card-forgotten"
                                    :background "red"
                                    :on-click   (fn []
-                                                 (score-and-next-card 1 card card-index cards phase review-records cb)
+                                                 (score-and-next-card 1 card card-index finished? phase review-records cb)
                                                  (let [tomorrow (tc/to-string (t/plus (t/today) (t/days 1)))]
                                                    (editor-handler/set-block-property! root-block-id card-next-schedule-property tomorrow)))})
 
                (btn-with-shortcut {:btn-text (if (util/mobile?) "Hard" "Took a while to recall")
                                    :shortcut "t"
                                    :id       "card-recall"
-                                   :on-click #(score-and-next-card 3 card card-index cards phase review-records cb)})
+                                   :on-click #(score-and-next-card 3 card card-index finished? phase review-records cb)})
 
                (btn-with-shortcut {:btn-text   "Remembered"
                                    :shortcut   "r"
                                    :id         "card-remembered"
                                    :background "green"
-                                   :on-click   #(score-and-next-card 5 card card-index cards phase review-records cb)})])
+                                   :on-click   #(score-and-next-card 5 card card-index finished? phase review-records cb)})])
 
             (when preview?
               (ui/tippy {:html [:div.text-sm
@@ -510,24 +503,19 @@
 
 (rum/defc view-modal <
   (shortcut/mixin :shortcut.handler/cards)
-  rum/reactive
-  db-mixins/query
   [blocks option card-index]
-  (let [option (update option :random-mode? (fn [v] (if (util/atom? v) @v v)))
-        blocks (if (fn? blocks) (blocks) blocks)
-        blocks (if (:random-mode? option)
-                 (shuffle blocks)
-                 blocks)]
-    [:div#cards-modal
-     (if (seq blocks)
+  [:div#cards-modal
+   (if (seq blocks)
+     (rum/with-key
        (view blocks option card-index)
-       review-finished)]))
+       (str "ls-card-" (:db/id (first blocks))))
+     review-finished)])
 
-(rum/defc preview-cp
+(rum/defc preview-cp < rum/reactive db-mixins/query
   [block-id]
-  (let [blocks-f (fn [] (db/get-paginated-blocks (state/get-current-repo) block-id
-                                                 {:scoped-block-id block-id}))]
-    (view-modal blocks-f {:preview? true} (atom 0))))
+  (let [blocks (db/get-paginated-blocks (state/get-current-repo) block-id
+                                        {:scoped-block-id block-id})]
+    (view-modal blocks {:preview? true} (atom 0))))
 
 (defn preview
   [block-id]
@@ -563,49 +551,69 @@
   (try
     (let [repo (state/get-current-repo)
           query-string ""
-          blocks (query repo query-string {:use-cache?        false
-                                           :disable-reactive? true})]
+          blocks (query repo query-string {:use-cache?        false})]
       (when (seq blocks)
-        (let [{:keys [result]} (query-scheduled repo blocks (tl/local-now))
+        (let [{:keys [result]} (query-scheduled blocks (tl/local-now))
               count (count result)]
           (reset! cards-total count)
           count)))
     (catch js/Error e
       (js/console.error e) 0)))
 
+(declare cards)
+
+(rum/defc cards-select
+  [{:keys [on-chosen]}]
+  (let [cards (db-model/get-macro-blocks (state/get-current-repo) "cards")
+        items (->> (map (comp :logseq.macro-arguments :block/properties) cards)
+                   (map (fn [col] (string/join " " col))))
+        items (concat items ["All"])]
+    (component-select/select {:items items
+                              :on-chosen on-chosen
+                              :close-modal? false
+                              :input-default-placeholder "Switch to"
+                              :extract-fn nil})))
+
 ;;; register cards macro
-(rum/defcs ^:large-vars/cleanup-todo cards < rum/reactive db-mixins/query
+(rum/defcs ^:large-vars/cleanup-todo cards-inner < rum/reactive db-mixins/query
   (rum/local 0 ::card-index)
   (rum/local false ::random-mode?)
   (rum/local false ::preview-mode?)
-  [state config options]
+  [state config options {:keys [query-atom query-string query-result due-result]}]
   (let [*random-mode? (::random-mode? state)
         *preview-mode? (::preview-mode? state)
-        repo (state/get-current-repo)
-        query-string (string/join ", " (:arguments options))
-        query-result (query repo query-string)
-        *card-index (::card-index state)
-        global? (:global? config)]
+        *card-index (::card-index state)]
     (if (seq query-result)
-      (let [{:keys [total result]} (query-scheduled repo query-result (tl/local-now))
-            review-cards result
+      (let [{:keys [total result]} due-result
+            review-cards (if @*preview-mode? query-result result)
             card-query-block (db/entity [:block/uuid (:block/uuid config)])
             filtered-total (count result)
-            ;; FIXME: It seems that model? is always true?
             modal? (:modal? config)
             callback-fn (fn [review-records]
                           (when-not @*preview-mode?
                             (operation-card-info-summary!
                              review-records review-cards card-query-block)
                             (persist-var/persist-save of-matrix)))]
-        [:div.flex-1.cards-review {:style (when modal? {:height "100%"})
-                                   :class (if global? "" "shadow-xl")}
+        [:div.flex-1.cards-review {:style (when modal? {:height "100%"})}
          [:div.flex.flex-row.items-center.justify-between.cards-title
           [:div.flex.flex-row.items-center
-           (if @*preview-mode?
-             (ui/icon "book" {:style {:font-size 20}})
-             (ui/icon "infinity" {:style {:font-size 20}}))
-           [:div.ml-1.text-sm.font-medium (if (string/blank? query-string) "All" query-string)]]
+           (ui/icon "infinity" {:style {:font-size 20}})
+           (ui/dropdown
+            (fn [{:keys [toggle-fn]}]
+              [:div.ml-1.text-sm.font-medium.cursor
+               {:on-mouse-down (fn [e]
+                                 (util/stop e)
+                                 (toggle-fn))}
+               [:span.flex (if (string/blank? query-string) "All" query-string)
+                [:span {:style {:margin-top 2}}
+                 (svg/caret-down)]]])
+            (fn [{:keys [toggle-fn]}]
+              (cards-select {:on-chosen (fn [query]
+                                          (let [query' (if (= query "All") "" query)]
+                                            (reset! query-atom query')
+                                            (toggle-fn)))}))
+            {:modal-class (util/hiccup->class
+                           "origin-top-right.absolute.left-0.mt-2.ml-2.rounded-md.shadow-lg")})]
 
           [:div.flex.flex-row.items-center
 
@@ -618,10 +626,10 @@
                         [:span "/"]
                         total])
              (ui/tippy {:html [:div.text-sm "overdue/total"]
-                      ;; :class "tippy-hover"
+                        ;; :class "tippy-hover"
                         :interactive true}
                        [:div.opacity-60.text-sm.mr-3
-                        filtered-total
+                        (max 0 (- filtered-total @*card-index))
                         [:span "/"]
                         total]))
 
@@ -654,34 +662,22 @@
                                                  :font-weight 600}
                                                  @*random-mode?
                                                  (assoc :color "orange"))})])]]
-         (if (or @*preview-mode? (seq review-cards))
-           [:div.px-1
-            (when (and (not modal?) (not @*preview-mode?))
-              {:on-click (fn []
-                           (let [blocks-f (fn []
-                                            (let [query-result (query repo query-string)]
-                                              (:result (query-scheduled repo query-result (tl/local-now)))))]
-                             (state/set-modal! #(view-modal
-                                                 blocks-f
-                                                 {:modal? true
-                                                  :random-mode? *random-mode?
-                                                  :preview? false
-                                                  :callback callback-fn}
-                                                 *card-index)
-                                               {:id :srs})))})
-            (let [view-fn (if modal? view-modal view)
-                  blocks (if @*preview-mode?
-                           (query repo query-string)
-                           review-cards)]
-              (view-fn blocks
-               (merge config
-                      {:global? global?
-                       :random-mode? @*random-mode?
-                       :preview? @*preview-mode?
-                       :callback callback-fn})
-               *card-index))]
-           review-finished)])
-      (if global?
+         [:div.px-1
+          (when (and (not modal?) (not @*preview-mode?))
+            {:on-click (fn []
+                         (state/set-modal! #(cards (assoc config :modal? true) {:query-string query-string})
+                                           {:id :srs}))})
+          (let [view-fn (if modal? view-modal view)
+                blocks (if @*preview-mode? query-result review-cards)
+                blocks (if @*random-mode? (shuffle blocks) blocks)]
+            (view-fn blocks
+                     (merge config
+                            (merge options
+                                   {:random-mode? @*random-mode?
+                                    :preview? @*preview-mode?
+                                    :callback callback-fn}))
+                     *card-index))]])
+      (if (:global? config)
         [:div.ls-card.content
          [:h1.title "Time to create a card!"]
 
@@ -697,6 +693,28 @@
           [:code.p-1 (str "Cards: " query-string)]]
          [:div.mt-2.ml-2.font-medium "No matched cards"]]))))
 
+(rum/defcs cards <
+  (rum/local nil ::query)
+  {:will-mount (fn [state]
+                 (state/set-state! :srs/mode? true)
+                 state)
+   :will-unmount (fn [state]
+                   (state/set-state! :srs/mode? false)
+                   state)}
+  [state config options]
+  (let [*query (::query state)
+        repo (state/get-current-repo)
+        query-string (or @*query
+                         (:query-string options)
+                         (string/join ", " (:arguments options)))
+        query-result (query repo query-string)
+        due-result (query-scheduled query-result (tl/local-now))]
+    (cards-inner config (assoc options :cards? true)
+                 {:query-atom *query
+                  :query-string query-string
+                  :query-result query-result
+                  :due-result due-result})))
+
 (rum/defc global-cards <
   {:will-mount (fn [state]
                  (reset! global-cards-mode? true)
@@ -739,10 +757,16 @@
          block-id
          (str (string/trim content) " #" card-hash-tag))))))
 
+(defonce *due-cards-interval (atom nil))
+
 (defn update-cards-due-count!
   []
-  (js/setTimeout
-   (fn []
-     (let [total (get-srs-cards-total)]
-       (state/set-state! :srs/cards-due-count total)))
-   200))
+  (when (state/enable-flashcards?)
+    (let [f (fn []
+              (let [total (get-srs-cards-total)]
+                (state/set-state! :srs/cards-due-count total)))]
+      (js/setTimeout f 1000)
+      (when (nil? @*due-cards-interval)
+        ;; refresh every hour
+        (let [interval (js/setInterval f (* 3600 1000))]
+          (reset! *due-cards-interval interval))))))

+ 5 - 14
src/main/frontend/format/block.cljs

@@ -15,7 +15,8 @@
 (defn extract-blocks
   "Wrapper around logseq.graph-parser.block/extract-blocks that adds in system state
 and handles unexpected failure."
-  [blocks content with-id? format]
+  [blocks content format {:keys [with-id?]
+                          :or {with-id? true}}]
   (try
     (gp-block/extract-blocks blocks content with-id? format
                              {:user-config (state/get-config)
@@ -38,25 +39,15 @@ and handles unexpected failure."
 (defn parse-block
   ([block]
    (parse-block block nil))
-  ([{:block/keys [uuid content page format] :as block} {:keys [with-id?]
+  ([{:block/keys [uuid content format] :as block} {:keys [with-id?]
                                                         :or {with-id? true}}]
    (when-not (string/blank? content)
      (let [block (dissoc block :block/pre-block?)
            ast (format/to-edn content format nil)
-           blocks (extract-blocks ast content with-id? format)
+           blocks (extract-blocks ast content format {:with-id? with-id?})
            new-block (first blocks)
-           parent-refs (->> (db/get-block-parent (state/get-current-repo) uuid)
-                            :block/path-refs
-                            (map :db/id))
-           {:block/keys [refs]} new-block
-           ref-pages (filter :block/name refs)
-           path-ref-pages (->> (concat ref-pages parent-refs [(:db/id page)])
-                               (remove nil?))
            block (cond->
-                   (merge
-                    block
-                    new-block
-                    {:block/path-refs path-ref-pages})
+                   (merge block new-block)
                    (> (count blocks) 1)
                    (assoc :block/warning :multiple-blocks))
            block (dissoc block :block/title :block/body :block/level)]

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

@@ -56,8 +56,7 @@
               (when (and (not (state/nfs-refreshing?))
                          (not (contains? (:file/unlinked-dirs @state/state)
                                          (config/get-repo-dir repo))))
-                (when (state/enable-flashcards?)
-                  (srs/update-cards-due-count!))
+
                 ;; Don't create the journal file until user writes something
                 (page-handler/create-today-journal!))))]
     (f)

+ 42 - 54
src/main/frontend/handler/block.cljs

@@ -21,58 +21,6 @@
   [repo page-id]
   (>= (db/get-page-blocks-count repo page-id) db-model/initial-blocks-length))
 
-(defn get-block-refs-with-children
-  [block]
-  (->>
-   (tree-seq :block/refs
-             :block/children
-             block)
-   (mapcat :block/refs)
-   (distinct)))
-
-(defn filter-blocks
-  [repo ref-blocks filters group-by-page?]
-  (let [ref-pages-ids (->> (if group-by-page?
-                             (mapcat last ref-blocks)
-                             ref-blocks)
-                           (mapcat (fn [b] (get-block-refs-with-children b)))
-                           (concat (when group-by-page? (map first ref-blocks)))
-                           (distinct)
-                           (map :db/id)
-                           (remove nil?))
-        ref-pages (db/pull-many repo '[:db/id :block/name] ref-pages-ids)
-        ref-pages (zipmap (map :block/name ref-pages) (map :db/id ref-pages))
-        exclude-ids (->> (map (fn [page] (get ref-pages page)) (get filters false))
-                         (remove nil?)
-                         (set))
-        include-ids (->> (map (fn [page] (get ref-pages page)) (get filters true))
-                         (remove nil?)
-                         (set))]
-    (if (empty? filters)
-      ref-blocks
-      (let [filter-f (fn [ref-blocks]
-                       (cond->> ref-blocks
-                         (seq exclude-ids)
-                         (remove (fn [block]
-                                   (let [ids (set (concat (map :db/id (get-block-refs-with-children block))
-                                                          [(:db/id (:block/page block))]))]
-                                     (seq (set/intersection exclude-ids ids)))))
-
-                         (seq include-ids)
-                         (remove (fn [block]
-                                   (let [page-block-id (:db/id (:block/page block))
-                                         ids (set (map :db/id (get-block-refs-with-children block)))]
-                                     (if (and (contains? include-ids page-block-id)
-                                              (= 1 (count include-ids)))
-                                       (not= page-block-id (first include-ids))
-                                       (empty? (set/intersection include-ids (set (conj ids page-block-id))))))))))]
-        (if group-by-page?
-          (->> (map (fn [[p ref-blocks]]
-                      [p (filter-f ref-blocks)]) ref-blocks)
-               (remove #(empty? (second %))))
-          (->> (filter-f ref-blocks)
-               (remove nil?)))))))
-
 ;; TODO: reduced version
 (defn- walk-block
   [block check? transform]
@@ -138,8 +86,8 @@
 (defn indent-outdent-block!
   [block direction]
   (outliner-tx/transact!
-   {:outliner-op :move-blocks}
-   (outliner-core/indent-outdent-blocks! [block] (= direction :right))))
+    {:outliner-op :move-blocks}
+    (outliner-core/indent-outdent-blocks! [block] (= direction :right))))
 
 (defn select-block!
   [block-uuid]
@@ -298,3 +246,43 @@
   (reset! *show-left-menu? false)
   (reset! *show-right-menu? false)
   (reset! *swipe nil))
+
+(defn get-blocks-refed-pages
+  [repo page-entity]
+  (let [pages (db-model/page-alias-set repo (:block/name page-entity))
+        refs (->> pages
+                  (mapcat (fn [id] (:block/_path-refs (db/entity id))))
+                  (mapcat (fn [b] (conj (:block/path-refs b) (:block/page b))))
+                  (remove (fn [r] (= (:db/id page-entity) (:db/id r)))))]
+    (keep (fn [ref]
+            (when (:block/name ref)
+              {:db/id (:db/id ref)
+               :block/name (:block/name ref)
+               :block/original-name (:block/original-name ref)})) refs)))
+
+(defn- filter-blocks
+  [ref-blocks filters ref-pages]
+  (let [ref-pages (distinct ref-pages)]
+    (if (empty? filters)
+      ref-blocks
+      (let [ref-pages (zipmap (map :block/name ref-pages) (map :db/id ref-pages))
+            exclude-ids (->> (keep (fn [page] (get ref-pages page)) (get filters false))
+                             (set))
+            include-ids (->> (keep (fn [page] (get ref-pages page)) (get filters true))
+                             (set))]
+        (cond->> ref-blocks
+          (seq exclude-ids)
+          (remove (fn [block]
+                    (let [ids (set (map :db/id (:block/path-refs block)))]
+                      (seq (set/intersection exclude-ids ids)))))
+
+          (seq include-ids)
+          (remove (fn [block]
+                    (let [ids (set (map :db/id (:block/path-refs block)))]
+                      (empty? (set/intersection include-ids ids))))))))))
+
+(defn get-filtered-ref-blocks
+  [ref-blocks filters ref-pages]
+  (let [ref-blocks' (mapcat second ref-blocks)
+        filtered-blocks (filter-blocks ref-blocks' filters ref-pages)]
+    (group-by :block/page filtered-blocks)))

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

@@ -399,15 +399,15 @@
         block (apply dissoc block db-schema/retract-attributes)]
     (profile
      "Save block: "
-     (let [block (wrap-parse-block block)]
+     (let [block' (wrap-parse-block block)]
        (outliner-tx/transact!
          {:outliner-op :save-block}
-         (outliner-core/save-block! block))
+         (outliner-core/save-block! block'))
 
        ;; sanitized page name changed
-       (when-let [title (get-in block [:block/properties :title])]
-         (when-let [old-page-name (:block/name (db/entity (:db/id (:block/page block))))]
-           (when (and (:block/pre-block? block)
+       (when-let [title (get-in block' [:block/properties :title])]
+         (when-let [old-page-name (:block/name (db/entity (:db/id (:block/page block'))))]
+           (when (and (:block/pre-block? block')
                       (not (string/blank? title))
                       (not= (util/page-name-sanity-lc title) old-page-name))
              (state/pub-event! [:page/title-property-changed old-page-name title]))))))))
@@ -1224,7 +1224,6 @@
   (when-let [start-block (state/get-selection-start-block-or-first)]
     (let [blocks (util/get-nodes-between-two-nodes start-block end-block "ls-block")
           direction (util/get-direction-between-two-nodes start-block end-block "ls-block")
-
           blocks (if (= :up direction)
                    (reverse blocks)
                    blocks)]
@@ -1914,9 +1913,7 @@
             :block/properties (apply dissoc (:block/properties block)
                                 (concat [:id :custom_id :custom-id]
                                         exclude-properties))
-            :block/content new-content
-            :block/path-refs (->> (cons (:db/id page) (:block/path-refs block))
-                                  (remove nil?))})))
+            :block/content new-content})))
 
 (defn- edit-last-block-after-inserted!
   [result]
@@ -1957,10 +1954,12 @@
 
                    :else
                    true)]
+
     (when has-unsaved-edits
       (outliner-tx/transact!
         {:outliner-op :save-block}
         (outliner-core/save-block! editing-block)))
+
     (outliner-tx/transact!
       {:outliner-op :insert-blocks}
       (when target-block
@@ -1984,7 +1983,7 @@
                     content* (str (if (= :markdown format) "- " "* ")
                                   (property/insert-properties format content props))
                     ast (mldoc/->edn content* (gp-mldoc/default-config format))
-                    blocks (block/extract-blocks ast content* true format)
+                    blocks (block/extract-blocks ast content* format {})
                     fst-block (first blocks)
                     fst-block (if (and keep-uuid? (uuid? (:uuid block)))
                                 (assoc fst-block :block/uuid (:uuid block))
@@ -2412,8 +2411,7 @@
             :else
             (profile
              "Insert block"
-             (do (save-current-block!)
-                 (insert-new-block! state)))))))))
+             (insert-new-block! state))))))))
 
 (defn keydown-new-block-handler [state e]
   (if (state/doc-mode-enter-for-new-line?)

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

@@ -79,7 +79,7 @@
   (when-let [repo (state/get-current-repo)]
     (let [[headers parsed-blocks] (mldoc/opml->edn data)
           parsed-blocks (->>
-                         (block/extract-blocks parsed-blocks "" true :markdown)
+                         (block/extract-blocks parsed-blocks "" :markdown {})
                          (mapv editor/wrap-parse-block))
           page-name (:title headers)]
       (when (not (db/page-exists? page-name))

+ 38 - 34
src/main/frontend/handler/file.cljs

@@ -107,44 +107,48 @@
   ([repo-url file content]
    (reset-file! repo-url file content {}))
   ([repo-url file content {:keys [verbose] :as options}]
-   (let [electron-local-repo? (and (util/electron?)
-                                   (config/local-db? repo-url))
-         file (cond
-                (and electron-local-repo?
-                     util/win32?
-                     (utils/win32 file))
-                file
+   (try
+     (let [electron-local-repo? (and (util/electron?)
+                                    (config/local-db? repo-url))
+          file (cond
+                 (and electron-local-repo?
+                      util/win32?
+                      (utils/win32 file))
+                 file
 
-                (and electron-local-repo? (or
-                                           util/win32?
-                                           (not= "/" (first file))))
-                (str (config/get-repo-dir repo-url) "/" file)
+                 (and electron-local-repo? (or
+                                            util/win32?
+                                            (not= "/" (first file))))
+                 (str (config/get-repo-dir repo-url) "/" file)
 
-                (and (mobile/native-android?) (not= "/" (first file)))
-                file
+                 (and (mobile/native-android?) (not= "/" (first file)))
+                 file
 
-                (and (mobile/native-ios?) (not= "/" (first file)))
-                file
+                 (and (mobile/native-ios?) (not= "/" (first file)))
+                 file
 
-                :else
-                file)
-         file (gp-util/path-normalize file)
-         new? (nil? (db/entity [:file/path file]))]
-     (:tx
-      (graph-parser/parse-file
-       (db/get-db repo-url false)
-       file
-       content
-       (merge (dissoc options :verbose)
-              {:new? new?
-               :delete-blocks-fn (partial get-delete-blocks repo-url)
-               :extract-options (merge
-                                 {:user-config (state/get-config)
-                                  :date-formatter (state/get-date-formatter)
-                                  :page-name-order (state/page-name-order)
-                                  :block-pattern (config/get-block-pattern (gp-util/get-format file))
-                                  :supported-formats (gp-config/supported-formats)}
-                                 (when (some? verbose) {:verbose verbose}))}))))))
+                 :else
+                 file)
+          file (gp-util/path-normalize file)
+          new? (nil? (db/entity [:file/path file]))]
+      (:tx
+       (graph-parser/parse-file
+        (db/get-db repo-url false)
+        file
+        content
+        (merge (dissoc options :verbose)
+               {:new? new?
+                :delete-blocks-fn (partial get-delete-blocks repo-url)
+                :extract-options (merge
+                                  {:user-config (state/get-config)
+                                   :date-formatter (state/get-date-formatter)
+                                   :page-name-order (state/page-name-order)
+                                   :block-pattern (config/get-block-pattern (gp-util/get-format file))
+                                   :supported-formats (gp-config/supported-formats)}
+                                  (when (some? verbose) {:verbose verbose}))}))))
+     (catch :default e
+       (prn "Reset file failed " {:file file})
+       (log/error :exception e)))))
 
 ;; TODO: Remove this function in favor of `alter-files`
 (defn alter-file

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

@@ -80,6 +80,7 @@
                                                       (state/get-date-formatter)
                                                       (state/get-config))]
      {:block/uuid (db/new-block-id)
+      :block/pre-block? true
       :block/properties ps
       :block/properties-order (keys ps)
       :block/refs refs
@@ -384,8 +385,7 @@
                                    :block/content    content
                                    :block/properties properties
                                    :block/properties-order (map first properties)
-                                   :block/refs (rename-update-block-refs! (:block/refs block) (:db/id page) (:db/id to-page))
-                                   :block/path-refs (rename-update-block-refs! (:block/path-refs block) (:db/id page) (:db/id to-page))})))) blocks)
+                                   :block/refs (rename-update-block-refs! (:block/refs block) (:db/id page) (:db/id to-page))})))) blocks)
                       (remove nil?))]
     (db/transact! repo tx)
     (doseq [page-id page-ids]
@@ -526,7 +526,6 @@
                            (cond->
                             {:db/id id
                              :block/page {:db/id to-id}
-                             :block/path-refs (rename-update-block-refs! (:block/path-refs block) from-id to-id)
                              :block/refs (rename-update-block-refs! (:block/refs block) from-id to-id)}
 
                              (and from-first-child (= id (:db/id from-first-child)))

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

@@ -26,7 +26,7 @@
   (when-let [editing-block (state/get-edit-block)]
     (let [page-id (:db/id (:block/page editing-block))
           blocks (block/extract-blocks
-                  (mldoc/->edn text (gp-mldoc/default-config format)) text true format)
+                  (mldoc/->edn text (gp-mldoc/default-config format)) text format {})
           blocks' (gp-block/with-parent-and-left page-id blocks)]
       (editor-handler/paste-blocks blocks' {}))))
 

+ 6 - 6
src/main/frontend/handler/recent.cljs

@@ -1,14 +1,14 @@
 (ns frontend.handler.recent
   (:require [frontend.db :as db]))
 
-
 (defn add-page-to-recent!
-  [repo page]
+  [repo page click-from-recent?]
   (let [pages (or (db/get-key-value repo :recent/pages)
-                  '())
-        new-pages (take 15 (distinct (cons page pages)))]
-    (db/set-key-value repo :recent/pages new-pages)))
-
+                  '())]
+    (when (or (and click-from-recent? (not ((set pages) page)))
+              (not click-from-recent?))
+      (let [new-pages (take 15 (distinct (cons page pages)))]
+        (db/set-key-value repo :recent/pages new-pages)))))
 
 (defn update-or-add-renamed-page [repo old-page-name new-page-name]
   (let [pages (or (db/get-key-value repo :recent/pages)

+ 13 - 14
src/main/frontend/handler/route.cljs

@@ -38,20 +38,19 @@
 (defn redirect-to-page!
   "Must ensure `page-name` is dereferenced (not an alias), or it will create a wrong new page with that name (#3511)."
   ([page-name]
-   (recent-handler/add-page-to-recent! (state/get-current-repo) page-name)
-   (redirect! {:to :page
-               :path-params {:name (str page-name)}}))
-  ([page-name anchor]
-   (recent-handler/add-page-to-recent! (state/get-current-repo) page-name)
-   (redirect! {:to :page
-               :path-params {:name (str page-name)}
-               :query-params {:anchor anchor}}))
-  ([page-name anchor push]
-   (recent-handler/add-page-to-recent! (state/get-current-repo) page-name)
-   (redirect! {:to :page
-               :path-params {:name (str page-name)}
-               :query-params {:anchor anchor}
-               :push push})))
+   (redirect-to-page! page-name {}))
+  ([page-name {:keys [anchor push click-from-recent?]
+               :or {click-from-recent? false}}]
+   (recent-handler/add-page-to-recent! (state/get-current-repo) page-name
+                                       click-from-recent?)
+   (let [m (cond->
+             {:to :page
+              :path-params {:name (str page-name)}}
+             anchor
+             (assoc :query-params {:anchor anchor})
+             push
+             (assoc :push push))]
+     (redirect! m))))
 
 (defn get-title
   [name path-params]

+ 10 - 6
src/main/frontend/modules/editor/undo_redo.cljs

@@ -125,9 +125,13 @@
   [{:keys [tx-data tx-meta] :as tx-report}]
   (when-not (empty? tx-data)
     (reset-redo)
-    (let [updated-blocks (db-report/get-blocks tx-report)
-          entity {:blocks updated-blocks
-                  :txs tx-data
-                  :editor-cursor (:editor-cursor tx-meta)
-                  :pagination-blocks-range (get-in [:ui/pagination-blocks-range (get-in tx-report [:db-after :max-tx])] @state/state)}]
-      (push-undo entity))))
+    (if (:compute-new-refs? tx-meta)
+      (let [[removed-e _prev-e] (pop-undo)
+            entity (update removed-e :txs concat tx-data)]
+        (push-undo entity))
+      (let [updated-blocks (db-report/get-blocks tx-report)
+            entity {:blocks updated-blocks
+                    :txs tx-data
+                    :editor-cursor (:editor-cursor tx-meta)
+                    :pagination-blocks-range (get-in [:ui/pagination-blocks-range (get-in tx-report [:db-after :max-tx])] @state/state)}]
+       (push-undo entity)))))

+ 8 - 5
src/main/frontend/modules/outliner/core.cljs

@@ -15,7 +15,7 @@
             [logseq.graph-parser.util :as gp-util]
             [cljs.spec.alpha :as s]))
 
-(s/def ::block-map (s/keys :req [:db/id :block/uuid]
+(s/def ::block-map (s/keys :req [:db/id]
                            :opt [:block/page :block/left :block/parent]))
 
 (s/def ::block-map-or-entity (s/or :entity de/entity?
@@ -141,8 +141,10 @@
           other-tx (:db/other-tx m)
           id (:db/id (:data this))
           block-entity (db/entity id)
-          old-refs (:block/refs block-entity)
-          new-refs (:block/refs m)]
+          remove-self-page #(remove (fn [b]
+                                      (= (:db/id b) (:db/id (:block/page block-entity)))) %)
+          old-refs (remove-self-page (:block/refs block-entity))
+          new-refs (remove-self-page (:block/refs m))]
       (when (seq other-tx)
         (swap! txs-state (fn [txs]
                            (vec (concat txs other-tx)))))
@@ -666,13 +668,14 @@
                     move-blocks-next-tx [(build-move-blocks-next-tx blocks non-consecutive-blocks?)]
                     children-page-tx (when not-same-page?
                                        (let [children-ids (mapcat #(db/get-block-children-ids (state/get-current-repo) (:block/uuid %)) blocks)]
-                                         (map (fn [uuid] {:block/uuid uuid
-                                                          :block/page target-page}) children-ids)))
+                                         (map (fn [id] {:block/uuid id
+                                                        :block/page target-page}) children-ids)))
                     fix-non-consecutive-tx (->> (fix-non-consecutive-blocks blocks target-block sibling?)
                                                 (remove (fn [b]
                                                           (contains? (set (map :db/id move-blocks-next-tx)) (:db/id b)))))
                     full-tx (util/concat-without-nil tx-data move-blocks-next-tx children-page-tx fix-non-consecutive-tx)
                     tx-meta (cond-> {:move-blocks (mapv :db/id blocks)
+                                     :move-op outliner-op
                                      :target (:db/id target-block)}
                               not-same-page?
                               (assoc :from-page first-block-page

+ 1 - 2
src/main/frontend/modules/outliner/datascript.cljc

@@ -63,8 +63,7 @@
                  conn (conn/get-db repo false)
                  editor-cursor (state/get-current-edit-block-and-position)
                  meta (merge opts {:editor-cursor editor-cursor})
-                 rs (d/transact! conn txs (assoc meta
-                                                 :outliner/transact? true))]
+                 rs (d/transact! conn txs (assoc meta :outliner/transact? true))]
              (when true                 ; TODO: add debug flag
                (let [eids (distinct (mapv first (:tx-data rs)))
                      left&parent-list (->>

+ 71 - 11
src/main/frontend/modules/outliner/pipeline.cljs

@@ -1,21 +1,81 @@
 (ns frontend.modules.outliner.pipeline
   (:require [frontend.modules.datascript-report.core :as ds-report]
             [frontend.modules.outliner.file :as file]
-            [frontend.state :as state]))
+            [frontend.state :as state]
+            [frontend.util :as util]
+            [frontend.db.model :as db-model]
+            [frontend.db.react :as react]
+            [frontend.db :as db]
+            [clojure.set :as set]))
 
 (defn updated-page-hook
   [_tx-report page]
   (file/sync-to-file page))
 
+;; TODO: it'll be great if we can calculate the :block/path-refs before any
+;; outliner transaction, this way we can group together the real outliner tx
+;; and the new path-refs changes, which makes both undo/redo and
+;; react-query/refresh! easier.
+
+;; Steps:
+;; 1. For each changed block, new-refs = its page + :block/refs + parents :block/refs
+;; 2. Its children' block/path-refs might need to be updated too.
+(defn compute-block-path-refs
+  [tx-meta blocks]
+  (let [repo (state/get-current-repo)
+        blocks (remove :block/name blocks)]
+    (when (:outliner-op tx-meta)
+      (when (react/path-refs-need-recalculated? tx-meta)
+        (let [*computed-ids (atom #{})]
+          (mapcat (fn [block]
+                    (when (and (not (@*computed-ids (:block/uuid block))) ; not computed yet
+                               (not (:block/name block)))
+                      (let [parents (db-model/get-block-parents repo (:block/uuid block))
+                            parents-refs (->> (mapcat :block/path-refs parents)
+                                              (map :db/id))
+                            old-refs (set (map :db/id (:block/path-refs block)))
+                            new-refs (set (util/concat-without-nil
+                                           [(:db/id (:block/page block))]
+                                           (map :db/id (:block/refs block))
+                                           parents-refs))
+                            refs-changed? (not= old-refs new-refs)
+                            children (db-model/get-block-children-ids repo (:block/uuid block))
+                            children-refs (map (fn [id]
+                                                 {:db/id (:db/id (db/entity [:block/uuid id]))
+                                                  :block/path-refs (concat
+                                                                    (map :db/id (:block/path-refs (db/entity id)))
+                                                                    new-refs)}) children)]
+                        (swap! *computed-ids set/union (set (cons (:block/uuid block) children)))
+                        (util/concat-without-nil
+                         [(when (and refs-changed? (seq new-refs))
+                            {:db/id (:db/id block)
+                             :block/path-refs new-refs})]
+                         children-refs))))
+                  blocks))))))
+
 (defn invoke-hooks
   [tx-report]
-  (when (and (not (:from-disk? (:tx-meta tx-report)))
-             (not (:new-graph? (:tx-meta tx-report))))
-    (let [{:keys [pages blocks]} (ds-report/get-blocks-and-pages tx-report)]
-      (doseq [p (seq pages)]
-        (updated-page-hook tx-report p))
-      (when (and state/lsp-enabled? (seq blocks))
-        (state/pub-event! [:plugin/hook-db-tx
-                           {:blocks  blocks
-                            :tx-data (:tx-data tx-report)
-                            :tx-meta (:tx-meta tx-report)}])))))
+  (let [tx-meta (:tx-meta tx-report)]
+    (when (and (not (:from-disk? tx-meta))
+               (not (:new-graph? tx-meta))
+               (not (:compute-new-refs? tx-meta)))
+      (let [{:keys [pages blocks]} (ds-report/get-blocks-and-pages tx-report)
+            repo (state/get-current-repo)
+            refs-tx (set (compute-block-path-refs (:tx-meta tx-report) blocks))
+            truncate-refs-tx (map (fn [m] [:db/retract (:db/id m) :block/path-refs]) refs-tx)
+            tx (util/concat-without-nil truncate-refs-tx refs-tx)
+            tx-report' (if (seq tx)
+                         (let [refs-tx-data' (:tx-data (db/transact! repo tx {:outliner/transact? true
+                                                                         :compute-new-refs? true}))]
+                           ;; merge
+                           (assoc tx-report :tx-data (concat (:tx-data tx-report) refs-tx-data')))
+                         tx-report)]
+        (react/refresh! repo tx-report')
+
+        (doseq [p (seq pages)]
+          (updated-page-hook tx-report p))
+        (when (and state/lsp-enabled? (seq blocks))
+          (state/pub-event! [:plugin/hook-db-tx
+                             {:blocks  blocks
+                              :tx-data (:tx-data tx-report)
+                              :tx-meta (:tx-meta tx-report)}]))))))

+ 37 - 0
src/main/frontend/modules/outliner/tree.cljs

@@ -1,5 +1,6 @@
 (ns frontend.modules.outliner.tree
   (:require [frontend.db :as db]
+            [frontend.db.model :as model]
             [clojure.string :as string]
             [frontend.state :as state]))
 
@@ -50,6 +51,7 @@
     [false root-id]))
 
 (defn blocks->vec-tree
+  "`blocks` need to be in the same page."
   ([blocks root-id]
    (blocks->vec-tree (state/get-current-repo) blocks root-id))
   ([repo blocks root-id]
@@ -64,6 +66,41 @@
                  root-block (assoc root-block :block/children result)]
              [root-block])))))))
 
+(defn- tree [flat-nodes root-id]
+  (let [children (group-by :block/parent flat-nodes)
+        nodes (fn nodes [parent-id]
+                (map (fn [b]
+                       (let [children (nodes (:db/id b))]
+                         (if (seq children)
+                           (assoc b :block/children children)
+                           b)))
+                  (children {:db/id parent-id})))]
+    (nodes root-id)))
+
+(defn non-consecutive-blocks->vec-tree
+  "`blocks` need to be in the same page."
+  [blocks]
+  (let [blocks (map (fn [e] {:db/id (:db/id e)
+                             :block/uuid (:block/uuid e)
+                             :block/parent {:db/id (:db/id (:block/parent e))}
+                             :block/page {:db/id (:db/id (:block/page e))}}) blocks)
+        blocks (model/sort-page-random-blocks blocks)
+        id->parent (zipmap (map :db/id blocks)
+                           (map (comp :db/id :block/parent) blocks))
+        top-level-ids (set (remove #(id->parent (id->parent %)) (map :db/id blocks)))
+        blocks' (loop [blocks blocks
+                       result []]
+                  (if-let [block (first blocks)]
+                    (if (top-level-ids (:db/id block))
+                      (recur (rest blocks) (conj result [block]))
+                      (recur (rest blocks) (conj (vec (butlast result)) (conj (last result) block))))
+                    result))]
+    (map (fn [[parent & children]]
+           (if (seq children)
+             (assoc parent :block/children
+                    (tree children (:db/id parent)))
+             parent)) blocks')))
+
 (defn- sort-blocks-aux
   [parents parent-groups]
   (mapv (fn [parent]

+ 5 - 5
src/main/frontend/state.cljs

@@ -121,6 +121,7 @@
      :db/last-transact-time                 {}
      ;; whether database is persisted
      :db/persisted?                         {}
+
      :cursor-range                          nil
 
      :selection/mode                        false
@@ -151,7 +152,7 @@
      :mobile/show-toolbar?                  false
      :mobile/show-recording-bar?            false
      :mobile/show-tabbar?                   false
-     
+
      ;; plugin
      :plugin/enabled                        (and (util/electron?)
                                                  ;; true false :theme-only
@@ -1631,11 +1632,10 @@
   []
   (:modal/id @state))
 
-(defn edit-in-query-component
+(defn edit-in-query-or-refs-component
   []
-  (and (editing?)
-       ;; config
-       (:custom-query? (last (get-editor-args)))))
+  (let [config (last (get-editor-args))]
+    (or (:custom-query? config) (:ref? config))))
 
 (defn set-auth-id-token
   [id-token]

+ 23 - 12
src/main/frontend/ui.cljs

@@ -639,13 +639,20 @@
                  (let [args (:rum/args state)]
                    (when (true? (:default-collapsed? (last args)))
                      (reset! (get state ::collapsed?) true)))
-                 state)}
-  [state header content {:keys [title-trigger?]}]
+                 state)
+   :did-mount (fn [state]
+                (when-let [f (:init-collapsed (last (:rum/args state)))]
+                  (f (::collapsed? state)))
+                state)}
+  [state header content {:keys [title-trigger? on-mouse-down
+                                _default-collapsed? _init-collapsed]}]
   (let [control? (get state ::control?)
         collapsed? (get state ::collapsed?)
         on-mouse-down (fn [e]
                         (util/stop e)
-                        (swap! collapsed? not))]
+                        (swap! collapsed? not)
+                        (when on-mouse-down
+                          (on-mouse-down @collapsed?)))]
     [:div.flex.flex-col
      [:div.content
       [:div.flex-1.flex-row.foldable-title (cond->
@@ -928,21 +935,25 @@
 (rum/defc lazy-visible
   ([content-fn]
    (lazy-visible content-fn nil))
-  ([content-fn _debug-id]
+  ([content-fn {:keys [trigger-once? _debug-id]
+                :or {trigger-once? false}}]
    (if (or (util/mobile?) (mobile-util/native-platform?))
      (content-fn)
-     (let [[hasBeenSeen setHasBeenSeen] (rum/use-state false)
+     (let [[visible? set-visible!] (rum/use-state false)
            [last-changed-time set-last-changed-time!] (rum/use-state nil)
            inViewState (useInView #js {:rootMargin "100px"
+                                       :triggerOnce trigger-once?
                                        :onChange (fn [in-view? entry]
                                                    (let [self-top (.-top (.-boundingClientRect entry))
                                                          time' (util/time-ms)]
-                                                     (when (or in-view?
-                                                               (and
-                                                                (nil? last-changed-time)
-                                                                (> (- time' last-changed-time) 50)
-                                                                (<= self-top 0)))
+                                                     (when (and
+                                                            (or (and (not visible?) in-view?)
+                                                                ;; hide only the components below the current top for better ux
+                                                                (and visible? (not in-view?) (> self-top 0)))
+                                                            (or (nil? last-changed-time)
+                                                                (and (some? last-changed-time)
+                                                                     (> (- time' last-changed-time) 50))))
                                                        (set-last-changed-time! time')
-                                                       (setHasBeenSeen in-view?))))})
+                                                       (set-visible! in-view?))))})
            ref (.-ref inViewState)]
-       (lazy-visible-inner hasBeenSeen content-fn ref)))))
+       (lazy-visible-inner visible? content-fn ref)))))

+ 8 - 14
src/main/frontend/ui.css

@@ -110,13 +110,16 @@
       padding: 2rem;
 
       @screen md {
-        min-width: 760px;
+        min-width: 768px;
         width: auto;
 
-        .ls-card {
+        .ls-card, .ls-search {
           width: 740px;
           min-height: 60vh;
-          max-height: 740px;
+        }
+
+        .ls-search {
+          width: var(--ls-main-content-max-width);
         }
       }
     }
@@ -128,15 +131,6 @@
     transition ease-in-out duration-150;
   }
 
-  &[label="ls-modal-align-center"] {
-    top: 0;
-
-    .ui__modal-panel {
-      top: 50vh;
-      transform: translateY(-60%);
-    }
-  }
-
   &[label="diff__cp"] {
       .panel-content {
           padding: 2rem 1rem;
@@ -150,7 +144,7 @@
   &[label="flashcards__cp"] {
       .panel-content {
           padding: 2rem 0rem;
-          
+
           @screen sm {
               padding: 2rem 2rem;
           }
@@ -162,7 +156,7 @@
       .ui__modal-panel {
           height: 90%;
       }
-    
+
       .panel-content {
           height: 100%;
       }

+ 9 - 0
src/main/frontend/util.cljc

@@ -1034,6 +1034,15 @@
             res#))
         (do ~@body))))
 
+#?(:clj
+   (defmacro with-time
+     "Evaluates expr and prints the time it took. Returns the value of expr and the spent time."
+     [expr]
+     `(let [start# (cljs.core/system-time)
+            ret# ~expr]
+        {:result ret#
+         :time (.toFixed (- (cljs.core/system-time) start#) 6)})))
+
 ;; TODO: profile and profileEnd
 
 ;; Copy from hiccup

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

@@ -617,7 +617,7 @@
   (when-let [page (and page-name-or-uuid (db-model/get-page page-name-or-uuid))]
     (let [page-name (:block/name page)
           ref-blocks (if page-name
-                       (db-model/get-page-referenced-blocks page-name)
+                       (db-model/get-page-referenced-blocks-full page-name)
                        (db-model/get-block-referenced-blocks (:block/uuid page)))
           ref-blocks (and (seq ref-blocks) (into [] ref-blocks))]
       (bean/->js (normalize-keyword-for-json ref-blocks)))))

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

@@ -401,6 +401,19 @@ tags: other
                 (map :block/content)))
         "NOT query")))
 
+(deftest nested-page-ref-queries
+  (load-test-files [{:file/path "pages/page1.md"
+                     :file/content "foo:: bar
+- p1 [[Parent page]]
+  - [[Child page]]
+- p2 [[Parent page]]
+  - Non linked content"}])
+  (is (= ["Non linked content"
+          "p2 [[Parent page]]"
+          "p1 [[Parent page]]"]
+         (map :block/content
+              (dsl-query "(and [[Parent page]] (not [[Child page]]))")))))
+
 (defn- load-test-files-with-timestamps
   []
   (let [files [{:file/path "journals/2020_12_26.md"

+ 11 - 6
src/test/frontend/modules/outliner/core_test.cljs

@@ -70,10 +70,11 @@
 
 (defn transact-tree!
   [tree]
-  (db/transact! test-db (concat [{:db/id 1
-                                  :block/uuid 1
-                                  :block/name "Test page"}]
-                                (build-blocks tree))))
+  (let [blocks (build-blocks tree)]
+    (db/transact! test-db (concat [{:db/id 1
+                                    :block/uuid 1
+                                    :block/name "Test page"}]
+                                  blocks))))
 
 (def tree
   [[22 [[2 [[3 [[4]
@@ -625,6 +626,10 @@
   (dotimes [i 5]
     (do
       (frontend.test.fixtures/reset-datascript test-db)
-      (cljs.test/run-tests))
-    )
+      (cljs.test/run-tests)))
+
+  (do
+    (frontend.test.fixtures/reset-datascript test-db)
+    (cljs.test/test-vars [#'random-deletes]))
+
   )