Selaa lähdekoodia

Merge branch 'master' into enhance/ux-of-file-sync

charlie 3 vuotta sitten
vanhempi
sitoutus
d78a18e438
36 muutettua tiedostoa jossa 751 lisäystä ja 431 poistoa
  1. 2 2
      android/app/build.gradle
  2. 37 45
      deps/graph-parser/src/logseq/graph_parser/block.cljs
  3. 4 2
      deps/graph-parser/test/logseq/graph_parser/nbb_test_runner.cljs
  4. 43 0
      deps/graph-parser/test/logseq/graph_parser_test.cljs
  5. 4 4
      ios/App/App.xcodeproj/project.pbxproj
  6. 1 1
      resources/package.json
  7. 31 20
      src/electron/electron/fs_watcher.cljs
  8. 10 7
      src/electron/electron/handler.cljs
  9. 22 21
      src/main/frontend/components/block.cljs
  10. 1 2
      src/main/frontend/components/block.css
  11. 1 1
      src/main/frontend/components/right_sidebar.cljs
  12. 0 7
      src/main/frontend/db/model.cljs
  13. 6 1
      src/main/frontend/db/query_react.cljs
  14. 29 9
      src/main/frontend/dicts.cljc
  15. 12 8
      src/main/frontend/extensions/pdf/assets.cljs
  16. 15 7
      src/main/frontend/format/block.cljs
  17. 2 0
      src/main/frontend/fs/node.cljs
  18. 30 5
      src/main/frontend/fs/watcher_handler.cljs
  19. 3 1
      src/main/frontend/handler.cljs
  20. 92 80
      src/main/frontend/handler/block.cljs
  21. 76 45
      src/main/frontend/handler/editor.cljs
  22. 4 0
      src/main/frontend/handler/notification.cljs
  23. 11 9
      src/main/frontend/handler/page.cljs
  24. 2 1
      src/main/frontend/mobile/footer.cljs
  25. 16 26
      src/main/frontend/mobile/mobile_bar.cljs
  26. 1 1
      src/main/frontend/modules/instrumentation/sentry.cljs
  27. 109 77
      src/main/frontend/modules/outliner/core.cljs
  28. 3 1
      src/main/frontend/modules/outliner/datascript.cljc
  29. 9 10
      src/main/frontend/modules/outliner/pipeline.cljs
  30. 7 0
      src/main/frontend/modules/outliner/tree.cljs
  31. 4 0
      src/main/frontend/modules/shortcut/config.cljs
  32. 56 3
      src/main/frontend/modules/shortcut/dicts.cljc
  33. 1 0
      src/main/frontend/publishing/html.cljs
  34. 2 1
      src/main/frontend/state.cljs
  35. 1 1
      src/main/frontend/version.cljs
  36. 104 33
      src/test/frontend/modules/outliner/core_test.cljs

+ 2 - 2
android/app/build.gradle

@@ -6,8 +6,8 @@ android {
         applicationId "com.logseq.app"
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 25
-        versionName "0.7.1"
+        versionCode 26
+        versionName "0.7.2"
         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
         aaptOptions {
              // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.

+ 37 - 45
deps/graph-parser/src/logseq/graph_parser/block.cljc → deps/graph-parser/src/logseq/graph_parser/block.cljs

@@ -1,7 +1,4 @@
 (ns logseq.graph-parser.block
-  ;; Disable clj linters since we don't support clj
-  #?(:clj {:clj-kondo/config {:linters {:unresolved-namespace {:level :off}
-                                        :unresolved-symbol {:level :off}}}})
   "Block related code needed for graph-parser"
   (:require [clojure.string :as string]
             [clojure.walk :as walk]
@@ -12,9 +9,7 @@
             [logseq.graph-parser.util :as gp-util]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.mldoc :as gp-mldoc]
