瀏覽代碼

chore: remove @logseq/capacitor-file-sync and capacitor fs

Tienson Qin 5 月之前
父節點
當前提交
0d99eea39f

+ 1 - 2
package.json

@@ -111,8 +111,7 @@
         "@js-joda/core": "3.2.0",
         "@js-joda/locale_en-us": "3.1.1",
         "@js-joda/timezone": "2.5.0",
-        "@logseq/capacitor-file-sync": "5.0.2",
-        "@logseq/diff-merge": "0.2.2",
+        "@logseq/diff-merge": "^0.2.2",
         "@logseq/react-tweet-embed": "1.3.1-1",
         "@logseq/sqlite-wasm": "=0.1.0",
         "@radix-ui/colors": "^0.1.8",

+ 6 - 16
src/main/frontend/db/persist.cljs

@@ -1,13 +1,13 @@
 (ns frontend.db.persist
   "Handles operations to persisting db to disk or indexedDB"
-  (:require [frontend.util :as util]
-            [frontend.idb :as idb]
+  (:require [cljs-bean.core :as bean]
             [electron.ipc :as ipc]
+            [frontend.config :as config]
             [frontend.db.conn :as db-conn]
-            [promesa.core :as p]
+            [frontend.idb :as idb]
             [frontend.persist-db :as persist-db]
-            [cljs-bean.core :as bean]
-            [frontend.config :as config]))
+            [frontend.util :as util]
+            [promesa.core :as p]))
 
 (defn get-all-graphs
   []
@@ -24,7 +24,7 @@
     (distinct (concat
                repos'
                (map (fn [repo-name] {:name repo-name})
-                 (concat idb-repos (some-> electron-disk-graphs bean/->clj)))))))
+                    (concat idb-repos (some-> electron-disk-graphs bean/->clj)))))))
 
 (defn delete-graph!
   [graph]
@@ -34,13 +34,3 @@
       (if (util/electron?)
         (ipc/ipc "deleteGraph" graph key db-based?)
         (idb/remove-item! key)))))
-
-(defn rename-graph!
-  [old-repo new-repo]
-  (let [old-key (db-conn/get-repo-path old-repo)
-        new-key (db-conn/get-repo-path new-repo)]
-    (if (util/electron?)
-      (do
-        (js/console.error "rename-graph! is not supported in electron")
-        (idb/rename-item! old-key new-key))
-      (idb/rename-item! old-key new-key))))

+ 7 - 28
src/main/frontend/encrypt.cljs

@@ -1,41 +1,20 @@
 (ns frontend.encrypt
   "Encryption related fns for use with encryption feature and file sync"
-  (:require [logseq.graph-parser.utf8 :as utf8]
+  (:require [electron.ipc :as ipc]
             [frontend.util :as util]
-            [promesa.core :as p]
-            [electron.ipc :as ipc]
-            [frontend.mobile.util :as mobile-util]))
+            [logseq.graph-parser.utf8 :as utf8]
+            [promesa.core :as p]))
 
 (defn encrypt-with-passphrase
   [passphrase content]
-  (cond
-    (util/electron?)
+  (when (util/electron?)
     (p/let [raw-content (utf8/encode content)
             encrypted (ipc/ipc "encrypt-with-passphrase" passphrase raw-content)]
-      (utf8/decode encrypted))
-
-    (mobile-util/native-platform?)
-    (p/chain (.encryptWithPassphrase mobile-util/file-sync
-                                     (clj->js {:passphrase passphrase :content content}))
-             #(js->clj % :keywordize-keys true)
-             :data)
-
-    :else
-    nil))
+      (utf8/decode encrypted))))
 
 (defn decrypt-with-passphrase
   [passphrase content]
-  (cond
-    (util/electron?)
+  (when (util/electron?)
     (p/let [raw-content (utf8/encode content)
             decrypted (ipc/ipc "decrypt-with-passphrase" passphrase raw-content)]
-      (utf8/decode decrypted))
-
-    (mobile-util/native-platform?)
-    (p/chain (.decryptWithPassphrase mobile-util/file-sync
-                                     (clj->js {:passphrase passphrase :content content}))
-             #(js->clj % :keywordize-keys true)
-             :data)
-
-    :else
-    nil))
+      (utf8/decode decrypted))))

+ 6 - 23
src/main/frontend/fs.cljs

@@ -5,11 +5,9 @@
             [clojure.string :as string]
             [electron.ipc :as ipc]
             [frontend.config :as config]
-            [frontend.fs.capacitor-fs :as capacitor-fs]
             [frontend.fs.memory-fs :as memory-fs]
             [frontend.fs.node :as node]
             [frontend.fs.protocol :as protocol]
-            [frontend.mobile.util :as mobile-util]
             [frontend.state :as state]
             [frontend.util :as util]
             [lambdaisland.glogi :as log]
@@ -19,17 +17,12 @@
 
 (defonce memory-backend (memory-fs/->MemoryFs))
 (defonce node-backend (node/->Node))
-(defonce mobile-backend (capacitor-fs/->Capacitorfs))
 
 (defn- get-native-backend
   "Native FS backend of current platform"
   []
-  (cond
-    (util/electron?)
-    node-backend
-
-    (mobile-util/native-platform?)
-    mobile-backend))
+  (when (util/electron?)
+    node-backend))
 
 (defn get-fs
   [dir & {:keys [repo rpath]}]
@@ -58,7 +51,7 @@
       node-backend
 
       :else
-      mobile-backend)))
+      nil)))
 
 (defn mkdir!
   [dir]
@@ -166,7 +159,7 @@
 
     :else
     (let [[old-path new-path]
-          (map #(if (or (util/electron?) (mobile-util/native-platform?))
+          (map #(if (util/electron?)
                   %
                   (str (config/get-repo-dir repo) "/" %))
                [old-path new-path])
@@ -246,9 +239,6 @@
     (util/electron?)
     (path/url-to-path path)
 
-    (mobile-util/native-platform?)
-    path
-
     :else
     path))
 
@@ -258,12 +248,5 @@
 
 (defn backup-db-file!
   [repo path db-content disk-content]
-  (cond
-    (util/electron?)
-    (ipc/ipc "backupDbFile" (config/get-local-dir repo) path db-content disk-content)
-
-    (mobile-util/native-platform?)
-    (capacitor-fs/backup-file repo :backup-dir path db-content)
-
-    ;; TODO: nfs
-    ))
+  (when (util/electron?)
+    (ipc/ipc "backupDbFile" (config/get-local-dir repo) path db-content disk-content)))

+ 0 - 392
src/main/frontend/fs/capacitor_fs.cljs

