瀏覽代碼

Merge branch 'master' into feat/integration-plugins-core

charlie 4 年之前
父節點
當前提交
012b5e022d

+ 3 - 1
README.md

@@ -15,7 +15,8 @@ Use it to organize your todo list, to write your journals, or to record your uni
 
 ## Why Logseq?
 
-[Logseq](https://logseq.com) is a platform for knowledge management and collaboration. It focuses on privacy, longevity, and user control.
+[Logseq](https://logseq.com) is a platform for knowledge management and collaboration. It focuses on privacy, longevity, and [user control](https://www.gnu.org/philosophy/free-sw.en.html).
+
 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).
@@ -66,6 +67,7 @@ Logseq is also made possible by the following projects:
 - Our blog: https://logseq.com/blog - Please be sure to visit our [About page](https://logseq.com/blog/about) for the latest updates of the app
 - Twitter: https://twitter.com/logseq
 - Discord: https://discord.gg/KpN4eHY - Where we answer questions, discuss workflows and share tips
+- 中文 Discord:https://discord.gg/yhNKeUdj
 - Github: https://github.com/logseq/logseq - everyone is encouraged to report issues!
 
 ---

+ 2 - 2
package.json

@@ -13,7 +13,7 @@
         "gulp": "^4.0.2",
         "gulp-clean-css": "^4.3.0",
         "npm-run-all": "^4.1.5",
-        "postcss": "8.2.8",
+        "postcss": "8.2.10",
         "postcss-cli": "8.3.1",
         "postcss-import": "^14.0.0",
         "postcss-import-ext-glob": "^2.0.1",
@@ -62,7 +62,7 @@
         "@kanru/rage-wasm": "^0.2.1",
         "@tippyjs/react": "^4.2.5",
         "chokidar": "^3.5.1",
-        "chrono-node": "^2.2.1",
+        "chrono-node": "^2.2.4",
         "codemirror": "^5.58.1",
         "diff": "5.0.0",
         "diff-match-patch": "^1.0.5",

文件差異過大導致無法顯示
+ 0 - 0
resources/css/tooltip.css


+ 0 - 1
resources/electron.html

@@ -26,7 +26,6 @@
   <title>Logseq: A local-first knowledge base</title>
   <meta content="logseq" property="og:site_name">
   <meta content="A local-first knowledge base which can be synced using Git." name="description">
-  <script crossorigin="anonymous" defer onload="Sentry.init({dsn: 'https://[email protected]/5311485'});" src="https://asset.logseq.com/static/js/sentry.min.js">
   </script>
 </head>
 <body>

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

@@ -208,7 +208,11 @@
                    *quitting? (atom false)]
                (.. logger (info (str "Logseq App(" (.getVersion app) ") Starting... ")))
 
+               (when (search/version-changed?)
+                 (search/rm-search-dir!))
+
                (search/ensure-search-dir!)
+
                (search/open-dbs!)
 
                (vreset! *setup-fn

+ 16 - 0
src/electron/electron/handler.cljs

@@ -2,6 +2,7 @@
   (:require ["electron" :refer [ipcMain dialog app]]
             [cljs-bean.core :as bean]
             ["fs" :as fs]
+            ["fs-extra" :as fs-extra]
             ["path" :as path]
             ["chokidar" :as watcher]
             [promesa.core :as p]
@@ -146,6 +147,21 @@
 (defmethod handle :remove-db [window [_ repo]]
   (search/delete-db! repo))
 
+(defn clear-cache!
+  []
+  (let [path (.getPath ^object app "userData")]
+    (doseq [dir ["search" "IndexedDB" "Local Storage" "databases" "cache"]]
+      (let [path (path/join path dir)]
+        (try
+          (fs-extra/removeSync path)
+          (catch js/Error e
+            (js/console.error e)))))))
+
+(defmethod handle :clearCache [_window _]
+  (search/close!)
+  (clear-cache!)
+  (search/ensure-search-dir!))
+
 (defn- get-file-ext
   [file]
   (last (string/split file #"\.")))

+ 29 - 2
src/electron/electron/search.cljs

@@ -6,6 +6,8 @@
             [electron.utils :refer [logger] :as utils]
             ["electron" :refer [app]]))
 
+(defonce version "0.0.1")
+
 (def error (partial (.-error logger) "[Search]"))
 
 (defonce databases (atom nil))
@@ -74,10 +76,36 @@
   (let [path (.getPath ^object app "userData")]
     (path/join path "search")))
 
+(defonce search-version "search.version")
+
+(defn get-search-version
+  []
+  (let [path (.getPath ^object app "userData")
+        path (path/join path search-version)]
+    (when (fs/existsSync path)
+      (.toString (fs/readFileSync path)))))
+
+(defn write-search-version!
+  []
+  (let [path (.getPath ^object app "userData")
+        path (path/join path search-version)]
+    (fs/writeFileSync path version)))
+
+(defn version-changed?
+  []
+  (not= version (get-search-version)))
+
 (defn ensure-search-dir!
   []
+  (write-search-version!)
   (fs/ensureDirSync (get-search-dir)))
 
+(defn rm-search-dir!
+  []
+  (let [search-dir (get-search-dir)]
+    (when (fs/existsSync search-dir)
+      (fs/removeSync search-dir))))
+
 (defn get-db-full-path
   [db-name]
   (let [db-name (normalize-db-name db-name)
@@ -90,8 +118,7 @@
         db (sqlite3 db-full-path nil)
         _ (create-blocks-table! db)
         _ (create-blocks-fts-table! db)
-        _ (add-triggers! db)
-        ]
+        _ (add-triggers! db)]
     (swap! databases assoc db-name db)))
 
 (defn open-dbs!

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

@@ -537,7 +537,10 @@
                          (->elem
                           :span.block-ref
                           (map-inline config title))))]
