Browse Source

Merge branch 'master' into encryption

Tienson Qin 4 years ago
parent
commit
678afea969
46 changed files with 609 additions and 371 deletions
  1. 1 1
      Dockerfile
  2. 1 1
      README.md
  3. 3 0
      externs.js
  4. 4 2
      package.json
  5. 4 2
      src/electron/electron/core.cljs
  6. 2 2
      src/electron/electron/handler.cljs
  7. 33 20
      src/main/frontend/components/block.cljs
  8. 22 2
      src/main/frontend/components/content.cljs
  9. 41 24
      src/main/frontend/components/editor.cljs
  10. 3 2
      src/main/frontend/components/header.cljs
  11. 8 5
      src/main/frontend/components/header.css
  12. 1 4
      src/main/frontend/components/journal.cljs
  13. 0 5
      src/main/frontend/components/page.cljs
  14. 36 1
      src/main/frontend/components/search.cljs
  15. 32 1
      src/main/frontend/components/settings.cljs
  16. 0 1
      src/main/frontend/components/sidebar.cljs
  17. 7 1
      src/main/frontend/date.cljs
  18. 6 2
      src/main/frontend/db.cljs
  19. 3 0
      src/main/frontend/db/conn.cljs
  20. 9 0
      src/main/frontend/db/default.cljs
  21. 2 9
      src/main/frontend/db/model.cljs
  22. 10 4
      src/main/frontend/dicts.cljs
  23. 11 1
      src/main/frontend/external/roam.cljc
  24. 3 3
      src/main/frontend/format/block.cljs
  25. 26 19
      src/main/frontend/fs.cljs
  26. 2 3
      src/main/frontend/fs/bfs.cljs
  27. 5 2
      src/main/frontend/fs/nfs.cljs
  28. 1 1
      src/main/frontend/fs/node.cljs
  29. 1 1
      src/main/frontend/fs/protocol.cljs
  30. 5 3
      src/main/frontend/handler/block.cljs
  31. 83 42
      src/main/frontend/handler/editor.cljs
  32. 1 1
      src/main/frontend/handler/extract.cljs
  33. 2 4
      src/main/frontend/handler/file.cljs
  34. 2 1
      src/main/frontend/handler/image.cljs
  35. 8 5
      src/main/frontend/handler/search.cljs
  36. 23 10
      src/main/frontend/handler/user.cljs
  37. 23 18
      src/main/frontend/history.cljs
  38. 59 75
      src/main/frontend/keyboards.cljs
  39. 0 27
      src/main/frontend/mixins.cljs
  40. 52 40
      src/main/frontend/search.cljs
  41. 23 0
      src/main/frontend/state.cljs
  42. 5 5
      src/main/frontend/ui.cljs
  43. 12 12
      src/main/frontend/util.cljc
  44. 11 1
      src/main/frontend/utils.js
  45. 1 1
      src/main/frontend/version.cljs
  46. 22 7
      yarn.lock

+ 1 - 1
Dockerfile

@@ -1,4 +1,4 @@
-FROM clojure:openjdk-11-tools-deps
+FROM clojure:openjdk-11-tools-deps-1.10.0.442
 
 
 RUN curl -sL https://deb.nodesource.com/setup_15.x | bash - && \
 RUN curl -sL https://deb.nodesource.com/setup_15.x | bash - && \
     apt-get install -y nodejs
     apt-get install -y nodejs

+ 1 - 1
README.md

@@ -16,7 +16,7 @@ Use it to organize your todo list, to write your journals, or to record your uni
 ## Why Logseq?
 ## 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 sharing and management. It focuses on privacy, longevity, and user control.
-Notice: the backend code will not be open-sourced for security reasons and other potential risks.
+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).
 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).
 
 

+ 3 - 0
externs.js

@@ -17,6 +17,9 @@ dummy.getRangeAt = function() {};
 dummy.getElementsByClassName = function() {};
 dummy.getElementsByClassName = function() {};
 dummy.containsNode = function() {};
 dummy.containsNode = function() {};
 dummy.select = function() {};
 dummy.select = function() {};
+dummy.search = function() {};
+dummy.add = function() {};
+dummy.remove = function() {};
 dummy.closest = function () {};
 dummy.closest = function () {};
 dummy.setAttribute = function() {};
 dummy.setAttribute = function() {};
 dummy.getAttribute = function() {};
 dummy.getAttribute = function() {};

+ 4 - 2
package.json

@@ -58,16 +58,18 @@
     "dependencies": {
     "dependencies": {
         "@kanru/rage-wasm": "^0.2.1",
         "@kanru/rage-wasm": "^0.2.1",
         "chokidar": "^3.5.1",
         "chokidar": "^3.5.1",
+        "chrono-node": "^2.2.1",
         "codemirror": "^5.58.1",
         "codemirror": "^5.58.1",
         "diff": "5.0.0",
         "diff": "5.0.0",
         "diff-match-patch": "^1.0.5",
         "diff-match-patch": "^1.0.5",
         "electron": "^11.2.0",
         "electron": "^11.2.0",
+        "flexsearch": "git+https://github.com/logseq/flexsearch",
         "fs": "^0.0.1-security",
         "fs": "^0.0.1-security",
-        "fuzzysort": "^1.1.4",
+        "fuzzysort": "git+https://github.com/getstation/fuzzysort#a66f5813825d2415b606cc69129070c4eb612ae2",
         "gulp-cached": "^1.1.1",
         "gulp-cached": "^1.1.1",
         "ignore": "^5.1.8",
         "ignore": "^5.1.8",
         "jszip": "^3.5.0",
         "jszip": "^3.5.0",
-        "mldoc": "^0.3.8",
+        "mldoc": "^0.4.0",
         "mousetrap": "^1.6.5",
         "mousetrap": "^1.6.5",
         "path": "^0.12.7",
         "path": "^0.12.7",
         "react": "^17.0.1",
         "react": "^17.0.1",

+ 4 - 2
src/electron/electron/core.cljs

@@ -77,8 +77,10 @@
 
 
     (.on web-contents  "new-window"
     (.on web-contents  "new-window"
          (fn [e url]
          (fn [e url]
-           (.. logger (info "new-window" url))
-           (open url)
+           (let [url (if (string/starts-with? url "file:")
+                       (js/decodeURIComponent url) url)]
+             (.. logger (info "new-window" url))
+             (open url))
            (.preventDefault e)))
            (.preventDefault e)))
 
 
     (doto win
     (doto win

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

@@ -65,11 +65,11 @@
     (vec (cons {:path (fix-win-path! path)} result))))
     (vec (cons {:path (fix-win-path! path)} result))))
 
 
 ;; TODO: Is it going to be slow if it's a huge directory
 ;; TODO: Is it going to be slow if it's a huge directory
-(defmethod handle :openDir [window _messages]
+(defmethod handle :openDir [^js window _messages]
   (let [result (.showOpenDialogSync dialog (bean/->js
   (let [result (.showOpenDialogSync dialog (bean/->js
                                             {:properties ["openDirectory"]}))
                                             {:properties ["openDirectory"]}))
         path (first result)]
         path (first result)]
-    (.. window -webContents
+    (.. ^js window -webContents
         (send "open-dir-confirmed"
         (send "open-dir-confirmed"
               (bean/->js {:opened? true})))
               (bean/->js {:opened? true})))
     (get-files path)))
     (get-files path)))

+ 33 - 20
src/main/frontend/components/block.cljs

@@ -181,7 +181,7 @@
                              (reset! *resizing-image? true))
                              (reset! *resizing-image? true))
                            (reset! size value))
                            (reset! size value))
           :onMouseUp (fn []
           :onMouseUp (fn []
-                       (when @size
+                       (when (and @size @*resizing-image?)
                          (when-let [block-id (:block/uuid config)]
                          (when-let [block-id (:block/uuid config)]
                            (let [size (bean/->clj @size)]
                            (let [size (bean/->clj @size)]
                              (editor-handler/resize-image! block-id metadata full_text size))))
                              (editor-handler/resize-image! block-id metadata full_text size))))
@@ -241,7 +241,8 @@
                    nil
                    nil
                    (safe-read-string metadata false))
                    (safe-read-string metadata false))
         title (second (first label))]
         title (second (first label))]
-    (if (config/local-asset? href)
+    (if (and (config/local-asset? href)
+             (config/local-db? (state/get-current-repo)))
       (asset-link config title href label metadata full_text)
       (asset-link config title href label metadata full_text)
       (let [href (if (util/starts-with? href "http")
       (let [href (if (util/starts-with? href "http")
                    href
                    href
@@ -380,7 +381,7 @@
 (rum/defc asset-reference
 (rum/defc asset-reference
   [title path]
   [title path]
   (let [repo-path (config/get-repo-dir (state/get-current-repo))
   (let [repo-path (config/get-repo-dir (state/get-current-repo))
-        full-path (str repo-path (string/replace path "../" "/"))]
+        full-path (.. util/node-path (join repo-path (config/get-local-asset-absolute-path path)))]
     [:a.asset-ref {:target "_blank" :href full-path} (or title path)]))
     [:a.asset-ref {:target "_blank" :href full-path} (or title path)]))
 
 
 (rum/defc page-reference < rum/reactive
 (rum/defc page-reference < rum/reactive
@@ -838,11 +839,15 @@
                                (get (state/get-macros) (keyword name)))
                                (get (state/get-macros) (keyword name)))
                 macro-content (if (and (seq arguments) macro-content)
                 macro-content (if (and (seq arguments) macro-content)
                                 (block/macro-subs macro-content arguments)
                                 (block/macro-subs macro-content arguments)
-                                macro-content)]
+                                macro-content)
+                macro-content (when macro-content
+                                (editor-handler/resolve-dynamic-template! macro-content))]
             (render-macro config name arguments macro-content format))
             (render-macro config name arguments macro-content format))
 
 
           (when-let [macro-txt (macro->text name arguments)]
           (when-let [macro-txt (macro->text name arguments)]
-            (let [format (get-in config [:block :block/format] :markdown)]
+            (let [macro-txt (when macro-txt
+                              (editor-handler/resolve-dynamic-template! macro-txt))
+                  format (get-in config [:block :block/format] :markdown)]
               (render-macro config name arguments macro-txt format))))))
               (render-macro config name arguments macro-txt format))))))
 
 
     :else
     :else
@@ -1100,7 +1105,8 @@
             {:style {:background-color bg-color
             {:style {:background-color bg-color
                      :padding-left 6
                      :padding-left 6
                      :padding-right 6
                      :padding-right 6
-                     :color "#FFFFFF"}}))
+                     :color "#FFFFFF"}
+             :class "with-bg-color"}))
          (remove-nils
          (remove-nils
           (concat
           (concat
            [(when-not slide? checkbox)
            [(when-not slide? checkbox)
@@ -1489,22 +1495,29 @@
                                (when doc-mode?
                                (when doc-mode?
                                  (when-let [parent (gdom/getElement block-id)]
                                  (when-let [parent (gdom/getElement block-id)]
                                    (when-let [node (.querySelector parent ".bullet-container")]
                                    (when-let [node (.querySelector parent ".bullet-container")]
-                                     (d/add-class! node "hide-inner-bullet")))))}]
+                                     (d/add-class! node "hide-inner-bullet")))))}
+        data-refs (let [refs (model/get-page-names-by-ids
+                              (->> (map :db/id refs-with-children)
+                                   (remove nil?)))]
+                    (text/build-data-value refs))
+        data-refs-self (let [refs  (model/get-page-names-by-ids
+                                    (->> (map :db/id (:block/ref-pages block))
+                                         (remove nil?)))]
+                         (text/build-data-value refs))]
     [:div.ls-block.flex.flex-col.rounded-sm
     [:div.ls-block.flex.flex-col.rounded-sm
      (cond->
      (cond->
-      {:id block-id
-       :data-refs (let [refs (model/get-page-names-by-ids
-                              (map :db/id refs-with-children))]
-                    (text/build-data-value refs))
-       :style {:position "relative"}
-       :class (str uuid
-                   (when dummy? " dummy")
-                   (when (and collapsed? has-child?) " collapsed")
-                   (when pre-block? " pre-block"))
-       :blockid (str uuid)
-       :repo repo
-       :level level
-       :haschild (str has-child?)}
+         {:id block-id
+          :data-refs data-refs
+          :data-refs-self data-refs-self
+          :style {:position "relative"}
+          :class (str uuid
+                      (when dummy? " dummy")
+                      (when (and collapsed? has-child?) " collapsed")
+                      (when pre-block? " pre-block"))
+          :blockid (str uuid)
+          :repo repo
+          :level level
+          :haschild (str has-child?)}
        (not slide?)
        (not slide?)
        (merge attrs))
        (merge attrs))
 
 

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

@@ -72,12 +72,28 @@
    "#264c9b"
    "#264c9b"
    "#793e3e"])
    "#793e3e"])
 
 
-(rum/defcs block-template <
+(defonce *including-parent? (atom nil))
+
+(rum/defc template-checkbox
+  [including-parent?]
+  [:div.flex.flex-row
+   [:span.text-medium.mr-2 "Including the parent block in the template?"]
+   (ui/toggle including-parent?
+              #(swap! *including-parent? not))])
+
+(rum/defcs block-template < rum/reactive
   (rum/local false ::edit?)
   (rum/local false ::edit?)
   (rum/local "" ::input)
   (rum/local "" ::input)
   [state block-id]
   [state block-id]
   (let [edit? (get state ::edit?)
   (let [edit? (get state ::edit?)
-        input (get state ::input)]
+        input (get state ::input)
+        including-parent? (rum/react *including-parent?)
+        block-id (if (string? block-id) (uuid block-id) block-id)
+        block (db/entity [:block/uuid block-id])
+        has-children? (seq (:block/children block))]
+    (when (and (nil? including-parent?) has-children?)
+      (reset! *including-parent? true))
+
     (if @edit?
     (if @edit?
       (do
       (do
         (state/clear-edit!)
         (state/clear-edit!)
@@ -87,6 +103,8 @@
           {:auto-focus true
           {:auto-focus true
            :on-change (fn [e]
            :on-change (fn [e]
                         (reset! input (util/evalue e)))}]
                         (reset! input (util/evalue e)))}]
+         (when has-children?
+           (template-checkbox including-parent?))
          (ui/button "Submit"
          (ui/button "Submit"
                     :on-click (fn []
                     :on-click (fn []
                                 (let [title (string/trim @input)]
                                 (let [title (string/trim @input)]
@@ -97,6 +115,8 @@
                                        :error)
                                        :error)
                                       (do
                                       (do
                                         (editor-handler/set-block-property! block-id "template" title)
                                         (editor-handler/set-block-property! block-id "template" title)
+                                        (when-not including-parent?
+                                          (editor-handler/set-block-property! block-id "including-parent" false))
                                         (state/hide-custom-context-menu!)))))))])
                                         (state/hide-custom-context-menu!)))))))])
       (ui/menu-link
       (ui/menu-link
        {:key "Make template"
        {:key "Make template"

+ 41 - 24
src/main/frontend/components/editor.cljs

@@ -132,23 +132,29 @@
             :empty-div [:div.text-gray-500.pl-4.pr-4 "Search for a page"]
             :empty-div [:div.text-gray-500.pl-4.pr-4 "Search for a page"]
             :class     "black"}))))))
             :class     "black"}))))))
 
 