-            [logseq.graph-parser.date-time-util :as date-time-util]
-            #?(:org.babashka/nbb [logseq.graph-parser.log :as log]
-               :default [lambdaisland.glogi :as log])))
+            [logseq.graph-parser.date-time-util :as date-time-util]))
 
 (defn heading-block?
   [block]
@@ -546,45 +541,42 @@
      :date-formatter and :db"
   [blocks content with-id? format {:keys [user-config] :as options}]
   {:pre [(seq blocks) (string? content) (boolean? with-id?) (contains? #{:markdown :org} format)]}
-  (try
-    (let [encoded-content (utf8/encode content)
-          [blocks body pre-block-properties]
-          (loop [headings []
-                 blocks (reverse blocks)
-                 timestamps {}
-                 properties {}
-                 body []]
-            (if (seq blocks)
-              (let [[block pos-meta] (first blocks)
-                    ;; fix start_pos
-                    pos-meta (assoc pos-meta :end_pos
-                                    (if (seq headings)
-                                      (get-in (last headings) [:meta :start_pos])
-                                      nil))]
-                (cond
-                  (paragraph-timestamp-block? block)
-                  (let [timestamps (extract-timestamps block)
-                        timestamps' (merge timestamps timestamps)]
-                    (recur headings (rest blocks) timestamps' properties body))
-
-                  (gp-property/properties-ast? block)
-                  (let [properties (extract-properties format (second block) user-config)]
-                    (recur headings (rest blocks) timestamps properties body))
-
-                  (heading-block? block)
-                  (let [block (construct-block block properties timestamps body encoded-content format pos-meta with-id? options)]
-                    (recur (conj headings block) (rest blocks) {} {} []))
-
-                  :else
-                  (recur headings (rest blocks) timestamps properties (conj body block))))
-              [(-> (reverse headings)
-                   sanity-blocks-data)
-               body
-               properties]))
-          result (with-pre-block-if-exists blocks body pre-block-properties encoded-content options)]
-      (map #(dissoc % :block/meta) result))
-    (catch :default e
-      (log/error :extract-blocks-failure e))))
+  (let [encoded-content (utf8/encode content)
+        [blocks body pre-block-properties]
+        (loop [headings []
+               blocks (reverse blocks)
+               timestamps {}
+               properties {}
+               body []]
+          (if (seq blocks)
+            (let [[block pos-meta] (first blocks)
+                  ;; fix start_pos
+                  pos-meta (assoc pos-meta :end_pos
+                                  (if (seq headings)
+                                    (get-in (last headings) [:meta :start_pos])
+                                    nil))]
+              (cond
+                (paragraph-timestamp-block? block)
+                (let [timestamps (extract-timestamps block)
+                      timestamps' (merge timestamps timestamps)]
+                  (recur headings (rest blocks) timestamps' properties body))
+
+                (gp-property/properties-ast? block)
+                (let [properties (extract-properties format (second block) user-config)]
+                  (recur headings (rest blocks) timestamps properties body))
+
+                (heading-block? block)
+                (let [block (construct-block block properties timestamps body encoded-content format pos-meta with-id? options)]
+                  (recur (conj headings block) (rest blocks) {} {} []))
+
+                :else
+                (recur headings (rest blocks) timestamps properties (conj body block))))
+            [(-> (reverse headings)
+                 sanity-blocks-data)
+             body
+             properties]))
+        result (with-pre-block-if-exists blocks body pre-block-properties encoded-content options)]
+    (map #(dissoc % :block/meta) result)))
 
 (defn with-parent-and-left
   [page-id blocks]

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

@@ -6,7 +6,8 @@
             [logseq.graph-parser.block-test]
             [logseq.graph-parser.property-test]
             [logseq.graph-parser.extract-test]
-            [logseq.graph-parser.cli-test]))
+            [logseq.graph-parser.cli-test]
+            [logseq.graph-parser-test]))
 
 (defmethod cljs.test/report [:cljs.test/default :end-run-tests] [m]
   (when-not (cljs.test/successful? m)
@@ -19,4 +20,5 @@
                'logseq.graph-parser.property-test
                'logseq.graph-parser.block-test
                'logseq.graph-parser.extract-test
-               'logseq.graph-parser.cli-test))
+               'logseq.graph-parser.cli-test
+               'logseq.graph-parser-test))

+ 43 - 0
deps/graph-parser/test/logseq/graph_parser_test.cljs

@@ -0,0 +1,43 @@
+(ns logseq.graph-parser-test
+  (:require [cljs.test :refer [deftest testing is]]
+            [logseq.graph-parser :as graph-parser]
+            [logseq.graph-parser.db :as gp-db]
+            [logseq.graph-parser.block :as gp-block]
+            [datascript.core :as d]))
+
+(deftest parse-file
+  (testing "id properties"
+    (let [conn (gp-db/start-conn)]
+      (graph-parser/parse-file conn "foo.md" "- id:: 628953c1-8d75-49fe-a648-f4c612109098" {})
+      (is (= [{:id "628953c1-8d75-49fe-a648-f4c612109098"}]
+             (->> (d/q '[:find (pull ?b [*])
+                         :in $
+                         :where [?b :block/content] [(missing? $ ?b :block/name)]]
+                       @conn)
+                  (map first)
+                  (map :block/properties)))
+          "id as text has correct :block/properties"))
+
+    (let [conn (gp-db/start-conn)]
+      (graph-parser/parse-file conn "foo.md" "- id:: [[628953c1-8d75-49fe-a648-f4c612109098]]" {})
+      (is (= [{:id #{"628953c1-8d75-49fe-a648-f4c612109098"}}]
+             (->> (d/q '[:find (pull ?b [*])
+                         :in $
+                         :where [?b :block/content] [(missing? $ ?b :block/name)]]
+                       @conn)
+                  (map first)
+                  (map :block/properties)))
+          "id as linked ref has correct :block/properties")))
+
+  (testing "unexpected failure during block extraction"
+    (let [conn (gp-db/start-conn)
+          deleted-page (atom nil)]
+      (with-redefs [gp-block/with-pre-block-if-exists (fn stub-failure [& _args]
+                                              (throw (js/Error "Testing unexpected failure")))]
+        (try
+          (graph-parser/parse-file conn "foo.md" "- id:: 628953c1-8d75-49fe-a648-f4c612109098"
+                                  {:delete-blocks-fn (fn [page _file]
+                                                       (reset! deleted-page page))})
+          (catch :default _)))
+      (is (= nil @deleted-page)
+          "Page should not be deleted when there is unexpected failure"))))

+ 4 - 4
ios/App/App.xcodeproj/project.pbxproj

@@ -554,7 +554,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.7.1;
+				MARKETING_VERSION = 0.7.2;
 				OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
@@ -580,7 +580,7 @@
 				INFOPLIST_FILE = App/Info.plist;
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 0.7.1;
+				MARKETING_VERSION = 0.7.2;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
@@ -605,7 +605,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.7.1;
+				MARKETING_VERSION = 0.7.2;
 				MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
@@ -632,7 +632,7 @@
 				INFOPLIST_KEY_NSHumanReadableCopyright = "";
 				IPHONEOS_DEPLOYMENT_TARGET = 13.0;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @executable_path/../../Frameworks";
-				MARKETING_VERSION = 0.7.1;
+				MARKETING_VERSION = 0.7.2;
 				MTL_FAST_MATH = YES;
 				PRODUCT_BUNDLE_IDENTIFIER = com.logseq.logseq.ShareViewController;
 				PRODUCT_NAME = "$(TARGET_NAME)";

+ 1 - 1
resources/package.json

@@ -1,6 +1,6 @@
 {
   "name": "Logseq",
-  "version": "0.7.1",
+  "version": "0.7.2",
   "main": "electron.js",
   "author": "Logseq",
   "license": "AGPL-3.0",

+ 31 - 20
src/electron/electron/fs_watcher.cljs

@@ -29,10 +29,13 @@
 
 (defn- publish-file-event!
   [dir path event]
-  (let [content (when (and (not= event "unlink")
+  (let [dir-path? (= dir path)
+        content (when (and (not= event "unlink")
+                           (not dir-path?)
                            (utils/should-read-content? path))
                   (utils/read-file path))
-        stat (when (not= event "unlink")
+        stat (when (and (not= event "unlink")
+                        (not dir-path?))
                (fs/statSync path))]
     (send-file-watcher! dir event {:dir (utils/fix-win-path! dir)
                                    :path (utils/fix-win-path! path)
@@ -44,31 +47,39 @@
   [_win dir]
   (when (and (fs/existsSync dir)
              (not (get @*file-watcher dir)))
-    (let [watcher (.watch watcher dir
-                          (clj->js
-                           {:ignored (fn [path]
-                                       (utils/ignored-path? dir path))
-                            :ignoreInitial false
-                            :ignorePermissionErrors true
-                            :interval polling-interval
-                            :binaryInterval polling-interval
-                            :persistent true
-                            :disableGlobbing true
-                            :usePolling false
-                            :awaitWriteFinish true}))
-          watcher-del-f #(.close watcher)]
-      (swap! *file-watcher assoc dir [watcher watcher-del-f])
+    (let [watcher-opts (clj->js
+                        {:ignored (fn [path]
+                                    (utils/ignored-path? dir path))
+                         :ignoreInitial false
+                         :ignorePermissionErrors true
+                         :interval polling-interval
+                         :binaryInterval polling-interval
+                         :persistent true
+                         :disableGlobbing true
+                         :usePolling false
+                         :awaitWriteFinish true})
+          dir-watcher (.watch watcher dir watcher-opts)
+          watcher-del-f #(.close dir-watcher)]
+      (swap! *file-watcher assoc dir [dir-watcher watcher-del-f])
       ;; TODO: batch sender
-      (.on watcher "add"
+      (.on dir-watcher "unlinkDir"
+           (fn [path]
+             (when (= dir path)
+               (publish-file-event! dir dir "unlinkDir"))))
+      (.on dir-watcher "addDir"
+           (fn [path]
+             (when (= dir path)
+               (publish-file-event! dir dir "addDir"))))
+      (.on dir-watcher "add"
            (fn [path]
              (publish-file-event! dir path "add")))
-      (.on watcher "change"
+      (.on dir-watcher "change"
            (fn [path]
              (publish-file-event! dir path "change")))
-      (.on watcher "unlink"
+      (.on dir-watcher "unlink"
            (fn [path]
              (publish-file-event! dir path "unlink")))
-      (.on watcher "error"
+      (.on dir-watcher "error"
            (fn [path]
              (println "Watch error happened: "
                       {:path path})))

+ 10 - 7
src/electron/electron/handler.cljs

@@ -53,13 +53,16 @@
 (defmethod handle :unlink [_window [_ repo path]]
   (if (plugin/dotdir-file? path)
     (fs/unlinkSync path)
-    (let [file-name   (-> (string/replace path (str repo "/") "")
-                          (string/replace "/" "_")
-                          (string/replace "\\" "_"))
-          recycle-dir (str repo "/logseq/.recycle")
-          _           (fs-extra/ensureDirSync recycle-dir)
-          new-path    (str recycle-dir "/" file-name)]
-      (fs/renameSync path new-path))))
+    (try
+      (let [file-name   (-> (string/replace path (str repo "/") "")
+                           (string/replace "/" "_")
+                           (string/replace "\\" "_"))
+           recycle-dir (str repo "/logseq/.recycle")
+           _           (fs-extra/ensureDirSync recycle-dir)
+           new-path    (str recycle-dir "/" file-name)]
+        (fs/renameSync path new-path))
+      (catch :default _e
+        nil))))
 
 (defonce Diff (google-diff.))
 (defn string-some-deleted?

+ 22 - 21
src/main/frontend/components/block.cljs

@@ -1617,7 +1617,9 @@
                      (if collapsed?
                        (editor-handler/expand-block! uuid)
                        (editor-handler/collapse-block! uuid))))}
-      [:span {:class (if control-show? "control-show cursor-pointer" "control-hide")}
+      [:span {:class (if (or collapsed?
+                             (and control-show?
+                                  (editor-handler/collapsable? uuid {:semantic? true}))) "control-show cursor-pointer" "control-hide")}
        (ui/rotating-arrow collapsed?)]]
      (let [bullet [:a {:on-click (fn [event]
                                    (bullet-on-click event block uuid))}
@@ -2154,12 +2156,14 @@
     [:div.indent (ui/icon "indent-increase" {:style {:fontSize 16}})]]])
 
 (rum/defc block-right-menu < rum/reactive
-  [_config {:block/keys [uuid] :as _block}]
+  [_config {:block/keys [uuid] :as _block} edit?]
   [:div.block-right-menu.flex.bg-base-2.rounded-md.ml-1
    [:div.commands-button.w-0.flex.flew-col.rounded-md
-    {:id (str "block-right-menu-" uuid)}
-    [:div.more (ui/icon "dots-circle-horizontal" {:style {:fontSize 16}})]
-    [:div.outdent (ui/icon "indent-decrease" {:style {:fontSize 16}})]]])
+    {:id (str "block-right-menu-" uuid)
+     :style {:max-width (if edit? 40 80)}}
+    [:div.outdent (ui/icon "indent-decrease" {:style {:fontSize 16}})]
+    (when-not edit?
+      [:div.more (ui/icon "dots-circle-horizontal" {:style {:fontSize 16}})])]])
 
 (rum/defcs block-content-or-editor < rum/reactive
   (rum/local true :hide-block-refs?)
@@ -2361,13 +2365,10 @@
   (editor-handler/unhighlight-blocks!))
 
 (defn- block-mouse-over
-  [uuid e *control-show? block-id doc-mode?]
+  [e *control-show? block-id doc-mode?]
   (when-not @*dragging?
     (util/stop e)
-    (when (or
-           (model/block-collapsed? uuid)
-           (editor-handler/collapsable? uuid {:semantic? true}))
-      (reset! *control-show? true))
+    (reset! *control-show? true)
     (when-let [parent (gdom/getElement block-id)]
       (let [node (.querySelector parent ".bullet-container")]
         (when doc-mode?
@@ -2491,7 +2492,7 @@
         :data-collapsed (and collapsed? has-child?)
         :class (str uuid
                     (when pre-block? " pre-block")
-                    (when (and card? (not review-cards?)) " shadow-xl")
+                    (when (and card? (not review-cards?)) " shadow-md")
                     (when (:ui/selected? block) " selected noselect"))
         :blockid (str uuid)
         :haschild (str has-child?)}
@@ -2522,14 +2523,14 @@
 
      [:div.flex.flex-row.pr-2
       {:class (if (and heading? (seq (:block/title block))) "items-baseline" "")
-       :on-touch-start block-handler/on-touch-start
+       :on-touch-start (fn [event uuid] (block-handler/on-touch-start event uuid))
        :on-touch-move (fn [event]
-                        (block-handler/on-touch-move event block uuid *show-left-menu? *show-right-menu?))
+                        (block-handler/on-touch-move event block uuid edit? *show-left-menu? *show-right-menu?))
        :on-touch-end (fn [event]
                        (block-handler/on-touch-end event block uuid *show-left-menu? *show-right-menu?))
        :on-touch-cancel block-handler/on-touch-cancel
        :on-mouse-over (fn [e]
-                        (block-mouse-over uuid e *control-show? block-id doc-mode?))
+                        (block-mouse-over e *control-show? block-id doc-mode?))
        :on-mouse-leave (fn [e]
                          (block-mouse-leave e *control-show? block-id doc-mode?))}
       (when (not slide?)
@@ -2539,7 +2540,7 @@
         (block-left-menu config block))
       (block-content-or-editor config block edit-input-id block-id heading-level edit?)
       (when @*show-right-menu?
-        (block-right-menu config block))]
+        (block-right-menu config block edit?))]
 
      (block-children config children collapsed?)
 
@@ -2846,12 +2847,12 @@
            [:span.opacity-60.text-sm.ml-2.results-count
             (str (count transformed-query-result) " results")]]
            ;;insert an "edit" button in the query view
-           [:a.opacity-70.hover:opacity-100.svg-small.inline 
-            {:on-mouse-down (fn [e]
-                              (util/stop e)
-                              (editor-handler/edit-block! current-block :max (:block/uuid current-block)))}
-            svg/edit]]
-          
+           (when-not built-in?
+            [:a.opacity-70.hover:opacity-100.svg-small.inline
+                      {:on-mouse-down (fn [e]
+                                        (util/stop e)
+                                        (editor-handler/edit-block! current-block :max (:block/uuid current-block)))}
+                      svg/edit])]
           (fn []
             [:div
              (when (and current-block (not view-f) (nil? table-view?))

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

@@ -191,7 +191,7 @@
     
     .commands-button {
         overflow: hidden;
-        max-width: 50px;
+        max-width: 40px;
         text-align: center;
         margin: auto 0;
 
@@ -207,7 +207,6 @@
 
     .commands-button {
         overflow: hidden;
-        max-width: 80px;
         text-align: center;
         margin: auto 0;
 

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

@@ -122,7 +122,7 @@
   (let [item (build-sidebar-item repo idx db-id block-type)]
     (when item
       (let [collapse? (state/sub [:ui/sidebar-collapsed-blocks db-id])]
-        [:div.sidebar-item.content.color-level.px-4.shadow-lg
+        [:div.sidebar-item.content.color-level.px-4.shadow-md
          (let [[title component] item]
            [:div.flex.flex-col
             [:div.flex.flex-row.justify-between

+ 0 - 7
src/main/frontend/db/model.cljs

@@ -802,13 +802,6 @@
     (->> (tree-seq map? (fn [x] [(:block/parent x)]) block)
          (some util/collapsed?))))
 
-(defn block-collapsed?
-  ([block-id]
-   (block-collapsed? (state/get-current-repo) block-id))
-  ([repo block-id]
-   (when-let [block (db-utils/entity repo [:block/uuid block-id])]
-     (util/collapsed? block))))
-
 (defn get-block-page
   [repo block-id]
   (when-let [block (db-utils/entity repo [:block/uuid block-id])]

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

@@ -114,6 +114,11 @@
                page-ref (string/lower-case page-ref)]
            (list 'contains? sym (text/page-ref-un-brackets! page-ref)))
 
+         (and (vector? f)
+              (= (first f) 'page-property)
+              (keyword? (util/nth-safe f 2)))
+         (update f 2 (fn [k] (keyword (string/replace (name k) "_" "-"))))
+
          :else
          f)) query)))
 
@@ -132,4 +137,4 @@
           k [:custom query']]
       (pprint "inputs (post-resolution):" resolved-inputs)
       (pprint "query-opts:" query-opts)
-      (apply react/q repo k query-opts query inputs))))
+      (apply react/q repo k query-opts query inputs))))

+ 29 - 9
src/main/frontend/dicts.cljc

@@ -1564,9 +1564,9 @@
         :file-sync/graph-deleted "El gráfico remoto actual se ha eliminado"}
 
    :nb-NO {:tutorial/text #?(:cljs (rc/inline "tutorial-no.md")
-                                :default "tutorial-no.md")
+                             :default "tutorial-no.md")
            :tutorial/dummy-notes #?(:cljs (rc/inline "dummy-notes-no.md")
-                                       :default "dummy-notes-no.md")
+                                    :default "dummy-notes-no.md")
            :on-boarding/demo-graph "Dette er en demo graf, endringer vil ikke bli lagret før du åpner en lokal mappe."
            :on-boarding/add-graph "Legg til en graf"
            :on-boarding/open-local-dir "Åpne en lokal mappe"
@@ -1623,11 +1623,11 @@
            :right-side-bar/favorites "Favoritter"
            :right-side-bar/page-graph "Sidegraf"
            :right-side-bar/block-ref "Blokkreferanse"
-           :right-side-bar/graph-view "Graph view"
-           :right-side-bar/all-pages "All pages"
+           :right-side-bar/graph-view "Grafvisning"
+           :right-side-bar/all-pages "Alle sider"
            :right-side-bar/flashcards "Flashcards"
-           :right-side-bar/new-page "New page"
-           :left-side-bar/journals "Journals"
+           :right-side-bar/new-page "Ny side"
+           :left-side-bar/journals "Dagbøker"
            :left-side-bar/new-page "Ny side"
            :left-side-bar/nav-favorites "Favoritter"
            :left-side-bar/nav-shortcuts "Snarveier"
@@ -1864,9 +1864,29 @@
            :select.graph/empty-placeholder-description "Ingen grafer matcher. Vil du legge til en ny?"
            :select.graph/add-graph "Ja, legg til en ny graf"
 
-           :file-sync/other-user-graph "Nåværende lokal graf er bundet til annen brukers fjernkontroll. Så kan ikke begynne å synkronisere."
-           :file-sync/graph-deleted "Nåværende fjernkontrollen er slettet"}
-
+           :file-sync/other-user-graph "Nåværende lokal graf er bundet til annen brukers fjerngraf. Kan ikke begynne å synkronisere."
+           :file-sync/graph-deleted "Nåværende fjerngraf er slettet"
+           :host "Vert"
+           :port "Port"
+           :re-index-discard-unsaved-changes-warning "Reindeksering vil forkaste nåværende graf, og deretter prosessere alle filene på nytt slik de er på disk akkurat nå. Du vil miste ulagrede endringer, og det kan ta litt tid. Forsette?"
+           :re-index-multiple-windows-warning "Du må lukke de andre vinduene før du kan reindeksere denne grafen"
+           :save "Lagrer..."
+           :settings-of-plugins "Innstillinger for utvidelser"
+           :sync-from-local-changes-detected "Oppfrisk oppdager og prosesserer filer på disk som er modifiserte og avviker fra sideinnholdet som vises i Logseq. Fortsett?"
+           :type "Type"
+           :graph/persist "Logeq synkroniserer intern status, vennligst vent i flere sekunder."
+           :graph/persist-error "Intern status synk feilet"
+           :graph/save "Lagrer..."
+           :graph/save-error "Lagring feilet"
+           :graph/save-success "Lagring vellykket"
+           :page/copy-page-url "Kopier side URL"
+           :page/file-sync-versions "Versjoner av siden"
+           :page/open-backup-directory "Åpne mappe med sidens sikkerhetskopier"
+           :plugin/not-installed "Ikke installert"
+           :settings-page/edit-export-css "Rediger export.css"
+           :settings-page/network-proxy "Nettverksproxy"
+           :settings-page/plugin-system "System for utvidelser"}
+   
    :pt-BR {:on-boarding/demo-graph "Esse é um gráfico de demonstração, mudanças não serão salvas enquanto uma pasta local não for aberta."
            :on-boarding/add-graph "Adicionar gráfico"
            :on-boarding/open-local-dir "Abrir pasta local"

+ 12 - 8
src/main/frontend/extensions/pdf/assets.cljs

@@ -7,6 +7,7 @@
             [frontend.fs :as fs]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.page :as page-handler]
+            [frontend.util.page-property :as page-property]
             [frontend.state :as state]
             [frontend.util :as util]
             [logseq.graph-parser.config :as gp-config]
@@ -154,14 +155,14 @@
         page-name (str "hls__" page-name)
         page (db-model/get-page page-name)
         url (:url pdf-current)
-        format (state/get-preferred-format)]
+        format (state/get-preferred-format)
+        repo-dir (config/get-repo-dir (state/get-current-repo))
+        asset-dir (util/node-path.join repo-dir gp-config/local-assets-dir)
+        url (if (string/includes? url asset-dir)
+              (str ".." (last (string/split url repo-dir)))
+              url)]
     (if-not page
-      (let [repo-dir (config/get-repo-dir (state/get-current-repo))
-            asset-dir (util/node-path.join repo-dir gp-config/local-assets-dir)
-            url (if (string/includes? url asset-dir)
-                  (str ".." (last (string/split url repo-dir)))
-                  url)
-            label (:filename pdf-current)]
+      (let [label (:filename pdf-current)]
         (page-handler/create! page-name {:redirect?        false :create-first-block? false
                                          :split-namespace? false
                                          :format           format
@@ -175,7 +176,10 @@
                                                                          url)
                                                             :file-path url}})
         (db-model/get-page page-name))
-      page)))
+
+      ;; try to update file path
+      (page-property/add-property! page-name :file-path url))
+    page))
 
 (defn create-ref-block!
   [{:keys [id content page]}]

+ 15 - 7
src/main/frontend/format/block.cljs

@@ -6,19 +6,27 @@
             [frontend.db :as db]
             [frontend.format :as format]
             [frontend.state :as state]
+            [frontend.handler.notification :as notification]
+            ["@sentry/react" :as Sentry]
             [logseq.graph-parser.config :as gp-config]
             [logseq.graph-parser.property :as gp-property]
             [logseq.graph-parser.mldoc :as gp-mldoc]))
 
 (defn extract-blocks
-  "Wrapper around logseq.graph-parser.block/extract-blocks that adds in system state"
+  "Wrapper around logseq.graph-parser.block/extract-blocks that adds in system state
+and handles unexpected failure."
   [blocks content with-id? format]
-  (gp-block/extract-blocks blocks content with-id? format
-                           {:user-config (state/get-config)
-                            :block-pattern (config/get-block-pattern format)
-                            :supported-formats (gp-config/supported-formats)
-                            :db (db/get-db (state/get-current-repo))
-                            :date-formatter (state/get-date-formatter)}))
+  (try
+    (gp-block/extract-blocks blocks content with-id? format
+                             {:user-config (state/get-config)
+                              :block-pattern (config/get-block-pattern format)
+                              :supported-formats (gp-config/supported-formats)
+                              :db (db/get-db (state/get-current-repo))
+                              :date-formatter (state/get-date-formatter)})
+    (catch :default e
+      (Sentry/captureException e)
+      (notification/show! "An unexpected error occurred during block extraction." :error)
+      [])))
 
 (defn page-name->map
   "Wrapper around logseq.graph-parser.block/page-name->map that adds in db"

+ 2 - 0
src/main/frontend/fs/node.cljs

@@ -69,6 +69,8 @@
         (->
          (p/let [result (ipc/ipc "writeFile" repo path content)
                  mtime (gobj/get result "mtime")]
+           (when-not contents-matched?
+             (ipc/ipc "backupDbFile" (config/get-local-dir repo) path disk-content content))
            (db/set-file-last-modified-at! repo path mtime)
            (p/let [content (if (encrypt/encrypted-db? (state/get-current-repo))
                              (encrypt/decrypt content)

+ 30 - 5
src/main/frontend/fs/watcher_handler.cljs

@@ -8,13 +8,15 @@
             [frontend.handler.page :as page-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.ui :as ui-handler]
+            [frontend.handler.notification :as notification]
             [logseq.graph-parser.util :as gp-util]
             [frontend.util.text :as text-util]
             [lambdaisland.glogi :as log]
             [electron.ipc :as ipc]
             [promesa.core :as p]
             [frontend.state :as state]
-            [frontend.encrypt :as encrypt]))
+            [frontend.encrypt :as encrypt]
+            [frontend.fs :as fs]))
 
 ;; all IPC paths must be normalized! (via gp-util/path-normalize)
 
@@ -48,10 +50,31 @@
           pages-metadata-path (config/get-pages-metadata-path)
           {:keys [mtime]} stat
           db-content (or (db/get-file repo path) "")]
-      (when (and (or content (= type "unlink"))
+      (when (and (or content (contains? #{"unlink" "unlinkDir" "addDir"} type))
                  (not (encrypt/content-encrypted? content))
                  (not (:encryption/graph-parsing? @state/state)))
         (cond
+          (and (= "unlinkDir" type) dir)
+          (do
+            (state/pub-event! [:notification/show
+                               {:content (str "The directory " dir " has been renamed or deleted, the editor will be disabled for this graph, you can unlink the graph.")
+                                :status :error
+                                :clear? false}])
+            (state/update-state! :file/unlinked-dirs (fn [dirs] (conj dirs dir))))
+
+          (= "addDir" type)
+          (when (contains? (:file/unlinked-dirs @state/state) dir)
+            (notification/clear-all!)
+            (state/pub-event! [:notification/show
+                               {:content (str "The directory " dir " has been back, you can edit your graph now.")
+                                :status :success
+                                :clear? true}])
+            (fs/watch-dir! dir)
+            (state/update-state! :file/unlinked-dirs (fn [dirs] (disj dirs dir))))
+
+          (contains? (:file/unlinked-dirs @state/state) dir)
+          nil
+
           (and (= "add" type)
                (not= (string/trim content) (string/trim db-content))
                (not= path pages-metadata-path))
@@ -76,9 +99,11 @@
 
           (and (= "unlink" type)
                (db/file-exists? repo path))
-          (when-let [page-name (db/get-file-page path)]
-            (println "Delete page: " page-name ", file path: " path ".")
-            (page-handler/delete! page-name #() :delete-file? false))
+          (p/let [dir-exists? (fs/file-exists? dir "")]
+            (when dir-exists?
+              (when-let [page-name (db/get-file-page path)]
+                (println "Delete page: " page-name ", file path: " path ".")
+                (page-handler/delete! page-name #() :unlink-file? true))))
 
           (and (contains? #{"add" "change" "unlink"} type)
                (string/ends-with? path "logseq/custom.css"))

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

@@ -53,7 +53,9 @@
   (let [f (fn []
             #_:clj-kondo/ignore
             (let [repo (state/get-current-repo)]
-              (when-not (state/nfs-refreshing?)
+              (when (and (not (state/nfs-refreshing?))
+                         (not (contains? (:file/unlinked-dirs @state/state)
+                                         (config/get-repo-dir repo))))
                 ;; Don't create the journal file until user writes something
                 (page-handler/create-today-journal!))))]
     (f)

+ 92 - 80
src/main/frontend/handler/block.cljs

@@ -1,6 +1,7 @@
 (ns frontend.handler.block
   (:require
    [clojure.set :as set]
+   [clojure.string :as string]
    [clojure.walk :as walk]
    [frontend.db :as db]
    [frontend.db.model :as db-model]
@@ -147,88 +148,99 @@
       (state/exit-editing-and-set-selected-blocks! blocks))))
 
 (def *swipe (atom nil))
+(def *touch-start (atom nil))
 
 (defn on-touch-start
-  [event]
-  (when-let [touches (.-targetTouches event)]
-    (when (= (.-length touches) 1)
-      (let [touch (aget touches 0)
-            x (.-clientX touch)
-            y (.-clientY touch)]
-        (reset! *swipe {:x0 x :y0 y :xi x :yi y :tx x :ty y :direction nil})))))
+  [event uuid]
+  (let [input (state/get-input)
+        input-id (state/get-edit-input-id)
+        selection-type (.-type (.getSelection js/document))]
+    (reset! *touch-start (js/Date.now))
+    (when-not (and input
+                   (string/ends-with? input-id (str uuid)))
+      (state/clear-edit!))
+    (when (not= selection-type "Range")
+      (when-let [touches (.-targetTouches event)]
+        (when (= (.-length touches) 1)
+          (let [touch (aget touches 0)
+                x (.-clientX touch)
+                y (.-clientY touch)]
+            (reset! *swipe {:x0 x :y0 y :xi x :yi y :tx x :ty y :direction nil})))))))
 
 (defn on-touch-move
-  [event block uuid *show-left-menu? *show-right-menu?]
+  [event block uuid edit? *show-left-menu? *show-right-menu?]
   (when-let [touches (.-targetTouches event)]
-    (when (and (= (.-length touches) 1) @*swipe)
-      (let [{:keys [x0 xi direction]} @*swipe
-            touch (aget touches 0)
-            tx (.-clientX touch)
-            ty (.-clientY touch)
-            direction (if (nil? direction)
-                        (if (> tx x0)
-                          :right
-                          :left)
-                        direction)]
-        (swap! *swipe #(-> %
-                           (assoc :tx tx)
-                           (assoc :ty ty)
-                           (assoc :xi tx)
-                           (assoc :yi ty)
-                           (assoc :direction direction)))
-        (when (< (* (- xi x0) (- tx xi)) 0)
-          (swap! *swipe #(-> %
-                             (assoc :x0 tx)
-                             (assoc :y0 ty))))
-        (let [{:keys [x0 y0]} @*swipe
-              dx (- tx x0)
-              dy (- ty y0)]
-          (when (and (< (. js/Math abs dy) 20)
-                     (> (. js/Math abs dx) 10))
-            (let [left (gdom/getElement (str "block-left-menu-" uuid))
-                  right (gdom/getElement (str "block-right-menu-" uuid))]
+    (let [selection-type (.-type (.getSelection js/document))]
+      (when-not (= selection-type "Range")
+        (when (< (- (js/Date.now) @*touch-start) 600)
+          (when (and (= (.-length touches) 1) @*swipe)
+            (let [{:keys [x0 xi direction]} @*swipe
+                  touch (aget touches 0)
+                  tx (.-clientX touch)
+                  ty (.-clientY touch)
+                  direction (if (nil? direction)
+                              (if (> tx x0)
+                                :right
+                                :left)
+                              direction)]
+              (swap! *swipe #(-> %
+                                 (assoc :tx tx)
+                                 (assoc :ty ty)
+                                 (assoc :xi tx)
+                                 (assoc :yi ty)
+                                 (assoc :direction direction)))
+              (when (< (* (- xi x0) (- tx xi)) 0)
+                (swap! *swipe #(-> %
+                                   (assoc :x0 tx)
+                                   (assoc :y0 ty))))
+              (let [{:keys [x0 y0]} @*swipe
+                    dx (- tx x0)
+                    dy (- ty y0)]
+                (when (and (< (. js/Math abs dy) 20)
+                           (> (. js/Math abs dx) 10))
+                  (let [left (gdom/getElement (str "block-left-menu-" uuid))
+                        right (gdom/getElement (str "block-right-menu-" uuid))]
 
-              (cond
-                (= direction :right)
-                (do
-                  (reset! *show-left-menu? true)
-                  (when left
-                    (when (>= dx 0)
-                      (set! (.. left -style -width) (str dx "px")))
-                    (when (< dx 0)
-                      (set! (.. left -style -width) (str (max (+ 50 dx) 0) "px")))
+                    (cond
+                      (= direction :right)
+                      (do
+                        (reset! *show-left-menu? true)
+                        (when left
+                          (when (>= dx 0)
+                            (set! (.. left -style -width) (str dx "px")))
+                          (when (< dx 0)
+                            (set! (.. left -style -width) (str (max (+ 40 dx) 0) "px")))
 
-                    (let [indent (gdom/getFirstElementChild left)]
-                      (when (indentable? block)
-                        (if (>= (.-clientWidth left) 50)
-                          (set! (.. indent -style -opacity) "100%")
-                          (set! (.. indent -style -opacity) "30%"))))))
+                          (let [indent (gdom/getFirstElementChild left)]
+                            (when (indentable? block)
+                              (if (>= (.-clientWidth left) 40)
+                                (set! (.. indent -style -opacity) "100%")
+                                (set! (.. indent -style -opacity) "30%"))))))
 
-                (= direction :left)
-                (do
-                  (reset! *show-right-menu? true)
-                  (when right
-                    (when (<= dx 0)
-                      (set! (.. right -style -width) (str (- dx) "px")))
-                    (when (> dx 0)
-                      (set! (.. right -style -width) (str (max (- 80 dx) 0) "px")))
+                      (= direction :left)
+                      (do
+                        (reset! *show-right-menu? true)
+                        (when right
+                          (when (<= dx 0)
+                            (set! (.. right -style -width) (str (- dx) "px")))
+                          (when (> dx 0)
+                            (set! (.. right -style -width) (str (max (- 80 dx) 0) "px")))
 
-                    (let [outdent (gdom/getFirstElementChild right)
-                          more (gdom/getLastElementChild right)]
-                      (if (and (>= (.-clientWidth right) 40)
-                               (< (.-clientWidth right) 80))
-                        (set! (.. outdent -style -opacity) "100%")
-                        (set! (.. outdent -style -opacity) "30%"))
+                          (let [outdent (gdom/getFirstElementChild right)
+                                more (when-not edit?
+                                       (gdom/getLastElementChild right))]
+                            (when (and outdent (outdentable? block))
+                              (if (and (>= (.-clientWidth right) 40)
+                                       (< (.-clientWidth right) 80))
+                                (set! (.. outdent -style -opacity) "100%")
+                                (set! (.. outdent -style -opacity) "30%")))
 
-                      (when (outdentable? block)
-                        (if (>= (.-clientWidth right) 80)
-                          (set! (.. more -style -opacity) "100%")
-                          (set! (.. more -style -opacity) "30%") 
-                        ;; (set! (.. outdent -style -opacity) "100%")
-                          ;; (set! (.. outdent -style -opacity) "30%")
-                        )))))
-                :else
-                nil))))))))
+                            (when more
+                              (if (>= (.-clientWidth right) 80)
+                                (set! (.. more -style -opacity) "100%")
+                                (set! (.. more -style -opacity) "30%"))))))
+                      :else
+                      nil)))))))))))
 
 (defn on-touch-end
   [_event block uuid *show-left-menu? *show-right-menu?]
@@ -240,25 +252,25 @@
       (try
         (when (> (. js/Math abs dx) 10)
           (cond
-            (and left-menu (>= (.-clientWidth left-menu) 50))
+            (and left-menu (>= (.-clientWidth left-menu) 40))
             (when (indentable? block)
               (haptics/with-haptics-impact
                 (indent-outdent-block! block :right)
                 :light))
 
-            (and right-menu (< 40 (.-clientWidth right-menu) 80))
+            (and right-menu (<= 40 (.-clientWidth right-menu) 79))
+            (when (outdentable? block)
+              (haptics/with-haptics-impact
+                (indent-outdent-block! block :left)
+                :light))
+
+            (and right-menu (>= (.-clientWidth right-menu) 80))
             (haptics/with-haptics-impact
               (do (state/set-state! :mobile/show-action-bar? true)
                   (state/set-state! :mobile/actioned-block block)
                   (select-block! uuid))
               :light)
 
-            (and right-menu (>= (.-clientWidth right-menu) 80))
-            (when (outdentable? block)
-              (haptics/with-haptics-impact
-                (indent-outdent-block! block :left)
-                :light))
-
             :else
             nil))
         (catch js/Error e

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

@@ -970,11 +970,12 @@
   [repo block-ids]
   (let [blocks (db-utils/pull-many repo '[*] (mapv (fn [id] [:block/uuid id]) block-ids))
         top-level-block-uuids (->> (outliner-core/get-top-level-blocks blocks)
-                                   (map :block/uuid))]
-    (export/export-blocks-as-markdown
-     repo top-level-block-uuids
-     (state/get-export-block-text-indent-style)
-     (into [] (state/get-export-block-text-remove-options)))))
+                                   (map :block/uuid))
+        content (export/export-blocks-as-markdown
+                 repo top-level-block-uuids
+                 (state/get-export-block-text-indent-style)
+                 (into [] (state/get-export-block-text-remove-options)))]
+    [top-level-block-uuids content]))
 
 (defn copy-selection-blocks
   [html?]
@@ -982,10 +983,10 @@
     (let [repo (state/get-current-repo)
           ids (distinct (keep #(when-let [id (dom/attr % "blockid")]
                                  (uuid id)) blocks))
-          content (compose-copied-blocks-contents repo ids)
+          [top-level-block-uuids content] (compose-copied-blocks-contents repo ids)
           block (db/entity [:block/uuid (first ids)])]
       (when block
-        (let [html (export/export-blocks-as-html repo ids)]
+        (let [html (export/export-blocks-as-html repo top-level-block-uuids)]
           (common-handler/copy-to-clipboard-without-id-property! (:block/format block) content (when html? html)))
         (state/set-copied-blocks content ids)
         (notification/show! "Copied!" :success)))))
@@ -1059,15 +1060,19 @@
   (when-let [blocks (seq (get-selected-blocks))]
     ;; remove embeds, references and queries
     (let [dom-blocks (remove (fn [block]
-                           (or (= "true" (dom/attr block "data-transclude"))
-                               (= "true" (dom/attr block "data-query")))) blocks)]
+                              (or (= "true" (dom/attr block "data-transclude"))
+                                  (= "true" (dom/attr block "data-query")))) blocks)]
       (when (seq dom-blocks)
         (let [repo (state/get-current-repo)
               block-uuids (distinct (map #(uuid (dom/attr % "blockid")) dom-blocks))
               lookup-refs (map (fn [id] [:block/uuid id]) block-uuids)
-              blocks (db/pull-many repo '[*] lookup-refs)]
-          (state/set-copied-full-blocks nil blocks)
-          (delete-blocks! repo block-uuids blocks dom-blocks))))))
+              blocks (db/pull-many repo '[*] lookup-refs)
+              top-level-blocks (outliner-core/get-top-level-blocks blocks)
+              sorted-blocks (mapcat (fn [block]
+                                      (tree/get-sorted-block-and-children repo (:db/id block)))
+                                    top-level-blocks)]
+          (state/set-copied-full-blocks nil sorted-blocks)
+          (delete-blocks! repo (map :block/uuid sorted-blocks) sorted-blocks dom-blocks))))))
 
 (def url-regex
   "Didn't use link/plain-link as it is incorrectly detects words as urls."
@@ -1194,9 +1199,10 @@
   (when-let [block (db/pull [:block/uuid block-id])]
     (let [repo (state/get-current-repo)
           ;; TODO: support org mode
-          md-content (compose-copied-blocks-contents repo [block-id])
-          html (export/export-blocks-as-html repo [block-id])]
-      (state/set-copied-full-blocks md-content [block])
+          [_top-level-block-uuids md-content] (compose-copied-blocks-contents repo [block-id])
+          html (export/export-blocks-as-html repo [block-id])
+          sorted-blocks (tree/get-sorted-block-and-children repo (:db/id block))]
+      (state/set-copied-full-blocks md-content sorted-blocks)
       (common-handler/copy-to-clipboard-without-id-property! (:block/format block) md-content html)
       (delete-block-aux! block true))))
 
@@ -1934,7 +1940,8 @@
               blocks' (map (fn [block]
                              (paste-block-cleanup block page exclude-properties format content-update-fn))
                         blocks)
-              result (outliner-core/insert-blocks! blocks' target-block {:sibling? sibling?})]
+              result (outliner-core/insert-blocks! blocks' target-block {:sibling? sibling?
+                                                                         :outliner-op :paste})]
           (edit-last-block-after-inserted! result))))))
 
 (defn- block-tree->blocks
@@ -2885,7 +2892,9 @@
     (util/format "{{twitter %s}}" url)
 
     :else
-    (notification/show! (util/format "No macro is available for %s" url) :warning)))
+    (do
+      (notification/show! (util/format "No macro is available for %s" url) :warning)
+      nil)))
 
 (defn- paste-copied-blocks-or-text
   [initial-text text e]
@@ -2893,7 +2902,6 @@
         copied-block-ids (:copy/block-ids copied-blocks)
         copied-graph (:copy/graph copied-blocks)
         input (state/get-input)]