-           (ui/tippy {:html (block-container config block)
+           (ui/tippy {:html [:div.tippy-wrapper
+                             {:style {:width 780
+                                      :text-align "left"}}
+                             (block-container config block)]
                       :interactive true}
             (if label
               (->elem

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

@@ -174,10 +174,10 @@
   [parent-state parent-id]
   [:div#mobile-editor-toolbar.bg-base-2.fix-ios-fixed-bottom
    [:button.bottom-action
-    {:on-click #(editor-handler/indent-on-tab parent-state)}
+    {:on-click #(editor-handler/indent-outdent parent-state true)}
     svg/indent-block]
    [:button.bottom-action
-    {:on-click #(editor-handler/outdent-on-shift-tab parent-state)}
+    {:on-click #(editor-handler/indent-outdent parent-state false)}
     svg/outdent-block]
    [:button.bottom-action
     {:on-click (editor-handler/move-up-down true)}

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

@@ -111,10 +111,9 @@
           :options {:href (rfe/href :all-journals)}
           :icon svg/calendar-sm})
 
-       (when current-repo
-         {:title (t :settings)
-          :options {:on-click #(ui-handler/toggle-settings-modal!)}
-          :icon svg/settings-sm})
+       {:title (t :settings)
+        :options {:on-click #(ui-handler/toggle-settings-modal!)}
+        :icon svg/settings-sm}
 
        (when developer-mode?
          {:title (t :plugins)

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

@@ -149,7 +149,6 @@
                              (= (string/lower-case search-q)
                                 (string/lower-case (:data (first pages)))))
                         (nil? result)
-                        (not= :global search-mode)
                         all?)
                      []
                      [{:type :new-page}])

+ 40 - 0
src/main/frontend/components/settings.cljs

@@ -8,6 +8,7 @@
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.config :as config-handler]
             [frontend.handler.page :as page-handler]
+            [frontend.handler :as handler]
             [frontend.state :as state]
             [frontend.version :refer [version]]
             [frontend.util :as util]
@@ -134,6 +135,15 @@
          :on-click close-fn}
         "Cancel"]]]]))
 
+(rum/defc outdenting-hint
+  []
+  [:div
+   [:p "See more details at " [:a {:href "https://discuss.logseq.com/t/whats-your-preferred-outdent-behavior-the-direct-one-or-the-logical-one/978"} "here"] "."]
+   [:p "default(left) vs logical(right)"]
+   [:img {:src "https://discuss.logseq.com/uploads/default/original/1X/e8ea82f63a5e01f6d21b5da827927f538f3277b9.gif"
+          :width 500
+          :height 500}]])
+
 (rum/defcs settings < rum/reactive
   []
   (let [preferred-format (state/get-preferred-format)
@@ -144,6 +154,8 @@
         current-repo (state/get-current-repo)
         enable-journals? (state/enable-journals? current-repo)
         enable-encryption? (state/enable-encryption? current-repo)
+        sentry-disabled? (state/sub :sentry/disabled?)
+        logical-outdenting? (state/logical-outdenting?)
         enable-git-auto-push? (state/enable-git-auto-push? current-repo)
         enable-block-time? (state/enable-block-time?)
         show-brackets? (state/show-brackets?)
@@ -271,6 +283,15 @@
                  "NOW/LATER"
                  "TODO/DOING")])]]]]
 