@@ -1,392 +0,0 @@
-(ns frontend.fs.capacitor-fs
-  "Implementation of fs protocol for mobile"
-  (:require ["@capacitor/filesystem" :refer [Encoding Filesystem]]
-            [cljs-bean.core :as bean]
-            [clojure.string :as string]
-            [frontend.config :as config]
-            [frontend.db :as db]
-            [frontend.fs.protocol :as protocol]
-            [frontend.mobile.util :as mobile-util]
-            [frontend.state :as state]
-            [frontend.util :as util]
-            [goog.string :as gstring]
-            [lambdaisland.glogi :as log]
-            [logseq.common.path :as path]
-            [promesa.core :as p]
-            [rum.core :as rum]))
-
-(when (mobile-util/native-ios?)
-  (defn ios-ensure-documents!
-    []
-    (.ensureDocuments mobile-util/ios-file-container)))
-
-(when (mobile-util/native-android?)
-  (defn- android-check-permission []
-    (p/let [permission (.checkPermissions Filesystem)
-            permission (-> permission
-                           bean/->clj
-                           :publicStorage)]
-      (when-not (= permission "granted")
-        (p/do!
-         (.requestPermissions Filesystem))))))
-
-(defn- <dir-exists?
-  [fpath]
-  (p/catch (p/let [fpath (path/path-normalize fpath)
-                   stat (.stat Filesystem (clj->js {:path fpath}))]
-             (-> stat
-                 bean/->clj
-                 :type
-                 (= "directory")))
-           (fn [_error]
-             false)))
-
-(defn- <write-file-with-utf8
-  [path content]
-  (when-not (string/blank? path)
-    (-> (p/chain (.writeFile Filesystem (clj->js {:path path
-                                                  :data content
-                                                  :encoding (.-UTF8 Encoding)
-                                                  :recursive true}))
-                 #(js->clj % :keywordize-keys true))
-        (p/catch (fn [error]
-                   (js/console.error "writeFile Error: " path ": " error)
-                   nil)))))
-
-(defn- <read-file-with-utf8
-  [path]
-  (when-not (string/blank? path)
-    (-> (p/chain (.readFile Filesystem (clj->js {:path path
-                                                 :encoding (.-UTF8 Encoding)}))
-                 #(js->clj % :keywordize-keys true)
-                 #(get % :data nil))
-        (p/catch (fn [error]
-                   (js/console.error "readFile Error: " path ": " error)
-                   nil)))))
-
-(defn- <readdir [path]
-  (-> (p/chain (.readdir Filesystem (clj->js {:path path}))
-               #(js->clj % :keywordize-keys true)
-               :files)
-      (p/catch (fn [error]
-                 (js/console.error "readdir Error: " path ": " error)
-                 nil))))
-
-(defn- get-file-paths
-  "get all file paths recursively"
-  [path]
-  (p/let [result (p/loop [result []
-                          dirs [path]]
-                   (if (empty? dirs)
-                     result
-                     (p/let [d (first dirs)
-                             files (<readdir d)
-                             files (->> files
-                                        (remove (fn [{:keys [name  type]}]
-                                                  (or (string/starts-with? name ".")
-                                                      (and (= type "directory")
-                                                           (or (= name "bak")
-                                                               (= name "version-files")))))))
-                             files-dir (->> files
-                                            (filterv #(= (:type %) "directory"))
-                                            (mapv :uri))
-                             paths-result (->> files
-                                               (filterv #(= (:type %) "file"))
-                                               (mapv :uri))]
-                       (p/recur (concat result paths-result)
-                                (concat (rest dirs) files-dir)))))]
-    result))
-
-(defn- get-files
-  "get all files and contents recursively"
-  [path]
-  (p/let [result (p/loop [result []
-                          dirs [path]]
-                   (if (empty? dirs)
-                     result
-                     (p/let [d (first dirs)
-                             files (<readdir d)
-                             files (->> files
-                                        (remove (fn [{:keys [name  type]}]
-                                                  (or (string/starts-with? name ".")
-                                                      (and (= type "directory")
-                                                           (or (= name "bak")
-                                                               (= name "version-files")))))))
-                             files-dir (->> files
-                                            (filterv #(= (:type %) "directory"))
-                                            (mapv :uri))
-                             files-result
-                             (p/all
-                              (->> files
-                                   (filter #(= (:type %) "file"))
-                                   (filter
-                                    (fn [{:keys [uri]}]
-                                      (some #(string/ends-with? uri %)
-                                            [".md" ".markdown" ".org" ".edn" ".css"])))
-                                   (mapv
-                                    (fn [{:keys [uri] :as file-info}]
-                                      (p/chain (<read-file-with-utf8 uri)
-                                               #(assoc (dissoc file-info :uri)
-                                                       :content %
-                                                       :path uri))))))]
-                       (p/recur (concat result files-result)
-                                (concat (rest dirs) files-dir)))))]
-    (js->clj result :keywordize-keys true)))
-
-(defn- <contents-matched?
-  [disk-content db-content]
-  (when (and (string? disk-content) (string? db-content))
-    (p/resolved (= (string/trim disk-content) (string/trim db-content)))))
-
-(def backup-dir "logseq/bak")
-(def version-file-dir "logseq/version-files/local")
-
-(defn- get-backup-dir
-  [repo-dir path bak-dir ext]
-  (let [relative-path (-> path
-                          (string/replace (re-pattern (str "^" (gstring/regExpEscape repo-dir)))
-                                          "")
-                          (string/replace (re-pattern (str "(?i)" (gstring/regExpEscape (str "." ext)) "$"))
-                                          ""))]
-    (path/path-join repo-dir bak-dir relative-path)))
-
-(defn- <truncate-old-versioned-files!
-  "reserve the latest 6 version files"
-  [dir]
-  (-> (p/let [files (.readdir Filesystem (clj->js {:path dir}))
-
-              files (:files (js->clj files :keywordize-keys true))]
-        (drop 6 (reverse (sort-by :mtime files))))
-      (p/then (fn [old-version-files]
-                (p/all (mapv (fn [file]
-                               (.deleteFile Filesystem (clj->js {:path (:uri file)})))
-                             old-version-files))))
-      (p/catch (fn [_]))))
-
-;; TODO: move this to FS protocol
-(defn backup-file
-  "backup CONTENT under DIR :backup-dir or :version-file-dir
-  :backup-dir = `backup-dir`
-  :version-file-dir = `version-file-dir`"
-  [repo dir path content]
-  {:pre [(contains? #{:backup-dir :version-file-dir} dir)]}
-  (let [repo-dir (config/get-local-dir repo)
-        ext (util/get-file-ext path)
-        dir (case dir
-              :backup-dir (get-backup-dir repo-dir path backup-dir ext)
-              :version-file-dir (get-backup-dir repo-dir path version-file-dir ext))
-        new-path (path/path-join dir (str (string/replace (.toISOString (js/Date.)) ":" "_") "." (mobile-util/platform) "." ext))]
-
-    (<write-file-with-utf8 new-path content)
-    (<truncate-old-versioned-files! dir)))
-
-(defn backup-file-handle-changed!
-  [repo-dir file-path content]
-  (let [divider-schema    "://"
-        file-schema       (string/split file-path divider-schema)
-        file-schema       (if (> (count file-schema) 1) (first file-schema) "")
-        dir-schema?       (and (string? repo-dir)
-                               (string/includes? repo-dir divider-schema))
-        repo-dir          (if-not dir-schema?
-                            (str file-schema divider-schema repo-dir) repo-dir)
-        backup-root       (path/path-join repo-dir backup-dir)
-        backup-dir-parent (util/node-path.dirname file-path)
-        backup-dir-parent (string/replace backup-dir-parent repo-dir "")
-        backup-dir-name (util/node-path.name file-path)
-        file-extname (.extname util/node-path file-path)
-        file-root (path/path-join backup-root backup-dir-parent backup-dir-name)
-        file-path (path/path-join file-root
-                                  (str (string/replace (.toISOString (js/Date.)) ":" "_") "." (mobile-util/platform) file-extname))]
-    (<write-file-with-utf8 file-path content)
-    (<truncate-old-versioned-files! file-root)))
-
-(defn- write-file-impl!
-  [repo dir rpath content {:keys [ok-handler error-handler old-content skip-compare?]} stat]
-  (let [fpath (path/path-join dir rpath)]
-    (if (or (string/blank? repo) skip-compare?)
-      (p/catch
-       (p/let [result (<write-file-with-utf8 fpath content)]
-         (when ok-handler
-           (ok-handler repo fpath result)))
-       (fn [error]
-         (if error-handler
-           (error-handler error)
-           (log/error :write-file-failed error))))
-
-    ;; Compare with disk content and backup if not equal
-      (p/let [disk-content (if (not= stat :not-found)
-                             (<read-file-with-utf8 fpath)
-                             "")
-              disk-content (or disk-content "")
-              repo-dir (config/get-local-dir repo)
-              db-content (or old-content (db/get-file repo rpath) "")
-              contents-matched? (<contents-matched? disk-content db-content)]
-        (->
-         (p/let [result (<write-file-with-utf8 fpath content)
-                 mtime (-> (js->clj stat :keywordize-keys true)
-                           :mtime)]
-           (when-not contents-matched?
-             (backup-file repo-dir :backup-dir fpath disk-content))
-           (db/set-file-last-modified-at! repo rpath mtime)
-           (db/set-file-content! repo rpath content)
-           (when ok-handler
-             (ok-handler repo fpath result))
-           result)
-         (p/catch (fn [error]
-                    (if error-handler
-                      (error-handler error)
-                      (log/error :write-file-failed error)))))))))
-
-(defn ios-force-include-private
-  "iOS sometimes return paths without the private part."
-  [path]
-  (if (mobile-util/native-ios?)
-    (cond
-      (or (string/includes? path "///private/")
-          ;; virtual machine
-          (string/starts-with? path "file:///Users/"))
-      path
-
-      (string/includes? path "///")
-      (let [[prefix others] (string/split path "///")]
-        (str prefix "///private/" others))
-
-      :else
-      path)
-    path))
-
-(defn- local-container-path?
-  "Check whether `path' is logseq's container `localDocumentsPath' on iOS"
-  [path localDocumentsPath]
-  (string/includes? path localDocumentsPath))
-
-(rum/defc instruction
-  []
-  [:div.instruction
-   [:h1.title "Please choose a valid directory!"]
-   [:p.leading-6 "Logseq app can only save or access your graphs stored in a specific directory with a "
-    [:strong "Logseq icon"]
-    " inside, located either in \"iCloud Drive\", \"On My iPhone\" or \"On My iPad\"."]
-   [:p.leading-6 "Please watch the following short instruction video. "
-    [:small.text-gray-500 "(may take few seconds to load...)"]]
-   [:iframe
-    {:src "https://www.loom.com/embed/dae612ae5fd94e508bd0acdf02efb888"
-     :frame-border "0"
-     :position "relative"
-     :allow-full-screen "allowfullscreen"
-     :webkit-allow-full-screen "webkitallowfullscreen"
-     :height "100%"}]])
-
-(defn- open-dir
-  [dir]
-  (p/let [_ (when (mobile-util/native-android?) (android-check-permission))
-          {:keys [path localDocumentsPath]} (-> (.pickFolder mobile-util/folder-picker
-                                                             (clj->js (when (and dir (mobile-util/native-ios?))
-                                                                        {:path dir})))
-                                                (p/then #(js->clj % :keywordize-keys true))
-                                                (p/catch (fn [e]
-                                                           (js/alert (str e))
-                                                           nil))) ;; NOTE: If pick folder fails, let it crash
-          _ (when (and (mobile-util/native-ios?)
-                       (not (or (local-container-path? path localDocumentsPath)
-                                (mobile-util/in-iCloud-container-path? path))))
-              (state/pub-event! [:modal/show-instruction]))
-          exists? (<dir-exists? path)
-          _ (when-not exists?
-              (p/rejected (str "Cannot access selected directory: " path)))
-          _ (when (mobile-util/is-iCloud-container-path? path)
-              (p/rejected (str "Please avoid accessing the top-level iCloud container path: " path)))
-          path (if (mobile-util/native-ios?)
-                 (ios-force-include-private path)
-                 path)
-          _ (js/console.log "Opening or Creating graph at directory: " path)
-          files (get-files path)]
-    {:path path
-     :files (into [] files)}))
-
-(defrecord ^:large-vars/cleanup-todo Capacitorfs []
-  protocol/Fs
-  (mkdir! [_this dir]
-    (-> (<dir-exists? dir)
-        (p/then (fn [exists?]
-                  (if exists?
-                    (p/resolved true)
-                    (.mkdir Filesystem
-                            (clj->js
-                             {:path dir})))))
-        (p/catch (fn [error]
-                   (log/error :mkdir! {:path dir
-                                       :error error})))))
-  (mkdir-recur! [_this dir]
-    (-> (<dir-exists? dir)
-        (p/then (fn [exists?]
-                  (if exists?
-                    (p/resolved true)
-                    (.mkdir Filesystem
-                            (clj->js
-                             {:path dir
-                              :recursive true})))))
-        (p/catch (fn [error]
-                   (log/error :mkdir-recur! {:path dir
-                                             :error error})))))
-  (readdir [_this dir]                  ; recursive
-    (let [dir (path/path-normalize dir)]
-      (get-file-paths dir)))
-  (unlink! [this repo fpath _opts]
-    (p/let [repo-dir (config/get-local-dir repo)
-            recycle-dir (path/path-join repo-dir config/app-name ".recycle") ;; logseq/.recycle
-            ;; convert url to pure path
-            file-name (-> (path/trim-dir-prefix repo-dir fpath)
-                          (string/replace "/" "_"))
-            new-path (path/path-join recycle-dir file-name)
-            _ (protocol/mkdir-recur! this recycle-dir)]
-      (protocol/rename! this repo fpath new-path)))
-  (rmdir! [_this _dir]
-    ;; Too dangerous!!! We'll never implement this.
-    nil)
-  (read-file [_this dir path _options]
-    (let [fpath (path/path-join dir path)]
-      (->
-       (<read-file-with-utf8 fpath)
-       (p/catch (fn [error]
-                  (log/error :read-file-failed error))))))
-  (write-file! [_this repo dir path content opts]
-    (let [fpath (path/path-join dir path)]
-      (p/let [stat (p/catch
-                    (.stat Filesystem (clj->js {:path fpath}))
-                    (fn [_e] :not-found))]
-        ;; `path` is full-path
-        (write-file-impl! repo dir path content opts stat))))
-  (rename! [_this _repo old-fpath new-fpath]
-    (-> (.rename Filesystem
-                 (clj->js
-                  {:from old-fpath
-                   :to new-fpath}))
-        (p/catch (fn [error]
-                   (log/error :rename-file-failed error)))))
-  (copy! [_this _repo old-path new-path]
-    (-> (.copy Filesystem
-               (clj->js
-                {:from old-path
-                 :to new-path}))
-        (p/catch (fn [error]
-                   (log/error :copy-file-failed error)))))
-  (stat [_this fpath]
-    (-> (p/chain (.stat Filesystem (clj->js {:path fpath}))
-                 #(js->clj % :keywordize-keys true))
-        (p/catch (fn [error]
-                   (let [errstr (if error (.toString error) "")]
-                     (when (string/includes? errstr "because you don’t have permission to view it")
-                       (state/pub-event! [:notification/show
-                                          {:content "No permission, please clear cache and re-open graph folder."
-                                           :status :error}]))
-                     (p/rejected error))))))
-  (open-dir [_this dir]
-    (open-dir dir))
-  (get-files [_this dir]
-    (get-files dir))
-  (watch-dir! [_this dir _options]
-    (p/let [_ (.unwatch mobile-util/fs-watcher)]
-      (.watch mobile-util/fs-watcher (clj->js {:path dir}))))
-  (unwatch-dir! [_this _dir]
-    (.unwatch mobile-util/fs-watcher)))

+ 2 - 195
src/main/frontend/fs/sync.cljs

@@ -1,7 +1,6 @@
 (ns frontend.fs.sync
   "Main ns for providing file sync functionality"
-  (:require ["@capawesome/capacitor-background-task" :refer [BackgroundTask]]
-            ["path" :as node-path]
+  (:require ["path" :as node-path]
             [cljs-http.client :as http]
             [cljs-time.coerce :as tc]
             [cljs-time.core :as t]
@@ -23,12 +22,10 @@
             [frontend.diff :as diff]
             [frontend.encrypt :as encrypt]
             [frontend.fs :as fs]
-            [frontend.fs.capacitor-fs :as capacitor-fs]
             [frontend.fs.diff-merge :as diff-merge]
             [frontend.handler.file-based.file :as file-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.user :as user]
-            [frontend.mobile.util :as mobile-util]
             [frontend.pubsub :as pubsub]
             [frontend.state :as state]
             [frontend.util :as util]
@@ -911,149 +908,10 @@
   (<add-new-version [_this repo path content]
     (p->c (ipc/ipc "addVersionFile" (config/get-local-dir repo) path content))))
 
-(deftype ^:large-vars/cleanup-todo CapacitorAPI [^:mutable graph-uuid' ^:mutable private-key ^:mutable public-key']
-  IToken
-  (<get-token [_this]
-    (user/<wrap-ensure-id&access-token
-     (state/get-auth-id-token)))
-
-  IRSAPI
-  (rsapi-ready? [_ graph-uuid] (and (= graph-uuid graph-uuid') private-key public-key'))
-  (<key-gen [_]
-    (go (let [r (<! (p->c (.keygen mobile-util/file-sync #js {})))]
-          (-> r
-              (js->clj :keywordize-keys true)))))
-  (<set-env [_ graph-uuid prod? secret-key public-key]
-    (set! graph-uuid' graph-uuid)
-    (set! private-key secret-key)
-    (set! public-key' public-key)
-    (p->c (.setEnv mobile-util/file-sync (clj->js {:graphUUID graph-uuid
-                                                   :env (if prod? "prod" "dev")
-                                                   :secretKey secret-key
-                                                   :publicKey public-key}))))
-
-  (<get-local-all-files-meta [this graph-uuid base-path]
-    (go
-      (let [r (<! (p->c (.getLocalAllFilesMeta mobile-util/file-sync (clj->js {:graphUUID graph-uuid
-                                                                               :basePath base-path}))))]
-        (or (guard-ex r)
-            (<! (<build-local-file-metadatas this graph-uuid (.-result r)))))))
-
-  (<get-local-files-meta [this graph-uuid base-path filepaths]
-    (go
-      (let [r (<! (p->c (.getLocalFilesMeta mobile-util/file-sync
-                                            (clj->js {:graphUUID graph-uuid
-                                                      :basePath base-path
-                                                      :filePaths filepaths}))))]
-        (assert (not (instance? ExceptionInfo r)) "get-local-files-meta shouldn't return exception")
-        (<! (<build-local-file-metadatas this graph-uuid (.-result r))))))
-
-  (<rename-local-file [_ graph-uuid base-path from to]
-    (p->c (.renameLocalFile mobile-util/file-sync
-                            (clj->js {:graphUUID graph-uuid
-                                      :basePath base-path
-                                      :from (path-normalize from)
-                                      :to (path-normalize to)}))))
-
-  (<update-local-files [this graph-uuid base-path filepaths]
-    (go
-      (let [token-or-exp (<! (<get-token this))
-            filepaths' (map path-normalize filepaths)]
-        (or (guard-ex token-or-exp)
-            (<! (p->c (.updateLocalFiles mobile-util/file-sync (clj->js {:graphUUID graph-uuid
-                                                                         :basePath base-path
-                                                                         :filePaths filepaths'
-                                                                         :token token-or-exp}))))))))
-  (<fetch-remote-files [this graph-uuid base-path filepaths]
-    (go
-      (let [token-or-exp (<! (<get-token this))]
-        (or (guard-ex token-or-exp)
-            (js->clj
-             (.-value
-              (<! (<retry-rsapi
-                   #(p->c (.fetchRemoteFiles mobile-util/file-sync
-                                             (clj->js {:graphUUID graph-uuid
-                                                       :basePath base-path
-                                                       :filePaths filepaths
-                                                       :token token-or-exp})))))))))))
-  (<download-version-files [this graph-uuid base-path filepaths]
-    (go
-      (let [token-or-exp (<! (<get-token this))]
-        (or (guard-ex token-or-exp)
-            (<! (<retry-rsapi
-                 #(p->c (.updateLocalVersionFiles mobile-util/file-sync
-                                                  (clj->js {:graphUUID graph-uuid
-                                                            :basePath base-path
-                                                            :filePaths filepaths
-                                                            :token token-or-exp})))))))))
-
-  (<delete-local-files [_ graph-uuid base-path filepaths]
-    (let [normalized-filepaths (mapv path-normalize filepaths)]
-      (go
-        (let [r (<! (<retry-rsapi #(p->c (.deleteLocalFiles mobile-util/file-sync
-                                                            (clj->js {:graphUUID graph-uuid
-                                                                      :basePath base-path
-                                                                      :filePaths normalized-filepaths})))))]
-          r))))
-
-  (<update-remote-files [this graph-uuid base-path filepaths local-txid]
-    (let [normalized-filepaths (mapv path-normalize filepaths)]
-      (go
-        (let [token-or-exp (<! (<get-token this))
-              r (or (guard-ex token-or-exp)
-                    (<! (p->c (.updateRemoteFiles mobile-util/file-sync
-                                                  (clj->js {:graphUUID graph-uuid
-                                                            :basePath base-path
-                                                            :filePaths normalized-filepaths
-                                                            :txid local-txid
-                                                            :token token-or-exp
-                                                            :fnameEncryption true})))))]
-          (or (guard-ex r)
-              (get (js->clj r) "txid"))))))
-
-  (<delete-remote-files [this graph-uuid base-path filepaths local-txid]
-    (let [normalized-filepaths (mapv path-normalize filepaths)]
-      (go
-        (let [token-or-exp (<! (<get-token this))
-              r (or (guard-ex token-or-exp)
-                    (<! (p->c (.deleteRemoteFiles mobile-util/file-sync
-                                                  (clj->js {:graphUUID graph-uuid
-                                                            :basePath base-path
-                                                            :filePaths normalized-filepaths
-                                                            :txid local-txid
-                                                            :token token-or-exp})))))]
-          (or (guard-ex r)
-              (get (js->clj r) "txid"))))))
-
-  (<encrypt-fnames [_ graph-uuid fnames]
-    (go
-      (let [r (<! (p->c (.encryptFnames mobile-util/file-sync
-                                        (clj->js {:graphUUID graph-uuid
-                                                  :filePaths fnames}))))]
-        (or (guard-ex r)
-            (get (js->clj r) "value")))))
-  (<decrypt-fnames [_ graph-uuid fnames]
-    (go (let [r (<! (p->c (.decryptFnames mobile-util/file-sync
-                                          (clj->js {:graphUUID graph-uuid
-                                                    :filePaths fnames}))))]
-          (if (instance? ExceptionInfo r)
-            (ex-info "decrypt-failed" {:fnames fnames} (ex-cause r))
-            (get (js->clj r) "value")))))
-  (<cancel-all-requests [_]
-    (p->c (.cancelAllRequests mobile-util/file-sync)))
-  (<add-new-version [_this repo path content]
-    (p->c (capacitor-fs/backup-file repo :version-file-dir path content))))
-
 (def rsapi (cond
              (util/electron?)
              (->RSAPI nil nil nil)
 
-             (mobile-util/native-ios?)
-             (->CapacitorAPI nil nil nil)
-
-             (mobile-util/native-android?)
-             (->CapacitorAPI nil nil nil)
-
              :else
              nil))
 
@@ -3227,9 +3085,7 @@
         local->remote-syncer (->Local->RemoteSyncer user-uuid graph-uuid
                                                     base-path
                                                     repo *sync-state remoteapi-with-stop
-                                                    (if (mobile-util/native-platform?)
-                                                      2000
-                                                      10000)
+                                                    10000
                                                     *txid *txid-for-get-deletion-log nil (chan) *stopped? *paused?
                                                     (chan 1) (chan 1))
         remote->local-syncer (->Remote->LocalSyncer user-uuid graph-uuid base-path
@@ -3260,11 +3116,6 @@
 
     (reset! current-sm-graph-uuid nil)))
 
-(defn <sync-local->remote-now []
-  (go
-    (when-let [_sm ^SyncManager (state/get-file-sync-manager (state/get-current-file-sync-graph-uuid))]
-      (offer! immediately-local->remote-chan true))))
-
 (defn sync-need-password!
   []
   (when-let [sm ^SyncManager (state/get-file-sync-manager (state/get-current-file-sync-graph-uuid))]
@@ -3410,52 +3261,8 @@
           (finally
             (reset! *sync-starting false)))))))
 
-(defn- restart-if-stopped!
-  [is-active?]
-  (cond
-    (and is-active? (graph-sync-off? (second @graphs-txid)))
-    (<sync-start)
-
-    :else
-    (offer! pause-resume-chan is-active?)))
-
-(def app-state-changed-cursor (rum/cursor state/state :mobile/app-state-change))
-
 (def finished-local->remote-chan (chan 1))
 
-(add-watch app-state-changed-cursor "sync"
-           (fn [_ _ _ {:keys [is-active?]}]
-             (cond
-               (mobile-util/native-android?)
-               (when-not is-active?
-                 (<sync-local->remote-now))
-
-               (mobile-util/native-ios?)
-               (let [*task-id (atom nil)]
-                 (if is-active?
-                   (restart-if-stopped! is-active?)
-                   (when (state/get-current-file-sync-graph-uuid)
-                     (p/let [task-id (.beforeExit ^js BackgroundTask
-                                                  (fn []
-                                                    (go
-                                                      ;; Wait for file watcher events
-                                                      (<! (timeout 2000))
-                                                      (util/drain-chan finished-local->remote-chan)
-                                                      (<! (<sync-local->remote-now))
-                                                      ;; wait at most 20s
-                                                      (async/alts! [finished-local->remote-chan (timeout 20000)])
-                                                      (p/let [active? (mobile-util/app-active?)]
-                                                        (when-not active?
-                                                          (offer! pause-resume-chan is-active?)))
-                                                      (<! (timeout 5000))
-                                                      (prn "finish task: " @*task-id)
-                                                      (let [opt #js {:taskId @*task-id}]
-                                                        (.finish ^js BackgroundTask opt)))))]
-                       (reset! *task-id task-id)))))
-
-               :else
-               nil)))
-
 ;;; ### some add-watches
 
 ;; TODO: replace this logic by pause/resume state

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

@@ -30,7 +30,6 @@
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.user :as user-handler]
             [frontend.idb :as idb]
-            [frontend.mobile.core :as mobile]
             [frontend.mobile.util :as mobile-util]
             [frontend.modules.instrumentation.core :as instrument]
             [frontend.modules.shortcut.core :as shortcut]
@@ -167,8 +166,6 @@
   (events/run!)
 
   (p/do!
-   (when (mobile-util/native-platform?)
-     (mobile/mobile-preinit))
    (-> (p/let [_ (db-browser/start-db-worker!)
                repos (repo-handler/get-repos)
                _ (state/set-repos! repos)

+ 0 - 3945
src/main/frontend/handler/editor.cljs.~9b1a565dd097a74872fec18d659f52d3296a7680~

@@ -1,3945 +0,0 @@
-(ns ^:no-doc frontend.handler.editor
-  (:require [clojure.set :as set]
-            [clojure.string :as string]
-            [clojure.walk :as w]
-            [dommy.core :as dom]
-            [frontend.commands :as commands]
-            [frontend.config :as config]
-            [frontend.date :as date]
-            [frontend.db :as db]
-            [frontend.db.async :as db-async]
-            [frontend.db.file-based.model :as file-model]
-            [frontend.db.model :as db-model]
-            [frontend.db.utils :as db-utils]
-            [frontend.diff :as diff]
-            [frontend.extensions.pdf.utils :as pdf-utils]
-            [frontend.format.block :as block]
-            [frontend.format.mldoc :as mldoc]
-            [frontend.fs :as fs]
-            [frontend.fs.capacitor-fs :as capacitor-fs]
-            [frontend.handler.assets :as assets-handler]
-            [frontend.handler.block :as block-handler]
-            [frontend.handler.common :as common-handler]
-            [frontend.handler.common.editor :as editor-common-handler]
-            [frontend.handler.db-based.editor :as db-editor-handler]
-            [frontend.handler.export.html :as export-html]
-            [frontend.handler.export.text :as export-text]
-            [frontend.handler.file-based.editor :as file-editor-handler]
-            [frontend.handler.file-based.status :as status]
-            [frontend.handler.notification :as notification]
-            [frontend.handler.property :as property-handler]
-            [frontend.handler.property.file :as property-file]
-            [frontend.handler.property.util :as pu]
-            [frontend.handler.route :as route-handler]
-            [frontend.mobile.util :as mobile-util]
-            [frontend.modules.outliner.op :as outliner-op]
-            [frontend.modules.outliner.tree :as tree]
-            [frontend.modules.outliner.ui :as ui-outliner-tx]
-            [frontend.ref :as ref]
-            [frontend.search :as search]
-            [frontend.state :as state]
-            [frontend.template :as template]
-            [frontend.util :as util]
-            [frontend.util.cursor :as cursor]
-            [frontend.util.file-based.drawer :as drawer]
-            [frontend.util.keycode :as keycode]
-            [frontend.util.list :as list]
-            [frontend.util.text :as text-util]
-            [frontend.util.thingatpt :as thingatpt]
-            [goog.crypt.base64 :as base64]
-            [goog.dom :as gdom]
-            [goog.dom.classes :as gdom-classes]
-            [goog.object :as gobj]
-            [goog.string :as gstring]
-            [lambdaisland.glogi :as log]
-            [logseq.common.path :as path]
-            [logseq.common.util :as common-util]
-            [logseq.common.util.block-ref :as block-ref]
-            [logseq.common.util.page-ref :as page-ref]
-            [logseq.db :as ldb]
-            [logseq.db.common.entity-plus :as entity-plus]
-            [logseq.db.file-based.schema :as file-schema]
-            [logseq.db.frontend.property :as db-property]
-            [logseq.graph-parser.block :as gp-block]
-            [logseq.graph-parser.mldoc :as gp-mldoc]
-            [logseq.graph-parser.property :as gp-property]
-            [logseq.graph-parser.text :as text]
-            [logseq.graph-parser.utf8 :as utf8]
-            [logseq.outliner.core :as outliner-core]
-            [logseq.outliner.property :as outliner-property]
-            [logseq.shui.popup.core :as shui-popup]
-            [promesa.core :as p]
-            [rum.core :as rum]))
-
-;; FIXME: should support multiple images concurrently uploading
-
-(defonce *asset-uploading? (atom false))
-(defonce *asset-uploading-process (atom 0))
-
-(def clear-selection! state/clear-selection!)
-(def edit-block! block-handler/edit-block!)
-
-(defn- outliner-save-block!
-  [block & {:as opts}]
-  (outliner-op/save-block! block opts))
-
-(defn get-block-own-order-list-type
-  [block]
-  (pu/lookup block :logseq.property/order-list-type))
-
-(defn set-block-own-order-list-type!
-  [block type]
-  (when-let [uuid (:block/uuid block)]
-    (property-handler/set-block-property! (state/get-current-repo) uuid (pu/get-pid :logseq.property/order-list-type) (name type))))
-
-(defn remove-block-own-order-list-type!
-  [block]
-  (when-let [uuid (:block/uuid block)]
-    (property-handler/remove-block-property! (state/get-current-repo) uuid (pu/get-pid :logseq.property/order-list-type))))
-
-(defn own-order-number-list?
-  [block]
-  (when-let [block (db/entity (:db/id block))]
-    (= (get-block-own-order-list-type block) "number")))
-
-(defn make-block-as-own-order-list!
-  [block]
-  (some-> block (set-block-own-order-list-type! "number")))
-
-(defn toggle-blocks-as-own-order-list!
-  [blocks]
-  (when (seq blocks)
-    (let [has-ordered?    (some own-order-number-list? blocks)
-          blocks-uuids    (some->> blocks (map :block/uuid) (remove nil?))
-          order-list-prop (pu/get-pid :logseq.property/order-list-type)
-          repo (state/get-current-repo)]
-      (if has-ordered?
-        (property-handler/batch-remove-block-property! repo blocks-uuids order-list-prop)
-        (property-handler/batch-set-block-property! repo blocks-uuids order-list-prop "number")))))
-
-(defn get-selection-and-format
-  []
-  (when-let [block (state/get-edit-block)]
-    (when (:block/uuid block)
-      (when-let [edit-id (state/get-edit-input-id)]
-        (when-let [input (gdom/getElement edit-id)]
-          (let [selection-start (util/get-selection-start input)
-                selection-end (util/get-selection-end input)
-                value (gobj/get input "value")
-                selection (when (not= selection-start selection-end)
-                            (subs value selection-start selection-end))
-                selection-start (+ selection-start
-                                   (count (take-while #(= " " %) selection)))
-                selection-end (- selection-end
-                                 (count (take-while #(= " " %) (reverse selection))))]
-            {:selection-start selection-start
-             :selection-end selection-end
-             :selection (some-> selection
-                                string/trim)
-             :format (get block :block/format :markdown)
-             :value value
-             :block block
-             :edit-id edit-id
-             :input input}))))))
-
-(defn- format-text!
-  [pattern-fn]
-  (when-let [m (get-selection-and-format)]
-    (let [{:keys [selection-start selection-end format selection value edit-id input]} m
-          pattern (pattern-fn format)
-          pattern-count (count pattern)
-          pattern-prefix (subs value (max 0 (- selection-start pattern-count)) selection-start)
-          pattern-suffix (subs value selection-end (min (count value) (+ selection-end pattern-count)))
-          already-wrapped? (= pattern pattern-prefix pattern-suffix)
-          prefix (if already-wrapped?
-                   (subs value 0 (- selection-start pattern-count))
-                   (subs value 0 selection-start))
-          postfix (if already-wrapped?
-                    (subs value (+ selection-end pattern-count))
-                    (subs value selection-end))
-          inner-value (cond-> selection
-                        (not already-wrapped?)
-                        (#(str pattern % pattern)))
-          new-value (str prefix inner-value postfix)]
-      (state/set-edit-content! edit-id new-value)
-      (cond
-        already-wrapped? (cursor/set-selection-to input (- selection-start pattern-count) (- selection-end pattern-count))
-        selection (cursor/move-cursor-to input (+ selection-end pattern-count))
-        :else (cursor/set-selection-to input (+ selection-start pattern-count) (+ selection-end pattern-count))))))
-
-(defn bold-format! []
-  (format-text! config/get-bold))
-
-(defn italics-format! []
-  (format-text! config/get-italic))
-
-(defn highlight-format! []
-  (when-let [block (state/get-edit-block)]
-    (let [format (get block :block/format :markdown)]
-      (format-text! #(config/get-highlight format)))))
-
-(defn strike-through-format! []
-  (format-text! config/get-strike-through))
-
-(defn html-link-format!
-  ([]
-   (html-link-format! nil))
-  ([text]
-   (when-let [m (get-selection-and-format)]
-     (let [{:keys [selection-start selection-end format selection value edit-id input]} m
-           empty-selection? (= selection-start selection-end)
-           selection-link? (and selection (gp-mldoc/mldoc-link? format selection))
-           [content forward-pos] (cond
-                                   empty-selection?
-                                   (config/get-empty-link-and-forward-pos format)
-
-                                   (and text selection-link?)
-                                   (config/with-label-link format text selection)
-
-                                   text
-                                   (config/with-label-link format selection text)
-
-                                   selection-link?
-                                   (config/with-default-link format selection)
-
-                                   :else
-                                   (config/with-default-label format selection))
-           new-value (str
-                      (subs value 0 selection-start)
-                      content
-                      (subs value selection-end))
-           cur-pos (or selection-start (cursor/pos input))]
-       (state/set-edit-content! edit-id new-value)
-       (cursor/move-cursor-to input (+ cur-pos forward-pos))))))
-
-(defn open-block-in-sidebar!
-  [block-id]
-  ; (assert (uuid? block-id) "frontend.handler.editor/open-block-in-sidebar! expects block-id to be of type uuid")
-  (when block-id
-    (when-let [block (db/entity (if (number? block-id) block-id [:block/uuid block-id]))]
-      (let [page? (nil? (:block/page block))]
-        (state/sidebar-add-block!
-         (state/get-current-repo)
-         (:db/id block)
-         (if page? :page :block))))))
-
-(defn reset-cursor-range!
-  [node]
-  (when node
-    (state/set-cursor-range! (util/caret-range node))))
-
-(defn restore-cursor-pos!
-  [id markup]
-  (when-let [node (gdom/getElement (str id))]
-    (let [cursor-range (state/get-cursor-range)
-          pos (or (state/get-editor-last-pos)
-                  (and cursor-range
-                       (diff/find-position markup cursor-range)))]
-      (cursor/move-cursor-to node pos))))
-
-(defn highlight-block!
-  [block-uuid]
-  (let [blocks (util/get-blocks-by-id block-uuid)]
-    (doseq [block blocks]
-      (dom/add-class! block "block-highlight"))))
-
-(defn unhighlight-blocks!
-  []
-  (let [blocks (some->> (array-seq (js/document.getElementsByClassName "block-highlight"))
-                        (repeat 2)
-                        (apply concat))]
-    (doseq [block blocks]
-      (gdom-classes/remove block "block-highlight"))))
-
-(defn wrap-parse-block
-  [block]
-  (if (config/db-based-graph? (state/get-current-repo))
-    (db-editor-handler/wrap-parse-block block)
-    (file-editor-handler/wrap-parse-block block)))
-
-(defn- save-block-inner!
-  [block value opts]
-  (let [block {:db/id (:db/id block)
-               :block/uuid (:block/uuid block)
-               :block/title value}
-        block' (-> (wrap-parse-block block)
-                   ;; :block/uuid might be changed when backspace/delete
-                   ;; a block that has been refed
-                   (assoc :block/uuid (:block/uuid block)))
-        opts' (assoc opts :outliner-op :save-block)]
-    (ui-outliner-tx/transact!
-     opts'
-     (outliner-save-block! block'))))
-
-;; id: block dom id, "ls-block-counter-uuid"
-(defn- another-block-with-same-id-exists?
-  [current-id block-id]
-  (when-let [id (and (string? block-id) (parse-uuid block-id))]
-    (and (not= current-id id)
-         (db/entity [:block/uuid id]))))
-
-(defn save-block-if-changed!
-  ([block value]
-   (save-block-if-changed! block value nil))
-  ([block value
-    {:keys [force?]
-     :as opts}]
-   (let [{:block/keys [uuid format repo title]} block
-         format (or format :markdown)
-         repo (or repo (state/get-current-repo))
-         format (or format (state/get-preferred-format))
-         block-id (let [properties (:block/properties block)]
-                    (when (and (not (config/db-based-graph? repo)) (map? properties))
-                      (get properties :id)))
-         content (if (config/db-based-graph? repo)
-                   (:block/title (db/entity (:db/id block)))
-                   (-> (property-file/remove-built-in-properties-when-file-based repo format title)
-                       (drawer/remove-logbook)))]
-     (cond
-       (another-block-with-same-id-exists? uuid block-id)
-       (notification/show!
-        [:p.content
-         (util/format "Block with the id %s already exists!" block-id)]
-        :error)
-
-       force?
-       (save-block-inner! block value opts)
-
-       :else
-       (when content
-         (let [content-changed? (not= (string/trim content) (string/trim value))]
-           (when content-changed?
-             (save-block-inner! block value opts))))))))
-
-(defn- compute-fst-snd-block-text
-  [value selection-start selection-end]
-  (when (string? value)
-    (let [fst-block-text (subs value 0 selection-start)
-          snd-block-text (string/triml (subs value selection-end))]
-      [fst-block-text snd-block-text])))
-
-(declare save-current-block!)
-(defn outliner-insert-block!
-  [config current-block new-block {:keys [sibling? keep-uuid? ordered-list?
-                                          replace-empty-target?]}]
-  (let [ref-query-top-block? (and (or (:ref? config)
-                                      (:custom-query? config))
-                                  (not (:ref-query-child? config)))
-        has-children? (db/has-children? (:block/uuid current-block))
-        sibling? (cond
-                   ref-query-top-block?
-                   false
-
-                   (boolean? sibling?)
-                   sibling?
-
-                   (util/collapsed? current-block)
-                   true
-
-                   :else
-                   (not has-children?))]
-    (ui-outliner-tx/transact!
-     {:outliner-op :insert-blocks}
-     (save-current-block! {:current-block current-block})
-     (outliner-op/insert-blocks! [new-block] current-block {:sibling? sibling?
-                                                            :keep-uuid? keep-uuid?
-                                                            :ordered-list? ordered-list?
-                                                            :replace-empty-target? replace-empty-target?}))))
-
-(defn- block-self-alone-when-insert?
-  [config uuid]
-  (let [current-page (state/get-current-page)
-        block-id (or (some-> (:id config) parse-uuid)
-                     (some-> current-page parse-uuid))]
-    (= uuid block-id)))
-
-(defn insert-new-block-before-block-aux!
-  [config block value]
-  (let [edit-input-id (state/get-edit-input-id)
-        input (gdom/getElement edit-input-id)
-        input-text-selected? (util/input-text-selected? input)
-        new-m {:block/uuid (db/new-block-id)
-               :block/title ""}
-        prev-block (-> (merge (select-keys block [:block/parent :block/format :block/page])
-                              new-m)
-                       (wrap-parse-block))
-        block' (db/entity (:db/id block))
-        left-or-parent (or (ldb/get-left-sibling block') (:block/parent block'))]
-    (when input-text-selected?
-      (let [selection-start (util/get-selection-start input)
-            selection-end (util/get-selection-end input)
-            [_ new-content] (compute-fst-snd-block-text value selection-start selection-end)]
-        (state/set-edit-content! edit-input-id new-content)))
-    (let [sibling? (not= (:db/id left-or-parent) (:db/id (:block/parent block)))
-          result (outliner-insert-block! config left-or-parent prev-block {:sibling? sibling?
-                                                                           :keep-uuid? true})]
-      [result sibling? prev-block])))
-
-(defn insert-new-block-aux!
-  [config
-   {:block/keys [uuid]
-    :as block}
-   value]
-  (let [block-self? (block-self-alone-when-insert? config uuid)
-        input (gdom/getElement (state/get-edit-input-id))
-        selection-start (util/get-selection-start input)
-        selection-end (util/get-selection-end input)
-        [fst-block-text snd-block-text] (compute-fst-snd-block-text value selection-start selection-end)
-        current-block (assoc block :block/title fst-block-text)
-        current-block (apply dissoc current-block file-schema/retract-attributes)
-        new-m {:block/uuid (db/new-block-id)
-               :block/title snd-block-text}
-        next-block (-> (merge (select-keys block [:block/parent :block/format :block/page])
-                              new-m)
-                       (wrap-parse-block))
-        sibling? (or (:block/collapsed? (:block/link block)) (when block-self? false))
-        result (outliner-insert-block! config current-block next-block {:sibling? sibling?
-                                                                        :keep-uuid? true})]
-    [result sibling? next-block]))
-
-(defn clear-when-saved!
-  []
-  (commands/restore-state))
-
-(defn get-state
-  []
-  (let [[{:keys [on-hide block block-id block-parent-id format sidebar?]} id config] (state/get-editor-args)
-        node (gdom/getElement id)]
-    (when node
-      (let [value (gobj/get node "value")
-            pos (util/get-selection-start node)]
-        {:config config
-         :on-hide on-hide
-         :sidebar? sidebar?
-         :format format
-         :id id
-         :block (or (db/entity [:block/uuid (:block/uuid block)]) block)
-         :block-id block-id
-         :block-parent-id block-parent-id
-         :node node
-         :value value
-         :pos pos
-         :block-container (util/rec-get-node node "ls-block")}))))
-
-(defn- get-node-container-id
-  [node]
-  (some-> (dom/attr node "containerid")
-          util/safe-parse-int))
-
-(defn- get-node-parent
-  [node]
-  (some-> (gobj/get node "parentNode")
-          (util/rec-get-node "ls-block")))
-
-(defn- get-new-container-id
-  [op data]
-  (let [{:keys [block block-container]} (get-state)]
-    (when block
-      (let [node block-container
-            linked? (some? (dom/attr node "originalblockid"))]
-        (case op
-          :insert
-          (when (and linked? (not (false? (:sibling? data))))
-            (some-> (util/rec-get-node node "blocks-container")
-                    get-node-container-id))
-
-          :indent
-          ;; Get prev sibling's container id
-          (when-let [prev (.-previousSibling node)]
-            (when (dom/attr prev "originalblockid")
-              (get-node-container-id prev)))
-
-          :move-up
-          (let [parent (get-node-parent node)
-                prev (when parent (.-previousSibling parent))]
-            (when (and prev (dom/attr prev "originalblockid"))
-              (get-node-container-id prev)))
-
-          :move-down
-          (let [parent (get-node-parent node)
-                next (when parent (.-nextSibling parent))]
-            (when (and next (dom/attr next "originalblockid"))
-              (get-node-container-id next)))
-
-          :outdent
-          ;; Get embed block's root container id
-          (when-let [parent (some-> (gobj/get node "parentNode")
-                                    (util/rec-get-node "ls-block"))]
-            (when (dom/attr parent "originalblockid")
-              (some-> (util/rec-get-node parent "blocks-container")
-                      get-node-container-id))))))))
-
-(defn insert-new-block!
-  "Won't save previous block content - remember to save!"
-  ([state]
-   (insert-new-block! state nil))
-  ([_state block-value]
-   (->
-    (when (not config/publishing?)
-      (when-let [state (get-state)]
-        (state/set-state! :editor/async-unsaved-chars "")
-        (let [{:keys [block value config]} state
-              value (if (string? block-value) block-value value)
-              block-id (:block/uuid block)
-              block-self? (block-self-alone-when-insert? config block-id)
-              input-id (state/get-edit-input-id)
-              input (gdom/getElement input-id)
-              selection-start (util/get-selection-start input)
-              selection-end (util/get-selection-end input)
-              [fst-block-text snd-block-text] (compute-fst-snd-block-text value selection-start selection-end)
-              insert-above? (and (string/blank? fst-block-text) (not (string/blank? snd-block-text)))
-              block' (or (db/entity [:block/uuid block-id]) block)
-              original-block (:original-block config)
-              block'' (or
-                       (when original-block
-                         (let [e (db/entity (:db/id block'))]
-                           (if (and (some? (first (:block/_parent e)))
-                                    (not (:block/collapsed? e)))
-                          ;; object has children and not collapsed
-                             block'
-                             original-block)))
-                       block')
-              insert-fn (cond
-                          block-self?
-                          insert-new-block-aux!
-
-                          insert-above?
-                          insert-new-block-before-block-aux!
-
-                          :else
-                          insert-new-block-aux!)
-              [result-promise sibling? next-block] (insert-fn config block'' value)]
-          (p/do!
-           result-promise
-           (clear-when-saved!)
-           (let [next-block' (db/entity [:block/uuid (:block/uuid next-block)])
-                 pos 0
-                 unsaved-chars @(:editor/async-unsaved-chars @state/state)
-                 container-id (get-new-container-id :insert {:sibling? sibling?})]
-             (edit-block! next-block' (+ pos (count unsaved-chars))
-                          {:container-id container-id
-                           :custom-content (str unsaved-chars (:block/title next-block'))}))))))
-    (p/finally (fn []
-                 (state/set-state! :editor/async-unsaved-chars nil))))))
-
-(defn api-insert-new-block!
-  [content {:keys [page block-uuid sibling? before? properties
-                   custom-uuid replace-empty-target? edit-block? ordered-list? other-attrs]
-            :or {sibling? false
-                 before? false
-                 edit-block? true}}]
-  (when (or page block-uuid)
-    (let [repo (state/get-current-repo)
-          db-based? (config/db-based-graph? repo)
-          before? (if page false before?)
-          sibling? (boolean sibling?)
-          sibling? (if before? true (if page false sibling?))
-          block (if page
-                  (db/get-page page)
-                  (db/entity [:block/uuid block-uuid]))]
-      (when block
-        (let [last-block (when (not sibling?)
-                           (let [children (:block/_parent block)
-                                 blocks (db/sort-by-order children)
-                                 last-block-id (:db/id (last blocks))]
-                             (when last-block-id
-                               (db/entity last-block-id))))
-              format (or
-                      (get block :block/format :markdown)
-                      (db/get-page-format (:block/name block))
-                      (state/get-preferred-format))
-              content (if (and (not db-based?) (seq properties))
-                        (property-file/insert-properties-when-file-based repo format content properties)
-                        content)
-              new-block (cond->
-                         (-> (select-keys block [:block/page])
-                             (assoc :block/title content))
-                          (not db-based?)
-                          (assoc :block/format format))
-              new-block (assoc new-block :block/page
-                               (if page
-                                 (:db/id block)
-                                 (:db/id (:block/page new-block))))
-              new-block (-> new-block
-                            (wrap-parse-block)
-                            (assoc :block/uuid (or custom-uuid (db/new-block-id))))
-              new-block (merge new-block other-attrs)
-              [block-m sibling?] (cond
-                                   before?
-                                   (let [left-or-parent (or (ldb/get-left-sibling block)
-                                                            (:block/parent block))
-                                         sibling? (if (= (:db/id (:block/parent block)) (:db/id left-or-parent))
-                                                    false sibling?)]
-                                     [left-or-parent sibling?])
-
-                                   sibling?
-                                   [(db/entity (:db/id block)) sibling?]
-
-                                   last-block
-                                   [last-block true]
-
-                                   block
-                                   [(db/entity (:db/id block)) sibling?]
-
-                                   ;; FIXME: assert
-                                   :else
-                                   nil)]
-          (when block-m
-            (p/do!
-             (ui-outliner-tx/transact!
-              {:outliner-op :insert-blocks}
-              (outliner-insert-block! {} block-m new-block {:sibling? sibling?
-                                                            :keep-uuid? true
-                                                            :ordered-list? ordered-list?
-                                                            :replace-empty-target? replace-empty-target?})
-              (when (and db-based? (seq properties))
-                (property-handler/set-block-properties! repo (:block/uuid new-block) properties)))
-             (when edit-block?
-               (if (and replace-empty-target?
-                        (string/blank? (:block/title last-block)))
-                 (edit-block! last-block :max)
-                 (edit-block! new-block :max)))
-             (when-let [id (:block/uuid new-block)]
-               (db/entity [:block/uuid id])))))))))
-
-(defn insert-first-page-block-if-not-exists!
-  [page-uuid-or-title]
-  (let [page-title (str page-uuid-or-title)]
-    (when-not (string/blank? page-title)
-      (when-let [page (db/get-page page-title)]
-        (let [class-or-property? (or (ldb/class? page) (ldb/property? page))]
-          (when (or class-or-property? (db/page-empty? (state/get-current-repo) (:db/id page)))
-            (let [new-block (cond-> {:block/title ""}
-                              (not (config/db-based-graph? (state/get-current-repo)))
-                              (assoc :block/format (get page :block/format :markdown)))]
-              (ui-outliner-tx/transact!
-               {:outliner-op :insert-blocks}
-               (outliner-op/insert-blocks! [new-block] page {:sibling? false})))))))))
-
-(defn check
-  [{:block/keys [marker title repeated? uuid] :as block}]
-  (let [new-content (string/replace-first title marker "DONE")
-        new-content (if repeated?
-                      (file-editor-handler/update-timestamps-content! block title)
-                      new-content)
-        input-id (state/get-edit-input-id)]
-    (if (and input-id
-             (string/ends-with? input-id (str uuid)))
-      (state/set-edit-content! input-id new-content)
-      (save-block-if-changed! block new-content))))
-
-(defn uncheck
-  [{:block/keys [title uuid] :as block}]
-  (let [marker (if (= :now (state/get-preferred-workflow))
-                 "LATER"
-                 "TODO")
-        new-content (string/replace-first title "DONE" marker)
-        input-id (state/get-edit-input-id)]
-    (if (and input-id
-             (string/ends-with? input-id (str uuid)))
-      (state/set-edit-content! input-id new-content)
-      (save-block-if-changed! block new-content))))
-
-(defn get-selected-blocks
-  []
-  (distinct (seq (state/get-selection-blocks))))
-
-(defn set-marker
-  "The set-marker will set a new marker on the selected block.
-  if the `new-marker` is nil, it will generate it automatically."
-  ([block]
-   (set-marker block nil))
-  ([{:block/keys [marker title format] :as block} new-marker]
-   (let [[new-content _] (status/cycle-marker title marker new-marker format (state/get-preferred-workflow))]
-     (save-block-if-changed! block new-content))))
-
-(defn file-based-cycle-todo!
-  [block]
-  (when (not-empty (:block/title block))
-    (set-marker block)))
-
-(defn db-based-cycle-todo!
-  [block]
-  (let [status-value (if (ldb/class-instance? (db/entity :logseq.class/Task) block)
-                       (:logseq.property/status block)
-                       (get block :logseq.property/status {}))
-        next-status (case (:db/ident status-value)
-                      :logseq.property/status.todo
-                      :logseq.property/status.doing
-                      :logseq.property/status.doing
-                      :logseq.property/status.done
-                      :logseq.property/status.done
-                      nil
-                      :logseq.property/status.todo)
-        repo (state/get-current-repo)]
-    (property-handler/set-block-property! repo (:block/uuid block)
-                                          :logseq.property/status
-                                          (:db/id (db/entity next-status)))))
-
-(defn cycle-todos!
-  []
-  (when-let [blocks (seq (get-selected-blocks))]
-    (let [db-based? (config/db-based-graph? (state/get-current-repo))
-          ids (->> (distinct (map #(when-let [id (dom/attr % "blockid")]
-                                     (uuid id)) blocks))
-                   (remove nil?))]
-      (ui-outliner-tx/transact!
-       {:outliner-op :cycle-todos}
-       (doseq [id ids]
-         (when-let [block (db/entity [:block/uuid id])]
-           (if db-based?
-             (db-based-cycle-todo! block)
-             (file-based-cycle-todo! block))))))))
-
-(defn cycle-todo!
-  []
-  #_:clj-kondo/ignore
-  (when-not (state/get-editor-action)
-    (if-let [blocks (seq (get-selected-blocks))]
-      (cycle-todos!)
-      (when-let [edit-block (state/get-edit-block)]
-        (let [edit-input-id (state/get-edit-input-id)
-              current-input (gdom/getElement edit-input-id)]
-          (if (config/db-based-graph? (state/get-current-repo))
-            (when-let [block (db/entity (:db/id edit-block))]
-              (let [pos (state/get-edit-pos)]
-                (p/do!
-                 (ui-outliner-tx/transact!
-                  {:outliner-op :cycle-todos}
-                  (db-based-cycle-todo! block))
-
-                 ;; FIXME: don't change current editor's position
-                 )))
-            (let [content (state/get-edit-content)
-                  format (or (db/get-page-format (state/get-current-page))
-                             (state/get-preferred-format))
-                  [new-content marker] (status/cycle-marker content nil nil format (state/get-preferred-workflow))
-                  new-pos (commands/compute-pos-delta-when-change-marker
-                           content marker (cursor/pos current-input))]
-              (state/set-edit-content! edit-input-id new-content)
-              (cursor/move-cursor-to current-input new-pos))))))))
-
-(defn set-priority
-  [{:block/keys [priority title] :as block} new-priority]
-  (when-not (config/db-based-graph? (state/get-current-repo))
-    (let [new-content (string/replace-first title
-                                            (util/format "[#%s]" priority)
-                                            (util/format "[#%s]" new-priority))]
-      (save-block-if-changed! block new-content))))
-
-(defn delete-block-aux!
-  [{:block/keys [uuid] :as _block}]
-  (let [block (db/entity [:block/uuid uuid])]
-    (when block
-      (let [blocks (block-handler/get-top-level-blocks [block])]
-        (ui-outliner-tx/transact!
-         {:outliner-op :delete-blocks}
-         (outliner-op/delete-blocks! blocks {}))))))
-
-(defn- move-to-prev-block
-  [repo sibling-block format value]
-  (when (and repo sibling-block)
-    (when-let [sibling-block-id (dom/attr sibling-block "blockid")]
-      (when-let [sibling-entity (db/entity [:block/uuid (uuid sibling-block-id)])]
-        (if (:block/name sibling-entity)
-          {:prev-block sibling-entity
-           :new-value (:block/title sibling-entity)
-           :edit-block-f #(edit-block! sibling-entity :max)}
-          (let [db? (config/db-based-graph? repo)
-                original-content (if (= (:db/id sibling-entity) (:db/id (state/get-edit-block)))
-                                   (state/get-edit-content)
-                                   (:block/title sibling-entity))
-                value' (if db?
-                         original-content
-                         (-> (property-file/remove-built-in-properties-when-file-based repo format original-content)
-                             (drawer/remove-logbook)))
-                value (if db?
-                        value
-                        (->> value
-                             (property-file/remove-properties-when-file-based repo format)
-                             (drawer/remove-logbook)))
-                new-value (str value' value)
-                tail-len (count value)
-                pos (max
-                     (if original-content
-                       (gobj/get (utf8/encode original-content) "length")
-                       0)
-                     0)
-                [edit-target container-id] [(db/entity (:db/id sibling-entity)) (some-> (dom/attr sibling-block "containerid")
-                                                                                        util/safe-parse-int)]]
-            {:prev-block sibling-entity
-             :new-content new-value
-             :pos pos
-             :edit-block-f #(edit-block! edit-target
-                                         pos
-                                         {:custom-content new-value
-                                          :tail-len tail-len
-                                          :container-id container-id})}))))))
-
-(declare save-block!)
-
-(declare expand-block!)
-
-(defn delete-block-inner!
-  [repo {:keys [block-id value format config]}]
-  (when block-id
-    (when-let [block-e (db/entity [:block/uuid block-id])]
-      (let [prev-block (db-model/get-prev (db/get-db) (:db/id block-e))
-            block-parent-id (str "ls-block-" block-id)]
-        (cond
-          (and (nil? prev-block)
-               (nil? (:block/parent block-e)))
-          nil
-
-          :else
-          (let [has-children? (seq (:block/_parent block-e))
-                block (db/entity (:db/id block-e))
-                left (or (ldb/get-left-sibling block) (:block/parent block))
-                left-has-children? (and left
-                                        (when-let [block-id (:block/uuid left)]
-                                          (let [block (db/entity [:block/uuid block-id])]
-                                            (seq (:block/_parent block)))))]
-            (when-not (and has-children? left-has-children?)
-              (when block-parent-id
-                (let [block-parent (gdom/getElement block-parent-id)
-                      sibling-or-parent-block
-                      (if (:embed? config)
-                        (util/get-prev-block-non-collapsed
-                         block-parent
-                         {:container (util/rec-get-blocks-container block-parent)})
-                        (util/get-prev-block-non-collapsed-non-embed block-parent))
-                      {:keys [prev-block new-content edit-block-f]}
-                      (move-to-prev-block repo sibling-or-parent-block format value)
-                      concat-prev-block? (boolean (and prev-block new-content))
-                      transact-opts {:outliner-op :delete-blocks}]
-                  (cond
-                    (and prev-block (:block/name prev-block)
-                         (not= (:db/id prev-block) (:db/id (:block/parent block)))
-                         (db-model/hidden-page? (:block/page block))) ; embed page
-                    nil
-
-                    concat-prev-block?
-                    (let [children (:block/_parent (db/entity (:db/id block)))
-                          db-based? (config/db-based-graph? repo)
-                          prev-block-is-not-parent? (empty? (:block/_parent prev-block))
-                          delete-prev-block? (and db-based?
-                                                  prev-block-is-not-parent?
-                                                  (empty? (:block/tags block))
-                                                  (not (:logseq.property.node/display-type block))
-                                                  (seq (:block/properties block))
-                                                  (empty? (:block/properties prev-block))
-                                                  (not (:logseq.property/created-from-property block)))]
-                      (if delete-prev-block?
-                        (p/do!
-                         (ui-outliner-tx/transact!
-                          transact-opts
-                          (delete-block-aux! prev-block)
-                          (save-block! repo block new-content {}))
-                         (edit-block! (assoc block :block/title new-content) (count (:block/title prev-block))))
-                        (p/do!
-                         (ui-outliner-tx/transact!
-                          transact-opts
-                          (when (seq children)
-                            (outliner-op/move-blocks! children prev-block false))
-                          (delete-block-aux! block)
-                          (save-block! repo prev-block new-content {}))
-                         (when edit-block-f (edit-block-f)))))
-
-                    :else
-                    (p/do!
-                     (delete-block-aux! block)
-                     (when edit-block-f (edit-block-f)))))))))))))
-
-(defn delete-block!
-  [repo]
-  (delete-block-inner! repo (get-state)))
-
-(defn delete-blocks!
-  [repo block-uuids blocks dom-blocks]
-  (when (seq block-uuids)
-    (let [uuid->dom-block (zipmap block-uuids dom-blocks)
-          block (first blocks)
-          block-parent (get uuid->dom-block (:block/uuid block))
-          sibling-block (when block-parent (util/get-prev-block-non-collapsed-non-embed block-parent))
-          blocks' (block-handler/get-top-level-blocks blocks)]
-      (p/do!
-       (ui-outliner-tx/transact!
-        {:outliner-op :delete-blocks}
-        (outliner-op/delete-blocks! blocks' nil))
-       (when sibling-block
-         (let [{:keys [edit-block-f]} (move-to-prev-block repo sibling-block
-                                                          (get block :block/format :markdown)
-                                                          "")]
-           (when edit-block-f (edit-block-f))))))))
-
-(defn set-block-timestamp!
-  [block-id key value]
-  (let [key (string/lower-case (str key))
-        block-id (if (string? block-id) (uuid block-id) block-id)
-        value (str value)]
-    (when-let [block (db/entity [:block/uuid block-id])]
-      (let [{:block/keys [title]} block
-            content (or title (state/get-edit-content))
-            new-content (-> (text-util/remove-timestamp content key)
-                            (text-util/add-timestamp key value))]
-        (when (not= content new-content)
-          (let [input-id (state/get-edit-input-id)]
-            (if (and input-id
-                     (string/ends-with? input-id (str block-id)))
-              (state/set-edit-content! input-id new-content)
-              (save-block-if-changed! block new-content))))))))
-
-(defn set-editing-block-timestamp!
-  "Almost the same as set-block-timestamp! except for:
-   - it doesn't save the block
-   - it extracts current content from current input"
-  [key value]
-  (let [key (string/lower-case (str key))
-        value (str value)
-        content (state/get-edit-content)
-        new-content (-> (text-util/remove-timestamp content key)
-                        (text-util/add-timestamp key value))]
-    (when (not= content new-content)
-      (let [input-id (state/get-edit-input-id)]
-        (state/set-edit-content! input-id new-content)))))
-
-(defn set-blocks-id!
-  "Persist block uuid to file if the uuid is valid, and it's not persisted in file.
-   Accepts a list of uuids."
-  [block-ids]
-  (let [repo (state/get-current-repo)]
-    (when-not (config/db-based-graph? repo)
-      (file-editor-handler/set-blocks-id! block-ids))))
-
-(defn copy-block-ref!
-  ([block-id]
-   (copy-block-ref! block-id #(str %)))
-  ([block-id tap-clipboard]
-   (p/do!
-    (save-current-block!)
-    (set-blocks-id! [block-id])
-    (util/copy-to-clipboard! (tap-clipboard block-id)))))
-
-(defn select-block!
-  [block-uuid]
-  (block-handler/select-block! block-uuid))
-
-(defn- compose-copied-blocks-contents
-  [repo block-ids & {:as opts}]
-  (let [blocks (map (fn [id] (db/entity [:block/uuid id])) block-ids)
-        top-level-block-uuids (->> (block-handler/get-top-level-blocks blocks)
-                                   (map :block/uuid))
-        content (export-text/export-blocks-as-markdown
-                 repo top-level-block-uuids
-                 (merge
-                  opts
-                  {:indent-style (state/get-export-block-text-indent-style)
-                   :remove-options (set (state/get-export-block-text-remove-options))}))]
-    [top-level-block-uuids content]))
-
-(defn- get-all-blocks-by-ids
-  [repo ids]
-  (loop [ids ids
-         result []]
-    (if (seq ids)
-      (let [db-id (:db/id (db/entity [:block/uuid (first ids)]))
-            blocks (tree/get-sorted-block-and-children repo db-id
-                                                       {:include-property-block? true})
-            result (vec (concat result blocks))]
-        (recur (remove (set (map :block/uuid result)) (rest ids)) result))
-      result)))
-
-(defn copy-selection-blocks
-  [html? & {:keys [selected-blocks] :as opts}]
-  (let [repo (state/get-current-repo)
-        selected-ids (state/get-selection-block-ids)
-        ids (or (seq selected-ids) (map :block/uuid selected-blocks))
-        [top-level-block-uuids content] (compose-copied-blocks-contents repo ids opts)
-        block (db/entity [:block/uuid (first ids)])
-        db-based? (config/db-based-graph? repo)]
-    (when block
-      (let [html (export-html/export-blocks-as-html repo top-level-block-uuids nil)
-            copied-blocks (cond->> (get-all-blocks-by-ids repo top-level-block-uuids)
-                            db-based?
-                            (map (fn [block]
-                                   (let [b (db/entity (:db/id block))]
-                                     (->
-                                      (->> (map (fn [[k v]]
-                                                  (let [v' (cond
-                                                             (and (map? v) (:db/id v))
-                                                             [:block/uuid (:block/uuid (db/entity (:db/id v)))]
-                                                             (and (coll? v) (every? #(and (map? %) (:db/id %)) v))
-                                                             (set (map (fn [i] [:block/uuid (:block/uuid (db/entity (:db/id i)))]) v))
-                                                             :else
-                                                             v)]
-                                                    [k v'])) b)
-                                           (into {}))
-                                      (assoc :db/id (:db/id b)))))))]
-        (common-handler/copy-to-clipboard-without-id-property! repo (get block :block/format :markdown) content (when html? html) copied-blocks))
-      (state/set-block-op-type! :copy)
-      (notification/show! "Copied!" :success))))
-
-(defn copy-block-refs
-  []
-  (when-let [selected-blocks (seq (get-selected-blocks))]
-    (let [blocks (->> (distinct (map #(when-let [id (dom/attr % "blockid")]
-                                        (let [level (dom/attr % "level")]
-                                          {:id (uuid id)
-                                           :level (int level)}))
-                                     selected-blocks))
-                      (remove nil?))
-          first-block (first blocks)
-          first-root-level-index (ffirst
-                                  (filter (fn [[_ block]] (= (:level block) 1))
-                                          (map-indexed vector blocks)))
-          root-level (atom (:level first-block))
-          adjusted-blocks (map-indexed
-                           (fn [index {:keys [id level]}]
-                             {:id id
-                              :level (if (< index first-root-level-index)
-                                       (if (< level @root-level)
-                                         (do
-                                           (reset! root-level level)
-                                           1)
-                                         (inc (- level @root-level)))
-                                       level)})
-                           blocks)
-          block (db/entity [:block/uuid (:id first-block)])
-          copy-str (some->> adjusted-blocks
-                            (map (fn [{:keys [id level]}]
-                                   (if (config/db-based-graph? (state/get-current-repo))
-                                     (str (string/join (repeat (dec level) "\t")) "- " (ref/->page-ref id))
-                                     (condp = (get block :block/format :markdown)
-                                       :org
-                                       (str (string/join (repeat level "*")) " " (ref/->block-ref id))
-                                       :markdown
-                                       (str (string/join (repeat (dec level) "\t")) "- " (ref/->block-ref id))))))
-                            (string/join "\n\n"))]
-      (set-blocks-id! (map :id blocks))
-      (util/copy-to-clipboard! copy-str))))
-
-(defn copy-block-embeds
-  []
-  (when-let [blocks (seq (get-selected-blocks))]
-    (let [ids (->> (distinct (map #(when-let [id (dom/attr % "blockid")]
-                                     (uuid id)) blocks))
-                   (remove nil?))
-          ids-str (if (config/db-based-graph? (state/get-current-repo))
-                    (some->> ids
-                             (map (fn [id] (ref/->block-ref id)))
-                             (string/join "\n\n"))
-                    (some->> ids
-                             (map (fn [id] (util/format "{{embed ((%s))}}" id)))
-                             (string/join "\n\n")))]
-      (set-blocks-id! ids)
-      (util/copy-to-clipboard! ids-str))))
-
-(defn get-selected-toplevel-block-uuids
-  []
-  (when-let [blocks (seq (get-selected-blocks))]
-    (let [repo (state/get-current-repo)
-          block-ids (->> (distinct (map #(when-let [id (dom/attr % "blockid")]
-                                           (uuid id)) blocks))
-                         (remove nil?))
-          blocks (db-utils/pull-many repo '[*] (mapv (fn [id] [:block/uuid id]) block-ids))
-          page-id (:db/id (:block/page (first blocks)))
-          ;; filter out blocks not belong to page with 'page-id'
-          blocks* (remove (fn [block] (some-> (:db/id (:block/page block)) (not= page-id))) blocks)]
-      (->> (block-handler/get-top-level-blocks blocks*)
-           (map :block/uuid)))))
-
-(defn cut-selection-blocks
-  [copy?]
-  (when copy? (copy-selection-blocks true))
-  (state/set-block-op-type! :cut)
-  (when-let [blocks (->> (get-selected-blocks)
-                         (remove #(dom/has-class? % "property-value-container"))
-                         seq)]
-    ;; remove queries
-    (let [dom-blocks (remove (fn [block] (= "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 (->> (map db/entity lookup-refs)
-                          (remove ldb/page?))
-              top-level-blocks (when (seq blocks) (block-handler/get-top-level-blocks blocks))
-              sorted-blocks (mapcat (fn [block]
-                                      (tree/get-sorted-block-and-children repo (:db/id block)))
-                                    top-level-blocks)]
-          (when (seq 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."
-  #"[^\s\(\[]+://[^\s\)\]]+")
-
-(defn extract-nearest-link-from-text
-  [text pos & additional-patterns]
-  (let [;; didn't use page-ref regexs b/c it handles page-ref and org link cases
-        page-pattern #"\[\[([^\]]+)]]"
-        tag-pattern #"#\S+"
-        page-matches (util/re-pos page-pattern text)
-        block-matches (util/re-pos block-ref/block-ref-re text)
-        tag-matches (util/re-pos tag-pattern text)
-        additional-matches (mapcat #(util/re-pos % text) additional-patterns)
-        matches (->> (concat page-matches block-matches tag-matches additional-matches)
-                     (remove nil?))
-        [_ match] (first (sort-by
-                          (fn [[start-pos content]]
-                            (let [end-pos (+ start-pos (count content))]
-                              (cond
-                                (< pos start-pos)
-                                (- pos start-pos)
-
-                                (> pos end-pos)
-                                (- end-pos pos)
-
-                                :else
-                                0)))
-                          >
-                          matches))]
-    (when match
-      (cond
-        (some #(re-find % match) additional-patterns)
-        match
-        (string/starts-with? match "#")
-        (subs match 1 (count match))
-        :else
-        (subs match 2 (- (count match) 2))))))
-
-(defn- get-nearest-page-or-url
-  "Return the nearest page-name (not dereferenced, may be an alias), block, tag or url"
-  []
-  (when-let [block (state/get-edit-block)]
-    (when (:block/uuid block)
-      (when-let [edit-id (state/get-edit-input-id)]
-        (when-let [input (gdom/getElement edit-id)]
-          (when-let [pos (cursor/pos input)]
-            (let [value (gobj/get input "value")]
-              (extract-nearest-link-from-text value pos url-regex))))))))
-
-(defn get-nearest-page
-  "Return the nearest page-name (not dereferenced, may be an alias), block or tag"
-  []
-  (when-let [block (state/get-edit-block)]
-    (when (:block/uuid block)
-      (when-let [edit-id (state/get-edit-input-id)]
-        (when-let [input (gdom/getElement edit-id)]
-          (when-let [pos (cursor/pos input)]
-            (let [value (gobj/get input "value")]
-              (extract-nearest-link-from-text value pos))))))))
-
-(defn follow-link-under-cursor!
-  []
-  (when-let [page (get-nearest-page-or-url)]
-    (when-not (string/blank? page)
-      (p/do!
-       (state/clear-editor-action!)
-       (save-current-block!)
-       (if (re-find url-regex page)
-         (js/window.open page)
-         (let [page-name (db-model/get-redirect-page-name page)]
-           (state/clear-edit!)
-           (insert-first-page-block-if-not-exists! page-name)))))))
-
-(defn open-link-in-sidebar!
-  []
-  (when-let [page (get-nearest-page)]
-    (let [page-name (string/lower-case page)
-          block? (util/uuid-string? page-name)]
-      (when-let [page (db/get-page page-name)]
-        (if block?
-          (state/sidebar-add-block!
-           (state/get-current-repo)
-           (:db/id page)
-           :block)
-          (state/sidebar-add-block!
-           (state/get-current-repo)
-           (:db/id page)
-           :page))))))
-
-(declare save-current-block!)
-
-;; FIXME: shortcut `mod+.` doesn't work on Web (Chrome)
-(defn zoom-in! []
-  (if (state/editing?)
-    (when-let [id (some-> (state/get-edit-block)
-                          :block/uuid
-                          ((fn [id] [:block/uuid id]))
-                          db/entity
-                          :block/uuid)]
-      (state/clear-editor-action!)
-      (state/set-editing-block-id! [:unknown-container id])
-      (p/do!
-       (save-current-block!)
-       (route-handler/redirect-to-page! id)))
-    (js/window.history.forward)))
-
-(defn zoom-out!
-  []
-  (if (state/editing?)
-    (let [page (state/get-current-page)
-          block-id (and (string? page) (parse-uuid page))]
-      (p/do!
-       (state/clear-editor-action!)
-       (save-current-block!)
-       (when block-id
-         (state/set-editing-block-id! [:unknown-container (:block/uuid (state/get-edit-block))])
-         (let [block-parent (db/get-block-parent block-id)]
-           (if-let [id (and
-                        (nil? (:block/name block-parent))
-                        (:block/uuid block-parent))]
-             (route-handler/redirect-to-page! id)
-             (let [page-id (some-> (db/entity [:block/uuid block-id])
-                                   :block/page
-                                   :db/id)]
-               (when-let [page (db/entity page-id)]
-                 (route-handler/redirect-to-page! (:block/uuid page)))))))))
-    (js/window.history.back)))
-
-(defn cut-block!
-  [block-id]
-  (when-let [block (db/entity [:block/uuid block-id])]
-    (let [repo (state/get-current-repo)
-          ;; TODO: support org mode
-          [_top-level-block-uuids md-content] (compose-copied-blocks-contents repo [block-id])
-          html (export-html/export-blocks-as-html repo [block-id] nil)
-          sorted-blocks (tree/get-sorted-block-and-children repo (:db/id block))]
-      (common-handler/copy-to-clipboard-without-id-property! repo (get block :block/format :markdown) md-content html sorted-blocks)
-      (state/set-block-op-type! :cut)
-      (delete-block-aux! block))))
-
-(defn highlight-selection-area!
-  [end-block-id & {:keys [append?]}]
-  (when-let [start-block (state/get-selection-start-block-or-first)]
-    (let [end-block-node (gdom/getElement end-block-id)
-          start-node (gdom/getElement start-block)
-          select-direction (state/get-selection-direction)
-          selected-blocks (state/get-unsorted-selection-blocks)
-          last-node (when-let [node (last selected-blocks)]
-                      (gdom/getElement (.-id ^js node)))
-          latest-visible-block (or last-node start-node)
-          latest-block-id (when latest-visible-block (.-id latest-visible-block))]
-      (if (and start-node end-block-node)
-        (let [blocks (util/get-nodes-between-two-nodes start-block end-block-id "ls-block")
-              direction (util/get-direction-between-two-nodes start-block end-block-id "ls-block")
-              blocks (if (= direction :up) (reverse blocks) blocks)]
-          (state/exit-editing-and-set-selected-blocks! blocks direction))
-        (when latest-visible-block
-          (let [blocks (util/get-nodes-between-two-nodes latest-block-id end-block-id "ls-block")
-                direction (if (= latest-block-id end-block-id)
-                            select-direction
-                            (util/get-direction-between-two-nodes latest-block-id end-block-id "ls-block"))
-                blocks (if (= direction :up) (reverse (util/sort-by-height blocks)) (util/sort-by-height blocks))]
-            (if append?
-              (do (state/clear-edit!)
-                  (if (and select-direction (not= direction select-direction))
-                    (state/drop-selection-blocks-starts-with! end-block-node)
-                    (state/conj-selection-block! blocks direction)))
-              (state/exit-editing-and-set-selected-blocks! blocks direction))))))))
-
-(defonce *action-bar-timeout (atom nil))
-
-(defn show-action-bar!
-  [& {:keys [delay]
-      :or {delay 200}}]
-  (when (config/db-based-graph?)
-    (when-let [timeout @*action-bar-timeout]
-      (js/clearTimeout timeout))
-    (state/pub-event! [:editor/hide-action-bar])
-    (let [timeout (js/setTimeout #(state/pub-event! [:editor/show-action-bar]) delay)]
-      (reset! *action-bar-timeout timeout))))
-
-(defn- select-block-up-down
-  [direction]
-  (cond
-    ;; when editing, quit editing and select current block
-    (state/editing?)
-    (let [element (gdom/getElement (state/get-editing-block-dom-id))]
-      (when element
-        (p/do!
-         (save-current-block!)
-         (util/scroll-to-block element)
-         (state/exit-editing-and-set-selected-blocks! [element]))))
-
-    ;; when selection and one block selected, select next block
-    (and (state/selection?) (== 1 (count (state/get-selection-blocks))))
-    (let [f (if (= :up direction) util/get-prev-block-non-collapsed util/get-next-block-non-collapsed-skip)
-          element (f (first (state/get-selection-blocks))
-                     {:up-down? true
-                      :exclude-property? true})]
-      (when element
-        (util/scroll-to-block element)
-        (state/conj-selection-block! element direction)))
-
-    ;; if same direction, keep conj on same direction
-    (and (state/selection?) (= direction (state/get-selection-direction)))
-    (let [f (if (= :up direction) util/get-prev-block-non-collapsed util/get-next-block-non-collapsed-skip)
-          first-last (if (= :up direction) first last)
-          element (f (first-last (state/get-selection-blocks))
-                     {:up-down? true
-                      :exclude-property? true})]
-      (when element
-        (util/scroll-to-block element)
-        (state/conj-selection-block! element direction)))
-
-    ;; if different direction, keep clear until one left
-    (state/selection?)
-    (let [f (if (= :up direction) util/get-prev-block-non-collapsed util/get-next-block-non-collapsed)
-          last-first (if (= :up direction) last first)
-          element (f (last-first (state/get-selection-blocks))
-                     {:up-down? true
-                      :exclude-property? true})]
-      (when element
-        (util/scroll-to-block element)
-        (state/drop-last-selection-block!))))
-  (show-action-bar! {:delay 500})
-  nil)
-
-(defn on-select-block
-  [direction]
-  (fn [_event]
-    (select-block-up-down direction)))
-
-(defn save-block-aux!
-  [block value opts]
-  (let [entity (db/entity [:block/uuid (:block/uuid block)])]
-    (when (and (:db/id entity) (not (ldb/built-in? entity)))
-      (let [value (string/trim value)]
-        ;; FIXME: somehow frontend.components.editor's will-unmount event will loop forever
-        ;; maybe we shouldn't save the block/file in "will-unmount" event?
-        (save-block-if-changed! block value opts)))))
-
-(defn save-block!
-  ([repo block-or-uuid content]
-   (save-block! repo block-or-uuid content {}))
-  ([repo block-or-uuid content {:keys [properties] :as opts}]
-   (let [block (if (or (uuid? block-or-uuid)
-                       (string? block-or-uuid))
-                 (db-model/query-block-by-uuid block-or-uuid) block-or-uuid)]
-     (save-block!
-      {:block block :repo repo :opts (dissoc opts :properties)}
-      (if (seq properties)
-        (property-file/insert-properties-when-file-based repo (get block :block/format :markdown) content properties)
-        content))))
-  ([{:keys [block repo opts] :as _state} value]
-   (let [repo (or repo (state/get-current-repo))]
-     (when (db/entity repo [:block/uuid (:block/uuid block)])
-       (save-block-aux! block value opts)))))
-
-(defn save-blocks!
-  [blocks]
-  (ui-outliner-tx/transact!
-   {:outliner-op :save-block}
-   (doseq [[block value] blocks]
-     (save-block-if-changed! block value))))
-
-(defn save-current-block!
-  "skip-properties? if set true, when editing block is likely be properties, skip saving"
-  ([]
-   (save-current-block! {}))
-  ([{:keys [force? skip-properties? current-block] :as opts}]
-   ;; non English input method
-   (when-not (or (state/editor-in-composition?)
-                 (state/get-editor-action))
-     (when (state/get-current-repo)
-       (try
-         (let [input-id (state/get-edit-input-id)
-               block (state/get-edit-block)
-               db-block (when-let [block-id (:block/uuid block)]
-                          (db/entity [:block/uuid block-id]))
-               elem (and input-id (gdom/getElement input-id))
-               db-content (:block/title db-block)
-               db-content-without-heading (and db-content
-                                               (common-util/safe-subs db-content (:block/level db-block)))
-               value (if (= (:block/uuid current-block) (:block/uuid block))
-                       (:block/title current-block)
-                       (and elem (gobj/get elem "value")))]
-           (when value
-             (cond
-               force?
-               (save-block-aux! db-block value opts)
-
-               (and skip-properties?
-                    (db-model/top-block? block)
-                    (when elem (thingatpt/properties-at-point elem)))
-               nil
-
-               (and block value db-content-without-heading
-                    (not= (string/trim db-content-without-heading)
-                          (string/trim value)))
-               (save-block-aux! db-block value opts))))
-         (catch :default error
-           (log/error :save-block-failed error)))))))
-
-(defn- clean-content!
-  [repo format content]
-  (if (config/db-based-graph? repo)
-    content
-    (some->> (text/remove-level-spaces content format (config/get-block-pattern format))
-             (drawer/remove-logbook)
-             (property-file/remove-properties-when-file-based repo format)
-             string/trim)))
-
-(def insert-command! editor-common-handler/insert-command!)
-
-(defn delete-asset-of-block!
-  [{:keys [repo asset-block href full-text block-id local? delete-local?] :as _opts}]
-  (let [block (db-model/query-block-by-uuid block-id)
-        _ (or block (throw (ex-info (str block-id " not exists")
-                                    {:block-id block-id})))
-        text (:block/title block)
-        content (if asset-block
-                  (string/replace text (ref/->page-ref (:block/uuid asset-block)) "")
-                  (string/replace text full-text ""))]
-    (save-block! repo block content)
-    (when (and local? delete-local?)
-      (if asset-block
-        (delete-block-aux! asset-block)
-        (when-let [href (if (util/electron?) href
-                            (second (re-find #"\((.+)\)$" full-text)))]
-          (let [block-file-rpath (file-model/get-block-file-path block)
-                asset-fpath (if (string/starts-with? href "assets://")
-                              (path/url-to-path href)
-                              (config/get-repo-fpath
-                               repo
-                               (path/resolve-relative-path block-file-rpath href)))]
-            (fs/unlink! repo asset-fpath nil)))))))
-
-(defn- write-file!
-  [repo dir file file-rpath file-name]
-  (if (util/electron?)
-    (if-let [from (not-empty (.-path file))]
-      (-> (js/window.apis.copyFileToAssets dir file-rpath from)
-          (p/catch #(js/console.error "Debug: Copy Asset Error#" %)))
-      (-> (p/let [buffer (.arrayBuffer file)]
-            (fs/write-file! repo dir file-rpath buffer {:skip-compare? false}))
-          (p/catch #(js/console.error "Debug: Writing Asset #" %))))
-    (->
-     (p/do! (js/console.debug "Debug: Writing Asset #" dir file-rpath)
-            (cond
-              (mobile-util/native-platform?)
-                          ;; capacitor fs accepts Blob, File implements Blob
-              (p/let [buffer (.arrayBuffer file)
-                      content (base64/encodeByteArray (js/Uint8Array. buffer))
-                      fpath (path/path-join dir file-rpath)]
-                (capacitor-fs/<write-file-with-base64 fpath content))
-
-              (config/db-based-graph? repo) ;; memory-fs
-              (p/let [buffer (.arrayBuffer file)
-                      content (js/Uint8Array. buffer)]
-                (fs/write-file! repo dir file-rpath content nil))
-
-              :else
-              (throw (ex-info "Paste failed"
-                              {:file-name file-name}))))
-     (p/catch (fn [error]
-                (prn :paste-file-error)
-                (js/console.error error))))))
-
-(defn db-based-save-assets!
-  "Save incoming(pasted) assets to assets directory.
-
-   Returns: asset entity"
-  [repo files & {:keys [pdf-area?]}]
-  (p/let [[repo-dir asset-dir-rpath] (assets-handler/ensure-assets-dir! repo)]
-    (p/all
-     (for [[_index ^js file] (map-indexed vector files)]
-      ;; WARN file name maybe fully qualified path when paste file
-       (p/let [file-name (util/node-path.basename (.-name file))
-               file-name-without-ext (.-name (util/node-path.parse file-name))
-               checksum (assets-handler/get-file-checksum file)
-               existing-asset (db-async/<get-asset-with-checksum repo checksum)]
-         (if existing-asset
-           existing-asset
-           (p/let [block-id (ldb/new-block-id)
-                   ext (when file-name
-                         (string/lower-case (.substr (util/node-path.extname file-name) 1)))
-                   _ (when (string/blank? ext)
-                       (throw (ex-info "File doesn't have a valid ext."
-                                       {:file-name file-name})))
-                   file-path   (str block-id "." ext)
-                   file-rpath  (str asset-dir-rpath "/" file-path)
-                   dir repo-dir
-                   asset (db/entity :logseq.class/Asset)
-                   properties {:logseq.property.asset/type ext
-                               :logseq.property.asset/size (.-size file)
-                               :logseq.property.asset/checksum checksum
-                               :block/tags (:db/id asset)}
-                   insert-opts {:custom-uuid block-id
-                                :edit-block? false
-                                :properties properties}
-                   _ (write-file! repo dir file file-rpath file-name)
-                   edit-block (state/get-edit-block)
-                   insert-to-current-block-page? (and (:block/uuid edit-block) (string/blank? (state/get-edit-content)) (not pdf-area?))
-                   insert-opts' (if insert-to-current-block-page?
-                                  (assoc insert-opts
-                                         :block-uuid (:block/uuid edit-block)
-                                         :replace-empty-target? true
-                                         :sibling? true)
-                                  (assoc insert-opts :page (:block/uuid asset)))
-                   result (api-insert-new-block! file-name-without-ext insert-opts')
-                   new-entity (db/entity [:block/uuid (:block/uuid result)])]
-             (when insert-to-current-block-page?
-               (state/clear-edit!))
-             (or new-entity
-                 (throw (ex-info "Can't save asset" {:files files}))))))))))
-
-(defn db-upload-assets!
-  "Paste asset for db graph and insert link to current editing block"
-  [repo id ^js files format uploading? drop-or-paste?]
-  (when (or (config/local-file-based-graph? repo)
-            (config/db-based-graph? repo))
-    (-> (db-based-save-assets! repo (js->clj files))
-          ;; FIXME: only the first asset is handled
-        (p/then
-         (fn [entities]
-           (let [entity (first entities)]
-             (insert-command!
-              id
-              (ref/->page-ref (:block/uuid entity))
-              format
-              {:last-pattern (if drop-or-paste? "" commands/command-trigger)
-               :restore?     true
-               :command      :insert-asset})
-             entities)))
-        (p/catch (fn [e]
-                   (js/console.error e)))
-        (p/finally
-          (fn []
-            (reset! uploading? false)
-            (reset! *asset-uploading? false)
-            (reset! *asset-uploading-process 0))))))
-
-(defn upload-asset!
-  "Paste asset and insert link to current editing block"
-  [id ^js files format uploading? drop-or-paste?]
-  (let [repo (state/get-current-repo)]
-    (if (config/db-based-graph? repo)
-      (db-upload-assets! repo id ^js files format uploading? drop-or-paste?)
-      (file-editor-handler/file-upload-assets! repo id ^js files format uploading? *asset-uploading? *asset-uploading-process drop-or-paste?))))
-
-;; Editor should track some useful information, like editor modes.
-;; For example:
-;; 1. Which file format is it, markdown or org mode?
-;; 2. Is it in the properties area? Then we can enable the ":" autopair
-(def autopair-map
-  {"[" "]"
-   "{" "}"
-   "(" ")"
-   "`" "`"
-   "~" "~"
-   "*" "*"
-   "_" "_"
-   "^" "^"
-   "=" "="
-   "/" "/"
-   "+" "+"})
-;; ":" ":"                              ; TODO: only properties editing and org mode tag
-
-(def reversed-autopair-map
-  (zipmap (vals autopair-map)
-          (keys autopair-map)))
-
-(def autopair-when-selected
-  #{"*" "^" "_" "=" "+" "/"})
-
-(def delete-map
-  (assoc autopair-map
-         "$" "$"
-         ":" ":"))
-
-(defn- autopair
-  [input-id prefix _format _option]
-  (let [value (get autopair-map prefix)
-        selected (util/get-selected-text)
-        postfix (str selected value)
-        value (str prefix postfix)
-        input (gdom/getElement input-id)]
-    (when value
-      (let [[prefix _pos] (commands/simple-replace! input-id value selected
-                                                    {:backward-pos (count postfix)
-                                                     :check-fn (fn [new-value prefix-pos]
-                                                                 (when (>= prefix-pos 0)
-                                                                   [(subs new-value prefix-pos (+ prefix-pos 2))
-                                                                    (+ prefix-pos 2)]))})]
-        (cond
-          (= prefix page-ref/left-brackets)
-          (do
-            (commands/handle-step [:editor/search-page])
-            (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)
-                                            :selected selected}))
-
-          (and (= prefix block-ref/left-parens)
-               (config/db-based-graph? (state/get-current-repo)))
-          (notification/show!
-           "To reference a node, please use `[[]]`."
-           :warning)
-
-          (= prefix block-ref/left-parens)
-          (do
-            (commands/handle-step [:editor/search-block :reference])
-            (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)
-                                            :selected selected})))))))
-
-(defn surround-by?
-  [input before end]
-  (when input
-    (let [value (gobj/get input "value")
-          pos (cursor/pos input)]
-      (text-util/surround-by? value pos before end))))
-
-(defn- autopair-left-paren?
-  [input key]
-  (and (= key "(")
-       (or (surround-by? input :start "")
-           (surround-by? input "\n" "")
-           (surround-by? input " " "")
-           (surround-by? input "]" "")
-           (surround-by? input "(" ""))))
-
-(defn wrapped-by?
-  [input before end]
-  (when input
-    (let [value (gobj/get input "value")
-          pos (cursor/pos input)]
-      (when (>= pos 0)
-        (text-util/wrapped-by? value pos before end)))))
-
-(defn get-matched-classes
-  "Return matched classes except the root tag"
-  [q]
-  (let [classes (->> (db-model/get-all-classes (state/get-current-repo) {:except-root-class? true})
-                     (mapcat (fn [class]
-                               (conj (:block/alias class) class)))
-                     (common-util/distinct-by :db/id)
-                     (map (fn [e] (select-keys e [:block/uuid :block/title]))))]
-    (search/fuzzy-search classes q {:extract-fn :block/title})))
-
-(defn <get-matched-blocks
-  "Return matched blocks that are not built-in"
-  [q & [{:keys [nlp-pages? page-only?]}]]
-  (p/let [block (state/get-edit-block)
-          result (search/block-search (state/get-current-repo) q {:built-in? false
-                                                                  :enable-snippet? false
-                                                                  :page-only? page-only?})
-          matched (remove (fn [b] (= (:block/uuid b) (:block/uuid block))) result)]
-    (-> (concat matched
-                (when nlp-pages?
-                  (map (fn [title] {:block/title title :nlp-date? true})
-                       date/nlp-pages)))
-        (search/fuzzy-search q {:extract-fn :block/title :limit 50}))))
-
-(defn <get-matched-templates
-  [q]
-  (search/template-search q))
-
-(defn <get-matched-properties
-  [q]
-  (search/property-search q))
-
-(defn get-matched-property-values
-  [property q]
-  (search/property-value-search property q))
-
-(defn get-last-command
-  [input]
-  (try
-    (let [edit-content (or (gobj/get input "value") "")
-          pos (cursor/pos input)
-          last-slash-caret-pos (:pos (:pos (state/get-editor-action-data)))
-          last-command (and last-slash-caret-pos (subs edit-content last-slash-caret-pos pos))]
-      (when (> pos 0) last-command))
-    (catch :default e
-      (js/console.error e)
-      nil)))
-
-(defn get-matched-commands
-  [command]
-  (condp = command
-    nil nil
-    "" @commands/*initial-commands
-    (commands/get-matched-commands command)))
-
-(defn auto-complete?
-  []
-  (or @*asset-uploading?
-      (state/get-editor-action)))
-
-(defn in-shui-popup?
-  []
-  (or (some-> js/document.activeElement
-              (.closest "[data-radix-menu-content]")
-              (nil?)
-              (not))
-      (.querySelector js/document.body
-                      "div[data-radix-popper-content-wrapper]")))
-
-(defn get-current-input-char
-  [input]
-  (when-let [pos (cursor/pos input)]
-    (let [value (gobj/get input "value")]
-      (when (and (>= (count value) (inc pos))
-                 (>= pos 1))
-        (util/nth-safe value pos)))))
-
-(defn move-up-down
-  [up?]
-  (fn [event]
-    (util/stop event)
-    (state/pub-event! [:editor/hide-action-bar])
-    (let [edit-block-id (:block/uuid (state/get-edit-block))
-          move-nodes (fn [blocks]
-                       (let [blocks' (block-handler/get-top-level-blocks blocks)
-                             result (ui-outliner-tx/transact!
-                                     {:outliner-op :move-blocks}
-                                     (outliner-op/move-blocks-up-down! blocks' up?))]
-                         (when-let [block-node (util/get-first-block-by-id (:block/uuid (first blocks)))]
-                           (.scrollIntoView block-node #js {:behavior "smooth" :block "nearest"}))
-                         result))]
-      (if edit-block-id
-        (when-let [block (db/entity [:block/uuid edit-block-id])]
-          (let [blocks [(assoc block :block/title (state/get-edit-content))]
-                container-id (get-new-container-id (if up? :move-up :move-down) {})]
-            (p/do!
-             (save-current-block!)
-             (move-nodes blocks)
-             (if container-id
-               (state/set-editing-block-id! [container-id edit-block-id])
-               (when-let [input (some-> (state/get-edit-input-id) gdom/getElement)]
-                 (.focus input)
-                 (util/scroll-editor-cursor input))))))
-        (let [ids (state/get-selection-block-ids)]
-          (when (seq ids)
-            (let [lookup-refs (map (fn [id] [:block/uuid id]) ids)
-                  blocks (map db/entity lookup-refs)]
-              (move-nodes blocks))))))))
-
-(defn get-selected-ordered-blocks
-  []
-  (let [repo (state/get-current-repo)
-        ids (state/get-selection-block-ids)
-        lookup-refs (->> (map (fn [id] [:block/uuid id]) ids)
-                         (remove nil?))]
-    (db/pull-many repo '[*] lookup-refs)))
-
-(defn on-tab
-  "`direction` = :left | :right."
-  [direction]
-  (let [blocks (get-selected-ordered-blocks)]
-    (block-handler/indent-outdent-blocks! blocks (= direction :right) nil)))
-
-(defn- get-link [format link label]
-  (let [link (or link "")
-        label (or label "")]
-    (case (keyword format)
-      :markdown (util/format "[%s](%s)" label link)
-      :org (util/format "[[%s][%s]]" link label)
-      nil)))
-
-(defn- get-image-link
-  [format link label]
-  (let [link (or link "")
-        label (or label "")]
-    (case (keyword format)
-      :markdown (util/format "![%s](%s)" label link)
-      :org (util/format "[[%s]]"))))
-
-(defn handle-command-input-close [id]
-  (state/set-editor-show-input! nil)
-  (when-let [saved-cursor (state/get-editor-last-pos)]
-    (when-let [input (gdom/getElement id)]
-      (cursor/move-cursor-to input saved-cursor true))))
-
-(defn handle-command-input [command id format m]
-  ;; TODO: Add error handling for when user doesn't provide a required field.
-  ;; (The current behavior is to just revert back to the editor.)
-  (case command
-
-    :link (let [{:keys [link label]} m]
-            (when-not (or (string/blank? link) (string/blank? label))
-              (insert-command!
-               id
-               (get-link format link label)
-               format
-               {:last-pattern (str commands/command-trigger "link")
-                :command :link})))
-
-    :image-link (let [{:keys [link label]} m]
-                  (when (not (string/blank? link))
-                    (insert-command!
-                     id
-                     (get-image-link format link label)
-                     format
-                     {:last-pattern (str commands/command-trigger "link")
-                      :command :image-link})))
-
-    nil)
-
-  (handle-command-input-close id))
-
-(defn restore-last-saved-cursor!
-  ([] (restore-last-saved-cursor! (state/get-input)))
-  ([input]
-   (when-let [saved-cursor (and input (state/get-editor-last-pos))]
-     (cursor/move-cursor-to input saved-cursor true))))
-
-(defn- close-autocomplete-if-outside
-  [input]
-  (when (and input
-             (contains? #{:page-search :page-search-hashtag :block-search} (state/get-editor-action))
-             (not (wrapped-by? input page-ref/left-brackets page-ref/right-brackets))
-             (not (wrapped-by? input block-ref/left-parens block-ref/right-parens))
-             ;; wrapped-by? doesn't detect multiple beginnings when ending with "" so
-             ;; use subs to correctly detect current hashtag
-             (not (text-util/wrapped-by? (subs (.-value input) 0 (cursor/pos input)) (cursor/pos input) commands/hashtag ""))
-             (not (and (config/db-based-graph? (state/get-current-repo))
-                       (= :block-search (state/get-editor-action)))))
-    (state/clear-editor-action!)))
-
-(defn resize-image!
-  [config block-id metadata full_text size]
-  (let [asset (:asset-block config)]
-    (if (config/db-based-graph?)
-      (property-handler/set-block-property! (state/get-current-repo)
-                                            (if asset (:db/id asset) block-id)
-                                            :logseq.property.asset/resize-metadata
-                                            size)
-      (let [new-meta (merge metadata size)
-            image-part (first (string/split full_text #"\{"))
-            md-link? (string/starts-with? image-part "![")
-            new-full-text (str (if md-link? image-part (str "![image](" image-part ")")) (pr-str new-meta))
-            block (db/entity [:block/uuid block-id])
-            value (:block/title block)
-            new-value (string/replace value full_text new-full-text)]
-        (save-block-aux! block new-value {})))))
-
-(defonce *auto-save-timeout (atom nil))
-(defn edit-box-on-change!
-  [e block id]
-  (when (= (:db/id block) (:db/id (state/get-edit-block)))
-    (let [value (util/evalue e)
-          repo (state/get-current-repo)]
-      (state/set-edit-content! id value false)
-      (when @*auto-save-timeout
-        (js/clearTimeout @*auto-save-timeout))
-      (block-handler/mark-last-input-time! repo)
-      (reset! *auto-save-timeout
-              (js/setTimeout
-               (fn []
-                 (when (and (state/input-idle? repo :diff 450)
-                          ;; don't auto-save block if it has tags
-                            (not (and
-                                  (config/db-based-graph? repo)
-                                  (re-find #"#\S+" value))))
-                 ; don't auto-save for page's properties block
-                   (save-current-block! {:skip-properties? true})))
-               450)))))
-
-(defn- start-of-new-word?
-  [input pos]
-  (contains? #{" " "\t"} (get (.-value input) (- pos 2))))
-
-(defn handle-last-input []
-  (let [input           (state/get-input)
-        input-id        (state/get-edit-input-id)
-        edit-block      (state/get-edit-block)
-        pos             (cursor/pos input)
-        content         (.-value input)
-        last-input-char (util/nth-safe content (dec pos))
-        last-prev-input-char (util/nth-safe content (dec (dec pos)))
-        prev-prev-input-char (util/nth-safe content (- pos 3))
-        repo (state/get-current-repo)
-        db-based? (config/db-based-graph? repo)]
-
-    ;; TODO: is it cross-browser compatible?
-    ;; (not= (gobj/get native-e "inputType") "insertFromPaste")
-    (cond
-      (and (= content "1. ") (= last-input-char " ") input-id edit-block
-           (not (own-order-number-list? edit-block)))
-      (p/let [_ (state/pub-event! [:editor/toggle-own-number-list edit-block])]
-        (state/set-edit-content! input-id ""))
-
-      (and (= last-input-char commands/command-trigger)
-           (or (re-find #"(?m)^/" (str (.-value input))) (start-of-new-word? input pos)))
-      (do
-        (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
-        (commands/reinit-matched-commands!)
-        (state/set-editor-show-commands!))
-
-      (and (= last-input-char last-prev-input-char commands/colon)
-           (or (nil? prev-prev-input-char)
-               (= prev-prev-input-char "\n"))
-           (not db-based?))
-      (do
-        (cursor/move-cursor-backward input 2)
-        (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
-        (state/set-editor-action! :property-search))
-
-      (and
-       (not= :property-search (state/get-editor-action))
-       (let [{:keys [line start-pos]} (text-util/get-current-line-by-pos (.-value input) (dec pos))]
-         (text-util/wrapped-by? line (- pos start-pos) "" gp-property/colons))
-       (not db-based?))
-      (do
-        (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
-        (state/set-editor-action! :property-search))
-
-      (and (= last-input-char commands/colon)
-           (= :property-search (state/get-editor-action))
-           (not db-based?))
-      (state/clear-editor-action!)
-
-      (or (= last-input-char last-prev-input-char commands/hashtag)
-          (and (= last-prev-input-char commands/hashtag)
-               (= last-input-char " ")))
-      (state/clear-editor-action!)
-
-      ;; Open "Search page or New page" auto-complete
-      (and (= last-input-char commands/hashtag)
-             ;; Only trigger at beginning of a line, before whitespace or after a reference
-           (or (re-find #"(?m)^#" (str (.-value input)))
-               (start-of-new-word? input pos)
-               (and db-based? (= page-ref/right-brackets (common-util/safe-subs (str (.-value input)) (- pos 3) (dec pos))))))
-      (do
-        (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)})
-        (state/set-editor-last-pos! pos)
-        (state/set-editor-action! :page-search-hashtag))
-
-      :else
-      nil)))
-
-(defn get-selected-text
-  []
-  (let [text (:selected (state/get-editor-action-data))]
-    (when-not (string/blank? text)
-      text)))
-
-(defn block-on-chosen-handler
-  [id q format selected-text]
-  (fn [chosen _click?]
-    (state/clear-editor-action!)
-    (let [uuid-string (str (:block/uuid chosen))]
-
-      ;; block reference
-      (insert-command! id
-                       (ref/->block-ref uuid-string)
-                       format
-                       {:last-pattern (str block-ref/left-parens (if selected-text "" q))
-                        :end-pattern block-ref/right-parens
-                        :postfix-fn   (fn [s] (util/replace-first block-ref/right-parens s ""))
-                        :forward-pos 3
-                        :command :block-ref})
-
-      ;; Save it so it'll be parsed correctly in the future
-      (property-handler/file-persist-block-id! (state/get-current-repo) (:block/uuid chosen))
-
-      (when-let [input (gdom/getElement id)]
-        (.focus input)))))
-
-(defn block-non-exist-handler
-  [input]
-  (fn []
-    (state/clear-editor-action!)
-    (cursor/move-cursor-forward input 2)))
-
-(defn- paste-block-cleanup
-  [repo block page exclude-properties format content-update-fn keep-uuid?]
-  (let [db-based? (config/db-based-graph? (state/get-current-repo))
-        new-content
-        (if content-update-fn
-          (content-update-fn (:block/title block))
-          (:block/title block))
-        new-content
-        (cond->> new-content
-          (not keep-uuid?) (property-file/remove-property-when-file-based repo format "id")
-          true             (property-file/remove-property-when-file-based repo format "custom_id"))]
-    (merge (apply dissoc block (conj (if-not keep-uuid? [:block/_refs] []) :block/pre-block? :block/meta))
-           (cond->
-            {:block/page {:db/id (:db/id page)}
-             :block/title new-content}
-             (not db-based?)
-             (assoc :block/properties (apply dissoc (not-empty (:block/properties block))
-                                             (concat
-                                              (when-not keep-uuid? [:id])
-                                              [:custom_id :custom-id]
-                                              exclude-properties))
-                    :block/format format)
-             (not db-based?)
-             (assoc :block/properties-text-values (apply dissoc (:block/properties-text-values block)
-                                                         (concat
-                                                          (when-not keep-uuid? [:id])
-                                                          exclude-properties)))))))
-
-(defn- edit-last-block-after-inserted!
-  [result]
-  (util/schedule
-   (fn []
-     (when-let [last-block (last (:blocks result))]
-       (clear-when-saved!)
-       (let [last-block' (db/entity [:block/uuid (:block/uuid last-block)])]
-         (edit-block! last-block' :max))))))
-
-(defn- nested-blocks
-  [blocks]
-  (let [ids (set (map :db/id blocks))]
-    (some? (some #(ids (:db/id (:block/parent %))) blocks))))
-
-(defn paste-blocks
-  "Given a vec of blocks, insert them into the target page.
-   keep-uuid?: if true, keep the uuid provided in the block structure."
-  [blocks {:keys [content-update-fn
-                  exclude-properties
-                  target-block
-                  sibling?
-                  keep-uuid?
-                  revert-cut-txs
-                  skip-empty-target?
-                  ops-only?]
-           :or {exclude-properties []}}]
-  (let [editing-block (when-let [editing-block (state/get-edit-block)]
-                        (some-> (db/entity [:block/uuid (:block/uuid editing-block)])
-                                (assoc :block/title (state/get-edit-content))))
-        has-unsaved-edits (and editing-block
-                               (not= (:block/title (db/entity (:db/id editing-block)))
-                                     (state/get-edit-content)))
-        target-block (or target-block editing-block)
-        block (db/entity (:db/id target-block))
-        page (if (:block/name block) block
-                 (when target-block (:block/page (db/entity (:db/id target-block)))))
-        empty-target? (if (true? skip-empty-target?) false
-                          (string/blank? (:block/title target-block)))
-        paste-nested-blocks? (nested-blocks blocks)
-        target-block-has-children? (db/has-children? (:block/uuid target-block))
-        replace-empty-target? (and empty-target?
-                                   (or (not target-block-has-children?)
-                                       (and target-block-has-children? (= (count blocks) 1))))
-        target-block' (if (and empty-target? target-block-has-children? paste-nested-blocks?)
-                        (or (ldb/get-left-sibling target-block)
-                            (:block/parent (db/entity (:db/id target-block))))
-                        target-block)
-        sibling? (cond
-                   (and paste-nested-blocks? empty-target?)
-                   (= (:block/parent target-block') (:block/parent target-block))
-
-                   (some? sibling?)
-                   sibling?
-
-                   target-block-has-children?
-                   false
-
-                   :else
-                   true)
-        transact-blocks! #(ui-outliner-tx/transact!
-                           {:outliner-op :insert-blocks
-                            :additional-tx revert-cut-txs}
-                           (when target-block'
-                             (let [format (get target-block' :block/format :markdown)
-                                   repo (state/get-current-repo)
-                                   blocks' (map (fn [block]
-                                                  (paste-block-cleanup repo block page exclude-properties format content-update-fn keep-uuid?))
-                                                blocks)]
-                               (outliner-op/insert-blocks! blocks' target-block' {:sibling? sibling?
-                                                                                  :outliner-op :paste
-                                                                                  :replace-empty-target? replace-empty-target?
-                                                                                  :keep-uuid? keep-uuid?}))))]
-    (if ops-only?
-      (transact-blocks!)
-      (p/let [_ (when has-unsaved-edits
-                  (ui-outliner-tx/transact!
-                   {:outliner-op :save-block}
-                   (outliner-save-block! editing-block)))
-              result (transact-blocks!)]
-        (state/set-block-op-type! nil)
-        (when result
-          (edit-last-block-after-inserted! result)
-          result)))))
-
-(defn- block-tree->blocks
-  "keep-uuid? - maintain the existing :uuid in tree vec"
-  [repo tree-vec format keep-uuid? page-name]
-  (->> (outliner-core/tree-vec-flatten tree-vec)
-       (map (fn [block]
-              (let [content (:content block)
-                    props (into [] (:properties block))
-                    content* (str (if (= :markdown format) "- " "* ")
-                                  (property-file/insert-properties-when-file-based repo format content props))
-                    ast (mldoc/->edn content* format)
-                    blocks (->> (block/extract-blocks ast content* format {:page-name page-name})
-                                (map wrap-parse-block))
-                    fst-block (first blocks)
-                    fst-block (if (and keep-uuid? (uuid? (:uuid block)))
-                                (assoc fst-block :block/uuid (:uuid block))
-                                fst-block)]
-                (assert fst-block "fst-block shouldn't be nil")
-                (assoc fst-block :block/level (:block/level block)))))))
-
-(defn insert-block-tree
-  "`tree-vec`: a vector of blocks.
-   A block element: {:content :properties :children [block-1, block-2, ...]}"
-  [tree-vec format {:keys [target-block keep-uuid?] :as opts}]
-  (let [repo (state/get-current-repo)
-        page-id (:db/id (:block/page target-block))
-        page-name (some-> page-id (db/entity) :block/name)
-        blocks (block-tree->blocks repo tree-vec format keep-uuid? page-name)
-        blocks (gp-block/with-parent-and-order page-id blocks)
-        block-refs (->> (mapcat :block/refs blocks)
-                        (set)
-                        (filter (fn [ref] (and (vector? ref) (= :block/uuid (first ref))))))]
-    (ui-outliner-tx/transact!
-     {:outliner-op :paste-blocks}
-     (when (seq block-refs)
-       (db/transact! (map (fn [[_ id]] {:block/uuid id}) block-refs)))
-     (paste-blocks blocks (merge opts {:ops-only? true})))))
-
-(defn insert-block-tree-after-target
-  "`tree-vec`: a vector of blocks.
-   A block element: {:content :properties :children [block-1, block-2, ...]}"
-  [target-block-id sibling? tree-vec format keep-uuid?]
-  (insert-block-tree tree-vec format
-                     {:target-block       (db/entity target-block-id)
-                      :keep-uuid?         keep-uuid?
-                      :skip-empty-target? true
-                      :sibling?           sibling?}))
-
-(defn insert-template!
-  ([element-id db-id]
-   (insert-template! element-id db-id {}))
-  ([element-id db-id {:keys [target] :as opts}]
-   (let [repo (state/get-current-repo)
-         db? (config/db-based-graph? repo)]
-     (p/let [block (if (integer? db-id)
-                     (db-async/<pull repo db-id)
-                     (db-async/<get-template-by-name (name db-id)))
-             block (when (:block/uuid block)
-                     (db-async/<get-block repo (:block/uuid block)
-                                          {:children? true
-                                           :nested-children? true}))]
-       (when (:db/id block)
-         (let [journal? (ldb/journal? target)
-               target (or target (state/get-edit-block))
-               format (get block :block/format :markdown)
-               block-uuid (:block/uuid block)
-               template-including-parent? (not (false? (:template-including-parent (:block/properties block))))
-               blocks (db/get-block-and-children repo block-uuid {:include-property-block? true})
-               sorted-blocks (if db?
-                               (let [blocks' (rest blocks)]
-                                 (cons
-                                  (-> (first blocks')
-                                      (assoc :logseq.property/used-template (:db/id block)))
-                                  (rest blocks')))
-                               (cons
-                                (-> (first blocks)
-                                    (update :block/properties-text-values dissoc :template)
-                                    (update :block/properties-order (fn [keys]
-                                                                      (vec (remove #{:template} keys)))))
-                                (rest blocks)))
-               blocks (cond
-                        db?
-                        sorted-blocks
-                        template-including-parent?
-                        sorted-blocks
-                        :else
-                        (drop 1 sorted-blocks))]
-           (when element-id
-             (insert-command! element-id "" format {:end-pattern commands/command-trigger}))
-           (let [exclude-properties [:id :template :template-including-parent]
-                 content-update-fn (fn [content]
-                                     (->> content
-                                          (property-file/remove-property-when-file-based repo format "template")
-                                          (property-file/remove-property-when-file-based repo format "template-including-parent")
-                                          template/resolve-dynamic-template!))
-                 page (if (:block/name block) block
-                          (when target (:block/page (db/entity (:db/id target)))))
-                 blocks' (if (config/db-based-graph?)
-                           blocks
-                           (map (fn [block]
-                                  (paste-block-cleanup repo block page exclude-properties format content-update-fn false))
-                                blocks))
-                 sibling? (:sibling? opts)
-                 sibling?' (cond
-                             (some? sibling?)
-                             sibling?
-
-                             (db/has-children? (:block/uuid target))
-                             false
-
-                             :else
-                             true)]
-             (when (seq blocks')
-               (try
-                 (p/let [result (ui-outliner-tx/transact!
-                                 {:outliner-op :insert-blocks
-                                  :created-from-journal-template? journal?}
-                                 (when-not (string/blank? (state/get-edit-content))
-                                   (save-current-block!))
-                                 (outliner-op/insert-blocks! blocks' target
-                                                             (assoc opts
-                                                                    :sibling? sibling?'
-                                                                    :insert-template? true)))]
-                   (when result (edit-last-block-after-inserted! result)))
-
-                 (catch :default ^js/Error e
-                   (notification/show!
-                    [:p.content
-                     (util/format "Template insert error: %s" (.-message e))]
-                    :error)))))))))))
-
-(defn template-on-chosen-handler
-  [element-id]
-  (fn [template-block]
-    (when-let [db-id (:db/id template-block)]
-      (insert-template! element-id db-id
-                        {:replace-empty-target? true}))))
-
-(defn get-searching-property
-  [input]
-  (let [value (.-value input)
-        pos (util/get-selection-start input)
-        postfix (subs value pos)
-        end-index (when-let [idx (string/index-of postfix gp-property/colons)]
-                    (+ (max 0 (count (subs value 0 pos))) idx))
-        start-index (or (when-let [p (string/last-index-of (subs value 0 pos) "\n")]
-                          (inc p))
-                        0)]
-    {:end-index end-index
-     :searching-property (when (and start-index end-index (>= end-index start-index))
-                           (subs value start-index end-index))}))
-
-(defn property-on-chosen-handler
-  [element-id q]
-  (fn [property]
-    (when-let [input (gdom/getElement element-id)]
-      (let [{:keys [end-index searching-property]} (get-searching-property input)]
-        (cursor/move-cursor-to input (+ end-index 2))
-        (commands/insert! element-id (str (or property q) gp-property/colons " ")
-                          {:last-pattern (str searching-property gp-property/colons)})
-        (state/clear-editor-action!)
-        (js/setTimeout (fn []
-                         (let [pos (let [input (gdom/getElement element-id)]
-                                     (cursor/get-caret-pos input))]
-                           (state/set-editor-action-data! {:property (or property q)
-                                                           :pos pos})
-                           (state/set-editor-action! :property-value-search)))
-                       50)))))
-
-(defn property-value-on-chosen-handler
-  [element-id q]
-  (fn [property-value]
-    (commands/insert! element-id (str gp-property/colons " " (or property-value q))
-                      {:last-pattern (str gp-property/colons " " q)})
-    (state/clear-editor-action!)))
-
-(declare indent-outdent)
-
-(defn- last-top-level-child?
-  [{:keys [id]} block]
-  (when id
-    (when-let [entity (if-let [id' (parse-uuid (str id))]
-                        (db/entity [:block/uuid id'])
-                        (db/get-page id))]
-      (= (:block/uuid entity) (:block/uuid (:block/parent block))))))
-
-(defn insert
-  ([insertion]
-   (insert insertion false))
-  ([insertion auto-complete-enabled?]
-   (when (or auto-complete-enabled?
-             (not (auto-complete?)))
-     (let [^js input (state/get-input)
-           selected-start (util/get-selection-start input)
-           selected-end (util/get-selection-end input)
-           value (.-value input)
-           s1 (subs value 0 selected-start)
-           s2 (subs value selected-end)]
-       (state/set-edit-content! (state/get-edit-input-id)
-                                (str s1 insertion))
-       ;; HACK: save scroll-pos of current pos, then add trailing content
-       ;; This logic is also in commands/simple-insert!
-       (let [scroll-container (util/nearest-scrollable-container input)
-             scroll-pos (.-scrollTop scroll-container)]
-         (state/set-edit-content! (state/get-edit-input-id)
-                                  (str s1 insertion s2))
-         (cursor/move-cursor-to input (+ selected-start (count insertion)))
-         (set! (.-scrollTop scroll-container) scroll-pos))))))
-
-(defn- keydown-new-line
-  "Insert newline to current cursor position"
-  []
-  (insert "\n"))
-
-(declare delete-and-update)
-
-(defn- dwim-in-properties
-  [state]
-  (when-not (auto-complete?)
-    (let [{:keys [block]} (get-state)]
-      (when block
-        (let [input (state/get-input)
-              content (gobj/get input "value")
-              format (get (:block (get-state)) :block/format :markdown)
-              property-key (:raw-content (thingatpt/property-key-at-point input))
-              org? (= format :org)
-              move-to-pos (if org? 2 3)]
-          (if org?
-            (cond
-              (and property-key (not= property-key ""))
-              (case property-key
-                ;; When cursor in "PROPERTIES", add :|: in a new line and move cursor to |
-                "PROPERTIES"
-                (do (cursor/move-cursor-to-line-end input)
-                    (insert "\n:: ")
-                    (cursor/move-cursor-backward input move-to-pos))
-                ;; When cursor in "END", new block (respect the previous enter behavior)
-                "END"
-                (do
-                  (cursor/move-cursor-to-end input)
-                  (save-current-block!)
-                  (insert-new-block! state))
-                ;; cursor in other positions of :ke|y: or ke|y::, move to line end for inserting value.
-                (if (property-file/property-key-exist?-when-file-based format content property-key)
-                  (notification/show!
-                   [:p.content
-                    (util/format "Property key \"%s\" already exists!" property-key)]
-                   :error)
-                  (cursor/move-cursor-to-line-end input)))
-
-              ;; when cursor in empty property key
-              (and property-key (= property-key ""))
-              (do (delete-and-update
-                   input
-                   (cursor/line-beginning-pos input)
-                   (inc (cursor/line-end-pos input)))
-                  (property-file/goto-properties-end-when-file-based format input)
-                  (cursor/move-cursor-to-line-end input))
-              :else
-              ;;When cursor in other place of PROPERTIES drawer, add :|: in a new line and move cursor to |
-              (do
-                (insert "\n:: ")
-                (cursor/move-cursor-backward input move-to-pos)))
-            (insert "\n")))))))
-
-(defn toggle-list-checkbox
-  [{:block/keys [title] :as block} item-content]
-  (let [toggle-fn (fn [m x-mark]
-                    (case (string/lower-case x-mark)
-                      "[ ]" (str "[x] " item-content)
-                      "[x]" (str "[ ] " item-content)
-                      m))
-        pattern (re-pattern (str "(\\[[xX ]\\])\\s+?" (gstring/regExpEscape item-content)))
-        new-content (string/replace-first title pattern toggle-fn)]
-    (save-block-if-changed! block new-content)))
-
-(defn- dwim-in-list
-  []
-  (when-not (auto-complete?)
-    (let [{:keys [block]} (get-state)]
-      (when block
-        (let [input (state/get-input)]
-          (when-let [item (thingatpt/list-item-at-point input)]
-            (let [{:keys [full-content indent bullet checkbox ordered _]} item
-                  next-bullet (if ordered (str (inc bullet) ".") bullet)
-                  checkbox (when checkbox "[ ] ")]
-              (if (and
-                   (= (count full-content)
-                      (+ (if ordered (+ (count (str bullet)) 2) 2) (when checkbox (count checkbox))))
-                   (string/includes? (.-value input) "\n"))
-                (delete-and-update input (cursor/line-beginning-pos input) (cursor/line-end-pos input))
-                (let [start-pos (util/get-selection-start input)
-                      value (.-value input)
-                      before (subs value 0 start-pos)
-                      after (subs value start-pos)
-                      cursor-in-item-content? (and (re-find #"^(\d+){1}\." (last (string/split-lines before)))
-                                                   (not (string/blank? (first (string/split-lines after)))))]
-                  (when-not cursor-in-item-content?
-                    (cursor/move-cursor-to-line-end input)
-                    (insert (str "\n" indent next-bullet " " checkbox)))
-                  (when ordered
-                    (let [value (.-value input)
-                          start-pos (util/get-selection-start input)
-                          after-lists-str (string/trim (subs value start-pos))
-                          after-lists-str (if cursor-in-item-content?
-                                            (str indent next-bullet " " after-lists-str)
-                                            after-lists-str)
-                          lines (string/split-lines after-lists-str)
-                          after-lists-str' (list/re-order-items lines (if cursor-in-item-content? bullet (inc bullet)))
-                          value' (str (subs value 0 start-pos) "\n" after-lists-str')
-                          cursor' (if cursor-in-item-content?
-                                    (inc (count (str (subs value 0 start-pos) indent next-bullet " ")))
-                                    (+ (:end item) (count next-bullet) 2))]
-                      (state/set-edit-content! (state/get-edit-input-id) value')
-                      (cursor/move-cursor-to input cursor'))))))))))))
-
-(defn toggle-page-reference-embed
-  [parent-id]
-  (let [{:keys [block]} (get-state)]
-    (when block
-      (let [input (state/get-input)
-            new-pos (cursor/get-caret-pos input)
-            page-ref-fn (fn [bounds backward-pos]
-                          (commands/simple-insert!
-                           parent-id bounds
-                           {:backward-pos backward-pos
-                            :check-fn (fn [_ _ _]
-                                        (state/set-editor-action-data! {:pos new-pos})
-                                        (commands/handle-step [:editor/search-page]))}))]
-        (state/clear-editor-action!)
-        (let [selection (get-selection-and-format)
-              {:keys [selection-start selection-end selection]} selection]
-          (if selection
-            (do (delete-and-update input selection-start selection-end)
-                (insert (ref/->page-ref selection)))
-            (if-let [embed-ref (thingatpt/embed-macro-at-point input)]
-              (let [{:keys [raw-content start end]} embed-ref]
-                (delete-and-update input start end)
-                (if (= 5 (count raw-content))
-                  (page-ref-fn page-ref/left-and-right-brackets 2)
-                  (insert raw-content)))
-              (if-let [page-ref (thingatpt/page-ref-at-point input)]
-                (let [{:keys [start end full-content raw-content]} page-ref]
-                  (delete-and-update input start end)
-                  (if (= raw-content "")
-                    (page-ref-fn "{{embed [[]]}}" 4)
-                    (insert (util/format "{{embed %s}}" full-content))))
-                (page-ref-fn page-ref/left-and-right-brackets 2)))))))))
-
-(defn toggle-block-reference-embed
-  [parent-id]
-  (let [{:keys [block]} (get-state)]
-    (when block
-      (let [input (state/get-input)
-            new-pos (cursor/get-caret-pos input)
-            block-ref-fn (fn [bounds backward-pos]
-                           (commands/simple-insert!
-                            parent-id bounds
-                            {:backward-pos backward-pos
-                             :check-fn     (fn [_ _ _]
-                                             (state/set-editor-action-data! {:pos new-pos})
-                                             (commands/handle-step [:editor/search-block]))}))]
-        (state/clear-editor-action!)
-        (if-let [embed-ref (thingatpt/embed-macro-at-point input)]
-          (let [{:keys [raw-content start end]} embed-ref]
-            (delete-and-update input start end)
-            (if (= 5 (count raw-content))
-              (block-ref-fn block-ref/left-and-right-parens 2)
-              (insert raw-content)))
-          (if-let [page-ref (thingatpt/block-ref-at-point input)]
-            (let [{:keys [start end full-content raw-content]} page-ref]
-              (delete-and-update input start end)
-              (if (= raw-content "")
-                (block-ref-fn "{{embed (())}}" 4)
-                (insert (util/format "{{embed %s}}" full-content))))
-            (block-ref-fn block-ref/left-and-right-parens 2)))))))
-
-(defn- keydown-new-block
-  [state]
-  (when-not (auto-complete?)
-    (let [{:keys [block config]} (get-state)]
-      (when block
-        (let [block (db/entity (:db/id block))
-              input (state/get-input)
-              config (assoc config :keydown-new-block true)
-              content (gobj/get input "value")
-              pos (cursor/pos input)
-              has-right? (ldb/get-right-sibling block)
-              db-based? (config/db-based-graph? (state/get-current-repo))
-              thing-at-point ;intern is not supported in cljs, need a more elegant solution
-              (or (when (thingatpt/get-setting :admonition&src?)
-                    (thingatpt/admonition&src-at-point input))
-                  (when (thingatpt/get-setting :markup?)
-                    (thingatpt/markup-at-point input))
-                  (when (thingatpt/get-setting :block-ref?)
-                    (thingatpt/block-ref-at-point input))
-                  (when (thingatpt/get-setting :page-ref?)
-                    (thingatpt/page-ref-at-point input))
-                  (when (and (not db-based?) (thingatpt/get-setting :properties?))
-                    (thingatpt/properties-at-point input))
-                  (when (thingatpt/get-setting :list?)
-                    (and (not (cursor/beginning-of-line? input))
-                         (thingatpt/list-item-at-point input))))]
-          (cond
-            thing-at-point
-            (case (:type thing-at-point)
-              "markup" (let [right-bound (:bounds thing-at-point)]
-                         (cursor/move-cursor-to
-                          input
-                          (+ (string/index-of content right-bound pos)
-                             (count right-bound))))
-              "admonition-block" (keydown-new-line)
-              "source-block" (do
-                               (keydown-new-line)
-                               (case (:action thing-at-point)
-                                 :into-code-editor
-                                 (state/into-code-editor-mode!)
-                                 nil))
-              "block-ref" (open-block-in-sidebar! (:link thing-at-point))
-              "page-ref" (when-not (string/blank? (:link thing-at-point))
-                           (let [page (:link thing-at-point)
-                                 page-name (db-model/get-redirect-page-name page)]
-                             (insert-first-page-block-if-not-exists! page-name)))
-              "list-item" (dwim-in-list)
-              "properties-drawer" (dwim-in-properties state))
-
-            (and (string/blank? content)
-                 (own-order-number-list? block)
-                 (not (some-> (db-model/get-block-parent (:block/uuid block))
-                              (own-order-number-list?))))
-            (remove-block-own-order-list-type! block)
-
-            (and
-             (string/blank? content)
-             (not has-right?)
-             (not (last-top-level-child? config block)))
-            (indent-outdent false)
-
-            :else
-            (insert-new-block! state)))))))
-
-(defn- inside-of-single-block
-  "When we are in a single block wrapper, we should always insert a new line instead of new block"
-  [el]
-  (some? (dom/closest el ".single-block")))
-
-(defn- inside-of-editor-block
-  [el]
-  (some? (dom/closest el ".block-editor")))
-
-(defn keydown-new-block-handler [^js e]
-  (let [state (get-state)]
-    (when (or (nil? (.-target e)) (inside-of-editor-block (.-target e)))
-      (if (or (state/doc-mode-enter-for-new-line?) (inside-of-single-block (rum/dom-node state)))
-        (keydown-new-line)
-        (do
-          (.preventDefault e)
-          (keydown-new-block state))))))
-
-(defn keydown-new-line-handler [e]
-  (let [state (get-state)]
-    (when (or (nil? (.-target e)) (inside-of-editor-block (.-target e)))
-      (if (and (state/doc-mode-enter-for-new-line?) (not (inside-of-single-block (rum/dom-node state))))
-        (keydown-new-block state)
-        (do
-          (.preventDefault e)
-          (keydown-new-line))))))
-
-(defn- select-first-last
-  "Select first or last block in viewport"
-  [direction]
-  (let [f (case direction :up last :down first)
-        container (if (some-> js/document.activeElement
-                              (.querySelector ".blocks-container"))
-                    js/document.activeElement js/document.body)
-        block (->> (util/get-blocks-noncollapse container)
-                   (f))]
-    (when block
-      (util/scroll-to-block block)
-      (state/exit-editing-and-set-selected-blocks! [block]))))
-
-(defn- select-up-down [direction]
-  (let [selected-blocks (state/get-selection-blocks)
-        selected (case direction
-                   :up (first selected-blocks)
-                   :down (last selected-blocks))
-        f (case direction
-            :up util/get-prev-block-non-collapsed
-            :down util/get-next-block-non-collapsed)
-        sibling-block (f selected {:up-down? true
-                                   :exclude-property? true})]
-    (when (and sibling-block (dom/attr sibling-block "blockid"))
-      (util/scroll-to-block sibling-block)
-      (state/exit-editing-and-set-selected-blocks! [sibling-block]))))
-
-(defn- active-jtrigger?
-  []
-  (some-> js/document.activeElement (dom/has-class? "jtrigger")))
-
-(defn- property-value-node?
-  [node]
-  (some-> node (dom/has-class? "property-value-container")))
-
-(defn- focus-trigger
-  [_current-block sibling-block]
-  (when-let [trigger (first (dom/by-class sibling-block "jtrigger"))]
-    (state/clear-edit!)
-    (if (or (dom/has-class? trigger "ls-number")
-            (dom/has-class? trigger "ls-empty-text-property"))
-      (.click trigger)
-      (.focus trigger))))
-
-(defn move-cross-boundary-up-down
-  [direction move-opts]
-  (let [input (or (:input move-opts) (state/get-input))
-        active-element js/document.activeElement
-        input-or-active-element (or input active-element)]
-    (when input-or-active-element
-      (let [repo (state/get-current-repo)
-            f (case direction
-                :up util/get-prev-block-non-collapsed
-                :down util/get-next-block-non-collapsed)
-            current-block (util/rec-get-node input-or-active-element "ls-block")
-            sibling-block (f current-block {:up-down? true})
-            {:block/keys [uuid title format]} (state/get-edit-block)
-            format (or format :markdown)
-            sibling-block (or (when (property-value-node? sibling-block)
-                                (first (dom/by-class sibling-block "ls-block")))
-                              sibling-block)
-            property-value-container? (property-value-node? sibling-block)]
-        (if sibling-block
-          (let [sibling-block-id (dom/attr sibling-block "blockid")
-                container-id (some-> (dom/attr sibling-block "containerid") js/parseInt)
-                value (state/get-edit-content)]
-            (p/do!
-             (when (and
-                    uuid
-                    (not (state/block-component-editing?))
-                    (not= (clean-content! repo format title)
-                          (string/trim value)))
-               (save-block! repo uuid value))
-
-             (if property-value-container?
-               (focus-trigger current-block sibling-block)
-               (let [new-uuid (cljs.core/uuid sibling-block-id)
-                     block (db/entity [:block/uuid new-uuid])]
-                 (edit-block! block
-                              (or (:pos move-opts)
-                                  (when input [direction (util/get-line-pos (.-value input) (util/get-selection-start input))])
-                                  0)
-                              {:container-id container-id
-                               :direction direction})))))
-          (case direction
-            :up (cursor/move-cursor-to input 0)
-            :down (cursor/move-cursor-to-end input)))))))
-
-(defn keydown-up-down-handler
-  [direction {:keys [_pos] :as move-opts}]
-  (let [input (state/get-input)
-        selected-start (util/get-selection-start input)
-        selected-end (util/get-selection-end input)
-        up? (= direction :up)
-        down? (= direction :down)]
-    (cond
-      (active-jtrigger?)
-      (move-cross-boundary-up-down direction move-opts)
-
-      (not= selected-start selected-end)
-      (if up?
-        (cursor/move-cursor-to input selected-start)
-        (cursor/move-cursor-to input selected-end))
-
-      (and input
-           (or (and up? (cursor/textarea-cursor-first-row? input))
-               (and down? (cursor/textarea-cursor-last-row? input))))
-      (move-cross-boundary-up-down direction move-opts)
-
-      :else
-      (when input
-        (if up?
-          (cursor/move-cursor-up input)
-          (cursor/move-cursor-down input))))))
-
-(defn move-to-block-when-cross-boundary
-  [direction {:keys [block]}]
-  (let [up? (= :left direction)
-        pos (if up? :max 0)
-        {:block/keys [format uuid] :as block} (or block (state/get-edit-block))
-        format (or format :markdown)
-        repo (state/get-current-repo)
-        editing-block (gdom/getElement (state/get-editing-block-dom-id))
-        f (if up? util/get-prev-block-non-collapsed util/get-next-block-non-collapsed)
-        sibling-block (f editing-block)
-        sibling-block (or (when (and sibling-block (property-value-node? sibling-block))
-                            (if (and up? editing-block (gdom/contains sibling-block editing-block))
-                              (f sibling-block)
-                              (first (dom/by-class sibling-block "ls-block"))))
-                          sibling-block)]
-    (when sibling-block
-      (if-let [sibling-block-id (dom/attr sibling-block "blockid")]
-        (do
-          (let [content (:block/title block)
-                value (state/get-edit-content)]
-            (when (and value (not= (clean-content! repo format content) (string/trim value)))
-              (save-block! repo uuid value)))
-
-          (let [container-id (some-> (dom/attr sibling-block "containerid") js/parseInt)
-                block (db/entity repo [:block/uuid (cljs.core/uuid sibling-block-id)])]
-            (edit-block! block pos {:container-id container-id})))
-        (when (property-value-node? sibling-block)
-          (focus-trigger editing-block sibling-block))))))
-
-(defn keydown-arrow-handler
-  [direction]
-  (let [input (state/get-input)
-        element js/document.activeElement
-        selected-start (util/get-selection-start input)
-        selected-end (util/get-selection-end input)
-        left? (= direction :left)
-        right? (= direction :right)
-        block (some-> (state/get-edit-block) :db/id db/entity)
-        property? (ldb/property? block)]
-    (cond
-      (and input (not= input element))
-      (.focus input)
-
-      (= input element)
-      (cond
-        (and property? right? (not (cursor/end? input)))
-        (cursor/move-cursor-to-end input)
-
-        (and property? left? (not (cursor/start? input)))
-        (cursor/move-cursor-to-start input)
-
-        (and property? right? (cursor/end? input)
-             (or (not= (:logseq.property/type block) :default)
-                 (seq (:property/closed-values block))))
-        (let [pair (util/rec-get-node input "property-pair")
-              jtrigger (when pair (dom/sel1 pair ".property-value-container .jtrigger"))]
-          (when jtrigger
-            (.focus jtrigger)))
-
-        (not= selected-start selected-end)
-        (cond
-          left?
-          (cursor/move-cursor-to input selected-start)
-          :else
-          (cursor/move-cursor-to input selected-end))
-
-        (or (and left? (cursor/start? input))
-            (and right? (cursor/end? input)))
-        (move-to-block-when-cross-boundary direction {})
-
-        :else
-        (if left?
-          (cursor/move-cursor-backward input)
-          (cursor/move-cursor-forward input)))
-
-      :else
-      nil)))
-
-(defn- delete-and-update [^js input start end]
-  (util/safe-set-range-text! input "" start end)
-  (state/set-edit-content! (state/get-edit-input-id) (.-value input)))
-
-(defn- delete-concat [current-block]
-  (p/let [repo (state/get-current-repo)
-          collapsed? (util/collapsed? current-block)
-          next-block (when-not collapsed?
-                       (let [db (db/get-db repo)]
-                         (when-let [e (or
-                                       ;; first child or next sibling
-                                       (ldb/get-first-child db (:db/id current-block))
-                                       (db-model/get-next db (:db/id current-block)))]
-                           (db/entity (:db/id e)))))]
-    (cond
-      collapsed?
-      nil
-
-      (nil? next-block)
-      nil
-
-      :else
-      (let [repo (state/get-current-repo)
-            editor-state (assoc (get-state)
-                                :block-id (:block/uuid next-block)
-                                :value (:block/title next-block))]
-        (delete-block-inner! repo editor-state)))))
-
-(defn keydown-delete-handler
-  [_e]
-  (let [^js input (state/get-input)
-        current-pos (cursor/pos input)
-        value (gobj/get input "value")
-        end? (= current-pos (count value))
-        current-block (state/get-edit-block)
-        selected-start (util/get-selection-start input)
-        selected-end (util/get-selection-end input)]
-    (when current-block
-      (cond
-        (not= selected-start selected-end)
-        (delete-and-update input selected-start selected-end)
-
-        (and end? current-block)
-        (let [editor-state (get-state)
-              custom-query? (get-in editor-state [:config :custom-query?])]
-          (when-not custom-query?
-            (delete-concat current-block)))
-
-        :else
-        (delete-and-update
-         input current-pos (util/safe-inc-current-pos-from-start (.-value input) current-pos))))))
-
-(defn keydown-backspace-handler
-  [cut? e]
-  (let [^js input (state/get-input)
-        element js/document.activeElement]
-    (if (= input element)
-      (let [id (state/get-edit-input-id)
-            current-pos (cursor/pos input)
-            value (gobj/get input "value")
-            deleted (and (> current-pos 0)
-                         (util/nth-safe value (dec current-pos)))
-            selected-start (util/get-selection-start input)
-            selected-end (util/get-selection-end input)
-            block (state/get-edit-block)
-            block (db/entity (:db/id block))
-            repo (state/get-current-repo)
-            top-block? (= (:db/id (or (ldb/get-left-sibling block) (:block/parent block)))
-                          (:db/id (:block/page block)))
-            single-block? (inside-of-single-block (.-target e))
-            root-block? (= (:block.temp/container block) (str (:block/uuid block)))]
-        (block-handler/mark-last-input-time! repo)
-        (cond
-          (not= selected-start selected-end)
-          (do
-            (util/stop e)
-            (when cut?
-              (js/document.execCommand "copy"))
-            (delete-and-update input selected-start selected-end))
-
-          (zero? current-pos)
-          (let [editor-state (get-state)
-                custom-query? (get-in editor-state [:config :custom-query?])]
-            (util/stop e)
-            (when (and (not (and top-block? (not (string/blank? value))))
-                       (not root-block?)
-                       (not single-block?)
-                       (not custom-query?))
-              (if (own-order-number-list? block)
-                (p/do!
-                 (save-current-block!)
-                 (remove-block-own-order-list-type! block))
-                (delete-block! repo))))
-
-          (and (> current-pos 0)
-               (contains? #{commands/command-trigger commands/command-ask}
-                          (util/nth-safe value (dec current-pos))))
-          (do
-            (util/stop e)
-            (commands/restore-state)
-            (delete-and-update input (dec current-pos) current-pos))
-
-          ;; pair
-          (and
-           deleted
-           (contains?
-            (set (keys delete-map))
-            deleted)
-           (>= (count value) (inc current-pos))
-           (= (util/nth-safe value current-pos)
-              (get delete-map deleted)))
-
-          (do
-            (util/stop e)
-            (commands/delete-pair! id)
-            (cond
-              (and (= deleted "[") (state/get-editor-show-page-search?))
-              (state/clear-editor-action!)
-
-              (and (= deleted "(") (state/get-editor-show-block-search?))
-              (state/clear-editor-action!)
-
-              :else
-              nil))
-
-          ;; deleting hashtag
-          (and (= deleted "#") (state/get-editor-show-page-search-hashtag?))
-          (do
-            (state/clear-editor-action!)
-            (delete-and-update input (dec current-pos) current-pos))
-
-          ;; just delete
-          :else
-          (when (and input (not (mobile-util/native-ios?)))
-            (util/stop e)
-            (delete-and-update
-             input (util/safe-dec-current-pos-from-end (.-value input) current-pos) current-pos))))
-      false)))
-
-(defn indent-outdent
-  [indent?]
-  (let [{:keys [block block-container]} (get-state)]
-    (when block
-      (let [node block-container
-            prev-container-id (get-node-container-id node)
-            container-id (get-new-container-id (if indent? :indent :outdent) {})]
-        (p/do!
-         (block-handler/indent-outdent-blocks! [block] indent? save-current-block!)
-         (when (and (not= prev-container-id container-id) container-id)
-           (state/set-editing-block-id! [container-id (:block/uuid block)])))))))
-
-(defn keydown-tab-handler
-  [direction]
-  (fn [e]
-    (cond
-      (state/editing?)
-      (when-not (state/get-editor-action)
-        (util/stop e)
-        (indent-outdent (not (= :left direction))))
-
-      (state/selection?)
-      (do
-        (util/stop e)
-        (state/pub-event! [:editor/hide-action-bar])
-        (on-tab direction)))
-    nil))
-
-(defn- double-chars-typed?
-  [value pos key sym]
-  (and (= key sym)
-       (>= (count value) 1)
-       (> pos 0)
-       (= (nth value (dec pos)) sym)
-       (if (> (count value) pos)
-         (not= (nth value pos) sym)
-         true)))
-
-(defn ^:large-vars/cleanup-todo keydown-not-matched-handler
-  "NOTE: Keydown cannot be used on Android platform"
-  [format]
-  (fn [e _key-code]
-    (let [input-id (state/get-edit-input-id)
-          input (state/get-input)
-          key (gobj/get e "key")
-          value (gobj/get input "value")
-          ctrlKey (gobj/get e "ctrlKey")
-          metaKey (gobj/get e "metaKey")
-          pos (cursor/pos input)
-          hashtag? (or (surround-by? input "#" " ")
-                       (surround-by? input "#" :end)
-                       (= key "#"))
-          db-based? (config/db-based-graph? (state/get-current-repo))]
-      (when (or (not @(:editor/start-pos @state/state))
-                (and key (string/starts-with? key "Arrow")))
-        (state/set-state! :editor/start-pos pos))
-
-      (cond
-        (and (= :page-search (state/get-editor-action))
-             (= key commands/hashtag))
-        (do
-          (util/stop e)
-          (notification/show! "Page name can't include \"#\"." :warning))
-        ;; stop accepting edits if the new block is not created yet
-        (some? @(:editor/async-unsaved-chars @state/state))
-        (do
-          (when (= 1 (count (str key)))
-            (state/update-state! :editor/async-unsaved-chars
-                                 (fn [s]
-                                   (str s key))))
-          (util/stop e))
-
-        (and (contains? #{"ArrowLeft" "ArrowRight"} key)
-             (contains? #{:property-search :property-value-search} (state/get-editor-action)))
-        (state/clear-editor-action!)
-
-        (and (util/goog-event-is-composing? e true) ;; #3218
-             (not hashtag?) ;; #3283 @Rime
-             (not (state/get-editor-show-page-search-hashtag?))) ;; #3283 @MacOS pinyin
-        nil
-
-        (or ctrlKey metaKey)
-        nil
-
-        ;; FIXME: On mobile, a backspace click to call keydown-backspace-handler
-        ;; does not work if cursor is at the beginning of a block, hence the block
-        ;; can't be deleted. Need to figure out why and find a better solution.
-        (and (mobile-util/native-platform?)
-             (= key "Backspace")
-             (zero? pos)
-             (string/blank? (.toString (js/window.getSelection))))
-        (keydown-backspace-handler false e)
-
-        (and (= key "#")
-             (> pos 0)
-             (= "#" (util/nth-safe value (dec pos))))
-        (state/clear-editor-action!)
-
-        (and (contains? (set/difference (set (keys reversed-autopair-map))
-                                        #{"`"})
-                        key)
-             (= (get-current-input-char input) key))
-        (do
-          (util/stop e)
-          (cursor/move-cursor-forward input))
-
-        (and (autopair-when-selected key) (string/blank? (util/get-selected-text)))
-        nil
-
-        (some? @(:editor/action @state/state))
-        nil
-
-        (and (not (string/blank? (util/get-selected-text)))
-             (contains? keycode/left-square-brackets-keys key))
-        (do
-          (autopair input-id "[" format nil)
-          (util/stop e))
-
-        (and (not (string/blank? (util/get-selected-text)))
-             (contains? keycode/left-paren-keys key))
-        (do (util/stop e)
-            (autopair input-id "(" format nil))
-
-          ;; If you type `xyz`, the last backtick should close the first and not add another autopair
-          ;; If you type several backticks in a row, each one should autopair to accommodate multiline code (```)
-        (-> (keys autopair-map)
-            set
-            (disj "(")
-            (contains? key)
-            (or (autopair-left-paren? input key)))
-        (let [curr (get-current-input-char input)
-              prev (util/nth-safe value (dec pos))]
-          (util/stop e)
-          (if (and (= key "`") (= "`" curr) (not= "`" prev))
-            (cursor/move-cursor-forward input)
-            (autopair input-id key format nil)))
-
-        ; `;;` to add or change property for db graphs
-        (let [sym ";"]
-          (and db-based? (double-chars-typed? value pos key sym)))
-        (state/pub-event! [:editor/new-property])
-
-        (let [sym "$"]
-          (double-chars-typed? value pos key sym))
-        (commands/simple-insert! input-id "$$" {:backward-pos 2})
-
-        (let [sym "^"]
-          (double-chars-typed? value pos key sym))
-        (commands/simple-insert! input-id "^^" {:backward-pos 2})
-
-        :else
-        nil))))
-
-(defn- input-page-ref?
-  [k current-pos blank-selected? last-key-code]
-  (and blank-selected?
-       (contains? keycode/left-square-brackets-keys k)
-       (= (:key last-key-code) k)
-       (> current-pos 0)))
-
-(defn- default-case-for-keyup-handler
-  [input current-pos k code is-processed?]
-  (let [last-key-code (state/get-last-key-code)
-        blank-selected? (string/blank? (util/get-selected-text))
-        non-enter-processed? (and is-processed? ;; #3251
-                                  (not= code keycode/enter-code))  ;; #3459
-        editor-action (state/get-editor-action)]
-    (if (and (= editor-action :page-search-hashtag)
-             (input-page-ref? k current-pos blank-selected? last-key-code))
-      (do
-        (commands/handle-step [:editor/input page-ref/right-brackets {:last-pattern :skip-check
-                                                                      :backward-pos 2}])
-        (commands/handle-step [:editor/search-page])
-        (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}))
-      (when (and (not editor-action) (not non-enter-processed?))
-        (cond
-         ;; When you type text inside square brackets
-          (and (not (contains? #{"ArrowDown" "ArrowLeft" "ArrowRight" "ArrowUp" "Escape"} k))
-               (wrapped-by? input page-ref/left-brackets page-ref/right-brackets))
-          (let [orig-pos (cursor/get-caret-pos input)
-                value (gobj/get input "value")
-                square-pos (string/last-index-of (subs value 0 (:pos orig-pos)) page-ref/left-brackets)
-                pos (+ square-pos 2)
-                _ (state/set-editor-last-pos! pos)
-                pos (assoc orig-pos :pos pos)
-                command-step (if (= \# (util/nth-safe value (dec square-pos)))
-                               :editor/search-page-hashtag
-                               :editor/search-page)]
-            (commands/handle-step [command-step])
-            (state/set-editor-action-data! {:pos pos}))
-
-         ;; Handle non-ascii square brackets
-          (and (input-page-ref? k current-pos blank-selected? last-key-code)
-               (not (wrapped-by? input page-ref/left-brackets page-ref/right-brackets)))
-          (do
-            (commands/handle-step [:editor/input page-ref/left-and-right-brackets {:backward-truncate-number 2
-                                                                                   :backward-pos 2}])
-            (commands/handle-step [:editor/search-page])
-            (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}))
-
-         ;; Handle non-ascii parentheses
-          (and blank-selected?
-               (contains? keycode/left-paren-keys k)
-               (= (:key last-key-code) k)
-               (> current-pos 0)
-               (not (wrapped-by? input block-ref/left-parens block-ref/right-parens)))
-          (do
-            (commands/handle-step [:editor/input block-ref/left-and-right-parens {:backward-truncate-number 2
-                                                                                  :backward-pos 2}])
-            (commands/handle-step [:editor/search-block :reference])
-            (state/set-editor-action-data! {:pos (cursor/get-caret-pos input)}))
-
-          :else
-          nil)))))
-
-(defn keyup-handler
-  [_state input]
-  (fn [e key-code]
-    (when-not (util/goog-event-is-composing? e)
-      (let [db-based? (config/db-based-graph?)
-            current-pos (cursor/pos input)
-            value (gobj/get input "value")
-            c (util/nth-safe value (dec current-pos))
-            [key-code k code is-processed?]
-            (if (and c
-                     (mobile-util/native-android?)
-                     (or (= key-code 229)
-                         (= key-code 0)))
-              [(.charCodeAt value (dec current-pos))
-               c
-               (cond
-                 (= c " ")
-                 "Space"
-
-                 (parse-long c)
-                 (str "Digit" c)
-
-                 :else
-                 (str "Key" (string/upper-case c)))
-               false]
-              [key-code
-               (gobj/get e "key")
-               (if (mobile-util/native-android?)
-                 (gobj/get e "key")
-                 (gobj/getValueByKeys e "event_" "code"))
-                ;; #3440
-               (util/goog-event-is-composing? e true)])]
-        (cond
-          (and db-based? (= value "``````")) ; turn this block into a code block
-          (do
-            (state/set-edit-content! (.-id input) "")
-            (state/pub-event! [:editor/upsert-type-block {:block (assoc (state/get-edit-block) :block/title "")
-                                                          :type :code
-                                                          :update-current-block? true}]))
-
-          (and db-based? (= value ">")) ; turn this block into a quote block
-          (do
-            (state/set-edit-content! (.-id input) "")
-            (state/pub-event! [:editor/upsert-type-block {:block (assoc (state/get-edit-block) :block/title "")
-                                                          :type :quote
-                                                          :update-current-block? true}]))
-
-          ;; When you type something after /
-          (and (= :commands (state/get-editor-action)) (not= k commands/command-trigger))
-          (if (= commands/command-trigger (second (re-find #"(\S+)\s+$" value)))
-            (state/clear-editor-action!)
-            (let [command (get-last-command input)
-                  matched-commands (get-matched-commands command)]
-              (if (seq matched-commands)
-                (commands/set-matched-commands! command matched-commands)
-                (if (> (- (count command) (count @commands/*latest-matched-command)) 2)
-                  (state/clear-editor-action!)
-                  (reset! commands/*matched-commands nil)))))
-
-          :else
-          (default-case-for-keyup-handler input current-pos k code is-processed?))
-
-        (close-autocomplete-if-outside input)
-
-        (when-not (or (= k "Shift") is-processed?)
-          (state/set-last-key-code! {:key-code key-code
-                                     :code code
-                                     :key k
-                                     :shift? (.-shiftKey e)}))
-        (when-not (state/get-editor-action)
-          (state/set-editor-last-pos! current-pos))))))
-
-(defn editor-on-click!
-  [id]
-  (fn [_e]
-    (let [input (gdom/getElement id)]
-      (util/scroll-editor-cursor input)
-      (close-autocomplete-if-outside input))))
-
-(defn editor-on-change!
-  [block id search-timeout]
-  (fn [e]
-    (if (= :block-search (state/sub :editor/action))
-      (let [timeout 50]
-        (when @search-timeout
-          (js/clearTimeout @search-timeout))
-        (reset! search-timeout
-                (js/setTimeout
-                 #(edit-box-on-change! e block id)
-                 timeout)))
-      (let [input (gdom/getElement id)]
-        (edit-box-on-change! e block id)
-        (util/scroll-editor-cursor input)))))
-
-(defn- cut-blocks-and-clear-selections!
-  [copy?]
-  (when-not (get-in @state/state [:ui/find-in-page :active?])
-    (cut-selection-blocks copy?)
-    (clear-selection!)))
-
-(defn shortcut-copy-selection
-  [_e]
-  (copy-selection-blocks true))
-
-(defn shortcut-cut-selection
-  [e]
-  (when-not (util/input? (.-target e))
-    (util/stop e)
-    (cut-blocks-and-clear-selections! true)))
-
-(defn shortcut-delete-selection
-  [e]
-  (when-not (util/input? (.-target e))
-    (util/stop e)
-    (cut-blocks-and-clear-selections! false)))
-
-(defn- copy-current-block-ref
-  [format]
-  (when-let [current-block (state/get-edit-block)]
-    (when-let [block-id (:block/uuid current-block)]
-      (let [db? (config/db-based-graph? (state/get-current-repo))]
-        (if (= format "embed")
-          (if db?
-            (p/do!
-             (save-current-block!)
-             (util/copy-to-clipboard! (ref/->page-ref block-id)
-                                      {:graph (state/get-current-repo)
-                                       :blocks [{:block/uuid (:block/uuid current-block)}]
-                                       :embed-block? true}))
-            (copy-block-ref! block-id #(str "{{embed ((" % "))}}")))
-          (copy-block-ref! block-id
-                           (if db?
-                             ref/->page-ref
-                             ref/->block-ref)))))))
-
-(defn copy-current-block-embed []
-  (copy-current-block-ref "embed"))
-
-(defn shortcut-copy
-  "shortcut copy action:
-  * when in selection mode, copy selected blocks
-  * when in edit mode but no text selected, copy current block ref
-  * when in edit mode with text selected, copy selected text as normal
-  * when text is selected on a PDF, copy the highlighted text"
-  [e]
-  (when-not (auto-complete?)
-    (cond
-      (state/selection?)
-      (shortcut-copy-selection e)
-
-      (and (state/editing?) (nil? (:editor/code-block-context @state/state)))
-      (let [input (state/get-input)
-            selected-start (util/get-selection-start input)
-            selected-end (util/get-selection-end input)]
-        (save-current-block!)
-        (when (= selected-start selected-end)
-          (copy-current-block-ref "ref")))
-
-      (and (state/get-current-pdf)
-           (.closest (.. js/window getSelection -baseNode -parentElement)  ".pdfViewer"))
-      (util/copy-to-clipboard!
-       (pdf-utils/fix-selection-text-breakline (.. js/window getSelection toString))
-       nil))))
-
-(defn shortcut-copy-text
-  "shortcut copy action:
-  * when in selection mode, copy selected blocks
-  * when in edit mode with text selected, copy selected text as normal"
-  [_e]
-  (when-not (auto-complete?)
-    (cond
-      (state/selection?)
-      (copy-selection-blocks false)
-
-      :else
-      (js/document.execCommand "copy"))))
-
-(defn whiteboard?
-  []
-  (and (db-model/whiteboard-page? (state/get-current-page))
-       (.closest (.-activeElement js/document) ".logseq-tldraw")))
-
-(defn shortcut-cut
-  "shortcut cut action:
-  * when in selection mode, cut selected blocks
-  * when in edit mode with text selected, cut selected text
-  * otherwise nothing need to be handled."
-  [e]
-  (cond
-    (state/selection?)
-    (shortcut-cut-selection e)
-
-    (and (state/editing?) (util/input-text-selected?
-                           (gdom/getElement (state/get-edit-input-id))))
-    (keydown-backspace-handler true e)
-
-    (whiteboard?)
-    (.cut (state/active-tldraw-app))
-
-    :else
-    nil))
-
-(defn delete-selection
-  [e]
-  (cond
-    (state/selection?)
-    (shortcut-delete-selection e)
-
-    (and (whiteboard?) (not (state/editing?)))
-    (.deleteShapes (.-api ^js (state/active-tldraw-app)))
-
-    :else
-    nil))
-
-(defn editor-delete
-  [e]
-  (when (state/editing?)
-    (util/stop e)
-    (keydown-delete-handler e)))
-
-(defn editor-backspace
-  [e]
-  (when (state/editing?)
-    (keydown-backspace-handler false e)))
-
-(defn- in-page-preview?
-  []
-  (some-> js/document.activeElement
-          (.closest ".ls-preview-popup")
-          (nil?) (not)))
-
-(defn shortcut-up-down [direction]
-  (fn [e]
-    (state/pub-event! [:editor/hide-action-bar])
-    (when (and (not (auto-complete?))
-               (or (in-page-preview?)
-                   (not (in-shui-popup?)))
-               (not (state/get-timestamp-block)))
-      (util/stop e)
-      (cond
-        (or (state/editing?) (active-jtrigger?))
-        (keydown-up-down-handler direction {})
-
-        (state/selection?)
-        (select-up-down direction)
-
-        ;; if there is an edit-input-id set, we are probably still on editing mode,
-        ;; that is not fully initialized
-        (not (state/get-edit-input-id))
-        (select-first-last direction)))
-    nil))
-
-(defn shortcut-select-up-down [direction]
-  (fn [e]
-    (util/stop e)
-    (if (state/editing?)
-      (let [input (state/get-input)
-            selected-start (util/get-selection-start input)
-            selected-end (util/get-selection-end input)
-            [anchor cursor] (case (util/get-selection-direction input)
-                              "backward" [selected-end selected-start]
-                              [selected-start selected-end])
-            cursor-rect (cursor/get-caret-pos input cursor)]
-        (if
-          ;; 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)
-          ;; simulate text selection
-          (cursor/select-up-down input direction anchor cursor-rect)))
-      (select-block-up-down direction))))
-
-(defn popup-exists?
-  [id]
-  (some->> (shui-popup/get-popups)
-           (some #(some-> % (:id) (str) (string/includes? (str id))))))
-
-(defn editor-commands-popup-exists?
-  []
-  (popup-exists? "editor.commands"))
-
-(defn open-selected-block!
-  [direction e]
-  (let [selected-blocks (state/get-selection-blocks)
-        f (case direction :left first :right last)
-        node (some-> selected-blocks f)]
-    (when-let [block-id (some-> node (dom/attr "blockid") uuid)]
-      (util/stop e)
-      (let [block {:block/uuid block-id}
-            left? (= direction :left)
-            opts {:container-id (some-> node (dom/attr "containerid") (parse-long))
-                  :event e}]
-        (edit-block! block (if left? 0 :max) opts)))))
-
-(defn shortcut-left-right [direction]
-  (fn [e]
-    (when (and (not (auto-complete?))
-               (not (state/get-timestamp-block)))
-      (cond
-        (state/editing?)
-        (do
-          (util/stop e)
-          (keydown-arrow-handler direction))
-
-        (state/selection?)
-        (do
-          (util/stop e)
-          (open-selected-block! direction e))
-
-        :else
-        nil))))
-
-(defn clear-block-content! []
-  (save-current-block! {:force? true})
-  (state/set-edit-content! (state/get-edit-input-id) ""))
-
-(defn kill-line-before! []
-  (save-current-block! {:force? true})
-  (util/kill-line-before! (state/get-input)))
-
-(defn kill-line-after! []
-  (save-current-block! {:force? true})
-  (util/kill-line-after! (state/get-input)))
-
-(defn beginning-of-block []
-  (cursor/move-cursor-to (state/get-input) 0))
-
-(defn end-of-block []
-  (cursor/move-cursor-to-end (state/get-input)))
-
-(defn cursor-forward-word []
-  (cursor/move-cursor-forward-by-word (state/get-input)))
-
-(defn cursor-backward-word []
-  (cursor/move-cursor-backward-by-word (state/get-input)))
-
-(defn backward-kill-word []
-  (let [input (state/get-input)]
-    (save-current-block! {:force? true})
-    (util/backward-kill-word input)
-    (state/set-edit-content! (state/get-edit-input-id) (.-value input))))
-
-(defn forward-kill-word []
-  (let [input (state/get-input)]
-    (save-current-block! {:force? true})
-    (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")
-       (if semantic?
-         (let [ast (mldoc/->edn content format)
-               first-elem-type (first (ffirst ast))]
-           (mldoc/block-with-title? first-elem-type))
-         true)))
-
-(defn- db-collapsable?
-  [block]
-  (let [class-properties (:classes-properties (outliner-property/get-block-classes-properties (db/get-db) (:db/id block)))
-        db (db/get-db)
-        attributes (set (remove #{:block/alias} db-property/db-attribute-properties))
-        properties (->> (:block.temp/property-keys block)
-                        (map (partial entity-plus/entity-memoized db))
-                        (concat class-properties)
-                        (remove (fn [e] (attributes (:db/ident e))))
-                        (remove outliner-property/property-with-other-position?)
-                        (remove (fn [e] (:logseq.property/hide? e)))
-                        (remove nil?))]
-    (or (seq properties)
-        (ldb/class-instance? (entity-plus/entity-memoized db :logseq.class/Query) block))))
-
-(defn collapsable?
-  ([block-id]
-   (collapsable? block-id {}))
-  ([block-id {:keys [semantic? ignore-children?]
-              :or {semantic? false
-                   ignore-children? false}}]
-   (when block-id
-     (let [repo (state/get-current-repo)]
-       (if-let [block (db/entity [:block/uuid block-id])]
-         (let [db-based? (config/db-based-graph? repo)]
-           (or (if ignore-children? false (db-model/has-children? block-id))
-               (and db-based? (db-collapsable? block))
-               (and (not db-based?)
-                    (or (file-editor-handler/valid-dsl-query-block? block)
-                        (file-editor-handler/valid-custom-query-block? block)))
-               (and
-                (:outliner/block-title-collapse-enabled? (state/get-config))
-                (block-with-title? (get block :block/format :markdown)
-                                   (:block/title block)
-                                   semantic?))))
-         false)))))
-
-(defn <all-blocks-with-level
-  "Return all blocks associated with correct level
-   if :root-block is not nil, only return root block with its children
-   if :expanded? true, return expanded children
-   if :collapse? true, return without any collapsed children
-   if :incremental? true, collapse/expand will be step by step
-   for example:
-   - a
-    - b (collapsed)
-     - c
-     - d
-    - e
-   return:
-    blocks
-    [{:block a :level 1}
-     {:block b :level 2}
-     {:block e :level 2}]"
-  [{:keys [collapse? expanded? incremental? root-block page]
-    :or {collapse? false expanded? false incremental? true root-block nil}}]
-  (when-let [page (or page
-                      (state/get-current-page)
-                      (date/today))]
-    (p/let [block-id (or root-block (parse-uuid page))
-            page-id (when-not block-id
-                      (:db/id (db/get-page page)))
-            repo (state/get-current-repo)
-            result (db-async/<get-block repo (or block-id page-id)
-                                        {:children-only? true
-                                         :nested-children? true})
-            blocks (if page-id
-                     result
-                     (cons (db/entity [:block/uuid block-id]) result))
-            root-block (or block-id root-block)]
-      (if incremental?
-        (let [blocks (tree/blocks->vec-tree blocks (or block-id page-id))]
-          (->>
-           (cond->> blocks
-             root-block
-             (map (fn find [root]
-                    (if (= root-block (:block/uuid root))
-                      root
-                      (first (filter find (:block/children root []))))))
-
-             collapse?
-             (w/postwalk
-              (fn [b]
-                (if (and (map? b)
-                         (util/collapsed? b)
-                         (not= root-block (:block/uuid b)))
-                  (assoc b :block/children []) b)))
-
-             true
-             (mapcat (fn [x] (tree-seq map? :block/children x)))
-
-             expanded?
-             (filter (fn [b] (collapsable? (:block/uuid b))))
-
-             true
-             (map (fn [x] (dissoc x :block/children))))
-           (remove nil?)))
-
-        (cond->> blocks
-          collapse?
-          (filter util/collapsed?)
-
-          expanded?
-          (filter (fn [b] (collapsable? (:block/uuid b))))
-
-          true
-          (remove nil?))))))
-
-(defn- skip-collapsing-in-db?
-  []
-  (let [config (last (state/get-editor-args))]
-    (:ref? config)))
-
-(defn set-blocks-collapsed!
-  [block-ids value]
-  (let [block-ids (map (fn [block-id] (if (string? block-id) (uuid block-id) block-id)) block-ids)
-        repo (state/get-current-repo)
-        value (boolean value)]
-    (when repo
-      (save-current-block!) ;; Save the input contents before collapsing
-      (ui-outliner-tx/transact! ;; Save the new collapsed state as an undo transaction (if it changed)
-       {:outliner-op :collapse-expand-blocks}
-       (doseq [block-id block-ids]
-         (when-let [block (db/entity [:block/uuid block-id])]
-           (let [current-value (boolean (:block/collapsed? block))]
-             (when-not (= current-value value)
-               (let [block {:block/uuid block-id
-                            :block/collapsed? value}]
-                 (outliner-save-block! block {:outliner-op :collapse-expand-blocks})))))))
-      (doseq [block-id block-ids]
-        (state/set-collapsed-block! block-id value)))))
-
-(defn collapse-block! [block-id]
-  (when (collapsable? block-id)
-    (when-not (skip-collapsing-in-db?)
-      (set-blocks-collapsed! [block-id] true))
-    (state/set-collapsed-block! block-id true)))
-
-(defn expand-block! [block-id & {:keys [skip-db-collpsing?]}]
-  (let [repo (state/get-current-repo)]
-    (p/do!
-     (db-async/<get-block repo block-id {:children-only? true})
-     (when-not (or skip-db-collpsing? (skip-collapsing-in-db?))
-       (set-blocks-collapsed! [block-id] false))
-     (state/set-collapsed-block! block-id false))))
-
-(defn expand!
-  ([e] (expand! e false))
-  ([e clear-selection?]
-   (util/stop e)
-   (cond
-     (state/editing?)
-     (when-let [block-id (:block/uuid (state/get-edit-block))]
-       (expand-block! block-id))
-
-     (state/selection?)
-     (do
-       (->> (get-selected-blocks)
-            (map (fn [dom]
-                   (-> (dom/attr dom "blockid")
-                       uuid
-                       expand-block!)))
-            doall)
-       (and clear-selection? (clear-selection!)))
-
-     (whiteboard?)
-     (.setCollapsed (.-api ^js (state/active-tldraw-app)) false)
-
-     :else
-     ;; expand one level
-     (p/let [blocks-with-level (<all-blocks-with-level {})
-             max-level (or (apply max (map :block/level blocks-with-level)) 99)]
-       (loop [level 1]
-         (if (> level max-level)
-           nil
-           (let [blocks-to-expand (->> blocks-with-level
-                                       (filter (fn [b] (= (:block/level b) level)))
-                                       (filter util/collapsed?))]
-             (if (empty? blocks-to-expand)
-               (recur (inc level))
-               (doseq [{:block/keys [uuid]} blocks-to-expand]
-                 (expand-block! uuid))))))))))
-
-(defn collapse!
-  ([e] (collapse! e false))
-  ([e clear-selection?]
-   (when e (util/stop e))
-   (cond
-     (state/editing?)
-     (when-let [block-id (:block/uuid (state/get-edit-block))]
-       (collapse-block! block-id))
-
-     (state/selection?)
-     (do
-       (->> (get-selected-blocks)
-            (map (fn [dom]
-                   (-> (dom/attr dom "blockid")
-                       uuid
-                       collapse-block!)))
-            doall)
-       (and clear-selection? (clear-selection!)))
-
-     (whiteboard?)
-     (.setCollapsed (.-api ^js (state/active-tldraw-app)) true)
-
-     :else
-     ;; collapse by one level from outside
-     (p/let [blocks-with-level
-             (<all-blocks-with-level {:collapse? true})
-             max-level (or (apply max (map :block/level blocks-with-level)) 99)]
-       (loop [level max-level]
-         (if (zero? level)
-           nil
-           (let [blocks-to-collapse
-                 (->> blocks-with-level
-                      (filter (fn [b] (= (:block/level b) level)))
-                      (filter (fn [b] (collapsable? (:block/uuid b)))))]
-             (if (empty? blocks-to-collapse)
-               (recur (dec level))
-               (doseq [{:block/keys [uuid]} blocks-to-collapse]
-                 (collapse-block! uuid))))))))))
-
-(defn toggle-collapse!
-  ([e] (toggle-collapse! e false))
-  ([e clear-selection?]
-   (when e (util/stop e))
-   (cond
-     (state/editing?)
-     (when-let [block (state/get-edit-block)]
-        ;; get-edit-block doesn't track the latest collapsed state, so we need to reload from db.
-       (let [block-id (:block/uuid block)
-             block (db/entity [:block/uuid block-id])]
-         (if (:block/collapsed? block)
-           (expand! e clear-selection?)
-           (collapse! e clear-selection?))))
-
-     (state/selection?)
-     (do
-       (let [block-ids (map #(-> % (dom/attr "blockid") uuid) (get-selected-blocks))
-             first-block-id (first block-ids)]
-         (when first-block-id
-            ;; If multiple blocks are selected, they may not have all the same collapsed state.
-            ;; For simplicity, use the first block's state to decide whether to collapse/expand all.
-           (let [first-block (db/entity [:block/uuid first-block-id])]
-             (if (:block/collapsed? first-block)
-               (doseq [block-id block-ids] (expand-block! block-id))
-               (doseq [block-id block-ids] (collapse-block! block-id))))))
-       (and clear-selection? (clear-selection!)))
-
-     (whiteboard?)
-      ;; TODO: Looks like detecting the whiteboard selection's collapse state will take more work.
-      ;; Leaving unimplemented for now.
-     nil
-
-     :else
-      ;; If no block is being edited or selected, the "toggle" action doesn't make sense,
-      ;; so we no-op here, unlike in the expand! & collapse! functions.
-     nil)))
-
-(defn collapse-all!
-  ([]
-   (collapse-all! nil {}))
-  ([block-id {:keys [collapse-self?]
-              :or {collapse-self? true}}]
-   (p/let [blocks (<all-blocks-with-level {:incremental? false
-                                           :expanded? true
-                                           :root-block block-id})
-           block-ids (cond->> (mapv :block/uuid blocks)
-                       (not collapse-self?)
-                       (remove #{block-id}))]
-     (set-blocks-collapsed! block-ids true))))
-
-(defn expand-all!
-  ([]
-   (expand-all! nil))
-  ([block-id]
-   (p/let [blocks (<all-blocks-with-level {:incremental? false
-                                           :collapse? true
-                                           :root-block block-id})
-           block-ids (map :block/uuid blocks)]
-     (set-blocks-collapsed! block-ids false))))
-
-(defn collapse-all-selection!
-  []
-  (p/let [blocks (p/all
-                  (map #(<all-blocks-with-level {:incremental? false
-                                                 :expanded? true
-                                                 :root-block %})
-                       (get-selected-toplevel-block-uuids)))
-          block-ids (->> blocks
-                         (map :block/uuid)
-                         distinct)]
-    (set-blocks-collapsed! block-ids true)))
-
-(defn expand-all-selection!
-  []
-  (let [blocks (p/all
-                (map #(<all-blocks-with-level {:incremental? false
-                                               :expanded? true
-                                               :root-block %})
-                     (get-selected-toplevel-block-uuids)))
-        block-ids (->> blocks
-                       (map :block/uuid)
-                       distinct)]
-    (set-blocks-collapsed! block-ids false)))
-
-(defn toggle-open! []
-  (p/let [blocks (<all-blocks-with-level {:incremental? false
-                                          :collapse? true})
-          all-expanded? (empty? blocks)]
-    (if all-expanded?
-      (collapse-all!)
-      (expand-all!))))
-
-(defn toggle-open-block-children! [block-id]
-  (p/let [blocks (<all-blocks-with-level {:incremental? false
-                                          :collapse? true})
-          all-expanded? (empty? blocks)]
-    (if all-expanded?
-      (collapse-all! block-id {:collapse-self? false})
-      (expand-all! block-id))))
-
-(defn select-all-blocks!
-  [{:keys [page]}]
-  (p/do!
-   (if-let [current-input-id (state/get-edit-input-id)]
-     (let [input (gdom/getElement current-input-id)
-           blocks-container (util/rec-get-blocks-container input)
-           blocks (dom/by-class blocks-container "ls-block")]
-       (state/exit-editing-and-set-selected-blocks! blocks))
-     (p/let [blocks (<all-blocks-with-level {:page page
-                                             :collapse? true})]
-       (->> blocks
-            (map (fn [b] (or (some-> (:db/id (:block/link b)) db/entity) b)))
-            (mapcat (fn [b] (util/get-blocks-by-id (:block/uuid b))))
-            state/exit-editing-and-set-selected-blocks!)))
-   (state/set-state! :selection/selected-all? true)))
-
-(defn select-parent [e]
-  (let [edit-input (some-> (state/get-edit-input-id) gdom/getElement)
-        edit-block (state/get-edit-block)
-        target-element (.-nodeName (.-target e))]
-    (cond
-      ;; editing block fully selected
-      (and edit-block edit-input
-           (= (util/get-selected-text) (.-value edit-input)))
-      (do
-        (util/stop e)
-        (state/exit-editing-and-set-selected-blocks!
-         [(util/get-first-block-by-id (:block/uuid edit-block))]))
-
-      edit-block
-      nil
-
-      ;; Focusing other input element, e.g. when editing page title.
-      (contains? #{"INPUT" "TEXTAREA"} target-element)
-      nil
-
-      (whiteboard?)
-      (do
-        (util/stop e)
-        (.selectAll (.-api ^js (state/active-tldraw-app))))
-
-      :else
-      (do
-        (util/stop e)
-        (when-not @(:selection/selected-all? @state/state)
-          (if-let [block-id (some-> (first (state/get-selection-blocks))
-                                    (dom/attr "blockid")
-                                    uuid)]
-            (when-let [block (db/entity [:block/uuid block-id])]
-              (let [parent (:block/parent block)]
-                (cond
-                  (= (state/get-current-page) (str (:block/uuid block)))
-                  nil
-
-                  (and parent (:block/parent parent))
-                  (state/exit-editing-and-set-selected-blocks!
-                   [(util/get-first-block-by-id (:block/uuid parent))])
-
-                  (:block/name parent)
-                  ;; page block
-                  (select-all-blocks! {:page (:block/name parent)}))))
-            (select-all-blocks! {})))))))
-
-(defn escape-editing
-  [& {:keys [select? save-block?]
-      :or {save-block? true}}]
-  (let [edit-block (state/get-edit-block)]
-    (p/do!
-     (when save-block? (save-current-block!))
-     (if select?
-       (when-let [node (some-> (state/get-input) (util/rec-get-node "ls-block"))]
-         (state/exit-editing-and-set-selected-blocks! [node]))
-       (when (= (:db/id edit-block) (:db/id (state/get-edit-block)))
-         (state/clear-edit!))))))
-
-(defn replace-block-reference-with-content-at-point
-  []
-  (let [repo (state/get-current-repo)]
-    (when-let [{:keys [start end link]} (thingatpt/block-ref-at-point)]
-      (when-let [block (db/entity [:block/uuid link])]
-        (let [block-content (:block/title block)
-              format (get block :block/format :markdown)
-              block-content-without-prop (-> (property-file/remove-properties-when-file-based repo format block-content)
-                                             (drawer/remove-logbook))]
-          (when-let [input (state/get-input)]
-            (when-let [current-block-content (gobj/get input "value")]
-              (let [block-content* (str (subs current-block-content 0 start)
-                                        block-content-without-prop
-                                        (subs current-block-content end))]
-                (state/set-block-content-and-last-pos! input block-content* 1)))))))))
-
-(defn copy-current-ref
-  [block-id]
-  (when block-id
-    (util/copy-to-clipboard! (ref/->block-ref block-id))))
-
-(defn delete-current-ref!
-  [block ref-id]
-  (when (and block ref-id)
-    (let [content (if (config/db-based-graph?)
-                    (string/replace (:block/title block) (ref/->page-ref ref-id) "")
-                    (let [match (re-pattern (str "\\s?"
-                                                 (string/replace (ref/->block-ref ref-id) #"([\(\)])" "\\$1")))]
-                      (string/replace (:block/title block) match "")))]
-      (save-block! (state/get-current-repo)
-                   (:block/uuid block)
-                   content))))
-
-(defn replace-ref-with-text!
-  [block ref-id]
-  (when (and block ref-id)
-    (let [repo (state/get-current-repo)
-          match (ref/->block-ref ref-id)
-          ref-block (db/entity [:block/uuid ref-id])
-          block-ref-content (->> (or (:block/title ref-block) "")
-                                 (property-file/remove-built-in-properties-when-file-based repo
-                                                                                           (get ref-block :block/format :markdown))
-                                 (drawer/remove-logbook))
-          content (string/replace-first (:block/title block) match
-                                        block-ref-content)]
-      (save-block! (state/get-current-repo)
-                   (:block/uuid block)
-                   content))))
-
-(defn replace-ref-with-embed!
-  [block ref-id]
-  (when (and block ref-id)
-    (let [match (ref/->block-ref ref-id)
-          content (string/replace-first (:block/title block) match
-                                        (util/format "{{embed ((%s))}}"
-                                                     (str ref-id)))]
-      (save-block! (state/get-current-repo)
-                   (:block/uuid block)
-                   content))))
-
-(defn block-default-collapsed?
-  "Whether a block should be collapsed by default.
-  Currently, this handles all the kinds of views."
-  [block config]
-  (let [block (or (db/entity (:db/id block)) block)]
-    (or
-     (util/collapsed? block)
-     (and (or (:view? config) (:popup? config))
-          (or (ldb/page? block)
-              (some? (:block/_parent block))
-              (:table-block-title? config))))))
-
-(defn batch-set-heading!
-  [block-ids heading]
-  (let [repo (state/get-current-repo)]
-    (if (config/db-based-graph? repo)
-      (db-editor-handler/batch-set-heading! repo block-ids heading)
-      (file-editor-handler/batch-set-heading! block-ids heading))))
-
-(defn set-heading!
-  [block-id heading]
-  (batch-set-heading! [block-id] heading))
-
-(defn remove-heading!
-  [block-id]
-  (set-heading! block-id nil))
-
-(defn batch-remove-heading!
-  [block-ids]
-  (batch-set-heading! block-ids nil))
-
-(defn block->data-transfer!
-  "Set block or page name to the given event's dataTransfer. Used in dnd."
-  [block-or-page-name event page?]
-  (.setData (gobj/get event "dataTransfer")
-            (if page? "page-name" "block-uuid")
-            (str block-or-page-name)))
-
-(defn run-query-command!
-  []
-  (let [repo (state/get-current-repo)]
-    (when-let [block (some-> (state/get-edit-block)
-                             :db/id
-                             (db/entity))]
-      (p/do!
-       (save-current-block!)
-       (state/clear-edit!)
-       (p/let [query-block (or (:logseq.property/query block)
-                               (p/do!
-                                (property-handler/set-block-property! repo (:db/id block) :logseq.property/query "")
-                                (:logseq.property/query (db/entity (:db/id block)))))
-               current-query (:block/title (db/entity (:db/id block)))]
-         (p/do!
-          (ui-outliner-tx/transact!
-           {:outliner-op :save-block}
-           (property-handler/set-block-property! repo (:db/id block) :block/tags :logseq.class/Query)
-           (save-block-inner! block "" {})
-           (when query-block
-             (save-block-inner! query-block current-query {})))))))))

+ 13 - 85
src/main/frontend/handler/events.cljs

@@ -3,8 +3,7 @@
   core.async channel to handle them. Any part of the system can dispatch
   one of these events using state/pub-event!"
   (:refer-clojure :exclude [run!])
-  (:require ["@capacitor/filesystem" :refer [Directory Filesystem]]
-            ["@sentry/react" :as Sentry]
+  (:require ["@sentry/react" :as Sentry]
             [cljs-bean.core :as bean]
             [clojure.core.async :as async]
             [clojure.core.async.interop :refer [p->c]]
@@ -14,9 +13,7 @@
             [frontend.date :as date]
             [frontend.db :as db]
             [frontend.db.async :as db-async]
-            [frontend.db.conn :as conn]
             [frontend.db.model :as db-model]
-            [frontend.db.persist :as db-persist]
             [frontend.db.transact :as db-transact]
             [frontend.extensions.fsrs :as fsrs]
             [frontend.fs :as fs]
@@ -30,7 +27,6 @@
             [frontend.handler.db-based.rtc-flows :as rtc-flows]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.export :as export]
-            [frontend.handler.file-sync :as file-sync-handler]
             [frontend.handler.graph :as graph-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.page :as page-handler]
@@ -49,7 +45,6 @@
             [frontend.modules.shortcut.core :as st]
             [frontend.persist-db :as persist-db]
             [frontend.quick-capture :as quick-capture]
-            [frontend.search :as search]
             [frontend.state :as state]
             [frontend.util :as util]
             [frontend.util.persist-var :as persist-var]
@@ -85,29 +80,22 @@
   (when (= (:url repo) current-repo)
     (file-sync-restart!)))
 
-;; FIXME(andelf): awful multi-arty function.
-;; Should use a `-impl` function instead of the awful `skip-ios-check?` param with nested callback.
 (defn- graph-switch
-  ([graph]
-   (graph-switch graph false))
-  ([graph skip-ios-check?]
-   (let [db-based? (config/db-based-graph? graph)]
-     (if (and (mobile-util/native-ios?) (not skip-ios-check?))
-       (state/pub-event! [:validate-appId graph-switch graph])
-       (do
-         (state/set-current-repo! graph)
-         (page-handler/init-commands!)
+  [graph]
+  (let [db-based? (config/db-based-graph? graph)]
+    (state/set-current-repo! graph)
+    (page-handler/init-commands!)
          ;; load config
-         (repo-config-handler/restore-repo-config! graph)
-         (when-not (= :draw (state/get-current-route))
-           (route-handler/redirect-to-home!))
-         (when-not db-based?
+    (repo-config-handler/restore-repo-config! graph)
+    (when-not (= :draw (state/get-current-route))
+      (route-handler/redirect-to-home!))
+    (when-not db-based?
            ;; graph-switch will trigger a rtc-start automatically
            ;; (rtc-handler/<rtc-start! graph)
-           (file-sync-restart!))
-         (when-let [dir-name (and (not db-based?) (config/get-repo-dir graph))]
-           (fs/watch-dir! dir-name))
-         (graph-handler/settle-metadata-to-local! {:last-seen-at (js/Date.now)}))))))
+      (file-sync-restart!))
+    (when-let [dir-name (and (not db-based?) (config/get-repo-dir graph))]
+      (fs/watch-dir! dir-name))
+    (graph-handler/settle-metadata-to-local! {:last-seen-at (js/Date.now)})))
 
 ;; Parameters for the `persist-db` function, to show the notification messages
 (defn- graph-switch-on-persisted
@@ -271,72 +259,12 @@
       (when-let [toolbar (.querySelector main-node "#mobile-editor-toolbar")]
         (set! (.. toolbar -style -bottom) 0)))))
 
-(defn- get-ios-app-id
-  [repo-url]
-  (when repo-url
-    (let [app-id (-> (first (string/split repo-url "/Documents"))
-                     (string/split "/")
-                     last)]
-      app-id)))
-
-(defmethod handle :validate-appId [[_ graph-switch-f graph]]
-  (when-let [deprecated-repo (or graph (state/get-current-repo))]
-    (if (mobile-util/in-iCloud-container-path? deprecated-repo)
-      ;; Installation is not changed for iCloud
-      (when graph-switch-f
-        (graph-switch-f graph true)
-        (state/pub-event! [:graph/ready (state/get-current-repo)]))
-      ;; Installation is changed for App Documents directory
-      (p/let [deprecated-app-id (get-ios-app-id deprecated-repo)
-              current-document-url (.getUri Filesystem #js {:path ""
-                                                            :directory (.-Documents Directory)})
-              current-app-id (-> (js->clj current-document-url :keywordize-keys true)
-                                 get-ios-app-id)]
-        (if (= deprecated-app-id current-app-id)
-          (when graph-switch-f (graph-switch-f graph true))
-          (do
-            (notification/show! [:div "Migrating from previous App installation..."]
-                                :warning
-                                true)
-            (prn ::migrate-app-id :from deprecated-app-id :to current-app-id)
-            (file-sync-stop!)
-            (.unwatch mobile-util/fs-watcher)
-            (let [current-repo (string/replace deprecated-repo deprecated-app-id current-app-id)
-                  current-repo-dir (config/get-repo-dir current-repo)]
-              (try
-                ;; replace app-id part of repo url
-                (reset! conn/conns
-                        (update-keys @conn/conns
-                                     (fn [key]
-                                       (if (string/includes? key deprecated-app-id)
-                                         (string/replace key deprecated-app-id current-app-id)
-                                         key))))
-                (db-persist/rename-graph! deprecated-repo current-repo)
-                (search/remove-db! deprecated-repo)
-                (state/add-repo! {:url current-repo :nfs? true})
-                (state/delete-repo! {:url deprecated-repo})
-                (catch :default e
-                  (js/console.error e)))
-              (state/set-current-repo! current-repo)
-              (repo-config-handler/restore-repo-config! current-repo)
-              (when graph-switch-f (graph-switch-f current-repo true))
-              (.watch mobile-util/fs-watcher #js {:path current-repo-dir})
-              (file-sync-restart!))))
-        (state/pub-event! [:graph/ready (state/get-current-repo)])))))
-
 (defmethod handle :plugin/hook-db-tx [[_ {:keys [blocks tx-data] :as payload}]]
   (when-let [payload (and (seq blocks)
                           (merge payload {:tx-data (map #(into [] %) tx-data)}))]
     (plugin-handler/hook-plugin-db :changed payload)
     (plugin-handler/hook-plugin-block-changes payload)))
 
-(defmethod handle :mobile-file-watcher/changed [[_ ^js event]]
-  (let [type (.-event event)
-        payload (js->clj event :keywordize-keys true)]
-    (fs-watcher/handle-changed! type payload)
-    (when (file-sync-handler/enable-sync?)
-      (sync/file-watch-handler type payload))))
-
 (defmethod handle :rebuild-slash-commands-list [[_]]
   (page-handler/rebuild-slash-commands-list!))
 

+ 0 - 7
src/main/frontend/handler/events/ui.cljs

@@ -19,7 +19,6 @@
             [frontend.db :as db]
             [frontend.extensions.fsrs :as fsrs]
             [frontend.extensions.srs :as srs]
-            [frontend.fs.capacitor-fs :as capacitor-fs]
             [frontend.fs.sync :as sync]
             [frontend.handler.db-based.rtc :as rtc-handler]
             [frontend.handler.editor :as editor-handler]
@@ -99,12 +98,6 @@
      {:id :srs
       :label "flashcards__cp"})))
 
-(defmethod events/handle :modal/show-instruction [_]
-  (shui/dialog-open!
-   capacitor-fs/instruction
-   {:id :instruction
-    :label "instruction__cp"}))
-
 (defmethod events/handle :modal/show-themes-modal [[_ classic?]]
   (if classic?
     (plugin/open-select-theme!)

+ 2 - 11
src/main/frontend/handler/file_based/file.cljs

@@ -6,13 +6,11 @@
             [frontend.db :as db]
             [frontend.db.file-based.model :as file-model]
             [frontend.fs :as fs]
-            [frontend.fs.capacitor-fs :as capacitor-fs]
             [frontend.handler.common.config-edn :as config-edn-common-handler]
             [frontend.handler.file-based.reset-file :as reset-file-handler]
             [frontend.handler.global-config :as global-config-handler]
             [frontend.handler.repo-config :as repo-config-handler]
             [frontend.handler.ui :as ui-handler]
-            [frontend.mobile.util :as mobile-util]
             [frontend.schema.handler.global-config :as global-config-schema]
             [frontend.schema.handler.repo-config :as repo-config-schema]
             [frontend.state :as state]
@@ -78,15 +76,8 @@
 (defn backup-file!
   "Backup db content to bak directory"
   [repo-url path db-content content]
-  (cond
-    (util/electron?)
-    (ipc/ipc "backupDbFile" repo-url path db-content content)
-
-    (mobile-util/native-platform?)
-    (capacitor-fs/backup-file-handle-changed! repo-url path db-content)
-
-    :else
-    nil))
+  (when (util/electron?)
+    (ipc/ipc "backupDbFile" repo-url path db-content content)))
 
 (defn- detect-deprecations
   [path content]

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

@@ -21,13 +21,14 @@
   (when (and key @store)
     (idb-keyval/set key value @store)))
 
-(defn rename-item!
-  [old-key new-key]
-  (when (and old-key new-key @store)
-    (p/let [value (idb-keyval/get old-key @store)]
-      (when value
-        (idb-keyval/set new-key value @store)
-        (idb-keyval/del old-key @store)))))
+(comment
+  (defn rename-item!
+    [old-key new-key]
+    (when (and old-key new-key @store)
+      (p/let [value (idb-keyval/get old-key @store)]
+        (when value
+          (idb-keyval/set new-key value @store)
+          (idb-keyval/del old-key @store))))))
 
 (comment
   (defn set-batch!

+ 4 - 51
src/main/frontend/mobile/core.cljs

@@ -3,16 +3,12 @@
   (:require ["@capacitor/app" :refer [^js App]]
             ["@capacitor/keyboard" :refer [^js Keyboard]]
             [clojure.string :as string]
-            [promesa.core :as p]
-            [frontend.fs.capacitor-fs :as capacitor-fs]
             [frontend.handler.editor :as editor-handler]
             [frontend.mobile.deeplink :as deeplink]
             [frontend.mobile.intent :as intent]
             [frontend.mobile.util :as mobile-util]
             [frontend.state :as state]
-            [frontend.util :as util]
-            [cljs-bean.core :as bean]
-            [frontend.config :as config]))
+            [frontend.util :as util]))
 
 (def *init-url (atom nil))
 ;; FIXME: `appUrlOpen` are fired twice when receiving a same intent.
@@ -21,14 +17,6 @@
 (def *last-shared-url (atom nil))
 (def *last-shared-seconds (atom 0))
 
-(defn mobile-preinit
-  "preinit logic of mobile platforms: setup document folder permission"
-  []
-  (when (mobile-util/native-ios?)
-    ;; Caution: This must be called before any file accessing
-    (capacitor-fs/ios-ensure-documents!)))
-
-
 (defn mobile-postinit
   "postinit logic of mobile platforms: handle deeplink and intent"
   []
@@ -40,30 +28,7 @@
 (defn- ios-init
   "Initialize iOS-specified event listeners"
   []
-  (p/let [^js paths (capacitor-fs/ios-ensure-documents!)]
-    (when paths
-      (let [paths (-> paths
-                      bean/->clj
-                      (update-vals capacitor-fs/ios-force-include-private))]
-        (state/set-state! :mobile/container-urls paths)
-        (println "iOS container path: " paths))))
-
-  ;; Fix iOS App directory change across installation
-  (when (not (config/demo-graph?))
-    (state/pub-event! [:validate-appId]))
-
-  (mobile-util/check-ios-zoomed-display)
-
-  ;; keep this the same logic as src/main/electron/listener.cljs
-  (.addListener mobile-util/file-sync "debug"
-                (fn [event]
-                  (let [event (js->clj event :keywordize-keys true)
-                        payload (:data event)]
-                    (when (or (= (:event event) "download:progress")
-                              (= (:event event) "upload:progress"))
-                      (state/set-state! [:file-sync/graph-state (:graphUUID payload) :file-sync/progress (:file payload)] payload))))))
-
-
+  (mobile-util/check-ios-zoomed-display))
 
 (defn- android-init
   "Initialize Android-specified event listeners"
@@ -98,13 +63,7 @@
                        (js/window.history.back)))))
 
   (.addEventListener js/window "sendIntentReceived"
-                     #(intent/handle-received))
-
-  (.addListener mobile-util/file-sync "progress"
-                (fn [event]
-                  (js/console.log "🔄" event)
-                  (let [event (js->clj event :keywordize-keys true)]
-                    (state/set-state! [:file-sync/graph-state (:graphUUID event) :file-sync/progress (:file event)] event)))))
+                     #(intent/handle-received)))
 
 (defn- app-state-change-handler
   [^js state]
@@ -112,8 +71,7 @@
   (when (state/get-current-repo)
     (let [is-active? (.-isActive state)]
       (when-not is-active?
-        (editor-handler/save-current-block!))
-      (state/set-mobile-app-state-change is-active?))))
+        (editor-handler/save-current-block!)))))
 
 (defn- general-init
   "Initialize event listeners used by both iOS and Android"
@@ -129,10 +87,6 @@
                         (reset! *last-shared-seconds (.getSeconds (js/Date.)))
                         (deeplink/deeplink url))))))
 
-  (.addListener mobile-util/fs-watcher "watcher"
-                (fn [event]
-                  (state/pub-event! [:mobile-file-watcher/changed event])))
-
   (.addListener Keyboard "keyboardWillShow"
                 (fn [^js info]
                   (let [keyboard-height (.-keyboardHeight info)]
@@ -147,7 +101,6 @@
 
   (.addListener App "appStateChange" app-state-change-handler))
 
-
 (defn init! []
   (when (mobile-util/native-android?)
     (android-init))

+ 10 - 22
src/main/frontend/mobile/util.cljs

@@ -1,10 +1,8 @@
 (ns frontend.mobile.util
-  (:require ["@capacitor/core" :refer [Capacitor registerPlugin ^js Plugins]]
+  (:require ["@capacitor/core" :refer [Capacitor registerPlugin]]
             ["@capacitor/splash-screen" :refer [SplashScreen]]
-            ["@logseq/capacitor-file-sync" :refer [FileSync]]
             [clojure.string :as string]
-            [promesa.core :as p]
-            [goog.object :as gobj]))
+            [promesa.core :as p]))
 
 (defn platform []
   (.getPlatform Capacitor))
@@ -25,13 +23,7 @@
 
 (defonce folder-picker (registerPlugin "FolderPicker"))
 (when (native-ios?)
-  (defonce ios-utils (registerPlugin "Utils"))
-  (defonce ios-file-container (registerPlugin "FileContainer")))
-
-;; NOTE: both iOS and android share the same API
-(when (native-platform?)
-  (defonce file-sync FileSync)
-  (defonce fs-watcher (registerPlugin "FsWatcher")))
+  (defonce ios-utils (registerPlugin "Utils")))
 
 (defn hide-splash []
   (.hide SplashScreen))
@@ -99,14 +91,10 @@
   [path]
   (string/includes? path "/iCloud~com~logseq~logseq/"))
 
-(defn is-iCloud-container-path?
-  "Check whether `path' is iCloud container path on iOS"
-  [path]
-  (re-matches #"/iCloud~com~logseq~logseq/Documents/?$" path))
-
-(defn app-active?
-  "Whether the app is active. This function returns a promise."
-  []
-  (let [app ^js (gobj/get Plugins "App")]
-    (p/let [state (.getState app)]
-      (gobj/get state "isActive"))))
+(comment
+  (defn app-active?
+    "Whether the app is active. This function returns a promise."
+    []
+    (let [app ^js (gobj/get Plugins "App")]
+      (p/let [state (.getState app)]
+        (gobj/get state "isActive")))))

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

@@ -218,10 +218,6 @@
       :mobile/show-toolbar?                  false
       :mobile/show-recording-bar?            false
       :mobile/show-tabbar?                   false
-;;; Used to monitor mobile app status,
-;;; value spec:
-;;; {:is-active? bool, :timestamp int}
-      :mobile/app-state-change                 (atom nil)
 
       ;; plugin
       :plugin/enabled                        (and util/plugin-platform?
@@ -2228,12 +2224,6 @@ Similar to re-frame subscriptions"
                 (every? not-empty (vals agent-opts)))
       (str protocol "://" host ":" port))))
 
-(defn set-mobile-app-state-change
-  [is-active?]
-  (set-state! :mobile/app-state-change
-              {:is-active? is-active?
-               :timestamp (inst-ms (js/Date.))}))
-
 (defn get-sync-graph-by-id
   [graph-uuid]
   (when graph-uuid

+ 6 - 6
yarn.lock

@@ -587,12 +587,7 @@
   resolved "https://registry.yarnpkg.com/@js-joda/timezone/-/timezone-2.5.0.tgz#b422ff400c25ae311384239c62724eecee2e442b"
   integrity sha512-HHFVhGUKIOtITiT+sbQRdYuO5Q+a8FDj/vQSGUSxe6+5w2in5JsavfRsAN2tU/NCdBeFx/6q8evHMtOrXfdn2g==
 
-"@logseq/[email protected]":
-  version "5.0.2"
-  resolved "https://registry.yarnpkg.com/@logseq/capacitor-file-sync/-/capacitor-file-sync-5.0.2.tgz#10c56e35b41b1a0afd293c9b045fbcfe150c3477"
-  integrity sha512-FymsTeRtF66zG+oeO+ohZxWICMQMC8An4n9pdI0zz1WaGLer4oWC/lUghlC2DpztRLA32p0CH28tEzF5+2jARg==
-
-"@logseq/[email protected]":
+"@logseq/diff-merge@^0.2.2":
   version "0.2.2"
   resolved "https://registry.yarnpkg.com/@logseq/diff-merge/-/diff-merge-0.2.2.tgz#583bd8c8c66d5ff05ea70906475efaa078e839a3"
   integrity sha512-0WeKNhq8PsjvunOqNEd9aSM4tgiClwhonXgXzrQ4KYj8VoyLaEAyEWWGOAoE7mwR+aqwM+bMB4MxuNFywnUb8A==
@@ -9002,6 +8997,11 @@ undertaker@^1.2.1:
     object.reduce "^1.0.0"
     undertaker-registry "^1.0.0"
 
+undici-types@~6.20.0:
+  version "6.20.0"
+  resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
+  integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
+
 undici-types@~6.21.0:
   version "6.21.0"
   resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb"