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

Merge remote-tracking branch 'upstream/master' into whiteboards

Peng Xiao 3 лет назад
Родитель
Сommit
b30adea656
80 измененных файлов с 1801 добавлено и 1315 удалено
  1. 3 1
      .clj-kondo/config.edn
  2. 2 2
      deps/db/src/logseq/db/rules.cljc
  3. 3 1
      deps/db/src/logseq/db/schema.cljs
  4. 14 0
      deps/graph-parser/.carve/ignore
  5. 3 1
      deps/graph-parser/.clj-kondo/config.edn
  6. 41 37
      deps/graph-parser/src/logseq/graph_parser.cljs
  7. 155 124
      deps/graph-parser/src/logseq/graph_parser/block.cljs
  8. 15 13
      deps/graph-parser/src/logseq/graph_parser/extract.cljc
  9. 4 2
      deps/graph-parser/src/logseq/graph_parser/property.cljs
  10. 2 2
      deps/graph-parser/src/logseq/graph_parser/test/docs_graph_helper.cljs
  11. 24 50
      deps/graph-parser/src/logseq/graph_parser/text.cljs
  12. 41 0
      deps/graph-parser/src/logseq/graph_parser/util/block_ref.cljs
  13. 39 0
      deps/graph-parser/src/logseq/graph_parser/util/page_ref.cljs
  14. 2 0
      deps/graph-parser/test/logseq/graph_parser/nbb_test_runner.cljs
  15. 0 8
      deps/graph-parser/test/logseq/graph_parser/text_test.cljs
  16. 12 0
      deps/graph-parser/test/logseq/graph_parser/util/page_ref_test.cljs
  17. 18 10
      src/main/frontend/commands.cljs
  18. 141 107
      src/main/frontend/components/block.cljs
  19. 20 4
      src/main/frontend/components/command_palette.css
  20. 3 2
      src/main/frontend/components/content.cljs
  21. 3 2
      src/main/frontend/components/datetime.cljs
  22. 3 1
      src/main/frontend/components/editor.cljs
  23. 7 1
      src/main/frontend/components/journal.cljs
  24. 7 3
      src/main/frontend/components/page.cljs
  25. 119 131
      src/main/frontend/components/reference.cljs
  26. 1 1
      src/main/frontend/components/reference.css
  27. 34 0
      src/main/frontend/components/scheduled_deadlines.cljs
  28. 28 30
      src/main/frontend/components/search.cljs
  29. 21 12
      src/main/frontend/components/select.cljs
  30. 4 2
      src/main/frontend/components/shortcut.cljs
  31. 7 5
      src/main/frontend/components/sidebar.cljs
  32. 2 5
      src/main/frontend/db.cljs
  33. 109 124
      src/main/frontend/db/model.cljs
  34. 13 10
      src/main/frontend/db/query_custom.cljs
  35. 37 35
      src/main/frontend/db/query_dsl.cljs
  36. 7 19
      src/main/frontend/db/query_react.cljs
  37. 81 64
      src/main/frontend/db/react.cljs
  38. 23 2
      src/main/frontend/dicts.cljc
  39. 1 1
      src/main/frontend/diff.cljs
  40. 2 1
      src/main/frontend/extensions/pdf/assets.cljs
  41. 173 148
      src/main/frontend/extensions/srs.cljs
  42. 11 15
      src/main/frontend/extensions/zotero/extractor.cljs
  43. 3 2
      src/main/frontend/extensions/zotero/handler.cljs
  44. 6 5
      src/main/frontend/external/roam.cljs
  45. 5 14
      src/main/frontend/format/block.cljs
  46. 5 2
      src/main/frontend/fs/nfs.cljs
  47. 2 2
      src/main/frontend/fs/watcher_handler.cljs
  48. 1 2
      src/main/frontend/handler.cljs
  49. 42 54
      src/main/frontend/handler/block.cljs
  50. 3 3
      src/main/frontend/handler/dnd.cljs
  51. 76 60
      src/main/frontend/handler/editor.cljs
  52. 4 0
      src/main/frontend/handler/events.cljs
  53. 6 13
      src/main/frontend/handler/export.cljs
  54. 1 1
      src/main/frontend/handler/external.cljs
  55. 38 34
      src/main/frontend/handler/file.cljs
  56. 24 22
      src/main/frontend/handler/page.cljs
  57. 7 6
      src/main/frontend/handler/paste.cljs
  58. 6 6
      src/main/frontend/handler/recent.cljs
  59. 13 14
      src/main/frontend/handler/route.cljs
  60. 2 3
      src/main/frontend/mobile/action_bar.cljs
  61. 2 1
      src/main/frontend/mobile/intent.cljs
  62. 10 6
      src/main/frontend/modules/editor/undo_redo.cljs
  63. 8 5
      src/main/frontend/modules/outliner/core.cljs
  64. 1 2
      src/main/frontend/modules/outliner/datascript.cljc
  65. 71 11
      src/main/frontend/modules/outliner/pipeline.cljs
  66. 37 0
      src/main/frontend/modules/outliner/tree.cljs
  67. 50 0
      src/main/frontend/modules/shortcut/dicts.cljc
  68. 5 5
      src/main/frontend/state.cljs
  69. 7 8
      src/main/frontend/template.cljs
  70. 23 12
      src/main/frontend/ui.cljs
  71. 8 14
      src/main/frontend/ui.css
  72. 9 0
      src/main/frontend/util.cljc
  73. 10 8
      src/main/frontend/util/property.cljs
  74. 3 6
      src/main/frontend/util/text.cljs
  75. 8 6
      src/main/frontend/util/thingatpt.cljs
  76. 1 1
      src/main/logseq/api.cljs
  77. 13 0
      src/test/frontend/db/query_dsl_test.cljs
  78. 32 0
      src/test/frontend/handler/editor_test.cljs
  79. 11 6
      src/test/frontend/modules/outliner/core_test.cljs
  80. 13 5
      src/test/frontend/util/property_test.cljs

+ 3 - 1
.clj-kondo/config.edn

@@ -32,7 +32,9 @@
              logseq.graph-parser.util gp-util
              logseq.graph-parser.property gp-property
              logseq.graph-parser.config gp-config
-             logseq.graph-parser.date-time-util date-time-util}}}
+             logseq.graph-parser.date-time-util date-time-util
+             logseq.graph-parser.util.page-ref page-ref
+             logseq.graph-parser.util.block-ref block-ref}}}
 
  :hooks {:analyze-call {rum.core/defc hooks.rum/defc
                         rum.core/defcs hooks.rum/defcs}}

+ 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}
 

+ 14 - 0
deps/graph-parser/.carve/ignore

@@ -4,3 +4,17 @@ logseq.graph-parser.cli/parse-graph
 logseq.graph-parser.mldoc/ast-export-markdown
 ;; API
 logseq.graph-parser.property/register-built-in-properties
+;; API
+logseq.graph-parser.util.block-ref/left-and-right-parens
+;; API
+logseq.graph-parser.util.block-ref/->block-ref
+;; API
+logseq.graph-parser.util.block-ref/block-ref?
+;; API
+logseq.graph-parser.util.block-ref/get-all-block-ref-ids
+;; API
+logseq.graph-parser.util.page-ref/left-and-right-brackets
+;; API
+logseq.graph-parser.util.page-ref/->page-ref
+;; API
+logseq.graph-parser.util.page-ref/get-page-name!

+ 3 - 1
deps/graph-parser/.clj-kondo/config.edn

@@ -9,6 +9,8 @@
              logseq.graph-parser.util gp-util
              logseq.graph-parser.property gp-property
              logseq.graph-parser.config gp-config
-             logseq.graph-parser.date-time-util date-time-util}}}
+             logseq.graph-parser.date-time-util date-time-util
+             logseq.graph-parser.util.page-ref page-ref
+             logseq.graph-parser.util.block-ref block-ref}}}
  :skip-comments true
  :output {:progress true}}

+ 41 - 37
deps/graph-parser/src/logseq/graph_parser.cljs

@@ -23,42 +23,47 @@
                       :as options}]
   (db-set-file-content! conn file content)
   (let [format (gp-util/get-format file)
-        file-content [(cond-> {:file/path file}
-                        new?
-                        ;; TODO: use file system timestamp?
-                        (assoc :file/created-at (date-time-util/time-ms)))]
-        extract-options' (merge {:block-pattern (gp-config/get-block-pattern format)
-                                 :date-formatter "MMM do, yyyy"
-                                 :supported-formats (gp-config/supported-formats)}
-                                extract-options
-                                {:db @conn})
-        {:keys [pages blocks ast]
-         :or   {pages []
-                blocks []
-                ast []}}
-        (cond (contains? gp-config/mldoc-support-formats format)
-              (extract/extract file content extract-options')
+        file-content [{:file/path file}]
+        {:keys [tx ast]}
+        (if (contains? gp-config/mldoc-support-formats format)
+          (let [extract-options' (merge {:block-pattern (gp-config/get-block-pattern format)
+                                         :date-formatter "MMM do, yyyy"
+                                         :supported-formats (gp-config/supported-formats)}
+                                        extract-options
+                                        {:db @conn})
+                {:keys [pages blocks ast]
+                 :or   {pages []
+                        blocks []
+                        ast []}}
+                (cond (contains? gp-config/mldoc-support-formats format)
+                      (extract/extract file content extract-options')
 
-              (gp-config/whiteboard? file)
-              (extract/extract-whiteboard-edn file content extract-options')
+                      (gp-config/whiteboard? file)
+                      (extract/extract-whiteboard-edn file content extract-options')
 
-              :else nil)
-
-        delete-blocks (delete-blocks-fn (first pages) file)
-        block-ids (map (fn [block] {:block/uuid (:block/uuid block)}) blocks)
-        block-refs-ids (->> (mapcat :block/refs blocks)
-                            (filter (fn [ref] (and (vector? ref)
-                                                   (= :block/uuid (first ref)))))
-                            (map (fn [ref] {:block/uuid (second ref)}))
-                            (seq))
-        ;; To prevent "unique constraint" on datascript
-        block-ids (set/union (set block-ids) (set block-refs-ids))
-        pages (extract/with-ref-pages pages blocks)
-        pages-index (map #(select-keys % [:block/name]) pages)
-
-        tx (concat file-content pages-index delete-blocks pages block-ids blocks)]
-    {:tx
-     (d/transact! conn (gp-util/remove-nils tx) (select-keys options [:new-graph? :from-disk?]))
+                      :else nil)
+                delete-blocks (delete-blocks-fn (first pages) file)
+                block-ids (map (fn [block] {:block/uuid (:block/uuid block)}) blocks)
+                block-refs-ids (->> (mapcat :block/refs blocks)
+                                    (filter (fn [ref] (and (vector? ref)
+                                                           (= :block/uuid (first ref)))))
+                                    (map (fn [ref] {:block/uuid (second ref)}))
+                                    (seq))
+                   ;; To prevent "unique constraint" on datascript
+                block-ids (set/union (set block-ids) (set block-refs-ids))
+                pages (extract/with-ref-pages pages blocks)
+                pages-index (map #(select-keys % [:block/name]) pages)]
+               ;; does order matter?
+            {:tx (concat file-content pages-index delete-blocks pages block-ids blocks)
+             :ast ast})
+          {:tx file-content})
+        tx (concat tx [(cond-> {:file/path file}
+                         new?
+                               ;; TODO: use file system timestamp?
+                         (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
@@ -72,9 +77,8 @@
                        files)
         support-files (sort-by :file/path support-files)
         {journals true non-journals false} (group-by (fn [file] (string/includes? (:file/path file) "journals/")) support-files)
-        {whiteboards true non-whiteboards false} (group-by (fn [file] (string/includes? (:file/path file) "whiteboards/")) non-journals)
         {built-in true others false} (group-by (fn [file]
                                                  (or (string/includes? (:file/path file) "contents.")
                                                      (string/includes? (:file/path file) ".edn")
-                                                     (string/includes? (:file/path file) "custom.css"))) non-whiteboards)]
-    (concat (reverse journals) (reverse whiteboards) built-in others)))
+                                                     (string/includes? (:file/path file) "custom.css"))) non-journals)]
+    (concat (reverse journals) built-in others)))

+ 155 - 124
deps/graph-parser/src/logseq/graph_parser/block.cljs

@@ -10,7 +10,9 @@
             [logseq.graph-parser.property :as gp-property]
             [logseq.graph-parser.text :as text]
             [logseq.graph-parser.utf8 :as utf8]
-            [logseq.graph-parser.util :as gp-util]))
+            [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.util.block-ref :as block-ref]
+            [logseq.graph-parser.util.page-ref :as page-ref]))
 
 (defn heading-block?
   [block]
@@ -49,7 +51,7 @@
 
                   (and
                    (= typ "Search")
-                   (text/page-ref? value)
+                   (page-ref/page-ref? value)
                    (text/page-ref-un-brackets! value))
 
                   (and
@@ -91,7 +93,7 @@
 
                :else
                nil)]