-(rum/defc block-search < rum/reactive
-  {:will-unmount (fn [state] (reset! editor-handler/*selected-text nil) state)}
-  [id format]
+(rum/defcs block-search < rum/reactive
+  {:will-unmount (fn [state]
+                   (reset! editor-handler/*selected-text nil)
+                   (state/clear-search-result!)
+                   state)}
+  [state id format]
   (when (state/sub :editor/show-block-search?)
   (when (state/sub :editor/show-block-search?)
     (let [pos (:editor/last-saved-cursor @state/state)
     (let [pos (:editor/last-saved-cursor @state/state)
-          input (gdom/getElement id)]
+          input (gdom/getElement id)
+          [id format] (:rum/args state)
+          current-pos (:pos (util/get-caret-pos input))
+          edit-content (state/sub [:editor/content id])
+          edit-block (state/get-edit-block)
+          q (or
+             @editor-handler/*selected-text
+             (when (> (count edit-content) current-pos)
+               (subs edit-content pos current-pos)))
+          _ (p/let [matched-blocks (when-not (string/blank? q)
+                                     (editor-handler/get-matched-blocks q (:block/uuid edit-block)))]
+              (state/set-search-result! matched-blocks))
+          matched-blocks (state/sub :search/result)]
       (when input
       (when input
-        (let [current-pos (:pos (util/get-caret-pos input))
-              edit-content (state/sub [:editor/content id])
-              edit-block (state/get-edit-block)
-              q (or
-                 @editor-handler/*selected-text
-                 (when (> (count edit-content) current-pos)
-                   (subs edit-content pos current-pos)))
-              matched-blocks (when-not (string/blank? q)
-                               (editor-handler/get-matched-blocks q (:block/uuid edit-block)))
-              chosen-handler (fn [chosen _click?]
+        (let [chosen-handler (fn [chosen _click?]
                                (state/set-editor-show-block-search! false)
                                (state/set-editor-show-block-search! false)
                                (let [uuid-string (str (:block/uuid chosen))]
                                (let [uuid-string (str (:block/uuid chosen))]
 
 
@@ -196,23 +202,33 @@
               chosen-handler (fn [[template db-id] _click?]
               chosen-handler (fn [[template db-id] _click?]
                                (if-let [block (db/entity db-id)]
                                (if-let [block (db/entity db-id)]
                                  (let [new-level (:block/level edit-block)
                                  (let [new-level (:block/level edit-block)
+                                       properties (:block/properties block)
+                                       block-uuid (:block/uuid block)
+                                       including-parent? (not= (get properties "including-parent") "false")
                                        template-parent-level (:block/level block)
                                        template-parent-level (:block/level block)
                                        pattern (config/get-block-pattern format)
                                        pattern (config/get-block-pattern format)
                                        content
                                        content
                                        (block-handler/get-block-full-content
                                        (block-handler/get-block-full-content
                                         (state/get-current-repo)
                                         (state/get-current-repo)
                                         (:block/uuid block)
                                         (:block/uuid block)
-                                        (fn [{:block/keys [level content properties] :as block}]
-                                          (let [new-level (+ new-level (- level template-parent-level))
-                                                properties' (dissoc (into {} properties) "id" "custom_id" "template")]
-                                            (-> content
-                                                (string/replace-first (apply str (repeat level pattern))
-                                                                      (apply str (repeat new-level pattern)))
-                                                text/remove-properties!
-                                                (text/rejoin-properties properties')))))
+                                        (fn [{:block/keys [uuid level content properties] :as block}]
+                                          (let [parent? (= uuid block-uuid)
+                                                ignore-parent? (and parent? (not including-parent?))]
+                                            (if ignore-parent?
+                                              ""
+                                              (let [new-level (+ new-level
+                                                                 (- level template-parent-level
+                                                                    (if (not including-parent?) 1 0)))
+                                                    properties' (dissoc (into {} properties) "id" "custom_id" "template")]
+                                                (-> content
+                                                   (string/replace-first (apply str (repeat level pattern))
+                                                                         (apply str (repeat new-level pattern)))
+                                                   text/remove-properties!
+                                                   (text/rejoin-properties properties')))))))
                                        content (if (string/includes? (string/trim edit-content) "\n")
                                        content (if (string/includes? (string/trim edit-content) "\n")
                                                  content
                                                  content
-                                                 (text/remove-level-spaces content format))]
+                                                 (text/remove-level-spaces content format))
+                                       content (editor-handler/resolve-dynamic-template! content)]
                                    (state/set-editor-show-template-search! false)
                                    (state/set-editor-show-template-search! false)
                                    (editor-handler/insert-command! id
                                    (editor-handler/insert-command! id
                                                                    content
                                                                    content
@@ -250,7 +266,7 @@
     {:on-click #(commands/simple-insert! parent-id "\n" {})}
     {:on-click #(commands/simple-insert! parent-id "\n" {})}
     svg/multi-line-input]
     svg/multi-line-input]
    [:button.bottom-action
    [:button.bottom-action
-    {:on-click #(commands/insert-before! parent-id "TODO " {})} 
+    {:on-click #(commands/insert-before! parent-id "TODO " {})}
     svg/checkbox]
     svg/checkbox]
    [:button.font-extrabold.bottom-action.-mt-1
    [:button.font-extrabold.bottom-action.-mt-1
     {:on-click #(commands/simple-insert!
     {:on-click #(commands/simple-insert!
@@ -712,6 +728,7 @@
      (when config/mobile? (mobile-bar state id))
      (when config/mobile? (mobile-bar state id))
      (ui/ls-textarea
      (ui/ls-textarea
       {:id                id
       {:id                id
+       :class             "mousetrap"
        :cacheMeasurements true
        :cacheMeasurements true
        :default-value     (or content "")
        :default-value     (or content "")
        :minRows           (if (state/enable-grammarly?) 2 1)
        :minRows           (if (state/enable-grammarly?) 2 1)

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

@@ -77,8 +77,9 @@
         {:on-click toggle-fn}
         {:on-click toggle-fn}
         (svg/horizontal-dots nil)])
         (svg/horizontal-dots nil)])
      (->>
      (->>
-      [{:title (t :help/toggle-right-sidebar)
-        :options {:on-click state/toggle-sidebar-open?!}}
+      [(when-not (util/mobile?)
+         {:title (t :help/toggle-right-sidebar)
+          :options {:on-click state/toggle-sidebar-open?!}})
 
 
        (when current-repo
        (when current-repo
          {:title (t :graph)
          {:title (t :graph)

+ 8 - 5
src/main/frontend/components/header.css

@@ -47,7 +47,10 @@
 .cp__header-logo,
 .cp__header-logo,
 .cp__right-menu-button {
 .cp__right-menu-button {
   opacity: 0.7;
   opacity: 0.7;
-  display: none;
+}
+
+.cp__header-logo {
+    display: none;
 }
 }
 
 
 .cp__header-logo:hover,
 .cp__header-logo:hover,
@@ -64,6 +67,10 @@
   @apply ml-3;
   @apply ml-3;
 }
 }
 
 
+.cp__right-menu-button {
+    display: block;
+}
+
 @screen sm {
 @screen sm {
   .cp__header {
   .cp__header {
     @apply shadow-none;
     @apply shadow-none;
@@ -77,8 +84,4 @@
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
   }
   }
-
-  .cp__right-menu-button {
-    display: block;
-  }
 }
 }

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

@@ -111,10 +111,7 @@
 
 
      (when intro? (onboarding/intro))]))
      (when intro? (onboarding/intro))]))
 
 
-(rum/defc journals <
-  {:did-mount (fn [state]
-                (editor-handler/open-last-block! true)
-                state)}
+(rum/defc journals
   [latest-journals]
   [latest-journals]
   [:div#journals
   [:div#journals
    (ui/infinite-list
    (ui/infinite-list

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

@@ -224,15 +224,10 @@
              [:a {:href (rfe/href :page {:name name})}
              [:a {:href (rfe/href :page {:name name})}
               original-name]])] false)]])))
               original-name]])] false)]])))
 
 
-(defonce last-route (atom :home))
 ;; A page is just a logical block
 ;; A page is just a logical block
 (rum/defcs page < rum/reactive
 (rum/defcs page < rum/reactive
   {:did-mount (fn [state]
   {:did-mount (fn [state]
                 (ui-handler/scroll-and-highlight! state)
                 (ui-handler/scroll-and-highlight! state)
-                ;; only when route changed
-                (when (not= @last-route (state/get-current-route))
-                  (editor-handler/open-last-block! false))
-                (reset! last-route (state/get-current-route))
                 state)
                 state)
    :did-update (fn [state]
    :did-update (fn [state]
                  (ui-handler/scroll-and-highlight! state)
                  (ui-handler/scroll-and-highlight! state)

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

@@ -31,6 +31,41 @@
   (let [switch (reductions not= true (map pred? coll (rest coll)))]
   (let [switch (reductions not= true (map pred? coll (rest coll)))]
     (map (partial map first) (partition-by second (map list coll switch)))))
     (map (partial map first) (partition-by second (map list coll switch)))))
 
 
+(defn highlight-exact-query
+  [content q]
+  (let [q-words (string/split q #" ")
+        lc-content (string/lower-case content)
+        lc-q (string/lower-case q)]
+    (if (or (string/includes? lc-content lc-q)
+            (not (re-find #" " q)))
+      (let [i (string/index-of lc-content lc-q)
+            [before after] [(subs content 0 i) (subs content (+ i (count q)))]]
+        [:p
+         (when-not (string/blank? before)
+           [:span before])
+         [:mark (subs content i (+ i (count q)))]
+         (when-not (string/blank? after)
+           [:span after])])
+      (let [elements (loop [words q-words
+                            content content
+                            result []]
+                       (if (and (seq words) content)
+                         (let [word (first words)
+                               lc-word (string/lower-case word)
+                               lc-content (string/lower-case content)]
+                           (if-let [i (string/index-of lc-content lc-word)]
+                             (recur (rest words)
+                                    (subs content (+ i (count word)))
+                                    (vec
+                                     (concat result
+                                             [[:span (subs content 0 i)]
+                                              [:mark (subs content i (+ i (count word)))]])))
+                             (recur nil
+                                    content
+                                    result)))
+                         (conj result [:span content])))]
+        [:p elements]))))
+
 (rum/defc highlight-fuzzy
 (rum/defc highlight-fuzzy
   [content indexes]
   [content indexes]
   (let [n (count content)
   (let [n (count content)
@@ -182,7 +217,7 @@
                                          (:page/name page))]
                                          (:page/name page))]
                             [:div.flex-1
                             [:div.flex-1
                              [:div.text-sm.font-medium (str "-> " page)]
                              [:div.text-sm.font-medium (str "-> " page)]
-                             (highlight-fuzzy content indexes)])
+                             (highlight-exact-query content search-q)])
 
 
                           nil))})])))
                           nil))})])))
 
 

+ 32 - 1
src/main/frontend/components/settings.cljs

@@ -111,6 +111,25 @@
                (util/stop e))}
                (util/stop e))}
             svg/external-link " release channel"]])])]))
             svg/external-link " release channel"]])])]))
 
 
+(rum/defc delete-account-confirm
+  [close-fn]
+  (rum/with-context [[t] i18n/*tongue-context*]
+    [:div
+     (ui/admonition
+      :important
+      [:p.text-gray-700 (t :user/delete-account-notice)])
+     [:div.mt-5.sm:mt-4.sm:flex.sm:flex-row-reverse
+      [:span.flex.w-full.rounded-md.shadow-sm.sm:ml-3.sm:w-auto
+       [:button.inline-flex.justify-center.w-full.rounded-md.border.border-transparent.px-4.py-2.bg-indigo-600.text-base.leading-6.font-medium.text-white.shadow-sm.hover:bg-indigo-500.focus:outline-none.focus:border-indigo-700.focus:shadow-outline-indigo.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
+        {:type "button"
+         :on-click user-handler/delete-account!}
+        (t :user/delete-account)]]
+      [:span.mt-3.flex.w-full.rounded-md.shadow-sm.sm:mt-0.sm:w-auto
+       [:button.inline-flex.justify-center.w-full.rounded-md.border.border-gray-300.px-4.py-2.bg-white.text-base.leading-6.font-medium.text-gray-700.shadow-sm.hover:text-gray-500.focus:outline-none.focus:border-blue-300.focus:shadow-outline-blue.transition.ease-in-out.duration-150.sm:text-sm.sm:leading-5
+        {:type "button"
+         :on-click close-fn}
+        "Cancel"]]]]))
+
 (rum/defcs settings < rum/reactive
 (rum/defcs settings < rum/reactive
   []
   []
   (let [preferred-format (state/get-preferred-format)
   (let [preferred-format (state/get-preferred-format)
@@ -329,4 +348,16 @@
                        :on-click #(state/set-developer-mode! (not developer-mode?)))]]]
                        :on-click #(state/set-developer-mode! (not developer-mode?)))]]]
 
 
          [:br]
          [:br]
-         (t :settings-page/developer-mode-desc)]]])))
+         (t :settings-page/developer-mode-desc)
+
+         [:hr]
+
+         (when logged?
+           [:div.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start.sm:pt-5
+            [:label.block.text-sm.font-medium.leading-5.sm:mt-px.sm:pt-2.opacity-70.text-red-600
+             {:for "delete account"}
+             (t :user/delete-account)]
+            [:div.mt-1.sm:mt-0.sm:col-span-2
+             [:div.max-w-lg.rounded-md.shadow-sm.sm:max-w-xs
+              (ui/button (t :user/delete-your-account)
+                :on-click #(state/set-modal! delete-account-confirm))]]])]]])))

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

@@ -290,7 +290,6 @@
   {:did-mount (fn [state]
   {:did-mount (fn [state]
                 (keyboards/bind-shortcuts!)
                 (keyboards/bind-shortcuts!)
                 state)}
                 state)}
-  (mixins/keyboards-mixin keyboards/keyboards)
   [state route-match main-content]
   [state route-match main-content]
   (let [{:keys [open? close-fn open-fn]} state
   (let [{:keys [open? close-fn open-fn]} state
         close-fn (fn []
         close-fn (fn []

+ 7 - 1
src/main/frontend/date.cljs

@@ -7,7 +7,13 @@
             [cljs-bean.core :as bean]
             [cljs-bean.core :as bean]
             [frontend.util :as util]
             [frontend.util :as util]
             [clojure.string :as string]
             [clojure.string :as string]
-            [goog.object :as gobj]))
+            [goog.object :as gobj]
+            ["chrono-node" :as chrono]))
+
+(defn nld-parse
+  [s]
+  (when (string? s)
+    ((gobj/get chrono "parseDate") s)))
 
 
 (defn format
 (defn format
   [date]
   [date]

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

@@ -10,6 +10,7 @@
             [frontend.state :as state]
             [frontend.state :as state]
             [promesa.core :as p]
             [promesa.core :as p]
             [frontend.db-schema :as db-schema]
             [frontend.db-schema :as db-schema]
+            [frontend.db.default :as default-db]
             [clojure.core.async :as async]
             [clojure.core.async :as async]
             [frontend.idb :as idb]))
             [frontend.idb :as idb]))
 
 
@@ -147,7 +148,8 @@
          (p/let [stored (idb/get-item db-name)
          (p/let [stored (idb/get-item db-name)
                  _ (when stored
                  _ (when stored
                      (let [stored-db (string->db stored)
                      (let [stored-db (string->db stored)
-                           attached-db (d/db-with stored-db [(me-tx stored-db me)])]
+                           attached-db (d/db-with stored-db
+                                                  [(me-tx stored-db me)])]
                        (conn/reset-conn! db-conn attached-db)))
                        (conn/reset-conn! db-conn attached-db)))
                  db-name (datascript-db repo)
                  db-name (datascript-db repo)
                  db-conn (d/create-conn db-schema/schema)
                  db-conn (d/create-conn db-schema/schema)
@@ -156,7 +158,9 @@
                  stored (idb/get-item db-name)
                  stored (idb/get-item db-name)
                  _ (if stored
                  _ (if stored
                      (let [stored-db (string->db stored)
                      (let [stored-db (string->db stored)
-                           attached-db (d/db-with stored-db [(me-tx stored-db me)])]
+                           attached-db (d/db-with stored-db (concat
+                                                             [(me-tx stored-db me)]
+                                                             default-db/built-in-pages))]
                        (conn/reset-conn! db-conn attached-db))
                        (conn/reset-conn! db-conn attached-db))
                      (when logged?
                      (when logged?
                        (d/transact! db-conn [(me-tx (d/db db-conn) me)])))]
                        (d/transact! db-conn [(me-tx (d/db db-conn) me)])))]

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

@@ -2,6 +2,7 @@
   "Contains db connections."
   "Contains db connections."
   (:require [clojure.string :as string]
   (:require [clojure.string :as string]
             [frontend.db-schema :as db-schema]
             [frontend.db-schema :as db-schema]
+            [frontend.db.default :as default-db]
             [frontend.util :as util]
             [frontend.util :as util]
             [frontend.state :as state]
             [frontend.state :as state]
             [frontend.config :as config]
             [frontend.config :as config]
@@ -85,6 +86,8 @@
      (when me
      (when me
        (d/transact! db-conn [(me-tx (d/db db-conn) me)]))
        (d/transact! db-conn [(me-tx (d/db db-conn) me)]))
 
 
+     (d/transact! db-conn default-db/built-in-pages)
+
      (when listen-handler (listen-handler repo)))))
      (when listen-handler (listen-handler repo)))))
 
 
 (defn destroy-all!
 (defn destroy-all!

+ 9 - 0
src/main/frontend/db/default.cljs

@@ -0,0 +1,9 @@
+(ns frontend.db.default
+  (:require [clojure.string :as string]))
+
+(def built-in-pages
+  (mapv (fn [p]
+          {:page/name (string/lower-case p)
+           :page/original-name p
+           :page/journal? false})
+        #{"NOW" "LATER" "DOING" "DONE" "IN-PROGRESS" "TODO" "WAIT" "WAITING" "A" "B" "C"}))

+ 2 - 9
src/main/frontend/db/model.cljs

@@ -717,11 +717,7 @@
                                     (string? title)
                                     (string? title)
                                     title))
                                     title))
             file-name (when-let [file-name (last (string/split file #"/"))]
             file-name (when-let [file-name (last (string/split file #"/"))]
-                        (when-let [file-name (first (util/split-last "." file-name))]
-                          (-> file-name
-                              (string/replace "-" " ")
-                              (string/replace "_" " ")
-                              (util/capitalize-all))))]
+                        (first (util/split-last "." file-name)))]
         (or property-name
         (or property-name
             (if (= (state/page-name-order) "file")
             (if (= (state/page-name-order) "file")
               (or file-name first-block-name)
               (or file-name first-block-name)
@@ -1204,10 +1200,7 @@
   [repo path content]
   [repo path content]
   (when (and repo path)
   (when (and repo path)
     (let [tx-data {:file/path path
     (let [tx-data {:file/path path
-                   :file/content content}
-          tx-data (if (config/local-db? repo)
-                    (dissoc tx-data :file/last-modified-at)
-                    tx-data)]
+                   :file/content content}]
       (react/transact-react!
       (react/transact-react!
        repo
        repo
        [tx-data]
        [tx-data]

+ 10 - 4
src/main/frontend/dicts.cljs

@@ -343,8 +343,11 @@ title: How to take dummy notes?
         :dark "Dark"
         :dark "Dark"
         :remove-background "Remove background"
         :remove-background "Remove background"
         :open "Open"
         :open "Open"
-        :open-a-directory "Open a local directory"}
-        
+        :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."}
+
    :de {:help/about "Über Logseq"
    :de {:help/about "Über Logseq"
         :help/bug "Fehlerbericht"
         :help/bug "Fehlerbericht"
         :help/feature "Feature-Anfrage"
         :help/feature "Feature-Anfrage"
@@ -573,7 +576,7 @@ title: How to take dummy notes?
         :remove-background "Hintergrund entfernen"
         :remove-background "Hintergrund entfernen"
         :open "Öffnen"
         :open "Öffnen"
         :open-a-directory "Öffne ein lokales Verzeichnis"}
         :open-a-directory "Öffne ein lokales Verzeichnis"}
-        
+
    :fr {:help/about "A propos de Logseq"
    :fr {:help/about "A propos de Logseq"
         :help/bug "Signaler une anomalie"
         :help/bug "Signaler une anomalie"
         :help/feature "Demander une fonctionnalité"
         :help/feature "Demander une fonctionnalité"
@@ -1057,7 +1060,10 @@ title: How to take dummy notes?
            :dark "暗黑"
            :dark "暗黑"
            :remove-background "去除背景"
            :remove-background "去除背景"
            :open "打开"
            :open "打开"
-           :open-a-directory "打开本地文件夹"}
+           :open-a-directory "打开本地文件夹"
+           :user/delete-account "删除帐号"
+           :user/delete-your-account "删除你的帐号"
+           :user/delete-account-notice "你在 logseq.com 发布的页面(假如有的话)也会被删除。"}
 
 
    :zh-Hant {:on-boarding/title "你好,歡迎使用 Logseq!"
    :zh-Hant {:on-boarding/title "你好,歡迎使用 Logseq!"
              :on-boarding/sharing "分享"
              :on-boarding/sharing "分享"

+ 11 - 1
src/main/frontend/external/roam.cljc

@@ -43,6 +43,15 @@
                                              (util/format "{{%s %s}}" name arg))
                                              (util/format "{{%s %s}}" name arg))
                                            original)))))
                                            original)))))
 
 
+(defn- fenced-code-transform
+  [text]
+  (string/replace text
+                  #"```([a-z]*\n[\s\S]*?\n*)```"
+                  (fn [[_ match]]
+                    (str "```"
+                         (str match "\n")
+                         "```"))))
+
 (defn load-all-refed-uids!
 (defn load-all-refed-uids!
   [data]
   [data]
   (let [full-text (atom "")]
   (let [full-text (atom "")]
@@ -66,7 +75,8 @@
       (string/replace "{{[[TODO]]}}" "TODO")
       (string/replace "{{[[TODO]]}}" "TODO")
       (string/replace "{{[[DONE]]}}" "DONE")
       (string/replace "{{[[DONE]]}}" "DONE")
       (uid-transform)
       (uid-transform)
-      (macro-transform)))
+      (macro-transform)
+      (fenced-code-transform)))
 
 
 (declare children->text)
 (declare children->text)
 (defn child->text
 (defn child->text

+ 3 - 3
src/main/frontend/format/block.cljs

@@ -251,9 +251,9 @@
      (concat title body))
      (concat title body))
     (let [ref-blocks (remove string/blank? @ref-blocks)]
     (let [ref-blocks (remove string/blank? @ref-blocks)]
       (assoc block :ref-blocks (map
       (assoc block :ref-blocks (map
-                                (fn [id]
-                                  [:block/uuid (medley/uuid id)])
-                                ref-blocks)))))
+                                 (fn [id]
+                                   [:block/uuid (medley/uuid id)])
+                                 ref-blocks)))))
 
 
 (defn update-src-pos-meta!
 (defn update-src-pos-meta!
   [{:keys [body] :as block}]
   [{:keys [body] :as block}]

+ 26 - 19
src/main/frontend/fs.cljs

@@ -57,28 +57,35 @@
   [dir]
   [dir]
   (protocol/rmdir! (get-fs dir) dir))
   (protocol/rmdir! (get-fs dir) dir))
 
 
-(defn read-file
-  [dir path]
-  (p/chain (protocol/read-file (get-fs dir) dir path)
-           encrypt/decrypt))
-
 (defn write-file!
 (defn write-file!
   [repo dir path content opts]
   [repo dir path content opts]
   (when content
   (when content
-    (p/let [metadata-or-css? (or (string/ends-with? path config/metadata-file)
-                                 (string/ends-with? path config/custom-css-file))
-            content (if metadata-or-css? content (encrypt/encrypt content))]
-      (->
-       (do
-         (protocol/write-file! (get-fs dir) repo dir path content opts)
-         (db/set-file-last-modified-at! repo (config/get-file-path repo path) (js/Date.)))
-       (p/catch (fn [error]
-                  (log/error :file/write-failed? {:dir dir
-                                                  :path path
-                                                  :error error})
-                  ;; Disable this temporarily
-                  ;; (js/alert "Current file can't be saved! Please copy its content to your local file system and click the refresh button.")
-))))))
+    (let [fs-record (get-fs dir)]
+      (p/let [metadata-or-css? (or (string/ends-with? path config/metadata-file)
+                                  (string/ends-with? path config/custom-css-file))
+             content (if metadata-or-css? content (encrypt/encrypt content))]
+       (->
+        (p/let [_ (protocol/write-file! (get-fs dir) repo dir path content opts)]
+          (when-not (= fs-record nfs-record)
+           (db/set-file-last-modified-at! repo (config/get-file-path repo path) (js/Date.))))
+        (p/catch (fn [error]
+                   (log/error :file/write-failed? {:dir dir
+                                                   :path path
+                                                   :error error})
+                   ;; Disable this temporarily
+                   ;; (js/alert "Current file can't be saved! Please copy its content to your local file system and click the refresh button.")
+                   )))))))
+
+(defn read-file
+  ([dir path]
+   (let [fs (get-fs dir)
+         options (if (= fs bfs-record)
+                   {:encoding "utf8"}
+                   {})]
+     (read-file dir path {})))
+  ([dir path options]
+   (p/chain (protocol/read-file (get-fs dir) dir path options)
+            encrypt/decrypt)))
 
 
 (defn rename!
 (defn rename!
   [repo old-path new-path]
   [repo old-path new-path]

+ 2 - 3
src/main/frontend/fs/bfs.cljs

@@ -22,9 +22,8 @@
           (p/rejected "Unlinking a directory is not allowed")))))
           (p/rejected "Unlinking a directory is not allowed")))))
   (rmdir! [this dir]
   (rmdir! [this dir]
     (js/window.workerThread.rimraf dir))
     (js/window.workerThread.rimraf dir))
-  (read-file [this dir path]
-    (let [option (clj->js {:encoding "utf8"})]
-      (js/window.pfs.readFile (str dir "/" path) option)))
+  (read-file [this dir path options]
+    (js/window.pfs.readFile (str dir "/" path) (clj->js options)))
   (write-file! [this repo dir path content opts]
   (write-file! [this repo dir path content opts]
     (when-not (util/electron?)
     (when-not (util/electron?)
       (js/window.pfs.writeFile (str dir "/" path) content)))
       (js/window.pfs.writeFile (str dir "/" path) content)))

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

@@ -97,14 +97,15 @@
   (rmdir! [this dir]
   (rmdir! [this dir]
     nil)
     nil)
 
 
-  (read-file [this dir path]
+  (read-file [this dir path options]
     (let [handle-path (str "handle" dir "/" path)]
     (let [handle-path (str "handle" dir "/" path)]
       (p/let [handle (idb/get-item handle-path)
       (p/let [handle (idb/get-item handle-path)
               local-file (and handle (.getFile handle))]
               local-file (and handle (.getFile handle))]
         (and local-file (.text local-file)))))
         (and local-file (.text local-file)))))
 
 
   (write-file! [this repo dir path content opts]
   (write-file! [this repo dir path content opts]
-    (let [{:keys [old-content last-modified-at]} opts
+    (let [{:keys [old-content]} opts
+          last-modified-at (db/get-file-last-modified-at repo path)
           parts (string/split path "/")
           parts (string/split path "/")
           basename (last parts)
           basename (last parts)
           sub-dir (->> (butlast parts)
           sub-dir (->> (butlast parts)
@@ -136,7 +137,9 @@
                                        ;; temporally fix
                                        ;; temporally fix
                                        (and path (string/ends-with? path ".excalidraw"))) new?
                                        (and path (string/ends-with? path ".excalidraw"))) new?
                      (or
                      (or
+                      ;; Writing not finished
                       (> pending-writes 0)
                       (> pending-writes 0)
+                      ;; not changed by other editors
                       not-changed?
                       not-changed?
                       new-created?))
                       new-created?))
               (do
               (do

+ 1 - 1
src/main/frontend/fs/node.cljs

@@ -30,7 +30,7 @@
     (ipc/ipc "unlink" path))
     (ipc/ipc "unlink" path))
   (rmdir! [this dir]
   (rmdir! [this dir]
     nil)
     nil)
-  (read-file [this dir path]
+  (read-file [this dir path _options]
     (let [path (concat-path dir path)]
     (let [path (concat-path dir path)]
       (ipc/ipc "readFile" path)))
       (ipc/ipc "readFile" path)))
   (write-file! [this repo dir path content _opts]
   (write-file! [this repo dir path content _opts]

+ 1 - 1
src/main/frontend/fs/protocol.cljs

@@ -5,7 +5,7 @@
   (readdir [this dir])
   (readdir [this dir])
   (unlink! [this path opts])
   (unlink! [this path opts])
   (rmdir! [this dir])
   (rmdir! [this dir])
-  (read-file [this dir path])
+  (read-file [this dir path opts])
   (write-file! [this repo dir path content opts])
   (write-file! [this repo dir path content opts])
   (rename! [this repo old-path new-path])
   (rename! [this repo old-path new-path])
   (stat [this dir path])
   (stat [this dir path])

+ 5 - 3
src/main/frontend/handler/block.cljs

@@ -31,7 +31,8 @@
               cur-level (:block/level item)
               cur-level (:block/level item)
               bottom-level (:block/level (first children))
               bottom-level (:block/level (first children))
               pre-block? (:block/pre-block? item)
               pre-block? (:block/pre-block? item)
-              item (assoc item :block/refs-with-children (get-all-refs item))]
+              item (assoc item :block/refs-with-children (->> (get-all-refs item)
+                                                              (remove nil?)))]
           (cond
           (cond
             (empty? children)
             (empty? children)
             (recur others (list item))
             (recur others (list item))
@@ -46,8 +47,9 @@
             (let [[children other-children] (split-with (fn [h]
             (let [[children other-children] (split-with (fn [h]
                                                           (> (:block/level h) cur-level))
                                                           (> (:block/level h) cur-level))
                                                         children)
                                                         children)
-                  refs-with-children (-> (mapcat get-all-refs (cons item children))
-                                         distinct)
+                  refs-with-children (->> (mapcat get-all-refs (cons item children))
+                                          (remove nil?)
+                                          distinct)
                   children (cons
                   children (cons
                             (assoc item
                             (assoc item
                                    :block/children children
                                    :block/children children

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

@@ -294,8 +294,8 @@
                                 (reset! last-child-end-pos old-end-pos)))
                                 (reset! last-child-end-pos old-end-pos)))
 
 
                             (cond->
                             (cond->
-                             {:block/uuid uuid
-                              :block/meta new-meta}
+                                {:block/uuid uuid
+                                 :block/meta new-meta}
                               (and (some? indent-left?) (not @next-leq-level?))
                               (and (some? indent-left?) (not @next-leq-level?))
                               (assoc :block/level (if indent-left? (dec level) (inc level)))
                               (assoc :block/level (if indent-left? (dec level) (inc level)))
                               (and new-content (not @next-leq-level?))
                               (and new-content (not @next-leq-level?))
@@ -448,10 +448,10 @@
                                       :page/original-name tag}) tags))
                                       :page/original-name tag}) tags))
          page-alias (when-let [alias (:alias new-properties)]
          page-alias (when-let [alias (:alias new-properties)]
                       (map
                       (map
-                       (fn [alias]
-                         {:page/original-name alias
-                          :page/name (string/lower-case alias)})
-                       (remove #{(:page/name page)} alias)))
+                        (fn [alias]
+                          {:page/original-name alias
+                           :page/name (string/lower-case alias)})
+                        (remove #{(:page/name page)} alias)))
 
 
          permalink-changed? (when (and pre-block? (:permalink old-properties))
          permalink-changed? (when (and pre-block? (:permalink old-properties))
                               (not= (:permalink old-properties)
                               (not= (:permalink old-properties)
@@ -508,8 +508,8 @@
                  ;; create the file
                  ;; create the file
                  (let [value (block-text-with-time nil format value)
                  (let [value (block-text-with-time nil format value)
                        content (str (util/default-content-with-title format
                        content (str (util/default-content-with-title format
-                                      (or (:page/original-name page)
-                                          (:page/name page)))
+                                                                     (or (:page/original-name page)
+                                                                         (:page/name page)))
                                     value)]
                                     value)]
                    (p/let [_ (fs/create-if-not-exists repo dir file-path content)
                    (p/let [_ (fs/create-if-not-exists repo dir file-path content)
                            _ (git-handler/git-add repo path)]
                            _ (git-handler/git-add repo path)]
@@ -981,7 +981,7 @@
   [{:block/keys [uuid marker content meta file dummy? properties] :as block} new-marker]
   [{:block/keys [uuid marker content meta file dummy? properties] :as block} new-marker]
   (let [new-content (string/replace-first content marker new-marker)]
   (let [new-content (string/replace-first content marker new-marker)]
     (save-block-if-changed! block new-content
     (save-block-if-changed! block new-content
-                            (with-marker-time block marker))))
+                            {:custom-properties (with-marker-time block new-marker)})))
 
 
 (defn set-priority
 (defn set-priority
   [{:block/keys [uuid marker priority content meta file dummy?] :as block} new-priority]
   [{:block/keys [uuid marker priority content meta file dummy?] :as block} new-priority]
@@ -1168,7 +1168,7 @@
                                                (if (string/starts-with? (string/lower-case line) key)
                                                (if (string/starts-with? (string/lower-case line) key)
                                                  new-line
                                                  new-line
                                                  line))
                                                  line))
-                                             lines)
+                                          lines)
                               new-lines (if (not= lines new-lines)
                               new-lines (if (not= lines new-lines)
                                           new-lines
                                           new-lines
                                           (cons (first new-lines) ;; title
                                           (cons (first new-lines) ;; title
@@ -1437,7 +1437,9 @@
    (save-current-block-when-idle! {}))
    (save-current-block-when-idle! {}))
   ([{:keys [check-idle? chan chan-callback]
   ([{:keys [check-idle? chan chan-callback]
      :or {check-idle? true}}]
      :or {check-idle? true}}]
-   (when (nil? (state/get-editor-op))
+   (when (and (nil? (state/get-editor-op))
+              ;; non English input method
+              (not (state/editor-in-composition?)))
      (when-let [repo (state/get-current-repo)]
      (when-let [repo (state/get-current-repo)]
        (when (and (if check-idle? (state/input-idle? repo) true)
        (when (and (if check-idle? (state/input-idle? repo) true)
                   (not (state/get-editor-show-page-search?))
                   (not (state/get-editor-show-page-search?))
@@ -1559,18 +1561,18 @@
             ext (if ext (subs ext (string/last-index-of ext ".")) "")
             ext (if ext (subs ext (string/last-index-of ext ".")) "")
             filename (str (gen-filename index file) ext)
             filename (str (gen-filename index file) ext)
             filename (str path "/" filename)]
             filename (str path "/" filename)]
-        ;(js/console.debug "Write asset #" dir filename file)
+                                        ;(js/console.debug "Write asset #" dir filename file)
         (if (util/electron?)
         (if (util/electron?)
           (let [from (.-path file)]
           (let [from (.-path file)]
             (p/then (js/window.apis.copyFileToAssets dir filename from)
             (p/then (js/window.apis.copyFileToAssets dir filename from)
-                    #(p/resolved [filename (if (string? %) (js/File. #js[] %) file)])))
+                    #(p/resolved [filename (if (string? %) (js/File. #js[] %) file) (.join util/node-path dir filename)])))
           (p/then (fs/write-file! repo dir filename (.stream file) nil)
           (p/then (fs/write-file! repo dir filename (.stream file) nil)
                   #(p/resolved [filename file]))))))))
                   #(p/resolved [filename file]))))))))
 
 
 (defonce *assets-url-cache (atom {}))
 (defonce *assets-url-cache (atom {}))
 
 
 (defn make-asset-url
 (defn make-asset-url
-  [path]                                                    ;; path start with "/assets" or compatible for "../assets"
+  [path] ;; path start with "/assets" or compatible for "../assets"
   (let [repo-dir (config/get-repo-dir (state/get-current-repo))
   (let [repo-dir (config/get-repo-dir (state/get-current-repo))
         path (string/replace path "../" "/")]
         path (string/replace path "../" "/")]
     (if (util/electron?)
     (if (util/electron?)
@@ -1620,11 +1622,11 @@
       (-> (save-assets! block repo (js->clj files))
       (-> (save-assets! block repo (js->clj files))
           (p/then
           (p/then
            (fn [res]
            (fn [res]
-             (when-let [[url file] (and (seq res) (first res))]
-               (let [image? (util/ext-of-image? url)]
+             (when-let [[asset-file-name file full-file-path] (and (seq res) (first res))]
+               (let [image? (util/ext-of-image? asset-file-name)]
                  (insert-command!
                  (insert-command!
                   id
                   id
-                  (get-asset-file-link format (resolve-relative-path url)
+                  (get-asset-file-link format (resolve-relative-path (or full-file-path asset-file-name))
                                        (if file (.-name file) (if image? "image" "asset"))
                                        (if file (.-name file) (if image? "image" "asset"))
                                        image?)
                                        image?)
                   format
                   format
@@ -1674,7 +1676,7 @@
    ;; "_" "_"
    ;; "_" "_"
    ;; ":" ":"                              ; TODO: only properties editing and org mode tag
    ;; ":" ":"                              ; TODO: only properties editing and org mode tag
    ;; "^" "^"
    ;; "^" "^"
-})
+   })
 
 
 (def reversed-autopair-map
 (def reversed-autopair-map
   (zipmap (vals autopair-map)
   (zipmap (vals autopair-map)
@@ -1762,10 +1764,11 @@
                                                       99)
                                                       99)
                                 (map (comp str :block/uuid))))
                                 (map (comp str :block/uuid))))
         current-and-parents (set/union #{(str (:block/uuid current-block))} block-parents)]
         current-and-parents (set/union #{(str (:block/uuid current-block))} block-parents)]
-    (remove
-     (fn [h]
-       (contains? current-and-parents (:block/uuid h)))
-     (search/search q 10))))
+    (p/let [result (search/block-search q 10)]
+      (remove
+       (fn [h]
+         (contains? current-and-parents (:block/uuid h)))
+       result))))
 
 
 (defn get-matched-templates
 (defn get-matched-templates
   [q]
   [q]
@@ -1980,7 +1983,7 @@
                                 true)))
                                 true)))
 
 
 (defn cycle-collapse!
 (defn cycle-collapse!
-  [_state e]
+  [e]
   (when (and
   (when (and
          ;; not input, t
          ;; not input, t
          (nil? (state/get-edit-input-id))
          (nil? (state/get-edit-input-id))
@@ -1991,7 +1994,7 @@
 
 
 (defn on-tab
 (defn on-tab
   [direction]
   [direction]
-  (fn [state e]
+  (fn [e]
     (when-let [repo (state/get-current-repo)]
     (when-let [repo (state/get-current-repo)]
       (let [blocks (seq (state/get-selection-blocks))]
       (let [blocks (seq (state/get-selection-blocks))]
         (cond
         (cond
@@ -2027,7 +2030,7 @@
                                                                 :end-pos end-pos}))]
                                                                 :end-pos end-pos}))]
                                  (reset! last-start-pos end-pos)
                                  (reset! last-start-pos end-pos)
                                  block))
                                  block))
-                             blocks))
+                          blocks))
                 file-id (:db/id (:block/file block))
                 file-id (:db/id (:block/file block))
                 file (db/entity file-id)
                 file (db/entity file-id)
                 page (:block/page block)
                 page (:block/page block)
@@ -2053,7 +2056,7 @@
           nil
           nil
 
 
           :else
           :else
-          (cycle-collapse! state e))))))
+          (cycle-collapse! e))))))
 
 
 (defn bulk-make-todos
 (defn bulk-make-todos
   [state e]
   [state e]
@@ -2094,7 +2097,7 @@
                                                               :end-pos end-pos}))]
                                                               :end-pos end-pos}))]
                                (reset! last-start-pos end-pos)
                                (reset! last-start-pos end-pos)
                                block))
                                block))
-                           blocks))
+                        blocks))
               file-id (:db/id (:block/file block))
               file-id (:db/id (:block/file block))
               file (db/entity file-id)
               file (db/entity file-id)
               page (:block/page block)
               page (:block/page block)
@@ -2112,7 +2115,7 @@
             {:key :block/change
             {:key :block/change
              :data (map (fn [block] (assoc block :block/page page)) blocks)}
              :data (map (fn [block] (assoc block :block/page page)) blocks)}
             [[file-path new-content]])))
             [[file-path new-content]])))
-        (cycle-collapse! state e)))))
+        (cycle-collapse! e)))))
 
 
 (defn- get-link
 (defn- get-link
   [format link label]
   [format link label]
@@ -2164,26 +2167,36 @@
   [block-id value]
   [block-id value]
   (set-block-property! block-id "heading" value))
   (set-block-property! block-id "heading" value))
 
 
-;; Should preserve the cursor too.
-(defn open-last-block!
-  [journal?]
-  (let [edit-id (state/get-edit-input-id)
-        last-pos (state/get-edit-pos)
-        block-id (when edit-id (subs edit-id (- (count edit-id) 36)))]
-    (let [last-edit-block (first (array-seq (js/document.getElementsByClassName block-id)))
-          first-block (first (array-seq (js/document.getElementsByClassName "ls-block")))
-          node (or last-edit-block
-                   (and (not journal?) first-block))]
+(defn open-block!
+  [first?]
+  (when-not (state/editing?)
+    (let [edit-id (state/get-last-edit-input-id)
+          block-id (when edit-id (subs edit-id (- (count edit-id) 36)))
+          last-edit-block (first (array-seq (js/document.getElementsByClassName block-id)))
+          nodes (array-seq (js/document.getElementsByClassName "ls-block"))
+          first-node (first nodes)
+          node (cond
+                 last-edit-block
+                 last-edit-block
+                 first?
+                 first-node
+                 :else
+                 (when-let [blocks-container (util/rec-get-blocks-container first-node)]
+                   (let [nodes (dom/by-class blocks-container "ls-block")]
+                     (last nodes))))]
       (when node
       (when node
+        (state/clear-selection!)
+        (unhighlight-block!)
         (let [block-id (and node (d/attr node "blockid"))
         (let [block-id (and node (d/attr node "blockid"))
               edit-block-id (string/replace (gobj/get node "id") "ls-block" "edit-block")
               edit-block-id (string/replace (gobj/get node "id") "ls-block" "edit-block")
               block-id (medley/uuid block-id)]
               block-id (medley/uuid block-id)]
-          (when-let [block (db/entity [:block/uuid block-id])]
+          (when-let [block (or (db/entity [:block/uuid block-id])
+                               {:block/uuid block-id})]
             (edit-block! block
             (edit-block! block
-                         (or (and last-edit-block last-pos)
-                             :max)
+                         :max
                          (:block/format block)
                          (:block/format block)
-                         edit-block-id)))))))
+                         edit-block-id))))
+      false)))
 
 
 (defn get-search-q
 (defn get-search-q
   []
   []
@@ -2232,3 +2245,31 @@
         value (:block/content block)
         value (:block/content block)
         new-value (string/replace value full_text new-full-text)]
         new-value (string/replace value full_text new-full-text)]
     (save-block-aux! block new-value (:block/format block) {})))
     (save-block-aux! block new-value (:block/format block) {})))
+
+(defn variable-rules
+  []
+  {"today" (util/format "[[%s]]" (date/today))
+   "yesterday" (util/format "[[%s]]" (date/yesterday))
+   "tomorrow" (util/format "[[%s]]" (date/tomorrow))
+   "time" (date/get-current-time)
+   "current page" (util/format "[[%s]]"
+                               (or (state/get-current-page)
+                                   (date/today)))})
+
+;; TODO: programmable
+;; context information, date, current page
+(defn resolve-dynamic-template!
+  [content]
+  (string/replace content #"<%([^%].*?)%>"
+                  (fn [[_ match]]
+                    (let [match (string/trim match)]
+                      (cond
+                       (string/blank? match)
+                       ""
+                       (get (variable-rules) (string/lower-case match))
+                       (get (variable-rules) (string/lower-case match))
+                       :else
+                       (if-let [nld (date/nld-parse match)]
+                         (let [date (tc/to-local-date-time nld)]
+                           (util/format "[[%s]]" (date/journal-name date)))
+                         match))))))

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

@@ -157,7 +157,7 @@
                       (remove empty?))]
                       (remove empty?))]
       (when (seq result)
       (when (seq result)
         (let [[pages block-ids blocks] (apply map concat result)
         (let [[pages block-ids blocks] (apply map concat result)
-              block-ids-set (set block-ids)
+              block-ids-set (set (map (fn [{:block/keys [uuid]}] [:block/uuid uuid]) block-ids))
               ;; To prevent "unique constraint" on datascript
               ;; To prevent "unique constraint" on datascript
               pages-index (map #(select-keys % [:page/name]) pages)
               pages-index (map #(select-keys % [:page/name]) pages)
               blocks (map (fn [b]
               blocks (map (fn [b]

+ 2 - 4
src/main/frontend/handler/file.cljs

@@ -180,8 +180,7 @@
         (reset-file! repo path content))
         (reset-file! repo path content))
       (db/set-file-content! repo path content))
       (db/set-file-content! repo path content))
     (util/p-handle
     (util/p-handle
-     (fs/write-file! repo (config/get-repo-dir repo) path content {:old-content original-content
-                                                                   :last-modified-at (db/get-file-last-modified-at repo path)})
+     (fs/write-file! repo (config/get-repo-dir repo) path content {:old-content original-content})
      (fn [_]
      (fn [_]
        (git-handler/git-add repo path update-status?)
        (git-handler/git-add repo path update-status?)
        (when (= path (config/get-config-path repo))
        (when (= path (config/get-config-path repo))
@@ -245,8 +244,7 @@
                        (let [original-content (get file->content path)]
                        (let [original-content (get file->content path)]
                          (-> (p/let [_ (nfs/check-directory-permission! repo)]
                          (-> (p/let [_ (nfs/check-directory-permission! repo)]
                                (fs/write-file! repo (config/get-repo-dir repo) path content
                                (fs/write-file! repo (config/get-repo-dir repo) path content
-                                               {:old-content original-content
-                                                :last-modified-at (db/get-file-last-modified-at repo path)}))
+                                               {:old-content original-content}))
                              (p/catch (fn [error]
                              (p/catch (fn [error]
                                         (log/error :write-file/failed {:path path
                                         (log/error :write-file/failed {:path path
                                                                        :content content
                                                                        :content content

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

@@ -37,7 +37,8 @@
                        path)]
                        path)]
             (util/p-handle
             (util/p-handle
              (fs/read-file (config/get-repo-dir (state/get-current-repo))
              (fs/read-file (config/get-repo-dir (state/get-current-repo))
-                           path)
+                           path
+                           {})
              (fn [blob]
              (fn [blob]
                (let [blob (js/Blob. (array blob) (clj->js {:type "image"}))
                (let [blob (js/Blob. (array blob) (clj->js {:type "image"}))
                      img-url (image/create-object-url blob)]
                      img-url (image/create-object-url blob)]

+ 8 - 5
src/main/frontend/handler/search.cljs

@@ -3,14 +3,17 @@
             [frontend.state :as state]
             [frontend.state :as state]
             [goog.dom :as gdom]
             [goog.dom :as gdom]
             [frontend.search :as search]
             [frontend.search :as search]
-            [frontend.handler.notification :as notification-handler]))
+            [frontend.handler.notification :as notification-handler]
+            [promesa.core :as p]))
 
 
 (defn search
 (defn search
   [q]
   [q]
-  (swap! state/state assoc :search/result
-         {:pages (search/page-search q)
-          :files (search/file-search q)
-          :blocks (search/search q 10)}))
+  ;; TODO: separate rendering for blocks
+  (p/let [blocks-result (search/block-search q 10)]
+    (swap! state/state assoc :search/result
+           {:pages (search/page-search q)
+            :files (search/file-search q)
+            :blocks blocks-result})))
 
 
 (defn clear-search!
 (defn clear-search!
   []
   []

+ 23 - 10
src/main/frontend/handler/user.cljs

@@ -8,7 +8,8 @@
             [promesa.core :as p]
             [promesa.core :as p]
             [goog.object :as gobj]
             [goog.object :as gobj]
             [frontend.handler.notification :as notification]
             [frontend.handler.notification :as notification]
-            [frontend.handler.config :as config-handler])
+            [frontend.handler.config :as config-handler]
+            [lambdaisland.glogi :as log])
   (:import [goog.format EmailAddress]))
   (:import [goog.format EmailAddress]))
 
 
 (defn email? [v]
 (defn email? [v]
@@ -63,12 +64,24 @@
                  (fn [_e])))))
                  (fn [_e])))))
 
 
 (defn sign-out!
 (defn sign-out!
-  [_e]
-  (when (js/confirm "Your local notes will be completely removed after signing out. Continue?")
-    (->
-     (idb/clear-local-storage-and-idb!)
-     (p/catch (fn [e]
-                (println "sign out error: ")
-                (js/console.dir e)))
-     (p/finally (fn []
-                  (set! (.-href js/window.location) "/logout"))))))
+  ([]
+   (sign-out! true))
+  ([confirm?]
+   (when (or (not confirm?)
+             (js/confirm "Your local notes will be completely removed after signing out. Continue?"))
+     (->
+      (idb/clear-local-storage-and-idb!)
+      (p/catch (fn [e]
+                 (println "sign out error: ")
+                 (js/console.dir e)))
+      (p/finally (fn []
+                   (set! (.-href js/window.location) "/logout")))))))
+
+(defn delete-account!
+  []
+  (p/let [_ (idb/clear-local-storage-and-idb!)]
+    (util/delete (str config/api "account")
+                 (fn []
+                   (sign-out! false))
+                 (fn [error]
+                   (log/error :user/delete-account-failed error)))))

+ 23 - 18
src/main/frontend/history.cljs

@@ -7,7 +7,8 @@
             [goog.dom :as gdom]
             [goog.dom :as gdom]
             [goog.object :as gobj]
             [goog.object :as gobj]
             [clojure.string :as string]
             [clojure.string :as string]
-            [frontend.util :as util]))
+            [frontend.util :as util]
+            [frontend.handler.ui :as ui-handler]))
 
 
 ;; Undo && Redo that works with files
 ;; Undo && Redo that works with files
 
 
@@ -21,7 +22,7 @@
 ;; repo -> idx
 ;; repo -> idx
 (defonce history-idx (atom {}))
 (defonce history-idx (atom {}))
 
 
-(defonce history-limit 500)
+(defonce history-limit 100)
 
 
 ;; tx [[file1-path patches] [file2-path patches]]
 ;; tx [[file1-path patches] [file2-path patches]]
 (defn add-history!
 (defn add-history!
@@ -30,10 +31,9 @@
                 (remove (fn [[_ old new]] (= old new)))
                 (remove (fn [[_ old new]] (= old new)))
                 (map (fn [[file old new]]
                 (map (fn [[file old new]]
                        (when (and old new)
                        (when (and old new)
-                         (let [diffs (diff/diffs new old)
-                              patches (diff/get-patches new old diffs)]
-                           [file patches {:old old
-                                          :new new}]))))
+                         (let [diffs (diff/diffs new old)]
+                           [file {:old old
+                                  :new new}]))))
                 (remove nil?))]
                 (remove nil?))]
     (when (seq tx)
     (when (seq tx)
       (let [last-edit-block (get @state/state :editor/last-edit-block)
       (let [last-edit-block (get @state/state :editor/last-edit-block)
@@ -74,12 +74,11 @@
             tx (get-in @history [repo idx'])
             tx (get-in @history [repo idx'])
             {:keys [data]} tx
             {:keys [data]} tx
             _ (reset! *undoing? true)
             _ (reset! *undoing? true)
-            promises (for [[path patches] data]
-                       (let [current-content (db/get-file-no-sub path)
-                             original-content (diff/apply-patches! current-content patches)]
-                         (alter-file repo path original-content
+            promises (for [[path {:keys [old]}] data]
+                       (let [current-content (db/get-file-no-sub path)]
+                         (alter-file repo path old
                                      {:add-history? false
                                      {:add-history? false
-                                      :re-render-root? true})))]
+                                      :re-render-root? false})))]
         (-> (p/all promises)
         (-> (p/all promises)
             (p/then (fn []
             (p/then (fn []
                       (db/clear-query-state!)
                       (db/clear-query-state!)
@@ -88,7 +87,8 @@
                       ;; restore cursor
                       ;; restore cursor
                       (when (> idx' 0)
                       (when (> idx' 0)
                         (let [prev-tx (get-in @history [repo (dec idx')])]
                         (let [prev-tx (get-in @history [repo (dec idx')])]
-                          (when restore-cursor (restore-cursor prev-tx)))))))))))
+                          (when restore-cursor (restore-cursor prev-tx))))
+                      (ui-handler/re-render-root!))))))))
 
 
 (defonce *redoing? (atom false))
 (defonce *redoing? (atom false))
 (defn redo!
 (defn redo!
@@ -98,17 +98,22 @@
     (when (and (> (count txs) idx) (false? @*redoing?))
     (when (and (> (count txs) idx) (false? @*redoing?))
       (let [tx (get-in @history [repo idx])
       (let [tx (get-in @history [repo idx])
             _ (reset! *redoing? true)
             _ (reset! *redoing? true)
-            promises (for [[path patches] (:data tx)]
-                       (let [current-content (db/get-file-no-sub path)
-                             reversed-patches (utils/reversePatch patches)
-                             content (diff/apply-patches! current-content reversed-patches)]
-                         (alter-file repo path content
+            promises (for [[path {:keys [new]}] (:data tx)]
+                       (let [current-content (db/get-file-no-sub path)]
+                         (alter-file repo path new
                                      {:add-history? false
                                      {:add-history? false
-                                      :re-render-root? true})))]
+                                      :re-render-root? false})))]
         (-> (p/all promises)
         (-> (p/all promises)
             (p/then (fn []
             (p/then (fn []
                       (db/clear-query-state!)
                       (db/clear-query-state!)
                       (swap! history-idx assoc repo (inc idx))
                       (swap! history-idx assoc repo (inc idx))
                       (reset! *redoing? false)
                       (reset! *redoing? false)
+                      (ui-handler/re-render-root!)
                       ;; restore cursor
                       ;; restore cursor
                       (when restore-cursor (restore-cursor tx)))))))))
                       (when restore-cursor (restore-cursor tx)))))))))
+
+(comment
+  (prn
+   {:history-idx (get @history-idx (frontend.state/get-current-repo))
+    :history (get @history (frontend.state/get-current-repo))})
+  )

+ 59 - 75
src/main/frontend/keyboards.cljs

@@ -12,73 +12,9 @@
             ["mousetrap" :as mousetrap]
             ["mousetrap" :as mousetrap]
             [goog.object :as gobj]))
             [goog.object :as gobj]))
 
 
-;; KeyCodes.QUESTION_MARK
-
 ;; Credits to roamresearch
 ;; Credits to roamresearch
 
 
-;; Triggers
-;; Slash Autocomplete /
-;; Block Insert Autocomplete <
-;; Page reference Autocomplete [[]]
-;; Block Reference Autocomplete (())
-
-;; Key Commands (working with lists)
-;; Indent Block Tab
-;; Unindent Block Shift-Tab
-;; Create New Block Enter
-;; New Line in Block Shift-Enter
-;; Undo Ctrl-z
-;; Redo Ctrl-y
-;; Zoom In Alt-Right
-;; Zoom out Alt-left
-;; Follow link under cursor Ctrl-o
-;; Open link in Sidebar Ctrl-shift-o
-;; Select Block Above Shift-Up
-;; Select Block Below Shift-Down
-;; Select All Blocks Ctrl-Shift-a
-
-;; General
-;; Full Text Search
-;; Open Link in Sidebar
-;; Context Menu
-;; Jump to Journals
-
-;; Formatting
-;; Bold Ctrl-b
-;; Italics Ctrl-i
-;; Html Link Ctrl-k
-;; Highlight Ctrl-h
-
-;; Markdown
-;; Block
-
-(def keyboards
-  (->>
-   {"tab" (editor-handler/on-tab :right)
-    "shift+tab" (editor-handler/on-tab :left)
-    "ctrl+z" history-handler/undo!
-    "ctrl+y" history-handler/redo!
-    "ctrl+u" route-handler/go-to-search!
-    "alt+j" route-handler/go-to-journals!
-    (or (state/get-shortcut :editor/zoom-in)
-        (if util/mac? "alt+." "alt+right")) editor-handler/zoom-in!
-    (or (state/get-shortcut :editor/zoom-out)
-        (if util/mac? "alt+," "alt+left")) editor-handler/zoom-out!
-    "ctrl+enter" editor-handler/cycle-todo!
-    "ctrl+down" editor-handler/expand!
-    "ctrl+up" editor-handler/collapse!
-    "ctrl+o" editor-handler/follow-link-under-cursor!
-    "ctrl+shift+o" editor-handler/open-link-in-sidebar!
-    "ctrl+b" editor-handler/bold-format!
-    "ctrl+i" editor-handler/italics-format!
-    "ctrl+k" editor-handler/html-link-format!
-    "ctrl+h" editor-handler/highlight-format!
-    "ctrl+shift+a" editor-handler/select-all-blocks!
-    "alt+shift+up" (fn [state e] (editor-handler/move-up-down e true))
-    "alt+shift+down" (fn [state e] (editor-handler/move-up-down e false))}
-   (medley/map-keys util/->system-modifier)))
-
-(defn chord-aux
+(defn prevent-default-behavior
   [f]
   [f]
   (fn [state e]
   (fn [state e]
     (f state e)
     (f state e)
@@ -86,20 +22,68 @@
     ;; and stop event from bubbling
     ;; and stop event from bubbling
     false))
     false))
 
 
+(defn enable-when-not-editing-mode!
+  [f]
+  (fn [state e]
+    (when-not (state/editing?)
+      (f state e))))
+
+(def shortcut state/get-shortcut)
+
 (defonce chords
 (defonce chords
-  {;; Toggle
-   "t d" state/toggle-document-mode!
-   "t t" state/toggle-theme!
-   "t r" ui-handler/toggle-right-sidebar!
-   "t e" state/toggle-new-block-shortcut!
-   "s" (chord-aux route-handler/toggle-between-page-and-file!)
-   "mod+s" (chord-aux editor-handler/save!)
-   "mod+c mod+s" (chord-aux search-handler/rebuild-indices!)
-   "mod+c mod+b" (chord-aux config-handler/toggle-ui-show-brackets!)})
+  {
+   ;; non-editing mode
+   (or (shortcut :editor/toggle-document-mode) "t d")
+   (enable-when-not-editing-mode! state/toggle-document-mode!)
+   (or (shortcut :ui/toggle-theme) "t t")
+   (enable-when-not-editing-mode! state/toggle-theme!)
+   (or (shortcut :ui/toggle-right-sidebar) "t r")
+   (enable-when-not-editing-mode! ui-handler/toggle-right-sidebar!)
+   (or (shortcut :ui/toggle-new-block) "t e")
+   (enable-when-not-editing-mode! state/toggle-new-block-shortcut!)
+   (or (shortcut :ui/toggle-between-page-and-file) "s")
+   (enable-when-not-editing-mode! route-handler/toggle-between-page-and-file!)
+   "tab" (-> (editor-handler/on-tab :right)
+             enable-when-not-editing-mode!)
+   "shift+tab" (-> (editor-handler/on-tab :left)
+                   enable-when-not-editing-mode!)
+
+   (or (shortcut :editor/undo) "mod+z") [history-handler/undo! true]
+   (or (shortcut :editor/redo) "mod+y") [history-handler/redo! true]
+   (or (shortcut :go/search) "mod+u") [route-handler/go-to-search! true]
+   (or (shortcut :go/journals) (if util/mac? "mod+j" "alt+j")) [route-handler/go-to-journals! true]
+   (or (shortcut :editor/zoom-in)
+       (if util/mac? "mod+." "alt+right")) [editor-handler/zoom-in! true]
+   (or (shortcut :editor/zoom-out)
+       (if util/mac? "mod+," "alt+left")) [editor-handler/zoom-out! true]
+   (or (shortcut :editor/cycle-todo)
+       "mod+enter") [editor-handler/cycle-todo! true]
+   (or (shortcut :editor/expand-block-children) "mod+down") [editor-handler/expand! true]
+   (or (shortcut :editor/collapse-block-children) "mod+up") [editor-handler/collapse! true]
+   (or (shortcut :editor/follow-link) "mod+o") [editor-handler/follow-link-under-cursor! true]
+   (or (shortcut :editor/open-link-in-sidebar) "mod+shift+o") [editor-handler/open-link-in-sidebar! true]
+   (or (shortcut :editor/bold) "mod+b") [editor-handler/bold-format! true]
+   (or (shortcut :editor/italics) "mod+i") [editor-handler/italics-format! true]
+   (or (shortcut :editor/highlight) "mod+h") [editor-handler/highlight-format! true]
+   (or (shortcut :editor/insert-link) "mod+k") [editor-handler/html-link-format! true]
+   (or (shortcut :editor/select-all-blocks) "mod+shift+a") [editor-handler/select-all-blocks! true]
+   (or (shortcut :editor/move-block-up) (if util/mac? "mod+shift+up" "alt+shift+up")) [(fn [state e] (editor-handler/move-up-down e true)) true]
+   (or (shortcut :editor/move-block-down) (if util/mac? "mod+shift+down" "alt+shift+down")) [(fn [state e] (editor-handler/move-up-down e false)) true]
+   (or (shortcut :editor/save) "mod+s") [editor-handler/save! true]
+
+   (or (shortcut :editor/next) "down") (fn [state e] (editor-handler/open-block! true))
+   (or (shortcut :editor/prev) "up") (fn [state e] (editor-handler/open-block! false))
+
+   (or (shortcut :search/re-index) "mod+c mod+s") [search-handler/rebuild-indices! true]
+   (or (shortcut :ui/toggle-brackets) "mod+c mod+b") [config-handler/toggle-ui-show-brackets! true]})
 
 
 (defonce bind! (gobj/get mousetrap "bind"))
 (defonce bind! (gobj/get mousetrap "bind"))
 
 
 (defn bind-shortcuts!
 (defn bind-shortcuts!
   []
   []
   (doseq [[k f] chords]
   (doseq [[k f] chords]
-    (bind! k f)))
+    (let [[f prevent?] (if (coll? f)
+                         f
+                         [f false])
+          f (if prevent? (prevent-default-behavior f) f)]
+      (bind! k f))))

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

@@ -187,33 +187,6 @@
             (f))
             (f))
           (dissoc state k)))})))
           (dissoc state k)))})))
 
 
-(defn keyboards-mixin
-  ([m] (keyboards-mixin m (fn [_] true) js/window))
-  ([m enable-f] (keyboards-mixin m enable-f js/window))
-  ([m enable-f target]
-   (when (seq m)
-     (let [target-fn (if (fn? target) target (fn [_] target))]
-       {:did-mount
-        (fn [state]
-          (if (enable-f state)
-            (let [keyboards (doall
-                             (map
-                              (fn [[key f]]
-                                [key
-                                 (keyboard/install-shortcut! key
-                                                             (fn [e] (f state e))
-                                                             false
-                                                             (target-fn state))])
-                              m))]
-              (assoc state ::keyboards-listener keyboards))
-            state))
-        :will-unmount
-        (fn [state]
-          (when (enable-f state)
-            (doseq [[_k f] (get state ::keyboards-listener)]
-              (f)))
-          state)}))))
-
 ;; also, from https://github.com/tonsky/rum/blob/75174b9ea0cf4b7a761d9293929bd40c95d35f74/doc/useful-mixins.md
 ;; also, from https://github.com/tonsky/rum/blob/75174b9ea0cf4b7a761d9293929bd40c95d35f74/doc/useful-mixins.md
 
 
 
 

+ 52 - 40
src/main/frontend/search.cljs

@@ -12,25 +12,31 @@
             [cljs-bean.core :as bean]
             [cljs-bean.core :as bean]
             [goog.object :as gobj]
             [goog.object :as gobj]
             ["fuzzysort" :as fuzzy]
             ["fuzzysort" :as fuzzy]
-            [medley.core :as medley]))
+            ["flexsearch" :as flexsearch]
+            [medley.core :as medley]
+            [promesa.core :as p]
+            ["/frontend/utils" :as utils]))
 
 
 (def fuzzy-go (gobj/get fuzzy "go"))
 (def fuzzy-go (gobj/get fuzzy "go"))
 (defonce prepare (gobj/get fuzzy "prepare"))
 (defonce prepare (gobj/get fuzzy "prepare"))
 (defonce highlight (gobj/get fuzzy "highlight"))
 (defonce highlight (gobj/get fuzzy "highlight"))
 
 
 (defn go
 (defn go
-  [q indice opts]
-  (fuzzy-go q indice opts))
+  [q indice-type indice opts]
+  (case indice-type
+    :page
+    (fuzzy-go q indice opts)
+
+    :block
+    (.search indice q opts)))
 
 
 (defn block->index
 (defn block->index
   [{:block/keys [uuid content format] :as block}]
   [{:block/keys [uuid content format] :as block}]
-  (when (<= (count content) 1000) ; performance
-    (when-let [result (->> (text/remove-level-spaces content format)
-                           (text/remove-properties!)
-                           (prepare))]
-      (gobj/set result "id" (:db/id block))
-      (gobj/set result "uuid" (str uuid))
-      result)))
+  (when-let [result (->> (text/remove-level-spaces content format)
+                         (text/remove-properties!))]
+    {:id (:db/id block)
+     :uuid (str uuid)
+     :content result}))
 
 
 (defn make-blocks-indice!
 (defn make-blocks-indice!
   []
   []
@@ -38,9 +44,17 @@
     (let [blocks (->> (db/get-all-block-contents)
     (let [blocks (->> (db/get-all-block-contents)
                       (map block->index)
                       (map block->index)
                       (remove nil?)
                       (remove nil?)
-                      (bean/->js))]
-      (swap! indices assoc-in [repo :blocks] blocks)
-      blocks)))
+                      (bean/->js))
+          indice (flexsearch.
+                  (clj->js
+                   {:encode "icase"
+                    :tokenize utils/searchTokenize
+                    :doc {:id "id"
+                          :field ["content"]}
+                    :async true}))]
+      (p/let [result (.add indice blocks)]
+        (swap! indices assoc-in [repo :blocks] indice))
+      indice)))
 
 
 (defn make-pages-indice!
 (defn make-pages-indice!
   []
   []
@@ -136,10 +150,9 @@
                                      :score (score query (.toLowerCase s))})))))
                                      :score (score query (.toLowerCase s))})))))
          (map :data))))
          (map :data))))
 
 
-(defn search
-  "Block search"
+(defn block-search
   ([q]
   ([q]
-   (search q 10))
+   (block-search q 10))
   ([q limit]
   ([q limit]
    (when-let [repo (state/get-current-repo)]
    (when-let [repo (state/get-current-repo)]
      (when-not (string/blank? q)
      (when-not (string/blank? q)
@@ -147,21 +160,17 @@
              q (escape-str q)]
              q (escape-str q)]
          (when-not (string/blank? q)
          (when-not (string/blank? q)
            (let [indice (or (get-in @indices [repo :blocks])
            (let [indice (or (get-in @indices [repo :blocks])
-                            (make-blocks-indice!))
-                 result (->
-                         (go q indice (clj->js {:limit limit
-                                                :allowTypo false
-                                                :threshold -10000}))
-                         (bean/->clj))]
-             (->>
-              (map
-               (fn [{:keys [target uuid indexes]}]
-                 {:block/uuid uuid
-                  :block/content target
-                  :block/page (:block/page (db/entity [:block/uuid (medley/uuid (str uuid))]))
-                  :block/indexes indexes}) ; For result highlight
-               result)
-              (remove nil?)))))))))
+                            (make-blocks-indice!))]
+             (p/let [result (go q :block indice (clj->js {:limit limit}))
+                     result (bean/->clj result)]
+               (->>
+                (map
+                 (fn [{:keys [content uuid] :as block}]
+                   {:block/uuid uuid
+                    :block/content content
+                    :block/page (:block/page (db/entity [:block/uuid (medley/uuid (str uuid))]))})
+                 result)
+                (remove nil?))))))))))
 
 
 (defn page-search
 (defn page-search
   ([q]
   ([q]
@@ -173,11 +182,12 @@
        (when-not (string/blank? q)
        (when-not (string/blank? q)
          (let [indice (or (get-in @indices [repo :pages])
          (let [indice (or (get-in @indices [repo :pages])
                           (make-pages-indice!))
                           (make-pages-indice!))
-               result (->> (go q indice (clj->js {:limit limit
-                                                  :key "name"
-                                                  :allowTypo false
-                                                  :threshold -10000}))
+               result (->> (go q :page indice (clj->js {:limit limit
+                                                        :key "name"
+                                                        :allowTypo false
+                                                        :threshold -10000}))
                            (bean/->clj))]
                            (bean/->clj))]
+           ;; TODO: add indexes for highlights
            (->> (map
            (->> (map
                  (fn [{:keys [obj]}]
                  (fn [{:keys [obj]}]
                    (:name obj))
                    (:name obj))
@@ -250,8 +260,10 @@
                                           (map :e)
                                           (map :e)
                                           (set))]
                                           (set))]
             (swap! search-db/indices update-in [repo :blocks]
             (swap! search-db/indices update-in [repo :blocks]
-                   (fn [blocks]
-                     (let [blocks (or blocks (array))
-                           blocks (.filter blocks (fn [block]
-                                                    (not (contains? blocks-to-remove-set (gobj/get block "id")))))]
-                       (.concat blocks (bean/->js blocks-to-add)))))))))))
+                   (fn [indice]
+                     (when indice
+                       (doseq [block-id blocks-to-remove-set]
+                         (.remove indice #js {:id block-id}))
+                       (when (seq blocks-to-add)
+                         (.add indice (bean/->js blocks-to-add))))
+                     indice))))))))

+ 23 - 0
src/main/frontend/state.cljs

@@ -71,6 +71,8 @@
     :editor/show-input nil
     :editor/show-input nil
     :editor/last-saved-cursor nil
     :editor/last-saved-cursor nil
     :editor/editing? nil
     :editor/editing? nil
+    :editor/last-edit-block-id nil
+    :editor/in-composition? false
     :editor/pos 0
     :editor/pos 0
     :editor/content {}
     :editor/content {}
     :editor/block nil
     :editor/block nil
@@ -370,6 +372,10 @@
   []
   []
   (ffirst (:editor/editing? @state)))
   (ffirst (:editor/editing? @state)))
 
 
+(defn get-last-edit-input-id
+  []
+  (:editor/last-edit-block-id @state))
+
 (defn editing?
 (defn editing?
   []
   []
   (some? (get-edit-input-id)))
   (some? (get-edit-input-id)))
@@ -666,6 +672,7 @@
                    (assoc
                    (assoc
                     :editor/block block
                     :editor/block block
                     :editor/editing? {edit-input-id true}
                     :editor/editing? {edit-input-id true}
+                    :editor/last-edit-block-id edit-input-id
                     :cursor-range cursor-range)))))))
                     :cursor-range cursor-range)))))))
 
 
 (defn clear-edit!
 (defn clear-edit!
@@ -981,6 +988,14 @@
   [value]
   [value]
   (set-state! :graph/syncing? value))
   (set-state! :graph/syncing? value))
 
 
+(defn set-editor-in-composition!
+  [value]
+  (set-state! :editor/in-composition? value))
+
+(defn editor-in-composition?
+  []
+  (:editor/in-composition? @state))
+
 (defn set-loading-files!
 (defn set-loading-files!
   [value]
   [value]
   (set-state! :repo/loading-files? value))
   (set-state! :repo/loading-files? value))
@@ -1066,6 +1081,14 @@
   []
   []
   (:nfs/refreshing? @state))
   (:nfs/refreshing? @state))
 
 
+(defn set-search-result!
+  [value]
+  (set-state! :search/result value))
+
+(defn clear-search-result!
+  []
+  (set-search-result! nil))
+
 ;; TODO: Move those to the uni `state`
 ;; TODO: Move those to the uni `state`
 
 
 (defonce editor-op (atom nil))
 (defonce editor-op (atom nil))

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

@@ -28,16 +28,16 @@
                            (state/sub :editor/show-page-search?)
                            (state/sub :editor/show-page-search?)
                            (state/sub :editor/show-block-search?)
                            (state/sub :editor/show-block-search?)
                            (state/sub :editor/show-template-search?))
                            (state/sub :editor/show-template-search?))
-        composition? (atom false)
-        set-composition? #(reset! composition? %)
         on-composition (fn [e]
         on-composition (fn [e]
                          (if skip-composition?
                          (if skip-composition?
                            (on-change e)
                            (on-change e)
                            (case e.type
                            (case e.type
-                             "compositionend" (do (set-composition? false) (on-change e))
-                             (set-composition? true))))
+                             "compositionend" (do
+                                                (state/set-editor-in-composition! false)
+                                                (on-change e))
+                             (state/set-editor-in-composition! true))))
         props (assoc props
         props (assoc props
-                     :on-change (fn [e] (when-not @composition?
+                     :on-change (fn [e] (when-not (state/editor-in-composition?)
                                           (on-change e)))
                                           (on-change e)))
                      :on-composition-start on-composition
                      :on-composition-start on-composition
                      :on-composition-update on-composition
                      :on-composition-update on-composition

+ 12 - 12
src/main/frontend/util.cljc

@@ -8,6 +8,7 @@
       #?(:cljs ["/frontend/caret_pos" :as caret-pos])
       #?(:cljs ["/frontend/caret_pos" :as caret-pos])
       #?(:cljs ["/frontend/selection" :as selection])
       #?(:cljs ["/frontend/selection" :as selection])
       #?(:cljs ["/frontend/utils" :as utils])
       #?(:cljs ["/frontend/utils" :as utils])
+      #?(:cljs ["path" :as nodePath])
       #?(:cljs [goog.dom :as gdom])
       #?(:cljs [goog.dom :as gdom])
       #?(:cljs [goog.object :as gobj])
       #?(:cljs [goog.object :as gobj])
       #?(:cljs [goog.string :as gstring])
       #?(:cljs [goog.string :as gstring])
@@ -29,7 +30,7 @@
       (-pr-writer [sym writer _]
       (-pr-writer [sym writer _]
         (-write writer (str "\"" (.toString sym) "\"")))))
         (-write writer (str "\"" (.toString sym) "\"")))))
 
 
-;; doms
+#?(:cljs (defonce ^js node-path nodePath))
 #?(:cljs (defn app-scroll-container-node []  js/document.documentElement))
 #?(:cljs (defn app-scroll-container-node []  js/document.documentElement))
 
 
 #?(:cljs
 #?(:cljs
@@ -399,20 +400,20 @@
                           :behavior "smooth"}))))))
                           :behavior "smooth"}))))))
 
 
 #?(:cljs
 #?(:cljs
-    (defn scroll-to
-      ([pos]
-       (scroll-to pos true))
-      ([pos animate?]
-       (scroll-to (app-scroll-container-node) pos animate?))
-      ([node pos animate?]
-       (.scroll node
-                #js {:top      pos
-                     :behavior (if animate? "smooth" "auto")}))))
+   (defn scroll-to
+     ([pos]
+      (scroll-to (app-scroll-container-node) pos))
+     ([node pos]
+      (scroll-to node pos true))
+     ([node pos animate?]
+      (.scroll node
+               #js {:top      pos
+                    :behavior (if animate? "smooth" "auto")}))))
 
 
 #?(:cljs
 #?(:cljs
     (defn scroll-to-top
     (defn scroll-to-top
       []
       []
-      (scroll-to 0 false)))
+      (scroll-to (app-scroll-container-node) 0 false)))
 
 
 (defn url-encode
 (defn url-encode
   [string]
   [string]
@@ -1014,7 +1015,6 @@
 (defn page-name-sanity
 (defn page-name-sanity
   [page-name]
   [page-name]
   (-> page-name
   (-> page-name
-      (string/replace #"\s+" "_")
       ;; Windows reserved path characters
       ;; Windows reserved path characters
       (string/replace #"[\\/:\\*\\?\"<>|]+" "_")))
       (string/replace #"[\\/:\\*\\?\"<>|]+" "_")))
 
 

+ 11 - 1
src/main/frontend/utils.js

@@ -203,4 +203,14 @@ export const win32 = path => {
 
 
   // UNC paths are always absolute
   // UNC paths are always absolute
   return Boolean(result[2] || isUnc);
   return Boolean(result[2] || isUnc);
-}
+};
+
+export const searchTokenize = str => {
+  let ascii_words = str.split(/\W+/);
+  let non_ascii_str = str.replace(/[\x00-\x7F]/g, '');
+  if (non_ascii_str == '') {
+    return ascii_words;
+  } else {
+    return ascii_words.concat(non_ascii_str.split('')).filter(e => !!e);
+  }
+};

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

@@ -1,3 +1,3 @@
 (ns frontend.version)
 (ns frontend.version)
 
 
-(defonce version "0.0.7")
+(defonce version "0.0.8")

+ 22 - 7
yarn.lock

@@ -1101,6 +1101,13 @@ chokidar@^3.3.0, chokidar@^3.3.1, chokidar@^3.5.1:
   optionalDependencies:
   optionalDependencies:
     fsevents "~2.3.1"
     fsevents "~2.3.1"
 
 
+chrono-node@^2.2.1:
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/chrono-node/-/chrono-node-2.2.1.tgz#6591c95724011fd7cadd8ae394bacb5004357bf2"
+  integrity sha512-Cdw4LxAlVb3BbfoAIw50v8MhVmFGBzzvTpQUO3F5ezyo/MnZ3db3G73cHNE2aiNsAhK9qpg39RpUl6jxTIUGeA==
+  dependencies:
+    dayjs "^1.10.0"
+
 cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
 cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
   version "1.0.4"
   version "1.0.4"
   resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
   resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
@@ -1605,6 +1612,11 @@ d@1, d@^1.0.1:
     es5-ext "^0.10.50"
     es5-ext "^0.10.50"
     type "^1.0.1"
     type "^1.0.1"
 
 
+dayjs@^1.10.0:
+  version "1.10.4"
+  resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2"
+  integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw==
+
 debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
 debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
   version "2.6.9"
   version "2.6.9"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
   resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -2298,6 +2310,10 @@ flatted@^3.1.0:
   resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469"
   resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.1.1.tgz#c4b489e80096d9df1dfc97c79871aea7c617c469"
   integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==
   integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==
 
 
+"flexsearch@git+https://github.com/logseq/flexsearch":
+  version "0.6.32"
+  resolved "git+https://github.com/logseq/flexsearch#0a04c518ef0a9b3c76e18da893642835f98d5616"
+
 flush-write-stream@^1.0.2:
 flush-write-stream@^1.0.2:
   version "1.1.1"
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
   resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
@@ -2380,10 +2396,9 @@ function-bind@^1.1.1:
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
   resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
   integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
   integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
 
 
-fuzzysort@^1.1.4:
+"fuzzysort@git+https://github.com/getstation/fuzzysort#a66f5813825d2415b606cc69129070c4eb612ae2":
   version "1.1.4"
   version "1.1.4"
-  resolved "https://registry.yarnpkg.com/fuzzysort/-/fuzzysort-1.1.4.tgz#a0510206ed44532cbb52cf797bf5a3cb12acd4ba"
-  integrity sha512-JzK/lHjVZ6joAg3OnCjylwYXYVjRiwTY6Yb25LvfpJHK8bjisfnZJ5bY8aVWwTwCXgxPNgLAtmHL+Hs5q1ddLQ==
+  resolved "git+https://github.com/getstation/fuzzysort#a66f5813825d2415b606cc69129070c4eb612ae2"
 
 
 gensync@^1.0.0-beta.1:
 gensync@^1.0.0-beta.1:
   version "1.0.0-beta.2"
   version "1.0.0-beta.2"
@@ -3894,10 +3909,10 @@ mkdirp@^0.5.4, mkdirp@~0.5.1:
   dependencies:
   dependencies:
     minimist "^1.2.5"
     minimist "^1.2.5"
 
 
-mldoc@^0.3.8:
-  version "0.3.8"
-  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-0.3.8.tgz#1e4500ab50f1bb7a6cb292a80d1e9ab92c4d48cc"
-  integrity sha512-b2qieoYJ71E+c4TVOIcylh87COl6Rxt6du4o3mB1yL0qDSfK1OW6XICGhYVrJi8MO5ioscf3Y4aRyWGYmwCBlQ==
+mldoc@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/mldoc/-/mldoc-0.4.0.tgz#3e372ec2a40856084d9051882fef609546b48174"
+  integrity sha512-FLb09RfqWltA3XEhUzLfxZFjGtbnfxhCCtSoB+AQo37WnpuDvPldGgmw8VtRuv05vvNh89q/onQ+8bYllWI8bQ==
   dependencies:
   dependencies:
     yargs "^12.0.2"
     yargs "^12.0.2"