-    (util/stop e)
     (cond
       ;; Internal blocks by either copy or cut blocks
       (and
@@ -2904,20 +2912,26 @@
        ;; not copied from the external clipboard
        (= (string/replace (string/trim initial-text) "\r" "")
           (string/replace (string/trim (or (:copy/content copied-blocks) "")) "\r" "")))
-      (let [blocks (or
-                    (:copy/full-blocks copied-blocks)
-                    (get-all-blocks-by-ids (state/get-current-repo) copied-block-ids))]
-        (when (seq blocks)
-          (state/set-copied-full-blocks! blocks)
-          (paste-blocks blocks {})))
+      (do
+        (util/stop e)
+        (let [blocks (or
+                      (:copy/full-blocks copied-blocks)
+                      (get-all-blocks-by-ids (state/get-current-repo) copied-block-ids))]
+          (when (seq blocks)
+            (state/set-copied-full-blocks! blocks)
+            (paste-blocks blocks {}))))
 
       (and (gp-util/url? text)
            (not (string/blank? (util/get-selected-text))))
-      (html-link-format! text)
+      (do
+        (util/stop e)
+        (html-link-format! text))
 
       (and (text/block-ref? text)
            (wrapped-by? input "((" "))"))
-      (commands/simple-insert! (state/get-edit-input-id) (text/get-block-ref text) nil)
+      (do
+        (util/stop e)
+        (commands/simple-insert! (state/get-edit-input-id) (text/get-block-ref text) nil))
 
       :else
       ;; from external
@@ -2927,31 +2941,39 @@
                 (nil? (util/safe-re-find #"(?m)^\s*\*+\s+" text))
                 (nil? (util/safe-re-find #"(?:\r?\n){2,}" text))]
           [:markdown false _ _]
-          (paste-text-parseable format text)
+          (do
+            (util/stop e)
+            (paste-text-parseable format text))
 
           [:org _ false _]
-          (paste-text-parseable format text)
+          (do
+            (util/stop e)
+            (paste-text-parseable format text))
 
           [:markdown true _ false]
-          (paste-segmented-text format text)
+          (do
+            (util/stop e)
+            (paste-segmented-text format text))
 
           [:markdown true _ true]
-          (commands/simple-insert! (state/get-edit-input-id) text nil)
+          nil
 
           [:org _ true false]
-          (paste-segmented-text format text)
+          (do
+            (util/stop e)
+            (paste-segmented-text format text))
 
           [:org _ true true]
-          (commands/simple-insert! (state/get-edit-input-id) text nil))))))
+          nil)))))
 
 (defn paste-text-in-one-block-at-point
   []
   (utils/getClipText
    (fn [clipboard-data]
      (when-let [_ (state/get-input)]
-       (let [data (if (gp-util/url? clipboard-data)
-                        (wrap-macro-url clipboard-data)
-                        clipboard-data)]
+       (let [data (or (when (gp-util/url? clipboard-data)
+                        (wrap-macro-url clipboard-data))
+                      clipboard-data)]
          (insert data true))))
    (fn [error]
      (js/console.error error))))
@@ -2965,9 +2987,9 @@
           edit-block (state/get-edit-block)
           format (or (:block/format edit-block) :markdown)
           initial-text (.getData clipboard-data "text")
-          text (if-not (string/blank? html)
-                 (html-parser/convert format html)
-                 initial-text)
+          text (or (when (string/blank? html)
+                     (html-parser/convert format html))
+                   initial-text)
           input (state/get-input)]
       (if-not (string/blank? text)
         (if (or (thingatpt/markdown-src-at-point input)
@@ -3005,18 +3027,25 @@
 
 ;; credits to @pengx17
 (defn- copy-current-block-ref
-  []
+  [format]
   (when-let [current-block (state/get-edit-block)]
     (when-let [block-id (:block/uuid current-block)]
-      (copy-block-ref! block-id #(str "((" % "))"))
+      (if (= format "embed")
+       (copy-block-ref! block-id #(str "{{embed ((" % "))}}"))
+       (copy-block-ref! block-id #(str "((" % "))")))
       (notification/show!
        [:div
-        [:span.mb-1.5 "Block ref copied!"]
-        [:div [:code.whitespace-nowrap (str "((" block-id "))")]]]
+        [:span.mb-1.5 (str "Block " format " copied!")]
+        [:div [:code.whitespace.break-all (if (= format "embed")
+                                         (str "{{embed ((" block-id "))}}")
+                                         (str "((" block-id "))"))]]]
        :success true
        ;; use uuid to make sure there is only one toast a time
        (str "copied-block-ref:" block-id)))))
 
+(defn copy-current-block-embed []
+  (copy-current-block-ref "embed"))
+
 (defn shortcut-copy
   "shortcut copy action:
   * when in selection mode, copy selected blocks
@@ -3033,7 +3062,7 @@
             selected-start (util/get-selection-start input)
             selected-end (util/get-selection-end input)]
         (if (= selected-start selected-end)
-          (copy-current-block-ref)
+          (copy-current-block-ref "ref")
           (js/document.execCommand "copy")))
 
       :else
@@ -3110,9 +3139,9 @@
           ;; if the move is to cross block boundary, select the whole block
          (or (and (= direction :up) (cursor/textarea-cursor-rect-first-row? cursor-rect))
              (and (= direction :down) (cursor/textarea-cursor-rect-last-row? cursor-rect)))
-          (select-block-up-down direction)
+         (select-block-up-down direction)
           ;; simulate text selection
-          (cursor/select-up-down input direction anchor cursor-rect)))
+         (cursor/select-up-down input direction anchor cursor-rect)))
       (select-block-up-down direction))))
 
 (defn open-selected-block!
@@ -3185,6 +3214,8 @@
     (util/forward-kill-word input)
     (state/set-edit-content! (state/get-edit-input-id) (.-value input))))
 