-    (text/block-ref-un-brackets! page)))
+    (when page (or (block-ref/get-block-ref-id page) page))))
 
 (defn- get-block-reference
   [block]
@@ -111,9 +113,8 @@
                         (let [{:keys [name arguments]} (second block)]
                           (when (and (= name "embed")
                                      (string? (first arguments))
-                                     (string/starts-with? (first arguments) "((")
-                                     (string/ends-with? (first arguments) "))"))
-                            (subs (first arguments) 2 (- (count (first arguments)) 2))))
+                                     (block-ref/string-block-ref? (first arguments)))
+                            (block-ref/get-string-block-ref-id (first arguments))))
 
                         (and (vector? block)
                              (= "Link" (first block))
@@ -121,7 +122,9 @@
                         (if (= "id" (:protocol (second (:url (second block)))))
                           (:link (second (:url (second block))))
                           (let [id (second (:url (second block)))]
-                            (text/block-ref-un-brackets! id)))
+                            ;; these can be maps
+                            (when (string? id)
+                              (or (block-ref/get-block-ref-id id) id))))
 
                         :else
                         nil)]
@@ -395,53 +398,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 +447,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 +539,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 +602,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 +630,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 +645,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

@@ -103,19 +103,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)

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

@@ -6,6 +6,8 @@
             [goog.string :as gstring]
             [goog.string.format]))
 
+(def colons "Property delimiter for markdown mode" "::")
+
 (defn properties-ast?
   [block]
   (and
@@ -45,7 +47,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))
 
@@ -77,7 +79,7 @@
                                            compare-k (keyword (string/lower-case k))
                                            k (if (contains? #{:id :custom_id :custom-id} compare-k) "id" k)
                                            k (if (contains? #{:last-modified-at} compare-k) "updated-at" k)]
-                                       (str k ":: " (string/trim v)))
+                                       (str k colons " " (string/trim v)))
                                      text)))))
               after (subvec lines (inc end-idx))
               lines (concat before middle after)]

+ 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

+ 24 - 50
deps/graph-parser/src/logseq/graph_parser/text.cljs

@@ -4,11 +4,8 @@
             [clojure.string :as string]
             [clojure.set :as set]
             [logseq.graph-parser.mldoc :as gp-mldoc]
-            [logseq.graph-parser.util :as gp-util]))
-
-(def page-ref-re-0 #"\[\[(.*)\]\]")
-(def org-page-ref-re #"\[\[(file:.*)\]\[.+?\]\]")
-(def markdown-page-ref-re #"\[(.*)\]\(file:.*\)")
+            [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.util.page-ref :as page-ref :refer [right-brackets]]))
 
 (defn get-file-basename
   [path]
@@ -16,7 +13,14 @@
     ;; Same as util/node-path.name
     (.-name (path/parse (string/replace path "+" "/")))))
 
+(def page-ref-re-0 #"\[\[(.*)\]\]")
+(def org-page-ref-re #"\[\[(file:.*)\]\[.+?\]\]")
+(def markdown-page-ref-re #"\[(.*)\]\(file:.*\)")
+
 (defn get-page-name
+  "Extracts page names from format-specific page-refs e.g. org/md specific and
+  logseq page-refs. Only call in contexts where format-specific page-refs are
+  used. For logseq page-refs use page-ref/get-page-name"
   [s]
   (and (string? s)
        (or (when-let [[_ label _path] (re-matches markdown-page-ref-re s)]
@@ -27,41 +31,10 @@
            (-> (re-matches page-ref-re-0 s)
                second))))
 
-(defn page-ref?
-  [s]
-  (and
-   (string? s)
-   (string/starts-with? s "[[")
-   (string/ends-with? s "]]")))
-
-(def block-ref-re #"\(\(([a-zA-z0-9]{8}-[a-zA-z0-9]{4}-[a-zA-z0-9]{4}-[a-zA-z0-9]{4}-[a-zA-z0-9]{12})\)\)")
-
-(defn get-block-ref
-  [s]
-  (and (string? s)
-       (second (re-matches block-ref-re s))))
-
-(defn block-ref?
-  [s]
-  (boolean (get-block-ref s)))
-
-(defonce page-ref-re #"\[\[(.*?)\]\]")
-
-(defonce page-ref-re-2 #"(\[\[.*?\]\])")
-
-(def page-ref-re-without-nested #"\[\[([^\[\]]+)\]\]")
-
 (defn page-ref-un-brackets!
   [s]
   (or (get-page-name s) s))
 
-(defn block-ref-un-brackets!
-  [s]
-  (when (string? s)
-    (if (block-ref? s)
-      (subs s 2 (- (count s) 2))
-      s)))
-
 ;; E.g "Foo Bar"
 (defn sep-by-comma
   [s]
@@ -82,18 +55,18 @@
 (defn- not-matched-nested-pages
   [s]
   (and (string? s)
-       (> (count (re-seq #"\[\[" s))
-          (count (re-seq #"\]\]" s)))))
+       (> (count (re-seq page-ref/left-brackets-re s))
+          (count (re-seq page-ref/right-brackets-re s)))))
 
 (defn- ref-matched?
   [s]
-  (let [x (re-seq #"\[\[" s)
-        y (re-seq #"\]\]" s)]
+  (let [x (re-seq page-ref/left-brackets-re s)
+        y (re-seq page-ref/right-brackets-re s)]
     (and (> (count x) 0) (= (count x) (count y)))))
 
 (defn get-nested-page-name
   [page-name]
-  (when-let [first-match (re-find page-ref-re-without-nested page-name)]
+  (when-let [first-match (re-find page-ref/page-ref-without-nested-re page-name)]
     (second first-match)))
 
 (defn- concat-nested-pages
@@ -101,7 +74,7 @@
   (first
    (reduce (fn [[acc not-matched-s] s]
              (cond
-               (and not-matched-s (= s "]]"))
+               (and not-matched-s (= s right-brackets))
                (let [s' (str not-matched-s s)]
                  (if (ref-matched? s')
                    [(conj acc s') nil]
@@ -136,30 +109,31 @@
 
      (and (string? s)
             ;; Either a page ref, a tag or a comma separated collection
-            (or (gp-util/safe-re-find page-ref-re s)
+            (or (gp-util/safe-re-find page-ref/page-ref-re s)
                 (gp-util/safe-re-find #"[\,|,|#|\"]+" s)))
      (let [result (->> (sep-by-quotes s)
                        (mapcat
                         (fn [s]
                           (when-not (gp-util/wrapped-by-quotes? (string/trim s))
-                            (string/split s page-ref-re-2))))
+                            (string/split s page-ref/page-ref-outer-capture-re))))
                        (mapcat (fn [s]
                                  (cond
                                    (gp-util/wrapped-by-quotes? s)
                                    nil
 
-                                   (string/includes? (string/trimr s) "]],")
-                                   (let [idx (string/index-of s "]],")]
+                                   (string/includes? (string/trimr s)
+                                                     (str right-brackets ","))
+                                   (let [idx (string/index-of s (str right-brackets ","))]
                                      [(subs s 0 idx)
-                                      "]]"
+                                      right-brackets
                                       (subs s (+ idx 3))])
 
                                    :else
                                    [s])))
                        (remove #(= % ""))
-                       (mapcat (fn [s] (if (string/ends-with? s "]]")
+                       (mapcat (fn [s] (if (string/ends-with? s right-brackets)
                                          [(subs s 0 (- (count s) 2))
-                                          "]]"]
+                                          right-brackets]
                                          [s])))
                        concat-nested-pages
                        (remove string/blank?)
@@ -168,7 +142,7 @@
                                    (gp-util/wrapped-by-quotes? s)
                                    nil
 
-                                   (page-ref? s)
+                                   (page-ref/page-ref? s)
                                    [(if un-brackets? (page-ref-un-brackets! s) s)]
 
                                    :else

+ 41 - 0
deps/graph-parser/src/logseq/graph_parser/util/block_ref.cljs

@@ -0,0 +1,41 @@
+(ns logseq.graph-parser.util.block-ref
+  "General purpose vars and util fns for block-refs"
+  (:require [clojure.string :as string]))
+
+(def left-parens "Opening characters for block-ref" "((")
+(def right-parens "Closing characters for block-ref" "))")
+(def left-and-right-parens "Opening and closing characters for block-ref"
+  (str left-parens right-parens))
+(def block-ref-re #"\(\(([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})\)\)")
+
+(defn get-all-block-ref-ids
+  [content]
+  (map second (re-seq block-ref-re content)))
+
+(defn get-block-ref-id
+  "Extracts block id from block-ref using regex"
+  [s]
+  (second (re-matches block-ref-re s)))
+
+(defn get-string-block-ref-id
+  "Extracts block id from block-ref by stripping parens e.g. ((123)) -> 123.
+  This is a less strict version of get-block-ref-id"
+  [s]
+  (subs s 2 (- (count s) 2)))
+
+(defn block-ref?
+  "Determines if string is block ref using regex"
+  [s]
+  (boolean (get-block-ref-id s)))
+
+(defn string-block-ref?
+  "Determines if string is block ref by checking parens. This is less strict version
+of block-ref?"
+  [s]
+  (and (string/starts-with? s left-parens)
+       (string/ends-with? s right-parens)))
+
+(defn ->block-ref
+  "Creates block ref string given id"
+  [block-id]
+  (str left-parens block-id right-parens))

+ 39 - 0
deps/graph-parser/src/logseq/graph_parser/util/page_ref.cljs

@@ -0,0 +1,39 @@
+(ns logseq.graph-parser.util.page-ref
+  "General purpose vars and util fns for page-ref. Currently this only handles
+a logseq page-ref e.g. [[page name]]"
+  (:require [clojure.string :as string]))
+
+(def left-brackets "Opening characters for page-ref" "[[")
+(def right-brackets "Closing characters for page-ref" "]]")
+(def left-and-right-brackets "Opening and closing characters for page-ref"
+  (str left-brackets right-brackets))
+
+;; common regular expressions
+(def left-brackets-re #"\[\[")
+(def right-brackets-re #"\]\]")
+(def page-ref-re "Inner capture and doesn't match nested brackets" #"\[\[(.*?)\]\]")
+(def page-ref-outer-capture-re #"(\[\[.*?\]\])")
+(def page-ref-without-nested-re "Matches most inner nested brackets" #"\[\[([^\[\]]+)\]\]")
+(def page-ref-any-re "Inner capture that matches anything between brackets" #"\[\[(.*)\]\]")
+
+(defn page-ref?
+  "Determines if string is page-ref. Avoid using with format-specific page-refs e.g. org"
+  [s]
+  (and (string/starts-with? s left-brackets)
+       (string/ends-with? s right-brackets)))
+
+(defn ->page-ref
+  "Create a page ref given a page name"
+  [page-name]
+  (str left-brackets page-name right-brackets))
+
+(defn get-page-name
+  "Extracts page-name from page-ref string"
+  [s]
+  (second (re-matches page-ref-any-re s)))
+
+(defn get-page-name!
+  "Extracts page-name from page-ref and fall back to arg. Useful for when user
+  input may (not) be a page-ref"
+  [s]
+  (or (get-page-name s) s))

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

@@ -7,6 +7,7 @@
             [logseq.graph-parser.property-test]
             [logseq.graph-parser.extract-test]
             [logseq.graph-parser.cli-test]
+            [logseq.graph-parser.util.page-ref-test]
             [logseq.graph-parser-test]))
 
 (defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m]
@@ -21,4 +22,5 @@
                'logseq.graph-parser.block-test
                'logseq.graph-parser.extract-test
                'logseq.graph-parser.cli-test
+               'logseq.graph-parser.util.page-ref-test
                'logseq.graph-parser-test))

+ 0 - 8
deps/graph-parser/test/logseq/graph_parser/text_test.cljs

@@ -26,14 +26,6 @@
          "[logseq/page](file:./logseq.page.md)" "logseq/page"
          "[logseq/page](file:./pages/logseq.page.md)" "logseq/page"))
 
-(deftest page-ref?
-  []
-  (are [x y] (= (text/page-ref? x) y)
-    "[[page]]" true
-    "[[another page]]" true
-    "[single bracket]" false
-    "no brackets" false))
-
 (deftest page-ref-un-brackets!
   []
   (are [x y] (= (text/page-ref-un-brackets! x) y)

+ 12 - 0
deps/graph-parser/test/logseq/graph_parser/util/page_ref_test.cljs

@@ -0,0 +1,12 @@
+(ns logseq.graph-parser.util.page-ref-test
+  (:require [logseq.graph-parser.util.page-ref :as page-ref]
+            [cljs.test :refer [are deftest]]))
+
+(deftest page-ref?
+  []
+  (are [x y] (= (page-ref/page-ref? x) y)
+       "[[page]]" true
+       "[[another page]]" true
+       "[[some [[nested]] page]]" true
+       "[single bracket]" false
+       "no brackets" false))

+ 18 - 10
src/main/frontend/commands.cljs

@@ -16,6 +16,10 @@
             [frontend.util.priority :as priority]
             [frontend.util.property :as property]
             [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.config :as gp-config]
+            [logseq.graph-parser.property :as gp-property]
+            [logseq.graph-parser.util.page-ref :as page-ref]
+            [logseq.graph-parser.util.block-ref :as block-ref]
             [goog.dom :as gdom]
             [goog.object :as gobj]))
 
@@ -213,10 +217,10 @@
   (->>
    (concat
     ;; basic
-    [["Page reference" [[:editor/input "[[]]" {:backward-pos 2}]
+    [["Page reference" [[:editor/input page-ref/left-and-right-brackets {:backward-pos 2}]
                         [:editor/search-page]] "Create a backlink to a page"]
      ["Page embed" (embed-page) "Embed a page here"]
-     ["Block reference" [[:editor/input "(())" {:backward-pos 2}]
+     ["Block reference" [[:editor/input block-ref/left-and-right-parens {:backward-pos 2}]
                          [:editor/search-block :reference]] "Create a backlink to a block"]
      ["Block embed" (embed-block) "Embed a block here" "Embed a block here"]
      ["Link" (link-steps) "Create a HTTP link"]
@@ -269,9 +273,13 @@
      ["Query table function" [[:editor/input "{{function }}" {:backward-pos 2}]] "Create a query table function"]
      ["Calculator" [[:editor/input "```calc\n\n```" {:backward-pos 4}]
                     [:codemirror/focus]] "Insert a calculator"]
-     
-     ["Draw" draw-handler/initialize-excalidarw-file "Draw a graph with Excalidraw"]
-     
+     ["Draw" (fn []
+               (let [file (draw/file-name)
+                     path (str gp-config/default-draw-directory "/" file)
+                     text (page-ref/->page-ref path)]
+                 (p/let [_ (draw/create-draw-with-default-content path)]
+                   (println "draw file created, " path))
+                 text)) "Draw a graph with Excalidraw"]
      ["Embed HTML " (->inline "html")]
 
      ["Embed Video URL" [[:editor/input "{{video }}" {:last-pattern (state/get-editor-command-trigger)
@@ -334,12 +342,12 @@
                                    (or
                                     (and s
                                          (string/ends-with? s "(")
-                                         (or (string/starts-with? last-pattern "((")
-                                             (string/starts-with? last-pattern "[[")))
+                                         (or (string/starts-with? last-pattern block-ref/left-parens)
+                                             (string/starts-with? last-pattern page-ref/left-brackets)))
                                     (and s (string/starts-with? s "{{embed"))
                                     (and last-pattern
-                                         (or (string/ends-with? last-pattern "::")
-                                             (string/starts-with? last-pattern "::")))))))]
+                                         (or (string/ends-with? last-pattern gp-property/colons)
+                                             (string/starts-with? last-pattern gp-property/colons)))))))]
                    (if (and space? (string/starts-with? last-pattern "#[["))
                      false
                      space?))
@@ -373,7 +381,7 @@
         (state/set-block-content-and-last-pos! id new-value new-pos)
         (cursor/move-cursor-to input
                                (if (and (or backward-pos forward-pos)
-                                        (not= end-pattern "]]"))
+                                        (not= end-pattern page-ref/right-brackets))
                                  new-pos
                                  (inc new-pos)))))))
 

+ 141 - 107
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]
@@ -63,6 +64,8 @@
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.text :as text]
             [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.util.page-ref :as page-ref]
+            [logseq.graph-parser.util.block-ref :as block-ref]
             [medley.core :as medley]
             [promesa.core :as p]
             [reitit.frontend.easy :as rfe]
@@ -644,7 +647,7 @@
        (when (and (or show-brackets? nested-link?)
                   (not html-export?)
                   (not contents-page?))
-         [:span.text-gray-500.bracket "[["])
+         [:span.text-gray-500.bracket page-ref/left-brackets])
        (when whiteboard? [:span.text-gray-500.ti.ti-artboard " "])
        (let [s (string/trim s)]
          (page-cp (assoc config
@@ -654,7 +657,7 @@
        (when (and (or show-brackets? nested-link?)
                   (not html-export?)
                   (not contents-page?))
-         [:span.text-gray-500.bracket "]]"])])))
+         [:span.text-gray-500.bracket page-ref/right-brackets])])))
 
 (defn- latex-environment-content
   [name option content]
@@ -742,7 +745,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)]
@@ -802,7 +806,7 @@
                         :delay       [1000, 100]} inner)
              inner)])
         [:span.warning.mr-1 {:title "Block ref invalid"}
-         (util/format "((%s))" id)]))))
+         (block-ref/->block-ref id)]))))
 
 (defn inline-text
   ([format v]
@@ -835,7 +839,7 @@
      (when (and show-brackets?
                 (not html-export?)
                 (not (= (:id config) "contents")))
-       [:span.text-gray-500 "[["])
+       [:span.text-gray-500 page-ref/left-brackets])
      (let [page-name (subs content 2 (- (count content) 2))]
        (page-cp (assoc config
                        :children children
@@ -843,7 +847,7 @@
      (when (and show-brackets?
                 (not html-export?)
                 (not (= (:id config) "contents")))
-       [:span.text-gray-500 "]]"])]))
+       [:span.text-gray-500 page-ref/right-brackets])]))
 
 (declare custom-query)
 
@@ -945,8 +949,8 @@
          (not= \* (last s)))
     (->elem :a {:on-click #(route-handler/jump-to-anchor! (mldoc/anchorLink (subs s 1)))} (subs s 1))
 
-    (text/block-ref? s)
-    (let [id (text/get-block-ref s)]
+    (block-ref/block-ref? s)
+    (let [id (block-ref/get-block-ref-id s)]
       (block-reference config id label))
 
     (not (string/includes? s "."))
@@ -1007,7 +1011,7 @@
           (image-link config url page nil metadata full_text)
           (let [label* (if (seq (mldoc/plain->text label)) label nil)]
             (if (and (string? page) (string/blank? page))
-              [:span (util/format "[[%s]]" page)]
+              [:span (page-ref/->page-ref page)]
               (page-reference (:html-export? config) page config label*)))))
 
       ["Embed_data" src]
@@ -1047,9 +1051,9 @@
                        (when-let [ext (util/get-file-ext href)]
                          (gp-config/mldoc-support? ext)))
                 [:span.page-reference
-                 (when show-brackets? [:span.text-gray-500 "[["])
+                 (when show-brackets? [:span.text-gray-500 page-ref/left-brackets])
                  (page-cp config page)
-                 (when show-brackets? [:span.text-gray-500 "]]"])]
+                 (when show-brackets? [:span.text-gray-500 page-ref/right-brackets])]
 
                 (let [href* (if (util/electron?)
                               (relative-assets-path->absolute-path href)
@@ -1129,18 +1133,14 @@
       (> link-depth max-depth-of-links)
       [:p.warning.text-sm "Embed depth is too deep"]
 
-      (and (string/starts-with? a "[[")
-           (string/ends-with? a "]]"))
+      (page-ref/page-ref? a)
       (let [page-name (text/get-page-name a)]
         (when-not (string/blank? page-name)
           (page-embed (assoc config :link-depth (inc link-depth)) page-name)))
 
-      (and (string/starts-with? a "((")
-           (string/ends-with? a "))"))
-      (when-let [s (-> (string/replace a "((" "")
-                       (string/replace "))" "")
-                       string/trim)]
-        (when-let [id (some-> s string/trim parse-uuid)]
+      (block-ref/string-block-ref? a)
+      (when-let [s (-> block-ref/get-string-block-ref-id string/trim)]
+        (when-let [id (some-> s parse-uuid)]
           (block-embed (assoc config :link-depth (inc link-depth)) id)))
 
       :else                         ;TODO: maybe collections?
@@ -1317,8 +1317,8 @@
   (let [{:keys [name arguments]} options
         arguments (if (and
                        (>= (count arguments) 2)
-                       (and (string/starts-with? (first arguments) "[[")
-                            (string/ends-with? (last arguments) "]]"))) ; page reference
+                       (and (string/starts-with? (first arguments) page-ref/left-brackets)
+                            (string/ends-with? (last arguments) page-ref/right-brackets))) ; page reference
                     (let [title (string/join ", " arguments)]
                       [title])
                     arguments)]
@@ -1332,7 +1332,7 @@
       (= name "namespace")
       (let [namespace (first arguments)]
         (when-not (string/blank? namespace)
-          (let [namespace (string/lower-case (text/page-ref-un-brackets! namespace))
+          (let [namespace (string/lower-case (page-ref/get-page-name! namespace))
                 children (model/get-namespace-hierarchy (state/get-current-repo) namespace)]
             (namespace-hierarchy config namespace children))))
 
@@ -1545,7 +1545,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?))
@@ -2172,10 +2173,10 @@
         editor-box (get config :editor-box)
         editor-id (str "editor-" edit-input-id)
         slide? (:slide? config)
-        trimmed-content (string/trim (:block/content block))
-        block-reference-only? (and (string/starts-with? trimmed-content "((")
-                                   (re-find (re-pattern util/uuid-pattern) trimmed-content)
-                                   (string/ends-with? trimmed-content "))"))]
+        block-reference-only? (some->
+                               (:block/content block)
+                               string/trim
+                               block-ref/block-ref?)]
     (if (and edit? editor-box)
       [:div.editor-wrapper {:id editor-id}
        (ui/catch-error
@@ -2477,6 +2478,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))
@@ -2495,13 +2499,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)
@@ -2518,19 +2515,21 @@
         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
-       :data-refs data-refs
-       :data-refs-self data-refs-self
-       :data-collapsed (and collapsed? has-child?)
-       :class (str uuid
-                   (when pre-block? " pre-block")
-                   (when (and card? (not review-cards?)) " shadow-md")
-                   (when (:ui/selected? block) " selected noselect"))
-       :blockid (str uuid)
-       :haschild (str has-child?)}
+       {:id block-id
+        :data-refs data-refs
+        :data-refs-self data-refs-self
+        :data-collapsed (and collapsed? has-child?)
+        :class (str uuid
+                    (when pre-block? " pre-block")
+                    (when (and card? (not review-cards?)) " shadow-md")
+                    (when selected? " selected noselect"))
+        :blockid (str uuid)
+        :haschild (str has-child?)}
 
        level
        (assoc :level level)
@@ -2602,7 +2601,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))
@@ -2815,23 +2814,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))
@@ -2874,29 +2899,30 @@
          (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
                                                              (get-in config [:block :block/format] :markdown)
                                                              title)
                                 :else title)]
-            [:span.opacity-60.text-sm.ml-2.results-count
-             (str (count transformed-query-result) " results")]]
+           [:span.opacity-60.text-sm.ml-2.results-count
+            (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?))
@@ -2961,7 +2987,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]
@@ -2969,7 +2998,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]
@@ -3252,14 +3281,8 @@
   (rum/local nil ::loading?)
   {:init (fn [state]
            (assoc state ::id (str (random-uuid))))}
