瀏覽代碼

fix: conflicts

charlie 4 年之前
父節點
當前提交
e3447ef021
共有 84 個文件被更改,包括 2412 次插入1865 次删除
  1. 1 1
      README.md
  2. 1 1
      deps.edn
  3. 2 0
      externs.js
  4. 4 2
      package.json
  5. 1 1
      resources/css/common.css
  6. 0 16
      resources/css/tooltip.css
  7. 1 1
      resources/package.json
  8. 2 1
      shadow-cljs.edn
  9. 9 2
      src/electron/electron/handler.cljs
  10. 3 0
      src/main/frontend/commands.cljs
  11. 283 248
      src/main/frontend/components/block.cljs
  12. 1 0
      src/main/frontend/components/block.css
  13. 14 0
      src/main/frontend/components/commit.cljs
  14. 12 1
      src/main/frontend/components/content.cljs
  15. 2 6
      src/main/frontend/components/editor.cljs
  16. 1 1
      src/main/frontend/components/file.cljs
  17. 1 1
      src/main/frontend/components/header.cljs
  18. 3 1
      src/main/frontend/components/journal.cljs
  19. 2 2
      src/main/frontend/components/lazy_editor.cljs
  20. 16 68
      src/main/frontend/components/onboarding.cljs
  21. 157 193
      src/main/frontend/components/page.cljs
  22. 14 0
      src/main/frontend/components/page.css
  23. 44 38
      src/main/frontend/components/reference.cljs
  24. 11 6
      src/main/frontend/components/search.cljs
  25. 65 0
      src/main/frontend/components/shortcut.cljs
  26. 2 2
      src/main/frontend/config.cljs
  27. 4 1
      src/main/frontend/context/i18n.cljs
  28. 1 1
      src/main/frontend/core.cljs
  29. 19 3
      src/main/frontend/date.cljs
  30. 3 3
      src/main/frontend/db.cljs
  31. 35 80
      src/main/frontend/db/model.cljs
  32. 0 4
      src/main/frontend/db/outliner.cljs
  33. 25 25
      src/main/frontend/db/query_react.cljs
  34. 8 5
      src/main/frontend/db/react.cljs
  35. 12 208
      src/main/frontend/dicts.cljs
  36. 50 56
      src/main/frontend/extensions/code.cljs
  37. 4 0
      src/main/frontend/extensions/excalidraw.cljs
  38. 18 41
      src/main/frontend/format/block.cljs
  39. 25 19
      src/main/frontend/format/mldoc.cljs
  40. 1 41
      src/main/frontend/format/mldoc_test.cljs
  41. 3 3
      src/main/frontend/fs/watcher_handler.cljs
  42. 21 7
      src/main/frontend/handler/block.cljs
  43. 8 1
      src/main/frontend/handler/common.cljs
  44. 91 42
      src/main/frontend/handler/editor.cljs
  45. 38 8
      src/main/frontend/handler/extract.cljs
  46. 1 18
      src/main/frontend/handler/git.cljs
  47. 64 23
      src/main/frontend/handler/page.cljs
  48. 22 22
      src/main/frontend/handler/repo.cljs
  49. 13 12
      src/main/frontend/handler/search.cljs
  50. 59 4
      src/main/frontend/handler/ui.cljs
  51. 6 4
      src/main/frontend/idb.cljs
  52. 0 30
      src/main/frontend/mixins.cljs
  53. 1 1
      src/main/frontend/modules/file/core.cljs
  54. 1 1
      src/main/frontend/modules/outliner/core.cljs
  55. 10 10
      src/main/frontend/modules/shortcut/before.cljs
  56. 0 122
      src/main/frontend/modules/shortcut/binding.cljc
  57. 403 0
      src/main/frontend/modules/shortcut/config.cljs
  58. 67 47
      src/main/frontend/modules/shortcut/core.cljs
  59. 110 0
      src/main/frontend/modules/shortcut/data_helper.cljs
  60. 176 0
      src/main/frontend/modules/shortcut/dict.cljs
  61. 0 94
      src/main/frontend/modules/shortcut/handler.cljs
  62. 10 0
      src/main/frontend/modules/shortcut/macro.clj
  63. 7 2
      src/main/frontend/routes.cljs
  64. 2 1
      src/main/frontend/search.cljs
  65. 9 4
      src/main/frontend/search/browser.cljs
  66. 3 2
      src/main/frontend/search/db.cljs
  67. 16 13
      src/main/frontend/state.cljs
  68. 49 33
      src/main/frontend/text.cljs
  69. 12 23
      src/main/frontend/ui.cljs
  70. 4 58
      src/main/frontend/ui/date_picker.cljs
  71. 11 2
      src/main/frontend/util.cljc
  72. 1 1
      src/main/frontend/version.cljs
  73. 1 1
      src/main/logseq/api.cljs
  74. 130 146
      src/test/frontend/db/query_dsl_test.cljs
  75. 5 1
      src/test/frontend/modules/outliner/core_test.cljs
  76. 18 32
      src/test/frontend/text_test.cljs
  77. 1 1
      src/test/frontend/util/marker_test.cljs
  78. 1 1
      src/test/frontend/util/priority_test.cljs
  79. 74 0
      templates/config.edn
  80. 5 0
      templates/contents.md
  81. 5 0
      templates/contents.org
  82. 14 0
      templates/dummy-notes-en.md
  83. 36 0
      templates/tutorial-en.md
  84. 47 16
      yarn.lock

+ 1 - 1
README.md

@@ -15,7 +15,7 @@ Use it to organize your todo list, to write your journals, or to record your uni
 
 ## Why Logseq?
 