+
+  
 (defn block-with-title?
   [format content semantic?]
   (and (string/includes? content "\n")

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

@@ -7,6 +7,10 @@
   (let [contents (state/get-notification-contents)]
     (state/set-state! :notification/contents (dissoc contents uid))))
 
+(defn clear-all!
+  []
+  (state/set-state! :notification/contents nil))
+
 (defn show!
   ([content status]
    (show! content status true nil 1500))

+ 11 - 9
src/main/frontend/handler/page.cljs

@@ -160,19 +160,21 @@
      page-name)))
 
 (defn delete-file!
-  [repo page-name]
+  [repo page-name unlink-file?]
   (let [file (db/get-page-file page-name)
         file-path (:file/path file)]
     ;; delete file
     (when-not (string/blank? file-path)
       (db/transact! [[:db.fn/retractEntity [:file/path file-path]]])
-      (->
-       (p/let [_ (and (config/local-db? repo)
-                      (mobile-util/native-platform?)
-                      (fs/delete-file! repo file-path file-path {}))
-               _ (fs/unlink! repo (config/get-repo-path repo file-path) nil)])
-       (p/catch (fn [err]
-                  (js/console.error "error: " err)))))))
+      (when unlink-file?
+        (->
+         (p/let [_ (and (config/local-db? repo)
+                        (mobile-util/native-platform?)
+                        ;; TODO: @leizhe remove fs/delete-file! and use fs/unlink!
+                        (fs/delete-file! repo file-path file-path {}))
+                 _ (fs/unlink! repo (config/get-repo-path repo file-path) nil)])
+         (p/catch (fn [err]
+                    (js/console.error "error: " err))))))))
 
 (defn- compute-new-file-path
   [old-path new-name]
@@ -316,7 +318,7 @@
             page (db/entity [:block/name page-name])]
         (db/transact! tx-data)
 