-  [state config flat-blocks blocks->vec-tree]
+  [state config blocks flat-blocks]
   (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
       (block-list config blocks)
@@ -3293,9 +3316,7 @@
                    "")})]))))
 
 (rum/defcs blocks-container <
-  {:init (fn [state]
-           (assoc state
-                  ::init-blocks-container-id (atom nil)))}
+  {:init (fn [state] (assoc state ::init-blocks-container-id (atom nil)))}
   [state blocks config]
   (let [*init-blocks-container-id (::init-blocks-container-id state)
         blocks-container-id (if @*init-blocks-container-id
@@ -3306,12 +3327,17 @@
         config (assoc config :blocks-container-id blocks-container-id)
         doc-mode? (:document/mode? config)]
     (when (seq blocks)
-      (let [blocks->vec-tree #(if (custom-query-or-ref? config) % (tree/blocks->vec-tree % (:id config)))
-            flat-blocks (vec blocks)
+      (let [flat-blocks (vec blocks)
+            query-or-ref? (custom-query-or-ref? config)
+            id (if (:navigated? config) @(:navigating-block config) (:id config))
+            blocks' (if (or (and query-or-ref? (:navigated? config))
+                            (not query-or-ref?))
+                      (tree/blocks->vec-tree flat-blocks id)
+                      flat-blocks)
             config (assoc config :start-time (util/time-ms))]
         [:div.blocks-container.flex-1
          {:class (when doc-mode? "document-mode")}
-         (lazy-blocks config flat-blocks blocks->vec-tree)]))))
+         (lazy-blocks config blocks' flat-blocks)]))))
 
 (rum/defcs breadcrumb-with-container < rum/reactive
   {:init (fn [state]
@@ -3340,7 +3366,8 @@
                     :navigating-block *navigating-block}))
      (blocks-container blocks (assoc config
                                      :breadcrumb-show? false
-                                     :navigating-block *navigating-block))]))
+                                     :navigating-block *navigating-block
+                                     :navigated? navigated?))]))
 
 ;; headers to hiccup
 (defn ->hiccup
@@ -3349,42 +3376,49 @@
    (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))