+        (toggle "preferred_outdenting"
+                (ui/tippy {:html (outdenting-hint)
+                           :interactive true
+                           :theme "customized"}
+                          (t :settings-page/preferred-outdenting))
+                logical-outdenting?
+                (fn []
+                  (config-handler/toggle-logical-outdenting!)))
+
         (toggle "enable_timetracking"
                 (t :settings-page/enable-timetracking)
                 enable-timetracking?
@@ -336,6 +357,18 @@
 
        [:hr]
 
+       [:div.panel-wrap
+
+        [:div.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center.sm:pt-5
+         [:label.block.text-sm.font-medium.leading-5.opacity-70
+          {:for "clear_cache"}
+          (t :settings-page/clear-cache)]
+         [:div.mt-1.sm:mt-0.sm:col-span-2
+          [:div.max-w-lg.rounded-md.sm:max-w-xs
+           (ui/button (t :settings-page/clear)
+             :on-click (fn []
+                         (handler/clear-cache!)))]]]]
+
        [:div.panel-wrap
         [:div.it.app-updater.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
          [:label.block.text-sm.font-medium.leading-5.opacity-70
@@ -344,6 +377,13 @@
           [:div.ver version]
           (if (util/electron?) (app-updater))]]
 
+        (toggle "disable_sentry"
+                (t :settings-page/disable-sentry)
+                sentry-disabled?
+                (fn []
+                  (let [value (not sentry-disabled?)]
+                    (state/set-sentry-disabled! value))))
+
         [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-start
          [:label.block.text-sm.font-medium.leading-5.opacity-70
           {:for "developer_mode"}

+ 9 - 9
src/main/frontend/context/i18n.cljs

@@ -6,11 +6,11 @@
             [frontend.state :as state]))
 
 ;; TODO
-;; - [x] Get the preffered language from state
-;; - [x] Update the preffered language
-;; - [x] Create t functiona which takes a keyword and returns text with the current preffered language
-;; - [x] Add fetch for local browser prefered language if user has set it already
-;; - [ ] Fetch prefered language from backend if user is logged in
+;; - [x] Get the preferred language from state
+;; - [x] Update the preferred language
+;; - [x] Create t functiona which takes a keyword and returns text with the current preferred language
+;; - [x] Add fetch for local browser preferred language if user has set it already
+;; - [ ] Fetch preferred language from backend if user is logged in
 
 (defn fetch-local-language []
   (.. js/window -navigator -language))
@@ -18,14 +18,14 @@
 (rum/defcontext *tongue-context*)
 
 (rum/defc tongue-provider [children]
-  (let [prefered-language (keyword (state/sub :preferred-language))
+  (let [preferred-language (keyword (state/sub :preferred-language))
         set-preferred-language state/set-preferred-language!
         all-dicts (deep-merge dicts/dicts shortcut-dict/dict)
-        t (partial (dicts/translate all-dicts) prefered-language)]
-    (if (nil? prefered-language)
+        t (partial (dicts/translate all-dicts) preferred-language)]
+    (if (nil? preferred-language)
       (set-preferred-language (fetch-local-language))
       :ok)
-    (rum/bind-context [*tongue-context* [t prefered-language set-preferred-language]]
+    (rum/bind-context [*tongue-context* [t preferred-language set-preferred-language]]
                       children)))
 
 (rum/defc use-tongue []

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

@@ -38,7 +38,7 @@
   delete-file! delete-file-blocks! delete-file-pages! delete-file-tx delete-files delete-pages-by-files
   filter-only-public-pages-and-blocks get-all-block-contents get-all-tagged-pages
   get-all-templates get-block-and-children get-block-by-uuid get-block-children sort-by-left
-  get-block-parent get-block-parents parents-collapsed? get-block-referenced-blocks get-block-refs-count
+  get-block-parent get-block-parents parents-collapsed? get-block-referenced-blocks
   get-block-children-ids get-block-immediate-children get-block-page
   get-blocks-contents get-custom-css
   get-date-scheduled-or-deadlines get-db-type get-empty-pages get-file

+ 41 - 44
src/main/frontend/db/model.cljs

@@ -17,7 +17,8 @@
             [cljs-time.coerce :as tc]
             [frontend.util :as util :refer [react] :refer-macros [profile]]
             [frontend.db-schema :as db-schema]
-            [clojure.walk :as walk]))
+            [clojure.walk :as walk]
+            [clojure.string :as string]))
 
 ;; TODO: extract to specific models and move data transform logic to the
 ;; correponding handlers.
@@ -70,6 +71,7 @@
     :block/created-at
     :block/updated-at
     :block/file
+    :block/parent
     {:block/page [:db/id :block/name :block/original-name :block/journal-day]}
     {:block/_parent ...}])
 
@@ -366,30 +368,17 @@
            distinct
            (remove #(= (string/lower-case %) (string/lower-case page-name)))))))
 
-(defn get-block-refs-count
-  [repo]
-  (->> (d/q
-        '[:find ?id2 ?id1
-          :where
-          [?id1 :block/refs ?id2]]
-        (conn/get-conn repo))
-       (map first)
-       (frequencies)))
-
 (defn with-block-refs-count
   [repo blocks]
-  (let [db-ids (map :db/id blocks)
-        refs (get-block-refs-count repo)]
-    (map (fn [block]
-           (assoc block :block/block-refs-count
-                  (get refs (:db/id block))))
-         blocks)))
+  (map (fn [block]
+         (let [refs-count (count (:block/_refs (db-utils/entity (:db/id block))))]
+           (assoc block :block/block-refs-count refs-count)))
+    blocks))
 
 (defn page-blocks-transform
   [repo-url result]
-  (let [result (db-utils/seq-flatten result)]
-    (->> (db-utils/with-repo repo-url result)
-         (with-block-refs-count repo-url))))
+  (->> (db-utils/with-repo repo-url result)
+       (with-block-refs-count repo-url)))
 
 (defn with-pages
   [blocks]
@@ -603,6 +592,13 @@
       (when (seq ids)
         (db-utils/pull-many repo '[*] ids)))))
 
+;; TODO: use the tree directly
+(defn- flatten-tree
+  [blocks-tree]
+  (if-let [children (:block/_parent blocks-tree)]
+    (cons (dissoc blocks-tree :block/_parent) (mapcat flatten-tree children))
+    [blocks-tree]))
+
 (defn get-block-and-children
   ([repo block-uuid]
    (get-block-and-children repo block-uuid true))
@@ -610,17 +606,15 @@
    (some-> (react/q repo [:block/block block-uuid]
              {:use-cache? use-cache?
               :transform-fn #(block-and-children-transform % repo block-uuid)}
-             '[:find (pull ?c [*])
-               :in $ ?id %
+             '[:find [(pull ?block ?block-attrs) ...]
+               :in $ ?id ?block-attrs
                :where
-               [?b :block/uuid ?id]
-               (or-join [?b ?c ?id]
-                        ;; including the parent
-                        [?c :block/uuid ?id]
-                        (parent ?b ?c))]
+               [?block :block/uuid ?id]]
              block-uuid
-             rules)
-           react)))
+             block-attrs)
+           react
+           first
+           flatten-tree)))
 
 (defn get-file-page
   ([file-path]
@@ -1122,6 +1116,19 @@
                    :block/content (:block/content e)
                    :block/format (:block/format e)}))))))
 
+(defn get-assets
+  [datoms]
+  (keep
+   (fn [datom]
+     (when (= :block/content (:a datom))
+       (let [matched (re-seq #"\([./]*/assets/([^)]+)\)" (:v datom))
+             matched (get (into [] matched) 0)
+             path (get matched 1)]
+         (when (and (string? path)
+                    (not (string/ends-with? path ".js")))
+           path))))
+   datoms))
+
 (defn clean-export!
   [db]
   (let [remove? #(contains? #{"me" "recent" "file"} %)
@@ -1129,8 +1136,9 @@
                               (fn [db datom]
                                 (let [ns (namespace (:a datom))]
                                   (not (remove? ns)))))
-        datoms (d/datoms filtered-db :eavt)]
-    @(d/conn-from-datoms datoms db-schema/schema)))
+        datoms (d/datoms filtered-db :eavt)
+        assets (get-assets datoms)]
+    [@(d/conn-from-datoms datoms db-schema/schema) assets]))
 
 (defn filter-only-public-pages-and-blocks
   [db]
@@ -1150,19 +1158,8 @@
                                              (contains? public-pages (:e datom))
                                              (contains? public-pages (:db/id (:block/page (d/entity db (:e datom)))))))))))
             datoms (d/datoms filtered-db :eavt)
-            public-assets-filesnames
-            (keep
-             (fn [datom]
-
-               (if (= :block/content (:a datom))
-                 (let [matched (re-seq #"\([./]*/assets/([^)]+)\)" (:v datom))
-
-                       path (get (get (into [] matched) 0) 1)]
-                   path)))
-             datoms)]
-
-
-        [@(d/conn-from-datoms datoms db-schema/schema) (into [] public-assets-filesnames)]))))
+            assets (get-assets datoms)]
+        [@(d/conn-from-datoms datoms db-schema/schema) assets]))))
 
 (defn delete-blocks
   [repo-url files]

+ 22 - 20
src/main/frontend/db/react.cljs

@@ -294,26 +294,28 @@
         (when-let [cache (get @query-state related-key)]
           (let [{:keys [query inputs transform-fn query-fn inputs-fn]} cache]
             (when (or query query-fn)
-              (let [new-result (->
-                                (cond
-                                  query-fn
-                                  (profile
-                                   "Query:"
-                                   (doall (query-fn db)))
-
-                                  inputs-fn
-                                  (let [inputs (inputs-fn)]
-                                    (apply d/q query db inputs))
-
-                                  (keyword? query)
-                                  (db-utils/get-key-value repo-url query)
-
-                                  (seq inputs)
-                                  (apply d/q query db inputs)
-
-                                  :else
-                                  (d/q query db))
-                                transform-fn)]
+              (let [new-result (profile
+                                "takes"
+                                (->
+                                 (cond
+                                   query-fn
+                                   (profile
+                                    "Query:"
+                                    (doall (query-fn db)))
+
+                                   inputs-fn
+                                   (let [inputs (inputs-fn)]
+                                     (apply d/q query db inputs))
+
+                                   (keyword? query)
+                                   (db-utils/get-key-value repo-url query)
+
+                                   (seq inputs)
+                                   (apply d/q query db inputs)
+
+                                   :else
+                                   (d/q query db))
+                                 transform-fn))]
                 (set-new-result! related-key new-result)))))))))
 
 (defn transact-react!

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

@@ -202,6 +202,8 @@
         :content/click-to-edit "Click to edit"
         :settings-page/edit-config-edn "Edit config.edn (for current repo)"
         :settings-page/show-brackets "Show brackets"
+        :settings-page/disable-sentry "Disable Sentry.io (for error tracking)"
+        :settings-page/preferred-outdenting "Enable logical outdenting"
         :settings-page/custom-date-format "Preferred journal format"
         :settings-page/preferred-file-format "Preferred file format"
         :settings-page/preferred-workflow "Preferred workflow"
@@ -211,6 +213,8 @@
         :settings-page/home-default-page "Set the default home page"
         :settings-page/enable-block-time "Enable block timestamps"
         :settings-page/dont-use-other-peoples-proxy-servers "Don't use other people's proxy servers. It's very dangerous, which could make your token and notes stolen. Logseq will not be responsible for this loss if you use other people's proxy servers. You can deploy it yourself, check "
+        :settings-page/clear-cache "Clear cache"
+        :settings-page/clear "Clear"
         :settings-page/custom-cors-proxy-server "Custom CORS proxy server"
         :settings-page/developer-mode "Developer mode"
         :settings-page/enable-developer-mode "Enable developer mode"

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

@@ -27,6 +27,7 @@
             [lambdaisland.glogi :as log]
             [frontend.handler.common :as common-handler]
             [electron.listener :as el]
+            [electron.ipc :as ipc]
             [frontend.version :as version]))
 
 (defn- watch-for-date!
@@ -137,10 +138,11 @@
 
 (defn init-sentry
   []
-  (let [cfg
-        {:dsn "https://[email protected]/5311485"
-         :release (util/format "logseq@%s" version/version)}]
-    (.init js/window.Sentry (clj->js cfg))))
+  (when-not (state/sentry-disabled?)
+    (let [cfg
+          {:dsn "https://[email protected]/5311485"
+           :release (util/format "logseq@%s" version/version)}]
+      (.init js/window.Sentry (clj->js cfg)))))
 
 (defn on-load-events
   []
@@ -148,9 +150,11 @@
             (when-not config/dev? (init-sentry)))]
     (set! js/window.onload f)))
 