-        (when delete-file? (delete-file! repo page-name))
+        (delete-file! repo page-name delete-file?)
 
         ;; if other page alias this pagename,
         ;; then just remove some attrs of this entity instead of retractEntity

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

@@ -50,7 +50,8 @@
 
 (rum/defc footer < rum/reactive
   []
-  (when (and (state/sub :mobile/show-tabbar?)
+  (when (and (not (state/sub :editor/editing?))
+             (state/sub :mobile/show-tabbar?)
              (state/get-current-repo))
     [:div.cp__footer.w-full.bottom-0.justify-between
      (audio-record-cp)

+ 16 - 26
src/main/frontend/mobile/mobile_bar.cljs

@@ -7,6 +7,7 @@
             [frontend.handler.history :as history]
             [frontend.handler.page :as page-handler]
             [frontend.mobile.camera :as mobile-camera]
+            [frontend.mobile.util :as mobile-util]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
@@ -44,14 +45,6 @@
                       (state/set-state! :mobile/toolbar-update-observer (rand-int 1000000)))}
     (ui/icon icon {:style {:fontSize ui/icon-size}})]])
 
-(rum/defc indent-outdent [indent? icon]
-  [:div
-   [:button.bottom-action
-    {:on-mouse-down (fn [e]
-                      (util/stop e)
-                      (editor-handler/indent-outdent indent?))}
-    (ui/icon icon {:style {:fontSize ui/icon-size}})]])
-
 (rum/defc timestamp-submenu
   [parent-id]
   (let [callback (fn [event]
@@ -115,27 +108,24 @@
 
 (rum/defc mobile-bar < rum/reactive
   []
-  (when (and (state/sub :mobile/toolbar-update-observer)
-             (state/sub :mobile/show-toolbar?))
+  (when (and (state/sub :editor/editing?)
+             (or (state/sub :mobile/show-toolbar?)
+                 (mobile-util/native-ipad?))
+             (state/sub :mobile/toolbar-update-observer))
     (when-let [config-toolbar-stats (:mobile/toolbar-stats (state/get-config))]
-      (prn :config-toolbar-stats config-toolbar-stats)
       (reset! commands-stats config-toolbar-stats))
     (let [parent-id (state/get-edit-input-id)
           commands (commands parent-id)
           sorted-commands (sort-by (comp :counts second) > @commands-stats)]
-      (when (and (state/sub :mobile/show-toolbar?)
-                 (state/sub :editor/editing?))
-        [:div#mobile-editor-toolbar.bg-base-2
-         [:div.toolbar-commands
-          (indent-outdent false "indent-decrease")
-          (indent-outdent true "indent-increase")
-          (command (editor-handler/move-up-down true) "arrow-bar-to-up")
-          (command (editor-handler/move-up-down false) "arrow-bar-to-down")
-          (command #(if (state/sub :document/mode?)
-                      (editor-handler/insert-new-block! nil)
-                      (commands/simple-insert! parent-id "\n" {})) "arrow-back")
-          (for [command sorted-commands]
-            ((first command) commands))]
-         [:div.toolbar-hide-keyboard
-          (command #(state/clear-edit!) "keyboard-show")]]))))
+      [:div#mobile-editor-toolbar.bg-base-2
+       [:div.toolbar-commands
+        (command (editor-handler/move-up-down true) "arrow-bar-to-up")
+        (command (editor-handler/move-up-down false) "arrow-bar-to-down")
+        (command #(if (state/sub :document/mode?)
+                    (editor-handler/insert-new-block! nil)
+                    (commands/simple-insert! parent-id "\n" {})) "arrow-back")
+        (for [command sorted-commands]
+          ((first command) commands))]
+       [:div.toolbar-hide-keyboard
+        (command #(state/clear-edit!) "keyboard-show")]])))
 

+ 1 - 1
src/main/frontend/modules/instrumentation/sentry.cljs

@@ -45,4 +45,4 @@
 (defn init []
   (when-not config/dev?
     (let [config (clj->js config)]
-     (Sentry/init config))))
+      (Sentry/init config))))

+ 109 - 77
src/main/frontend/modules/outliner/core.cljs

@@ -370,6 +370,21 @@
 
 ;;; ### insert-blocks, delete-blocks, move-blocks
 
+(defn- fix-top-level-blocks
+  "Blocks with :block/level"
+  [blocks]
+  (loop [blocks blocks
+         last-top-level-block nil
+         result []]
+    (if-let [block (first blocks)]
+      (if (= 1 (:block/level block))
+        (let [block' (assoc block
+                            :block/left {:db/id (:db/id last-top-level-block)}
+                            :block/parent (:block/parent last-top-level-block))]
+          (recur (rest blocks) block (conj result block')))
+        (recur (rest blocks) last-top-level-block (conj result block)))
+      result)))
+
 (defn- insert-blocks-aux
   [blocks target-block {:keys [sibling? replace-empty-target? keep-uuid? move? outliner-op]}]
   (let [block-uuids (map :block/uuid blocks)
@@ -454,6 +469,9 @@
                                      (> (count blocks) 1)
                                      (not move?)))
         blocks' (blocks-with-level blocks)
+        blocks' (if (= outliner-op ::paste)
+                  (fix-top-level-blocks blocks')
+                  blocks')
         insert-opts {:sibling? sibling?
                      :replace-empty-target? replace-empty-target?
                      :keep-uuid? keep-uuid?
@@ -479,7 +497,8 @@
             next (if sibling?
                    (tree/-get-right target-node)
                    (tree/-get-down target-node))
-            next-tx (when (and next (not (contains? (set (map :db/id blocks)) (:db/id (:data next)))))
+            next-tx (when (and next
+                               (if move? (not (contains? (set (map :db/id blocks)) (:db/id (:data next)))) true))
                       (when-let [left (last (filter (fn [b] (= 1 (:block/level b))) tx))]
                         [{:block/uuid (tree/-get-id next)
                           :block/left (:db/id left)}]))
@@ -598,42 +617,53 @@
           (swap! txs-state concat fix-non-consecutive-tx))))
     {:tx-data @txs-state}))
 
+(defn- move-to-original-position?
+  [blocks target-block sibling?]
+  (let [non-consecutive-blocks (db-model/get-non-consecutive-blocks blocks)]
+    (and (empty? non-consecutive-blocks)
+         (= (:db/id (:block/left (first blocks))) (:db/id target-block))
+         (not= (= (:db/id (:block/parent (first blocks)))
+                  (:db/id target-block))
+               sibling?))))
+
 (defn move-blocks
   "Move `blocks` to `target-block` as siblings or children."
   [blocks target-block {:keys [sibling? outliner-op]}]
   [:pre [(seq blocks)
          (s/valid? ::block-map-or-entity target-block)]]
-  (when (not (contains? (set (map :db/id blocks)) (:db/id target-block)))
-    (let [parents (->> (db/get-block-parents (state/get-current-repo) (:block/uuid target-block))
-                       (map :db/id)
-                       (set))
-          move-parents-to-child? (some parents (map :db/id blocks))]
-      (when-not move-parents-to-child?
-        (let [blocks (get-top-level-blocks blocks)
-              first-block (first blocks)
-              {:keys [tx-data]} (insert-blocks blocks target-block {:sibling? sibling?
-                                                                    :outliner-op (or outliner-op :move-blocks)})]
-          (when (seq tx-data)
-            (let [first-block-page (:db/id (:block/page first-block))
-                  target-page (or (:db/id (:block/page target-block))
-                                  (:db/id target-block))
-                  not-same-page? (not= first-block-page target-page)
-                  move-blocks-next-tx [(build-move-blocks-next-tx blocks)]
-                  children-page-tx (when not-same-page?
-                                     (let [children-ids (mapcat #(db/get-block-children-ids (state/get-current-repo) (:block/uuid %)) blocks)]
-                                       (map (fn [uuid] {:block/uuid uuid
-                                                        :block/page target-page}) children-ids)))
-                  fix-non-consecutive-tx (->> (fix-non-consecutive-blocks blocks target-block sibling?)
-                                              (remove (fn [b]
-                                                        (contains? (set (map :db/id move-blocks-next-tx)) (:db/id b)))))
-                  full-tx (util/concat-without-nil tx-data move-blocks-next-tx children-page-tx fix-non-consecutive-tx)
-                  tx-meta (cond-> {:move-blocks (mapv :db/id blocks)
-                                   :target (:db/id target-block)}
-                            not-same-page?
-                            (assoc :from-page first-block-page
-                                   :target-page target-page))]
-              {:tx-data full-tx
-               :tx-meta tx-meta})))))))
+  (let [original-position? (move-to-original-position? blocks target-block sibling?)]
+    (when (and (not (contains? (set (map :db/id blocks)) (:db/id target-block)))
+               (not original-position?))
+      (let [parents (->> (db/get-block-parents (state/get-current-repo) (:block/uuid target-block))
+                         (map :db/id)
+                         (set))
+            move-parents-to-child? (some parents (map :db/id blocks))]
+        (when-not move-parents-to-child?
+          (let [blocks (get-top-level-blocks blocks)
+                first-block (first blocks)
+                {:keys [tx-data]} (insert-blocks blocks target-block {:sibling? sibling?
+                                                                      :outliner-op (or outliner-op :move-blocks)})]
+            (when (seq tx-data)
+              (let [first-block-page (:db/id (:block/page first-block))
+                    target-page (or (:db/id (:block/page target-block))
+                                    (:db/id target-block))
+                    not-same-page? (not= first-block-page target-page)
+                    move-blocks-next-tx [(build-move-blocks-next-tx blocks)]
+                    children-page-tx (when not-same-page?
+                                       (let [children-ids (mapcat #(db/get-block-children-ids (state/get-current-repo) (:block/uuid %)) blocks)]
+                                         (map (fn [uuid] {:block/uuid uuid
+                                                          :block/page target-page}) children-ids)))
+                    fix-non-consecutive-tx (->> (fix-non-consecutive-blocks blocks target-block sibling?)
+                                                (remove (fn [b]
+                                                          (contains? (set (map :db/id move-blocks-next-tx)) (:db/id b)))))
+                    full-tx (util/concat-without-nil tx-data move-blocks-next-tx children-page-tx fix-non-consecutive-tx)
+                    tx-meta (cond-> {:move-blocks (mapv :db/id blocks)
+                                     :target (:db/id target-block)}
+                              not-same-page?
+                              (assoc :from-page first-block-page
+                                     :target-page target-page))]
+                {:tx-data full-tx
+                 :tx-meta tx-meta}))))))))
 
 (defn move-blocks-up-down
   "Move blocks up/down."
@@ -681,51 +711,53 @@
   "Indent or outdent `blocks`."
   [blocks indent?]
   {:pre [(seq blocks) (boolean? indent?)]}
-  (let [first-block (db/entity (:db/id (first blocks)))
-        left (db/entity (:db/id (:block/left first-block)))
-        parent (:block/parent first-block)
-        db (db/get-db)
-        top-level-blocks (get-top-level-blocks blocks)
-        concat-tx-fn (fn [& results]
-                       {:tx-data (->> (map :tx-data results)
-                                      (apply util/concat-without-nil))
-                        :tx-meta (:tx-meta (first results))})
-        opts {:outliner-op :indent-outdent-blocks}]
-    (if indent?
-      (when (and left (not (page-first-child? first-block)))
-        (let [last-direct-child-id (db-model/get-block-last-direct-child db (:db/id left) false)
-              blocks' (drop-while (fn [b]
-                                    (= (:db/id (:block/parent b))
-                                       (:db/id left)))
-                                  top-level-blocks)]
-          (when (seq blocks')
-            (if last-direct-child-id
-              (let [last-direct-child (db/entity last-direct-child-id)
-                    result (move-blocks blocks' last-direct-child (merge opts {:sibling? true}))
-                    ;; expand `left` if it's collapsed
-                    collapsed-tx (when (:block/collapsed? left)
-                                   {:tx-data [{:db/id (:db/id left)
-                                               :block/collapsed? false}]})]
-                (concat-tx-fn result collapsed-tx))
-              (move-blocks blocks' left (merge opts {:sibling? false}))))))
-      (when (and parent (not (page-block? (db/entity (:db/id parent)))))
-        (let [blocks' (take-while (fn [b]
-                                    (not= (:db/id (:block/parent b))
-                                          (:db/id (:block/parent parent))))
-                                  top-level-blocks)
-              result (move-blocks blocks' parent (merge opts {:sibling? true}))]
-          (if (state/logical-outdenting?)
-            result
-            ;; direct outdenting (default behavior)
-            (let [last-top-block (db/pull (:db/id (last blocks')))
-                  right-siblings (->> (get-right-siblings (block last-top-block))
-                                      (map :data))]
-              (if (seq right-siblings)
-                (let [result2 (if-let [last-direct-child-id (db-model/get-block-last-direct-child db (:db/id last-top-block) false)]
-                                (move-blocks right-siblings (db/entity last-direct-child-id) (merge opts {:sibling? true}))
-                                (move-blocks right-siblings last-top-block (merge opts {:sibling? false})))]
-                  (concat-tx-fn result result2))
-                result))))))))
+  (let [non-consecutive-blocks (db-model/get-non-consecutive-blocks blocks)]
+    (when (empty? non-consecutive-blocks)
+      (let [first-block (db/entity (:db/id (first blocks)))
+            left (db/entity (:db/id (:block/left first-block)))
+            parent (:block/parent first-block)
+            db (db/get-db)
+            top-level-blocks (get-top-level-blocks blocks)
+            concat-tx-fn (fn [& results]
+                           {:tx-data (->> (map :tx-data results)
+                                          (apply util/concat-without-nil))
+                            :tx-meta (:tx-meta (first results))})
+            opts {:outliner-op :indent-outdent-blocks}]
+        (if indent?
+          (when (and left (not (page-first-child? first-block)))
+            (let [last-direct-child-id (db-model/get-block-last-direct-child db (:db/id left) false)
+                  blocks' (drop-while (fn [b]
+                                        (= (:db/id (:block/parent b))
+                                           (:db/id left)))
+                                      top-level-blocks)]
+              (when (seq blocks')
+                (if last-direct-child-id
+                  (let [last-direct-child (db/entity last-direct-child-id)
+                        result (move-blocks blocks' last-direct-child (merge opts {:sibling? true}))
+                        ;; expand `left` if it's collapsed
+                        collapsed-tx (when (:block/collapsed? left)
+                                       {:tx-data [{:db/id (:db/id left)
+                                                   :block/collapsed? false}]})]
+                    (concat-tx-fn result collapsed-tx))
+                  (move-blocks blocks' left (merge opts {:sibling? false}))))))
+          (when (and parent (not (page-block? (db/entity (:db/id parent)))))
+            (let [blocks' (take-while (fn [b]
+                                        (not= (:db/id (:block/parent b))
+                                              (:db/id (:block/parent parent))))
+                                      top-level-blocks)
+                  result (move-blocks blocks' parent (merge opts {:sibling? true}))]
+              (if (state/logical-outdenting?)
+                result
+                ;; direct outdenting (default behavior)
+                (let [last-top-block (db/pull (:db/id (last blocks')))
+                      right-siblings (->> (get-right-siblings (block last-top-block))
+                                          (map :data))]
+                  (if (seq right-siblings)
+                    (let [result2 (if-let [last-direct-child-id (db-model/get-block-last-direct-child db (:db/id last-top-block) false)]
+                                    (move-blocks right-siblings (db/entity last-direct-child-id) (merge opts {:sibling? true}))
+                                    (move-blocks right-siblings last-top-block (merge opts {:sibling? false})))]
+                      (concat-tx-fn result result2))
+                    result))))))))))
 
 ;;; ### write-operations have side-effects (do transactions) ;;;;;;;;;;;;;;;;
 

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

@@ -54,7 +54,9 @@
                                       :block/title :block/body :block/level :block/container :db/other-tx)
                               m)) txs)]
        (when (and (seq txs)
-                  (not (:skip-transact? opts)))
+                  (not (:skip-transact? opts))
+                  (not (contains? (:file/unlinked-dirs @state/state)
+                                  (config/get-repo-dir (state/get-current-repo)))))
          ;; (frontend.util/pprint txs)
          (try
            (let [repo (get opts :repo (state/get-current-repo))

+ 9 - 10
src/main/frontend/modules/outliner/pipeline.cljs

@@ -9,13 +9,12 @@
 
 (defn invoke-hooks
   [tx-report]
-  (let [{:keys [pages blocks]} (ds-report/get-blocks-and-pages tx-report)]
-    (when-not (:from-disk? (:tx-meta tx-report))
-      (doseq [p (seq pages)] (updated-page-hook tx-report p)))
-    (when (and state/lsp-enabled? (seq blocks))
-      (state/pub-event! [:plugin/hook-db-tx
-                         {:blocks  blocks
-                          :tx-data (:tx-data tx-report)
-                          :tx-meta (:tx-meta tx-report)}]))
-    ;; TODO: Add blocks to hooks
-    #_(doseq [b (seq blocks)])))
+  (when (and (not (:from-disk? (:tx-meta tx-report)))
+             (not (:new-graph? (:tx-meta tx-report))))
+    (let [{:keys [pages blocks]} (ds-report/get-blocks-and-pages tx-report)]
+      (doseq [p (seq pages)] (updated-page-hook tx-report p))
+      (when (and state/lsp-enabled? (seq blocks))
+        (state/pub-event! [:plugin/hook-db-tx
+                           {:blocks  blocks
+                            :tx-data (:tx-data tx-report)
+                            :tx-meta (:tx-meta tx-report)}])))))

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

@@ -79,3 +79,10 @@
   [blocks-exclude-root root]
   (let [parent-groups (atom (group-by :block/parent blocks-exclude-root))]
     (flatten (concat (sort-blocks-aux [root] parent-groups) (vals @parent-groups)))))
+
+(defn get-sorted-block-and-children
+  [repo db-id]
+  (when-let [root-block (db/pull db-id)]
+    (let [blocks (db/get-block-and-children repo (:block/uuid root-block))
+          blocks-exclude-root (remove (fn [b] (= (:db/id b) db-id)) blocks)]
+      (sort-blocks blocks-exclude-root root-block))))

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

@@ -145,6 +145,8 @@
 
    :editor/replace-block-reference-at-point {:binding "mod+shift+r"
                                              :fn      editor-handler/replace-block-reference-with-content-at-point}
+   :editor/copy-embed {:binding "mod+e"
+                       :fn      editor-handler/copy-current-block-embed}
 
    :editor/paste-text-in-one-block-at-point {:binding "mod+shift+v"
                                              :fn      editor-handler/paste-text-in-one-block-at-point}
@@ -447,6 +449,7 @@
                           :editor/forward-kill-word
                           :editor/backward-kill-word
                           :editor/replace-block-reference-at-point
+                          :editor/copy-embed
                           :editor/paste-text-in-one-block-at-point
                           :editor/insert-youtube-timestamp])
      (with-meta {:before m/enable-when-editing-mode!}))
@@ -609,6 +612,7 @@
     :editor/forward-kill-word
     :editor/backward-kill-word
     :editor/replace-block-reference-at-point
+    :editor/copy-embed
     :editor/paste-text-in-one-block-at-point
     :editor/select-up
     :editor/select-down]

+ 56 - 3
src/main/frontend/modules/shortcut/dicts.cljc

@@ -38,6 +38,7 @@
    :editor/strike-through        "Strikethrough"
    :editor/clear-block           "Delete entire block content"
    :editor/kill-line-before      "Delete line before cursor position"
+   :editor/copy-embed            "Copy a block embed pointing to the current block"
    :editor/kill-line-after       "Delete line after cursor position"
    :editor/beginning-of-block    "Move cursor to the beginning of a block"
    :editor/end-of-block          "Move cursor to the end of a block"
@@ -488,7 +489,59 @@
              :command.editor/backward-kill-word       "Slett ett ord bakover"
              :command.editor/open-edit                "Rediger valgt blokk"
              :command.editor/delete-selection         "Slett valgte blokker"
-             :command.editor/toggle-open-blocks       "Veksle åpne blokker (slå sammen eller utvid alle blokker)"}
+             :command.editor/toggle-open-blocks       "Veksle åpne blokker (slå sammen eller utvid alle blokker)"
+             :command.auto-complete/complete "Autofullfør: Bruk valgt punkt"
+             :command.auto-complete/next "Autofullfør: Velg neste punkt"
+             :command.auto-complete/open-link "Autofullfør: Åpne valgt punkt i nettleser"
+             :command.auto-complete/prev "Autofullfør: Velg forrige punkt"
+             :command.auto-complete/shift-complete "Autofullfør: Åpne valgt punkt i sidestolpen"
+             :command.cards/forgotten "Kort: Glemte"
+             :command.cards/next-card "Kort: Neste kort"
+             :command.cards/recall "Kort: bruk litt tid på å huske"
+             :command.cards/remembered "Kort: husket"
+             :command.cards/toggle-answers "Kort: vis/skjul svar/clozes"
+             :command.command/run "Kjør git kommando"
+             :command.command/toggle-favorite "Legg til eller fjern fra favoritter"
+             :command.command-palette/toggle "Veksle kommandolinje"
+             :command.date-picker/complete "Datovelger: Bruk valgt dag"
+             :command.date-picker/next-day "Datovelger: Velg neste dag"
+             :command.date-picker/next-week "Datovelger: Velg neste uke"
+             :command.date-picker/prev-day "Datovelger: Velg forrige dag"
+             :command.date-picker/prev-week "Datovelger: Velg forrige uke"
+             :command.editor/copy-current-file "Kopier nåværende fil"
+             :command.editor/escape-editing "Avslutt redigering"
+             :command.editor/insert-youtube-timestamp "Sett inn YouTube tidsstempel"
+             :command.editor/open-file-in-default-app "Åpne fil i forhåndsvalgt app"
+             :command.editor/open-file-in-directory "Åpne fil i overordnet katalog"
+             :command.editor/paste-text-in-one-block-at-point "Lim inn tekst i blokk ved markør"
+             :command.editor/replace-block-reference-at-point "Erstatt blokkreferanse med dets innhold ved markør"
+             :command.editor/select-down "Velg innhold under"
+             :command.editor/select-up "Velg innhold over"
+             :command.editor/strike-through "Gjennomstreking"
+             :command.go/all-pages "Gå til alle sider"
+             :command.go/backward "Bakover"
+             :command.go/flashcards "Veksle flashcards"
+             :command.go/forward "Fremover"
+             :command.go/graph-view "Gå til graf visning"
+             :command.go/home "Gå hjem"
+             :command.go/keyboard-shortcuts "Gå til tastatursnarveier"
+             :command.go/next-journal "Gå til neste dagbok"
+             :command.go/prev-journal "Gå til forrige dagbok"
+             :command.go/tomorrow "Gå til i morgen"
+             :command.graph/add "Legg til graf"
+             :command.graph/open "Velg graf for å åpne"
+             :command.graph/remove "Fjern en graf"
+             :command.graph/save "Lagre nåværende graf til disk"
+             :command.misc/copy "mod+c"
+             :command.pdf/close "Lukk nåværende pdf leser"
+             :command.pdf/next-page "Neste side i nåværende pdf dok"
+             :command.pdf/previous-page "Forrige side i nåværende pdf dok"
+             :command.sidebar/clear "Fjern alt i høyre sidestolpe"
+             :command.sidebar/open-today-page "Åpne dagens side i høyre sidestolpe"
+             :command.ui/goto-plugins "Gå til dashbord for utvidelser"
+             :command.ui/open-new-window "Åpne et nytt vindu"
+             :command.ui/select-theme-color "Velg tilgjengelige temafarger"
+             :command.ui/toggle-cards "Veksle kort"}
 
    :pt-PT   {:shortcut.category/formatting            "Formatação"
              :shortcut.category/basics                "Básico"
@@ -900,9 +953,9 @@
              :shortcut.category/block-command-editing "Modifica comandi blocco"
              :shortcut.category/block-selection       "Selezione blocco (premi Esc per uscire dalla selezione)"
              :shortcut.category/toggle                "Attiva/disattiva"
-             :shortcut.category/others                "Altri"
+             :shortcut.category/others                "Altri"}
 
-   }
+   
    :tr      {:shortcut.category/basics "Temel bilgiler"
              :shortcut.category/formatting "Biçimlendirme"
              :shortcut.category/navigating "Gezinme"

+ 1 - 0
src/main/frontend/publishing/html.cljs

@@ -87,4 +87,5 @@
            [:script {:src "static/js/main.js"}]
            [:script {:src "static/js/highlight.min.js"}]
            [:script {:src "static/js/interact.min.js"}]
+           [:script {:src "static/js/katex.min.js"}]
            [:script {:src "static/js/code-editor.js"}]]))))

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

@@ -27,6 +27,7 @@
      :system/events                         (async/chan 100)
      :db/batch-txs                          (async/chan 100)
      :file/writes                           (async/chan 100)
+     :file/unlinked-dirs                    #{}
      :reactive/custom-queries               (async/chan 100)
      :notification/show?                    false
      :notification/content                  nil
@@ -1442,7 +1443,7 @@
 (defn set-copied-full-blocks
   [content blocks]
   (set-state! :copy/blocks {:copy/graph (get-current-repo)
-                            :copy/content content
+                            :copy/content (or content (get-in @state [:copy/blocks :copy/content]))
                             :copy/full-blocks blocks}))
 
 (defn set-copied-full-blocks!

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

@@ -1,3 +1,3 @@
 (ns frontend.version)
 
-(defonce version "0.7.1")
+(defonce version "0.7.2")

+ 104 - 33
src/test/frontend/modules/outliner/core_test.cljs

@@ -10,7 +10,8 @@
             [clojure.walk :as walk]
             [logseq.graph-parser.block :as gp-block]
             [datascript.core :as d]
-            [frontend.test.helper :as helper]))
+            [frontend.test.helper :as helper]
+            [clojure.set :as set]))
 
 (def test-db helper/test-db)
 
@@ -89,6 +90,10 @@
   []
   (count (d/datoms (db/get-db test-db) :avet :block/uuid)))
 
+(defn get-blocks-ids
+  []
+  (set (map :v (d/datoms (db/get-db test-db) :avet :block/uuid))))
+
 (defn get-children
   [id]
   (->> (get-block id true)
@@ -174,6 +179,42 @@
       (outliner-core/indent-outdent-blocks! [(get-block 6) (get-block 9)] true))
     (is (= [4 5 6 9] (get-children 3)))))
 
+(deftest test-indent-blocks-regression-5604
+  (testing "
+  [22 [[2 [[3
+           [[4]
+            [5]
+            [6 [[7 [[8]]]]]
+            [9 [[10]
+                [11]]]]]]]
+      [12 [[13]                         ; outdents 13, 14, 15
+           [14]
+           [15]]]
+      [16 [[17]]]]]
+  "
+    (transact-tree! tree)
+    (outliner-tx/transact!
+      {:graph test-db}
+      (outliner-core/indent-outdent-blocks! [(get-block 13) (get-block 14) (get-block 15)] false))
+    (is (= [2 12 13 14 15 16] (get-children 22))))
+  (testing "
+  [22 [[2 [[3
+           [[4]
+            [5]
+            [6 [[7 [[8]]]]]
+            [9 [[10]
+                [11]]]]]]]
+      [12 [[13]                         ; outdents 13, 14
+           [14]
+           [15]]]
+      [16 [[17]]]]]
+  "
+    (transact-tree! tree)
+    (outliner-tx/transact!
+      {:graph test-db}
+      (outliner-core/indent-outdent-blocks! [(get-block 13) (get-block 14)] false))
+    (is (= [2 12 13 14 16] (get-children 22)))))
+
 (deftest test-outdent-blocks
   (testing "
   [1 [[2 [[3]
@@ -321,6 +362,21 @@
              :block/parent #:db{:id 6},
              :block/uuid 9}]))))
 
+(deftest test-get-sorted-block-and-children
+  (testing "get-sorted-block-and-children"
+    (transact-tree! tree)
+    (is (=
+         '(2 3 4 5 6 7 8 9 10 11)
+         (map :block/uuid (tree/get-sorted-block-and-children test-db (:db/id (get-block 2))))))
+
+    (is (=
+         '(22 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17)
+         (map :block/uuid (tree/get-sorted-block-and-children test-db (:db/id (get-block 22))))))
+
+    (is (=
+         '(16 17)
+         (map :block/uuid (tree/get-sorted-block-and-children test-db (:db/id (get-block 16))))))))
+
 ;;; Fuzzy tests
 
 (def init-id (atom 100))
@@ -390,22 +446,28 @@
               (recur (conj result next) next)))
           result)))))
 
-#_(deftest ^:long random-inserts
+(deftest ^:long random-inserts
   (testing "Random inserts"
     (transact-random-tree!)
-    (let [c1 (get-blocks-count)
-          *random-count (atom 0)]
+    (let [c1 (get-blocks-ids)
+          *random-blocks (atom c1)]
       (dotimes [_i 100]
+        ;; (prn "random insert: " i)
         (let [blocks (gen-blocks)]
-          (swap! *random-count + (count blocks))
-          (insert-blocks! blocks (get-random-block))))
-      (let [total (get-blocks-count)]
-        (is (= total (+ c1 @*random-count)))))))
+          (swap! *random-blocks (fn [old]
+                                  (set/union old (set (map :block/uuid blocks)))))
+          (insert-blocks! blocks (get-random-block)))
+        (let [total (get-blocks-count)]
+          ;; (when (not= total (count @*random-blocks))
+          ;;   (defonce wrong-db (db/get-db test-db))
+          ;;   (defonce random-blocks @*random-blocks))
+          (is (= total (count @*random-blocks))))))))
 