-                whiteboard? (:block/whiteboard? 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"])]
-              (when-not whiteboard? (blocks-container blocks config))
-              {})])))]
+          (let [blocks (remove nil? blocks)]
+            (when (seq blocks)
+              (let [alias? (:block/alias? page)
+                    page (db/entity (:db/id page))
+                    whiteboard? (:block/whiteboard? 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"])]
+                  (when-not whiteboard? (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;
+    }
 }

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

@@ -22,6 +22,7 @@
             [frontend.ui :as ui]
             [frontend.util :as util]
             [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.util.block-ref :as block-ref]
             [frontend.util.url :as url-util]
             [goog.dom :as gdom]
             [goog.object :as gobj]
@@ -207,7 +208,7 @@
           (ui/menu-link
            {:key      "Copy block ref"
             :on-click (fn [_e]
-                        (editor-handler/copy-block-ref! block-id #(str "((" % "))")))}
+                        (editor-handler/copy-block-ref! block-id block-ref/->block-ref))}
            "Copy block ref")
 
           (ui/menu-link
@@ -238,7 +239,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"

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

@@ -10,7 +10,8 @@
             [frontend.ui :as ui]
             [frontend.util :as util]
             [frontend.mixins :as mixins]
-            [rum.core :as rum]))
+            [rum.core :as rum]
+            [logseq.graph-parser.util.page-ref :as page-ref]))
 
 (defonce default-timestamp-value {:time ""
                                   :repeater {}})
@@ -160,7 +161,7 @@
              (when-not deadline-or-schedule?
                ;; similar to page reference
                (editor-handler/insert-command! id
-                                               (util/format "[[%s]]" journal)
+                                               (page-ref/->page-ref journal)
                                                format
                                                nil)
                (state/clear-editor-action!)

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

@@ -23,6 +23,7 @@
             [frontend.util.cursor :as cursor]
             [frontend.util.keycode :as keycode]
             [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.property :as gp-property]
             [goog.dom :as gdom]
             [promesa.core :as p]
             [react-draggable]
@@ -262,7 +263,8 @@
                (not (string/blank? property)))
       (let [current-pos (cursor/pos input)
             edit-content (state/sub [:editor/content id])
-            start-idx (string/last-index-of (subs edit-content 0 current-pos) "::")
+            start-idx (string/last-index-of (subs edit-content 0 current-pos)
+                                            gp-property/colons)
             q (or
                (when (>= current-pos (+ start-idx 2))
                  (subs edit-content (+ start-idx 2) current-pos))

+ 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]
@@ -317,7 +318,7 @@
   ((state/get-component :whiteboard/tldraw-preview) page-name))
 
 ;; 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}]
@@ -400,9 +401,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))
 
@@ -539,7 +543,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}))))

+ 28 - 30
src/main/frontend/components/search.cljs

@@ -139,30 +139,27 @@
       (route/redirect! {:to :file
                         :path-params {:path data}})
 
-      :block
-      (let [block-uuid (uuid (:block/uuid data))
-            collapsed? (db/parents-collapsed? repo block-uuid)
-            page (:block/page (db/entity [:block/uuid block-uuid]))
-            page-name (:block/name page)
-            long-page? (block-handler/long-page? repo (:db/id page))]
-
-        (println page (model/whiteboard-page? page))
-
-        (if page
-          (cond
-            (model/whiteboard-page? page-name)
-            (route/redirect-to-whiteboard! page-name {:block-id block-uuid})
-
-            (or collapsed? long-page?)
-            (route/redirect-to-page! block-uuid)
-
-            :else
-            (route/redirect-to-page! page-name (str "ls-block-" (:block/uuid data))))
-            ;; search indice outdated
-          (println "[Error] Block page missing: "
-                   {:block-id block-uuid
-                    :block (db/pull [:block/uuid block-uuid])})))
-      nil))
+    :block
+    (let [block-uuid (uuid (:block/uuid data))
+          collapsed? (db/parents-collapsed? repo block-uuid)
+          page (:block/page (db/entity [:block/uuid block-uuid]))
+          page-name (:block/name page)
+          long-page? (block-handler/long-page? repo (:db/id page))]
+      (if page
+        (cond
+          (model/whiteboard-page? page-name)
+          (route/redirect-to-whiteboard! page-name {:block-id block-uuid})
+
+          (or collapsed? long-page?)
+          (route/redirect-to-page! block-uuid)
+
+          :else
+          (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
+                  :block (db/pull [:block/uuid block-uuid])})))
+    nil))
   (state/close-modal!))
 
 (defn- search-on-shift-chosen
@@ -388,7 +385,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
@@ -421,10 +419,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]
@@ -436,4 +434,4 @@
       [:p.font-medium.tx-sm (str (count (:blocks search-result)) " " (t :search/items))]
       [:div#search-wrapper.relative.w-full.text-gray-400.focus-within:text-gray-600
        (when-not (string/blank? search-q)
-         (search-auto-complete search-result search-q true))]]]))
+         (search-auto-complete search-result search-q true))]]]))

+ 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)})]]))
 

+ 4 - 2
src/main/frontend/components/shortcut.cljs

@@ -7,6 +7,8 @@
             [frontend.ui :as ui]
             [frontend.extensions.latex :as latex]
             [frontend.extensions.highlight :as highlight]
+            [logseq.graph-parser.util.block-ref :as block-ref]
+            [logseq.graph-parser.util.page-ref :as page-ref]
             [rum.core :as rum]))
 
 (rum/defcs customize-shortcut-dialog-inner <
@@ -97,10 +99,10 @@
      [:td.text-right [:code "<"]]]
     [:tr
      [:td.text-left (t :help/reference-autocomplete)]
-     [:td.text-right [:code "[[]]"]]]
+     [:td.text-right [:code page-ref/left-and-right-brackets]]]
     [:tr
      [:td.text-left (t :help/block-reference)]
-     [:td.text-right [:code "(())"]]]
+     [:td.text-right [:code block-ref/left-and-right-parens]]]
     [:tr
      [:td.text-left (t :command.editor/open-link-in-sidebar)]
      [:td.text-right (ui/render-keyboard-shortcut ["shift" "click"])]]

+ 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)
         whiteboard-page? (db-model/whiteboard-page? name)]
     [:a {:on-click (fn [e]
@@ -80,7 +80,7 @@
                             :page))
                          (if whiteboard-page?
                            (route-handler/redirect-to-whiteboard! name)
-                           (route-handler/redirect-to-page! name)))))}
+                           (route-handler/redirect-to-page! name {:click-from-recent? recent?})))))}
      [:span.page-icon (if whiteboard-page?
                         [:span.ti.ti-artboard]
                         icon)]
@@ -122,7 +122,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]
@@ -172,7 +172,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]
@@ -182,7 +182,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

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

@@ -60,18 +60,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]
@@ -866,14 +861,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
@@ -890,27 +885,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
@@ -1078,24 +1059,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 #"/")
 
@@ -1203,59 +1166,91 @@
           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))
+                           {:db/id (:db/id k)
+                            :block/alias? true
+                            :block/journal-day (:block/journal-day k)}
+                           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]
@@ -1321,7 +1316,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)
@@ -1338,30 +1333,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"
@@ -1704,6 +1676,19 @@
                         (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))