-(defn clear-stores-and-refresh!
+(defn clear-cache!
   []
-  (p/let [_ (idb/clear-local-storage-and-idb!)]
+  (p/let [_ (idb/clear-local-storage-and-idb!)
+          _ (when (util/electron?)
+              (ipc/ipc "clearCache"))]
     (js/window.location.reload)))
 
 (defn start!

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

@@ -12,3 +12,7 @@
 (defn toggle-ui-show-brackets! []
   (let [show-brackets? (state/show-brackets?)]
     (set-config! :ui/show-brackets? (not show-brackets?))))
+
+(defn toggle-logical-outdenting! []
+  (let [logical-outdenting? (state/logical-outdenting?)]
+    (set-config! :editor/logical-outdenting? (not logical-outdenting?))))

+ 50 - 61
src/main/frontend/handler/editor.cljs

@@ -991,15 +991,15 @@
         level-blocks (mapv (fn [uuid] (get level-blocks-uuid-map uuid)) block-ids)
         tree (blocks-vec->tree level-blocks)
         contents
-        (mapv (fn [[id block]]
+        (mapv (fn [block]
                 (let [header
                       (if (and unordered? (= format :markdown))
-                        (str (string/join (repeat (:level block) "  ")) "-")
+                        (str (string/join (repeat (- (:level block) 1) "  ")) "-")
                         (let [header-char (if (= format :markdown) "#" "*")
                               init-char (if (= format :markdown) "##" "*")]
                           (str (string/join (repeat (:level block) header-char)) init-char)))]
                   (str header " " (:block/content block) "\n")))
-              level-blocks-map)
+              level-blocks)
         content-without-properties
         (mapv
          (fn [content]
@@ -1680,6 +1680,7 @@
   [e]
   )
 
+;; selections
 (defn on-tab
   "direction = :left|:right, only indent or outdent when blocks are siblings"
   [direction]
@@ -2350,43 +2351,17 @@
         (util/stop e)
         (delete-and-update input (dec current-pos) current-pos)))))
 
-;; TODO: merge indent-on-tab, outdent-on-shift-tab, on-tab
-(defn indent-on-tab
-  [state]
-  (state/set-editor-op! :indent)
-  (profile "indent on tab"
-           (let [{:keys [block block-parent-id value config]} (get-state)]
-             (when block
-               (let [current-node (outliner-core/block block)
-                     first-child? (outliner-core/first-child? current-node)]
-                 (when-not first-child?
-                   (let [left (tree/-get-left current-node)
-                         children-of-left (tree/-get-children left)]
-                     (if (seq children-of-left)
-                       (let [target-node (last children-of-left)]
-                         (outliner-core/move-subtree current-node target-node true))
-                       (outliner-core/move-subtree current-node left false))
-                     (let [repo (state/get-current-repo)]
-                       (db/refresh! repo
-                                    {:key :block/change :data [(:data current-node)]}))))))))
-  (state/set-editor-op! :nil))
-
-(defn outdent-on-shift-tab
-  ([state]
-   (outdent-on-shift-tab state 100))
-  ([state retry-limit]
-   (state/set-editor-op! :outdent)
-   (let [{:keys [block block-parent-id value config]} (get-state)
-         {:block/keys [parent page]} block
-         current-node (outliner-core/block block)
-         parent-is-page? (= parent page)]
-     (when-not parent-is-page?
-       (let [parent (tree/-get-parent current-node)]
-         (outliner-core/move-subtree current-node parent true))
-       (let [repo (state/get-current-repo)]
-         (db/refresh! repo
-                      {:key :block/change :data [(:data current-node)]}))))
-   (state/set-editor-op! nil)))
+(defn indent-outdent
+  [state indent?]
+  (state/set-editor-op! :indent-outdent)
+  (let [{:keys [block block-parent-id value config]} (get-state)]
+    (when block
+      (let [current-node (outliner-core/block block)]
+        (outliner-core/indent-outdent-nodes [current-node] indent?)
+        (let [repo (state/get-current-repo)]
+          (db/refresh! repo
+                       {:key :block/change :data [(:data current-node)]}))))
+  (state/set-editor-op! :nil)))
 
 (defn keydown-tab-handler
   [direction]
@@ -2396,12 +2371,10 @@
       (when (and (not (state/get-editor-show-input))
                  (not (state/get-editor-show-date-picker?))
                  (not (state/get-editor-show-template-search?)))
-        (do (if (= :left direction)
-              (outdent-on-shift-tab state)
-              (indent-on-tab state))
-            (and input pos
-                 (when-let [input (state/get-input)]
-                   (util/move-cursor-to input pos))))))))
+        (indent-outdent state (not (= :left direction)))
+        (and input pos
+             (when-let [input (state/get-input)]
+               (util/move-cursor-to input pos)))))))
 
 (defn keydown-not-matched-handler
   [format]
@@ -2550,10 +2523,13 @@
   [format text]
   (let [tree (->>
               (block/extract-blocks
-               (mldoc/->edn text (mldoc/default-config format)) text true format)
-              (mapv #(assoc % :level (:block/level %)))
-              (blocks-vec->tree))]
-    (paste-block-tree-at-point tree [])))
+               (mldoc/->edn text (mldoc/default-config format)) text true format))
+        min-level (apply min (mapv #(:block/level %) tree))
+        prefix-level (if (> min-level 1) (- min-level 1) 0)
+        tree* (->> tree
+                   (mapv #(assoc % :level (- (:block/level %) prefix-level)))
+                   (blocks-vec->tree))]
+    (paste-block-tree-at-point tree* [])))
 
 (defn- paste-segmented-text
   [format text]
@@ -2592,20 +2568,33 @@
         ;; from external
         (let [format (or (db/get-page-format (state/get-current-page)) :markdown)]
           (match [format
-                  (nil? (re-find #"^\s*(?:[-+*]|#+)\s+" text))
-                  (nil? (re-find #"^\s*\*+\s+" text))]
-                 [:markdown false _]
-                 (paste-text-parseable format text)
+                  (nil? (re-find #"(?m)^\s*(?:[-+*]|#+)\s+" text))
+                  (nil? (re-find #"(?m)^\s*\*+\s+" text))
+                  (nil? (re-find #"(?:\r?\n){2,}" text))]
+            [:markdown false _ _]
+            (do
+              (paste-text-parseable format text)
+              (util/stop e))
 
-                 [:org _ false]
-                 (paste-text-parseable format text)
+            [:org _ false _]
+            (do
+              (paste-text-parseable format text)
+              (util/stop e))
+
+            [:markdown true _ false]
+            (do
+              (paste-segmented-text format text)
+              (util/stop e))
 
-                 [:markdown true _]
-                 (paste-segmented-text format text)
+            [:markdown true _ true]
+            (do)
 
-                 [:org _ true]
-                 (paste-segmented-text format text))
-          (util/stop e))))))
+            [:org _ true false]
+            (do
+              (paste-segmented-text format text)
+              (util/stop e))
+            [:org _ true true]
+            (do)))))))
 
 (defn editor-on-paste!
   [id]

+ 1 - 1
src/main/frontend/handler/editor/lifecycle.cljs

@@ -59,7 +59,7 @@
               :upload-images))))
     (editor-handler/clear-when-saved!)
     ;; TODO: ugly