-#_(deftest ^:long random-deletes
+(deftest ^:long random-deletes
   (testing "Random deletes"
     (transact-random-tree!)
     (dotimes [_i 100]
+      ;; (prn "Random deletes: " i)
       (insert-blocks! (gen-blocks) (get-random-block))
       (let [blocks (get-random-successive-blocks)]
         (when (seq blocks)
@@ -415,11 +477,13 @@
 (deftest ^:long random-moves
   (testing "Random moves"
     (transact-random-tree!)
-    (let [c1 (get-blocks-count)
-          *random-count (atom 0)]
+    (let [c1 (get-blocks-ids)
+          *random-blocks (atom c1)]
       (dotimes [_i 100]
+        ;; (prn "Random move: " i)
         (let [blocks (gen-blocks)]
-          (swap! *random-count + (count blocks))
+          (swap! *random-blocks (fn [old]
+                                  (set/union old (set (map :block/uuid blocks)))))
           (insert-blocks! blocks (get-random-block)))
         (let [blocks (get-random-successive-blocks)]
           (when (seq blocks)
@@ -427,59 +491,63 @@
               (outliner-tx/transact! {:graph test-db}
                 (outliner-core/move-blocks! blocks target (gen/generate gen/boolean)))
               (let [total (get-blocks-count)]
-                (is (= total (+ c1 @*random-count)))))))))))
+                (is (= total (count @*random-blocks)))))))))))
 