-[Logseq](https://logseq.com) is a platform for knowledge sharing and management. It focuses on privacy, longevity, and user control.
+[Logseq](https://logseq.com) is a platform for knowledge management and collaboration. It focuses on privacy, longevity, and user control.
 Notice: the backend code will be open-sourced as soon as we’re sure that the backend service meets the security standards.
 
 The server will never store or analyze your private notes. Your data are plain text files and we currently support both Markdown and Emacs Org mode (more to be added soon).

+ 1 - 1
deps.edn

@@ -1,4 +1,4 @@
-{:paths ["src/main"]
+{:paths ["src/main" "templates"]
  :deps
  {org.clojure/clojure         {:mvn/version "1.10.0"}
   cheshire/cheshire {:mvn/version "5.10.0"}

+ 2 - 0
externs.js

@@ -63,6 +63,8 @@ dummy.run = function() {};
 dummy.all = function() {};
 dummy.transaction = function() {};
 dummy.getPath = function() {};
+dummy.getDoc = function() {};
+dummy.setValue = function() {};
 
 
 /**

+ 4 - 2
package.json

@@ -60,6 +60,7 @@
     "dependencies": {
         "@excalidraw/excalidraw": "^0.4.2",
         "@kanru/rage-wasm": "^0.2.1",
+        "@tippyjs/react": "^4.2.5",
         "chokidar": "^3.5.1",
         "chrono-node": "^2.2.1",
         "codemirror": "^5.58.1",
@@ -75,10 +76,11 @@
         "jszip": "^3.5.0",
         "mldoc": "0.6.16",
         "path": "^0.12.7",
-        "react": "^17.0.1",
-        "react-dom": "^17.0.1",
+        "react": "^17.0.2",
+        "react-dom": "^17.0.2",
         "react-resize-context": "^3.0.0",
         "react-textarea-autosize": "^8.0.1",
+        "react-tippy": "^1.4.0",
         "react-transition-group": "^4.3.0",
         "url": "^0.11.0",
         "yargs-parser": "^20.2.4"

+ 1 - 1
resources/css/common.css

@@ -34,7 +34,7 @@ html[data-theme='dark'] {
   --ls-table-tr-even-background-color: #03333f;
   --ls-active-primary-color: #8ec2c2;
   --ls-active-secondary-color: #d0e8e8;
-  --ls-block-properties-background-color: #02222a;
+  --ls-block-properties-background-color: #06323e;
   --ls-block-ref-link-text-color: #1a6376;
   --ls-search-background-color: linear-gradient(
     to right,

File diff suppressed because it is too large
+ 0 - 16
resources/css/tooltip.css


+ 1 - 1
resources/package.json

@@ -3,7 +3,7 @@
   "version": "0.0.1",
   "main": "electron.js",
   "author": "Logseq",
-  "description": "A privacy-first, open-source platform for knowledge sharing and management.",
+  "description": "A privacy-first, open-source platform for knowledge management and collaboration.",
   "repository": "https://github.com/logseq/logseq",
   "scripts": {
     "electron:dev": "electron-forge start",

+ 2 - 1
shadow-cljs.edn

@@ -37,7 +37,8 @@
     :http-root    "public"
     :http-port    3001
     :watch-path   "static"
-    :preloads     [devtools.preload]}}
+    :preloads     [devtools.preload
+                   shadow.remote.runtime.cljs.browser]}}
 
   :electron {:target :node-script
              :output-to "static/electron.js"

+ 9 - 2
src/electron/electron/handler.cljs

@@ -62,8 +62,8 @@
   (or
    (some #(string/starts-with? path (str dir "/" %))
          ["." "assets" "node_modules"])
-   (some #(string/ends-with? path (str dir "/" %))
-         [".swap" ".crswap" ".tmp"])))
+   (some #(string/ends-with? path %)
+         [".swap" ".crswap" ".tmp" ".DS_Store"])))
 
 (defonce allowed-formats
   #{:org :markdown :md :edn :json :css :excalidraw})
@@ -156,6 +156,7 @@
       (send file-watcher-chan
             (bean/->js {:type type :payload payload}))))
 
+(defonce polling-interval 5000)
 (defn watch-dir!
   [win dir]
   (when (fs/existsSync dir)
@@ -163,8 +164,14 @@
                           (clj->js
                            {:ignored (partial ignored-path? dir)
                             :ignoreInitial true
+                            :ignorePermissionErrors true
+                            :interval polling-interval
+                            :binaryInterval polling-interval
                             :persistent true
+                            :disableGlobbing true
+
                             :awaitWriteFinish true}))]
+      ;; TODO: batch sender
       (.on watcher "add"
            (fn [path]
              (send-file-watcher! win "add"

+ 3 - 0
src/main/frontend/commands.cljs

@@ -286,6 +286,9 @@
                                (string/ends-with? s "(")
                                (or (string/starts-with? last-pattern "((")
                                    (string/starts-with? last-pattern "[["))))))
+          space? (if (and space? (string/starts-with? last-pattern "#[["))
+                   false
+                   space?)
           prefix (if (string/blank? last-pattern)
                    (if space?
                      (util/concat-without-spaces prefix value)

+ 283 - 248
src/main/frontend/components/block.cljs

@@ -140,7 +140,7 @@
             parts-2 (string/split path #"/")
             current-dir (string/join "/" (drop-last 1 parts))]
         (cond
-          (util/starts-with? path "/")
+          (if util/win32? (utils/win32 path) (util/starts-with? path "/"))
           path
 
           (and (not (util/starts-with? path ".."))
@@ -330,6 +330,39 @@
 
 (declare page-reference)
 
+(rum/defc page-inner
+  [config page-name href redirect-page-name page-entity contents-page? children html-export? label]
+  [:a.page-ref
+   {:data-ref page-name
+    :href href
+    :on-click (fn [e]
+                (util/stop e)
+                (if (gobj/get e "shiftKey")
+                  (when-let [page-entity (db/entity [:block/name redirect-page-name])]
+                    (state/sidebar-add-block!
+                     (state/get-current-repo)
+                     (:db/id page-entity)
+                     :page
+                     {:page page-entity}))
+                  (route-handler/redirect! {:to :page
+                                            :path-params {:name redirect-page-name}}))
+                (when (and contents-page?
+                           (state/get-left-sidebar-open?))
+                  (ui-handler/close-left-sidebar!)))}
+
+   (if (seq children)
+     (for [child children]
+       (if (= (first child) "Label")
+         (last child)
+         (let [{:keys [content children]} (last child)
+               page-name (subs content 2 (- (count content) 2))]
+           (page-reference html-export? page-name (assoc config :children children) nil))))
+     (if (and label
+              (string? label)
+              (not (string/blank? label))) ; alias
+       label
+       (get page-entity :block/original-name page-name)))])
+
 (rum/defc page-cp
   [{:keys [html-export? label children contents-page?] :as config} page]
   (when-let [page-name (:block/name page)]
@@ -349,37 +382,14 @@
                                page)
           href (if html-export?
                  (util/encode-str page)
-                 (rfe/href :page {:name redirect-page-name}))]
-      [:a.page-ref
-       {:data-ref page-name
-        :href href
-        :on-click (fn [e]
-                    (util/stop e)
-                    (if (gobj/get e "shiftKey")
-                      (when-let [page-entity (db/entity [:block/name redirect-page-name])]
-                        (state/sidebar-add-block!
-                         (state/get-current-repo)
-                         (:db/id page-entity)
-                         :page
-                         {:page page-entity}))
-                      (route-handler/redirect! {:to :page
-                                                :path-params {:name redirect-page-name}}))
-                    (when (and contents-page?
-                               (state/get-left-sidebar-open?))
-                      (ui-handler/close-left-sidebar!)))}
-
-       (if (seq children)
-         (for [child children]
-           (if (= (first child) "Label")
-             (last child)
-             (let [{:keys [content children]} (last child)
-                   page-name (subs content 2 (- (count content) 2))]
-               (page-reference html-export? page-name (assoc config :children children) nil))))
-         (if (and label
-                  (string? label)
-                  (not (string/blank? label))) ; alias
-           label
-           (get page-entity :block/original-name page-name)))])))
+                 (rfe/href :page {:name redirect-page-name}))
+          inner (page-inner config page-name href redirect-page-name page-entity contents-page? children html-export? label)]
+      inner
+      ;; (ui/tippy
+      ;;  {:interactive true
+      ;;   :html (page-preview page-name)}
+      ;;  inner)
+      )))
 
 (rum/defc asset-reference
   [title path]
@@ -453,7 +463,8 @@
   (let [blocks (db/get-block-and-children (state/get-current-repo) id)]
     [:div.color-level.embed-block.bg-base-2 {:style {:z-index 2}}
      [:div.px-3.pt-1.pb-2
-      (blocks-container blocks (assoc (assoc config :id (str id))
+      (blocks-container blocks (assoc config
+                                      :id (str id)
                                       :embed? true
                                       :ref? false))]]))
 
@@ -473,6 +484,7 @@
                   page-name))
        (let [blocks (db/get-page-blocks (state/get-current-repo) page-name)]
          (blocks-container blocks (assoc config
+                                         :id page-name
                                          :embed? true
                                          :ref? false))))]))
 
@@ -496,6 +508,7 @@
     (util/format "{{{%s}}}" name)))
 
 (declare block-content)
+(declare block-container)
 (rum/defc block-reference < rum/reactive
   [config id label]
   (when-not (string/blank? id)
@@ -523,11 +536,13 @@
                          (->elem
                           :span.block-ref
                           (map-inline config title))))]
-           (if label
-             (->elem
-              :span.block-ref {:title (:block/content block)} ; TODO: replace with a popup
-              (map-inline config label))
-             title))]
+           (ui/tippy {:html (block-container config block)
+                      :interactive true}
+            (if label
+              (->elem
+               :span.block-ref
+               (map-inline config label))
+              title)))]
         [:span.warning.mr-1 {:title "Block ref invalid"}
          (util/format "((%s))" id)]))))
 
@@ -644,7 +659,7 @@
 
     ["Block_reference" id]
     ;; FIXME: alert when self block reference
-    (block-reference config id nil)
+    (block-reference (assoc config :reference? true) id nil)
 
     ["Nested_link" link]
     (nested-link config html-export? link)
@@ -870,7 +885,6 @@
                             (<= (count url) 15) url
                             :else
                             (last (re-find id-regex url)))]
-              (prn {:id id})
               (when-not (string/blank? id)
                 (let [width (min (- (util/get-width) 96)
                                  560)
@@ -1097,7 +1111,6 @@
                 {:top 0}
                 {:bottom 0}))}]))
 
-(declare block-container)
 (defn block-checkbox
   [block class]
   (let [marker (:block/marker block)
@@ -1163,20 +1176,28 @@
               :style {:margin-right 3.5}}
        (string/upper-case marker)])))
 
+(rum/defc set-priority
+  [block priority]
+  [:ul
+   (for [p (remove #(= priority %) ["A" "B" "C"])]
+     [:a.mr-2.text-base.tooltip-priority {:priority p
+                                          :on-click (fn [] (editor-handler/set-priority block p))}])])
+
+(rum/defc priority-text
+  [priority]
+  [:a.opacity-50.hover:opacity-100
+   {:class "priority"
+    :href (rfe/href :page {:name priority})
+    :style {:margin-right 3.5}}
+   (util/format "[#%s]" (str priority))])
+
 (defn priority-cp
   [{:block/keys [pre-block? priority] :as block}]
-
   (when (and (not pre-block?) priority)
-    (ui/tooltip
-     [:ul
-      (for [p (remove #(= priority %) ["A" "B" "C"])]
-        [:a.mr-2.text-base.tooltip-priority {:priority p
-                                             :on-click (fn [] (editor-handler/set-priority block p))}])]
-     [:a.opacity-50.hover:opacity-100
-      {:class "priority"
-       :href (rfe/href :page {:name priority})
-       :style {:margin-right 3.5}}
-      (util/format "[#%s]" (str priority))])))
+    (ui/tippy
+     {:interactive true
+      :html (set-priority block priority)}
+     (priority-text priority))))
 
 (defn block-tags-cp
   [{:block/keys [pre-block? tags] :as block}]
@@ -1212,9 +1233,16 @@
         priority (priority-cp t)
         tags (block-tags-cp t)
         bg-color (:background-color properties)
-        elem (if (and (false? unordered)
-                      heading-level
-                      (<= heading-level 6))
+        heading-level (or (and (false? unordered)
+                               heading-level
+                               (<= heading-level 6)
+                               heading-level)
+                          (and (get properties :heading)
+                               (<= level 6)
+                               level
+                               ;; FIXME: construct the proper level later
+                               2))
+        elem (if heading-level
                (keyword (str "h" heading-level))
                :div)]
     (->elem
@@ -1262,27 +1290,32 @@
 
 (rum/defc property-cp
   [config block k v]
-  [:div.my-1
-   [:b (name k)]
-   [:span.mr-1 ":"]
-   (cond
-     (int? v)
-     v
-
-     (coll? v)
-     (let [v (->> (remove string/blank? v)
-                  (filter string?))
-           vals (for [v-item v]
-                  (page-cp config {:block/name v-item}))
-           elems (interpose (span-comma) vals)]
-       (for [elem elems]
-         (rum/with-key elem (str (random-uuid)))))
-
-     :else
-     (let [page-name (string/lower-case (str v))]
-       (if (db/entity [:block/name page-name])
-         (page-cp config {:block/name page-name})
-         (inline-text (:block/format block) (str v)))))])
+  (let [pre-block? (:block/pre-block? block)
+        date (and (= k :date) (date/get-locale-string (str v)))]
+    [:div
+     [:span.font-bold (name k)]
+     [:span.mr-1 ":"]
+     (cond
+       (int? v)
+       v
+
+       date
+       date
+
+       (coll? v)
+       (let [v (->> (remove string/blank? v)
+                    (filter string?))
+             vals (for [v-item v]
+                    (page-cp config {:block/name v-item}))
+             elems (interpose (span-comma) vals)]
+         (for [elem elems]
+           (rum/with-key elem (str (random-uuid)))))
+
+       :else
+       (let [page-name (string/lower-case (str v))]
+         (if (db/entity [:block/name page-name])
+           (page-cp config {:block/name page-name})
+           (inline-text (:block/format block) (str v)))))]))
 
 (rum/defc properties-cp
   [config block]
@@ -1295,7 +1328,7 @@
         properties (sort properties)]
     (cond
       (seq properties)
-      [:div.blocks-properties.text-sm.opacity-80.my-1.p-2
+      [:div.blocks-properties
        (for [[k v] properties]
          (rum/with-key (property-cp config block k v)
            (str (:block/uuid block) "-" k)))]
@@ -1659,6 +1692,7 @@
         slide? (boolean (:slide? config))
         doc-mode? (:document/mode? config)
         embed? (:embed? config)
+        reference? (:reference? config)
         unique-dom-id (build-id (dissoc config :block/uuid))
         block-id (str "ls-block-" unique-dom-id uuid)
         has-child? (boolean
@@ -1681,8 +1715,12 @@
        :blockid (str uuid)
        :repo repo
        :haschild (str has-child?)}
+
        (not slide?)
-       (merge attrs))
+       (merge attrs)
+
+       (or reference? embed?)
+       (assoc :data-transclude true))
 
      (when (and ref? breadcrumb-show?)
        (when-let [comp (block-parents config repo uuid format false)]
@@ -1710,7 +1748,7 @@
        (let [children (db/get-block-immediate-children repo uuid)
              children (block-handler/filter-blocks repo children (:filters config) false)]
          (when (seq children)
-           [:div.ref-children.ml-12
+           [:div.ref-children {:style {:margin-left "1.8rem"}}
             (blocks-container children (assoc config
                                               :breadcrumb-show? false
                                               :ref? true))])))
@@ -1780,7 +1818,7 @@
         (->elem
          :li
          (cond->
-           {:checked checked?}
+          {:checked checked?}
            number
            (assoc :value number))
          (vec-cat
@@ -1905,15 +1943,8 @@
            ;; exclude the current one, otherwise it'll loop forever
            remove-blocks (if current-block-uuid [current-block-uuid] nil)
            query-result (and query-atom (rum/react query-atom))
-           result (cond
-                    (and query-result dsl-query?)
-                    (apply concat query-result)
-
-                    query-result
-                    (db/custom-query-result-transform query-result remove-blocks q)
-
-                    :else
-                    nil)
+           result (when query-result
+                    (db/custom-query-result-transform query-result remove-blocks q))
            view-f (and view (sci/eval-string (pr-str view)))
            only-blocks? (:block/uuid (first result))
            blocks-grouped-by-page? (and (seq result)
@@ -2016,173 +2047,176 @@
 ;;       :else
 ;;       ["Src" options])))
 
+(rum/defc src-cp < rum/static
+  [config options html-export?]
+  (when options
+    (let [{:keys [language lines pos_meta]} options
+          attr (if language
+                 {:data-lang language})
+          code (join-lines lines)]
+      (cond
+        html-export?
+        (highlight/html-export attr code)
+
+        :else
+        (let [language (if (contains? #{"edn" "clj" "cljc" "cljs" "clojure"} language) "text/x-clojure" language)]
+          (if (:slide? config)
+            (highlight/highlight (str (medley/random-uuid)) {:data-lang language} code)
+            [:div
+             (lazy-editor/editor config (str (dc/squuid)) attr code options)
+             (let [options (:options options)]
+               (when (and (= language "text/x-clojure") (contains? (set options) ":results"))
+                 (sci/eval-result code)))]))))))
+
 (defn markup-element-cp
   [{:keys [html-export?] :as config} item]
   (let [format (or (:block/format config)
                    :markdown)]
     (try
-     (match item
-       ["Properties" m]
-       [:div.properties
-        (for [[k v] (dissoc m :roam_alias :roam_tags)]
-          (when (and (not (and (= k :macros) (empty? v))) ; empty macros
-                     (not (= k :title))
-                     (not (= k :filters)))
-            [:div.property
-             [:span.font-medium.mr-1 (str (name k) ": ")]
-             (if (coll? v)
-               (let [vals (for [item v]
-                            (if (coll? v)
-                              (let [config (if (= k :alias)
-                                             (assoc config :block/alias? true))]
-                                (page-cp config {:block/name item}))
-                              (inline-text format item)))]
-                 (interpose [:span ", "] vals))
-               (inline-text format v))]))]
-
-       ["Paragraph" l]
+      (match item
+        ["Properties" m]
+        [:div.properties
+         (for [[k v] (dissoc m :roam_alias :roam_tags)]
+           (when (and (not (and (= k :macros) (empty? v))) ; empty macros
+                      (not (= k :title))
+                      (not (= k :filters)))
+             [:div.property
+              [:span.font-medium.mr-1 (str (name k) ": ")]
+              (if (coll? v)
+                (let [vals (for [item v]
+                             (if (coll? v)
+                               (let [config (if (= k :alias)
+                                              (assoc config :block/alias? true))]
+                                 (page-cp config {:block/name item}))
+                               (inline-text format item)))]
+                  (interpose [:span ", "] vals))
+                (inline-text format v))]))]
+
+        ["Paragraph" l]
        ;; TODO: speedup
-       (if (re-find #"\"Export_Snippet\" \"embed\"" (str l))
-         (->elem :div (map-inline config l))
-         (->elem :div.is-paragraph (map-inline config l)))
-
-       ["Horizontal_Rule"]
-       (when-not (:slide? config)
-         [:hr])
-       ["Heading" h]
-       (block-container config h)
-       ["List" l]
-       (let [lists (divide-lists l)]
-         (if (= 1 (count lists))
-           (let [l (first lists)]
-             (->elem
-              (list-element l)
-              (map #(list-item config %) l)))
-           [:div.list-group
-            (for [l lists]
+        (if (re-find #"\"Export_Snippet\" \"embed\"" (str l))
+          (->elem :div (map-inline config l))
+          (->elem :div.is-paragraph (map-inline config l)))
+
+        ["Horizontal_Rule"]
+        (when-not (:slide? config)
+          [:hr])
+        ["Heading" h]
+        (block-container config h)
+        ["List" l]
+        (let [lists (divide-lists l)]
+          (if (= 1 (count lists))
+            (let [l (first lists)]
               (->elem
                (list-element l)
-               (map #(list-item config %) l)))]))
-       ["Table" t]
-       (table config t)
-       ["Math" s]
-       (if html-export?
-         (latex/html-export s true true)
-         (latex/latex (str (dc/squuid)) s true true))
-       ["Example" l]
-       [:pre.pre-wrap-white-space
-        (join-lines l)]
-       ["Quote" l]
-       (->elem
-        :blockquote
-        (markup-elements-cp config l))
-       ["Raw_Html" content]
-       (when (not html-export?)
-         [:div.raw_html {:dangerouslySetInnerHTML
-                         {:__html content}}])
-       ["Export" "html" options content]
-       (when (not html-export?)
-         [:div.export_html {:dangerouslySetInnerHTML
-                            {:__html content}}])
-       ["Hiccup" content]
-       (ui/catch-error
-        [:div.warning {:title "Invalid hiccup"}
-         content]
-        (-> (safe-read-string content)
-            (security/remove-javascript-links-in-href)))
-
-       ["Export" "latex" options content]
-       (if html-export?
-         (latex/html-export content true false)
-         (latex/latex (str (dc/squuid)) content true false))
-
-       ["Custom" "query" _options result content]
-       (try
-         (let [query (reader/read-string content)]
-           (custom-query config query))
-         (catch js/Error e
-           (println "read-string error:")
-           (js/console.error e)
-           [:div.warning {:title "Invalid query"}
-            content]))
-
-       ["Custom" "note" options result content]
-       (admonition config "note" options result)
-
-       ["Custom" "tip" options result content]
-       (admonition config "tip" options result)
-
-       ["Custom" "important" options result content]
-       (admonition config "important" options result)
-
-       ["Custom" "caution" options result content]
-       (admonition config "caution" options result)
-
-       ["Custom" "warning" options result content]
-       (admonition config "warning" options result)
-
-       ["Custom" "pinned" options result content]
-       (admonition config "pinned" options result)
-
-       ["Custom" name options l content]
-       (->elem
-        :div
-        {:class name}
-        (markup-elements-cp config l))
-
-       ["Latex_Fragment" l]
-       [:p.latex-fragment
-        (inline config ["Latex_Fragment" l])]
-
-       ["Latex_Environment" name option content]
-       (let [content (latex-environment-content name option content)]
-         (if html-export?
-           (latex/html-export content true true)
-           (latex/latex (str (dc/squuid)) content true true)))
-
-       ["Displayed_Math" content]
-       (if html-export?
-         (latex/html-export content true true)
-         (latex/latex (str (dc/squuid)) content true true))
-
-       ["Footnote_Definition" name definition]
-       (let [id (util/url-encode name)]
-         [:div.footdef
-          [:div.footpara
-           (conj
-            (markup-element-cp config ["Paragraph" definition])
-            [:a.ml-1 {:id (str "fn." id)
-                      :style {:font-size 14}
-                      :class "footnum"
-                      :on-click #(route-handler/jump-to-anchor! (str "fnr." id))}
-             [:sup.fn (str name "↩︎")]])]])
-
-       ["Src" options]
-       (when options
-         (let [{:keys [language options lines pos_meta]} options
-               attr (if language
-                      {:data-lang language})
-               code (join-lines lines)]
-           (cond
-             html-export?
-             (highlight/html-export attr code)
-
-             :else
-             (let [language (if (contains? #{"edn" "clj" "cljc" "cljs" "clojure"} language) "text/x-clojure" language)]
-               (highlight/highlight (str (medley/random-uuid)) {:data-lang language} code)
-               ;; (if (:slide? config)
-               ;;   (highlight/highlight (str (medley/random-uuid)) {:data-lang language} code)
-               ;;   [:div
-               ;;    (lazy-editor/editor config (str (dc/squuid)) attr code pos_meta)
-               ;;    (when (and (= language "text/x-clojure") (contains? (set options) ":results"))
-               ;;      (sci/eval-result code))])
-               ))))
+               (map #(list-item config %) l)))
+            [:div.list-group
+             (for [l lists]
+               (->elem
+                (list-element l)
+                (map #(list-item config %) l)))]))
+        ["Table" t]
+        (table config t)
+        ["Math" s]
+        (if html-export?
+          (latex/html-export s true true)
+          (latex/latex (str (dc/squuid)) s true true))
+        ["Example" l]
+        [:pre.pre-wrap-white-space
+         (join-lines l)]
+        ["Quote" l]
+        (->elem
+         :blockquote
+         (markup-elements-cp config l))
+        ["Raw_Html" content]
+        (when (not html-export?)
+          [:div.raw_html {:dangerouslySetInnerHTML
+                          {:__html content}}])
+        ["Export" "html" options content]
+        (when (not html-export?)
+          [:div.export_html {:dangerouslySetInnerHTML
+                             {:__html content}}])
+        ["Hiccup" content]
+        (ui/catch-error
+         [:div.warning {:title "Invalid hiccup"}
+          content]
+         (-> (safe-read-string content)
+             (security/remove-javascript-links-in-href)))
+
+        ["Export" "latex" options content]
+        (if html-export?
+          (latex/html-export content true false)
+          (latex/latex (str (dc/squuid)) content true false))
+
+        ["Custom" "query" _options result content]
+        (try
+          (let [query (reader/read-string content)]
+            (custom-query config query))
+          (catch js/Error e
+            (println "read-string error:")
+            (js/console.error e)
+            [:div.warning {:title "Invalid query"}
+             content]))
+
+        ["Custom" "note" options result content]
+        (admonition config "note" options result)
+
+        ["Custom" "tip" options result content]
+        (admonition config "tip" options result)
+
+        ["Custom" "important" options result content]
+        (admonition config "important" options result)
+
+        ["Custom" "caution" options result content]
+        (admonition config "caution" options result)
+
+        ["Custom" "warning" options result content]
+        (admonition config "warning" options result)
+
+        ["Custom" "pinned" options result content]
+        (admonition config "pinned" options result)
+
+        ["Custom" name options l content]
+        (->elem
+         :div
+         {:class name}
+         (markup-elements-cp config l))
+
+        ["Latex_Fragment" l]
+        [:p.latex-fragment
+         (inline config ["Latex_Fragment" l])]
+
+        ["Latex_Environment" name option content]
+        (let [content (latex-environment-content name option content)]
+          (if html-export?
+            (latex/html-export content true true)
+            (latex/latex (str (dc/squuid)) content true true)))
+
+        ["Displayed_Math" content]
+        (if html-export?
+          (latex/html-export content true true)
+          (latex/latex (str (dc/squuid)) content true true))
+
+        ["Footnote_Definition" name definition]
+        (let [id (util/url-encode name)]
+          [:div.footdef
+           [:div.footpara
+            (conj
+             (markup-element-cp config ["Paragraph" definition])
+             [:a.ml-1 {:id (str "fn." id)
+                       :style {:font-size 14}
+                       :class "footnum"
+                       :on-click #(route-handler/jump-to-anchor! (str "fnr." id))}
+              [:sup.fn (str name "↩︎")]])]])
+
+        ["Src" options]
+        (src-cp config options html-export?)
 
-       :else
-       "")
-     (catch js/Error e
-       (println "Convert to html failed, error: " e)
-       ""))))
+        :else
+        "")
+      (catch js/Error e
+        (println "Convert to html failed, error: " e)
+        ""))))
 
 (defn markup-elements-cp
   [config col]
@@ -2252,7 +2286,8 @@
    (cond-> option
      (:document/mode? config)
      (assoc :class "doc-mode"))
-   (if (:group-by-page? config)
+   (if (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]

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

@@ -161,6 +161,7 @@
 }
 
 .blocks-properties {
+  padding: 4px 8px;
   background-color: var(--ls-block-properties-background-color, #f0f8ff);
 }
 

+ 14 - 0
src/main/frontend/components/commit.cljs

@@ -1,6 +1,7 @@
 (ns frontend.components.commit
   (:require [rum.core :as rum]
             [frontend.util :as util :refer-macros [profile]]
+            [clojure.string :as string]
             [frontend.handler.repo :as repo-handler]
             [frontend.state :as state]
             [frontend.mixins :as mixins]
@@ -53,3 +54,16 @@
         {:type "button"
          :on-click close-fn}
         "Cancel"]]]]))
+
+(defn show-commit-modal! [e]
+  (when (and
+         (string/starts-with? (state/get-current-repo) "https://")
+         (not (util/input? (gobj/get e "target")))
+         (not (gobj/get e "shiftKey"))
+         (not (gobj/get e "ctrlKey"))
+         (not (gobj/get e "altKey"))
+         (not (gobj/get e "metaKey")))
+    (when-let [repo-url (state/get-current-repo)]
+      (when-not (state/get-edit-input-id)
+        (util/stop e)
+        (state/set-modal! commit-and-push!)))))

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

@@ -128,7 +128,8 @@
   [target block-id]
   (rum/with-context [[t] i18n/*tongue-context*]
     (when-let [block (db/entity [:block/uuid block-id])]
-      (let [properties (:block/properties block)]
+      (let [properties (:block/properties block)
+            heading? (true? (:heading properties))]
         [:div#custom-context-menu
          [:div.py-1.rounded-md.bg-base-3.shadow-xs
           [:div.flex-row.flex.justify-between.py-4.pl-2
@@ -146,6 +147,16 @@
                          (editor-handler/remove-block-property! block-id "background-color"))}
             "Clear"]]
 
+          (ui/menu-link
+           {:key "Convert heading"
+            :on-click (fn [_e]
+                        (if heading?
+                          (editor-handler/remove-block-property! block-id :heading)
+                          (editor-handler/set-block-property! block-id :heading true)))}
+           (if heading?
+             "Convert back to a block"
+             "Convert to a heading"))
+
           (ui/menu-link
            {:key "Open in sidebar"
             :on-click (fn [_e]

+ 2 - 6
src/main/frontend/components/editor.cljs

@@ -14,7 +14,6 @@
             [frontend.mixins :as mixins]
             [frontend.ui :as ui]
             [frontend.db :as db]
-            [frontend.modules.shortcut.handler :as shortcut-handler]
             [dommy.core :as d]
             [goog.object :as gobj]
             [goog.dom :as gdom]
@@ -78,7 +77,7 @@
           input (gdom/getElement id)]
       (when input
         (let [current-pos (:pos (util/get-caret-pos input))
-              edit-content (state/sub [:editor/content id])
+              edit-content (or (state/sub [:editor/content id]) "")
               edit-block (state/sub :editor/block)
               q (or
                  @editor-handler/*selected-text
@@ -382,10 +381,7 @@
                 (state/set-editor-args! (:rum/args state))
                 state)}
   (mixins/event-mixin setup-key-listener!)
-  (mixins/shortcuts
-   #(shortcut/install-shortcut! % {})
-   :shortcut-listener/editor-prevent-default
-   shortcut-handler/editing-only-prevent-default)
+  (shortcut/mixin :shortcut.handler/block-editing-only)
   lifecycle/lifecycle
   [state {:keys [on-hide dummy? node format block block-parent-id heading-level]
           :or   {dummy? false}

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

@@ -115,7 +115,7 @@
                  mode (util/get-file-ext path)
                  mode (if (contains? #{"edn" "clj" "cljc" "cljs" "clojure"} mode) "text/x-clojure" mode)]
              (lazy-editor/editor {:file? true
-                                  :file-path path} path {:data-lang mode} content nil)))
+                                  :file-path path} path {:data-lang mode} content {})))
 
          :else
          [:div (tongue :file/format-not-supported (name format))])])))

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

@@ -81,7 +81,7 @@
         (svg/horizontal-dots nil)])
      (->>
       [(when-not (util/mobile?)
-         {:title (t :help/toggle-right-sidebar)
+         {:title (t :shortcut.ui/toggle-right-sidebar)
           :options {:on-click state/toggle-sidebar-open?!}})
 
        (when current-repo

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

@@ -105,7 +105,9 @@
 
      (page/today-queries repo today? false)
 
-     (reference/references title false)
+     (rum/with-key
+       (reference/references title false)
+       (str title "-refs"))
 
      (when intro? (onboarding/intro))]))
 

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

@@ -13,8 +13,8 @@
                             (fn []
                               (reset! loaded? true)))
                  state)}
-  [config id attr code pos_meta]
+  [config id attr code options]
   (let [loaded? (rum/react loaded?)]
     (if loaded?
-      (@lazy-editor config id attr code pos_meta)
+      (@lazy-editor config id attr code options)
       (ui/loading "CodeMirror"))))

+ 16 - 68
src/main/frontend/components/onboarding.cljs

@@ -1,10 +1,12 @@
 (ns frontend.components.onboarding
-  (:require [rum.core :as rum]
+  (:require [frontend.components.shortcut :as shortcut]
             [frontend.components.svg :as svg]
-            [frontend.extensions.latex :as latex]
-            [frontend.extensions.highlight :as highlight]
             [frontend.context.i18n :as i18n]
-            [frontend.util :as util]))
+            [frontend.extensions.highlight :as highlight]
+            [frontend.extensions.latex :as latex]
+            [frontend.handler.route :as route-handler]
+            [frontend.ui :as ui]
+            [rum.core :as rum]))
 
 (rum/defc intro
   []
@@ -216,70 +218,16 @@
          svg/discord]]]
       [:li
        (t :help/shortcuts)
-       [:table
-        [:thead
-         [:tr
-          [:th [:b (t :help/shortcuts-triggers)]]
-          [:th (t :help/shortcut)]]]
-        [:tbody
-         [:tr [:td (t :help/slash-autocomplete)] [:td "/"]]
-         [:tr [:td (t :help/block-content-autocomplete)] [:td "<"]]
-         [:tr [:td (t :help/reference-autocomplete)] [:td "[[]]"]]
-         [:tr [:td (t :help/block-reference)] [:td "(())"]]]]
-       [:table
-        [:thead
-         [:tr
-          [:th [:span [:b (t :help/key-commands)]
-                (t :help/working-with-lists)]]
-          [:th (t :help/shortcut)]]]
-        [:tbody
-         [:tr [:td (t :help/indent-block-tab)] [:td "Tab"]]
-         [:tr [:td (t :help/unindent-block)] [:td "Shift-Tab"]]
-         [:tr [:td (t :help/move-block-up)] [:td (util/->platform-shortcut "Alt-Shift-Up")]]
-         [:tr [:td (t :help/move-block-down)] [:td (util/->platform-shortcut "Alt-Shift-Down")]]
-         [:tr [:td (t :help/create-new-block)] [:td "Enter"]]
-         [:tr [:td (t :help/new-line-in-block)] [:td "Shift-Enter"]]
-         [:tr [:td (t :undo)] [:td (util/->platform-shortcut "Ctrl-z")]]
-         [:tr [:td (t :redo)] [:td (util/->platform-shortcut "Ctrl-y")]]
-         [:tr [:td (t :help/zoom-in)] [:td (util/->platform-shortcut (if util/mac? "Cmd-." "Alt-Right"))]]
-         [:tr [:td (t :help/zoom-out)] [:td (util/->platform-shortcut (if util/mac? "Cmd-," "Alt-left"))]]
-         [:tr [:td (t :help/follow-link-under-cursor)] [:td (util/->platform-shortcut "Ctrl-o")]]
-         [:tr [:td (t :help/open-link-in-sidebar)] [:td (util/->platform-shortcut "Ctrl-shift-o")]]
-         [:tr [:td (t :expand)] [:td (util/->platform-shortcut "Ctrl-Down")]]
-         [:tr [:td (t :collapse)] [:td (util/->platform-shortcut "Ctrl-Up")]]
-         [:tr [:td (t :select-block-above)] [:td "Shift-Up"]]
-         [:tr [:td (t :select-block-below)] [:td "Shift-Down"]]
-         [:tr [:td (t :select-all-blocks)] [:td (util/->platform-shortcut "Ctrl-Shift-a")]]]]
-       [:table
-        [:thead
-         [:tr
-          [:th [:b (t :general)]]
-          [:th (t :help/shortcut)]]]
-        [:tbody
-         [:tr [:td (t :help/toggle)] [:td "?"]]
-         [:tr [:td (t :help/git-commit-message)] [:td "c"]]
-         [:tr [:td (t :help/full-text-search)] [:td (util/->platform-shortcut "Ctrl-u")]]
-         [:tr [:td (t :help/page-search)] [:td (util/->platform-shortcut "Ctrl-Shift-u")]]
-         [:tr [:td (t :help/open-link-in-sidebar)] [:td "Shift-Click"]]
-         [:tr [:td (t :help/context-menu)] [:td "Right Click"]]
-         [:tr [:td (t :help/fold-unfold)] [:td "Tab"]]
-         [:tr [:td (t :help/toggle-contents)] [:td "t c"]]
-         [:tr [:td (t :help/toggle-doc-mode)] [:td "t d"]]
-         [:tr [:td (t :help/toggle-theme)] [:td "t t"]]
-         [:tr [:td (t :help/toggle-right-sidebar)] [:td "t r"]]
-         [:tr [:td (t :help/toggle-settings)] [:td "t s"]]
-         [:tr [:td (t :help/toggle-insert-new-block)] [:td "t e"]]
-         [:tr [:td (t :help/jump-to-journals)] [:td (if util/mac? "Cmd-j" "Alt-j")]]]]
-       [:table
-        [:thead
-         [:tr
-          [:th [:b (t :formatting)]]
-          [:th (t :help/shortcut)]]]
-        [:tbody
-         [:tr [:td (t :bold)] [:td (util/->platform-shortcut "Ctrl-b")]]
-         [:tr [:td (t :italics)] [:td (util/->platform-shortcut "Ctrl-i")]]
-         [:tr [:td (t :html-link)] [:td (util/->platform-shortcut "Ctrl-k")]]
-         [:tr [:td (t :highlight)] [:td (util/->platform-shortcut "Ctrl-Shift-h")]]]]]
+       [:br]
+       (ui/button
+        "Learn more"
+        :on-click
+        (fn []
+          (route-handler/redirect! {:to :shortcut})))
+       (shortcut/trigger-table)
+       (shortcut/shortcut-table :shortcut.category/basics)
+       (shortcut/shortcut-table :shortcut.category/block-editing)
+       (shortcut/shortcut-table :shortcut.category/formatting)]
 
       [:li
        (t :help/markdown-syntax)

+ 157 - 193
src/main/frontend/components/page.cljs

@@ -56,48 +56,41 @@
         (page-handler/add-page-to-recent! repo page-original-name)
         (db/get-page-blocks repo page-name)))))
 
+(defn- open-first-block!
+  [state]
+  (let [blocks (nth (:rum/args state) 1)
+        block (first blocks)]
+    (when (:block/dummy? block)
+      (editor-handler/edit-block! block :max (:block/format block) (:block/uuid block))))
+  state)
 (rum/defc page-blocks-inner <
-  {:did-mount (fn [state]
-                (let [block (first (nth (:rum/args state) 1))]
-                  (when (:block/dummy? block)
-                    (editor-handler/edit-block! block 0 (:block/format block) (:block/uuid block))))
-                state)}
+  {:did-mount open-first-block!
+   :did-update open-first-block!}
   [page-name page-blocks hiccup sidebar?]
   [:div.page-blocks-inner
-   ;; (when (and (seq grouped-blocks-by-file)
-   ;;            (> (count grouped-blocks-by-file) 1))
-   ;;   (ui/admonition
-   ;;    :warning
-   ;;    [:div.text-sm
-   ;;     [:p.font-medium "Those pages have the same title, you might want to only keep one file."]
-   ;;     [:ol
-   ;;      (for [[file-path blocks] (into (sorted-map) grouped-blocks-by-file)]
-   ;;        [:li [:a {:key file-path
-   ;;                  :href (rfe/href :file {:path file-path})} file-path]])]]))
-
    (rum/with-key
      (content/content page-name
                       {:hiccup   hiccup
                        :sidebar? sidebar?})
      (str page-name "-hiccup"))])
 
+(declare page)
+
 (rum/defc page-blocks-cp < rum/reactive
   db-mixins/query
-  [repo page file-path page-name page-original-name encoded-page-name sidebar? journal? block? block-id format]
+  [repo page-e file-path page-name page-original-name encoded-page-name sidebar? journal? block? block-id format]
   (let [raw-page-blocks (get-blocks repo page-name page-original-name block? block-id)
-        ;; grouped-blocks-by-file (into {} (for [[k v] (db-utils/group-by-file raw-page-blocks)]
-        ;;                                   [(:file/path (db-utils/entity (:db/id k))) v]))
-        ;; raw-page-blocks (get grouped-blocks-by-file file-path raw-page-blocks)
         page-blocks (block-handler/with-dummy-block raw-page-blocks format
                       (if (empty? raw-page-blocks)
-                        {:block/page {:db/id (:db/id page)}
-                         :block/file {:db/id (:db/id (:block/file page))}})
+                        {:block/page {:db/id (:db/id page-e)}
+                         :block/file {:db/id (:db/id (:block/file page-e))}})
                       {:journal? journal?
                        :page-name page-name})
         hiccup-config {:id (if block? (str block-id) page-name)
                        :sidebar? sidebar?
                        :block? block?
-                       :editor-box editor/box}
+                       :editor-box editor/box
+                       :page page}
         hiccup-config (common-handler/config-with-document-mode hiccup-config)
         hiccup (block/->hiccup page-blocks hiccup-config {})]
     (page-blocks-inner page-name page-blocks hiccup sidebar?)))
@@ -109,19 +102,6 @@
           file-path (:file/path file)]
       (page-blocks-cp repo contents file-path name original-name name true false false nil format))))
 
-(defn presentation
-  [repo page]
-  [:a.opacity-30.hover:opacity-100.page-op
-   {:title "Presentation mode (Powered by Reveal.js)"
-    :style {:margin-top -2}
-    :on-click (fn []
-                (state/sidebar-add-block!
-                 repo
-                 (:db/id page)
-                 :page-presentation
-                 {:page page}))}
-   svg/slideshow])
-
 (rum/defc today-queries < rum/reactive
   [repo today? sidebar?]
   (when (and today? (not sidebar?))
@@ -131,7 +111,8 @@
          (for [{:keys [title] :as query} queries]
            (rum/with-key
              (block/custom-query {:attr {:class "mt-10"}
-                                  :editor-box editor/box} query)
+                                  :editor-box editor/box
+                                  :page page} query)
              (str repo "-custom-query-" (:query query))))]))))
 
 (defn- delete-page!
@@ -234,76 +215,86 @@
    :did-update (fn [state]
                  (ui-handler/scroll-and-highlight! state)
                  state)}
-  [state {:keys [repo] :as option}]
-  (when-let [path-page-name (or (get-page-name state)
+  [state {:keys [repo page-name preview?] :as option}]
+  (when-let [path-page-name (or page-name
+                                (get-page-name state)
                                 (state/get-current-page))]
     (let [current-repo (state/sub :git/current-repo)
-         repo (or repo current-repo)
-         page-name (string/lower-case path-page-name)
-         marker-page? (marker/marker? page-name)
-         priority-page? (contains? #{"a" "b" "c"} page-name)
-         block? (util/uuid-string? page-name)
-         block-id (and block? (uuid page-name))
-         format (let [page (if block-id
-                             (:block/name (:block/page (db/entity [:block/uuid block-id])))
-                             page-name)]
-                  (db/get-page-format page))
-         journal? (db/journal-page? page-name)
-         sidebar? (:sidebar? option)]
-     (rum/with-context [[t] i18n/*tongue-context*]
-       (cond
-         priority-page?
-         [:div.page
-          [:h1.title
-           (t :page/priority (string/upper-case page-name))]
-          [:div.ml-2
-           (reference/references page-name false true)]]
-
-         marker-page?
-         [:div.page
-          [:h1.title
-           (string/upper-case page-name)]
-          [:div.ml-2
-           (reference/references page-name true false)]]
-
-         :else
-         (let [route-page-name page-name
-               page (if block?
-                      (->> (:db/id (:block/page (db/entity repo [:block/uuid block-id])))
-                           (db/entity repo))
-                      (db/entity repo [:block/name page-name]))
-               ;; TODO: replace page with frontend.format.block/page->map
-               page (if page page (do
-                                    (db/transact! repo [{:block/name page-name
-                                                         :block/original-name path-page-name
-                                                         :block/uuid (db/new-block-id)}])
-                                    (db/entity repo [:block/name page-name])))
-               {:keys [title] :as properties} (:block/properties page)
-               page-name (:block/name page)
-               page-original-name (:block/original-name page)
-               title (or title page-original-name page-name)
-               file (:block/file page)
-               file-path (and (:db/id file) (:file/path (db/entity repo (:db/id file))))
-               today? (and
-                       journal?
-                       (= page-name (string/lower-case (date/journal-name))))
-               developer-mode? (state/sub [:ui/developer-mode?])
-               public? (true? (:public properties))]
-           [:div.flex-1.page.relative (if (seq (:block/tags page))
-                                        (let [page-names (model/get-page-names-by-ids (map :db/id (:block/tags page)))]
-                                          {:data-page-tags (text/build-data-value page-names)})
-                                        {})
-            [:div.relative
-             (when (and (not block?)
-                        (not sidebar?)
-                        (not config/publishing?))
-
-                (let [contents? (= (string/lower-case (str page-name)) "contents")
-                      links (fn [] (->>
+          repo (or repo current-repo)
+          page-name (string/lower-case path-page-name)
+          block? (util/uuid-string? page-name)
+          block-id (and block? (uuid page-name))
+          format (let [page (if block-id
+                              (:block/name (:block/page (db/entity [:block/uuid block-id])))
+                              page-name)]
+                   (db/get-page-format page))
+          journal? (db/journal-page? page-name)
+          sidebar? (:sidebar? option)]
+      (rum/with-context [[t] i18n/*tongue-context*]
+        (let [route-page-name path-page-name
+              page (if block?
+                     (->> (:db/id (:block/page (db/entity repo [:block/uuid block-id])))
+                          (db/entity repo))
+                     (db/entity repo [:block/name page-name]))
+             ;; TODO: replace page with frontend.format.block/page->map
+              page (if page page (do
+                                   (db/transact! repo [{:block/name page-name
+                                                        :block/original-name path-page-name
+                                                        :block/uuid (db/new-block-id)}])
+                                   (db/entity repo [:block/name page-name])))
+              {:keys [title] :as properties} (:block/properties page)
+              page-name (:block/name page)
+              page-original-name (:block/original-name page)
+              title (or title page-original-name page-name)
+              file (:block/file page)
+              file-path (and (:db/id file) (:file/path (db/entity repo (:db/id file))))
+              today? (and
+                      journal?
+                      (= page-name (string/lower-case (date/journal-name))))
+              developer-mode? (state/sub [:ui/developer-mode?])
+              public? (true? (:public properties))]
+          [:div.flex-1.page.relative (if (seq (:block/tags page))
+                                       (let [page-names (model/get-page-names-by-ids (map :db/id (:block/tags page)))]
+                                         {:data-page-tags (text/build-data-value page-names)})
+                                       {})
+           [:div.relative
+            (when (and (not sidebar?)
+                       (not block?))
+              [:div.flex.flex-row.space-between
+               [:div.flex-1.flex-row
+                [:a {:on-click (fn [e]
+                                 (.preventDefault e)
+                                 (when (gobj/get e "shiftKey")
+                                   (when-let [page (db/pull repo '[*] [:block/name page-name])]
+                                     (state/sidebar-add-block!
+                                      repo
+                                      (:db/id page)
+                                      :page
+                                      {:page page}))))}
+                 [:h1.title {:style {:margin-left -2}}
+                  (if page-original-name
+                    (if (and (string/includes? page-original-name "[[")
+                             (string/includes? page-original-name "]]"))
+                      (let [ast (mldoc/->edn page-original-name (mldoc/default-config format))]
+                        (block/markup-element-cp {} (ffirst ast)))
+                      page-original-name)
+                    (or
+                     page-name
+                     path-page-name))]]]
+               (when (not config/publishing?)
+                 (let [contents? (= (string/lower-case (str page-name)) "contents")
+                       links (fn [] (->>
                                      [(when-not contents?
                                         {:title   (t :page/add-to-contents)
                                          :options {:on-click (fn [] (page-handler/handle-add-page-to-contents! page-original-name))}})
 
+                                      {:title "Go to presentation mode"
+                                       :options {:on-click (fn []
+                                                             (state/sidebar-add-block!
+                                                              repo
+                                                              (:db/id page)
+                                                              :page-presentation
+                                                              {:page page}))}}
                                       (when-not contents?
                                         {:title   (t :page/rename)
                                          :options {:on-click #(state/set-modal! (rename-page-dialog title page-name))}})
@@ -327,8 +318,8 @@
                                          :options {:on-click
                                                    (fn []
                                                      (page-handler/update-public-attribute!
-                                                       page-name
-                                                       (if public? false true))
+                                                      page-name
+                                                      (if public? false true))
                                                      (state/close-modal!))}})
 
                                       (when developer-mode?
@@ -337,103 +328,76 @@
                                                                (let [page-data (with-out-str (pprint/pprint (db/pull (:db/id page))))]
                                                                  (println page-data)
                                                                  (notification/show!
-                                                                   [:div
-                                                                    [:pre.code page-data]
-                                                                    [:br]
-                                                                    (ui/button "Copy to clipboard"
-                                                                               :on-click #(.writeText js/navigator.clipboard page-data))]
-                                                                   :success
-                                                                   false)))}})]
+                                                                  [:div
+                                                                   [:pre.code page-data]
+                                                                   [:br]
+                                                                   (ui/button "Copy to clipboard"
+                                                                              :on-click #(.writeText js/navigator.clipboard page-data))]
+                                                                  :success
+                                                                  false)))}})]
                                      (flatten)
                                      (remove nil?)))]
-                  [:div {:style {:position "absolute"
-                                 :right 0
-                                 :top 20}}
                    [:div.flex.flex-row
-                    [:a.opacity-30.hover:opacity-100.page-op.mr-2
+                    [:a.opacity-30.hover:opacity-100.page-op.mr-1
                      {:title "Search in current page"
                       :on-click #(route-handler/go-to-search! :page)}
                      svg/search]
-                    (when (not config/mobile?)
-                      (presentation repo page))
-
-                    (plugins/hook-ui-slot :page-file-mounted nil)
-
                     (ui/dropdown-with-links
-                      (fn [{:keys [toggle-fn]}]
-                        [:a.opacity-30.hover:opacity-100
-                         {:title    "More options"
-                          :on-click toggle-fn}
-                         (svg/vertical-dots {:class (util/hiccup->class "opacity-30.hover:opacity-100.h-5.w-5")})])
-                      links
-                      {:modal-class (util/hiccup->class
-                                      "origin-top-right.absolute.right-0.top-10.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.dropdown-overflow-auto.page-drop-options")
-                       :z-index     1})]]))
-              (when (and (not sidebar?)
-                         (not block?))
-                [:a {:on-click (fn [e]
-                                 (.preventDefault e)
-                                 (when (gobj/get e "shiftKey")
-                                   (when-let [page (db/pull repo '[*] [:block/name page-name])]
-                                     (state/sidebar-add-block!
-                                      repo
-                                      (:db/id page)
-                                      :page
-                                      {:page page}))))}
-                 [:h1.title {:style {:margin-left -2}}
-                  (if page-original-name
-                    (if (and (string/includes? page-original-name "[[")
-                             (string/includes? page-original-name "]]"))
-                      (let [ast (mldoc/->edn page-original-name (mldoc/default-config format))]
-                        (block/markup-element-cp {} (ffirst ast)))
-                      page-original-name)
-                    (or
-                     page-name
-                     path-page-name))]])
-              [:div
-              ;; [:div.content
-              ;;  (when (and file-path
-              ;;             (not sidebar?)
-              ;;             (not block?)
-              ;;             (not (state/hide-file?))
-              ;;             (not config/publishing?))
-              ;;    [:div.text-sm.ml-1.mb-4.flex-1.inline-flex
-              ;;     {:key "page-file"}
-              ;;     [:span.opacity-50 {:style {:margin-top 2}} (t :file/file)]
-              ;;     [:a.bg-base-2.px-1.ml-1.mr-3 {:style {:border-radius 4
-              ;;                                           :word-break    "break-word"}
-              ;;                                   :href  (rfe/href :file {:path file-path})}
-              ;;      file-path]])]
-
-              (when (and repo (not block?))
-                (let [alias (db/get-page-alias-names repo page-name)]
-                  (when (seq alias)
-                    [:div.text-sm.ml-1.mb-4 {:key "page-file"}
-                     [:span.opacity-50 "Alias: "]
-                     (for [item alias]
-                       [:a.ml-1.mr-1 {:href (rfe/href :page {:name item})}
-                        item])])))
-
-              (when (and block? (not sidebar?))
-                (let [config {:id "block-parent"
-                              :block? true}]
-                  [:div.mb-4
-                   (block/block-parents config repo block-id format)]))
-
-              ;; blocks
-              (page-blocks-cp repo page file-path page-name page-original-name page-name sidebar? journal? block? block-id format)]]
-
-            (when-not block?
-              (today-queries repo today? sidebar?))
-
-            (tagged-pages repo page-name)
-
-            ;; referenced blocks
-            [:div {:key "page-references"}
-             (reference/references route-page-name false)]
-
-            [:div {:key "page-unlinked-references"}
-             (reference/unlinked-references route-page-name)]]))))))
+                     (fn [{:keys [toggle-fn]}]
+                       [:a.cp__vertial-menu-button
+                        {:title    "More options"
+                         :on-click toggle-fn}
+                        (svg/vertical-dots nil)])
+                     links
+                     {:modal-class (util/hiccup->class
+                                    "origin-top-right.absolute.right-0.top-10.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.dropdown-overflow-auto.page-drop-options")
+                      :z-index     1})]))])
+            [:div
+            ;; [:div.content
+            ;;  (when (and file-path
+            ;;             (not sidebar?)
+            ;;             (not block?)
+            ;;             (not (state/hide-file?))
+            ;;             (not config/publishing?))
+            ;;    [:div.text-sm.ml-1.mb-4.flex-1.inline-flex
+            ;;     {:key "page-file"}
+            ;;     [:span.opacity-50 {:style {:margin-top 2}} (t :file/file)]
+            ;;     [:a.bg-base-2.px-1.ml-1.mr-3 {:style {:border-radius 4
+            ;;                                           :word-break    "break-word"}
+            ;;                                   :href  (rfe/href :file {:path file-path})}
+            ;;      file-path]])]
+
+             (when (and repo (not block?))
+               (let [alias (db/get-page-alias-names repo page-name)]
+                 (when (seq alias)
+                   [:div.text-sm.ml-1.mb-4 {:key "page-file"}
+                    [:span.opacity-50 "Alias: "]
+                    (for [item alias]
+                      [:a.ml-1.mr-1 {:href (rfe/href :page {:name item})}
+                       item])])))
+
+             (when (and block? (not sidebar?))
+               (let [config {:id "block-parent"
+                             :block? true}]
+                 [:div.mb-4
+                  (block/block-parents config repo block-id format)]))
+
+            ;; blocks
+             (page-blocks-cp repo page file-path page-name page-original-name page-name sidebar? journal? block? block-id format)]]
+
+           (when-not block?
+             (today-queries repo today? sidebar?))
+
+           (tagged-pages repo page-name)
+
+          ;; referenced blocks
+           [:div {:key "page-references"}
+            (rum/with-key
+              (reference/references route-page-name false)
+              (str route-page-name "-refs"))]
+
+           [:div {:key "page-unlinked-references"}
+            (reference/unlinked-references route-page-name)]])))))
 
 (defonce layout (atom [js/window.outerWidth js/window.outerHeight]))
 

+ 14 - 0
src/main/frontend/components/page.css

@@ -28,3 +28,17 @@
     }
   }
 }
+
+.cp__vertial-menu-button {
+    opacity: 0.3;
+    display: block;
+}
+
+.cp__vertial-menu-button:hover {
+    opacity: 1;
+}
+
+.cp__vertial-menu-button svg {
+    width: 20px;
+    height: 20px;
+}

+ 44 - 38
src/main/frontend/components/reference.cljs

@@ -18,70 +18,76 @@
             [medley.core :as medley]))
 
 (rum/defc filter-dialog-inner < rum/reactive
-  [close-fn references page-name]
-  (let [filter-state (page-handler/get-filters page-name)]
-    [:div.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
-       (svg/filter-icon)]
-      [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
-       [: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."]]]
-     (when (seq references)
+  [filters-atom close-fn references page-name]
+  [:div.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
+     (svg/filter-icon)]
+    [:div.mt-3.text-center.sm:mt-0.sm:ml-4.sm:text-left
+     [: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."]]]
+   (when (seq references)
+     (let [filters (rum/react filters-atom)]
        [:div.mt-5.sm:mt-4.sm:flex.sm.gap-1.flex-wrap
-        (for [reference references]
-          (let [filtered (get (rum/react filter-state) reference)
-                color (condp = filtered
-                        true "text-green-400"
-                        false "text-red-400"
-                        nil)]
-            [:button.border.rounded.px-1.mb-1 {:key reference :class color :style {:border-color "currentColor"}
-                                               :on-click (fn [e]
-                                                           (swap! filter-state #(if (nil? (get @filter-state reference))
-                                                                                  (assoc % reference (not (.-shiftKey e)))
-                                                                                  (dissoc % reference)))
-                                                           (page-handler/save-filter! page-name @filter-state))}
-             reference]))])]))
+       (for [reference references]
+         (let [lc-reference (string/lower-case reference)
+               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 {:key reference :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))}
+            reference]))]))])
 
 (defn filter-dialog
-  [references page-name]
+  [filters-atom references page-name]
   (fn [close-fn]
-    (filter-dialog-inner close-fn references page-name)))
+    (filter-dialog-inner filters-atom close-fn references page-name)))
 
-(rum/defc references < rum/reactive
-  [page-name marker? priority?]
+(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 marker? priority?]
   (when page-name
-    (let [block? (util/uuid-string? page-name)
+    (let [filters-atom (get state ::filters)
+          block? (util/uuid-string? page-name)
           block-id (and block? (uuid page-name))
           page-name (string/lower-case page-name)
           journal? (date/valid-journal-title? (string/capitalize page-name))
           repo (state/get-current-repo)
           ref-blocks (cond
-                       priority?
-                       (db/get-blocks-by-priority repo page-name)
-
-                       marker?
-                       (db/get-marker-blocks repo page-name)
                        block-id
                        (db/get-block-referenced-blocks block-id)
                        :else
                        (db/get-page-referenced-blocks page-name))
+          ref-pages (map (comp :block/original-name first) ref-blocks)
           scheduled-or-deadlines (if (and journal?
                                           (not (true? (state/scheduled-deadlines-disabled?)))
                                           (= page-name (string/lower-case (date/journal-name))))
                                    (db/get-date-scheduled-or-deadlines (string/capitalize page-name))
                                    nil)
           references (db/get-page-linked-refs-refed-pages repo page-name)
-          filters (page-handler/get-filters page-name)
-          filter-state (rum/react filters)
+          references (->> (concat ref-pages references)
+                          (remove nil?)
+                          (distinct))
+          filter-state (rum/react filters-atom)
           filters (when (seq filter-state)
                     (->> (group-by second filter-state)
                          (medley/map-vals #(map first %))))
           filtered-ref-blocks (block-handler/filter-blocks repo ref-blocks filters true)
           n-ref (count filtered-ref-blocks)]
       (when (or (> n-ref 0)
-                (seq scheduled-or-deadlines))
+                (seq scheduled-or-deadlines)
+                (seq filter-state))
         [:div.references.mt-6.flex-1.flex-row
          [:div.content
           (when (seq scheduled-or-deadlines)
@@ -104,7 +110,7 @@
                                              (if (> n-ref 1) "s")))]
             [:a.opacity-50.hover:opacity-100
              {:title "Filter"
-              :on-click #(state/set-modal! (filter-dialog references page-name))}
+              :on-click #(state/set-modal! (filter-dialog filters-atom references page-name))}
              (svg/filter-icon (cond
                                 (empty? filter-state) nil
                                 (every? true? (vals filter-state)) "text-green-400"

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

@@ -182,10 +182,14 @@
 
                         :block
                         (let [block-uuid (uuid (:block/uuid data))
-                              page (:block/name (:block/page (db/entity [:block/uuid block-uuid])))]
-                          (route/redirect! {:to :page
-                                            :path-params {:name page}
-                                            :query-params {:anchor (str "ls-block-" (:block/uuid data))}}))
+                              collapsed? (db/parents-collapsed? (state/get-current-repo) block-uuid)]
+                          (if collapsed?
+                            (route/redirect! {:to :page
+                                              :path-params {:name (str block-uuid)}})
+                            (let [page (:block/name (:block/page (db/entity [:block/uuid block-uuid])))]
+                             (route/redirect! {:to :page
+                                               :path-params {:name page}
+                                               :query-params {:anchor (str "ls-block-" (:block/uuid data))}}))))
                         nil))
          :on-shift-chosen (fn [{:keys [type data]}]
                             (case type
@@ -206,7 +210,8 @@
                                  :block
                                  block))
 
-                              nil))
+                              nil)
+                            (search-handler/clear-search!))
          :item-render (fn [{:keys [type data]}]
                         (let [search-mode (state/get-search-mode)]
                           [:div {:class "py-2"} (case type
@@ -282,7 +287,7 @@
                           (js/clearTimeout @search-timeout))
                         (let [value (util/evalue e)]
                           (if (string/blank? value)
-                            (search-handler/clear-search!)
+                            (search-handler/clear-search! false)
                             (let [search-mode (state/get-search-mode)
                                   opts (if (= :page search-mode)
                                          (let [current-page (or (state/get-current-page)

+ 65 - 0
src/main/frontend/components/shortcut.cljs

@@ -0,0 +1,65 @@
+(ns frontend.components.shortcut
+  (:require [frontend.context.i18n :as i18n]
+            [frontend.modules.shortcut.data-helper :as dh]
+            [frontend.state :as state]
+            [rum.core :as rum]))
+(def *shortcut-config (rum/cursor-in state/state [:config (state/get-current-repo) :shortcuts]))
+
+(rum/defc shortcut-table < rum/reactive
+  [name]
+  (let [_ (rum/react *shortcut-config)]
+    (rum/with-context [[t] i18n/*tongue-context*]
+      [:div
+       [:table
+        [:thead
+         [:tr
+          [:th.text-left [:b (t name)]]
+          [:th.text-right [:b (t :help/shortcut)]]]]
+        [:tbody
+         (map (fn [[k {:keys [binding]}]]
+                [:tr {:key k}
+                 [:td.text-left (t (dh/decorate-namespace k))]
+                 [:td.text-right (dh/binding-for-display k binding)]])
+              (dh/binding-by-category name))]]])))
+
+(rum/defc trigger-table []
+  (rum/with-context [[t] i18n/*tongue-context*]
+    [:table
+     [:thead
+      [:tr
+       [:th.text-left [:b (t :help/shortcuts-triggers)]]
+       [:th.text-right [:b (t :help/shortcut)]]]]
+     [:tbody
+      [:tr
+       [:td.text-left (t :help/slash-autocomplete)]
+       [:td.text-right "/"]]
+      [:tr
+       [:td.text-left (t :help/block-content-autocomplete)]
+       [:td.text-right "<"]]
+      [:tr
+       [:td.text-left (t :help/reference-autocomplete)]
+       [:td.text-right "[[]]"]]
+      [:tr
+       [:td.text-left (t :help/block-reference)]
+       [:td.text-right "(())"]]
+      [:tr
+       [:td.text-left (t :shortcut.editor/open-link-in-sidebar)]
+       [:td.text-right "shift-click"]]
+      [:tr
+       [:td.text-left (t :help/context-menu)]
+       [:td.text-right "right click"]]]]))
+
+(rum/defc shortcut
+  []
+  (rum/with-context [[t] i18n/*tongue-context*]
+    [:div
+     [:h1.title (t :help/shortcut-page-title)]
+     (trigger-table)
+     (shortcut-table :shortcut.category/basics)
+     (shortcut-table :shortcut.category/navigating)
+     (shortcut-table :shortcut.category/block-editing)
+     (shortcut-table :shortcut.category/block-command-editing)
+     (shortcut-table :shortcut.category/block-selection)
+     (shortcut-table :shortcut.category/formatting)
+     (shortcut-table :shortcut.category/toggle)
+     (shortcut-table :shortcut.category/others)]))

File diff suppressed because it is too large
+ 2 - 2
src/main/frontend/config.cljs


+ 4 - 1
src/main/frontend/context/i18n.cljs

@@ -1,6 +1,8 @@
 (ns frontend.context.i18n
   (:require [frontend.dicts :as dicts]
+            [frontend.modules.shortcut.dict :as shortcut-dict]
             [rum.core :as rum]
+            [medley.core :refer [deep-merge]]
             [frontend.state :as state]))
 
 ;; TODO
@@ -18,7 +20,8 @@
 (rum/defc tongue-provider [children]
   (let [prefered-language (keyword (state/sub :preferred-language))
         set-preferred-language state/set-preferred-language!
-        t (partial dicts/translate prefered-language)]
+        all-dicts (deep-merge dicts/dicts shortcut-dict/dict)
+        t (partial (dicts/translate all-dicts) prefered-language)]
     (if (nil? prefered-language)
       (set-preferred-language (fetch-local-language))
       :ok)

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

@@ -9,7 +9,7 @@
             [frontend.log]
             [reitit.frontend :as rf]
             [reitit.frontend.easy :as rfe]
-            [api]))
+            [logseq.api]))
 
 (defn set-router!
   []

+ 19 - 3
src/main/frontend/date.cljs

@@ -20,7 +20,7 @@
   (when-let [formatter-string (state/get-date-formatter)]
     (tf/unparse (tf/formatter formatter-string) date)))
 
-(def custom-formatter (tf/formatter "yyyy-MM-dd HH:mm:ssZ"))
+(def custom-formatter (tf/formatter "yyyy-MM-dd'T'HH:mm:ssZZ"))
 
 (defn journal-title-formatters
   []
@@ -44,8 +44,24 @@
      "yyyy年MM月dd日"}
    (state/get-date-formatter)))
 
-(defn get-date-time-string [date-time]
-  (tf/unparse custom-formatter date-time))
+(defn get-date-time-string
+  ([]
+   (get-date-time-string (t/now)))
+  ([date-time]
+   (tf/unparse custom-formatter date-time)))
+
+(defn get-locale-string
+  [s]
+  (try
+    (->> (tf/parse (tf/formatters :date-time-no-ms) s)
+        (t/to-default-time-zone)
+        (tf/unparse (tf/formatter "MMM do, yyyy")))
+    (catch js/Error e
+      nil)))
+
+(defn ISO-string
+  []
+  (.toISOString (js/Date.)))
 
 (defn get-local-date-time-string
   []

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

@@ -38,14 +38,14 @@
   delete-file! delete-file-blocks! delete-file-pages! delete-file-tx delete-files delete-pages-by-files
   filter-only-public-pages-and-blocks get-all-block-contents get-all-tagged-pages
   get-all-templates get-block-and-children get-block-by-uuid get-block-children sort-by-left
-  get-block-children-ids get-block-file get-block-immediate-children get-block-page
-  get-block-page-end-pos get-block-parent get-block-parents get-block-referenced-blocks get-block-refs-count
+  get-block-parent get-block-parents parents-collapsed? get-block-referenced-blocks get-block-refs-count
+  get-block-children-ids get-block-immediate-children get-block-page
   get-blocks-by-priority get-blocks-contents get-custom-css
   get-date-scheduled-or-deadlines get-db-type get-empty-pages get-file
   get-file-blocks get-file-contents get-file-last-modified-at get-file-no-sub get-file-page get-file-page-id file-exists?
   get-file-pages get-files get-files-blocks get-files-full get-files-that-referenced-page get-journals-length
   get-latest-journals get-marker-blocks get-matched-blocks get-page get-page-alias get-page-alias-names get-page-blocks get-page-linked-refs-refed-pages
-  get-page-blocks-count get-page-blocks-no-cache get-page-file get-page-format get-page-name get-page-properties
+  get-page-blocks-count get-page-blocks-no-cache get-page-file get-page-format get-page-properties
   get-page-properties-content get-page-referenced-blocks get-page-referenced-pages get-page-unlinked-references get-page-referenced-blocks-no-cache
   get-pages get-pages-relation get-pages-that-mentioned-page get-public-pages get-tag-pages
   journal-page? local-native-fs? mark-repo-as-cloned! page-alias-set page-blocks-transform pull-block

+ 35 - 80
src/main/frontend/db/model.cljs

@@ -467,25 +467,32 @@
           (recur (:block/uuid parent) (conj parents parent) (inc d))
           parents)))))
 
+(comment
+  (defn get-immediate-children-v2
+    [repo block-id]
+    (d/pull (conn/get-conn repo)
+            '[:block/_parent]
+            [:block/uuid block-id])))
+
+;; Use built-in recursive
+(defn get-block-parents-v2
+  [repo block-id]
+  (d/pull (conn/get-conn repo)
+          '[:db/id :block/properties {:block/parent ...}]
+          [:block/uuid block-id]))
+
+(defn parents-collapsed?
+  [repo block-id]
+  (when-let [block (:block/parent (get-block-parents-v2 repo block-id))]
+    (->> (tree-seq map? (fn [x] [(:block/parent x)]) block)
+         (map (comp :collapsed :block/properties))
+         (some true?))))
+
 (defn get-block-page
   [repo block-id]
   (when-let [block (db-utils/entity repo [:block/uuid block-id])]
     (db-utils/entity repo (:db/id (:block/page block)))))
 
-(defn get-block-page-end-pos
-  [repo page-name]
-  (or
-   (when-let [page-id (:db/id (db-utils/entity repo [:block/name (string/lower-case page-name)]))]
-     (when-let [db (conn/get-conn repo)]
-       (let [block-eids (->> (d/datoms db :avet :block/page page-id)
-                             (mapv :e))]
-         (when (seq block-eids)
-           (let [blocks (db-utils/pull-many repo '[:block/meta] block-eids)]
-             (-> (last (db-utils/sort-by-pos blocks))
-                 (get-in [:block/meta :end-pos])))))))
-   ;; TODO: need more thoughts
-   0))
-
 (defn get-blocks-by-priority
   [repo priority]
   (let [priority (string/capitalize priority)]
@@ -667,13 +674,6 @@
               (db-utils/entity [:block/original-name page-name]))
           :block/file))
 
-(defn get-block-file
-  [block-id]
-  (let [page-id (some-> (db-utils/entity [:block/uuid block-id])
-                        :block/page
-                        :db/id)]
-    (:block/file (db-utils/entity page-id))))
-
 (defn get-file-page-id
   [file-path]
   (when-let [repo (state/get-current-repo)]
@@ -701,27 +701,6 @@
    (vector? block)
    (= "Heading" (first block))))
 
-(defn get-page-name
-  [file ast]
-  ;; headline
-  (let [ast (map first ast)]
-    (if (string/includes? file "pages/contents.")
-      "Contents"
-      (let [first-block (last (first (filter heading-block? ast)))
-            property-name (when (and (= "Properties" (ffirst ast))
-                                     (not (string/blank? (:title (last (first ast))))))
-                            (:title (last (first ast))))
-            first-block-name (let [title (last (first (:title first-block)))]
-                               (and first-block
-                                    (string? title)
-                                    title))
-            file-name (when-let [file-name (last (string/split file #"/"))]
-                        (first (util/split-last "." file-name)))]
-        (or property-name
-            (if (= (state/page-name-order) "heading")
-              (or first-block-name file-name)
-              (or file-name first-block-name)))))))
-
 (defn get-page-original-name
   [page-name]
   (when page-name
@@ -801,7 +780,7 @@
         [?b :block/path-refs ?p]
         [?b :block/refs ?other-p]
         [(not= ?p ?other-p)]
-        [?other-p :block/name ?ref-page]]
+        [?other-p :block/original-name ?ref-page]]
       conn
       rules
       page)
@@ -1042,17 +1021,18 @@
   [block-uuid]
   (when-let [repo (state/get-current-repo)]
     (when (conn/get-conn repo)
-      (->> (react/q repo [:block/refed-blocks block-uuid] {}
-                    '[:find (pull ?ref-block [*])
-                      :in $ ?block-uuid
-                      :where
-                      [?block :block/uuid ?block-uuid]
-                      [?ref-block :block/refs ?block]]
-                    block-uuid)
-           react
-           db-utils/seq-flatten
-           sort-blocks
-           db-utils/group-by-page))))
+      (let [block (db-utils/entity [:block/uuid block-uuid])]
+        (->> (react/q repo [:block/refed-blocks (:db/id block)] {}
+              '[:find (pull ?ref-block [*])
+                :in $ ?block-uuid
+                :where
+                [?block :block/uuid ?block-uuid]
+                [?ref-block :block/refs ?block]]
+              block-uuid)
+            react
+            db-utils/seq-flatten
+            sort-blocks
+            db-utils/group-by-page)))))
 
 (defn get-matched-blocks
   [match-fn limit]
@@ -1164,6 +1144,7 @@
                 (let [e (db-utils/entity [:block/uuid id])]
                   {:db/id (:db/id e)
                    :block/uuid id
+                   :block/page (:db/id (:block/page e))
                    :block/content (:block/content e)
                    :block/format (:block/format e)}))))))
 
@@ -1282,29 +1263,3 @@
            (conn/get-conn repo)
            page-id)
       ffirst))
-
-(comment
-  (def page-names ["foo" "bar"])
-
-  (def page-ids (set (get-page-ids-by-names page-names)))
-
-  (d/q '[:find [(pull ?b [*]) ...]
-         :in $ % ?refs
-         :where
-         [?b :block/refs ?p]
-         ;; Filter other blocks
-         [(contains? ?refs ?p)]
-         (or-join [?b ?refs]
-                  (matches-all ?b :block/refs ?refs)
-                  (and
-                   (parent ?p ?b)
-                   ;; FIXME: not working
-                   ;; (matches-all (union ?p ?b) :block/refs ?refs)
-                   [?p :block/refs ?p-ref]
-                   [?b :block/refs ?b-ref]
-                   [(not= ?p-ref ?b-ref)]
-                   [(contains? ?refs ?p-ref)]
-                   [(contains? ?refs ?b-ref)]))]
-       (conn/get-conn)
-       rules
-       page-ids))

+ 0 - 4
src/main/frontend/db/outliner.cljs

@@ -22,10 +22,6 @@
             @conn parent-id left-id)]
     (ffirst r)))
 
-(defn get-first-child
-  [conn parent-id]
-  (get-by-parent-&-left conn parent-id parent-id))
-
 ;; key [:block/children parent-id]
 
 (def get-by-parent-id

+ 25 - 25
src/main/frontend/db/query_react.cljs

@@ -47,31 +47,31 @@
   [query-result remove-blocks q]
   (try
     (let [repo (state/get-current-repo)
-         result (db-utils/seq-flatten query-result)
-         block? (:block/uuid (first result))]
-     (let [result (if block?
-                    (let [result (if (seq remove-blocks)
-                                   (let [remove-blocks (set remove-blocks)]
-                                     (remove (fn [h]
-                                               (contains? remove-blocks (:block/uuid h)))
-                                             result))
-                                   result)]
-                      (some->> result
-                               (db-utils/with-repo repo)
-                               (model/with-block-refs-count repo)
-                               (model/sort-blocks)))
-                    result)]
-       (if-let [result-transform (:result-transform q)]
-         (if-let [f (sci/eval-string (pr-str result-transform))]
-           (try
-             (sci/call-fn f result)
-             (catch js/Error e
-               (log/error :sci/call-error e)
-               result))
-           result)
-         (if block?
-           (db-utils/group-by-page result)
-           result))))
+          result (db-utils/seq-flatten query-result)
+          block? (:block/uuid (first result))]
+      (let [result (if block?
+                     (let [result (if (seq remove-blocks)
+                                    (let [remove-blocks (set remove-blocks)]
+                                      (remove (fn [h]
+                                                (contains? remove-blocks (:block/uuid h)))
+                                              result))
+                                    result)]
+                       (some->> result
+                                (db-utils/with-repo repo)
+                                (model/with-block-refs-count repo)
+                                (model/sort-blocks)))
+                     result)]
+        (if-let [result-transform (:result-transform q)]
+          (if-let [f (sci/eval-string (pr-str result-transform))]
+            (try
+              (sci/call-fn f result)
+              (catch js/Error e
+                (log/error :sci/call-error e)
+                result))
+            result)
+          (if block?
+            (db-utils/group-by-page result)
+            result))))
     (catch js/Error e
       (log/error :query/failed e))))
 

+ 8 - 5
src/main/frontend/db/react.cljs

@@ -234,11 +234,14 @@
                                 [:page/mentioned-pages current-page-id]])
 
                              (apply concat
-                                    (for [{:block/keys [refs]} blocks]
-                                      (map (fn [page]
-                                             (when-let [page (db-utils/entity [:block/name (:block/name page)])]
-                                               [:block/refed-blocks (:db/id page)]))
-                                        refs))))
+                               (for [{:block/keys [refs]} blocks]
+                                 (mapcat (fn [ref]
+                                           (when-let [block (if (and (map? ref) (:block/name ref))
+                                                              (db-utils/entity [:block/name (:block/name ref)])
+                                                              (db-utils/entity ref))]
+                                             [[:page/blocks (:db/id (:block/page block))]
+                                              [:block/refed-blocks (:db/id block)]]))
+                                         refs))))
                             (distinct))
               refed-pages (map
                            (fn [[k page-id]]

+ 12 - 208
src/main/frontend/dicts.cljs

@@ -1,64 +1,15 @@
 (ns frontend.dicts
-  (:require [tongue.core :as tongue]
-            [frontend.config :as config]))
+  (:require [frontend.config :as config]
+            [shadow.resource :as rc]
+            [tongue.core :as tongue]))
 
 (def dicts
-  {:en {:tutorial/text "## Hi, welcome to Logseq!
-- Logseq is a _privacy-first_, _open-source_ platform for _knowledge_ sharing and management.
-- This is a 3 minute tutorial on how to use Logseq. Let's get started!
-- (Feel free to edit anything, no change will be saved at this moment. If you do want to persist your work, click the **top-right** corner of the screen to connect Logseq to either Github or local directory.)
-- Here are some tips might be useful.
-#+BEGIN_TIP
-Click to edit any block.
-Type `Enter` to create a new block.
-Type `Shift+Enter` to create a new line.
-Type `/` to show all the commands.
-#+END_TIP
-- 1. Let's create a page called [[How to take dummy notes?]]. You can click it to go to that page, or you can `Shift+Click` to open it in the right sidebar! Now you should see both _Linked References_ and _Unlinked References_.
-- 2. Let's reference some blocks on [[How to take dummy notes?]], you can `Shift+Click` any block reference to open it in the right sidebar. Try making
-some changes on the right sidebar, those referenced blocks will be changed too!
-    - ((5f713e91-8a3c-4b04-a33a-c39482428e2d)) : This is a block reference.
-    - ((5f713ea8-8cba-403d-ac00-9964b1ec7190)) : This is another block reference.
-- 3. Do you support tags?
-    - Of course, this is a #dummy tag.
-- 4. Do you support tasks like todo/doing/done and priorities?
-    - Yes, type `/` and pick your favorite todo keyword or priority (A/B/C).
-    - NOW [#A] A dummy tutorial on \"How to take dummy notes?\"
-    - LATER [#A] Check out this awesome video by [:a {:href \"https://twitter.com/EdTravelling\" :target \"_blank\"} \"@EdTravelling\"], which shows how to use logseq to open your local directory.
-
-[:div.video-wrapper.mb-4
-        [:iframe
-         {:allowFullScreen \"allowfullscreen\"
-          :allow
-          \"accelerometer; autoplay; encrypted-media; gyroscope\"
-        :frameBorder \"0\"
-        :src \"https://www.youtube.com/embed/Afmqowr0qEQ\"
-        :height \"367\"
-        :width \"653\"}]]
-    - DONE Create a page
-    - CANCELED [#C] Write a page with more than 1000 blocks
-- That's it! You can create more bullets or open a local directory to import some notes now!
-- You can also download our desktop app at https://github.com/logseq/logseq/releases
-"
-        :tutorial/dummy-notes "---
-title: How to take dummy notes?
----
-
-- Hello, I'm a block!
-:PROPERTIES:
-:id: 5f713e91-8a3c-4b04-a33a-c39482428e2d
-:END:
-    - I'm a child block!
-    - I'm another child block!
-- Hey, I'm another block!
-:PROPERTIES:
-:id: 5f713ea8-8cba-403d-ac00-9964b1ec7190
-:END:
-"
+  {:en {:tutorial/text (rc/inline "tutorial-en.md")
+        :tutorial/dummy-notes (rc/inline "dummy-notes-en.md")
         :on-boarding/title "Hi, welcome to Logseq!"
         :on-boarding/sharing "sharing"
         :on-boarding/is-a " is a "
-        :on-boarding/vision "A privacy-first, open-source platform for knowledge sharing and management."
+        :on-boarding/vision "A privacy-first, open-source platform for knowledge management and collaboration."
         :on-boarding/local-first "local-first"
         :on-boarding/non-linear "non-linear"
         :on-boarding/outliner "outliner"
@@ -122,42 +73,15 @@ title: How to take dummy notes?
         :help/block-reference "Block Reference"
         :help/key-commands "Key Commands"
         :help/working-with-lists " (working with lists)"
-        :help/indent-block-tab "Indent Block Tab"
-        :help/unindent-block "Unindent Block"
-        :help/move-block-up "Move Block Up"
-        :help/move-block-down "Move Block Down"
-        :help/create-new-block "Create New Block"
-        :help/new-line-in-block "New Line in Block"
         :help/select-nfs-browser "Please use another browser (like latest chrome) which support NFS features to open local directory."
         :undo "Undo"
         :redo "Redo"
-        :help/zoom-in "Zoom In/Forward"
-        :help/zoom-out "Zoom out/Back"
-        :help/follow-link-under-cursor "Follow link under cursor"
-        :help/open-link-in-sidebar "Open link in Sidebar"
-        :expand "Expand"
-        :collapse "Collapse"
-        :select-block-above "Select Block Above"
-        :select-block-below "Select Block Below"
-        :select-all-blocks "Select All Blocks"
         :general "General"
         :more "More"
         :search/result-for "Search result for "
         :search/items "items"
-        :help/toggle "Toggle help"
-        :help/git-commit-message "Git commit message"
-        :help/full-text-search "Full Text Search"
-        :help/page-search "Search in the current page"
         :help/context-menu "Context Menu"
         :help/fold-unfold "Fold/Unfold blocks (when not in edit mode)"
-        :help/toggle-doc-mode "Toggle document mode"
-        :help/toggle-contents "Toggle Contents"
-        :help/toggle-theme "Toggle between dark/light theme"
-        :help/toggle-right-sidebar "Toggle right sidebar"
-        :help/toggle-settings "Toggle settings"
-        :help/toggle-insert-new-block "Toggle Enter/Alt+Enter for inserting new block"
-        :help/jump-to-journals "Jump to Journals"
-        :formatting "Formatting"
         :help/markdown-syntax "Markdown syntax"
         :help/org-mode-syntax "Org mode syntax"
         :bold "Bold"
@@ -353,7 +277,9 @@ title: How to take dummy notes?
         :open-a-directory "Open a local directory"
         :user/delete-account "Delete account"
         :user/delete-your-account "Delete your account"
-        :user/delete-account-notice "All your published pages on logseq.com will be deleted."}
+        :user/delete-account-notice "All your published pages on logseq.com will be deleted."
+
+        :help/shortcut-page-title "Learn & Customize Shortcuts"}
 
    :de {:help/about "Über Logseq"
         :help/bug "Fehlerbericht"
@@ -373,36 +299,12 @@ title: How to take dummy notes?
         :help/block-reference "Blockverweis"
         :help/key-commands "Tastenbefehle"
         :help/working-with-lists " (mit Listen arbeiten)"
-        :help/indent-block-tab "Block einrücken"
-        :help/unindent-block "Block ausrücken"
-        :help/move-block-up "Block nach oben verschieben"
-        :help/move-block-down "Block nach unten verschieben"
-        :help/create-new-block "Neuen Block erstellen"
-        :help/new-line-in-block "Neue Zeile innerhalb des Blocks erstellen"
         :help/select-nfs-browser "Bitte einen anderen Browser verwenden (z. B. den neuesten Chrome), der NFS-Funktionen unterstützt, um lokale Verzeichnisse zu öffnen."
         :undo "Rückgängig machen"
         :redo "Wiederholen"
-        :help/zoom-in "Heranzoomen"
-        :help/zoom-out "Herauszoomen"
-        :help/follow-link-under-cursor "Link unter dem Cursor folgen"
-        :help/open-link-in-sidebar "Link in Seitenleiste öffnen"
-        :expand "Erweitern"
-        :collapse "Zusammenklappen"
-        :select-block-above "Block oberhalb auswählen"
-        :select-block-below "Block unterhalb auswählen"
-        :select-all-blocks "Alle Blöcke auswählen"
         :general "Allgemein"
-        :help/toggle "Hilfe aktivieren"
-        :help/git-commit-message "Git Commit-Nachricht"
-        :help/full-text-search "Volltextsuche"
         :help/context-menu "Kontextmenü"
         :help/fold-unfold "Blöcke ein-/ausklappen (wenn nicht im Bearbeitungsmodus)"
-        :help/toggle-doc-mode "Dokumentenmodus umschalten"
-        :help/toggle-theme "Umschalten zwischen dunklem/hellem Thema"
-        :help/toggle-right-sidebar "Rechte Seitenleiste umschalten"
-        :help/toggle-insert-new-block "Umschalten von Enter/Alt+Enter zum Einfügen eines neuen Blocks"
-        :help/jump-to-journals "Zu Journalen springen"
-        :formatting "Formatierung"
         :help/markdown-syntax "Markdown-Syntax"
         :help/org-mode-syntax "Org-Mode-Syntax"
         :bold "Fett"
@@ -599,36 +501,12 @@ title: How to take dummy notes?
         :help/block-reference "Référence à un Bloc"
         :help/key-commands "Key Commands"
         :help/working-with-lists " (working with lists)"
-        :help/indent-block-tab "Indenter un Bloc vers la droite"
-        :help/unindent-block "Indenter un Bloc vers la gauche"
-        :help/move-block-up "Déplacer un bloc au dessus"
-        :help/move-block-down "Déplacer un bloc en dessous"
-        :help/create-new-block "Créer un nouveau bloc"
-        :help/new-line-in-block "Aller à la ligne dans un bloc"
         :help/select-nfs-browser "Please use another browser (like latest chrome) which support NFS features to open local directory."
         :undo "Annuler"
         :redo "Redo"
-        :help/zoom-in "Zoomer"
-        :help/zoom-out "Dézoomer"
-        :help/follow-link-under-cursor "Suivre le lien sous le curseur"
-        :help/open-link-in-sidebar "Ouvrir le lien dans la barre latérale"
-        :expand "Etendre"
-        :collapse "Réduire"
-        :select-block-above "Sélectionner le bloc au dessus"
-        :select-block-below "Sélectionner le bloc en dessous"
-        :select-all-blocks "Sélectionner tous les blocs"
         :general "Général"
-        :help/toggle "Afficher l'aide"
-        :help/git-commit-message "Message de commit Git"
-        :help/full-text-search "Recherche globale dans le texte"
         :help/context-menu "Menu contextuel"
         :help/fold-unfold "Plier/Déplier les blocs (hors mode édition)"
-        :help/toggle-doc-mode "Intervertir le mode document"
-        :help/toggle-theme "Intervertir le thème foncé/clair"
-        :help/toggle-right-sidebar "Afficher/cacher la barre latérale"
-        :help/toggle-insert-new-block "Activer Entreée ou Alt+Enter pour insérer un bloc"
-        :help/jump-to-journals "Aller au Journal"
-        :formatting "Formats"
         :help/markdown-syntax "Syntaxe Markdown"
         :help/org-mode-syntax "Syntaxe Org mode"
         :bold "Gras"
@@ -847,51 +725,25 @@ title: How to take dummy notes?
            :help/shortcuts "快捷键"
            :help/shortcuts-triggers "触发"
            :help/shortcut "快捷键"
+           :help/shortcut-page-title "完整快捷键"
            :help/slash-autocomplete "Slash 自动提示"
            :help/block-content-autocomplete "块内容 (Src, Quote, Query 等) 自动完成"
            :help/reference-autocomplete "页面引用自动补全"
            :help/block-reference "块引用"
            :help/key-commands "关键命令"
            :help/working-with-lists " (与列表相关)"
-           :help/indent-block-tab "缩进块标签"
-           :help/unindent-block "取消缩进块"
-           :help/move-block-up "向上移动块"
-           :help/move-block-down "向下移动块"
-           :help/create-new-block "创建块"
-           :help/new-line-in-block "块中新建行"
            :help/select-nfs-browser "请选择支持nfs的浏览来使用logseq本地文件夹功能, 如最新的Chrome浏览器."
            :text/image "图片"
            :asset/confirm-delete "确定要删除{1}吗?"
            :asset/physical-delete "同时删除本地文件(目前不可撤销)"
            :undo "撤销"
            :redo "重做"
-           :help/zoom-in "聚焦"
-           :help/zoom-out "退出聚焦"
-           :help/follow-link-under-cursor "跟随光标下的链接"
-           :help/open-link-in-sidebar "在侧边栏打开"
-           :expand "展开"
-           :collapse "折叠"
-           :select-block-above "选择上方的块"
-           :select-block-below "选择下方的块"
-           :select-all-blocks "选择所有块"
            :general "常规​​​​​"
            :more "更多"
            :search/result-for "更多搜索结果 "
            :search/items "条目"
-           :help/toggle "显示/关闭帮助"
-           :help/git-commit-message "提交消息"
-           :help/full-text-search "全文搜索"
-           :help/page-search "在当前页面搜索"
            :help/context-menu "右键菜单"
            :help/fold-unfold "折叠/展开方块(不在编辑模式中)"
-           :help/toggle-doc-mode "切换文档模式"
-           :help/toggle-contents "打开/关闭目录"
-           :help/toggle-theme "“在暗色/亮色主题之间切换”"
-           :help/toggle-right-sidebar "启用/关闭右侧栏"
-           :help/toggle-settings "显示/关闭设置"
-           :help/toggle-insert-new-block "切换 Enter/Alt+Enter 以插入新块"
-           :help/jump-to-journals "跳转到日记"
-           :formatting "格式化"
            :help/markdown-syntax "Markdown 语法"
            :help/org-mode-syntax "Org Mode 语法"
            :bold "粗体"
@@ -1081,6 +933,7 @@ title: How to take dummy notes?
            :user/delete-your-account "删除你的帐号"
            :user/delete-account-notice "你在 logseq.com 发布的页面(假如有的话)也会被删除。"}
 
+
    :zh-Hant {:on-boarding/title "你好,歡迎使用 Logseq!"
              :on-boarding/sharing "分享"
              :on-boarding/is-a " 是一個 "
@@ -1145,36 +998,12 @@ title: How to take dummy notes?
              :help/block-reference "塊引用"
              :help/key-commands "關鍵命令"
              :help/working-with-lists " (與列表相關)"
-             :help/indent-block-tab "縮進塊標簽"
-             :help/unindent-block "取消縮進塊"
-             :help/move-block-up "向上移動塊"
-             :help/move-block-down "向下移動塊"
-             :help/create-new-block "創建塊"
-             :help/new-line-in-block "塊中新建行"
              :help/select-nfs-browser "Please use another browser (like latest chrome) which support NFS features to open local directory."
              :undo "撤銷"
              :redo "重做"
-             :help/zoom-in "聚焦"
-             :help/zoom-out "推出聚焦"
-             :help/follow-link-under-cursor "跟隨光標下的鏈接"
-             :help/open-link-in-sidebar "在側邊欄打開"
-             :expand "展開"
-             :collapse "折疊"
-             :select-block-above "選擇上方的塊"
-             :select-block-below "選擇下方的塊"
-             :select-all-blocks "選擇所有塊"
              :general "常規​​​​​"
-             :help/toggle "顯示/關閉幫助"
-             :help/git-commit-message "提交消息"
-             :help/full-text-search "全文搜索"
              :help/context-menu "右鍵菜單"
              :help/fold-unfold "折疊/展開方塊(不在編輯模式中)"
-             :help/toggle-doc-mode "切換文檔模式"
-             :help/toggle-theme "“在暗色/亮色主題之間切換”"
-             :help/toggle-right-sidebar "啟用/關閉右側欄"
-             :help/toggle-insert-new-block "切換 Enter/Alt+Enter 以插入新塊"
-             :help/jump-to-journals "跳轉到日記"
-             :formatting "格式化"
              :help/markdown-syntax "Markdown 語法"
              :help/org-mode-syntax "Org Mode 語法"
              :bold "粗體"
@@ -1398,36 +1227,12 @@ title: How to take dummy notes?
         :help/block-reference "Blok verwysing"
         :help/key-commands "Sleutel instruksies"
         :help/working-with-lists " (werk met lyste)"
-        :help/indent-block-tab "Ingekeepte blok oortjie"
-        :help/unindent-block "Oningekeepte blok"
-        :help/move-block-up "Skuif Blok Boontoe"
-        :help/move-block-down "Skuif Blok Ondertoe"
-        :help/create-new-block "Skep 'n nuwe blok"
-        :help/new-line-in-block "Nuwe lyn in blok"
         :help/select-nfs-browser "Please use another browser (like latest chrome) which support NFS features to open local directory."
         :undo "Ontdoen"
         :redo "Herdoen"
-        :help/zoom-in "Zoem in"
-        :help/zoom-out "Zoem uit"
-        :help/follow-link-under-cursor "Volg die skakel onder die wyser"
-        :help/open-link-in-sidebar "Maak skakel in kantlys oop"
-        :expand "Brei uit"
-        :collapse "Vou in"
-        :select-block-above "Kies blok bo"
-        :select-block-below "Kies blok onder"
-        :select-all-blocks "Kies alle blokke"
         :general "Algemeen"
-        :help/toggle "Wissel help"
-        :help/git-commit-message "Jou git stoor boodskap"
-        :help/full-text-search "Volteks soek"
         :help/context-menu "Konteks kaart"
         :help/fold-unfold "Vou/ontvou blokke (wanneer nie in wysigings modus)"
-        :help/toggle-doc-mode "Wissel dokument modus"
-        :help/toggle-theme "Wissel tussen donker/lig temas"
-        :help/toggle-right-sidebar "Wissel regter sybalk"
-        :help/toggle-insert-new-block "Wissel Enter/Alt+enter vir die byvoeging van nuwe blokke"
-        :help/jump-to-journals "Spring na joernale"
-        :formatting "Formatering"
         :help/markdown-syntax "Markdown sintaksis"
         :help/org-mode-syntax "Org mode sintaksis"
         :bold "Vetdruk"
@@ -1560,7 +1365,6 @@ title: How to take dummy notes?
         :settings "Verstellings"
         :import "Invoer"
         :join-community "Sluit by die gemeenskap aan"
-        :discord-title "Ons discord groep!"
         :sign-out "Teken af"
         :help-shortcut-title "Kliek op die kortpad en ander wenke"
         :loading "Laai tans"
@@ -1585,5 +1389,5 @@ title: How to take dummy notes?
                 {:label "繁體中文" :value :zh-Hant}
                 {:label "Afrikaans" :value :af}])
 
-(def translate
+(defn translate [dicts]
   (tongue/build-translate dicts))

+ 50 - 56
src/main/frontend/extensions/code.cljs

@@ -51,42 +51,37 @@
   [editor textarea config state]
   (.save editor)
   (let [value (gobj/get textarea "value")
-        default-value (gobj/get textarea "defaultValue")
-        pos-meta (:pos-meta state)]
+        default-value (gobj/get textarea "defaultValue")]
     (when (not= value default-value)
       (cond
-       (:block/uuid config)
-       (let [block (db/pull [:block/uuid (:block/uuid config)])
-             format (:block/format block)
-             content (:block/content block)
-             {:keys [start_pos end_pos]} @pos-meta
-             prev-content (utf8/substring (utf8/encode content)
-                                          0 start_pos)
-             value (str (if (not= "\n" (last prev-content))
-                          "\n")
-                        (string/trimr value)
-                        "\n")
-             content' (utf8/insert! content start_pos end_pos value)]
-         (editor-handler/save-block-if-changed! block content')
-         (let [new-pos-meta {:start_pos start_pos
-                             :end_pos (+ start_pos
-                                         (utf8/length (utf8/encode value)))}
-               old-pos-meta @pos-meta]
-           (reset! pos-meta new-pos-meta)))
+        (:block/uuid config)
+        (let [block (db/pull [:block/uuid (:block/uuid config)])
+              format (:block/format block)
+              content (:block/content block)
+              full-content (:full_content (last (:rum/args state)))]
+          (when (and full-content (string/includes? content full-content))
+            (let [lines (string/split-lines full-content)
+                  fl (first lines)
+                  ll (last lines)]
+              (when (and fl ll)
+                (let [value' (str fl "\n" value "\n" ll)
+                      ;; FIXME: What if there're multiple code blocks with the same value?
+                      content' (string/replace-first content full-content value')]
+                  (editor-handler/save-block-if-changed! block content'))))))
 
-       (:file-path config)
-       (let [path (:file-path config)
-             content (db/get-file-no-sub path)
-             value (some-> (gdom/getElement path)
-                           (gobj/get "value"))]
-         (when (and
-                (not (string/blank? value))
-                (not= (string/trim value) (string/trim content)))
-           (file-handler/alter-file (state/get-current-repo) path (string/trim value)
-                                    {:re-render-root? true})))
+        (:file-path config)
+        (let [path (:file-path config)
+              content (db/get-file-no-sub path)
+              value (some-> (gdom/getElement path)
+                            (gobj/get "value"))]
+          (when (and
+                 (not (string/blank? value))
+                 (not= (string/trim value) (string/trim content)))
+            (file-handler/alter-file (state/get-current-repo) path (string/trim value)
+                                     {:re-render-root? true})))
 
-       :else
-       nil))))
+        :else
+        nil))))
 
 (defn- text->cm-mode
   [text]
@@ -114,7 +109,10 @@
   (let [editor-atom (:editor-atom state)
         esc-pressed? (atom nil)]
     (if @editor-atom
-      @editor-atom
+      (let [editor @editor-atom
+            doc (.getDoc editor)
+            code (nth (:rum/args state) 3)]
+        (.setValue doc code))
       (let [[config id attr code] (:rum/args state)
             original-mode (get attr :data-lang)
             mode (or original-mode "javascript")
@@ -133,16 +131,12 @@
                                           :lineNumbers true
                                           :styleActiveLine true
                                           :extraKeys #js {"Esc" (fn [cm]
-                                                                  (let [save! #(save-file-or-block-when-blur-or-esc! cm textarea config state)]
-                                                                    (if-let [block-id (:block/uuid config)]
-                                                                      (let [block (db/pull [:block/uuid block-id])
-                                                                            value (.getValue cm)
-                                                                            textarea-value (gobj/get textarea "value")
-                                                                            changed? (not= value textarea-value)]
-                                                                        (if changed?
-                                                                          (save!)
-                                                                          (editor-handler/edit-block! block :max (:block/format block) block-id)))
-                                                                      (save!)))
+                                                                  (save-file-or-block-when-blur-or-esc! cm textarea config state)
+                                                                  (when-let [block-id (:block/uuid config)]
+                                                                    (let [block (db/pull [:block/uuid block-id])
+                                                                          value (.getValue cm)
+                                                                          textarea-value (gobj/get textarea "value")]
+                                                                      (editor-handler/edit-block! block :max (:block/format block) block-id)))
                                                                   ;; TODO: return "handled" or false doesn't always prevent event bubbles
                                                                   (reset! esc-pressed? true)
                                                                   (js/setTimeout #(reset! esc-pressed? false) 10))}})))]
@@ -150,35 +144,35 @@
           (let [element (.getWrapperElement editor)]
             (.on editor "blur" (fn [_cm e]
                                  (util/stop e)
+                                 (state/set-block-component-editing-mode! false)
                                  (when-not @esc-pressed?
                                    (save-file-or-block-when-blur-or-esc! editor textarea config state))))
-            (.addEventListener element "click"
+            (.addEventListener element "mousedown"
                                (fn [e]
-                                 (util/stop e)))
+                                 (state/clear-selection!)
+                                 (util/stop e)
+                                 (state/set-block-component-editing-mode! true)))
             (.save editor)
             (.refresh editor)))
         editor))))
 
 (defn- load-and-render!
   [state]
-  (let [editor-atom (:editor-atom state)]
-    (let [editor (render! state)]
-      (reset! editor-atom editor))))
+  (let [editor-atom (:editor-atom state)
+        editor (render! state)]
+    (reset! editor-atom editor)))
 
 (rum/defcs editor < rum/reactive
   {:init (fn [state]
-           (assoc state
-                  :pos-meta (atom (last (:rum/args state)))
-                  :editor-atom (atom nil)))
+           (assoc state :editor-atom (atom nil)))
    :did-mount (fn [state]
                 (load-and-render! state)
                 state)
-   :did-remount (fn [old_state state]
-                  (load-and-render! state)
-                  state)}
-  [state config id attr code pos-meta]
+   :did-update (fn [state]
+                 (load-and-render! state)
+                 state)}
+  [state config id attr code options]
   [:div.extensions__code
-   {:on-mouse-down (fn [e] (util/stop e))}
    [:div.extensions__code-lang
     (let [mode (string/lower-case (get attr :data-lang "javascript"))]
       (if (= mode "text/x-clojure")

+ 4 - 0
src/main/frontend/extensions/excalidraw.cljs

@@ -80,6 +80,10 @@
         [:a.mr-2 {:on-click #(swap! *view-mode? not)}
          (util/format "View Mode (%s)" (if @*view-mode? "ON" "OFF"))]]
        [:div.draw-wrap
+        {:on-mouse-down (fn [e]
+                          (util/stop e)
+                          (state/set-block-component-editing-mode! true))
+         :on-blur #(state/set-block-component-editing-mode! false)}
         (excalidraw
          (merge
           {:on-change (fn [elements state]

+ 18 - 41
src/main/frontend/format/block.cljs

@@ -41,7 +41,8 @@
                      (when (and (not (util/starts-with? page "http:"))
                                 (not (util/starts-with? page "https:"))
                                 (not (util/starts-with? page "file:"))
-                                (or (= ext :excalidraw) (not (contains? (config/supported-formats) ext))))
+                                (or (= ext :excalidraw)
+                                    (not (contains? (config/supported-formats) ext))))
                        page)))
 
                   (and
@@ -108,10 +109,12 @@
 
                         (and (vector? block)
                              (= "Link" (first block))
-                             (map? (second block))
-                             (= "id" (:protocol (second (:url (second block))))))
-
-                        (:link (second (:url (second block))))
+                             (map? (second block)))
+                        (if (= "id" (:protocol (second (:url (second block)))))
+                          (:link (second (:url (second block))))
+                          (let [id (second (:url (second block)))]
+                            (when (text/block-ref? id)
+                             (text/block-ref-un-brackets! id))))
 
                         :else
                         nil)]
@@ -144,13 +147,6 @@
    (vector? block)
    (= "Timestamp" (first block))))
 
-(defn properties-block?
-  [block]
-  (and
-   (vector? block)
-   (contains? #{"Property_Drawer" "Properties"}
-              (first block))))
-
 (defn definition-list-block?
   [block]
   (and
@@ -183,8 +179,7 @@
                                            (let [k (name k)
                                                 v (string/trim v)
                                                 k (string/replace k " " "-")
-                                                k (string/replace k "_" "-")
-                                                k (string/lower-case k)
+                                                 k (string/lower-case k)
                                                 v (cond
                                                     (= v "true")
                                                     true
@@ -201,14 +196,12 @@
                                                     v
 
                                                     :else
-                                                    (let [v' v
-                                                          ;; built-in collections
-                                                          comma? (contains? #{"tags" "alias"} k)]
+                                                    (let [v' v]
                                                       (if (and k v'
                                                                (contains? config/markers k)
                                                                (util/safe-parse-int v'))
                                                         (util/safe-parse-int v')
-                                                        (text/split-page-refs-without-brackets v' comma?))))]
+                                                        (text/split-page-refs-without-brackets v' true))))]
                                             [(keyword k) v])))))]
     {:properties properties
      :page-refs page-refs}))
@@ -274,8 +267,8 @@
         (assoc m :block/journal? false)))))
 
 (defn with-page-refs
-  [{:keys [title body tags refs] :as block} with-id?]
-  (let [refs (->> (concat tags refs)
+  [{:keys [title body tags refs marker priority] :as block} with-id?]
+  (let [refs (->> (concat tags refs [marker priority])
                   (remove string/blank?)
                   (distinct))
         refs (atom refs)]
@@ -299,7 +292,7 @@
                                       refs)
                               (remove string/blank?))
           refs (->> (distinct (concat refs children-pages))
-                         (remove nil?))
+                    (remove nil?))
           refs (map (fn [ref] (page-name->map ref with-id?)) refs)]
       (assoc block :refs refs))))
 
@@ -321,22 +314,6 @@
           refs (distinct (concat (:refs block) ref-blocks))]
       (assoc block :refs refs))))
 
-(defn update-src-pos-meta!
-  [{:keys [body] :as block}]
-  (let [body (walk/postwalk
-              (fn [form]
-                (if (and (vector? form)
-                         (= (first form) "Src")
-                         (map? (:pos_meta (second form))))
-                  (let [{:keys [start_pos end_pos]} (:pos_meta (second form))
-                        new_start_pos (- start_pos (get-in block [:meta :start-pos]))]
-                    ["Src" (assoc (second form)
-                                  :pos_meta {:start_pos new_start_pos
-                                             :end_pos (+ new_start_pos (- end_pos start_pos))})])
-                  form))
-              body)]
-    (assoc block :body body)))
-
 (defn block-keywordize
   [block]
   (medley/map-keys
@@ -434,7 +411,7 @@
                                       (drop-while #(= ["Break_Line"] %)))]
                   (recur headings (conj block-body ["Paragraph" other-body]) (rest blocks) timestamps' properties last-pos last-level children))
 
-                (properties-block? block)
+                (text/properties-block? block)
                 (let [properties (extract-properties block start_pos end_pos)]
                   (recur headings block-body (rest blocks) timestamps properties last-pos last-level children))
 
@@ -485,8 +462,7 @@
                       block (-> block
                                 (with-page-refs with-id?)
                                 with-block-refs
-                                block-tags->pages
-                                update-src-pos-meta!)
+                                block-tags->pages)
                       last-pos' (get-in block [:meta :start-pos])]
                   (recur (conj headings block) [] (rest blocks) {} {} last-pos' (:level block) children))
 
@@ -497,7 +473,8 @@
               (when (seq block-body)
                 (reset! pre-block-body (reverse block-body)))
               (when (seq properties)
-                (reset! pre-block-properties (:properties properties)))
+                (let [properties (:properties properties)]
+                  (reset! pre-block-properties properties)))
               (-> (reverse headings)
                   safe-blocks))))]
     (let [first-block (first blocks)

+ 25 - 19
src/main/frontend/format/mldoc.cljs

@@ -1,6 +1,7 @@
 (ns frontend.format.mldoc
   (:require [frontend.format.protocol :as protocol]
             [frontend.util :as util]
+            [frontend.utf8 :as utf8]
             [clojure.string :as string]
             [cljs-bean.core :as bean]
             [cljs.core.match :refer-macros [match]]
@@ -15,6 +16,7 @@
 (defonce parseHtml (gobj/get Mldoc "parseHtml"))
 (defonce anchorLink (gobj/get Mldoc "anchorLink"))
 (defonce parseAndExportMarkdown (gobj/get Mldoc "parseAndExportMarkdown"))
+(defonce astExportMarkdown (gobj/get Mldoc "astExportMarkdown"))
 
 (defn default-config
   ([format]
@@ -37,8 +39,7 @@
         :keep_line_break true
         :format format
         :heading_to_list export-heading-to-list?
-        :exporting_keep_properties exporting-keep-properties?}))))
-  )
+        :exporting_keep_properties exporting-keep-properties?})))))
 
 (def default-references
   (js/JSON.stringify
@@ -59,6 +60,12 @@
                           (or config default-config)
                           (or references default-references)))
 
+(defn ast-export-markdown
+  [ast config references]
+  (astExportMarkdown ast
+                     (or config default-config)
+                     (or references default-references)))
+
 ;; Org-roam
 (defn get-tags-from-definition
   [ast]
@@ -116,10 +123,9 @@
           properties (->> (map first directive-ast)
                           (map (fn [[_ k v]]
                                  (let [k (keyword (string/lower-case k))
-                                       comma? (contains? #{:tags :alias :roam_tags} k)
                                        v (if (contains? #{:title :description :roam_tags} k)
                                            v
-                                           (text/split-page-refs-without-brackets v comma?))]
+                                           (text/split-page-refs-without-brackets v true))]
                                    [k v])))
                           (reverse)
                           (into {}))
@@ -171,6 +177,19 @@
         original-ast))
     ast))
 
+(defn update-src-full-content
+  [ast content]
+  (let [content (utf8/encode content)]
+    (map (fn [[block pos-meta]]
+          (if (and (vector? block)
+                   (= "Src" (first block)))
+            (let [{:keys [start_pos end_pos]} pos-meta
+                  block ["Src" (assoc (second block)
+                                      :full_content
+                                      (utf8/substring content start_pos end_pos))]]
+              [block pos-meta])
+            [block pos-meta])) ast)))
+
 (defn ->edn
   [content config]
   (try
@@ -179,6 +198,7 @@
       (-> content
           (parse-json config)
           (util/json->clj)
+          (update-src-full-content content)
           (collect-page-properties)))
     (catch js/Error e
       (log/error :edn/convert-failed e)
@@ -206,22 +226,8 @@
   (lazyLoad [this ok-handler]
     true)
   (exportMarkdown [this content config references]
-    (parse-export-markdown content config references))
-  )
+    (parse-export-markdown content config references)))
 
 (defn plain->text
   [plains]
   (string/join (map last plains)))
-
-(defn parse-properties
-  [content format]
-  (let [ast (->> (->edn content
-                        (default-config format))
-                 (map first))
-        properties (collect-page-properties ast)
-        properties (let [properties (and (seq ast)
-                                         (= "Properties" (ffirst ast))
-                                         (last (first ast)))]
-                     (if (and properties (seq properties))
-                       properties))]
-    (into {} properties)))

+ 1 - 41
src/main/frontend/format/mldoc_test.cljs

@@ -1,44 +1,4 @@
 (ns frontend.format.mldoc-test
-  (:require [frontend.format.mldoc :refer [parse-properties]]
+  (:require [frontend.format.mldoc]
             [clojure.string :as string]
             [cljs.test :refer [deftest are is testing]]))
-
-(deftest test-parse-org-properties
-  []
-  (testing "just title"
-    (let [content "#+TITLE:   some title   "
-          props (parse-properties content "org")]
-      (are [x y] (= x y)
-        ;; TODO: should we trim in parse-properties?
-        "some title" (string/trim (:title props)))))
-
-  (testing "filetags"
-    (let [content "
-#+FILETAGS:   :tag1:tag_2:@tag:
-#+ROAM_TAGS:  roamtag
-body"
-          props (parse-properties content "org")]
-      (are [x y] (= x y)
-        (list "@tag" "tag1" "tag_2") (sort (:filetags props))
-        ["roamtag"] (:roam_tags props)
-        (list "@tag" "roamtag" "tag1" "tag_2") (sort (:tags props)))))
-
-  (testing "roam tags"
-    (let [content "
-#+FILETAGS: filetag
-#+ROAM_TAGS: roam1 roam2
-body
-"
-          props (parse-properties content "org")]
-      (are [x y] (= x y)
-        ["roam1" "roam2"] (:roam_tags props)
-        (list "filetag" "roam1" "roam2") (sort (:tags props)))))
-
-  (testing "quoted roam tags"
-    (let [content "
-#+ROAM_TAGS: \"why would\"  you use \"spaces\" xxx
-body
-"
-          props (parse-properties content "org")]
-      ;; TODO maybe need to sort or something
-      (is (= ["why would" "spaces" "you" "use" "xxx"] (:roam_tags props))))))

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

@@ -21,9 +21,9 @@
         (cond
           (= "add" type)
           (when-not (db/file-exists? repo path)
-              (let [_ (file-handler/alter-file repo path content {:re-render-root? true
-                                                                  :from-disk? true})]
-             (db/set-file-last-modified-at! repo path mtime)))
+            (let [_ (file-handler/alter-file repo path content {:re-render-root? true
+                                                                :from-disk? true})]
+              (db/set-file-last-modified-at! repo path mtime)))
 
           (and (= "change" type)
                (not (db/file-exists? repo path)))

+ 21 - 7
src/main/frontend/handler/block.cljs

@@ -11,7 +11,9 @@
             [medley.core :as medley]
             [frontend.format.block :as block]
             [frontend.debug :as debug]
-            [clojure.string :as string]))
+            [clojure.string :as string]
+            [frontend.text :as text]
+            [frontend.handler.common :as common-handler]))
 
 (defn get-block-ids
   [block]
@@ -35,14 +37,21 @@
          blocks (vec blocks)]
      (if (seq blocks)
        blocks
-       (let [page-block (when page-name (db/entity [:block/name (string/lower-case page-name)]))
+       (let [page-block (when page-name (db/pull [:block/name (string/lower-case page-name)]))
+             create-title-property? (util/include-windows-reserved-chars? page-name)
+             content (if create-title-property?
+                       (let [title (or (:block/original-name page-block)
+                                       (:block/name page-block))
+                             properties (common-handler/get-page-default-properties title)]
+                         (text/build-properties-str format properties))
+                       "")
              page-id {:db/id (:db/id page-block)}
              dummy (merge {:block/uuid (db/new-block-id)
                            :block/left page-id
                            :block/parent page-id
                            :block/page page-id
                            :block/title ""
-                           :block/content ""
+                           :block/content content
                            :block/format format
                            :block/dummy? true}
                           default-option)
@@ -57,6 +66,7 @@
                          (mapcat last ref-blocks)
                          ref-blocks)
                        (mapcat (fn [b] (concat (:block/refs b) (:block/children-refs b))))
+                       (concat (when group-by-page? (map first ref-blocks)))
                        (distinct)
                        (map :db/id)
                        (db/pull-many repo '[:db/id :block/name]))
@@ -74,15 +84,19 @@
                          (seq exclude-ids)
                          (remove (fn [block]
                                    (let [ids (set (concat (map :db/id (:block/refs block))
-                                                          (map :db/id (:block/children-refs block))))]
+                                                          (map :db/id (:block/children-refs block))
+                                                          [(:db/id (:block/page block))]))]
                                      (seq (set/intersection exclude-ids ids)))))
 
                          (seq include-ids)
                          (remove (fn [block]
-                                   (let [ids (set (concat (map :db/id (:block/refs block))
+                                   (let [page-block-id (:db/id (:block/page block))
+                                         ids (set (concat (map :db/id (:block/refs block))
                                                           (map :db/id (:block/children-refs block))))]
-                                     (empty? (set/intersection include-ids ids)))))
-                         ))]
+                                     (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)

+ 8 - 1
src/main/frontend/handler/common.cljs

@@ -15,7 +15,8 @@
             [cljs-time.format :as tf]
             [frontend.config :as config]
             ["ignore" :as Ignore]
-            ["/frontend/utils" :as utils]))
+            ["/frontend/utils" :as utils]
+            [frontend.date :as date]))
 
 (defn get-ref
   [repo-url]
@@ -160,3 +161,9 @@
                    (do (log/error :token/failed-get-token token-m)
                        (reject)))))
              nil))))))))
+
+(defn get-page-default-properties
+  [page-name]
+  {:title page-name
+   ;; :date (date/get-date-time-string)
+   })

+ 91 - 42
src/main/frontend/handler/editor.cljs

@@ -304,7 +304,10 @@
         content (string/triml content)
         content (string/replace content (util/format "((%s))" (str uuid)) "")
         [content content'] (cond
-                             (or properties? (and markdown-heading? top-level?))
+                             (and first-block? properties?)
+                             [content content]
+
+                             (and markdown-heading? top-level?)
                              [content content]
 
                              markdown-heading?
@@ -314,8 +317,9 @@
                              :else
                              (let [content' (str (config/get-block-pattern format) (if heading? " " "\n") content)]
                                [content content']))
-        block (block/parse-block (-> (assoc block :block/content content')
-                                     (dissoc :block/properties)))
+        block (assoc block :block/content content')
+        block (apply dissoc block (remove #{:block/pre-block?} db-schema/retract-attributes))
+        block (block/parse-block block)
         block (if (and first-block? (:block/pre-block? block))
                 block
                 (dissoc block :block/pre-block?))
@@ -353,12 +357,13 @@
   ([block value
     {:keys [force?]
      :as opts}]
-   (let [{:block/keys [uuid content file page format repo content properties]} block
+   (let [{:block/keys [uuid file page format repo content properties]} block
          repo (or repo (state/get-current-repo))
          e (db/entity repo [:block/uuid uuid])
          format (or format (state/get-preferred-format))
          page (db/entity repo (:db/id page))
-         block-id (when (map? properties) (get properties :id))]
+         block-id (when (map? properties) (get properties :id))
+         content (text/remove-built-in-properties! format content)]
      (cond
        (another-block-with-same-id-exists? uuid block-id)
        (notification/show!
@@ -381,7 +386,7 @@
     [fst-block-text snd-block-text]))
 
 (defn outliner-insert-block!
-  [current-block new-block child?]
+  [current-block new-block child? sibling?]
   (let [dummy? (:block/dummy? current-block)
         [current-node new-node]
         (mapv outliner-core/block [current-block new-block])
@@ -391,6 +396,9 @@
                    child?
                    false
 
+                   (some? sibling?)
+                   sibling?
+
                    (:collapsed (:block/properties current-block))
                    true
 
@@ -451,6 +459,36 @@
       (when blocks-atom
         (reset! blocks-atom blocks)))))
 
+(defn insert-new-block-before-block-aux!
+  [config
+   {:block/keys [uuid content repo format page dummy?]
+    db-id :db/id
+    :as block}
+   value
+   {:keys [ok-handler]
+    :as opts}]
+  (let [input (gdom/getElement (state/get-edit-input-id))
+        pos (util/get-input-pos input)
+        repo (or repo (state/get-current-repo))
+        [fst-block-text snd-block-text] (compute-fst-snd-block-text value pos)
+        current-block (assoc block :block/content snd-block-text)
+        current-block (apply dissoc current-block db-schema/retract-attributes)
+        current-block (wrap-parse-block current-block)
+        new-m {:block/uuid (db/new-block-id)
+               :block/content fst-block-text}
+        prev-block (-> (merge (select-keys block [:block/parent :block/left :block/format
+                                                  :block/page :block/level :block/file :block/journal?]) new-m)
+                       (wrap-parse-block))
+        left-block (db/pull (:db/id (:block/left block)))
+        _ (outliner-core/save-node (outliner-core/block current-block))
+        sibling? (not= (:db/id left-block) (:db/id (:block/parent block)))
+        {:keys [sibling? blocks]} (profile
+                                   "outliner insert block"
+                                   (outliner-insert-block! left-block prev-block nil sibling?))]
+
+    (db/refresh! repo {:key :block/insert :data [prev-block left-block current-block]})
+    (profile "ok handler" (ok-handler prev-block))))
+
 (defn insert-new-block-aux!
   [config
    {:block/keys [uuid content repo format page dummy?]
@@ -474,7 +512,7 @@
                        (wrap-parse-block))
         {:keys [sibling? blocks]} (profile
                                    "outliner insert block"
-                                   (outliner-insert-block! current-block next-block block-self?))
+                                   (outliner-insert-block! current-block next-block block-self? nil))
         refresh-fn (fn []
                      (let [opts {:key :block/insert
                                  :data [current-block next-block]}]
@@ -554,9 +592,17 @@
              block (or (db/pull [:block/uuid block-id])
                        block)
              repo (or (:block/repo block) (state/get-current-repo))
-             [properties value] (with-timetracking-properties block value)]
-         ;; save the current block and insert a new block
-         (insert-new-block-aux!
+             [properties value] (with-timetracking-properties block value)
+             block-self? (block-self-alone-when-insert? config block-id)
+             input (gdom/getElement (state/get-edit-input-id))
+             pos (util/get-input-pos input)
+             repo (or repo (state/get-current-repo))
+             [fst-block-text snd-block-text] (compute-fst-snd-block-text value pos)
+             insert-fn (match (mapv boolean [block-self? (seq fst-block-text) (seq snd-block-text)])
+                         [true _ _] insert-new-block-aux!
+                         [_ false true] insert-new-block-before-block-aux!
+                         [_ _ _] insert-new-block-aux!)]
+         (insert-fn
           config
           (assoc block :block/properties properties)
           value
@@ -949,12 +995,15 @@
   [copy?]
   (when copy? (copy-selection-blocks))
   (when-let [blocks (seq (get-selected-blocks-with-children))]
-    (let [repo (dom/attr (first blocks) "repo")
-          ids (distinct (map #(uuid (dom/attr % "blockid")) blocks))
-          ids (if (= :up (state/get-selection-direction))
-                (reverse ids)
-                ids)]
-      (delete-blocks! repo ids))))
+    ;; remove embeds and references
+    (let [blocks (remove (fn [block] (= "true" (dom/attr block "data-transclude"))) blocks)]
+      (when (seq blocks)
+        (let [repo (dom/attr (first blocks) "repo")
+             ids (distinct (map #(uuid (dom/attr % "blockid")) blocks))
+             ids (if (= :up (state/get-selection-direction))
+                   (reverse ids)
+                   ids)]
+         (delete-blocks! repo ids))))))
 
 (defn- get-nearest-page
   []
@@ -1224,17 +1273,16 @@
 
 (defn save-assets!
   ([{block-id :block/uuid} repo files]
-   (when-let [block-file (db-model/get-block-file block-id)]
-     (p/let [[repo-dir assets-dir] (ensure-assets-dir! repo)]
-       (save-assets! repo repo-dir assets-dir files
-                     (fn [index file-base]
-                       ;; TODO: maybe there're other chars we need to handle?
-                       (let [file-base (-> file-base
-                                           (string/replace " " "_")
-                                           (string/replace "%" "_")
-                                           (string/replace "/" "_"))
-                             file-name (str file-base "_" (.now js/Date) "_" index)]
-                         (string/replace file-name #"_+" "_")))))))
+   (p/let [[repo-dir assets-dir] (ensure-assets-dir! repo)]
+     (save-assets! repo repo-dir assets-dir files
+                   (fn [index file-base]
+                     ;; TODO: maybe there're other chars we need to handle?
+                     (let [file-base (-> file-base
+                                         (string/replace " " "_")
+                                         (string/replace "%" "_")
+                                         (string/replace "/" "_"))
+                           file-name (str file-base "_" (.now js/Date) "_" index)]
+                       (string/replace file-name #"_+" "_"))))))
   ([repo dir path files gen-filename]
    (p/all
     (for [[index ^js file] (map-indexed vector files)]
@@ -1686,7 +1734,7 @@
     (when-let [input (gdom/getElement id)]
       (let [current-pos (:pos (util/get-caret-pos input))
             pos (:editor/last-saved-cursor @state/state)
-            edit-content (state/sub [:editor/content id])]
+            edit-content (or (state/sub [:editor/content id]) "")]
         (or
          @*selected-text
          (util/safe-subs edit-content pos current-pos))))))
@@ -1829,7 +1877,7 @@
                                                                                        (mldoc/->edn (str (case format
                                                                                                            :markdown "- "
                                                                                                            :org "* ")
-                                                                                                         (if (:block/title %) "" "\n")
+                                                                                                         (if (seq (:block/title %)) "" "\n")
                                                                                                          new-content)
                                                                                                     (mldoc/default-config format)))))
                                                                       (:block/title %))]
@@ -2085,7 +2133,7 @@
         right (outliner-core/get-right-node (outliner-core/block current-block))
         current-block-has-children? (db/has-children? repo (:block/uuid current-block))
         collapsed? (:collapsed (:block/properties current-block))
-        first-child (outliner-db/get-first-child (db/get-conn repo false) (:db/id current-block))
+        first-child (:data (tree/-get-down (outliner-core/block current-block)))
         next-block (if (or collapsed? (not current-block-has-children?))
                      (:data right)
                      first-child)]
@@ -2277,15 +2325,6 @@
               (= "#" (util/nth-safe value (dec pos)))))
         (state/set-editor-show-page-search-hashtag! false)
 
-        (or
-         (surround-by? input "#" " ")
-         (surround-by? input "#" :end)
-         (= key "#"))
-        (do
-          (commands/handle-step [:editor/search-page-hashtag])
-          (state/set-last-pos! (:pos (util/get-caret-pos input)))
-          (reset! commands/*slash-caret-pos (util/get-caret-pos input)))
-
         (and
          (contains? (set/difference (set (keys reversed-autopair-map))
                                     #{"`"})
@@ -2311,6 +2350,15 @@
             :else
             nil))
 
+        (or
+         (surround-by? input "#" " ")
+         (surround-by? input "#" :end)
+         (= key "#"))
+        (do
+          (commands/handle-step [:editor/search-page-hashtag])
+          (state/set-last-pos! (:pos (util/get-caret-pos input)))
+          (reset! commands/*slash-caret-pos (util/get-caret-pos input)))
+
         (let [sym "$"]
           (and (= key sym)
                (>= (count value) 1)
@@ -2344,9 +2392,10 @@
           value (gobj/get input "value")
           c (util/nth-safe value (dec current-pos))]
       (when-not (state/get-editor-show-input)
-        (when (and (= c " ")
-                   (not (state/get-editor-show-page-search?)))
-          (state/set-editor-show-page-search-hashtag! false))
+        (when (= c " ")
+          (when (or (= (util/nth-safe value (dec (dec current-pos))) "#")
+                    (not (state/get-editor-show-page-search?)))
+            (state/set-editor-show-page-search-hashtag! false)))
 
         (when (and @*show-commands (not= key-code 191)) ; not /
           (let [matched-commands (get-matched-commands input)]

+ 38 - 8
src/main/frontend/handler/extract.cljs

@@ -12,7 +12,10 @@
             [frontend.format.block :as block]
             [frontend.format :as format]
             [cljs-time.core :as t]
-            [cljs-time.coerce :as tc]))
+            [cljs-time.coerce :as tc]
+            [medley.core :as medley]
+            [clojure.walk :as walk]
+            [frontend.state :as state]))
 
 (defn- extract-page-list
   [content]
@@ -52,12 +55,34 @@
           content
           (remove-indentation-spaces content (:block/level block)))))))
 
+(defn get-page-name
+  [file ast]
+  ;; headline
+  (let [ast (map first ast)]
+    (if (string/includes? file "pages/contents.")
+      "Contents"
+      (let [first-block (last (first (filter block/heading-block? ast)))
+            property-name (when (and (contains? #{"Properties" "Property_Drawer"} (ffirst ast))
+                                     (not (string/blank? (:title (last (first ast))))))
+                            (:title (last (first ast))))
+            first-block-name (let [title (last (first (:title first-block)))]
+                               (and first-block
+                                    (string? title)
+                                    title))
+            file-name (when-let [file-name (last (string/split file #"/"))]
+                        (-> (first (util/split-last "." file-name))
+                            (string/replace "." "/")))]
+        (or property-name
+            (if (= (state/page-name-order) "heading")
+              (or first-block-name file-name)
+              (or file-name first-block-name)))))))
+
 ;; TODO: performance improvement
 (defn- extract-pages-and-blocks
   [repo-url format ast properties file content utf8-content journal?]
   (try
     (let [now (tc/to-long (t/now))
-          page (db/get-page-name file ast)
+          page (get-page-name file ast)
           [page page-name journal-day] (block/convert-page-if-journal page)
           blocks (->> (block/extract-blocks ast content false format)
                       (block/with-parent-and-left [:block/name (string/lower-case page)]))
@@ -154,12 +179,17 @@
            format (format/get-format file)
            ast (mldoc/->edn content
                             (mldoc/default-config format))
-           first-block (first ast)
-           properties (let [properties (and (seq first-block)
-                                            (= "Properties" (ffirst first-block))
-                                            (last (first first-block)))]
-                        (if (and properties (seq properties))
-                          properties))]
+           first-block (ffirst ast)
+           properties (let [properties (and (text/properties-block? first-block)
+                                            (->> (last first-block)
+                                                 (into {})
+                                                 (walk/keywordize-keys)))]
+                        (when (and properties (seq properties))
+                          (if (:filters properties)
+                            (update properties :filters
+                                    (fn [v]
+                                      (string/replace (or v "") "\\" "")))
+                            properties)))]
        (extract-pages-and-blocks
         repo-url
         format ast properties

+ 1 - 18
src/main/frontend/handler/git.cljs

@@ -10,9 +10,7 @@
             [frontend.handler.route :as route-handler]
             [frontend.handler.common :as common-handler]
             [frontend.config :as config]
-            [cljs-time.local :as tl]
-            [clojure.string :as string]
-            [goog.object :as gobj]))
+            [cljs-time.local :as tl]))
 
 (defn- set-git-status!
   [repo-url value]
@@ -63,18 +61,3 @@
      (config/get-repo-dir repo-url)
      name
      email)))
-
-(defn show-commit-modal!
-  [modal]
-  (fn [e]
-    (when (and
-          (string/starts-with? (state/get-current-repo) "https://")
-          (not (util/input? (gobj/get e "target")))
-          (not (gobj/get e "shiftKey"))
-          (not (gobj/get e "ctrlKey"))
-          (not (gobj/get e "altKey"))
-          (not (gobj/get e "metaKey")))
-     (when-let [repo-url (state/get-current-repo)]
-       (when-not (state/get-edit-input-id)
-         (util/stop e)
-         (state/set-modal! modal))))))

+ 64 - 23
src/main/frontend/handler/page.cljs

@@ -16,6 +16,7 @@
             [frontend.handler.ui :as ui-handler]
             [frontend.modules.outliner.file :as outliner-file]
             [frontend.modules.outliner.core :as outliner-core]
+            [frontend.modules.outliner.tree :as outliner-tree]
             [frontend.commands :as commands]
             [frontend.date :as date]
             [clojure.walk :as walk]
@@ -48,18 +49,49 @@
   ([page-name] (when-let [page (db/entity [:block/name page-name])]
                  (:file/path (:block/file page)))))
 
+(defn default-properties-block
+  [title format page]
+  (let [properties (common-handler/get-page-default-properties title)
+        content (text/build-properties-str format properties)]
+    {:block/pre-block? true
+     :block/uuid (db/new-block-id)
+     :block/properties properties
+     :block/left page
+     :block/format format
+     :block/content content
+     :block/parent page
+     :block/unordered true
+     :block/page page}))
+
 (defn create!
   ([title]
    (create! title {}))
   ([title {:keys [redirect?]
            :or {redirect? true}}]
    (let [title (string/trim title)
-         page (string/lower-case title)]
-     (let [tx (block/page-name->map title true)]
-       (db/transact! [tx]))
+         page (string/lower-case title)
+         tx (block/page-name->map title true)
+         format (state/get-preferred-format)
+         page-entity [:block/uuid (:block/uuid tx)]
+         create-title-property? (util/include-windows-reserved-chars? title)
+         default-properties (default-properties-block title format page-entity)
+         empty-block {:block/uuid (db/new-block-id)
+                      :block/left [:block/uuid (:block/uuid default-properties)]
+                      :block/format format
+                      :block/content ""
+                      :block/parent page-entity
+                      :block/unordered true
+                      :block/page page-entity}
+         txs (if create-title-property?
+               [tx default-properties empty-block]
+               [tx])]
+     (db/transact! txs)
      (when redirect?
       (route-handler/redirect! {:to :page
-                                :path-params {:name page}})))))
+                                :path-params {:name page}})
+      (when create-title-property?
+        (js/setTimeout (fn []
+                        (editor-handler/edit-block! empty-block 0 format (:block/uuid empty-block))) 50))))))
 
 (defn page-add-property!
   [page-name key value]
@@ -173,12 +205,12 @@
 (defn rename-file!
   [file new-name ok-handler]
   (let [repo (state/get-current-repo)
+        file (db/pull (:db/id file))
         old-path (:file/path file)
         new-path (compute-new-file-path old-path new-name)]
     ;; update db
     (db/transact! repo [{:db/id (:db/id file)
                          :file/path new-path}])
-
     (->
      (p/let [_ (fs/rename! repo
                            (if (util/electron?)
@@ -217,15 +249,26 @@
         (let [name-changed? (not= (string/lower-case (string/trim old-name))
                                   (string/lower-case (string/trim new-name)))]
           (when-let [repo (state/get-current-repo)]
-            (when-let [page (db/entity [:block/name (string/lower-case old-name)])]
+            (when-let [page (db/pull [:block/name (string/lower-case old-name)])]
               (let [old-original-name (:block/original-name page)
                     file (:block/file page)
-                    journal? (:block/journal? page)]
-                (d/transact! (db/get-conn repo false)
-                             [{:db/id (:db/id page)
+                    journal? (:block/journal? page)
+                    properties-block (:data (outliner-tree/-get-down (outliner-core/block page)))
+                    properties-block-tx (when (and properties-block
+                                                   (string/includes? (string/lower-case (:block/content properties-block))
+                                                                     (string/lower-case old-name)))
+                                          {:db/id (:db/id properties-block)
+                                           :block/content (text/insert-property! (:block/format properties-block)
+                                                                                 (:block/content properties-block)
+                                                                                 :title
+                                                                                 new-name)})
+                    page-txs [{:db/id (:db/id page)
                                :block/uuid (:block/uuid page)
                                :block/name (string/lower-case new-name)
-                               :block/original-name new-name}])
+                               :block/original-name new-name}]
+                    page-txs (if properties-block-tx (conj page-txs properties-block-tx) page-txs)]
+
+                (d/transact! (db/get-conn repo false) page-txs)
 
                 (when (and file (not journal?) name-changed?)
                   (rename-file! file new-name (fn [] nil)))
@@ -254,7 +297,9 @@
                               (remove nil?))]
                   (db/transact! repo tx)
                   (doseq [page-id page-ids]
-                    (outliner-file/sync-to-file page-id))))
+                    (outliner-file/sync-to-file page-id)))
+
+                (outliner-file/sync-to-file page))
 
               ;; TODO: update browser history, remove the current one
 
@@ -361,18 +406,14 @@
   (->> (db/get-modified-pages repo)
        (remove util/file-page?)))
 
-(defonce filters-state (atom nil))
 (defn get-filters
   [page-name]
-  (let [properties (db/get-page-properties page-name)
-        filters (reader/read-string (get properties :filters "{}"))]
-    (reset! filters-state filters)
-    filters-state))
+  (let [properties (db/get-page-properties page-name)]
+    (reader/read-string (get properties :filters "{}"))))
 
 (defn save-filter!
   [page-name filter-state]
-  (page-add-property! page-name :filters filter-state)
-  (reset! filters-state (pr-str filter-state)))
+  (page-add-property! page-name :filters filter-state))
 
 (defn page-exists?
   [page-name]
@@ -412,16 +453,16 @@
     (if (state/sub :editor/show-page-search-hashtag?)
       (fn [chosen _click?]
         (state/set-editor-show-page-search! false)
-        (let [chosen (if (re-find #"\s+" chosen)
+        (let [wrapped? (= "[[" (util/safe-subs edit-content (- pos 2) pos))
+              chosen (if (and (re-find #"\s+" chosen) (not wrapped?))
                        (util/format "[[%s]]" chosen)
                        chosen)]
           (editor-handler/insert-command! id
-                                          (str "#" chosen)
+                                          (str "#" (when wrapped? "[[") chosen)
                                           format
                                           {:last-pattern (let [q (if @editor-handler/*selected-text "" q)]
-                                                           (if (and q (string/starts-with? q "#"))
-                                                             q
-                                                             (str "#" q)))})))
+                                                           (str "#" (when wrapped? "[[") q))
+                                           :forward-pos (if wrapped? 3 2)})))
       (fn [chosen _click?]
         (state/set-editor-show-page-search! false)
         (let [page-ref-text (get-page-ref-text chosen)]

+ 22 - 22
src/main/frontend/handler/repo.cljs

@@ -1,30 +1,31 @@
 (ns frontend.handler.repo
   (:refer-clojure :exclude [clone])
-  (:require [frontend.util :as util :refer-macros [profile]]
+  (:require [cljs-bean.core :as bean]
+            [clojure.string :as string]
+            [frontend.config :as config]
+            [frontend.date :as date]
+            [frontend.db :as db]
+            [frontend.dicts :as dicts]
+            [frontend.encrypt :as encrypt]
+            [frontend.format :as format]
             [frontend.fs :as fs]
             [frontend.fs.nfs :as nfs]
-            [promesa.core :as p]
-            [lambdaisland.glogi :as log]
-            [frontend.state :as state]
-            [frontend.db :as db]
-            [frontend.idb :as idb]
             [frontend.git :as git]
-            [cljs-bean.core :as bean]
-            [frontend.date :as date]
-            [frontend.config :as config]
-            [frontend.format :as format]
-            [frontend.search :as search]
-            [frontend.handler.ui :as ui-handler]
-            [frontend.handler.git :as git-handler]
+            [frontend.handler.common :as common-handler]
+            [frontend.handler.extract :as extract-handler]
             [frontend.handler.file :as file-handler]
+            [frontend.handler.git :as git-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.route :as route-handler]
-            [frontend.handler.common :as common-handler]
-            [frontend.handler.extract :as extract-handler]
-            [clojure.string :as string]
-            [frontend.dicts :as dicts]
+            [frontend.handler.ui :as ui-handler]
+            [frontend.idb :as idb]
+            [frontend.search :as search]
             [frontend.spec :as spec]
-            [frontend.encrypt :as encrypt]))
+            [frontend.state :as state]
+            [frontend.util :as util]
+            [lambdaisland.glogi :as log]
+            [promesa.core :as p]
+            [shadow.resource :as rc]))
 
 ;; Project settings should be checked in two situations:
 ;; 1. User changes the config.edn directly in logseq.com (fn: alter-file)
@@ -54,10 +55,8 @@
                   (config/get-file-extension format))
         file-path (str "/" path)
         default-content (case (name format)
-                          "org"
-                          "* What's **Contents**?\n** It's a normal page called [[Contents]], you can use it for:\n*** 1. table of content/index/MOC\n*** 2. pinning/bookmarking favorites pages/blocks (e.g. [[Logseq]])\n*** 3. You can also put many different things, depending on your personal workflow."
-                          "markdown"
-                          "- What's **Contents**?\n    - It's a normal page called [[Contents]], you can use it for:\n    - 1. table of content/index/MOC\n    - 2. pinning/bookmarking favorites pages/blocks (e.g. [[Logseq]])\n    - 3. You can also put many different things, depending on your personal workflow."
+                          "org" (rc/inline "contents.org")
+                          "markdown" (rc/inline "contents.md")
                           "")]
     (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" (state/get-pages-directory)))
             file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
@@ -237,6 +236,7 @@
       (if db-encrypted?
         (let [close-fn #(parse-files-and-create-default-files! repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts metadata)]
           (state/pub-event! [:modal/encryption-input-secret-dialog repo-url
+                             db-encrypted-secret
                              close-fn]))
         (parse-files-and-create-default-files! repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts metadata)))))
 

+ 13 - 12
src/main/frontend/handler/search.cljs

@@ -18,12 +18,10 @@
             :as opts}]
    (let [page-db-id (if (string? page-db-id)
                       (:db/id (db/entity repo [:block/name (string/lower-case page-db-id)]))
-                      page-db-id)]
+                      page-db-id)
+         opts (if page-db-id (assoc opts :page (str page-db-id)) opts)]
      (p/let [blocks (search/block-search repo q opts)]
-      (let [blocks (if page-db-id
-                     (filter (fn [block] (= (get-in block [:block/page :db/id]) page-db-id)) blocks)
-                     blocks)
-            result (merge
+      (let [result (merge
                     {:blocks blocks
                      :has-more? (= limit (count blocks))}
                     (when-not page-db-id
@@ -33,13 +31,16 @@
         (swap! state/state assoc search-key result))))))
 
 (defn clear-search!
-  []
-  (swap! state/state assoc
-         :search/result nil
-         :search/q ""
-         :search/mode :global)
-  (when-let [input (gdom/getElement "search-field")]
-    (gobj/set input "value" "")))
+  ([]
+   (clear-search! true))
+  ([clear-search-mode?]
+   (let [m (cond-> {:search/result nil
+                    :search/q ""}
+             clear-search-mode?
+             (assoc :search/mode :global))]
+     (swap! state/state merge m))
+   (when-let [input (gdom/getElement "search-field")]
+     (gobj/set input "value" ""))))
 
 (defn rebuild-indices!
   []

+ 59 - 4
src/main/frontend/handler/ui.cljs

@@ -1,11 +1,13 @@
 (ns frontend.handler.ui
-  (:require [dommy.core :as dom]
-            [frontend.state :as state]
+  (:require [cljs-time.core :refer [plus days weeks]]
+            [dommy.core :as dom]
+            [frontend.util :as util]
             [frontend.db :as db]
-            [rum.core :as rum]
+            [frontend.state :as state]
             [goog.dom :as gdom]
             [goog.object :as gobj]
-            [frontend.util :as util :refer-macros [profile]]))
+            [clojure.string :as string]
+            [rum.core :as rum]))
 
 ;; sidebars
 (defn close-left-sidebar!
@@ -152,3 +154,56 @@
                 @current-idx))
       (on-chosen (nth matched @current-idx) false)
       (and on-enter (on-enter state)))))
+
+;; date-picker
+;; TODO: find a better way
+(def *internal-model (rum/cursor state/state :date-picker/date))
+
+(defn- non-edit-input?
+  []
+  (when-let [elem js/document.activeElement]
+    (and (util/input? elem)
+         (when-let [id (gobj/get elem "id")]
+           (not (string/starts-with? id "edit-block-"))))))
+
+(defn- input-or-select?
+  []
+  (when-let [elem js/document.activeElement]
+    (or (non-edit-input?)
+        (util/select? elem))))
+
+(defn- inc-date [date n] (plus date (days n)))
+
+(defn- inc-week [date n] (plus date (weeks n)))
+
+(defn shortcut-complete
+  [state e]
+  (let [{:keys [on-change deadline-or-schedule?]} (last (:rum/args state))]
+    (when (and on-change
+               (not (input-or-select?)))
+      (when-not deadline-or-schedule?
+        (on-change e @*internal-model)))))
+
+(defn shortcut-prev-day
+  [_state e]
+  (when-not (input-or-select?)
+    (util/stop e)
+    (swap! *internal-model inc-date -1)))
+
+(defn shortcut-next-day
+  [_state e]
+  (when-not (input-or-select?)
+    (util/stop e)
+    (swap! *internal-model inc-date 1)))
+
+(defn shortcut-prev-week
+  [_state e]
+  (when-not (input-or-select?)
+    (util/stop e)
+    (swap! *internal-model inc-week -1)))
+
+(defn shortcut-next-week
+  [_state e]
+  (when-not (input-or-select?)
+    (util/stop e)
+    (swap! *internal-model inc-week 1)))

+ 6 - 4
src/main/frontend/idb.cljs

@@ -18,10 +18,12 @@
 
 (defn clear-idb!
   []
-  (p/let [_ (idb-keyval/clear store)
-          dbs (js/window.indexedDB.databases)]
-    (doseq [db dbs]
-      (js/window.indexedDB.deleteDatabase (gobj/get db "name")))))
+  (->
+   (p/let [_ (idb-keyval/clear store)
+           dbs (js/window.indexedDB.databases)]
+     (doseq [db dbs]
+       (js/window.indexedDB.deleteDatabase (gobj/get db "name"))))
+   (p/catch (fn [_e]))))
 
 (defn clear-local-storage-and-idb!
   []

+ 0 - 30
src/main/frontend/mixins.cljs

@@ -1,7 +1,6 @@
 (ns frontend.mixins
   (:require [rum.core :as rum]
             [goog.dom :as dom]
-            [goog.object :as gobj]
             [frontend.util :refer-macros [profile]])
   (:import [goog.events EventHandler]))
 
@@ -173,32 +172,3 @@
        (profile
         (str "Render " desc)
         (render-fn state))))})
-
-(defn shortcuts
-  [install-shortcut! listener dispatcher]
-  {:did-mount
-   (fn [state]
-     (->> dispatcher
-          (reduce-kv (fn [result id handle-fn]
-                       (assoc result id (partial handle-fn state)))
-                     {})
-          install-shortcut!
-          (assoc state listener)))
-   :did-remount (fn [old-state new-state]
-
-                  ;; remove shortcuts and unlisten
-                  (when-let [f (get old-state listener)]
-                    (f))
-
-                  ;; update new states
-                  (->> dispatcher
-                       (reduce-kv (fn [result id handle-fn]
-                                    (assoc result id (partial handle-fn new-state)))
-                                  {})
-                       install-shortcut!
-                       (assoc new-state listener)))
-   :will-unmount
-   (fn [state]
-     (when-let [f (get state listener)]
-       (f))
-     (dissoc state listener))})

+ 1 - 1
src/main/frontend/modules/file/core.cljs

@@ -114,7 +114,7 @@
                 "/"
                 (if journal-page?
                   (date/journal-title->default title)
-                  (-> (:block/name page)
+                  (-> (or (:block/original-name page) (:block/name page))
                       (util/page-name-sanity))) "."
                 (if (= format "markdown") "md" format))
           file-path (str "/" path)

+ 1 - 1
src/main/frontend/modules/outliner/core.cljs

@@ -412,7 +412,7 @@
   {:pre [(tree/satisfied-inode? start-node)
          (tree/satisfied-inode? end-node)]}
   (let [sibling? (= (tree/-get-parent-id start-node)
-                   (tree/-get-parent-id end-node))]
+                    (tree/-get-parent-id end-node))]
     (when sibling?
       (ds/auto-transact!
         [txs-state (ds/new-outliner-txs-state)]

+ 10 - 10
src/main/frontend/modules/shortcut/mixin.cljs → src/main/frontend/modules/shortcut/before.cljs

@@ -1,15 +1,9 @@
-(ns frontend.modules.shortcut.mixin
+(ns frontend.modules.shortcut.before
   (:require [frontend.config :as config]
             [frontend.state :as state]
             [frontend.util :as util]))
 
-(defn before [f shortcut-map]
-  (reduce-kv (fn [r k v]
-               (assoc r k (f v)))
-             {}
-             shortcut-map))
-
-;; middleware for before function
+;; before function
 (defn prevent-default-behavior
   [f]
   (fn [e]
@@ -28,10 +22,16 @@
 
 (defn enable-when-editing-mode!
   [f]
-  (fn [state e]
+  (fn [e]
     (when (state/editing?)
       (util/stop e)
-      (f state e))))
+      (f e))))
+
+(defn enable-when-not-component-editing!
+  [f]
+  (fn [e]
+    (when-not (state/block-component-editing?)
+      (f e))))
 
 (defn only-enable-when-dev!
   [_]

+ 0 - 122
src/main/frontend/modules/shortcut/binding.cljc

@@ -1,122 +0,0 @@
-(ns frontend.modules.shortcut.binding
-  (:require [frontend.util :refer [mac?]]))
-
-(def default
-  {:date-picker/complete "enter"
-   :date-picker/prev-day "left"
-   :date-picker/next-day "right"
-   :date-picker/prev-week "up"
-   :date-picker/next-week "down"
-
-   ;; auto complete navigation, works for command prompt, search prompt or any auto complete prompt
-   :auto-complete/prev "up"
-   :auto-complete/next "down"
-   :auto-complete/complete "enter"
-
-   :editor/clear-selection "esc"
-   :editor/toggle-document-mode "t d"
-   :editor/toggle-settings (if mac? "t s" ["t s" "mod+,"])
-   :editor/undo "mod+z"
-   :editor/redo ["shift+mod+z" "mod+y"]
-   :editor/zoom-in (if mac? "mod+." "alt+right")
-   :editor/zoom-out (if mac? "mod+," "alt+left")
-   :editor/cycle-todo "mod+enter"
-   :editor/expand-block-children "mod+down"
-   :editor/collapse-block-children "mod+up"
-   :editor/follow-link "mod+o"
-   :editor/open-link-in-sidebar "mod+shift+o"
-   :editor/bold "mod+b"
-   :editor/italics "mod+i"
-   :editor/highlight "mod+shift+h"
-   :editor/insert-link "mod+k"
-   :editor/select-all-blocks "mod+shift+a"
-   :editor/move-block-up (if mac? "mod+shift+up"  "alt+shift+up")
-   :editor/move-block-down (if mac? "mod+shift+down" "alt+shift+down")
-   :editor/save "mod+s"
-   :editor/open-block-first "alt+down"
-   :editor/open-block-last "alt+up"
-   :editor/select-block-up "shift+up"
-   :editor/select-block-down "shift+down"
-
-   :editor/new-block "enter"
-   :editor/new-line "shift+enter"
-   ;; 1. when block selection, select up/down
-   ;;    open edit block at leftmost or rightmost
-   ;; 2. when in editing, normal cursor arrow key move
-   :editor/up "up"
-   :editor/down "down"
-   :editor/left "left"
-   :editor/right "right"
-   ;; open selected block and edit
-   :editor/open-edit "enter"
-   :editor/indent "tab"
-   :editor/outindent "shift+tab"
-
-   :editor/copy "mod+c"
-   :editor/cut "mod+x"
-   :editor/backspace "backspace"
-   :editor/delete "delete"
-   :editor/delete-selection ["backspace" "delete"]
-
-   ;; clear the block content
-   :editor/clear-block (if mac? "ctrl+l" "alt+l")
-   ;; kill the line before the cursor position
-   :editor/kill-line-before (if mac? "ctrl+u" "alt+u")
-   ;; kill the line after the cursor position
-   :editor/kill-line-after (if mac? false "alt+k")
-   ;; go to the beginning of the block
-   :editor/beginning-of-block (if mac? false "alt+a")
-   ;; go to the end of the block
-   :editor/end-of-block (if mac? false "alt+e")
-   ;; forward one word
-   :editor/forward-word (if mac? "ctrl+shift+f" "alt+f")
-   ;; backward one word
-   :editor/backward-word (if mac? "ctrl+shift+b" "alt+b")
-   ;; kill one word backward
-   :editor/backward-kill-word (if mac? "ctrl+w" "alt+w")
-   ;; kill one word forward
-   :editor/forward-kill-word (if mac? false "alt+d")
-
-
-   :editor/selection-up "up"
-   :editor/selection-down "down"
-
-   :ui/toggle-help "shift+/"
-   :ui/toggle-theme "t t"
-   :ui/toggle-right-sidebar "t r"
-   :ui/toggle-new-block "t e"
-   :ui/show-contents "t c"
-   :ui/toggle-wide-mode "t w"
-   ;; :ui/toggle-between-page-and-file "s"
-   :ui/fold "tab"
-   :ui/un-fold "shift+tab"
-   :ui/toggle-brackets "mod+c mod+b"
-   :ui/refresh ["f5" "mod+r" "mod+shift+r"]
-
-   :go/search "mod+u"
-   :go/search-page "mod+shift+u"
-   :go/journals (if mac? "mod+j" "alt+j")
-
-   :git/commit "g c"
-
-   :search/re-index "mod+c mod+s"
-   :graph/re-index "mod+c mod+r"})
-
-;; (def custom
-;;   {:editor/new-block "enter"
-;;    :editor/new-line "shift+enter"
-;;    :editor/up ["ctrl+k" "up"]
-;;    :editor/down ["ctrl+j" "down"]
-;;    :editor/left ["ctrl+h" "left"]
-;;    :editor/right ["ctrl+l" "right"]
-;;    :editor/delete ["ctrl+d" "backspace"]
-
-;;    :date-picker/complete ["ctrl+a" "enter"]
-;;    :date-picker/prev-day ["ctrl+h" "left"]
-;;    :date-picker/next-day ["ctrl+l" "right"]
-;;    :date-picker/prev-week ["ctrl+k" "up"]
-;;    :date-picker/next-week ["ctrl+j" "down"]
-
-;;    :auto-complete/prev ["ctrl+k" "up"]
-;;    :auto-complete/next ["ctrl+j" "down"]
-;;    :auto-complete/complete ["ctrl+l" "enter"]})

+ 403 - 0
src/main/frontend/modules/shortcut/config.cljs

@@ -0,0 +1,403 @@
+(ns frontend.modules.shortcut.config
+  (:require [frontend.components.commit :as commit]
+            [frontend.handler.config :as config-handler]
+            [frontend.handler.editor :as editor-handler]
+            [frontend.handler.history :as history]
+            [frontend.handler.repo :as repo-handler]
+            [frontend.handler.route :as route-handler]
+            [frontend.handler.search :as search-handler]
+            [frontend.handler.ui :as ui-handler]
+            [frontend.handler.web.nfs :as nfs-handler]
+            [frontend.modules.shortcut.before :as m]
+            [frontend.state :as state]
+            [frontend.util :refer [mac?]]))
+
+(def default-config
+  {:shortcut.handler/date-picker
+   {:date-picker/complete
+    {:desc    "Date picker choose selected day"
+     :binding "enter"
+     :fn      ui-handler/shortcut-complete}
+    :date-picker/prev-day
+    {:desc    "Date picker select previous day"
+     :binding "left"
+     :fn      ui-handler/shortcut-prev-day}
+    :date-picker/next-day
+    {:desc    "Date picker select next day"
+     :binding "right"
+     :fn      ui-handler/shortcut-next-day}
+    :date-picker/prev-week
+    {:desc    "Date picker select prev week"
+     :binding "up"
+     :fn      ui-handler/shortcut-prev-week}
+    :date-picker/next-week
+    {:desc    "Date picker select next week"
+     :binding "down"
+     :fn      ui-handler/shortcut-next-week}}
+
+   :shortcut.handler/auto-complete
+   {:auto-complete/prev
+    {:desc    "Auto-complete previous selected item"
+     :binding "up"
+     :fn      ui-handler/auto-complete-prev}
+    :auto-complete/next
+    {:desc    "Auto-complete next selected item"
+     :binding "down"
+     :fn      ui-handler/auto-complete-next}
+    :auto-complete/complete
+    {:desc    "Auto-complete choose selected item"
+     :binding "enter"
+     :fn      ui-handler/auto-complete-complete}}
+
+   :shortcut.handler/block-editing-only
+   ^{:before m/enable-when-editing-mode!}
+   {:editor/backspace
+    {:desc    "Backspace / Delete backwards"
+     :binding "backspace"
+     :fn      editor-handler/editor-backspace}
+    :editor/delete
+    {:desc    "Delete / Delete forwards"
+     :binding "delete"
+     :fn      editor-handler/editor-delete}
+    :editor/indent
+    {:desc    "Indent block"
+     :binding "tab"
+     :fn      (editor-handler/keydown-tab-handler :right)}
+    :editor/outdent
+    {:desc    "Outdent block"
+     :binding "shift+tab"
+     :fn      (editor-handler/keydown-tab-handler :left)}
+    :editor/new-block
+    {:desc    "Create new block"
+     :binding "enter"
+     :fn      editor-handler/keydown-new-block-handler}
+    :editor/new-line
+    {:desc    "Newline in block"
+     :binding "shift+enter"
+     :fn      editor-handler/keydown-new-line-handler}
+    :editor/cycle-todo
+    {:desc    "Rotate the TODO state of the current item"
+     :binding "mod+enter"
+     :fn      editor-handler/cycle-todo!}
+    :editor/expand-block-children
+    {:desc    "Expand"
+     :binding "mod+down"
+     :fn      editor-handler/expand!}
+    :editor/collapse-block-children
+    {:desc    "Collapse"
+     :binding "mod+up"
+     :fn      editor-handler/collapse!}
+    :editor/follow-link
+    {:desc    "Follow link under cursor"
+     :binding "mod+o"
+     :fn      editor-handler/follow-link-under-cursor!}
+    :editor/open-link-in-sidebar
+    {:desc    "Open link in sidebar"
+     :binding "mod+shift+o"
+     :fn      editor-handler/open-link-in-sidebar!}
+    :editor/bold
+    {:desc    "Bold"
+     :binding "mod+b"
+     :fn      editor-handler/bold-format!}
+    :editor/italics
+    {:desc    "Italics"
+     :binding "mod+i"
+     :fn      editor-handler/italics-format!}
+    :editor/highlight
+    {:desc    "Highlight"
+     :binding "mod+shift+h"
+     :fn      editor-handler/highlight-format!}
+    :editor/insert-link
+    {:desc    "Html Link"
+     :binding "mod+k"
+     :fn      editor-handler/html-link-format!}
+    ;; FIXME
+    ;; select-all-blocks only works in block editing mode
+    ;; maybe we can improve this
+    :editor/select-all-blocks
+    {:desc    "Select all blocks"
+     :binding "mod+shift+a"
+     :fn      editor-handler/select-all-blocks!}
+    :editor/move-block-up
+    {:desc    "Move block up"
+     :binding (if mac? "mod+shift+up"  "alt+shift+up")
+     :fn      (editor-handler/move-up-down true)}
+    :editor/move-block-down
+    {:desc    "Move block down"
+     :binding (if mac? "mod+shift+down" "alt+shift+down")
+     :fn      (editor-handler/move-up-down false)}
+    :editor/clear-block
+    {:desc    "Clear entire block content"
+     :binding (if mac? "ctrl+l" "alt+l")
+     :fn      editor-handler/clear-block-content!}
+    :editor/kill-line-before
+    {:desc    "Kill line before cursor position"
+     :binding (if mac? "ctrl+u" "alt+u")
+     :fn      editor-handler/kill-line-before!}
+    :editor/kill-line-after
+    {:desc    "Kill line after cursor position"
+     :binding (if mac? false "alt+k")
+     :fn      editor-handler/kill-line-after!}
+    :editor/beginning-of-block
+    {:desc    "Move cursor to the beginning of block"
+     :binding (if mac? false "alt+a")
+     :fn      editor-handler/beginning-of-block}
+    :editor/end-of-block
+    {:desc    "Move cursor to the end of block"
+     :binding (if mac? false "alt+e")
+     :fn      editor-handler/end-of-block}
+    :editor/forward-word
+    {:desc    "Move cursor forward by word"
+     :binding (if mac? "ctrl+shift+f" "alt+f")
+     :fn      editor-handler/cursor-forward-word}
+    :editor/backward-word
+    {:desc    "Move cursor backward by word"
+     :binding (if mac? "ctrl+shift+b" "alt+b")
+     :fn      editor-handler/cursor-backward-word}
+    :editor/forward-kill-word
+    {:desc    "Kill a word forwards"
+     :binding (if mac? "ctrl+w" "alt+d")
+     :fn      editor-handler/forward-kill-word}
+    :editor/backward-kill-word
+    {:desc    "Kill a word backwards"
+     :binding (if mac? false "alt+w")
+     :fn      editor-handler/backward-kill-word}}
+
+   :shortcut.handler/editor-global
+   ^{:before m/enable-when-not-component-editing!}
+   {:editor/up
+    {:desc    "Move cursor up / Select up"
+     :binding "up"
+     :fn      (editor-handler/shortcut-up-down :up)}
+    :editor/down
+    {:desc    "Move cursor down / Select down"
+     :binding "down"
+     :fn      (editor-handler/shortcut-up-down :down)}
+    :editor/left
+    {:desc    "Move cursor left / Open selected block at beginning"
+     :binding "left"
+     :fn      (editor-handler/shortcut-left-right :left)}
+    :editor/right
+    {:desc    "Move cursor right / Open selected block at end"
+     :binding "right"
+     :fn      (editor-handler/shortcut-left-right :right)}
+    ;; FIXME
+    ;; add open edit in non-selection mode
+    :editor/open-edit
+    {:desc    "Edit selected block"
+     :binding "enter"
+     :fn      (partial editor-handler/open-selected-block! :right)}
+    :editor/select-block-up
+    {:desc    "Select block above"
+     :binding "shift+up"
+     :fn      (editor-handler/on-select-block :up)}
+    :editor/select-block-down
+    {:desc    "Select block below"
+     :binding "shift+down"
+     :fn      (editor-handler/on-select-block :down)}
+    :editor/delete-selection
+    {:desc    "Delete selected blocks"
+     :binding ["backspace" "delete"]
+     :fn      editor-handler/delete-selection}
+    :editor/copy
+    {:desc    "Copy"
+     :binding "mod+c"
+     :fn      editor-handler/shortcut-copy}
+    :editor/cut
+    {:desc    "Cut"
+     :binding "mod+x"
+     :fn      editor-handler/shortcut-cut}
+    :editor/undo
+    {:desc    "Undo"
+     :binding "mod+z"
+     :fn      history/undo!}
+    :editor/redo
+    {:desc    "Redo"
+     :binding ["shift+mod+z" "mod+y"]
+     :fn      history/redo!}
+    ;; FIXME
+    ;; save in block editing only doesn't seems needed?
+    :editor/save
+    {:binding "mod+s"
+     :fn      editor-handler/save!}}
+
+   :shortcut.handler/global-prevent-default
+   ^{:before m/prevent-default-behavior}
+   {:editor/zoom-in
+    {:desc    "Zoom in / Forward"
+     :binding (if mac? "mod+." "alt+right")
+     :fn      editor-handler/zoom-in!}
+    :editor/zoom-out
+    {:desc    "Zoom out / Back"
+     :binding (if mac? "mod+," "alt+left")
+     :fn      editor-handler/zoom-out!}
+    :ui/toggle-brackets
+    {:desc    "Toggle whether to display brackets"
+     :binding "mod+c mod+b"
+     :fn      config-handler/toggle-ui-show-brackets!}
+    :go/search-in-page
+    {:desc    "Search in the current page"
+     :binding "mod+shift+u"
+     :fn      #(route-handler/go-to-search! :page)}
+    :go/search
+    {:desc    "Full text search"
+     :binding "mod+u"
+     :fn      route-handler/go-to-search!}
+    :go/journals
+    {:desc    "Jump to journals"
+     :binding (if mac? "mod+j" "alt+j")
+     :fn      route-handler/go-to-journals!}
+    :search/re-index
+    {:desc    "Rebuild search index"
+     :binding "mod+c mod+s"
+     :fn      search-handler/rebuild-indices!}
+    :graph/re-index
+    {:desc    "Re-index the whole graph"
+     :binding "mod+c mod+r"
+     :fn      #(repo-handler/re-index! nfs-handler/rebuild-index!)}}
+
+   :shortcut.handler/global-non-editing-only
+   ^{:before m/enable-when-not-editing-mode!}
+   {:ui/toggle-document-mode
+    {:desc    "Toggle document mode"
+     :binding "t d"
+     :fn      state/toggle-document-mode!}
+    :ui/toggle-settings
+    {:desc    "Toggle settings"
+     :binding (if mac? "t s" ["t s" "mod+,"])
+     :fn      ui-handler/toggle-settings-modal!}
+    :ui/toggle-right-sidebar
+    {:desc    "Toggle right sidebar"
+     :binding "t r"
+     :fn      ui-handler/toggle-right-sidebar!}
+    :ui/toggle-help
+    {:desc    "Toggle help"
+     :binding "shift+/"
+     :fn      ui-handler/toggle-help!}
+    :ui/toggle-theme
+    {:desc    "Toggle between dark/light theme"
+     :binding "t t"
+     :fn      state/toggle-theme!}
+    :ui/toggle-new-block
+    {:desc    "Toggle newblock/newline command for inserting newline/newblock"
+     :binding "t e"
+     :fn      state/toggle-new-block-shortcut!}
+    :ui/toggle-contents
+    {:desc    "Toggle Contents in sidebar"
+     :binding "t c"
+     :fn      ui-handler/toggle-contents!}
+    :ui/toggle-wide-mode
+    {:desc    "Toggle wide mode"
+     :binding "t w"
+     :fn      ui-handler/toggle-wide-mode!}
+    ;; :ui/toggle-between-page-and-file route-handler/toggle-between-page-and-file!
+    :ui/fold
+    {:desc    "Fold blocks (when not in edit mode)"
+     :binding "tab"
+     :fn      (editor-handler/on-tab :right)}
+    :ui/un-fold
+    {:desc    "Unfold blocks (when not in edit mode)"
+     :binding "shift+tab"
+     :fn      (editor-handler/on-tab :left)}
+    :git/commit
+    {:desc    "Git commit message"
+     :binding "g c"
+     :fn      commit/show-commit-modal!}}})
+
+
+;; Categories for docs purpose
+(def category
+  {:shortcut.category/basics
+   ^{:doc "Basics"}
+   [:editor/new-block
+    :editor/new-line
+    :editor/indent
+    :editor/outdent
+    :ui/fold
+    :ui/un-fold
+    :go/search
+    :go/search-in-page
+    :editor/undo
+    :editor/redo
+    :editor/zoom-in
+    :editor/zoom-out
+    :editor/copy
+    :editor/cut
+    :ui/toggle-wide-mode]
+
+   :shortcut.category/formatting
+   ^{:doc "Formatting"}
+   [:editor/bold
+    :editor/insert-link
+    :editor/italics
+    :editor/highlight]
+
+   :shortcut.category/navigating
+   ^{:doc "Navigation"}
+   [:editor/up
+    :editor/down
+    :editor/left
+    :editor/right]
+
+   :shortcut.category/block-editing
+   ^{:doc "Block editing general"}
+   [:editor/backspace
+    :editor/delete
+    :editor/indent
+    :editor/outdent
+    :editor/new-block
+    :editor/new-line
+    :editor/zoom-in
+    :editor/zoom-out
+    :editor/cycle-todo
+    :editor/follow-link
+    :editor/open-link-in-sidebar
+    :editor/select-all-blocks
+    :editor/move-block-up
+    :editor/move-block-down]
+
+   :shortcut.category/block-command-editing
+   ^{:doc "Block command editing"}
+   [:editor/backspace
+    :editor/clear-block
+    :editor/kill-line-before
+    :editor/kill-line-after
+    :editor/beginning-of-block
+    :editor/end-of-block
+    :editor/forward-word
+    :editor/backward-word
+    :editor/forward-kill-word
+    :editor/backward-kill-word]
+
+   :shortcut.category/block-selection
+   ^{:doc "Block selection (press Esc to quit selection)"}
+   [:editor/open-edit
+    :editor/select-block-up
+    :editor/select-block-down
+    :editor/delete-selection]
+
+   :shortcut.category/toggle
+   ^{:doc "Toggle"}
+   [:ui/toggle-help
+    :ui/toggle-new-block
+    :ui/toggle-wide-mode
+    :ui/toggle-document-mode
+    :ui/toggle-brackets
+    :ui/toggle-theme
+    :ui/toggle-right-sidebar
+    :ui/toggle-settings
+    :ui/toggle-contents]
+
+   :shortcut.category/others
+   ^{:doc "Others"}
+   [:go/journals
+    :search/re-index
+    :graph/re-index
+    :auto-complete/prev
+    :auto-complete/next
+    :auto-complete/complete
+    :date-picker/prev-day
+    :date-picker/next-day
+    :date-picker/prev-week
+    :date-picker/next-week
+    :date-picker/complete]})

+ 67 - 47
src/main/frontend/modules/shortcut/core.cljs

@@ -1,39 +1,16 @@
 (ns frontend.modules.shortcut.core
   (:require [clojure.string :as str]
-            [frontend.modules.shortcut.binding :as binding]
-            [frontend.modules.shortcut.handler :refer [handler]]
-            [frontend.state :as state]
+            [frontend.handler.notification :as notification]
+            [frontend.modules.shortcut.data-helper :as dh]
             [frontend.util :as util]
             [goog.events :as events]
             [goog.ui.KeyboardShortcutHandler.EventType :as EventType]
-            [lambdaisland.glogi :as log])
-  (:import [goog.ui KeyboardShortcutHandler]
-           [goog.events KeyCodes]))
-
-(def installed (atom []))
-;; (def binding-profile (atom [binding/default binding/custom]))
-(def binding-profile (atom [binding/default]))
-
-(defn- mod-key [shortcut]
-  (str/replace shortcut #"(?i)mod"
-               (if util/mac? "meta" "ctrl")))
-(defn shortcut-binding
-  [id]
-  (let [shortcut (or (state/get-shortcut id)
-                     (get (apply merge @binding-profile) id))]
-    (cond
-      (nil? shortcut)
-      (log/error :shortcut/binding-not-found {:id id})
-
-      (false? shortcut)
-      (log/debug :shortcut/disabled {:id id})
-
-      :else
-      (->>
-       (if (string? shortcut)
-         [shortcut]
-         shortcut)
-       (mapv mod-key)))))
+            [lambdaisland.glogi :as log]
+            [medley.core :as medley])
+  (:import [goog.events KeyCodes]
+           [goog.ui KeyboardShortcutHandler]))
+
+(def *installed (atom {}))
 
 (def global-keys #js
   [KeyCodes/TAB
@@ -42,10 +19,11 @@
    KeyCodes/UP KeyCodes/LEFT KeyCodes/DOWN KeyCodes/RIGHT])
 
 (defn install-shortcut!
-  [shortcut-map {:keys [set-global-keys? prevent-default?]
-                 :or {set-global-keys? true
+  [handler-id {:keys [set-global-keys? prevent-default? state]
+               :or   {set-global-keys? true
                       prevent-default? false}}]
-  (let [handler (new KeyboardShortcutHandler js/window)]
+  (let [shortcut-map (dh/shortcut-map handler-id state)
+        handler      (new KeyboardShortcutHandler js/window)]
      ;; set arrows enter, tab to global
     (when set-global-keys?
       (.setGlobalKeys handler global-keys))
@@ -53,27 +31,69 @@
     (.setAlwaysPreventDefault handler prevent-default?)
 
     ;; register shortcuts
+    ;; TODO add try catch for register conflicts
     (doseq [[id _] shortcut-map]
-      ;; (log/info :shortcut/install-shortcut {:id id :shortcut (shortcut-binding id)})
-      (doseq [k (shortcut-binding id)]
-        (.registerShortcut handler (util/keyname id) k)))
+      ;; (log/info :shortcut/install-shortcut {:id id :shortcut (dh/shortcut-binding id)})
+      (doseq [k (dh/shortcut-binding id)]
+        (try
+          (.registerShortcut handler (util/keyname id) k)
+          (catch js/Object e
+            (log/error :shortcut/register-shortcut {:id id
+                                                    :binding k
+                                                    :error e})
+            (notification/show! (str/join " " [id k (.-message e)]) :error false)))))
 
     (let [f (fn [e]
               (let [dispatch-fn (get shortcut-map (keyword (.-identifier e)))]
-                 ;; trigger fn
+                ;; trigger fn
                 (dispatch-fn e)))
-          unlisten-fn (fn [] (.dispose handler))]
+          install-id (medley/random-uuid)
+          data       {install-id
+                      {:group      handler-id
+                       :dispath-fn f
+                       :handler    handler}}]
 
       (events/listen handler EventType/SHORTCUT_TRIGGERED f)
 
-       ;; return deregister fn
-      (fn []
-        ;; (log/info :shortcut/dispose (into [] (keys shortcut-map)))
-        (unlisten-fn)))))
+      (swap! *installed merge data)
+
+      install-id)))
 
 (defn install-shortcuts!
   []
-  (let [result (->> handler
-                    (map #(install-shortcut! % {}))
-                    doall)]
-    (reset! installed result)))
+  (->> [:shortcut.handler/editor-global
+        :shortcut.handler/global-non-editing-only
+        :shortcut.handler/global-prevent-default]
+       (map #(install-shortcut! % {}))
+       doall))
+
+(defn uninstall-shortcut! [install-id]
+  (let [handler
+        (-> (get @*installed install-id)
+            :handler)]
+    (.dispose ^js handler)
+    (swap! *installed dissoc install-id)))
+
+
+(defn mixin [handler-id]
+  {:did-mount
+   (fn [state]
+     (let [install-id (-> handler-id
+                          (install-shortcut! {:state state}))]
+       (assoc state :shortcut-key install-id)))
+
+   :did-remount (fn [old-state new-state]
+
+                  ;; uninstall
+                  (-> (get old-state :shortcut-key)
+                      uninstall-shortcut!)
+
+                  ;; update new states
+                  (let [install-id (-> handler-id
+                                       (install-shortcut! {:state new-state}))]
+                    (assoc new-state :shortcut-key install-id)))
+   :will-unmount
+   (fn [state]
+     (-> (get state :shortcut-key)
+         uninstall-shortcut!)
+     (dissoc state :shortcut-key))})

+ 110 - 0
src/main/frontend/modules/shortcut/data_helper.cljs

@@ -0,0 +1,110 @@
+(ns frontend.modules.shortcut.data-helper
+  (:require [clojure.string :as str]
+            [frontend.modules.shortcut.config :as config]
+            [frontend.state :as state]
+            [frontend.util :as util]
+            [lambdaisland.glogi :as log]))
+(defonce binding-map
+  (->> (vals config/default-config)
+       (apply merge)
+       (map (fn [[k {:keys [binding]}]]
+              {k (or (state/get-shortcut k) binding)}))
+       (into {})))
+
+(defn- mod-key [shortcut]
+  (str/replace shortcut #"(?i)mod"
+               (if util/mac? "meta" "ctrl")))
+
+(defn shortcut-binding
+  [id]
+  (let [shortcut (get binding-map id)]
+    (cond
+      (nil? shortcut)
+      (log/error :shortcut/binding-not-found {:id id})
+
+      (false? shortcut)
+      (log/debug :shortcut/disabled {:id id})
+
+      :else
+      (->>
+       (if (string? shortcut)
+         [shortcut]
+         shortcut)
+       (mapv mod-key)))))
+
+;; returns a vector to preserve order
+(defn binding-by-category [name]
+  (let [dict (->> (vals config/default-config)
+                  (apply merge)
+                  (map (fn [[k {:keys [i18n]}]]
+                         {k {:binding (get binding-map k)
+                             :i18n    i18n}}))
+                  (into {}))]
+    (->> (config/category name)
+         (mapv (fn [k] [k (k dict)])))))
+
+(defn shortcut-map
+  ([handler-id]
+   (shortcut-map handler-id nil))
+  ([handler-id state]
+   (let [raw       (get config/default-config handler-id)
+         handler-m (->> raw
+                        (map (fn [[k {:keys [fn]}]]
+                               {k fn}))
+                        (into {}))
+         before    (-> raw meta :before)]
+     (cond->> handler-m
+       state  (reduce-kv (fn [r k handle-fn]
+                           (assoc r k (partial handle-fn state)))
+                         {})
+       before (reduce-kv (fn [r k v]
+                           (assoc r k (before v)))
+                         {})))))
+
+(defn decorate-namespace [k]
+  (let [n (name k)
+        ns (namespace k)]
+    (keyword (str "shortcut." ns) n)))
+
+(defn desc-helper []
+  (->> (vals config/default-config)
+       (apply merge)
+       (map (fn [[k {:keys [desc]}]]
+              {(decorate-namespace k) desc}))
+       (into {})))
+
+(defn category-helper []
+  (->> config/category
+       (map (fn [[k v]]
+              {k (:doc (meta v))}))
+       (into {})))
+
+(defn decorate-binding [binding]
+  (-> binding
+      (str/replace "mod" (if util/mac? "cmd" "ctrl"))
+      (str/replace "alt" (if util/mac? "opt" "alt"))
+      (str/replace "shift+/" "?")
+      (str/lower-case)))
+
+(defn binding-for-display [k binding]
+  (cond
+    (false? binding)
+    (cond
+      (and util/mac? (= k :editor/kill-line-after))
+      "disabled (system default: ctrl+k)"
+      (and util/mac? (= k :editor/beginning-of-block))
+      "disabled (system default: ctrl+a)"
+      (and util/mac? (= k :editor/end-of-block))
+      "disabled (system default: ctrl+e)"
+      (and util/mac? (= k :editor/backward-kill-word))
+      "disabled (system default: opt+delete)"
+      :else
+      "disabled")
+
+    (string? binding)
+    (decorate-binding binding)
+
+    :else
+    (->> binding
+         (map decorate-binding)
+         (str/join " | "))))

+ 176 - 0
src/main/frontend/modules/shortcut/dict.cljs

@@ -0,0 +1,176 @@
+(ns frontend.modules.shortcut.dict
+  (:require [frontend.modules.shortcut.data-helper :as dh])
+  (:require-macros [frontend.modules.shortcut.macro :refer [shortcut-dict]]))
+
+(def dict
+  (shortcut-dict
+   (dh/desc-helper)
+   (dh/category-helper)
+   {:zh-CN
+    {:shortcut.category/formatting            "格式化"
+     :shortcut.editor/indent                  "缩进块标签"
+     :shortcut.editor/outdent                 "取消缩进块"
+     :shortcut.editor/move-block-up           "向上移动块"
+     :shortcut.editor/move-block-down         "向下移动块"
+     :shortcut.editor/new-block               "创建块"
+     :shortcut.editor/new-line                "块中新建行"
+     :shortcut.editor/zoom-in                 "聚焦"
+     :shortcut.editor/zoom-out                "退出聚焦"
+     :shortcut.editor/follow-link             "跟随光标下的链接"
+     :shortcut.editor/open-link-in-sidebar    "在侧边栏打开"
+     :shortcut.editor/expand-block-children   "展开"
+     :shortcut.editor/collapse-block-children "折叠"
+     :shortcut.editor/select-block-up         "选择上方的块"
+     :shortcut.editor/select-block-down       "选择下方的块"
+     :shortcut.editor/select-all-blocks       "选择所有块"
+     :shortcut.ui/toggle-help                 "显示/关闭帮助"
+     :shortcut.git/commit                     "提交消息"
+     :shortcut.go/search                      "全文搜索"
+     :shortcut.go/search-in-page              "在当前页面搜索"
+     :shortcut.ui/toggle-document-mode        "切换文档模式"
+     :shortcut.ui/toggle-contents             "打开/关闭目录"
+     :shortcut.ui/toggle-theme                "在暗色/亮色主题之间切换"
+     :shortcut.ui/toggle-right-sidebar        "启用/关闭右侧栏"
+     :shortcut.ui/toggle-settings             "显示/关闭设置"
+     :shortcut.ui/toggle-new-block            "切换 Enter/Alt+Enter 以插入新块"
+     :shortcut.go/journals                    "跳转到日记"
+     ;; TODO translate those in fr/de/etc..
+     :shortcut.category/basics                "基础操作"
+     :shortcut.category/navigating            "移动"
+     :shortcut.category/block-editing         "块编辑基本"
+     :shortcut.category/block-command-editing "块编辑文本操作"
+     :shortcut.category/block-selection       "块选择操作"
+     :shortcut.category/toggle                "切换"
+     :shortcut.category/others                "其他"
+     :shortcut.ui/toggle-wide-mode            "切换宽屏模式"
+     :shortcut.ui/toggle-brackets             "切换是否显示括号"
+     :shortcut.ui/fold                        "折叠块(非编辑状态)"
+     :shortcut.ui/un-fold                     "展开块(非编辑状态)"
+     :shortcut.search/re-index                "重新建立搜索索引"
+     :shortcut.graph/re-index                 "重新建立图库索引"
+     :shortcut.editor/bold                    "粗体"
+     :shortcut.editor/italics                 "斜体"
+     :shortcut.editor/insert-link             "Html 链接"
+     :shortcut.editor/highlight               "高亮"
+     :shortcut.editor/undo                    "撤销"
+     :shortcut.editor/redo                    "重做"
+     :shortcut.editor/copy                    "复制"
+     :shortcut.editor/cut                     "剪切"
+     :shortcut.editor/up                      "向上移动光标 / 向上选择"
+     :shortcut.editor/down                    "向下移动光标 / 向下选择"
+     :shortcut.editor/left                    "向左移动光标 / 向左选择"
+     :shortcut.editor/right                   "向右移动光标 / 向右选择"
+     :shortcut.editor/backspace               "向左删除"
+     :shortcut.editor/delete                  "向右删除"
+     :shortcut.editor/cycle-todo              "切换TODO状态"
+     :shortcut.editor/clear-block             "清除块内容"
+     :shortcut.editor/kill-line-before        "删除光标右侧行"
+     :shortcut.editor/kill-line-after         "删除光标左侧行"
+     :shortcut.editor/beginning-of-block      "移动光标到块开始位置"
+     :shortcut.editor/end-of-block            "移动光标到块末尾"
+     :shortcut.editor/forward-word            "光标像后移动一个单词"
+     :shortcut.editor/backward-word           "光标向前移动一个单词"
+     :shortcut.editor/forward-kill-word       "像后删除一个单词"
+     :shortcut.editor/backward-kill-word      "像前删除一个单词"
+     :shortcut.editor/open-edit               "编辑选中块"
+     :shortcut.editor/delete-selection        "删除选中块"}
+    :zh-Hant
+    {:shortcut.editor/indent                  "縮進塊標簽"
+     :shortcut.editor/outdent                 "取消縮進塊"
+     :shortcut.editor/move-block-up           "向上移動塊"
+     :shortcut.editor/move-block-down         "向下移動塊"
+     :shortcut.editor/new-block               "創建塊"
+     :shortcut.editor/new-line                "塊中新建行"
+     :shortcut.editor/zoom-in                 "聚焦"
+     :shortcut.editor/zoom-out                "推出聚焦"
+     :shortcut.editor/follow-link             "跟隨光標下的鏈接"
+     :shortcut.editor/open-link-in-sidebar    "在側邊欄打開"
+     :shortcut.editor/expand-block-children   "展開"
+     :shortcut.editor/collapse-block-children "折疊"
+     :shortcut.editor/select-block-up         "選擇上方的塊"
+     :shortcut.editor/select-block-down       "選擇下方的塊"
+     :shortcut.editor/select-all-blocks       "選擇所有塊"
+     :shortcut.ui/toggle-help                 "顯示/關閉幫助"
+     :shortcut.git/commit                     "提交消息"
+     :shortcut.go/search                      "全文搜索"
+     :shortcut.ui/toggle-document-mode        "切換文檔模式"
+     :shortcut.ui/toggle-theme                "“在暗色/亮色主題之間切換”"
+     :shortcut.ui/toggle-right-sidebar        "啟用/關閉右側欄"
+     :shortcut.ui/toggle-new-block            "切換 Enter/Alt+Enter 以插入新塊"
+     :shortcut.go/journals                    "跳轉到日記"
+     :shortcut.category/formatting            "格式化"}
+    :de
+    {:shortcut.editor/indent                  "Block einrücken"
+     :shortcut.editor/outdent                 "Block ausrücken"
+     :shortcut.editor/move-block-up           "Block nach oben verschieben"
+     :shortcut.editor/move-block-down         "Block nach unten verschieben"
+     :shortcut.editor/new-block               "Neuen Block erstellen"
+     :shortcut.editor/new-line                "Neue Zeile innerhalb des Blocks erstellen"
+     :shortcut.editor/zoom-in                 "Heranzoomen"
+     :shortcut.editor/zoom-out                "Herauszoomen"
+     :shortcut.editor/follow-link             "Link unter dem Cursor folgen"
+     :shortcut.editor/open-link-in-sidebar    "Link in Seitenleiste öffnen"
+     :shortcut.editor/expand-block-children   "Erweitern"
+     :shortcut.editor/collapse-block-children "Zusammenklappen"
+     :shortcut.editor/select-block-up         "Block oberhalb auswählen"
+     :shortcut.ui/toggle-help                 "Hilfe aktivieren"
+     :shortcut.go/search                      "Volltextsuche"
+     :shortcut.ui/toggle-document-mode        "Dokumentenmodus umschalten"
+     :shortcut.ui/toggle-theme                "Umschalten zwischen dunklem/hellem Thema"
+     :shortcut.ui/toggle-right-sidebar        "Rechte Seitenleiste umschalten"
+     :shortcut.ui/toggle-new-block            "Umschalten von Enter/Alt+Enter zum Einfügen eines neuen Blocks"
+     :shortcut.go/journals                    "Zu Journalen springen"
+     :shortcut.git/commit                     "Git Commit-Nachricht"
+     :shortcut.editor/select-block-down       "Block unterhalb auswählen"
+     :shortcut.editor/select-all-blocks       "Alle Blöcke auswählen"
+     :shortcut.category/formatting            "Formatierung"}
+    :fr
+    {:shortcut.editor/indent                  "Indenter un Bloc vers la droite"
+     :shortcut.editor/outdent                 "Indenter un Bloc vers la gauche"
+     :shortcut.editor/move-block-up           "Déplacer un bloc au dessus"
+     :shortcut.editor/move-block-down         "Déplacer un bloc en dessous"
+     :shortcut.editor/new-block               "Créer un nouveau bloc"
+     :shortcut.editor/new-line                "Aller à la ligne dans un bloc"
+     :shortcut.editor/zoom-in                 "Zoomer"
+     :shortcut.editor/zoom-out                "Dézoomer"
+     :shortcut.editor/follow-link             "Suivre le lien sous le curseur"
+     :shortcut.editor/open-link-in-sidebar    "Ouvrir le lien dans la barre latérale"
+     :shortcut.editor/expand-block-children   "Etendre"
+     :shortcut.editor/collapse-block-children "Réduire"
+     :shortcut.editor/select-block-up         "Sélectionner le bloc au dessus"
+     :shortcut.editor/select-block-down       "Sélectionner le bloc en dessous"
+     :shortcut.editor/select-all-blocks       "Sélectionner tous les blocs"
+     :shortcut.ui/toggle-help                 "Afficher l'aide"
+     :shortcut.git/commit                     "Message de commit Git"
+     :shortcut.go/search                      "Recherche globale dans le texte"
+     :shortcut.ui/toggle-document-mode        "Intervertir le mode document"
+     :shortcut.ui/toggle-theme                "Intervertir le thème foncé/clair"
+     :shortcut.ui/toggle-right-sidebar        "Afficher/cacher la barre latérale"
+     :shortcut.ui/toggle-new-block            "Activer Entreée ou Alt+Enter pour insérer un bloc"
+     :shortcut.go/journals                    "Aller au Journal"
+     :shortcut.category/formatting            "Formats"}
+    :af
+    {:shortcut.editor/indent                  "Ingekeepte blok oortjie"
+     :shortcut.editor/outdent                 "Oningekeepte blok"
+     :shortcut.editor/move-block-up           "Skuif Blok Boontoe"
+     :shortcut.editor/move-block-down         "Skuif Blok Ondertoe"
+     :shortcut.editor/new-block               "Skep 'n nuwe blok"
+     :shortcut.editor/new-line                "Nuwe lyn in blok"
+     :shortcut.editor/zoom-in                 "Zoem in"
+     :shortcut.editor/zoom-out                "Zoem uit"
+     :shortcut.editor/follow-link             "Volg die skakel onder die wyser"
+     :shortcut.editor/open-link-in-sidebar    "Maak skakel in kantlys oop"
+     :shortcut.editor/expand-block-children   "Brei uit"
+     :shortcut.editor/collapse-block-children "Vou in"
+     :shortcut.editor/select-block-up         "Kies blok bo"
+     :shortcut.editor/select-block-down       "Kies blok onder"
+     :shortcut.editor/select-all-blocks       "Kies alle blokke"
+     :shortcut.ui/toggle-help                 "Wissel help"
+     :shortcut.git/commit                     "Jou git stoor boodskap"
+     :shortcut.go/search                      "Volteks soek"
+     :shortcut.ui/toggle-document-mode        "Wissel dokument modus"
+     :shortcut.go/journals                    "Spring na joernale"
+     :shortcut.category/formatting            "Formatering"
+     :shortcut.ui/toggle-theme                "Wissel tussen donker/lig temas"
+     :shortcut.ui/toggle-right-sidebar        "Wissel regter sybalk"
+     :shortcut.ui/toggle-new-block            "Wissel Enter/Alt+enter vir die byvoeging van nuwe blokke"}}))

+ 0 - 94
src/main/frontend/modules/shortcut/handler.cljs

@@ -1,94 +0,0 @@
-(ns frontend.modules.shortcut.handler
-  (:require [frontend.components.commit :as commit]
-            [frontend.handler.config :as config-handler]
-            [frontend.handler.editor :as editor-handler]
-            [frontend.handler.git :as git-handler]
-            [frontend.handler.repo :as repo-handler]
-            [frontend.handler.route :as route-handler]
-            [frontend.handler.search :as search-handler]
-            [frontend.handler.ui :as ui-handler]
-            [frontend.handler.web.nfs :as nfs-handler]
-            [frontend.modules.shortcut.mixin :refer [before] :as m]
-            [frontend.state :as state]
-            [frontend.handler.history :as history]))
-
-(def editing-only-prevent-default
-  (before
-   m/enable-when-editing-mode!
-   {:editor/backspace editor-handler/editor-backspace
-    :editor/delete editor-handler/editor-delete
-    :editor/indent (editor-handler/keydown-tab-handler :right)
-    :editor/outindent (editor-handler/keydown-tab-handler :left)
-    :editor/new-block editor-handler/keydown-new-block-handler
-    :editor/new-line editor-handler/keydown-new-line-handler
-    :editor/zoom-in  editor-handler/zoom-in!
-    :editor/zoom-out  editor-handler/zoom-out!
-    :editor/cycle-todo editor-handler/cycle-todo!
-    :editor/expand-block-children editor-handler/expand!
-    :editor/collapse-block-children editor-handler/collapse!
-    :editor/follow-link editor-handler/follow-link-under-cursor!
-    :editor/open-link-in-sidebar editor-handler/open-link-in-sidebar!
-    :editor/bold editor-handler/bold-format!
-    :editor/italics editor-handler/italics-format!
-    :editor/highlight editor-handler/highlight-format!
-    :editor/insert-link editor-handler/html-link-format!
-    :editor/select-all-blocks editor-handler/select-all-blocks!
-    :editor/move-block-up (editor-handler/move-up-down true)
-    :editor/move-block-down (editor-handler/move-up-down false)
-    :editor/clear-block editor-handler/clear-block-content!
-    :editor/kill-line-before editor-handler/kill-line-before!
-    :editor/kill-line-after editor-handler/kill-line-after!
-    :editor/beginning-of-block editor-handler/beginning-of-block
-    :editor/end-of-block editor-handler/end-of-block
-    :editor/forward-word editor-handler/cursor-forward-word
-    :editor/backward-word editor-handler/cursor-backward-word
-    :editor/backward-kill-word editor-handler/backward-kill-word
-    :editor/forward-kill-word editor-handler/forward-kill-word}))
-
-(def handler
-  [;; global editor shortcut
-   {:editor/up (editor-handler/shortcut-up-down :up)
-    :editor/down (editor-handler/shortcut-up-down :down)
-    :editor/left (editor-handler/shortcut-left-right :left)
-    :editor/right (editor-handler/shortcut-left-right :right)
-    :editor/open-edit (partial editor-handler/open-selected-block! :right)
-    :editor/select-block-up (editor-handler/on-select-block :up)
-    :editor/select-block-down (editor-handler/on-select-block :down)
-    :editor/copy editor-handler/shortcut-copy
-    :editor/cut editor-handler/shortcut-cut
-    :editor/delete-selection editor-handler/delete-selection
-    :editor/save editor-handler/save!
-    :editor/undo history/undo!
-    :editor/redo history/redo!}
-
-   ;; global
-   (before
-    m/prevent-default-behavior
-    {:ui/toggle-brackets config-handler/toggle-ui-show-brackets!
-     :go/search route-handler/go-to-search!
-     :go/search-page #(route-handler/go-to-search! :page)
-     :go/journals route-handler/go-to-journals!
-
-     :search/re-index search-handler/rebuild-indices!
-     :graph/re-index #(repo-handler/re-index! nfs-handler/rebuild-index!)})
-
-   ;; non-editing only
-   (before
-    m/enable-when-not-editing-mode!
-    {:editor/toggle-document-mode state/toggle-document-mode!
-     :editor/toggle-settings ui-handler/toggle-settings-modal!
-
-     :editor/open-block-first (editor-handler/open-block! true)
-     :editor/open-block-last (editor-handler/open-block! false)
-
-     :ui/toggle-right-sidebar ui-handler/toggle-right-sidebar!
-     :ui/toggle-help ui-handler/toggle-help!
-     :ui/toggle-theme state/toggle-theme!
-     :ui/toggle-new-block state/toggle-new-block-shortcut!
-     :ui/show-contents ui-handler/toggle-contents!
-     :ui/toggle-wide-mode ui-handler/toggle-wide-mode!
-     ;; :ui/toggle-between-page-and-file route-handler/toggle-between-page-and-file!
-     :ui/fold (editor-handler/on-tab :right)
-     :ui/un-fold (editor-handler/on-tab :left)
-
-     :git/commit (git-handler/show-commit-modal! commit/add-commit-message)})])

+ 10 - 0
src/main/frontend/modules/shortcut/macro.clj

@@ -0,0 +1,10 @@
+(ns frontend.modules.shortcut.macro)
+
+(defmacro shortcut-dict
+  "All docs for EN are generated from :desc field of shortcut default-config map.
+  For all other languages, need manual translation in dict file. "
+  [desc category & maps]
+  `(medley.core/deep-merge
+    {:en ~category}
+    {:en ~desc}
+    ~@maps))

+ 7 - 2
src/main/frontend/routes.cljs

@@ -8,7 +8,8 @@
             [frontend.components.journal :as journal]
             [frontend.components.search :as search]
             [frontend.components.settings :as settings]
-            [frontend.components.external :as external]))
+            [frontend.components.external :as external]
+            [frontend.components.shortcut :as shortcut]))
 
 ;; http://localhost:3000/#?anchor=fn.1
 (def routes
@@ -70,4 +71,8 @@
 
    ["/plugins"
     {:name :plugins
-     :view plugins/installed-page}]])
+     :view plugins/installed-page}]
+
+   ["/helper/shortcut"
+    {:name :shortcut
+     :view shortcut/shortcut}]])

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

@@ -177,7 +177,8 @@
                            (.add indice (bean/->js page)))))
                      indice))))
         (when (seq blocks)
-          (let [blocks-result (db/pull-many '[:db/id :block/uuid :block/format :block/content] (set (map :e blocks)))
+          (let [blocks-result (->> (db/pull-many '[:db/id :block/uuid :block/format :block/content :block/page] (set (map :e blocks)))
+                                   (map (fn [b] (assoc b :block/page (get-in b [:block/page :db/id])))))
                 blocks-to-add-set (->> (filter :added blocks)
                                        (map :e)
                                        (set))

+ 9 - 4
src/main/frontend/search/browser.cljs

@@ -12,20 +12,25 @@
 ;; fuse.js
 
 (defn search-blocks
-  [repo q {:keys [limit]
+  [repo q {:keys [limit page]
             :or {limit 20}
             :as option}]
   (let [indice (or (get-in @indices [repo :blocks])
                    (search-db/make-blocks-indice! repo))
-        result (.search indice q (clj->js {:limit limit}))
+        result
+        (if page
+          (.search indice
+                   (clj->js {:$and [{"page" page} {"content" q}]})
+                   (clj->js {:limit limit}))
+          (.search indice q (clj->js {:limit limit})))
         result (bean/->clj result)]
     (->>
      (map
        (fn [{:keys [item matches] :as block}]
-         (let [{:keys [content uuid]} item]
+         (let [{:keys [content uuid page]} item]
            {:block/uuid uuid
             :block/content content
-            :block/page (:block/page (db/entity [:block/uuid (medley/uuid (str uuid))]))
+            :block/page page
             :search/matches matches}))
        result)
      (remove nil?))))

+ 3 - 2
src/main/frontend/search/db.cljs

@@ -14,11 +14,12 @@
   (nil? (get @indices repo)))
 
 (defn block->index
-  [{:block/keys [uuid content format] :as block}]
+  [{:block/keys [uuid content format page] :as block}]
   (when-let [result (->> (text/remove-level-spaces content format)
                          (text/remove-id-property! format))]
     {:id (:db/id block)
      :uuid (str uuid)
+     :page page
      :content result}))
 
 (defn build-blocks-indice
@@ -32,7 +33,7 @@
   [repo]
   (let [blocks (build-blocks-indice repo)
         indice (fuse. blocks
-                      (clj->js {:keys ["uuid" "content"]
+                      (clj->js {:keys ["uuid" "content" "page"]
                                 :shouldSort true
                                 :tokenize true
                                 :minMatchCharLength 1

+ 16 - 13
src/main/frontend/state.cljs

@@ -45,9 +45,6 @@
     :search/mode :global
     :search/result nil
 
-    ;; custom shortcuts
-    :shortcuts {:editor/new-block "enter"}
-
     ;; modals
     :modal/show? false
 
@@ -70,7 +67,8 @@
 
     :github/contents {}
     :config {}
-    :editor/code-mode? false
+    :block/component-editing-mode? false
+    :editor/draw-mode? false
     :editor/show-page-search? false
     :editor/show-page-search-hashtag? false
     :editor/show-date-picker? false
@@ -128,7 +126,9 @@
     :graph/syncing? false
 
     ;; copied blocks
-    :copy/blocks {:copy/content nil :copy/block-tree nil}}))
+    :copy/blocks {:copy/content nil :copy/block-tree nil}
+
+    :date-picker/date nil}))
 
 (defn get-route-match
   []
@@ -475,8 +475,7 @@
 
 (defn set-editor-show-page-search!
   [value]
-  (set-state! :editor/show-page-search? value)
-  (set-state! :editor/show-page-search-hashtag? false))
+  (set-state! :editor/show-page-search? value))
 
 (defn get-editor-show-page-search?
   []
@@ -669,7 +668,7 @@
                                         ; FIXME: No need to call `distinct`?
                                           (distinct))))
     (open-right-sidebar!)
-    (when-let [elem (gdom/getElement "right-sidebar-container")]
+    (when-let [elem (gdom/getElementByClass "cp__right-sidebar-scollable")]
       (util/scroll-to elem 0))))
 
 (defn sidebar-remove-block!
@@ -766,6 +765,10 @@
   (set-state! :ui/theme theme)
   (storage/set :ui/theme theme))
 
+(defn dark?
+  []
+  (= "dark" (:ui/theme @state)))
+
 (defn set-editing-block-dom-id!
   [block-dom-id]
   (set-state! :editor/block-dom-id block-dom-id))
@@ -1220,13 +1223,13 @@
   [args]
   (set-state! :editor/args args))
 
-(defn code-mode?
+(defn block-component-editing?
   []
-  (:editor/code-mode? @state))
+  (:block/component-editing-mode? @state))
 
-(defn go-to-code-mode!
-  []
-  (set-state! :editor/code-mode? true))
+(defn set-block-component-editing-mode!
+  [value]
+  (set-state! :block/component-editing-mode? value))
 
 (defn get-editor-args
   []

+ 49 - 33
src/main/frontend/text.cljs

@@ -51,23 +51,32 @@
      (remove string/blank?)
      (map string/trim))))
 
+(defn- not-matched-nested-pages
+  [s]
+  (and (string? s)
+       (> (count (re-seq #"\[\[" s))
+          (count (re-seq #"\]\]" s)))))
+
 (defn- concat-nested-pages
   [coll]
-  (loop [coll coll
-         result []]
-    (if (seq coll)
-      (let [item (first coll)]
-        (if (= item "]]")
-          (recur (rest coll)
-                 (conj
-                  (vec (butlast result))
-                  (str (last result) item)))
-          (recur (rest coll) (conj result item))))
-      result)))
+  (first
+   (reduce (fn [[acc not-matched-s] s]
+             (cond
+               (and not-matched-s (= s "]]"))
+               [(conj acc (str not-matched-s s)) nil]
+
+               not-matched-s
+               [acc (str not-matched-s s)]
+
+               (not-matched-nested-pages s)
+               [acc s]
+
+               :else
+               [(conj acc s) not-matched-s])) [[] nil] coll)))
 
 (defn split-page-refs-without-brackets
   ([s]
-   (split-page-refs-without-brackets s false))
+   (split-page-refs-without-brackets s true))
   ([s comma?]
    (cond
      (and (string? s)
@@ -75,8 +84,12 @@
             (or (re-find page-ref-re s)
                 (re-find (if comma? #"[\,|,|#]+" #"#") s)))
      (let [result (->> (string/split s page-ref-re-2)
-                       (remove string/blank?)
+                       (map (fn [s] (if (string/ends-with? (string/trimr s) "]],")
+                                     (let [s (string/trimr s)]
+                                       (subs s 0 (dec (count s))))
+                                     s)))
                        concat-nested-pages
+                       (remove string/blank?)
                        (mapcat (fn [s]
                                  (if (page-ref? s)
                                    [(page-ref-un-brackets! s)]
@@ -126,22 +139,11 @@
      :else
      (remove-level-space-aux! text (config/get-block-pattern format) space?))))
 
-(defn append-newline-after-level-spaces
-  [text format]
-  (if-not (string/blank? text)
-    (let [pattern (util/format
-                   "^[%s]+\\s?\n?"
-                   (config/get-block-pattern format))
-          matched-text (re-find (re-pattern pattern) text)]
-      (if matched-text
-        (string/replace-first text matched-text (str (string/trimr matched-text) "\n"))
-        text))))
-
 ;; properties
 
 (def built-in-properties
   (set/union
-   #{:id :custom-id :background-color :heading :collapsed :created-at :last-modified-at :created_at :last_modified_at}
+   #{:id :custom_id :custom-id :background-color :heading :collapsed :created-at :last-modified-at :created_at :last_modified_at}
    (set (map keyword config/markers))))
 
 (defn properties-built-in?
@@ -217,14 +219,21 @@
       content)))
 
 (defn build-properties-str
-  [format properties]
-  (when (seq properties)
-    (let [org? (= format :org)
-          kv-format (if org? ":%s: %s" "%s:: %s")
-          full-format (if org? ":PROPERTIES:\n%s\n:END:\n" "%s\n")
-          properties-content (->> (map (fn [[k v]] (util/format kv-format k v)) properties)
-                                  (string/join "\n"))]
-      (util/format full-format properties-content))))
+  ([format properties]
+   (build-properties-str format properties false))
+  ([format properties front-matter?]
+   (when (seq properties)
+     (let [org? (= format :org)
+           [kv-format wrapper] (cond
+                                 org?
+                                 [":%s: %s" ":PROPERTIES:\n%s\n:END:"]
+                                 front-matter?
+                                 ["%s: %s" "---\n%s\n---"]
+                                 :else
+                                 ["%s:: %s" "%s"])
+           properties-content (->> (map (fn [[k v]] (util/format kv-format (name k) v)) properties)
+                                   (string/join "\n"))]
+       (util/format wrapper properties-content)))))
 
 ;; title properties body
 (defn with-built-in-properties
@@ -433,3 +442,10 @@
 (defn image-link?
   [img-formats s]
   (some (fn [fmt] (re-find (re-pattern (str "(?i)\\." fmt "(?:\\?([^#]*))?(?:#(.*))?$")) s)) img-formats))
+
+(defn properties-block?
+  [block]
+  (and
+   (vector? block)
+   (contains? #{"Property_Drawer" "Properties"}
+              (first block))))

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

@@ -4,6 +4,7 @@
             ["react-transition-group" :refer [TransitionGroup CSSTransition]]
             ["react-textarea-autosize" :as TextareaAutosize]
             ["react-resize-context" :as Resize]
+            ["react-tippy" :as react-tippy]
             [frontend.util :as util]
             [frontend.mixins :as mixins]
             [frontend.handler.notification :as notification-handler]
@@ -23,6 +24,7 @@
 (defonce textarea (r/adapt-class (gobj/get TextareaAutosize "default")))
 (def resize-provider (r/adapt-class (gobj/get Resize "ResizeProvider")))
 (def resize-consumer (r/adapt-class (gobj/get Resize "ResizeConsumer")))
+(def Tippy (r/adapt-class (gobj/get react-tippy "Tooltip")))
 
 (rum/defc ls-textarea < rum/reactive
   [{:keys [on-change] :as props}]
@@ -314,20 +316,14 @@
   (rum/with-context [[t] i18n/*tongue-context*]
     (rum/fragment
      body
-     [:a.fade-link.text-link.font-bold.text-4xl
-      {:on-click on-load
-       :disabled (not has-more)
-       :class (when (not has-more) "cursor-not-allowed ")}
-      (t (if has-more :page/earlier :page/no-more-journals))])))
+     (when has-more
+       [:a.fade-link.text-link.font-bold.text-4xl
+        {:on-click on-load}
+        (t :page/earlier)]))))
 
 (rum/defcs auto-complete <
   (rum/local 0 ::current-idx)
-  (mixins/shortcuts
-   #(shortcut/install-shortcut! % {})
-   :shortcut-listener/auto-complete
-   {:auto-complete/prev ui-handler/auto-complete-prev
-    :auto-complete/next ui-handler/auto-complete-next
-    :auto-complete/complete ui-handler/auto-complete-complete})
+  (shortcut/mixin :shortcut.handler/auto-complete)
   [state matched {:keys [on-chosen
                          on-shift-chosen
                          on-enter
@@ -369,18 +365,6 @@
       {:class       (if on? (if small? "translate-x-4" "translate-x-5") "translate-x-0")
        :aria-hidden "true"}]]]))
 
-(defn tooltip
-  ([label children]
-   (tooltip label children {}))
-  ([label children {:keys [label-style]}]
-   [:div.Tooltip {:style {:display "inline"}}
-    [:div (cond->
-           {:class "Tooltip__label"}
-            label-style
-            (assoc :style label-style))
-     label]
-    children]))
-
 (defonce modal-show? (atom false))
 (rum/defc modal-overlay
   [state]
@@ -585,3 +569,8 @@
                 selected
                 (assoc :selected selected))
       label])])
+
+(rum/defc tippy
+  [opts child]
+  (Tippy (merge {:arrow true} opts)
+         child))

+ 4 - 58
src/main/frontend/ui/date_picker.cljs

@@ -5,12 +5,10 @@
    [cljs-time.predicates :refer [sunday?]]
    [cljs-time.format     :refer [parse unparse formatters formatter]]
    [frontend.util          :refer [deref-or-value now->utc]]
-   [frontend.mixins :as mixins]
+   [frontend.modules.shortcut.core :as shortcut]
    [frontend.util :as util]
    [frontend.state :as state]
-   [goog.object :as gobj]
-   [clojure.string :as string]
-   [frontend.modules.shortcut.core :as shortcut]))
+   [goog.object :as gobj]))
 
 ;; Adapted from re-com date-picker
 
@@ -88,7 +86,7 @@
 
 ;; ----------------------------------------------------------------------------
 
-(def *internal-model (atom nil))
+(def *internal-model (rum/cursor state/state :date-picker/date))
 
 (defn- main-div-with
   [table-div class style attr]
@@ -181,64 +179,12 @@
                         (constantly true))]
     (merge attributes {:selectable-fn selectable-fn})))
 
-;; TODO: find a better way
-(defn- non-edit-input?
-  []
-  (when-let [elem js/document.activeElement]
-    (and (util/input? elem)
-         (when-let [id (gobj/get elem "id")]
-           (not (string/starts-with? id "edit-block-"))))))
-
-(defn- input-or-select?
-  []
-  (when-let [elem js/document.activeElement]
-    (or (non-edit-input?)
-        (util/select? elem))))
-
-(defn shortcut-complete
-  [state e]
-  (let [{:keys [on-change deadline-or-schedule?]} (last (:rum/args state))]
-    (when (and on-change
-               (not (input-or-select?)))
-      (when-not deadline-or-schedule?
-        (on-change e @*internal-model)))))
-
-(defn shortcut-prev-day
-  [_state e]
-  (when-not (input-or-select?)
-    (util/stop e)
-    (swap! *internal-model inc-date -1)))
-
-(defn shortcut-next-day
-  [_state e]
-  (when-not (input-or-select?)
-    (util/stop e)
-    (swap! *internal-model inc-date 1)))
-
-(defn shortcut-prev-week
-  [_state e]
-  (when-not (input-or-select?)
-    (util/stop e)
-    (swap! *internal-model inc-week -1)))
-
-(defn shortcut-next-week
-  [_state e]
-  (when-not (input-or-select?)
-    (util/stop e)
-    (swap! *internal-model inc-week 1)))
 
 (rum/defc date-picker < rum/reactive
-  (mixins/shortcuts
-   #(shortcut/install-shortcut! % {})
-   :shortcut-listener/date-picker
-   {:date-picker/complete shortcut-complete
-    :date-picker/prev-day shortcut-prev-day
-    :date-picker/next-day shortcut-next-day
-    :date-picker/prev-week shortcut-prev-week
-    :date-picker/next-week shortcut-next-week})
   {:init (fn [state]
            (reset! *internal-model (first (:rum/args state)))
            state)}
+  (shortcut/mixin :shortcut.handler/date-picker)
   [model {:keys [on-change on-switch disabled? start-of-week class style attr]
           :or   {start-of-week (state/get-start-of-week)} ;; Default to Sunday
           :as   args}]

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

@@ -1089,12 +1089,18 @@
          (when (uuid-string? block-id)
            (first (array-seq (js/document.getElementsByClassName block-id))))))))
 
+(defonce windows-reserved-chars #"[\\/:\\*\\?\"<>|]+")
+
+(defn include-windows-reserved-chars?
+  [s]
+  (re-find windows-reserved-chars s))
+
 (defn page-name-sanity
   [page-name]
   (-> page-name
       (string/replace #"/" ".")
       ;; Windows reserved path characters
-      (string/replace #"[\\/:\\*\\?\"<>|]+" "_")))
+      (string/replace windows-reserved-chars "_")))
 
 (defn lowercase-first
   [s]
@@ -1141,7 +1147,10 @@
 ;; fs
 (defn get-file-ext
   [file]
-  (last (string/split file #"\.")))
+  (and
+   (string? file)
+   (string/includes? file ".")
+   (last (string/split file #"\."))))
 
 (defn get-dir-and-basename
   [path]

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

@@ -1,3 +1,3 @@
 (ns frontend.version)
 
-(defonce version "0.0.2")
+(defonce version "0.0.3")

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

@@ -1,4 +1,4 @@
-(ns ^:no-doc api
+(ns ^:no-doc logseq.api
   (:require [frontend.db :as db]
             [frontend.db.model :as db-model]
             [frontend.db.utils :as db-utils]

+ 130 - 146
src/test/frontend/db/query_dsl_test.cljs

@@ -22,24 +22,18 @@ title: Dec 26th, 2020
 tags: [[page-tag-1]], page-tag-2
 parent: [[child page 1]]
 ---
-## DONE 26-b1 [[page 1]]
-:PROPERTIES:
-:created_at: 1608968448113
-:last_modified_at: 1608968448113
-:prop_a: val_a
-:prop_c: [[page a]], [[page b]], [[page c]]
-:END:
-## LATER 26-b2-modified-later [[page 2]] #tag1
-:PROPERTIES:
-:created_at: 1608968448114
-:last_modified_at: 1608968448120
-:prop_b: val_b
-:END:
-## DONE [#A] 26-b3 [[page 1]]
-:PROPERTIES:
-:created_at: 1608968448115
-:last_modified_at: 1608968448115
-:END:
+- DONE 26-b1 [[page 1]]
+created_at:: 1608968448113
+last_modified_at:: 1608968448113
+prop_a:: val_a
+prop_c:: [[page a]], [[page b]], [[page c]]
+- LATER 26-b2-modified-later [[page 2]] #tag1
+created_at:: 1608968448114
+last_modified_at:: 1608968448120
+prop_b:: val_b
+- DONE [#A] 26-b3 [[page 1]]
+created_at:: 1608968448115
+last_modified_at:: 1608968448115
 "}
                {:file/path "journals/2020_12_27.md"
                 :file/content "---
@@ -47,53 +41,36 @@ title: Dec 27th, 2020
 tags: page-tag-2, [[page-tag-3]]
 parent: [[child page 1]], child page 2
 ---
-## NOW [#A] b1 [[page 1]]
-:PROPERTIES:
-:created_at: 1609052958714
-:last_modified_at: 1609052958714
-:END:
-## LATER [#B] b2-modified-later [[page 2]]
-:PROPERTIES:
-:created_at: 1609052959376
-:last_modified_at: 1609052974285
-:END:
-## b3 [[page 1]]
-:PROPERTIES:
-:created_at: 1609052959954
-:last_modified_at: 1609052959954
-:prop_a: val_a
-:END:
-## b4 [[page 2]]
-:PROPERTIES:
-:created_at: 1609052961569
-:last_modified_at: 1609052961569
-:END:
-## b5
-:PROPERTIES:
-:created_at: 1609052963089
-:last_modified_at: 1609052963089
-:END:"}
+- NOW [#A] b1 [[page 1]]
+created_at:: 1609052958714
+last_modified_at:: 1609052958714
+- LATER [#B] b2-modified-later [[page 2]]
+created_at:: 1609052959376
+last_modified_at:: 1609052974285
+- b3 [[page 1]]
+created_at:: 1609052959954
+last_modified_at:: 1609052959954
+prop_a:: val_a
+- b4 [[page 2]]
+created_at:: 1609052961569
+last_modified_at:: 1609052961569
+- b5
+created_at:: 1609052963089
+last_modified_at:: 1609052963089"}
                {:file/path "journals/2020_12_28.md"
                 :file/content "---
 title: Dec 28th, 2020
 parent: child page 2
 ---
-## 28-b1 [[page 1]]
-:PROPERTIES:
-:created_at: 1609084800000
-:last_modified_at: 1609084800000
-:END:
-## 28-b2-modified-later [[page 2]]
-:PROPERTIES:
-:created_at: 1609084800001
-:last_modified_at: 1609084800020
-:END:
-## 28-b3 [[page 1]]
-:PROPERTIES:
-:created_at: 1609084800002
-:last_modified_at: 1609084800002
-:END:
-"}]]
+- 28-b1 [[page 1]]
+created_at:: 1609084800000
+last_modified_at:: 1609084800000
+- 28-b2-modified-later [[page 2]]
+created_at:: 1609084800001
+last_modified_at:: 1609084800020
+- 28-b3 [[page 1]]
+created_at:: 1609084800002
+last_modified_at:: 1609084800002"}]]
     (repo-handler/parse-files-and-load-to-db! test-db files {:re-render? false})))
 
 (def parse (partial dsl/parse test-db))
@@ -147,7 +124,7 @@ parent: child page 2
     (are [x y] (= (q-count x) y)
       "(property prop_a val_a)"
       {:query '[[?b :block/properties ?prop]
-                [(get ?prop "prop_a") ?v]
+                [(get ?prop :prop_a) ?v]
                 (or
                  [(= ?v "val_a")]
                  [(contains? ?v "val_a")])]
@@ -155,7 +132,7 @@ parent: child page 2
 
       "(property prop_b val_b)"
       {:query '[[?b :block/properties ?prop]
-                [(get ?prop "prop_b") ?v]
+                [(get ?prop :prop_b) ?v]
                 (or
                  [(= ?v "val_b")]
                  [(contains? ?v "val_b")])]
@@ -163,7 +140,7 @@ parent: child page 2
 
       "(and (property prop_b val_b))"
       {:query '[[?b :block/properties ?prop]
-                [(get ?prop "prop_b") ?v]
+                [(get ?prop :prop_b) ?v]
                 (or
                  [(= ?v "val_b")]
                  [(contains? ?v "val_b")])]
@@ -171,7 +148,7 @@ parent: child page 2
 
       "(and (property prop_c \"page c\"))"
       {:query '[[?b :block/properties ?prop]
-                [(get ?prop "prop_c") ?v]
+                [(get ?prop :prop_c) ?v]
                 (or
                  [(= ?v "page c")]
                  [(contains? ?v "page c")])]
@@ -180,44 +157,44 @@ parent: child page 2
       ;; TODO: optimize
       "(and (property prop_c \"page c\") (property prop_c \"page b\"))"
       {:query '([?b :block/properties ?prop]
-                [(get ?prop "prop_c") ?v]
+                [(get ?prop :prop_c) ?v]
                 (or [(= ?v "page c")] [(contains? ?v "page c")])
-                [(get ?prop "prop_c") ?v1]
+                [(get ?prop :prop_c) ?v1]
                 (or [(= ?v1 "page b")] [(contains? ?v1 "page b")]))
        :count 1}
 
       "(or (property prop_c \"page c\") (property prop_b val_b))"
       {:query '(or (and [?b :block/properties ?prop]
-                        [(get ?prop "prop_c") ?v]
+                        [(get ?prop :prop_c) ?v]
                         (or [(= ?v "page c")] [(contains? ?v "page c")]))
                    (and [?b :block/properties ?prop]
-                        [(get ?prop "prop_b") ?v]
+                        [(get ?prop :prop_b) ?v]
                         (or [(= ?v "val_b")] [(contains? ?v "val_b")])))
        :count 2}))
 
-  (testing "TODO queries"
+  (testing "task queries"
     (are [x y] (= (q-count x) y)
-      "(todo now)"
+      "(task now)"
       {:query '[[?b :block/marker ?marker]
                 [(contains? #{"NOW"} ?marker)]]
        :count 1}
 
-      "(todo NOW)"
+      "(task NOW)"
       {:query '[[?b :block/marker ?marker]
                 [(contains? #{"NOW"} ?marker)]]
        :count 1}
 
-      "(todo later)"
+      "(task later)"
       {:query '[[?b :block/marker ?marker]
                 [(contains? #{"LATER"} ?marker)]]
        :count 2}
 
-      "(todo now later)"
+      "(task now later)"
       {:query '[[?b :block/marker ?marker]
                 [(contains? #{"NOW" "LATER"} ?marker)]]
        :count 3}
 
-      "(todo [now later])"
+      "(task [now later])"
       {:query '[[?b :block/marker ?marker]
                 [(contains? #{"NOW" "LATER"} ?marker)]]
        :count 3}))
@@ -290,7 +267,8 @@ parent: child page 2
   (testing "page-property queries"
     (are [x y] (= (q-count x) y)
       "(page-property parent [[child page 1]])"
-      {:query '[[?p :block/properties ?prop]
+      {:query '[[?p :block/name]
+                [?p :block/properties ?prop]
                 [(get ?prop :parent) ?v]
                 (or
                  [(= ?v "child page 1")]
@@ -298,7 +276,8 @@ parent: child page 2
        :count 2}
 
       "(page-property parent \"child page 1\")"
-      {:query '[[?p :block/properties ?prop]
+      {:query '[[?p :block/name]
+                [?p :block/properties ?prop]
                 [(get ?prop :parent) ?v]
                 (or
                  [(= ?v "child page 1")]
@@ -306,7 +285,8 @@ parent: child page 2
        :count 2}
 
       "(and (page-property parent [[child page 1]]) (page-property parent [[child page 2]]))"
-      {:query '([?p :block/properties ?prop]
+      {:query '([?p :block/name]
+                [?p :block/properties ?prop]
                 [(get ?prop :parent) ?v]
                 (or [(= ?v "child page 1")] [(contains? ?v "child page 1")])
                 (or [(= ?v "child page 2")] [(contains? ?v "child page 2")]))
@@ -314,10 +294,12 @@ parent: child page 2
 
       "(or (page-property parent [[child page 1]]) (page-property parent [[child page 2]]))"
       {:query '(or (and
+                    [?p :block/name]
                     [?p :block/properties ?prop]
                     [(get ?prop :parent) ?v]
                     (or [(= ?v "child page 1")] [(contains? ?v "child page 1")]))
                    (and
+                    [?p :block/name]
                     [?p :block/properties ?prop]
                     [(get ?prop :parent) ?v]
                     (or [(= ?v "child page 2")] [(contains? ?v "child page 2")])))
@@ -350,24 +332,25 @@ parent: child page 2
       "(not [[page 1]])"
       {:query '([?b :block/uuid]
                 (not [?b :block/path-refs [:block/name "page 1"]]))
-       :count 8}))
+       :count 25}))
 
   (testing "Between query"
     (are [x y] (= (count-only x) y)
-      "(and (todo now later done) (between [[Dec 26th, 2020]] tomorrow))"
+      "(and (task now later done) (between [[Dec 26th, 2020]] tomorrow))"
       5
 
       ;; between with journal pages
-      "(and (todo now later done) (between [[Dec 27th, 2020]] [[Dec 28th, 2020]]))"
+      "(and (task now later done) (between [[Dec 27th, 2020]] [[Dec 28th, 2020]]))"
       2
 
-      ;; between with created_at
-      "(and (todo now later done) (between created_at [[Dec 26th, 2020]] tomorrow))"
-      5
+      ;; ;; between with created_at
+      ;; "(and (task now later done) (between created_at [[Dec 26th, 2020]] tomorrow))"
+      ;; 5
 
-      ;; between with last_modified_at
-      "(and (todo now later done) (between last_modified_at [[Dec 26th, 2020]] tomorrow))"
-      5))
+      ;; ;; between with last_modified_at
+      ;; "(and (task now later done) (between last_modified_at [[Dec 26th, 2020]] tomorrow))"
+      ;; 5
+      ))
 
   (testing "Nested boolean queries"
     (are [x y] (= (q-count x) y)
@@ -395,7 +378,7 @@ parent: child page 2
                  (or
                   (and [?b :block/path-refs [:block/name "page 1"]])
                   (and [?b :block/path-refs [:block/name "page 2"]]))))
-       :count 11})
+       :count 28})
 
     ;; FIXME: not working
     ;; (are [x y] (= (q-count x) y)
@@ -416,65 +399,66 @@ parent: child page 2
                  (and (not [?b :block/path-refs [:block/name "page 1"]]))))
        :count 5}))
 
-  (testing "sort-by (created_at defaults to desc)"
-    (db/clear-query-state!)
-    (let [result (->> (q "(and (todo now later done)
-                               (sort-by created_at))")
-                      :result
-                      deref
-                      (map #(get-in % [:block/properties "created_at"])))]
-      (is (= result
-             '(1609052959376 1609052958714 1608968448115 1608968448114 1608968448113)))))
-
-  (testing "sort-by (created_at desc)"
-    (db/clear-query-state!)
-    (let [result (->> (q "(and (todo now later done)
-                               (sort-by created_at desc))")
-                      :result
-                      deref
-                      (map #(get-in % [:block/properties "created_at"])))]
-      (is (= result
-             '(1609052959376 1609052958714 1608968448115 1608968448114 1608968448113)))))
-
-  (testing "sort-by (created_at asc)"
-    (db/clear-query-state!)
-    (let [result (->> (q "(and (todo now later done)
-                               (sort-by created_at asc))")
-                      :result
-                      deref
-                      (map #(get-in % [:block/properties "created_at"])))]
-      (is (= result
-             '(1608968448113 1608968448114 1608968448115 1609052958714 1609052959376)))))
-
-  (testing "sort-by (last_modified_at defaults to desc)"
-    (db/clear-query-state!)
-    (let [result (->> (q "(and (todo now later done)
-                               (sort-by last_modified_at))")
-                      :result
-                      deref
-                      (map #(get-in % [:block/properties "last_modified_at"])))]
-      (is (= result
-             '(1609052974285 1609052958714 1608968448120 1608968448115 1608968448113)))))
-
-  (testing "sort-by (last_modified_at desc)"
-    (db/clear-query-state!)
-    (let [result (->> (q "(and (todo now later done)
-                               (sort-by last_modified_at desc))")
-                      :result
-                      deref
-                      (map #(get-in % [:block/properties "last_modified_at"])))]
-      (is (= result
-             '(1609052974285 1609052958714 1608968448120 1608968448115 1608968448113)))))
-
-  (testing "sort-by (last_modified_at desc)"
-    (db/clear-query-state!)
-    (let [result (->> (q "(and (todo now later done)
-                               (sort-by last_modified_at asc))")
-                      :result
-                      deref
-                      (map #(get-in % [:block/properties "last_modified_at"])))]
-      (is (= result
-             '(1608968448113 1608968448115 1608968448120 1609052958714 1609052974285))))))
+  ;; (testing "sort-by (created_at defaults to desc)"
+  ;;   (db/clear-query-state!)
+  ;;   (let [result (->> (q "(and (task now later done)
+  ;;                              (sort-by created_at))")
+  ;;                     :result
+  ;;                     deref
+  ;;                     (map #(get-in % [:block/properties "created_at"])))]
+  ;;     (is (= result
+  ;;            '(1609052959376 1609052958714 1608968448115 1608968448114 1608968448113)))))
+
+  ;; (testing "sort-by (created_at desc)"
+  ;;   (db/clear-query-state!)
+  ;;   (let [result (->> (q "(and (todo now later done)
+  ;;                              (sort-by created_at desc))")
+  ;;                     :result
+  ;;                     deref
+  ;;                     (map #(get-in % [:block/properties "created_at"])))]
+  ;;     (is (= result
+  ;;            '(1609052959376 1609052958714 1608968448115 1608968448114 1608968448113)))))
+
+  ;; (testing "sort-by (created_at asc)"
+  ;;   (db/clear-query-state!)
+  ;;   (let [result (->> (q "(and (todo now later done)
+  ;;                              (sort-by created_at asc))")
+  ;;                     :result
+  ;;                     deref
+  ;;                     (map #(get-in % [:block/properties "created_at"])))]
+  ;;     (is (= result
+  ;;            '(1608968448113 1608968448114 1608968448115 1609052958714 1609052959376)))))
+
+  ;; (testing "sort-by (last_modified_at defaults to desc)"
+  ;;   (db/clear-query-state!)
+  ;;   (let [result (->> (q "(and (todo now later done)
+  ;;                              (sort-by last_modified_at))")
+  ;;                     :result
+  ;;                     deref
+  ;;                     (map #(get-in % [:block/properties "last_modified_at"])))]
+  ;;     (is (= result
+  ;;            '(1609052974285 1609052958714 1608968448120 1608968448115 1608968448113)))))
+
+  ;; (testing "sort-by (last_modified_at desc)"
+  ;;   (db/clear-query-state!)
+  ;;   (let [result (->> (q "(and (todo now later done)
+  ;;                              (sort-by last_modified_at desc))")
+  ;;                     :result
+  ;;                     deref
+  ;;                     (map #(get-in % [:block/properties "last_modified_at"])))]
+  ;;     (is (= result
+  ;;            '(1609052974285 1609052958714 1608968448120 1608968448115 1608968448113)))))
+
+  ;; (testing "sort-by (last_modified_at desc)"
+  ;;   (db/clear-query-state!)
+  ;;   (let [result (->> (q "(and (todo now later done)
+  ;;                              (sort-by last_modified_at asc))")
+  ;;                     :result
+  ;;                     deref
+  ;;                     (map #(get-in % [:block/properties "last_modified_at"])))]
+  ;;     (is (= result
+  ;;            '(1608968448113 1608968448115 1608968448120 1609052958714 1609052974285)))))
+  )
 
 (use-fixtures :once
   {:before (fn []

+ 5 - 1
src/test/frontend/modules/outliner/core_test.cljs

@@ -270,11 +270,12 @@
   (let [start-node (build-block 6 2 3)
         end-node (build-block 11 9 10)
         block-ids [7 8 9 10]]
+    ;; FIXME: not sibling, nothing happens
     (outliner-core/delete-nodes start-node end-node block-ids)
     (let [children-of-2 (->> (build-block 2)
                           (tree/-get-children)
                           (mapv #(-> % :data :block/uuid)))]
-      (is (= [3] children-of-2)))))
+      (is (= [3 6 9] children-of-2)))))
 
 (comment
   (run-test test-delete-nodes))
@@ -336,3 +337,6 @@
 
 (comment
   (run-test test-insert-nodes))
+
+(comment
+  (run-tests))

+ 18 - 32
src/test/frontend/text_test.cljs

@@ -37,13 +37,14 @@
     "foobar" "foobar"
     "foo bar" "foo bar"
     "foo, bar" #{"foo" "bar"}
-    "foo \"bar\"" #{"foo" "bar"}
-    "[[foo]] [[bar]]" #{"foo]] [[bar"}
+    "[[foo]] [[bar]]" #{"foo" "bar"}
     "[[foo]],[[bar]]" #{"foo", "bar"}
     "[[foo]], [[bar]]" #{"foo", "bar"}
     "[[foo]]" #{"foo"}
     "[[nested [[foo]]]]" #{"nested [[foo]]"}
     "[[nested [[foo]]]], [[foo]]" #{"nested [[foo]]" "foo"}
+    "[[nested [[foo]] [[bar]]]], [[foo]]" #{"nested [[foo]] [[bar]]" "foo"}
+    "[[nested [[foo]], [[bar]]]], [[foo]]" #{"nested [[foo]], [[bar]]" "foo"}
     "#tag," #{"tag"}
     "#tag" #{"tag"}
     "#tag1,#tag2" #{"tag1" "tag2"}
@@ -53,27 +54,24 @@
   []
   (testing "markdown"
     (are [x y] (= (text/extract-level-spaces x :markdown) y)
-      "# foobar" "# "
-      "##   foobar" "##   "
-      "#####################   foobar" "#####################   "))
+      "- foobar" "- "
+      "--   foobar" "-- "
+      "---------------------   foobar" "--------------------- "))
   (testing "org mode"
     (are [x y] (= (text/extract-level-spaces x :org) y)
       "* foobar" "* "
-      "**   foobar" "**   "
-      "*********************  foobar" "*********************  ")))
+      "**   foobar" "** "
+      "*********************  foobar" "********************* ")))
 
 (deftest remove-level-spaces
   []
   (testing "markdown"
     (are [x y] (= (text/remove-level-spaces x :markdown true) y)
-      "# foobar" "foobar"
-      "##   foobar" "foobar"
-      "#####################   foobar" "foobar"))
+      "- foobar" "foobar"
+      " - foobar" "foobar"))
   (testing "markdown without spaces between the `#` and title"
     (are [x y] (= (text/remove-level-spaces x :markdown) y)
-      "#foobar" "foobar"
-      "##foobar" "foobar"
-      "#####################foobar" "foobar"))
+      "-foobar" "foobar"))
   (testing "org"
     (are [x y] (= (text/remove-level-spaces x :org true) y)
       "* foobar" "foobar"
@@ -85,18 +83,6 @@
       "**foobar" "foobar"
       "*********************foobar" "foobar")))
 
-(deftest append-newline-after-level-spaces
-  []
-  (are [x y] (= (text/append-newline-after-level-spaces x :markdown) y)
-    "# foobar" "#\nfoobar"
-    "# foobar\nfoo" "#\nfoobar\nfoo"
-    "## foobar\nfoo" "##\nfoobar\nfoo")
-
-  (are [x y] (= (text/append-newline-after-level-spaces x :org) y)
-    "* foobar" "*\nfoobar"
-    "* foobar\nfoo" "*\nfoobar\nfoo"
-    "** foobar\nfoo" "**\nfoobar\nfoo"))
-
 (deftest remove-id-property
   []
   (are [x y] (= (text/remove-id-property! :org x) y)
@@ -113,30 +99,30 @@
       (text/remove-properties! :org "** hello\n:PROPERTIES:\n:x: y\n:END:\n")
       "** hello"
 
-      (text/remove-properties! :org "** hello\n:PROPERTIES:\n:x: y\na:b\n:END:\n")
+      (text/remove-properties! :org "** hello\n:PROPERTIES:\n:x: y\n:a: b\n:END:\n")
       "** hello"))
   (testing "properties with blank lines"
     (are [x y] (= x y)
       (text/remove-properties! :org "** hello\n:PROPERTIES:\n\n:x: y\n:END:\n")
       "** hello"
 
-      (text/remove-properties! :org "** hello\n:PROPERTIES:\n:x: y\n\na:b\n:END:\n")
+      (text/remove-properties! :org "** hello\n:PROPERTIES:\n:x: y\n\n:a: b\n:END:\n")
       "** hello")))
 
 (deftest test-insert-property
   []
   (are [x y] (= x y)
     (text/insert-property! :org "hello" "a" "b")
-    "hello\n:PROPERTIES:\n:a: b\n:END:\n"
+    "hello\n:PROPERTIES:\n:a: b\n:END:"
 
     (text/insert-property! :org "hello" "a" false)
-    "hello\n:PROPERTIES:\n:a: false\n:END:\n"
+    "hello\n:PROPERTIES:\n:a: false\n:END:"
 
-    (text/insert-property! :org "hello\n:PROPERTIES:\n:a: b\n:END:\n" "c" "d")
+    (text/insert-property! :org "hello\n:PROPERTIES:\n:a: b\n:END:" "c" "d")
     "hello\n:PROPERTIES:\n:a: b\n:c: d\n:END:"
 
-    (text/insert-property! :org "hello\n:PROPERTIES:\n:a: b\n:END: world\n" "c" "d")
-    "hello\n:PROPERTIES:\n:c: d\n:END:\n:PROPERTIES:\n:a: b\n:END: world\n"))
+    (text/insert-property! :org "hello\n:PROPERTIES:\n:a: b\n:END:\nworld" "c" "d")
+    "hello\n:PROPERTIES:\n:a: b\n:c: d\n:END:\nworld"))
 
 (deftest test->new-properties
   []

+ 1 - 1
src/test/frontend/util/marker_test.cljs

@@ -1,4 +1,4 @@
-(ns frontend.util.marker_test
+(ns frontend.util.marker-test
   (:require [cljs.test :refer [deftest is are testing]]
             [frontend.util.marker :as marker]))
 

+ 1 - 1
src/test/frontend/util/priority_test.cljs

@@ -1,4 +1,4 @@
-(ns frontend.util.priority_test
+(ns frontend.util.priority-test
   (:require [cljs.test :refer [deftest is are testing]]
             [frontend.util.priority :as priority]))
 

+ 74 - 0
templates/config.edn

@@ -0,0 +1,74 @@
+{;; Currently, we support either "Markdown" or "Org".
+ ;; This can overwrite your global preference so that
+ ;; maybe your personal preferred format is Org but you'd
+ ;; need to use Markdown for some projects.
+ ;; :preferred-format ""
+
+ ;; Preferred workflow style.
+ ;; Value is either ":now" for NOW/LATER style,
+ ;; or ":todo" for TODO/DOING style.
+ :preferred-workflow :now
+
+ ;; Git settings
+ :git-pull-secs 60
+ :git-push-secs 10
+ :git-auto-push true
+
+ ;; The app will ignore those directories or files.
+ ;; E.g. "/archived" "/test.md"
+ :hidden []
+
+ ;; When creating the new journal page, the app will use your template content here.
+ ;; Example for Markdown users: "[[Work]]\n  -\n- [[Family]]\n  -\n"
+ ;; Example for Org mode users: "** [[Work]]\n***\n** [[Family]]\n***\n"
+ :default-templates
+ {:journals ""}
+
+ ;; The app will show those queries in today's journal page,
+ ;; the "NOW" query asks the tasks which need to be finished "now",
+ ;; the "NEXT" query asks the future tasks.
+ :default-queries
+ {:journals
+  [{:title "🔨 NOW"
+    :query [:find (pull ?h [*])
+            :in $ ?start ?today
+            :where
+            [?h :block/marker ?marker]
+            [?h :block/page ?p]
+            [?p :block/journal? true]
+            [?p :block/journal-day ?d]
+            [(>= ?d ?start)]
+            [(<= ?d ?today)]
+            [(contains? #{"NOW" "DOING"} ?marker)]]
+    :inputs [:14d :today]
+    :result-transform (fn [result]
+                        (sort-by (fn [h]
+                                   (get h :block/priority "Z")) result))
+    :collapsed? false}
+   {:title "📅 NEXT"
+    :query [:find (pull ?h [*])
+            :in $ ?start ?next
+            :where
+            [?h :block/marker ?marker]
+            [?h :block/ref-pages ?p]
+            [?p :block/journal? true]
+            [?p :block/journal-day ?d]
+            [(> ?d ?start)]
+            [(< ?d ?next)]
+            [(contains? #{"NOW" "LATER" "TODO"} ?marker)]]
+    :inputs [:today :7d-after]
+    :collapsed? false}]}
+
+ ;; Add your own commands to speedup.
+ ;; E.g. [["js" "Javascript"]]
+ :commands
+ []
+
+ ;; Macros replace texts and will make you more productive.
+ ;; For example:
+ ;; Add this to the macros below:
+ ;; {"poem" "Rose is $1, violet's $2. Life's ordered: Org assists you."}
+ ;; input "{{{poem red,blue}}}"
+ ;; becomes
+ ;; Rose is red, violet's blue. Life's ordered: Org assists you.
+ :macros {}}

+ 5 - 0
templates/contents.md

@@ -0,0 +1,5 @@
+- What's **Contents**?
+    - It's a normal page called [[Contents]], you can use it for:
+    - 1. table of content/index/MOC
+    - 2. pinning/bookmarking favorites pages/blocks (e.g. [[Logseq]])
+    - 3. You can also put many different things, depending on your personal workflow.

+ 5 - 0
templates/contents.org

@@ -0,0 +1,5 @@
+* What's **Contents**?
+** It's a normal page called [[Contents]], you can use it for:
+*** 1. table of content/index/MOC
+*** 2. pinning/bookmarking favorites pages/blocks (e.g. [[Logseq]])
+*** 3. You can also put many different things, depending on your personal workflow.

+ 14 - 0
templates/dummy-notes-en.md

@@ -0,0 +1,14 @@
+---
+title: How to take dummy notes?
+---
+
+- Hello, I'm a block!
+:PROPERTIES:
+:id: 5f713e91-8a3c-4b04-a33a-c39482428e2d
+:END:
+    - I'm a child block!
+    - I'm another child block!
+- Hey, I'm another block!
+:PROPERTIES:
+:id: 5f713ea8-8cba-403d-ac00-9964b1ec7190
+:END:

+ 36 - 0
templates/tutorial-en.md

@@ -0,0 +1,36 @@
+## Hi, welcome to Logseq!
+- Logseq is a _privacy-first_, _open-source_ platform for _knowledge_ sharing and management.
+- This is a 3 minute tutorial on how to use Logseq. Let's get started!
+- (Feel free to edit anything, no change will be saved at this moment. If you do want to persist your work, click the **top-right** corner of the screen to connect Logseq to either Github or local directory.)
+- Here are some tips might be useful.
+#+BEGIN_TIP
+Click to edit any block.
+Type `Enter` to create a new block.
+Type `Shift+Enter` to create a new line.
+Type `/` to show all the commands.
+#+END_TIP
+- 1. Let's create a page called [[How to take dummy notes?]]. You can click it to go to that page, or you can `Shift+Click` to open it in the right sidebar! Now you should see both _Linked References_ and _Unlinked References_.
+- 2. Let's reference some blocks on [[How to take dummy notes?]], you can `Shift+Click` any block reference to open it in the right sidebar. Try making
+some changes on the right sidebar, those referenced blocks will be changed too!
+    - ((5f713e91-8a3c-4b04-a33a-c39482428e2d)) : This is a block reference.
+    - ((5f713ea8-8cba-403d-ac00-9964b1ec7190)) : This is another block reference.
+- 3. Do you support tags?
+    - Of course, this is a #dummy tag.
+- 4. Do you support tasks like todo/doing/done and priorities?
+    - Yes, type `/` and pick your favorite todo keyword or priority (A/B/C).
+    - NOW [#A] A dummy tutorial on "How to take dummy notes?"
+    - LATER [#A] Check out this awesome video by [:a {:href "https://twitter.com/EdTravelling" :target "_blank"} "@EdTravelling"], which shows how to use logseq to open your local directory.
+
+[:div.video-wrapper.mb-4
+        [:iframe
+         {:allowFullScreen "allowfullscreen"
+          :allow
+          "accelerometer; autoplay; encrypted-media; gyroscope"
+        :frameBorder "0"
+        :src "https://www.youtube.com/embed/Afmqowr0qEQ"
+        :height "367"
+        :width "653"}]]
+    - DONE Create a page
+    - CANCELED [#C] Write a page with more than 1000 blocks
+- That's it! You can create more bullets or open a local directory to import some notes now!
+- You can also download our desktop app at https://github.com/logseq/logseq/releases

+ 47 - 16
yarn.lock

@@ -200,9 +200,9 @@
     global-tunnel-ng "^2.7.1"
 
 "@excalidraw/excalidraw@^0.4.2":
-  version "0.4.2"
-  resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.4.2.tgz"
-  integrity sha512-2vX/P9/p+gBvdUsqAbHSiV1yO2sLtG6fqvzWjlWYzRb6AkeNvpe2Wp7UfJOGb+VQmhymBE07be9ACzyA9ib2jw==
+  version "0.4.3"
+  resolved "https://registry.yarnpkg.com/@excalidraw/excalidraw/-/excalidraw-0.4.3.tgz#e1b2cfbafe405a5b0504c5f631b0857e61d5a5de"
+  integrity sha512-AAQ87Q2VFojcBJjOp/Sc2F/y4s9Hb1sB/YmEo4QwKnZUnB3cnn8hPpBIMIIi9z7090pOLunC5ewaiQAirWcCKw==
 
 "@fullhuman/postcss-purgecss@^3.1.3":
   version "3.1.3"
@@ -237,6 +237,11 @@
     "@nodelib/fs.scandir" "2.1.4"
     fastq "^1.6.0"
 
+"@popperjs/core@^2.8.3":
+  version "2.9.2"
+  resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
+  integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
+
 "@sindresorhus/is@^0.14.0":
   version "0.14.0"
   resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz"
@@ -301,6 +306,13 @@
     hex-rgb "^4.1.0"
     postcss-selector-parser "^6.0.2"
 
+"@tippyjs/react@^4.2.5":
+  version "4.2.5"
+  resolved "https://registry.yarnpkg.com/@tippyjs/react/-/react-4.2.5.tgz#9b5837db93a1cac953962404df906aef1a18e80d"
+  integrity sha512-YBLgy+1zznBNbx4JOoOdFXWMLXjBh9hLPwRtq3s8RRdrez2l3tPBRt2m2909wZd9S1KUeKjOOYYsnitccI9I3A==
+  dependencies:
+    tippy.js "^6.3.1"
+
 "@types/expect@^1.20.4":
   version "1.20.4"
   resolved "https://registry.yarnpkg.com/@types/expect/-/expect-1.20.4.tgz"
@@ -4480,6 +4492,11 @@ [email protected]:
     arr-union "^3.1.0"
     extend-shallow "^3.0.2"
 
+popper.js@^1.11.1:
+  version "1.16.1"
+  resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
+  integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
+
 posix-character-classes@^0.1.0:
   version "0.1.1"
   resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz"
@@ -5087,14 +5104,14 @@ randomfill@^1.0.3:
     randombytes "^2.0.5"
     safe-buffer "^5.1.0"
 
-react-dom@^17.0.1:
-  version "17.0.1"
-  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.1.tgz"
-  integrity sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==
+react-dom@^17.0.2:
+  version "17.0.2"
+  resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
+  integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
   dependencies:
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
-    scheduler "^0.20.1"
+    scheduler "^0.20.2"
 
 react-is@^16.8.1:
   version "16.13.1"
@@ -5120,6 +5137,13 @@ react-textarea-autosize@^8.0.1:
     use-composed-ref "^1.0.0"
     use-latest "^1.0.0"
 
+react-tippy@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/react-tippy/-/react-tippy-1.4.0.tgz#e8a8b4085ec985e5c94fe128918b733b588a1465"
+  integrity sha512-r/hM5XK9Ztr2ZY7IWKuRmISTlUPS/R6ddz6PO2EuxCgW+4JBcGZRPU06XcVPRDCOIiio8ryBQFrXMhFMhsuaHA==
+  dependencies:
+    popper.js "^1.11.1"
+
 react-transition-group@^4.3.0:
   version "4.4.1"
   resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz"
@@ -5130,10 +5154,10 @@ react-transition-group@^4.3.0:
     loose-envify "^1.4.0"
     prop-types "^15.6.2"
 
-react@^17.0.1:
-  version "17.0.1"
-  resolved "https://registry.yarnpkg.com/react/-/react-17.0.1.tgz"
-  integrity sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==
+react@^17.0.2:
+  version "17.0.2"
+  resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
+  integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
   dependencies:
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
@@ -5482,10 +5506,10 @@ sax@~1.2.4:
   resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz"
   integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
 
-scheduler@^0.20.1:
-  version "0.20.1"
-  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.1.tgz"
-  integrity sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==
+scheduler@^0.20.2:
+  version "0.20.2"
+  resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
+  integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
   dependencies:
     loose-envify "^1.1.0"
     object-assign "^4.1.1"
@@ -6137,6 +6161,13 @@ timsort@^0.3.0:
   resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz"
   integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
 
+tippy.js@^6.3.1:
+  version "6.3.1"
+  resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.1.tgz#3788a007be7015eee0fd589a66b98fb3f8f10181"
+  integrity sha512-JnFncCq+rF1dTURupoJ4yPie5Cof978inW6/4S6kmWV7LL9YOSEVMifED3KdrVPEG+Z/TFH2CDNJcQEfaeuQww==
+  dependencies:
+    "@popperjs/core" "^2.8.3"
+
 to-absolute-glob@^2.0.0:
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz"

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