-    (when-not (contains? #{:insert :indent :outdent :auto-save :undo :redo} (state/get-editor-op))
+    (when-not (contains? #{:insert :indent-outdent :auto-save :undo :redo} (state/get-editor-op))
       (editor-handler/save-block! (get-state) value)))
   state)
 

+ 50 - 20
src/main/frontend/modules/outliner/core.cljs

@@ -478,10 +478,32 @@
   [node]
   (nil? (tree/-get-parent (tree/-get-parent node))))
 
+(defn get-right-siblings
+  [node]
+  {:pre [(tree/satisfied-inode? node)]}
+  (when-let [parent (tree/-get-parent node)]
+    (let [children (tree/-get-children parent)]
+      (->> (split-with #(not= (tree/-get-id node) (tree/-get-id %)) children)
+           last
+           rest))))
+
+(defn- logical-outdenting
+  [txs-state parent nodes first-node last-node last-node-right parent-parent-id parent-right]
+  (some-> last-node-right
+          (tree/-set-left-id (tree/-get-left-id first-node))
+          (tree/-save txs-state))
+  (let [first-node (tree/-set-left-id first-node (tree/-get-id parent))]
+    (doseq [node (cons first-node (rest nodes))]
+      (-> (tree/-set-parent-id node parent-parent-id)
+          (tree/-save txs-state))))
+  (some-> parent-right
+          (tree/-set-left-id (tree/-get-id last-node))
+          (tree/-save txs-state)))
+
 (defn indent-outdent-nodes
   [nodes indent?]
   (ds/auto-transact!
-    [txs-state (ds/new-outliner-txs-state)] {:outliner-op :indent-outdent-nodes}
+   [txs-state (ds/new-outliner-txs-state)] {:outliner-op :indent-outdent-nodes}
    (let [first-node (first nodes)
          last-node (last nodes)]
      (if indent?
@@ -489,33 +511,41 @@
          (let [first-node-left-id (tree/-get-left-id first-node)
                last-node-right (tree/-get-right last-node)
                parent-or-last-child-id (or (-> (db/get-block-immediate-children (state/get-current-repo)
-                                                 first-node-left-id)
-                                             last
-                                             :block/uuid)
-                                         first-node-left-id)
+                                                                                first-node-left-id)
+                                               last
+                                               :block/uuid)
+                                           first-node-left-id)
                first-node (tree/-set-left-id first-node parent-or-last-child-id)]
            (doseq [node (cons first-node (rest nodes))]
              (-> (tree/-set-parent-id node first-node-left-id)
-               (tree/-save txs-state)))
+                 (tree/-save txs-state)))
            (some-> last-node-right
-             (tree/-set-left-id first-node-left-id)
-             (tree/-save txs-state))))
+                   (tree/-set-left-id first-node-left-id)
+                   (tree/-save txs-state))))
        (when-not (first-level? first-node)
          (let [parent (tree/-get-parent first-node)
                parent-parent-id (tree/-get-parent-id parent)
                parent-right (tree/-get-right parent)
-               last-node-right (tree/-get-right last-node)]
-           (some-> last-node-right
-             (tree/-set-left-id (tree/-get-left-id first-node))
-             (tree/-save txs-state))
-           (let [first-node (tree/-set-left-id first-node (tree/-get-id parent))]
-             (doseq [node (cons first-node (rest nodes))]
-               (-> (tree/-set-parent-id node parent-parent-id)
-                 (tree/-save txs-state))))
-           (some-> parent-right
-             (tree/-set-left-id (tree/-get-id last-node))
-             (tree/-save txs-state))))))))
-
+               last-node-right (tree/-get-right last-node)
+               last-node-id (tree/-get-id last-node)]
+           (logical-outdenting txs-state parent nodes first-node last-node last-node-right parent-parent-id parent-right)
+           (when-not (state/logical-outdenting?)
+             ;; direct outdenting (the old behavior)
+             (let [right-siblings (get-right-siblings last-node)
+                   right-siblings (doall
+                                   (map (fn [sibling right-siblings]
+                                          (some->
+                                           (tree/-set-parent-id sibling last-node-id)
+                                           (tree/-save txs-state)))
+                                     right-siblings))]
+               (when-let [last-node-right (first right-siblings)]
+                 (let [last-node-children (tree/-get-children last-node)
+                       left-id (if (seq last-node-children)
+                                 (tree/-get-id (last last-node-children))
+                                 last-node-id)]
+                   (when left-id
+                     (some-> (tree/-set-left-id last-node-right left-id)
+                             (tree/-save txs-state)))))))))))))
 
 (defn- set-nodes-page&file-aux
   [node page file txs-state]

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

@@ -256,6 +256,12 @@
      :binding "mod+c mod+r"
      :fn      #(repo-handler/re-index! nfs-handler/rebuild-index!)}}
 
+   :shortcut.handler/misc
+   ;; always overrides the copy due to "mod+c mod+s"
+   {:misc/copy
+    {:binding "mod+c"
+     :fn     (fn [] (js/document.execCommand "copy"))}}
+
    :shortcut.handler/global-non-editing-only
    ^{:before m/enable-when-not-editing-mode!}
    {:ui/toggle-document-mode

+ 9 - 3
src/main/frontend/modules/shortcut/core.cljs

@@ -19,9 +19,13 @@
    KeyCodes/UP KeyCodes/LEFT KeyCodes/DOWN KeyCodes/RIGHT])
 
 (defn install-shortcut!
-  [handler-id {:keys [set-global-keys? prevent-default? state]
+  [handler-id {:keys [set-global-keys?
+                      prevent-default?
+                      skip-installed?
+                      state]
                :or   {set-global-keys? true
-                      prevent-default? false}}]
+                      prevent-default? false
+                      skip-installed? false}}]
   (let [shortcut-map (dh/shortcut-map handler-id state)
         handler      (new KeyboardShortcutHandler js/window)]
      ;; set arrows enter, tab to global
@@ -55,12 +59,14 @@
 
       (events/listen handler EventType/SHORTCUT_TRIGGERED f)
 
-      (swap! *installed merge data)
+      (when-not skip-installed?
+        (swap! *installed merge data))
 
       install-id)))
 
 (defn install-shortcuts!
   []
+  (install-shortcut! :shortcut.handler/misc {:skip-installed? true})
   (->> [:shortcut.handler/editor-global
         :shortcut.handler/global-non-editing-only
         :shortcut.handler/global-prevent-default]

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

@@ -29,6 +29,7 @@
     :repo/changed-files nil
     :nfs/user-granted? {}
     :nfs/refreshing? nil
+    :sentry/disabled? (storage/get "sentry-disabled")
     ;; TODO: how to detect the network reliably?
     :network/online? true
     :indexeddb/support? true
@@ -1234,6 +1235,22 @@
   [value]
   (set-state! :block/component-editing-mode? value))
 
+(defn sentry-disabled?
+  []
+  (:sentry/disabled? @state))
+
+(defn set-sentry-disabled!
+  [value]
+  (set-state! :sentry/disabled? value)
+  (storage/set "sentry-disabled" value)
+  (when value
+    (.close js/window.Sentry)))
+
+(defn logical-outdenting?
+  []
+  (:editor/logical-outdenting?
+   (get (sub-config) (get-current-repo))))
+
 (defn get-editor-args
   []
   (:editor/args @state))

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

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

+ 4 - 4
templates/config.edn

@@ -34,12 +34,12 @@
             :in $ ?start ?today
             :where
             [?h :block/marker ?marker]
+            [(contains? #{"NOW" "DOING"} ?marker)]
             [?h :block/page ?p]
             [?p :block/journal? true]
             [?p :block/journal-day ?d]
             [(>= ?d ?start)]
-            [(<= ?d ?today)]
-            [(contains? #{"NOW" "DOING"} ?marker)]]
+            [(<= ?d ?today)]]
     :inputs [:14d :today]
     :result-transform (fn [result]
                         (sort-by (fn [h]
@@ -50,12 +50,12 @@
             :in $ ?start ?next
             :where
             [?h :block/marker ?marker]
+            [(contains? #{"NOW" "LATER" "TODO"} ?marker)]
             [?h :block/ref-pages ?p]
             [?p :block/journal? true]
             [?p :block/journal-day ?d]
             [(> ?d ?start)]
-            [(< ?d ?next)]
-            [(contains? #{"NOW" "LATER" "TODO"} ?marker)]]
+            [(< ?d ?next)]]
     :inputs [:today :7d-after]
     :collapsed? false}]}
 

+ 17 - 31
yarn.lock

@@ -1099,10 +1099,10 @@ chokidar@^3.3.0, chokidar@^3.3.1, chokidar@^3.5.1:
   optionalDependencies:
     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"
-  integrity sha512-Cdw4LxAlVb3BbfoAIw50v8MhVmFGBzzvTpQUO3F5ezyo/MnZ3db3G73cHNE2aiNsAhK9qpg39RpUl6jxTIUGeA==
+chrono-node@^2.2.4:
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/chrono-node/-/chrono-node-2.2.4.tgz#0fa169e1d158935bb1d541010046f6107576347d"
+  integrity sha512-58ERHGfWtWzBxckF9ZyFuwJjcreLDr7C79lHEynSqZ7J8y1JfMa8ok1TvCCf/TOZmkGME1ZSaEEb0DqqWaNFHg==
   dependencies:
     dayjs "^1.10.0"
 
@@ -1282,12 +1282,7 @@ color@^3.0.0, color@^3.1.3:
     color-convert "^1.9.1"
     color-string "^1.5.4"
 
-colorette@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz"
-  integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
-
-colorette@^1.2.2:
+colorette@^1.2.1, colorette@^1.2.2:
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz"
   integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
@@ -3574,9 +3569,9 @@ lodash.uniq@^4.5.0:
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
 lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.19, lodash@^4.17.20:
-  version "4.17.20"
-  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz"
-  integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
+  version "4.17.21"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
 
 log-symbols@^4.0.0:
   version "4.0.0"
@@ -3897,10 +3892,10 @@ nan@^2.12.1:
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
   integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
 
-nanoid@^3.1.20:
-  version "3.1.20"
-  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz"
-  integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==
+nanoid@^3.1.22:
+  version "3.1.23"
+  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
+  integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==
 
 nanomatch@^1.2.9:
   version "1.2.13"
@@ -4917,13 +4912,13 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz"
   integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
 
[email protected]:
-  version "8.2.8"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.8.tgz"
-  integrity sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw==
[email protected].10, postcss@^8.1.6, postcss@^8.2.1:
+  version "8.2.10"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.10.tgz#ca7a042aa8aff494b334d0ff3e9e77079f6f702b"
+  integrity sha512-b/h7CPV7QEdrqIxtAf2j31U5ef05uBDuvoXv6L51Q4rcS1jdlXAVKJv+atCFdUXYl9dyTHGyoMzIepwowRJjFw==
   dependencies:
     colorette "^1.2.2"
-    nanoid "^3.1.20"
+    nanoid "^3.1.22"
     source-map "^0.6.1"
 
 postcss@^6.0.9:
@@ -4944,15 +4939,6 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.21
     source-map "^0.6.1"
     supports-color "^6.1.0"
 
-postcss@^8.1.6, postcss@^8.2.1:
-  version "8.2.5"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.5.tgz"
-  integrity sha512-wMcb7BpDcm3gxQOQx46NDNT36Kk0Ao6PJLLI2ed5vehbbbxCEuslSQzbQ2sfSKy+gkYxhWcGWSeaK+gwm4KIZg==
-  dependencies:
-    colorette "^1.2.1"
-    nanoid "^3.1.20"
-    source-map "^0.6.1"
-
 prepend-http@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz"

部分文件因文件數量過多而無法顯示