-;; TODO: Enable when not failing as intermittently
-#_(deftest ^:long random-move-up-down
+(deftest ^:long random-move-up-down
   (testing "Random move up down"
     (transact-random-tree!)
-    (let [c1 (get-blocks-count)
-          *random-count (atom 0)]
+    (let [c1 (get-blocks-ids)
+          *random-blocks (atom c1)]
       (dotimes [_i 100]
+        ;; (prn "Random move up/down: " i)
         (let [blocks (gen-blocks)]
-          (swap! *random-count + (count blocks))
+          (swap! *random-blocks (fn [old]
+                                  (set/union old (set (map :block/uuid blocks)))))
           (insert-blocks! blocks (get-random-block)))
         (let [blocks (get-random-successive-blocks)]
           (when (seq blocks)
             (outliner-tx/transact! {:graph test-db}
               (outliner-core/move-blocks-up-down! blocks (gen/generate gen/boolean)))
             (let [total (get-blocks-count)]
-              (is (= total (+ c1 @*random-count))))))))))
+              (is (= total (count @*random-blocks))))))))))
 
-;; TODO: Enable when not failing as intermittently
-#_(deftest ^:long random-indent-outdent
+(deftest ^:long random-indent-outdent
   (testing "Random indent and outdent"
     (transact-random-tree!)
-    (let [c1 (get-blocks-count)
-          *random-count (atom 0)]
+    (let [c1 (get-blocks-ids)
+          *random-blocks (atom c1)]
       (dotimes [_i 100]
+        ;; (prn "Random move indent/outdent: " i)
         (let [blocks (gen-blocks)]
-          (swap! *random-count + (count blocks))
+          (swap! *random-blocks (fn [old]
+                                  (set/union old (set (map :block/uuid blocks)))))
           (insert-blocks! blocks (get-random-block)))
         (let [blocks (get-random-successive-blocks)]
           (when (seq blocks)
             (outliner-tx/transact! {:graph test-db}
               (outliner-core/indent-outdent-blocks! blocks (gen/generate gen/boolean)))
             (let [total (get-blocks-count)]
-              (is (= total (+ c1 @*random-count))))))))))
+              (is (= total (count @*random-blocks))))))))))
 
 (deftest ^:long random-mixed-ops
   (testing "Random mixed operations"
     (transact-random-tree!)
-    (let [c1 (get-blocks-count)
-          *random-count (atom 0)
+    (let [c1 (get-blocks-ids)
+          *random-blocks (atom c1)
           ops [
                ;; insert
                (fn []
                  (let [blocks (gen-blocks)]
-                   (swap! *random-count + (count blocks))
+                   (swap! *random-blocks (fn [old]
+                                           (set/union old (set (map :block/uuid blocks)))))
                    (insert-blocks! blocks (get-random-block))))
 
                ;; delete
                (fn []
                  (let [blocks (get-random-successive-blocks)]
                    (when (seq blocks)
-                     (swap! *random-count - (count blocks))
+                     (swap! *random-blocks (fn [old]
+                                             (set/difference old (set (map :block/uuid blocks)))))
                      (outliner-tx/transact! {:graph test-db}
                        (outliner-core/delete-blocks! blocks {})))))
 
@@ -503,15 +571,15 @@
                    (when (seq blocks)
                      (outliner-tx/transact! {:graph test-db}
                        (outliner-core/indent-outdent-blocks! blocks (gen/generate gen/boolean))))))]]
-      (dotimes [_i 500]
+      (dotimes [_i 100]
         ((rand-nth ops)))
       (let [total (get-blocks-count)
             page-id 1]
 
         ;; Invariants:
 
-        ;; 1. created blocks length >= existing blocks + deleted top-level blocks
-        (is (<= total (+ c1 @*random-count)))
+        ;; 1. total blocks <= inserted blocks - deleted block
+        (is (<= total (count @*random-blocks)))
 
         ;; 2. verify page's length + page itself = total blocks
         (is (= (inc (db-model/get-page-blocks-count test-db page-id))
@@ -524,5 +592,8 @@
 
 (comment
   (dotimes [i 5]
-    (cljs.test/run-tests))
+    (do
+      (frontend.test.fixtures/reset-datascript test-db)
+      (cljs.test/run-tests))
+    )
   )