+
 (defn whiteboard-page?
   [page-name]
   (let [page (db-utils/entity [:block/name (util/safe-page-name-sanity-lc page-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)

+ 37 - 35
src/main/frontend/db/query_dsl.cljs

@@ -14,6 +14,7 @@
             [logseq.db.rules :as rules]
             [frontend.template :as template]
             [logseq.graph-parser.text :as text]
+            [logseq.graph-parser.util.page-ref :as page-ref]
             [frontend.util.text :as text-util]
             [frontend.util :as util]))
 
@@ -60,8 +61,8 @@
       (= "tomorrow" input)
       (db-utils/date->int (t/plus (t/today) (t/days 1)))
 
-      (text/page-ref? input)
-      (let [input (-> (text/page-ref-un-brackets! input)
+      (page-ref/page-ref? input)
+      (let [input (-> (page-ref/get-page-name input)
                       (string/replace ":" "")
                       (string/capitalize))]
         (when (date/valid-journal-title? input)
@@ -92,8 +93,8 @@
       (= "tomorrow" input)
       (tc/to-long (t/plus (t/today) (t/days 1)))
 
-      (text/page-ref? input)
-      (let [input (-> (text/page-ref-un-brackets! input)
+      (page-ref/page-ref? input)
+      (let [input (-> (page-ref/get-page-name input)
                       (string/replace ":" "")
                       (string/capitalize))]
         (when (date/valid-journal-title? input)
@@ -298,7 +299,7 @@
                (rest e))
         tags (map (comp string/lower-case name) tags)]
     (when (seq tags)
-      (let [tags (set (map (comp text/page-ref-un-brackets! string/lower-case name) tags))]
+      (let [tags (set (map (comp page-ref/get-page-name! string/lower-case name) tags))]
         {:query (list 'page-tags '?p tags)
          :rules [:page-tags]}))))
 
@@ -342,14 +343,14 @@
 
 (defn- build-page
   [e]
-  (let [page-name (text/page-ref-un-brackets! (str (first (rest e))))
+  (let [page-name (page-ref/get-page-name! (str (first (rest e))))
         page-name (util/page-name-sanity-lc page-name)]
     {:query (list 'page '?b page-name)
      :rules [:page]}))
 
 (defn- build-namespace
   [e]
-  (let [page-name (text/page-ref-un-brackets! (str (first (rest e))))
+  (let [page-name (page-ref/get-page-name! (str (first (rest e))))
         page (util/page-name-sanity-lc page-name)]
     (when-not (string/blank? page)
       {:query (list 'namespace '?p page)
@@ -357,7 +358,7 @@
 
 (defn- build-page-ref
   [e]
-  (let [page-name (-> (text/page-ref-un-brackets! e)
+  (let [page-name (-> (page-ref/get-page-name! e)
                       (util/page-name-sanity-lc))]
     {:query (list 'page-ref '?b page-name)
      :rules [:page-ref]}))
@@ -381,7 +382,7 @@ Some bindings in this fn:
    ; {:post [(or (nil? %) (map? %))]}
    (let [fe (first e)
          fe (when fe (symbol (string/lower-case (name fe))))
-         page-ref? (text/page-ref? e)]
+         page-ref? (page-ref/page-ref? e)]
      (when (or (and page-ref?
                     (not (contains? #{'page-property 'page-tags} (:current-filter env))))
                (contains? #{'between 'property 'todo 'task 'priority 'sort-by 'page} fe)
@@ -442,20 +443,21 @@ Some bindings in this fn:
 
 (defn- pre-transform
   [s]
-  (some-> s
-          (string/replace text/page-ref-re "\"[[$1]]\"")
-          (string/replace text-util/between-re
-                          (fn [[_ x]]
-                            (->> (string/split x #" ")
-                                 (remove string/blank?)
-                                 (map (fn [x]
-                                        (if (or (contains? #{"+" "-"} (first x))
-                                                (and (util/safe-re-find #"\d" (first x))
-                                                     (some #(string/ends-with? x %) ["y" "m" "d" "h" "min"])))
-                                          (keyword (name x))
-                                          x)))
-                                 (string/join " ")
-                                 (util/format "(between %s)"))))))
+  (let [quoted-page-ref (str "\"" page-ref/left-brackets "$1" page-ref/right-brackets "\"")]
+    (some-> s
+            (string/replace page-ref/page-ref-re quoted-page-ref)
+            (string/replace text-util/between-re
+                            (fn [[_ x]]
+                              (->> (string/split x #" ")
+                                   (remove string/blank?)
+                                   (map (fn [x]
+                                          (if (or (contains? #{"+" "-"} (first x))
+                                                  (and (util/safe-re-find #"\d" (first x))
+                                                       (some #(string/ends-with? x %) ["y" "m" "d" "h" "min"])))
+                                            (keyword (name x))
+                                            x)))
+                                   (string/join " ")
+                                   (util/format "(between %s)")))))))
 
 (defn- add-bindings!
   [form q]
@@ -482,7 +484,7 @@ Some bindings in this fn:
       or?
       (cond
         (->> (flatten form)
-             (remove text/page-ref?)
+             (remove (every-pred string? page-ref/page-ref?))
              (some string?))            ; block full-text search
         (concat [['?b :block/content '?content]] [q])
 
@@ -499,7 +501,7 @@ Some bindings in this fn:
   [s]
   (when (and (string? s)
              (not (string/blank? s)))
-    (let [s (if (= \# (first s)) (util/format "[[%s]]" (subs s 1)) s)
+    (let [s (if (= \# (first s)) (page-ref/->page-ref (subs s 1)) s)
           form (some-> s
                        (pre-transform)
                        (reader/read-string))
@@ -528,18 +530,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 +549,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 +569,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

+ 7 - 19
src/main/frontend/db/query_react.cljs

@@ -10,7 +10,7 @@
             [frontend.debug :as debug]
             [frontend.extensions.sci :as sci]
             [frontend.state :as state]
-            [logseq.graph-parser.text :as text]
+            [logseq.graph-parser.util.page-ref :as page-ref]
             [frontend.util :as util]
             [frontend.date :as date]
             [lambdaisland.glogi :as log]))
@@ -44,22 +44,13 @@
           days (parse-long (re-find #"^\d+" input))]
       (date->int (t/plus (t/today) (t/days days))))
 
-    (and (string? input) (text/page-ref? input))
-    (-> (text/page-ref-un-brackets! input)
+    (and (string? input) (page-ref/page-ref? input))
+    (-> (page-ref/get-page-name input)
         (string/lower-case))
 
     :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)]
@@ -93,7 +81,7 @@
 
 (defn- resolve-query
   [query]
-  (let [page-ref? #(and (string? %) (text/page-ref? %))]
+  (let [page-ref? #(and (string? %) (page-ref/page-ref? %))]
     (walk/postwalk
      (fn [f]
        (cond
@@ -112,7 +100,7 @@
          (let [[x y] (rest f)
                [page-ref sym] (if (page-ref? x) [x y] [y x])
                page-ref (string/lower-case page-ref)]
-           (list 'contains? sym (text/page-ref-un-brackets! page-ref)))
+           (list 'contains? sym (page-ref/get-page-name page-ref)))
 
          (and (vector? f)
               (= (first f) 'page-property)
@@ -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]

+ 23 - 2
src/main/frontend/dicts.cljc

@@ -1156,6 +1156,27 @@
            :highlight "高亮"
            :strikethrough "删除线"
            :code "代码"
+           :discourse-title "我们的论坛"
+           :export-datascript-edn "导出 datascript EDN"
+           :export-edn "导出为 EDN"
+           :export-public-pages "导出公开页"
+           :content/copy-block-emebed "复制内嵌块"
+           :help/awesome-logseq "绝妙的 Logseq"
+           :help/forum-community "论坛讨论"
+           :linked-references/filter-search "在链接的页面内搜索"
+           :page/copy-page-url "复制页面 URL"
+           :plugin/contribute "✨编写和提交新插件"
+           :right-side-bar/show-journals "显示日记"
+           :select/default-prompt "请选择"
+           :select.graph/add-graph "再添加一个图谱"
+           :select.graph/empty-placeholder-description "没有匹配的图谱。你想再添加一个图谱吗?"
+           :select.graph/prompt "请选择一个图谱"
+           :settings-page/edit-export-css "修改导出样式 export.css"
+           :settings-page/enable-flashcards "记忆卡片"
+           :settings-page/enable-shortcut-tooltip "启用快捷键提示"
+           :settings-page/export-theme "导出皮肤"
+           :tutorial/dummy-notes "练习笔记.md"
+           :tutorial/text "指南.md(英文)"
            :right-side-bar/help "帮助"
            :right-side-bar/switch-theme "主题模式"
            :right-side-bar/theme "{1}主题"
@@ -1175,8 +1196,8 @@
            :left-side-bar/nav-shortcuts "快捷导航"
            :left-side-bar/nav-recent-pages "最近使用"
            :format/preferred-mode "请选择偏好格式"
-           :format/markdown "Markdown"
-           :format/org-mode "Org Mode"
+           :format/markdown "Markdown 格式"
+           :format/org-mode "Org Mode 格式"
            :reference/linked "已链接的引用"
            :reference/unlinked-ref "未链接的引用"
            :page/edit-properties-placeholder "点击这里编辑当前页面的属性 (标签,别名等)"

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

@@ -47,7 +47,7 @@
 
                       :else
                       (recur r1 t2 (inc i1) i2))))
-            current-line (text-util/get-current-line-by-pos markup pos)]
+            current-line (:line (text-util/get-current-line-by-pos markup pos))]
         (cond
           (= (util/nth-safe markup pos)
              (util/nth-safe markup (inc pos))

+ 2 - 1
src/main/frontend/extensions/pdf/assets.cljs

@@ -11,6 +11,7 @@
             [frontend.state :as state]
             [frontend.util :as util]
             [logseq.graph-parser.config :as gp-config]
+            [logseq.graph-parser.util.block-ref :as block-ref]
             [medley.core :as medley]
             [promesa.core :as p]
             [reitit.frontend.easy :as rfe]
@@ -213,7 +214,7 @@
 (defn copy-hl-ref!
   [highlight]
   (when-let [ref-block (create-ref-block! highlight)]
-    (util/copy-to-clipboard! (str "((" (:block/uuid ref-block) "))"))))
+    (util/copy-to-clipboard! (block-ref/->block-ref (:block/uuid ref-block)))))
 
 (defn open-block-ref!
   [block]

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

@@ -4,15 +4,19 @@
             [frontend.db.query-react :as query-react]
             [frontend.util :as util]
             [logseq.graph-parser.property :as gp-property]
+            [logseq.graph-parser.util.page-ref :as page-ref]
             [frontend.util.property :as property]
             [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 +25,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 +218,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 +245,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 +256,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 "["))
+                                         (page-ref/->page-ref (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 +339,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 +352,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 +383,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 +405,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 +440,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 +456,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 +474,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 +504,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 +552,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 +627,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 +663,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 +694,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 +758,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))))))

+ 11 - 15
src/main/frontend/extensions/zotero/extractor.cljs

@@ -5,7 +5,8 @@
             [frontend.extensions.html-parser :as html-parser]
             [frontend.extensions.zotero.schema :as schema]
             [frontend.extensions.zotero.setting :as setting]
-            [frontend.util :as util]))
+            [frontend.util :as util]
+            [logseq.graph-parser.util.page-ref :as page-ref]))
 
 (defn item-type [item] (-> item :data :item-type))
 
@@ -64,8 +65,8 @@
 
 (defn date->journal [item]
   (if-let [date (-> item :meta :parsed-date
-                      (date/journal-name-s))]
-    (util/format "[[%s]]" date)
+                    (date/journal-name-s))]
+    (page-ref/->page-ref date)
     (-> item :data :date)))
 
 (defn wrap-in-doublequotes [m]
@@ -129,7 +130,7 @@
                                 :authors authors
                                 :tags tags
                                 :date date
-                                :item-type (util/format "[[%s]]" type))
+                                :item-type (page-ref/->page-ref type))
                          (dissoc :creators :abstract-note)
                          (rename-keys {:title :original-title})
                          (assoc :title (page-name item)))]
@@ -146,11 +147,11 @@
   (util/format "{{zotero-imported-file %s, %s}}" item-key (pr-str filename)))
 
 (defn zotero-linked-file-macro [path]
-  (util/format "{{zotero-linked-file %s}}" (pr-str path)))
+  (util/format "{{zotero-linked-file %s}}" (pr-str (util/node-path.basename path))))
 
 (defmethod extract "attachment"
   [item]
-  (let [{:keys [title url link-mode path content-type filename]} (-> item :data)]
+  (let [{:keys [title url link-mode path filename]} (-> item :data)]
     (case link-mode
       "imported_file"
       (str
@@ -158,15 +159,10 @@
        " "
        (zotero-imported-file-macro (item-key item) filename))
       "linked_file"
-      (if (string/starts-with? path "attachments:")
-        (str
-         (markdown-link title (local-link item))
-         " "
-         (zotero-linked-file-macro path))
-        (let [path (string/replace path " " "%20")]
-          (if (= content-type "application/pdf")
-            (markdown-link title (str "file://" path) true)
-            (markdown-link title (str "file://" path)))))
+      (str
+       (markdown-link title (local-link item))
+       " "
+       (zotero-linked-file-macro path))
       "imported_url"
       (str
        (markdown-link title url)

+ 3 - 2
src/main/frontend/extensions/zotero/handler.cljs

@@ -8,7 +8,8 @@
             [frontend.state :as state]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.page :as page-handler]
-            [frontend.db :as db]))
+            [frontend.db :as db]
+            [logseq.graph-parser.util.page-ref :as page-ref]))
 
 (defn add [page-name type item]
   (go
@@ -42,7 +43,7 @@
 (defn handle-command-zotero
   [id page-name]
   (state/clear-editor-action!)
-  (editor-handler/insert-command! id (str "[[" page-name "]]") nil {}))
+  (editor-handler/insert-command! id (page-ref/->page-ref page-name) nil {}))
 
 (defn- create-abstract-note!
   [page-name abstract-note]

+ 6 - 5
src/main/frontend/external/roam.cljs

@@ -4,7 +4,8 @@
             [frontend.date :as date]
             [clojure.walk :as walk]
             [clojure.string :as string]
-            [frontend.util :as util]
+            [goog.string :as gstring]
+            [logseq.graph-parser.util.block-ref :as block-ref]
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.text :as text]))
 
@@ -30,7 +31,7 @@
   [text]
   (string/replace text uid-pattern (fn [[_ uid]]
                                      (let [id (get @uid->uuid uid uid)]
-                                       (str "((" id "))")))))
+                                       (block-ref/->block-ref id)))))
 
 (defn macro-transform
   [text]
@@ -38,7 +39,7 @@
                                        (let [[name arg] (gp-util/split-first ":" text)]
                                          (if name
                                            (let [name (text/page-ref-un-brackets! name)]
-                                             (util/format "{{%s %s}}" name arg))
+                                             (gstring/format "{{%s %s}}" name arg))
                                            original)))))
 
 (defn- fenced-code-transform
@@ -83,7 +84,7 @@
                              " -"))
         properties (when (contains? @all-refed-uids uid)
                      (str
-                      (util/format "id:: %s"
+                      (gstring/format "id:: %s"
                                    (str (get @uid->uuid uid)))
                       "\n"))]
     (if string
@@ -109,7 +110,7 @@
                  (let [journal? (date/valid-journal-title? title)
                        front-matter (if journal?
                                       ""
-                                      (util/format "---\ntitle: %s\n---\n\n" title))]
+                                      (gstring/format "---\ntitle: %s\n---\n\n" title))]
                    (str front-matter (transform text)))))]
     (when (and (not (string/blank? title))
                text)

+ 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)]

+ 5 - 2
src/main/frontend/fs/nfs.cljs

@@ -203,8 +203,11 @@
                              file (.getFile file-handle)]
                        (when file
                          (nfs-saved-handler repo path file)))
-                     (notification/show! (str "The file " path " already exists, please save your changes and click the refresh button to reload it.")
-                                         :warning)))
+                     (do
+                       (notification/show! (str "The file " path " already exists, please append the content if you need it.\n Unsaved content: \n" content)
+                                          :warning
+                                          false)
+                       (state/pub-event! [:file/alter repo path text]))))
                  (println "Error: directory handle not exists: " handle-path)))
              (p/catch (fn [error]
                         (println "Write local file failed: " {:path path})

+ 2 - 2
src/main/frontend/fs/watcher_handler.cljs

@@ -9,7 +9,7 @@
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.ui :as ui-handler]
             [logseq.graph-parser.util :as gp-util]
-            [frontend.util.text :as text-util]
+            [logseq.graph-parser.util.block-ref :as block-ref]
             [lambdaisland.glogi :as log]
             [electron.ipc :as ipc]
             [promesa.core :as p]
@@ -22,7 +22,7 @@
 (defn- set-missing-block-ids!
   [content]
   (when (string? content)
-    (doseq [block-id (text-util/extract-all-block-refs content)]
+    (doseq [block-id (block-ref/get-all-block-ref-ids content)]
       (when-let [block (try
                          (model/get-block-by-uuid block-id)
                          (catch js/Error _e

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

@@ -59,8 +59,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)))

+ 3 - 3
src/main/frontend/handler/dnd.cljs

@@ -3,8 +3,8 @@
             [frontend.modules.outliner.core :as outliner-core]
             [frontend.modules.outliner.tree :as tree]
             [frontend.modules.outliner.transaction :as outliner-tx]
-            [frontend.state :as state]
-            [frontend.util :as util]))
+            [logseq.graph-parser.util.block-ref :as block-ref]
+            [frontend.state :as state]))
 
 (defn move-blocks
   [^js event blocks target-block move-to]
@@ -23,7 +23,7 @@
                                             :id
                                             (str (:block/uuid first-block)))
         (editor-handler/api-insert-new-block!
-         (util/format "((%s))" (str (:block/uuid first-block)))
+         (block-ref/->block-ref (:block/uuid first-block))
          {:block-uuid (:block/uuid target-block)
           :sibling? (not nested?)
           :before? top?}))

+ 76 - 60
src/main/frontend/handler/editor.cljs

@@ -30,6 +30,10 @@
             [frontend.search :as search]
             [frontend.state :as state]
             [frontend.template :as template]
+            [logseq.graph-parser.text :as text]
+            [logseq.graph-parser.utf8 :as utf8]
+            [logseq.graph-parser.property :as gp-property]
+            [logseq.graph-parser.block :as gp-block]
             [frontend.util :as util :refer [profile]]
             [frontend.util.clock :as clock]
             [frontend.util.cursor :as cursor]
@@ -45,8 +49,10 @@
             [goog.dom.classes :as gdom-classes]
             [goog.object :as gobj]
             [lambdaisland.glogi :as log]
-            [logseq.db.schema :as db-schema]
-            [logseq.graph-parser.block :as gp-block]
+            [promesa.core :as p]
+            [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.util.block-ref :as block-ref]
+            [logseq.graph-parser.util.page-ref :as page-ref]
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.text :as text]
             [logseq.graph-parser.utf8 :as utf8]
@@ -363,7 +369,7 @@
                                (nil? (:size first-elem-meta)))
         block-with-title? (mldoc/block-with-title? first-elem-type)
         content (string/triml content)
-        content (string/replace content (util/format "((%s))" (str uuid)) "")
+        content (string/replace content (block-ref/->block-ref uuid) "")
         [content content'] (cond
                              (and first-block? properties?)
                              [content content]
@@ -400,15 +406,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-op :save-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]))))))))
@@ -1031,9 +1037,9 @@
                             (map (fn [{:keys [id level]}]
                                    (condp = (:block/format block)
                                      :org
-                                     (util/format (str (string/join (repeat level "*")) " ((%s))") id)
+                                     (str (string/join (repeat level "*")) " " (block-ref/->block-ref id))
                                      :markdown
-                                     (util/format (str (string/join (repeat (dec level) "\t")) "- ((%s))") id))))
+                                     (str (string/join (repeat (dec level) "\t")) "- " (block-ref/->block-ref id)))))
                             (string/join "\n\n"))]
       (set-blocks-id! (map :id blocks))
       (util/copy-to-clipboard! copy-str))))
@@ -1089,11 +1095,11 @@
 
 (defn extract-nearest-link-from-text
   [text pos & additional-patterns]
-  (let [page-pattern #"\[\[([^\]]+)]]"
-        block-pattern #"\(\(([^\)]+)\)\)"
+  (let [;; didn't use page-ref regexs b/c it handles page-ref and org link cases
+        page-pattern #"\[\[([^\]]+)]]"
         tag-pattern #"#\S+"
         page-matches (util/re-pos page-pattern text)
-        block-matches (util/re-pos block-pattern text)
+        block-matches (util/re-pos block-ref/block-ref-re text)
         tag-matches (util/re-pos tag-pattern text)
         additional-matches (mapcat #(util/re-pos % text) additional-patterns)
         matches (->> (concat page-matches block-matches tag-matches additional-matches)
@@ -1225,7 +1231,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)]
@@ -1553,18 +1558,16 @@
                                                                  (when (>= prefix-pos 0)
                                                                    [(subs new-value prefix-pos (+ prefix-pos 2))
                                                                     (+ prefix-pos 2)]))})]
-        (case prefix
-          "[["
+        (cond
+          (= prefix page-ref/left-brackets)
           (do
             (commands/handle-step [:editor/search-page])
             (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}))
 
-          "(("
+          (= prefix block-ref/left-parens)
           (do
             (commands/handle-step [:editor/search-block :reference])
-            (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}))
-
-          nil)))))
+            (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})))))))
 
 (defn surround-by?
   [input before end]
@@ -1775,7 +1778,7 @@
   [input]
   (when (and input
              (state/get-editor-action)
-             (not (wrapped-by? input "[[" "]]")))
+             (not (wrapped-by? input page-ref/left-brackets page-ref/right-brackets)))
     (when (get-search-q)
       (let [value (gobj/get input "value")
             pos (state/get-editor-last-pos)
@@ -1856,8 +1859,9 @@
 
       (and
        (not= :property-search (state/get-editor-action))
-       (or (wrapped-by? input "" "::")
-           (wrapped-by? input "\n" "::")))
+       (let [{:keys [line start-pos]} (text-util/get-current-line-by-pos (.-value input) (dec pos))]
+         (text-util/wrapped-by? line (dec (- pos start-pos)) "" gp-property/colons)))
+
       (do
         (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
         (state/set-editor-action! :property-search))
@@ -1876,11 +1880,11 @@
 
       ;; block reference
       (insert-command! id
-                       (util/format "((%s))" uuid-string)
+                       (block-ref/->block-ref uuid-string)
                        format
-                       {:last-pattern (str "((" (if @*selected-text "" q))
-                        :end-pattern "))"
-                        :postfix-fn   (fn [s] (util/replace-first "))" s ""))
+                       {:last-pattern (str block-ref/left-parens (if @*selected-text "" q))
+                        :end-pattern block-ref/right-parens
+                        :postfix-fn   (fn [s] (util/replace-first block-ref/right-parens s ""))
                         :forward-pos 3})
 
       ;; Save it so it'll be parsed correctly in the future
@@ -1913,11 +1917,9 @@
            {:block/page {:db/id (:db/id page)}
             :block/format format
             :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?))})))
+                                (concat [:id :custom_id :custom-id]
+                                        exclude-properties))
+            :block/content new-content})))
 
 (defn- edit-last-block-after-inserted!
   [result]
@@ -1958,10 +1960,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
@@ -1985,7 +1989,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))
@@ -2074,7 +2078,7 @@
   (let [value (.-value input)
         pos (util/get-selection-start input)
         postfix (subs value pos)
-        end-index (when-let [idx (string/index-of postfix "::")]
+        end-index (when-let [idx (string/index-of postfix gp-property/colons)]
                     (+ (max 0 (count (subs value 0 pos))) idx))
         start-index (or (when-let [p (string/last-index-of (subs value 0 pos) "\n")]
                           (inc p))
@@ -2089,8 +2093,8 @@
     (when-let [input (gdom/getElement element-id)]
       (let [{:keys [end-index searching-property]} (get-searching-property input)]
         (cursor/move-cursor-to input (+ end-index 2))
-        (commands/insert! element-id (str (or property q) ":: ")
-                          {:last-pattern (str searching-property "::")})
+        (commands/insert! element-id (str (or property q) gp-property/colons " ")
+                          {:last-pattern (str searching-property gp-property/colons)})
         (state/clear-editor-action!)
         (js/setTimeout (fn []
                          (let [pos (let [input (gdom/getElement element-id)]
@@ -2103,8 +2107,8 @@
 (defn property-value-on-chosen-handler
   [element-id q]
   (fn [property-value]
-    (commands/insert! element-id (str ":: " (or property-value q))
-                      {:last-pattern (str ":: " q)})
+    (commands/insert! element-id (str gp-property/colons " " (or property-value q))
+                      {:last-pattern (str gp-property/colons " " q)})
     (state/clear-editor-action!)))
 
 (defn parent-is-page?
@@ -2313,12 +2317,12 @@
               {:keys [selection-start selection-end selection]} selection]
           (if selection
             (do (delete-and-update input selection-start selection-end)
-                (insert (util/format "[[%s]]" selection)))
+                (insert (page-ref/->page-ref selection)))
             (if-let [embed-ref (thingatpt/embed-macro-at-point input)]
               (let [{:keys [raw-content start end]} embed-ref]
                 (delete-and-update input start end)
                 (if (= 5 (count raw-content))
-                  (page-ref-fn "[[]]" 2)
+                  (page-ref-fn page-ref/left-and-right-brackets 2)
                   (insert raw-content)))
               (if-let [page-ref (thingatpt/page-ref-at-point input)]
                 (let [{:keys [start end full-content raw-content]} page-ref]
@@ -2326,7 +2330,7 @@
                   (if (= raw-content "")
                     (page-ref-fn "{{embed [[]]}}" 4)
                     (insert (util/format "{{embed %s}}" full-content))))
-                (page-ref-fn "[[]]" 2)))))))))
+                (page-ref-fn page-ref/left-and-right-brackets 2)))))))))
 
 (defn toggle-block-reference-embed
   [parent-id]
@@ -2346,7 +2350,7 @@
           (let [{:keys [raw-content start end]} embed-ref]
             (delete-and-update input start end)
             (if (= 5 (count raw-content))
-              (block-ref-fn "(())" 2)
+              (block-ref-fn block-ref/left-and-right-parens 2)
               (insert raw-content)))
           (if-let [page-ref (thingatpt/block-ref-at-point input)]
             (let [{:keys [start end full-content raw-content]} page-ref]
@@ -2354,7 +2358,7 @@
               (if (= raw-content "")
                 (block-ref-fn "{{embed (())}}" 4)
                 (insert (util/format "{{embed %s}}" full-content))))
-            (block-ref-fn "(())" 2)))))))
+            (block-ref-fn block-ref/left-and-right-parens 2)))))))
 
 (defn- keydown-new-block
   [state]
@@ -2706,7 +2710,7 @@
         (on-tab direction)))
     nil))
 
-(defn keydown-not-matched-handler
+(defn ^:large-vars/cleanup-todo keydown-not-matched-handler
   [format]
   (fn [e _key-code]
     (let [input-id (state/get-edit-input-id)
@@ -2720,6 +2724,10 @@
                        (surround-by? input "#" :end)
                        (= key "#"))]
       (cond
+        (and (contains? #{"ArrowLeft" "ArrowRight" "ArrowUp" "ArrowDown"} key)
+             (contains? #{:property-search :property-value-search} (state/get-editor-action)))
+        (state/clear-editor-action!)
+
         (and (util/event-is-composing? e true) ;; #3218
              (not hashtag?) ;; #3283 @Rime
              (not (state/get-editor-show-page-search-hashtag?))) ;; #3283 @MacOS pinyin
@@ -2840,12 +2848,14 @@
                                       (not= code keycode/enter-code))  ;; #3459
             editor-action (state/get-editor-action)]
         (cond
+          ;; When you type something after /
           (and (= :commands (state/get-editor-action)) (not= k (state/get-editor-command-trigger)))
           (let [matched-commands (get-matched-commands input)]
             (if (seq matched-commands)
               (reset! commands/*matched-commands matched-commands)
               (state/clear-editor-action!)))
 
+          ;; When you type search text after < (and when you release shift after typing <)
           (and (= :block-commands editor-action) (not= key-code 188)) ; not <
           (let [matched-block-commands (get-matched-block-commands input)]
             (if (seq matched-block-commands)
@@ -2862,10 +2872,12 @@
                 (reset! commands/*matched-block-commands matched-block-commands))
               (state/clear-editor-action!)))
 
+          ;; When you type two spaces after a command character (may always just be handled by the above instead?)
           (and (contains? #{:commands :block-commands} (state/get-editor-action))
                (= c (util/nth-safe value (dec (dec current-pos))) " "))
           (state/clear-editor-action!)
 
+          ;; When you type a space after a #
           (and (state/get-editor-show-page-search-hashtag?)
                (= c " "))
           (state/clear-editor-action!)
@@ -2873,11 +2885,12 @@
           :else
           (when (and (not editor-action) (not non-enter-processed?))
             (cond
+              ;; When you type text inside square brackets
               (and (not (contains? #{"ArrowDown" "ArrowLeft" "ArrowRight" "ArrowUp"} k))
-                   (wrapped-by? input "[[" "]]"))
+                   (wrapped-by? input page-ref/left-brackets page-ref/right-brackets))
               (let [orig-pos (cursor/get-caret-pos input)
                     value (gobj/get input "value")
-                    square-pos (string/last-index-of (subs value 0 (:pos orig-pos)) "[[")
+                    square-pos (string/last-index-of (subs value 0 (:pos orig-pos)) page-ref/left-brackets)
                     pos (+ square-pos 2)
                     _ (state/set-editor-last-pos! pos)
                     pos (assoc orig-pos :pos pos)
@@ -2887,28 +2900,31 @@
                 (commands/handle-step [command-step])
                 (state/set-editor-action-data! {:pos pos}))
 
+              ;; Handle non-ascii square brackets
               (and blank-selected?
                    (contains? keycode/left-square-brackets-keys k)
                    (= (:key last-key-code) k)
                    (> current-pos 0)
-                   (not (wrapped-by? input "[[" "]]")))
+                   (not (wrapped-by? input page-ref/left-brackets page-ref/right-brackets)))
               (do
-                (commands/handle-step [:editor/input "[[]]" {:backward-truncate-number 2
+                (commands/handle-step [:editor/input page-ref/left-and-right-brackets {:backward-truncate-number 2
                                                              :backward-pos 2}])
                 (commands/handle-step [:editor/search-page])
                 (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}))
 
+              ;; Handle non-ascii parentheses
               (and blank-selected?
                    (contains? keycode/left-paren-keys k)
                    (= (:key last-key-code) k)
                    (> current-pos 0)
-                   (not (wrapped-by? input "((" "))")))
+                   (not (wrapped-by? input block-ref/left-parens block-ref/right-parens)))
               (do
-                (commands/handle-step [:editor/input "(())" {:backward-truncate-number 2
+                (commands/handle-step [:editor/input block-ref/left-and-right-parens {:backward-truncate-number 2
                                                              :backward-pos 2}])
                 (commands/handle-step [:editor/search-block :reference])
                 (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}))
 
+              ;; Handle non-ascii angle brackets
               (and (= "〈" c)
                    (= "《" (util/nth-safe value (dec (dec current-pos))))
                    (> current-pos 0))
@@ -2970,20 +2986,19 @@
   (util/stop e)
   (cut-blocks-and-clear-selections! false))
 
-;; credits to @pengx17
 (defn- copy-current-block-ref
   [format]
   (when-let [current-block (state/get-edit-block)]
     (when-let [block-id (:block/uuid current-block)]
       (if (= format "embed")
-        (copy-block-ref! block-id #(str "{{embed ((" % "))}}"))
-        (copy-block-ref! block-id #(str "((" % "))")))
+       (copy-block-ref! block-id #(str "{{embed ((" % "))}}"))
+       (copy-block-ref! block-id block-ref/->block-ref))
       (notification/show!
        [:div
         [:span.mb-1.5 (str "Block " format " copied!")]
         [:div [:code.whitespace.break-all (if (= format "embed")
-                                            (str "{{embed ((" block-id "))}}")
-                                            (str "((" block-id "))"))]]]
+                                         (str "{{embed ((" block-id "))}}")
+                                         (block-ref/->block-ref block-id))]]]
        :success true
        ;; use uuid to make sure there is only one toast a time
        (str "copied-block-ref:" block-id)))))
@@ -3431,12 +3446,13 @@
 (defn copy-current-ref
   [block-id]
   (when block-id
-    (util/copy-to-clipboard! (util/format "((%s))" (str block-id)))))
+    (util/copy-to-clipboard! (block-ref/->block-ref block-id))))
 
 (defn delete-current-ref!
   [block ref-id]
   (when (and block ref-id)
-    (let [match (re-pattern (str "\\s?" (util/format "\\(\\(%s\\)\\)" (str ref-id))))
+    (let [match (re-pattern (str "\\s?"
+                                 (string/replace (block-ref/->block-ref ref-id) #"([\(\)])" "\\$1")))
           content (string/replace-first (:block/content block) match "")]
       (save-block! (state/get-current-repo)
                    (:block/uuid block)
@@ -3445,7 +3461,7 @@
 (defn replace-ref-with-text!
   [block ref-id]
   (when (and block ref-id)
-    (let [match (util/format "((%s))" (str ref-id))
+    (let [match (block-ref/->block-ref ref-id)
           ref-block (db/entity [:block/uuid ref-id])
           block-ref-content (->> (or (:block/content ref-block)
                                      "")
@@ -3460,7 +3476,7 @@
 (defn replace-ref-with-embed!
   [block ref-id]
   (when (and block ref-id)
-    (let [match (util/format "((%s))" (str ref-id))
+    (let [match (block-ref/->block-ref ref-id)
           content (string/replace-first (:block/content block) match
                                         (util/format "{{embed ((%s))}}"
                                                      (str ref-id)))]

+ 4 - 0
src/main/frontend/handler/events.cljs

@@ -530,6 +530,10 @@
   (when (= dir (config/get-repo-dir repo))
     (fs/watch-dir! dir)))
 
+(defmethod handle :file/alter [[_ repo path content]]
+  (p/let [_ (file-handler/alter-file repo path content {:from-disk? true})]
+    (ui-handler/re-render-root!)))
+
 (defn run!
   []
   (let [chan (state/get-events-chan)]

+ 6 - 13
src/main/frontend/handler/export.cljs

@@ -23,6 +23,8 @@
             [lambdaisland.glogi :as log]
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.util :as gp-util]
+            [logseq.graph-parser.util.block-ref :as block-ref]
+            [logseq.graph-parser.util.page-ref :as page-ref]
             [promesa.core :as p]
             [frontend.handler.notification :as notification])
   (:import
@@ -159,11 +161,8 @@
                              (= "embed" (some-> (:name (second i))
                                                 (string/lower-case)))
                              (some-> (:arguments (second i))
-                                     (first)
-                                     (string/starts-with? "[["))
-                             (some-> (:arguments (second i))
-                                     (first)
-                                     (string/ends-with? "]]")))
+                                     first
+                                     page-ref/page-ref?))
                         (let [arguments (:arguments (second i))
                               page-ref (first arguments)
                               page-name (-> page-ref
@@ -188,15 +187,9 @@
                                                 (string/lower-case)))
                              (some-> (:arguments (second i))
                                      (first)
-                                     (string/starts-with? "(("))
-                             (some-> (:arguments (second i))
-                                     (first)
-                                     (string/ends-with? "))")))
+                                     block-ref/string-block-ref?))
                         (let [arguments (:arguments (second i))
-                              block-ref (first arguments)
-                              block-uuid (-> block-ref
-                                             (subs 2)
-                                             (#(subs % 0 (- (count %) 2))))]
+                              block-uuid (block-ref/get-string-block-ref-id (first arguments))]
                           (conj! result block-uuid)
                           i)
                         :else

+ 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

+ 24 - 22
src/main/frontend/handler/page.cljs

@@ -36,6 +36,8 @@
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.block :as gp-block]
+            [logseq.graph-parser.property :as gp-property]
+            [logseq.graph-parser.util.page-ref :as page-ref]
             [frontend.format.block :as block]
             [goog.functions :refer [debounce]]))
 
@@ -81,6 +83,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
@@ -212,7 +215,7 @@
   "Unsanitized names"
   [content old-name new-name]
   (let [[original-old-name original-new-name] (map string/trim [old-name new-name])
-        [old-ref new-ref] (map #(util/format "[[%s]]" %) [old-name new-name])
+        [old-ref new-ref] (map page-ref/->page-ref [old-name new-name])
         [old-name new-name] (map #(if (string/includes? % "/")
                                     (string/replace % "/" ".")
                                     %)
@@ -249,8 +252,8 @@
 (defn- replace-property-ref!
   [content old-name new-name]
   (let [new-name (keyword (string/replace (string/lower-case new-name) #"\s+" "-"))
-        old-property (str old-name "::")
-        new-property (str (name new-name) "::")]
+        old-property (str old-name gp-property/colons)
+        new-property (str (name new-name) gp-property/colons)]
     (util/replace-ignore-case content old-property new-property)))
 
 (defn- replace-old-page!
@@ -387,8 +390,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]
@@ -459,8 +461,9 @@
   "Unsanitized names only"
   [old-ns-name new-ns-name]
   (let [repo            (state/get-current-repo)
-        nested-page-str (util/format "[[%s]]" (util/page-name-sanity-lc old-ns-name))
-        ns-prefix       (util/format "[[%s/" (util/page-name-sanity-lc old-ns-name))
+        nested-page-str (page-ref/->page-ref (util/page-name-sanity-lc old-ns-name))
+        ns-prefix-format-str (str page-ref/left-brackets "%s/")
+        ns-prefix       (util/format ns-prefix-format-str (util/page-name-sanity-lc old-ns-name))
         nested-pages    (db/get-pages-by-name-partition repo nested-page-str)
         nested-pages-ns (db/get-pages-by-name-partition repo ns-prefix)]
     (when nested-pages
@@ -469,8 +472,8 @@
         (let [old-page-title (or original-name name)
               new-page-title (string/replace
                               old-page-title
-                              (util/format "[[%s]]" old-ns-name)
-                              (util/format "[[%s]]" new-ns-name))]
+                              (page-ref/->page-ref old-ns-name)
+                              (page-ref/->page-ref new-ns-name))]
           (when (and old-page-title new-page-title)
             (p/do!
              (rename-page-aux old-page-title new-page-title false)
@@ -481,8 +484,8 @@
         (let [old-page-title (or original-name name)
               new-page-title (string/replace
                               old-page-title
-                              (util/format "[[%s/" old-ns-name)
-                              (util/format "[[%s/" new-ns-name))]
+                              (util/format ns-prefix-format-str old-ns-name)
+                              (util/format ns-prefix-format-str new-ns-name))]
           (when (and old-page-title new-page-title)
             (p/do!
              (rename-page-aux old-page-title new-page-title false)
@@ -531,7 +534,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)))
@@ -642,7 +644,7 @@
           (util/format "[[file:%s][%s]]"
                        (util/get-relative-path edit-block-file-path ref-file-path)
                        page)))
-      (util/format "[[%s]]" page))))
+      (page-ref/->page-ref page))))
 
 (defn init-commands!
   []
@@ -695,7 +697,7 @@
   (if (state/org-mode-file-link? (state/get-current-repo))
     (let [page-ref-text (get-page-ref-text q)
           value (gobj/get input "value")
-          old-page-ref (util/format "[[%s]]" q)
+          old-page-ref (page-ref/->page-ref q)
           new-value (string/replace value
                                     old-page-ref
                                     page-ref-text)]
@@ -723,13 +725,13 @@
     (if hashtag?
       (fn [chosen _click?]
         (state/clear-editor-action!)
-        (let [wrapped? (= "[[" (gp-util/safe-subs edit-content (- pos 2) pos))
+        (let [wrapped? (= page-ref/left-brackets (gp-util/safe-subs edit-content (- pos 2) pos))
               prefix (str (t :new-page) ": ")
               chosen (if (string/starts-with? chosen prefix) ;; FIXME: What if a page named "New page: XXX"?
                        (string/replace-first chosen prefix "")
                        chosen)
               chosen (if (and (util/safe-re-find #"\s+" chosen) (not wrapped?))
-                       (util/format "[[%s]]" chosen)
+                       (page-ref/->page-ref chosen)
                        chosen)
               q (if @editor-handler/*selected-text "" q)
               [last-pattern forward-pos] (if wrapped?
@@ -737,12 +739,12 @@
                                            (if (= \# (first q))
                                              [(subs q 1) 1]
                                              [q 2]))
-              last-pattern (str "#" (when wrapped? "[[") last-pattern)]
+              last-pattern (str "#" (when wrapped? page-ref/left-brackets) last-pattern)]
           (editor-handler/insert-command! id
-                                          (str "#" (when wrapped? "[[") chosen)
+                                          (str "#" (when wrapped? page-ref/left-brackets) chosen)
                                           format
                                           {:last-pattern last-pattern
-                                           :end-pattern (when wrapped? "]]")
+                                           :end-pattern (when wrapped? page-ref/right-brackets)
                                            :forward-pos forward-pos})))
       (fn [chosen _click?]
         (state/clear-editor-action!)
@@ -754,9 +756,9 @@
           (editor-handler/insert-command! id
                                           page-ref-text
                                           format
-                                          {:last-pattern (str "[[" (if @editor-handler/*selected-text "" q))
-                                           :end-pattern "]]"
-                                           :postfix-fn   (fn [s] (util/replace-first "]]" s ""))
+                                          {:last-pattern (str page-ref/left-brackets (if @editor-handler/*selected-text "" q))
+                                           :end-pattern page-ref/right-brackets
+                                           :postfix-fn   (fn [s] (util/replace-first page-ref/right-brackets s ""))
                                            :forward-pos 3}))))))
 
 (defn create-today-journal!

+ 7 - 6
src/main/frontend/handler/paste.cljs

@@ -5,6 +5,7 @@
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.block :as gp-block]
+            [logseq.graph-parser.util.block-ref :as block-ref]
             [clojure.string :as string]
             [frontend.util :as util]
             [frontend.handler.editor :as editor-handler]
@@ -15,7 +16,6 @@
             ["/frontend/utils" :as utils]
             [frontend.commands :as commands]
             [cljs.core.match :refer [match]]
-            [logseq.graph-parser.text :as text]
             [frontend.handler.notification :as notification]
             [frontend.util.text :as text-util]
             [frontend.format.mldoc :as mldoc]
@@ -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' {}))))
 
@@ -77,7 +77,8 @@
         internal-paste? (and
                          (seq (:copy/blocks copied-blocks))
                          ;; not copied from the external clipboard
-                         (= text (:copy/content copied-blocks)))]
+                         (= (string/trimr text)
+                            (string/trimr (:copy/content copied-blocks))))]
     (if internal-paste?
       (let [blocks (:copy/blocks copied-blocks)]
         (when (seq blocks)
@@ -89,9 +90,9 @@
                (not (string/blank? (util/get-selected-text))))
           (editor-handler/html-link-format! text)
 
-          (and (text/block-ref? text)
-               (editor-handler/wrapped-by? input "((" "))"))
-          (commands/simple-insert! (state/get-edit-input-id) (text/get-block-ref text) nil)
+          (and (block-ref/block-ref? text)
+               (editor-handler/wrapped-by? input block-ref/left-parens block-ref/right-parens))
+          (commands/simple-insert! (state/get-edit-input-id) (block-ref/get-block-ref-id text) nil)
 
           :else
           ;; from external

+ 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 redirect-to-whiteboard!
   ([name]

+ 2 - 3
src/main/frontend/mobile/action_bar.cljs

@@ -11,6 +11,7 @@
    [goog.dom :as gdom]
    [goog.object :as gobj]
    [rum.core :as rum]
+   [logseq.graph-parser.util.block-ref :as block-ref]
    [frontend.mobile.util :as mobile-util]))
 
 (defn- action-command
@@ -63,7 +64,7 @@
         (action-command "cut" "Cut" #(editor-handler/cut-selection-blocks true))
         (action-command "trash" "Delete" #(editor-handler/delete-block-aux! block true))
         (action-command "registered" "Copy ref"
-                        (fn [_event] (editor-handler/copy-block-ref! uuid #(str "((" % "))"))))
+                        (fn [_event] (editor-handler/copy-block-ref! uuid block-ref/->block-ref)))
         (action-command "link" "Copy url"
                         (fn [_event] (let [current-repo (state/get-current-repo)
                                            tap-f (fn [block-id]
@@ -74,5 +75,3 @@
                           (fn [_event]
                             (let [current-repo (state/get-current-repo)]
                               (state/sidebar-add-block! current-repo uuid :block-ref)))))]])))
-
-

+ 2 - 1
src/main/frontend/mobile/intent.cljs

@@ -17,6 +17,7 @@
             [lambdaisland.glogi :as log]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.mldoc :as gp-mldoc]
+            [logseq.graph-parser.util.page-ref :as page-ref]
             [promesa.core :as p]))
 
 (defn- handle-received-text [result]
@@ -89,7 +90,7 @@
                 (.copy Filesystem (clj->js {:from url :to path}))
                 (fn [error]
                   (log/error :copy-file-error {:error error})))
-          url (util/format "[[%s]]" title)
+          url (page-ref/->page-ref title)
           template (get-in (state/get-config)
                            [:quick-capture-templates :text]
                            "**{time}** [[quick capture]]: {url}")]

+ 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]

+ 50 - 0
src/main/frontend/modules/shortcut/dicts.cljc

@@ -145,6 +145,56 @@
              :shortcut.category/block-selection       "块选择操作"
              :shortcut.category/toggle                "切换"
              :shortcut.category/others                "其他"
+             :command.auto-complete/complete          "自动完成:选择当前项"
+             :command.auto-complete/next              "自动完成:选择下一项"
+             :command.auto-complete/open-link         "自动完成:在浏览器中打开当前项"
+             :command.auto-complete/prev              "自动完成:选择上一项"
+             :command.auto-complete/shift-complete    "自动完成:在侧边栏打开"
+             :command.cards/forgotten                 "卡片:忘记了"
+             :command.cards/next-card                 "卡片:下一个"
+             :command.cards/recall                    "卡片:需要一会儿才能记起"
+             :command.cards/remembered                "卡片:记得"
+             :command.cards/toggle-answers            "卡片:显示/隐藏 答案/填空"
+             :command.command/run                     "运行 git 命令"
+             :command.command/toggle-favorite         "切换收藏"
+             :command.command-palette/toggle          "切换命令面板"
+             :command.date-picker/complete            "日期选择:选择当前天"
+             :command.date-picker/next-day            "日期选择:下一天"
+             :command.date-picker/next-week           "日期选择:下一周"
+             :command.date-picker/prev-day            "日期选择:上一天"
+             :command.date-picker/prev-week           "日期选择:上一周"
+             :command.editor/copy-current-file        "复制当前文件"
+             :command.editor/copy-embed               "复制指向当前块的嵌入块"
+             :command.editor/copy-text                "复制选中的文本"
+             :command.editor/escape-editing           "转义编辑"
+             :command.editor/insert-youtube-timestamp "插入 Youtube 时间戳"
+             :command.editor/open-file-in-default-app "在默认应用中打开文件"
+             :command.editor/open-file-in-directory   "在目录中打开文件"
+             :command.editor/paste-text-in-one-block-at-point  "粘贴文本到块的点"
+             :command.editor/replace-block-reference-at-point  "用内容替换块引用"
+             :command.editor/select-down              "选择下面内容"
+             :command.editor/select-up                "选择上面的内容"
+             :command.editor/strike-through           "删除线"
+             :command.go/all-pages                    "全部页面"
+             :command.go/flashcards                   "记忆卡片"
+             :command.go/graph-view                   "记忆图谱"
+             :command.go/home                         "首页"
+             :command.go/keyboard-shortcuts           "快捷键"
+             :command.go/next-journal                 "下一篇日记"
+             :command.go/prev-journal                 "上一篇日记"
+             :command.go/tomorrow                     "明天"
+             :command.graph/add                       "添加图谱"
+             :command.graph/open                      "打开图谱"
+             :command.graph/re-index                  "重建索引"
+             :command.graph/remove                    "移除图谱"
+             :command.graph/save                      "保存"
+             :command.misc/copy                       "Ctrl/Command + c"
+             :command.sidebar/clear                   "清除侧边栏"
+             :command.sidebar/open-today-page         "在侧边栏打开今日"
+             :command.ui/goto-plugins                 "插件"
+             :command.ui/select-theme-color           "选择皮肤颜色"
+             :command.ui/toggle-cards                 "切换卡片"
+             :command.ui/toggle-left-sidebar          "切换左侧边栏"
              :command.editor/indent                   "缩进块标签"
              :command.editor/outdent                  "取消缩进块"
              :command.editor/move-block-up            "向上移动块"

+ 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
@@ -1648,11 +1649,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]

+ 7 - 8
src/main/frontend/template.cljs

@@ -2,17 +2,16 @@
   (:require [clojure.string :as string]
             [frontend.date :as date]
             [frontend.state :as state]
-            [frontend.util :as util]))
+            [logseq.graph-parser.util.page-ref :as page-ref]))
 
 (defn- variable-rules
   []
-  {"today" (util/format "[[%s]]" (date/today))
-   "yesterday" (util/format "[[%s]]" (date/yesterday))
-   "tomorrow" (util/format "[[%s]]" (date/tomorrow))
+  {"today" (page-ref/->page-ref (date/today))
+   "yesterday" (page-ref/->page-ref (date/yesterday))
+   "tomorrow" (page-ref/->page-ref (date/tomorrow))
    "time" (date/get-current-time)
-   "current page" (util/format "[[%s]]"
-                               (or (state/get-current-page)
-                                   (date/today)))})
+   "current page" (page-ref/->page-ref (or (state/get-current-page)
+                                           (date/today)))})
 
 ;; TODO: programmable
 ;; context information, date, current page
@@ -31,5 +30,5 @@
                          (let [;; NOTE: This following cannot handle timezones
                                ;; date (tc/to-local-date-time nld)
                                date (doto (goog.date.DateTime.) (.setTime (.getTime nld)))]
-                           (util/format "[[%s]]" (date/journal-name date)))
+                           (page-ref/->page-ref (date/journal-name date)))
                          match))))))

+ 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

+ 10 - 8
src/main/frontend/util/property.cljs

@@ -7,6 +7,7 @@
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.mldoc :as gp-mldoc]
             [logseq.graph-parser.property :as gp-property :refer [properties-start properties-end]]
+            [logseq.graph-parser.util.page-ref :as page-ref]
             [frontend.format.mldoc :as mldoc]
             [logseq.graph-parser.text :as text]
             [frontend.util.cursor :as cursor]))
@@ -44,7 +45,7 @@
   [line]
   (boolean
    (and (string? line)
-        (util/safe-re-find #"^\s?[^ ]+:: " line))))
+        (re-find (re-pattern (str "^\\s?[^ ]+" gp-property/colons " ")) line))))
 
 (defn front-matter-property?
   [line]
@@ -93,9 +94,10 @@
 (defn get-markdown-property-keys
   [content]
   (let [content-lines (string/split-lines content)
-        properties (filter #(re-matches #"^.+::\s*.+" %) content-lines)]
+        properties (filter #(re-matches (re-pattern (str "^.+" gp-property/colons "\\s*.+")) %)
+                           content-lines)]
     (when (seq properties)
-      (map #(->> (string/split % "::")
+      (map #(->> (string/split % gp-property/colons)
                  (remove string/blank?)
                  first
                  string/upper-case)
@@ -165,7 +167,7 @@
   [format properties]
   (when (seq properties)
     (let [org? (= format :org)
-          kv-format (if org? ":%s: %s" "%s:: %s")
+          kv-format (if org? ":%s: %s" (str "%s" gp-property/colons " %s"))
           full-format (if org? ":PROPERTIES:\n%s\n:END:" "%s\n")
           properties-content (->> (map (fn [[k v]] (util/format kv-format (name k) v)) properties)
                                   (string/join "\n"))]
@@ -202,7 +204,7 @@
             built-in-properties-area (map (fn [[k v]]
                                             (if org?
                                               (str ":" (name k) ": " v)
-                                              (str (name k) ":: " v))) properties)
+                                              (str (name k) gp-property/colons " " v))) properties)
             body (concat (if no-title? nil [title])
                          (when org? [properties-start])
                          built-in-properties-area
@@ -276,7 +278,7 @@
 
                     (not org?)
                     (let [exists? (atom false)
-                          sym (if front-matter? ": " ":: ")
+                          sym (if front-matter? ": " (str gp-property/colons " "))
                           new-property-s (str key sym value)
                           property-f (if front-matter? front-matter-property? simplified-property?)
                           groups (partition-by property-f lines)
@@ -324,7 +326,7 @@
                (some->>
                 (seq v)
                 (distinct)
-                (map (fn [item] (util/format "[[%s]]" (text/page-ref-un-brackets! item))))
+                (map (fn [item] (page-ref/->page-ref (text/page-ref-un-brackets! item))))
                 (string/join ", "))
                v)]
        (insert-property format content k v)))
@@ -344,7 +346,7 @@
                           (remove-f (fn [line]
                                       (let [s (string/triml (string/lower-case line))]
                                         (or (string/starts-with? s (str ":" key ":"))
-                                            (string/starts-with? s (str key ":: ")))))))]
+                                            (string/starts-with? s (str key gp-property/colons " ")))))))]
            (string/join "\n" lines)))))))
 
 (defn remove-id-property

+ 3 - 6
src/main/frontend/util/text.cljs

@@ -59,9 +59,10 @@
         result (reduce (fn [acc line]
                          (let [new-pos (+ acc (count line))]
                            (if (>= new-pos pos)
-                             (reduced line)
+                             (reduced {:line line
+                                       :start-pos acc})
                              (inc new-pos)))) 0 lines)]
-    (when (string? result)
+    (when (map? result)
       result)))
 
 (defn surround-by?
@@ -122,7 +123,3 @@
             (string/join "/" parts)
             (last parts))
           js/decodeURI))))
-
-(defn extract-all-block-refs
-  [content]
-  (map second (re-seq #"\(\(([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12})\)\)" content)))

+ 8 - 6
src/main/frontend/util/thingatpt.cljs

@@ -5,6 +5,8 @@
             [frontend.config :as config]
             [logseq.graph-parser.text :as text]
             [logseq.graph-parser.property :as gp-property]
+            [logseq.graph-parser.util.block-ref :as block-ref]
+            [logseq.graph-parser.util.page-ref :as page-ref]
             [cljs.reader :as reader]
             [goog.object :as gobj]))
 
@@ -46,14 +48,14 @@
          :end line-end-pos}))))
 
 (defn block-ref-at-point [& [input]]
-  (when-let [block-ref (thing-at-point ["((" "))"] input " ")]
+  (when-let [block-ref (thing-at-point [block-ref/left-parens block-ref/right-parens] input " ")]
     (when-let [uuid (uuid (:raw-content block-ref))]
       (assoc block-ref
              :type "block-ref"
              :link uuid))))
 
 (defn page-ref-at-point [& [input]]
-  (when-let [page-ref (thing-at-point ["[[" "]]"] input)]
+  (when-let [page-ref (thing-at-point [page-ref/left-brackets page-ref/right-brackets] input)]
     (assoc page-ref
            :type "page-ref"
            :link (text/get-page-name
@@ -85,14 +87,14 @@
           (case (state/get-preferred-format) ;; TODO fix me to block's format
             :org (thing-at-point ":" input "\n")
             (when-let [line (:raw-content (line-at-point input))]
-              (let [key (first (string/split line "::"))
+              (let [key (first (string/split line gp-property/colons))
                     line-beginning-pos (cursor/line-beginning-pos input)
                     pos-in-line (- (cursor/pos input) line-beginning-pos)]
-                (when (<= 0 pos-in-line (+ (count key) (count "::")))
-                  {:full-content (str key "::")
+                (when (<= 0 pos-in-line (+ (count key) (count gp-property/colons)))
+                  {:full-content (str key gp-property/colons)
                    :raw-content key
                    :start line-beginning-pos
-                   :end (+ line-beginning-pos (count (str key "::")))}))))]
+                   :end (+ line-beginning-pos (count (str key gp-property/colons)))}))))]
       (assoc property :type "property-key"))))
 
 (defn get-list-item-indent&bullet [line]

+ 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"

+ 32 - 0
src/test/frontend/handler/editor_test.cljs

@@ -116,3 +116,35 @@
     "TODO" "TODO content" "DOING content"
     "TODO" "## TODO content" "## DOING content"
     "DONE" "DONE content" "content"))
+
+(defn- handle-last-input-handler
+  "Spied version of editor/keydown-not-matched-handler"
+  [{:keys [value cursor-pos]}]
+  ;; Reset editor action in order to test result
+  (state/set-editor-action! nil)
+  ;; Default cursor pos to end of line
+  (let [pos (or cursor-pos (count value))]
+    (with-redefs [state/get-input (constantly #js {:value value})
+                  cursor/pos (constantly pos)
+                  cursor/move-cursor-backward (constantly nil) ;; ignore if called
+                  cursor/get-caret-pos (constantly {})]
+      (editor/handle-last-input))))
+
+(deftest handle-last-input-handler-test
+  (testing "Property autocompletion"
+    (handle-last-input-handler {:value "::"})
+    (is (= :property-search (state/get-editor-action))
+        "Autocomplete properties if only colons have been typed")
+
+    (handle-last-input-handler {:value "foo::bar\n::"})
+    (is (= :property-search (state/get-editor-action))
+        "Autocomplete properties if typing colons on a second line")
+
+    (handle-last-input-handler {:value "middle of line::"})
+    (is (= nil (state/get-editor-action))
+        "Don't autocomplete properties if typing colons in the middle of a line")
+
+    (handle-last-input-handler {:value "first \nfoo::bar"
+                                :cursor-pos (dec (count "first "))})
+    (is (= nil (state/get-editor-action))
+        "Don't autocomplete properties if typing in a block where properties already exist")))

+ 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]))
+
   )

+ 13 - 5
src/test/frontend/util/property_test.cljs

@@ -71,12 +71,20 @@
       "** hello\n\na:: b")))
 
 (deftest test-get-property-keys
-  (are [x y] (= x y)
-    (property/get-property-keys :org "hello\n:PROPERTIES:\n:x1: y1\n:x2: y2\n:END:\n")
-    ["X1" "X2"]
+  (testing "org mode"
+    (are [x y] (= x y)
+        (property/get-property-keys :org "hello\n:PROPERTIES:\n:x1: y1\n:x2: y2\n:END:\n")
+        ["X1" "X2"]
+
+        (property/get-property-keys :org "hello\n:PROPERTIES:\n:END:\n")
+        nil))
+  (testing "markdown mode"
+    (are [x y] (= x y)
+        (property/get-property-keys :markdown "hello\nx1:: y1\nx2:: y2\n")
+        ["X1" "X2"]
 
-    (property/get-property-keys :org "hello\n:PROPERTIES:\n:END:\n")
-    nil))
+        (property/get-property-keys :markdown "hello\n")
+        nil)))
 
 (deftest test-insert-property
   (are [x y] (= x y)