Przeglądaj źródła

wip: separate og and new version apps

Tienson Qin 3 dni temu
rodzic
commit
da06bc2db0
30 zmienionych plików z 218 dodań i 7590 usunięć
  1. 3 18
      src/main/electron/listener.cljs
  2. 45 528
      src/main/frontend/components/block.cljs
  3. 0 241
      src/main/frontend/components/encryption.cljs
  4. 0 152
      src/main/frontend/components/file_based/block.cljs
  5. 0 82
      src/main/frontend/components/file_based/git.cljs
  6. 0 66
      src/main/frontend/components/file_based/hierarchy.cljs
  7. 0 80
      src/main/frontend/components/file_based/query.cljs
  8. 0 263
      src/main/frontend/components/file_based/query_table.cljs
  9. 0 858
      src/main/frontend/components/file_sync.cljs
  10. 0 7
      src/main/frontend/components/header.cljs
  11. 0 5
      src/main/frontend/components/page.cljs
  12. 9 55
      src/main/frontend/components/page_menu.cljs
  13. 7 45
      src/main/frontend/components/query.cljs
  14. 28 112
      src/main/frontend/components/repo.cljs
  15. 3 73
      src/main/frontend/components/settings.cljs
  16. 1 7
      src/main/frontend/core.cljs
  17. 0 3322
      src/main/frontend/fs/sync.cljs
  18. 0 1
      src/main/frontend/handler.cljs
  19. 2 27
      src/main/frontend/handler/events.cljs
  20. 7 0
      src/main/frontend/handler/events/rtc.cljs
  21. 2 56
      src/main/frontend/handler/events/ui.cljs
  22. 0 382
      src/main/frontend/handler/file_based/events.cljs
  23. 0 290
      src/main/frontend/handler/file_based/native_fs.cljs
  24. 0 267
      src/main/frontend/handler/file_sync.cljs
  25. 0 13
      src/main/frontend/handler/page.cljs
  26. 111 1
      src/main/frontend/handler/user.cljs
  27. 0 158
      src/main/frontend/mobile/graph_picker.cljs
  28. 0 38
      src/main/frontend/state.cljs
  29. 0 398
      src/test/frontend/fs/diff_merge_test.cljs
  30. 0 45
      src/test/frontend/fs/sync_test.cljs

+ 3 - 18
src/main/electron/listener.cljs

@@ -7,10 +7,7 @@
             [electron.ipc :as ipc]
             [frontend.db :as db]
             [frontend.db.async :as db-async]
-            [frontend.db.file-based.model :as file-model]
-            [frontend.fs.sync :as sync]
             [frontend.fs.watcher-handler :as watcher-handler]
-            [frontend.handler.file-sync :as file-sync-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.property.util :as pu]
             [frontend.handler.route :as route-handler]
@@ -38,14 +35,7 @@
                          path (common-util/path-normalize (:path payload))
                          dir (:dir payload)
                          payload (assoc payload :path (path/relative-path dir path))]
-                     (watcher-handler/handle-changed! type payload)
-                     (when (file-sync-handler/enable-sync?)
-                       (sync/file-watch-handler type payload)))))
-
-  (safe-api-call "file-sync-progress"
-                 (fn [data]
-                   (let [payload (bean/->clj data)]
-                     (state/set-state! [:file-sync/graph-state (:graphUUID payload) :file-sync/progress (:file payload)] payload))))
+                     (watcher-handler/handle-changed! type payload))))
 
   (safe-api-call "notification"
                  (fn [data]
@@ -80,7 +70,7 @@
                  ;;  :page-name : the title of the page.
                  ;;  :block-id : uuid.
                  (fn [data]
-                   (let [{:keys [page-name block-id file]} (bean/->clj data)]
+                   (let [{:keys [page-name block-id]} (bean/->clj data)]
                      (cond
                        page-name
                        (when (db/get-page page-name)
@@ -92,12 +82,7 @@
                            (if (pu/shape-block? block)
                              (route-handler/redirect-to-page! (get-in block [:block/page :block/uuid]) {:block-id block-id})
                              (route-handler/redirect-to-page! block-id))
-                           (notification/show! (str "Open link failed. Block-id `" block-id "` doesn't exist in the graph.") :error false)))
-
-                       file
-                       (if-let [db-page-name (file-model/get-file-page file false)]
-                         (route-handler/redirect-to-page! db-page-name)
-                         (notification/show! (str "Open link failed. File `" file "` doesn't exist in the graph.") :error false))))))
+                           (notification/show! (str "Open link failed. Block-id `" block-id "` doesn't exist in the graph.") :error false)))))))
 
   (safe-api-call "foundInPage"
                  (fn [data]

+ 45 - 528
src/main/frontend/components/block.cljs

@@ -11,7 +11,6 @@
             [dommy.core :as dom]
             [electron.ipc :as ipc]
             [frontend.components.block.macros :as block-macros]
-            [frontend.components.file-based.block :as file-block]
             [frontend.components.icon :as icon-component]
             [frontend.components.lazy-editor :as lazy-editor]
             [frontend.components.macro :as macro]
@@ -28,7 +27,6 @@
             [frontend.db :as db]
             [frontend.db-mixins :as db-mixins]
             [frontend.db.async :as db-async]
-            [frontend.db.file-based.model :as file-model]
             [frontend.db.model :as model]
             [frontend.extensions.highlight :as highlight]
             [frontend.extensions.latex :as latex]
@@ -46,13 +44,9 @@
             [frontend.handler.dnd :as dnd]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.export.common :as export-common-handler]
-            [frontend.handler.file-based.editor :as file-editor-handler]
-            [frontend.handler.file-based.property.util :as property-util]
-            [frontend.handler.file-sync :as file-sync]
             [frontend.handler.notification :as notification]
             [frontend.handler.plugin :as plugin-handler]
             [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.handler.ui :as ui-handler]
@@ -65,11 +59,9 @@
             [frontend.modules.shortcut.utils :as shortcut-utils]
             [frontend.security :as security]
             [frontend.state :as state]
-            [frontend.template :as template]
             [frontend.ui :as ui]
             [frontend.util :as util]
             [frontend.util.file-based.clock :as clock]
-            [frontend.util.file-based.drawer :as drawer]
             [frontend.util.ref :as ref]
             [frontend.util.text :as text-util]
             [goog.dom :as gdom]
@@ -80,7 +72,6 @@
             [logseq.common.path :as path]
             [logseq.common.util :as common-util]
             [logseq.common.util.block-ref :as block-ref]
-            [logseq.common.util.macro :as macro-util]
             [logseq.common.util.page-ref :as page-ref]
             [logseq.db :as ldb]
             [logseq.db.common.entity-plus :as entity-plus]
@@ -93,7 +84,6 @@
             [logseq.shui.ui :as shui]
             [medley.core :as medley]
             [promesa.core :as p]
-            [reitit.frontend.easy :as rfe]
             [rum.core :as rum]
             [shadow.loader :as loader]))
 
@@ -156,68 +146,6 @@
         link
         (str protocol "://" link)))))
 
-(rum/defcs file-based-asset-loader
-  < rum/reactive
-  (rum/local nil ::exist?)
-  (rum/local false ::loading?)
-  {:will-mount (fn [state]
-                 (let [src (first (:rum/args state))]
-                   (if (and (common-config/local-protocol-asset? src)
-                            (file-sync/current-graph-sync-on?))
-                     (let [*exist? (::exist? state)
-                             ;; special handling for asset:// protocol
-                             ;; Capacitor uses a special URL for assets loading
-                           asset-path (common-config/remove-asset-protocol src)
-                           asset-path (fs/asset-path-normalize asset-path)]
-                       (if (string/blank? asset-path)
-                         (reset! *exist? false)
-                           ;; FIXME(andelf): possible bug here
-                         (p/let [exist? (fs/asset-href-exists? asset-path)]
-                           (reset! *exist? (boolean exist?))))
-                       (assoc state ::asset-path asset-path ::asset-file? true))
-                     state)))
-   :will-update (fn [state]
-                  (let [src (first (:rum/args state))
-                        asset-file? (boolean (::asset-file? state))
-                        sync-on? (file-sync/current-graph-sync-on?)
-                        *loading? (::loading? state)
-                        *exist? (::exist? state)]
-                    (when (and sync-on? asset-file? (false? @*exist?))
-                      (let [sync-state (state/get-file-sync-state (state/get-current-file-sync-graph-uuid))
-                            downloading-files (:current-remote->local-files sync-state)
-                            contain-url? (and (seq downloading-files)
-                                              (some #(string/ends-with? src %) downloading-files))]
-                        (cond
-                          (and (not @*loading?) contain-url?)
-                          (reset! *loading? true)
-
-                          (and @*loading? (not contain-url?))
-                          (do
-                            (reset! *exist? true)
-                            (reset! *loading? false))))))
-                  state)}
-  [state src content-fn]
-  (let [_ (state/sub-file-sync-state (state/get-current-file-sync-graph-uuid))
-        exist? @(::exist? state)
-        loading? @(::loading? state)
-        asset-file? (::asset-file? state)
-        sync-enabled? (boolean (file-sync/current-graph-sync-on?))
-        ext (keyword (util/get-file-ext src))
-        img? (contains? (common-config/img-formats) ext)
-        audio? (contains? config/audio-formats ext)
-        type (cond img? "image"
-                   audio? "audio"
-                   :else "asset")]
-    (if (not sync-enabled?)
-      (content-fn)
-      (if (and asset-file? (or loading? (nil? exist?)))
-        [:p.text-sm.opacity-50 (ui/loading (util/format "Syncing %s ..." type))]
-        (if (or (not asset-file?)
-                (and exist? (not loading?)))
-          (content-fn)
-          [:p.text-error.text-xs [:small.opacity-80
-                                  (util/format "%s not found!" (string/capitalize type))]])))))
-
 (defn open-lightbox!
   [e]
   (let [images (js/document.querySelectorAll ".asset-container img")
@@ -484,8 +412,7 @@
         repo (state/get-current-repo)
         href (cond-> href
                (nil? js-url)
-               (config/get-local-asset-absolute-path))
-        db-based? (config/db-based-graph? repo)]
+               (config/get-local-asset-absolute-path))]
     (when (nil? @src)
       (-> (assets-handler/<make-asset-url href js-url)
           (p/then (fn [url]
@@ -498,7 +425,6 @@
                              (util/get-file-ext href)))
             repo (state/get-current-repo)
             repo-dir (config/get-repo-dir repo)
-            path (str repo-dir href)
             share-fn (fn [event]
                        (util/stop event)
                        (when (mobile-util/native-platform?)
@@ -511,26 +437,14 @@
         (cond
           (or (contains? config/audio-formats ext)
               (and (= ext :webm) (string/starts-with? title "Audio-")))
-          (if db-based?
-            (audio-cp @src ext)
-            (file-based-asset-loader @src #(audio-cp @src)))
+          (audio-cp @src ext)
 
           (contains? config/video-formats ext)
           [:video {:src @src
                    :controls true}]
 
           (contains? (common-config/img-formats) ext)
-          (if db-based?
-            (resizable-image config title @src metadata full_text true)
-            (file-based-asset-loader @src
-                                     #(resizable-image config title @src metadata full_text true)))
-
-          (and (not db-based?) (contains? (common-config/text-formats) ext))
-          [:a.asset-ref.is-plaintext {:href (rfe/href :file {:path path})
-                                      :on-click (fn [_event]
-                                                  (p/let [result (fs/read-file repo-dir path)]
-                                                    (db/set-file-content! repo path result)))}
-           title]
+          (resizable-image config title @src metadata full_text true)
 
           (= ext :pdf)
           [:a.asset-ref.is-pdf
@@ -541,16 +455,14 @@
             :on-click (fn [e]
                         (util/stop e)
                         (open-pdf-file e (:asset-block config) @src))}
-           (if db-based?
-             title
-             [:span [:span.opacity-70 "[[📚"] title [:span.opacity-70 "]]"]])]
+           title]
 
           (util/mobile?)
           [:a.asset-ref {:href @src
                          :on-click share-fn}
            title]
 
-          (and db-based? util/web-platform?)
+          util/web-platform?
           (let [file-name (str (:block/title (:asset-block config)) "." (name ext))]
             [:a.asset-ref
              {:href @src
@@ -559,9 +471,7 @@
 
           (and (util/electron?) (:asset-block config))
           (let [asset-block (:asset-block config)
-                file-name (if db-based?
-                            (str (:block/title asset-block) "." (name ext))
-                            href)]
+                file-name (str (:block/title asset-block) "." (name ext))]
             [:a.asset-ref
              {:on-click (fn [e]
                           (util/stop e)
@@ -579,13 +489,10 @@
   (let [metadata (if (string/blank? metadata)
                    nil
                    (common-util/safe-read-map-string metadata))
-        title (second (first label))
-        repo (state/get-current-repo)]
+        title (second (first label))]
     (ui/catch-error
      [:span.warning full_text]
-     (if (and (common-config/local-relative-asset? href)
-              (or (config/local-file-based-graph? repo)
-                  (config/db-based-graph? repo)))
+     (if (common-config/local-relative-asset? href)
        (asset-link config title href metadata full_text)
        (let [href (cond
                     (util/starts-with? href "http")
@@ -1135,9 +1042,7 @@
               (and (string? uuid-or-title) (string/ends-with? uuid-or-title ".excalidraw"))
               [:div.draw {:on-click (fn [e]
                                       (.stopPropagation e))}
-               (if (config/db-based-graph?)
-                 [:div.warning "Excalidraw is no longer supported by default, we plan to support it through plugins."]
-                 (excalidraw uuid-or-title (:block/uuid config)))]
+               [:div.warning "Excalidraw is no longer supported by default, we plan to support it through plugins."]]
 
               :else
               (let [blank-title? (string/blank? (:block/title block))]
@@ -1145,10 +1050,9 @@
                  {:data-ref (str uuid-or-title)}
                  (when (and brackets? (not blank-title?))
                    [:span.text-gray-500.bracket page-ref/left-brackets])
-                 (when (and (config/db-based-graph?)
-                            (or (ldb/class-instance? (db/entity :logseq.class/Task) block)
-                                (:logseq.property/status block)
-                                (:logseq.property/priority block)))
+                 (when (or (ldb/class-instance? (db/entity :logseq.class/Task) block)
+                           (:logseq.property/status block)
+                           (:logseq.property/priority block))
                    [:div.inline-block
                     {:style {:margin-right 1
                              :margin-top -2
@@ -1259,112 +1163,10 @@
 (declare block-content)
 (declare breadcrumb)
 
-(rum/defc block-reference-preview
-  [children {:keys [repo config id]}]
-  (let [*timer (hooks/use-ref nil)                          ;; show
-        *timer1 (hooks/use-ref nil)                         ;; hide
-        [visible? set-visible!] (rum/use-state nil)
-        _ #_:clj-kondo/ignore (rum/defc render []
-                                [:div.tippy-wrapper.as-block
-                                 {:style {:width 600
-                                          :font-weight 500
-                                          :text-align "left"}
-                                  :on-mouse-enter (fn []
-                                                    (when-let [timer1 (hooks/deref *timer1)]
-                                                      (js/clearTimeout timer1)))
-
-                                  :on-mouse-leave (fn []
-                                                    (when (ui/last-shui-preview-popup?)
-                                                      (hooks/set-ref! *timer1
-                                                                      (js/setTimeout #(set-visible! false) 500))))}
-                                 [(breadcrumb config repo id {:indent? true})
-                                  (blocks-container
-                                   (assoc config :id (str id) :preview? true)
-                                   [(db/entity [:block/uuid id])])]])]
-    (popup-preview-impl children
-                        {:visible? visible? :set-visible! set-visible!
-                         :*timer *timer :*timer1 *timer1
-                         :render render})))
-
-(rum/defc block-reference-aux < rum/reactive db-mixins/query
-  [config block label]
-  (let [db-id (:db/id block)
-        block-id (:block/uuid block)
-        block (when db-id (db/sub-block db-id))
-        block-type (keyword (pu/lookup block :logseq.property/ls-type))
-        hl-type (pu/lookup block :logseq.property.pdf/hl-type)
-        repo (state/get-current-repo)
-        stop-inner-events? (= block-type :whiteboard-shape)
-        config' (assoc config
-                       :block-ref? true
-                       :stop-events? stop-inner-events?)]
-    (if (and block (:block/title block))
-      (let [content-cp (block-content config'
-                                      block nil (:block/uuid block)
-                                      nil)
-            display-type (:logseq.property.node/display-type block)]
-        (if (and display-type (not (contains? #{:quote :math} display-type)))
-          content-cp
-          (let [title [:span.block-ref content-cp]
-                inner (cond
-                        (seq label)
-                        (->elem
-                         :span.block-ref
-                         (map-inline config label))
-                        :else
-                        title)]
-            [:div.block-ref-wrap.inline
-             {:data-type (name (or block-type :default))
-              :data-hl-type hl-type
-              :on-pointer-down
-              (fn [^js/MouseEvent e]
-                (if (util/right-click? e)
-                  (state/set-state! :block-ref/context {:block (:block config)
-                                                        :block-ref block-id})
-                  (when (and
-                         (or (gobj/get e "shiftKey")
-                             (not (.. e -target (closest ".blank"))))
-                         (not (util/right-click? e)))
-                    (util/stop e)
-
-                    (cond
-                      (gobj/get e "shiftKey")
-                      (state/sidebar-add-block!
-                       (state/get-current-repo)
-                       (:db/id block)
-                       :block-ref)
-
-                      (and (util/meta-key? e) (whiteboard-handler/inside-portal? (.-target e)))
-                      (whiteboard-handler/add-new-block-portal-shape!
-                       (:block/uuid block)
-                       (whiteboard-handler/closest-shape (.-target e)))
-
-                      :else
-                      (match [block-type (util/electron?)]
-                             ;; pdf annotation
-                        [:annotation true] (pdf-assets/open-block-ref! block)
-
-                        [:whiteboard-shape true] (route-handler/redirect-to-page!
-                                                  (get-in block [:block/page :block/uuid]) {:block-id block-id})
-
-                             ;; default open block page
-                        :else (route-handler/redirect-to-page! block-id))))))}
-
-             (if (and (not (util/mobile?))
-                      (not (:preview? config))
-                      (not (shui-dialog/has-modal?))
-                      (nil? block-type))
-               (block-reference-preview inner
-                                        {:repo repo :config config :id block-id})
-               inner)])))
-      (do
-        (log/warn :invalid-node block)
-        (invalid-node-ref block-id)))))
-
 (rum/defc block-reference
   [config id label]
   (let [block-id (and id (if (uuid? id) id (parse-uuid id)))
-        [block set-block!] (hooks/use-state (db/entity [:block/uuid block-id]))
+        [_block set-block!] (hooks/use-state (db/entity [:block/uuid block-id]))
         self-reference? (when (set? (:ref-set config))
                           (contains? (:ref-set config) block-id))]
     (hooks/use-effect!
@@ -1376,19 +1178,7 @@
          (set-block! block)))
      [])
     (when-not self-reference?
-      (cond
-        (config/db-based-graph?)
-        (page-reference config block-id label)
-
-        block
-        (let [config' (update config :ref-set (fn [s]
-                                                (let [bid (:block/uuid (:block config))]
-                                                  (if (nil? s)
-                                                    #{bid}
-                                                    (conj s bid block-id)))))]
-          (block-reference-aux config' block label))
-        :else
-        (invalid-node-ref block-id)))))
+      (page-reference config block-id label))))
 
 (defn- render-macro
   [config name arguments macro-content format]
@@ -1460,9 +1250,7 @@
 
 (rum/defc audio-link
   [config url href _label metadata full_text]
-  (if (and (common-config/local-relative-asset? href)
-           (or (config/local-file-based-graph? (state/get-current-repo))
-               (config/db-based-graph? (state/get-current-repo))))
+  (if (common-config/local-relative-asset? href)
     (asset-link config nil href metadata full_text)
     (let [href (cond
                  (util/starts-with? href "http")
@@ -1785,93 +1573,6 @@
     [:span.warning.mr-1 {:title "Empty URL"}
      (macro->text "video" arguments)]))
 
-(defn- macro-else-cp
-  [name config arguments]
-  (if-let [block-uuid (:block/uuid config)]
-    (let [format (get-in config [:block :block/format] :markdown)
-          ;; :macros is deprecated for db graphs
-          macros-from-property (when (config/local-file-based-graph? (state/get-current-repo))
-                                 (-> (db/entity [:block/uuid block-uuid])
-                                     (:block/page)
-                                     (:db/id)
-                                     (db/entity)
-                                     :block/properties
-                                     :macros
-                                     (get name)))
-          macro-content (or macros-from-property
-                            (get (state/get-macros) name)
-                            (get (state/get-macros) (keyword name)))
-          macro-content (cond
-                          (= (str name) "img")
-                          (case (count arguments)
-                            1
-                            (util/format "[:img {:src \"%s\"}]" (first arguments))
-                            4
-                            (when (and (util/safe-parse-int (nth arguments 1))
-                                       (util/safe-parse-int (nth arguments 2)))
-                              (util/format "[:img.%s {:src \"%s\" :style {:width %s :height %s}}]"
-                                           (nth arguments 3)
-                                           (first arguments)
-                                           (util/safe-parse-int (nth arguments 1))
-                                           (util/safe-parse-int (nth arguments 2))))
-                            3
-                            (when (and (util/safe-parse-int (nth arguments 1))
-                                       (util/safe-parse-int (nth arguments 2)))
-                              (util/format "[:img {:src \"%s\" :style {:width %s :height %s}}]"
-                                           (first arguments)
-                                           (util/safe-parse-int (nth arguments 1))
-                                           (util/safe-parse-int (nth arguments 2))))
-
-                            2
-                            (cond
-                              (util/safe-parse-int (nth arguments 1))
-                              (util/format "[:img {:src \"%s\" :style {:width %s}}]"
-                                           (first arguments)
-                                           (util/safe-parse-int (nth arguments 1)))
-                              (contains? #{"left" "right" "center"} (string/lower-case (nth arguments 1)))
-                              (util/format "[:img.%s {:src \"%s\"}]"
-                                           (string/lower-case (nth arguments 1))
-                                           (first arguments))
-                              :else
-                              macro-content)
-
-                            macro-content)
-
-                          (and (seq arguments) macro-content)
-                          (macro-util/macro-subs macro-content arguments)
-
-                          :else
-                          macro-content)
-          macro-content (when macro-content
-                          (template/resolve-dynamic-template! macro-content))]
-      (render-macro config name arguments macro-content format))
-    (let [macro-content (or
-                         (get (state/get-macros) name)
-                         (get (state/get-macros) (keyword name)))
-          format (get-in config [:block :block/format] :markdown)]
-      (render-macro config name arguments macro-content format))))
-
-(rum/defc namespace-hierarchy-aux
-  [config namespace children]
-  [:ul
-   (for [child children]
-     [:li {:key (str "namespace-" namespace "-" (:db/id child))}
-      (let [shorten-name (some-> (or (:block/title child) (:block/name child))
-                                 (string/split "/")
-                                 last)]
-        (page-cp {:label shorten-name} child))
-      (when (seq (:namespace/children child))
-        (namespace-hierarchy-aux config (:block/name child)
-                                 (:namespace/children child)))])])
-
-(rum/defc namespace-hierarchy
-  [config namespace children]
-  [:div.namespace
-   [:div.font-medium.flex.flex-row.items-center.pb-2
-    [:span.text-sm.mr-1 "Namespace "]
-    (page-cp config {:block/name namespace})]
-   (namespace-hierarchy-aux config namespace children)])
-
 (defn- macro-cp
   [config options]
   (let [{:keys [name arguments]} options
@@ -1884,21 +1585,13 @@
                     arguments)]
     (cond
       (= name "query")
-      (if (config/db-based-graph? (state/get-current-repo))
-        [:div.warning "{{query}} is deprecated. Use '/Query' command instead."]
-        (macro-query-cp config arguments))
+      [:div.warning "{{query}} is deprecated. Use '/Query' command instead."]
 
       (= name "function")
       (macro-function-cp config arguments)
 
       (= name "namespace")
-      (if (config/db-based-graph? (state/get-current-repo))
-        [:div.warning (str "{{namespace}} is deprecated. Use the " common-config/library-page-name " feature instead.")]
-        (let [namespace (first arguments)]
-          (when-not (string/blank? namespace)
-            (let [namespace (string/lower-case (page-ref/get-page-name! namespace))
-                  children (file-model/get-namespace-hierarchy (state/get-current-repo) namespace)]
-              (namespace-hierarchy config namespace children)))))
+      [:div.warning (str "{{namespace}} is deprecated. Use the " common-config/library-page-name " feature instead.")]
 
       (= name "youtube")
       (when-let [url (first arguments)]
@@ -1945,9 +1638,7 @@
               (ui/tweet-embed id)))))
 
       (= name "embed")
-      (if (config/db-based-graph? (state/get-current-repo))
-        [:div.warning "{{embed}} is deprecated. Use '/Node embed' command instead."]
-        (macro-embed-cp config arguments))
+      [:div.warning "{{embed}} is deprecated. Use '/Node embed' command instead."]
 
       (= name "renderer")
       (when config/lsp-enabled?
@@ -1958,7 +1649,7 @@
       ((get @macro/macros name) config options)
 
       :else
-      (macro-else-cp name config arguments))))
+      nil)))
 
 (defn- emphasis-cp
   [config kind data]
@@ -1994,11 +1685,9 @@
     ["Tag" _]
     (when-let [s (gp-block/get-tag item)]
       (let [s (text/page-ref-un-brackets! s)]
-        (if (config/db-based-graph?)
-          (if (common-util/uuid-string? s)
-            (page-cp (assoc config :tag? true) {:block/name s})
-            [:span (str "#" s)])
-          (page-cp (assoc config :tag? true) {:block/name s}))))
+        (if (common-util/uuid-string? s)
+          (page-cp (assoc config :tag? true) {:block/name s})
+          [:span (str "#" s)])))
 
     ["Emphasis" [[kind] data]]
     (emphasis-cp config kind data)
@@ -2276,7 +1965,7 @@
 
                        :else
                        bullet)]
-         (if (and (config/db-based-graph?) (not @*bullet-dragging?))
+         (when-not @*bullet-dragging?
            (ui/tooltip
             bullet'
             [:div.flex.flex-col.gap-1.p-2
@@ -2284,8 +1973,7 @@
                                         (:logseq.property/created-by-ref block))]
                [:div (:block/title created-by)])
              [:div "Created: " (date/int->local-time-2 (:block/created-at block))]
-             [:div "Last edited: " (date/int->local-time-2 (:block/updated-at block))]])
-           bullet')))]))
+             [:div "Last edited: " (date/int->local-time-2 (:block/updated-at block))]]))))]))
 
 (rum/defc dnd-separator
   [move-to]
@@ -2316,12 +2004,9 @@
 
 (rum/defc ^:large-vars/cleanup-todo text-block-title
   [config block]
-  (let [db-based? (config/db-based-graph? (state/get-current-repo))
-        format (if db-based? :markdown (or (:block/format block) :markdown))
-        pre-block? (if db-based? false (:block/pre-block? block))
-        marker (when-not db-based? (:block/marker block))
+  (let [format :markdown
         block (if-not (:block.temp/ast-title block)
-                (merge block (block/parse-title-and-body uuid format pre-block?
+                (merge block (block/parse-title-and-body uuid format false
                                                          (:block/title block)))
                 block)
         block-ast-title (:block.temp/ast-title block)
@@ -2329,7 +2014,6 @@
         level (:level config)
         block-ref? (:block-ref? config)
         block-type (or (keyword (pu/lookup block :logseq.property/ls-type)) :default)
-        html-export? (:html-export? config)
         ;; `heading-level` is for backward compatibility, will remove it in later releases
         heading-level (:block/heading-level block)
         heading (or
@@ -2345,18 +2029,13 @@
     (->elem
      elem
      (merge
-      {:data-hl-type (pu/lookup block :logseq.property.pdf/hl-type)}
-      (when (and marker
-                 (not (string/blank? marker))
-                 (not= "nil" marker))
-        {:data-marker (str (string/lower-case marker))}))
+      {:data-hl-type (pu/lookup block :logseq.property.pdf/hl-type)})
 
      ;; children
      (let [area? (= :area (keyword (pu/lookup block :logseq.property.pdf/hl-type)))
            hl-ref #(when (not (#{:default :whiteboard-shape} block-type))
                      [:div.prefix-link
-                      {:class (when (and (not db-based?) area?) "as-block")
-                       :on-pointer-down
+                      {:on-pointer-down
                        (fn [^js e]
                          (let [^js target (.-target e)]
                            (case block-type
@@ -2381,16 +2060,6 @@
                         (pdf-assets/area-display block))])]
        (remove-nils
         (concat
-         (when (config/local-file-based-graph? (state/get-current-repo))
-           [(when (and (not pre-block?)
-                       (not html-export?))
-              (file-block/block-checkbox block (str "mr-1 cursor")))
-            (when (and (not pre-block?)
-                       (not html-export?))
-              (file-block/marker-switch block))
-            (file-block/marker-cp block)
-            (file-block/priority-cp block)])
-
          ;; highlight ref block (inline)
          [(hl-ref)]
 
@@ -2500,81 +2169,6 @@
       (block-title-aux config block {:query? query?
                                      :*show-query? *show-query?}))))
 
-(rum/defc span-comma
-  []
-  [:span ", "])
-
-(rum/defc property-cp
-  [config block k value]
-  (let [date (and (= k :date) (date/get-locale-string (str value)))
-        user-config (state/get-config)
-        ;; When value is a set of refs, display full property text
-        ;; because :block/properties value only contains refs but user wants to see text
-        property-separated-by-commas? (text/separated-by-commas? (state/get-config) k)
-        v (or
-           (when (and (coll? value) (seq value)
-                      (not property-separated-by-commas?))
-             (get (:block/properties-text-values block) k))
-           value)
-        property-pages-enabled? (contains? #{true nil} (:property-pages/enabled? user-config))]
-    [:div
-     (if property-pages-enabled?
-       (if (and (not (config/db-based-graph? (state/get-current-repo)))
-                (nil? (db/get-page (name k))))
-         [:span.page-property-key.font-medium (name k)]
-         (page-cp (assoc config :property? true) {:block/name (subs (str k) 1)}))
-       [:span.page-property-key.font-medium (name k)])
-     [:span.mr-1 ":"]
-     [:div.page-property-value.inline
-      (cond
-        (int? v)
-        v
-
-        (= k :file-path)
-        v
-
-        date
-        date
-
-        (and (string? v) (common-util/wrapped-by-quotes? v))
-        (common-util/unquote-string v)
-
-        (and property-separated-by-commas? (coll? v))
-        (let [v (->> (remove string/blank? v)
-                     (filter string?))
-              vals (for [v-item v]
-                     (page-cp config {:block/name v-item}))
-              elems (interpose (span-comma) vals)]
-          (for [elem elems]
-            (rum/with-key elem (str (random-uuid)))))
-
-        :else
-        (inline-text config (get block :block/format :markdown) (str v)))]]))
-
-(rum/defc properties-cp
-  [config {:block/keys [pre-block?] :as block}]
-  (let [ordered-properties
-        (property-util/get-visible-ordered-properties (:block/properties block)
-                                                      (:block/properties-order block)
-                                                      {:pre-block? pre-block?
-                                                       :page-id (:db/id (:block/page block))})]
-    (cond
-      (seq ordered-properties)
-      [:div.block-properties.rounded
-       {:class (when pre-block? "page-properties")
-        :title (if pre-block?
-                 "Click to edit this page's properties"
-                 "Click to edit this block's properties")}
-       (for [[k v] ordered-properties]
-         (rum/with-key (property-cp config block k v)
-           (str (:block/uuid block) "-" k)))]
-
-      (and pre-block? ordered-properties)
-      [:span.opacity-50 "Properties"]
-
-      :else
-      nil)))
-
 (rum/defcs db-properties-cp < rum/static
   {:init (fn [state]
            (let [container-id (or (:container-id (first (:rum/args state)))
@@ -2592,16 +2186,6 @@
                                                           (::initial-container-id state))}
                                        opts)))
 
-(rum/defc invalid-properties-cp
-  [invalid-properties]
-  (when (seq invalid-properties)
-    [:div.invalid-properties.mb-2
-     [:div.warning {:title "Invalid properties"}
-      "Invalid property names: "
-      (for [p invalid-properties]
-        [:button.p-1.mr-2 p])]
-     [:code "Property name begins with a non-numeric character and can contain alphanumeric characters and . * + ! - _ ? $ % & = < >. If -, + or . are the first character, the second character (if any) must be non-numeric."]]))
-
 (defn- target-forbidden-edit?
   [target]
   (or
@@ -2693,12 +2277,7 @@
                    (let [cursor-range (if mobile? mobile-range (get-cursor-range))
                          block (db/entity (:db/id block))
                          {:block/keys [title format]} block
-                         content (if (config/db-based-graph? (state/get-current-repo))
-                                   (:block/title block)
-                                   (->> title
-                                        (property-file/remove-built-in-properties-when-file-based
-                                         (state/get-current-repo) format)
-                                        (drawer/remove-logbook)))]
+                         content (:block/title block)]
 
                      (state/set-editing!
                       edit-input-id
@@ -2941,18 +2520,12 @@
 (rum/defc ^:large-vars/cleanup-todo block-content < rum/reactive
   [config {:block/keys [uuid] :as block} edit-input-id block-id *show-query?]
   (let [repo (state/get-current-repo)
-        db-based? (config/db-based-graph? (state/get-current-repo))
-        scheduled (when-not db-based? (:block/scheduled block))
-        deadline (when-not db-based? (:block/deadline block))
-        format (if db-based? :markdown (or (:block/format block) :markdown))
-        pre-block? (when-not db-based? (:block/pre-block? block))
+        format :markdown
         collapsed? (:collapsed? config)
-        content (if db-based?
-                  (:block/raw-title block)
-                  (property-util/remove-built-in-properties format (:block/raw-title block)))
+        content (:block/raw-title block)
         content (if (string? content) (string/trim content) "")
         block-ref? (:block-ref? config)
-        block (merge block (block/parse-title-and-body uuid format pre-block? content))
+        block (merge block (block/parse-title-and-body uuid format false content))
         ast-body (:block.temp/ast-body block)
         ast-title (:block.temp/ast-title block)
         block (assoc block :block/title content)
@@ -3021,30 +2594,7 @@
          [:div.block-head-wrap
           (block-title config block {:*show-query? *show-query?})])
 
-       (if db-based?
-         (task-spent-time-cp block)
-         (file-block/clock-summary-cp block ast-body))]
-
-      (when deadline
-        (when-let [deadline-ast (block-handler/get-deadline-ast block)]
-          (file-block/timestamp-cp block "DEADLINE" deadline-ast)))
-
-      (when scheduled
-        (when-let [scheduled-ast (block-handler/get-scheduled-ast block)]
-          (file-block/timestamp-cp block "SCHEDULED" scheduled-ast)))
-
-      (when-not (config/db-based-graph? repo)
-        (when-let [invalid-properties (:block/invalid-properties block)]
-          (invalid-properties-cp invalid-properties)))
-
-      (when (and (not (config/db-based-graph? repo))
-                 (seq (:block/properties block))
-                 (let [hidden? (property-file/properties-hidden? (:block/properties block))]
-                   (not hidden?))
-                 (not (and block-ref? (or (seq ast-title) (seq ast-body))))
-                 (not= block-type :whiteboard-shape)
-                 (not (:table-block-title? config)))
-        (properties-cp config block))
+       (task-spent-time-cp block)]
 
       (block-content-inner config block ast-body plugin-slotted? collapsed? block-ref-with-title?)
 
@@ -3109,9 +2659,7 @@
 
 (rum/defcs ^:large-vars/cleanup-todo block-content-or-editor < rum/reactive
   [state config {:block/keys [uuid] :as block} {:keys [edit-input-id block-id edit? hide-block-refs-count? refs-count *hide-block-refs? *show-query?]}]
-  (let [format (if (config/db-based-graph? (state/get-current-repo))
-                 :markdown
-                 (or (:block/format block) :markdown))
+  (let [format :markdown
         editor-box (state/get-component :editor/box)
         editor-id (str "editor-" edit-input-id)
         block-reference-only? (some->
@@ -3120,7 +2668,6 @@
                                block-ref/block-ref?)
         named? (some? (:block/name block))
         repo (state/get-current-repo)
-        db-based? (config/db-based-graph? repo)
         table? (:table? config)
         raw-mode-block (state/sub :editor/raw-mode-block)
         type-block-editor? (and (contains? #{:code} (:logseq.property.node/display-type block))
@@ -3139,7 +2686,9 @@
                                        bg-color)
                    :color (when-not built-in-color? "white")}})))
 
-     (when (and db-based? (not table?)) (block-positioned-properties config block :block-left))
+     (when-not table?
+       (block-positioned-properties config block :block-left))
+
      [:div.block-content-or-editor-inner
       [:div.block-row.flex.flex-1.flex-row.gap-1.items-center
        (let [block-content-f (fn block-content-f
@@ -3204,13 +2753,13 @@
 
        (when-not (:table-block-title? config)
          [:div.ls-block-right.flex.flex-row.items-center.self-start.gap-1
-          (when (and db-based? (not table?))
+          (when-not table?
             [:div.opacity-70.hover:opacity-100
              (block-positioned-properties config block :block-right)])
 
           (when-not (or (:block-ref? config) (:table? config) (:gallery-view? config)
                         (:property? config))
-            (when (and db-based? (seq (:block/tags block)))
+            (when (seq (:block/tags block))
               (tags-cp (assoc config :block/uuid (:block/uuid block)) block)))])]]]))
 
 (rum/defcs single-block-cp < mixins/container-id
@@ -3407,8 +2956,7 @@
             (state/set-state! :mobile/show-action-bar? false)
             (state/clear-selection!)))
         ;; handle DataTransfer
-        (let [repo (state/get-current-repo)
-              data-transfer (.-dataTransfer event)
+        (let [data-transfer (.-dataTransfer event)
               transfer-types (set (js->clj (.-types data-transfer)))]
           (cond
             (contains? transfer-types "text/plain")
@@ -3420,35 +2968,6 @@
                 :sibling? (= @*move-to' :sibling)
                 :before? (= @*move-to' :top)}))
 
-            (contains? transfer-types "Files")
-            (let [files (.-files data-transfer)
-                  format (get target-block :block/format :markdown)]
-              ;; When editing, this event will be handled by editor-handler/upload-asset(editor-on-paste)
-              (when (and (config/local-file-based-graph? repo) (not (state/editing?)))
-                ;; Basically the same logic as editor-handler/upload-asset,
-                ;; does not require edting
-                (-> (file-editor-handler/file-based-save-assets! repo (js->clj files))
-                    (p/then
-                     (fn [res]
-                       (when-let [[asset-file-name file-obj asset-file-fpath matched-alias] (first res)]
-                         (let [image? (config/ext-of-image? asset-file-name)
-                               link-content (assets-handler/get-asset-file-link format
-                                                                                (if matched-alias
-                                                                                  (str
-                                                                                   (if image? "../assets/" "")
-                                                                                   "@" (:name matched-alias) "/" asset-file-name)
-                                                                                  (file-editor-handler/resolve-relative-path (or asset-file-fpath asset-file-name)))
-                                                                                (if file-obj (.-name file-obj) (if image? "image" "asset"))
-                                                                                image?)]
-                           (editor-handler/api-insert-new-block!
-                            link-content
-                            {:block-uuid uuid
-                             :edit-block? false
-                             :replace-empty-target? true
-                             :sibling? true
-                             :before? false}))
-                         (recur (rest res))))))))
-
             :else
             (prn ::unhandled-drop-data-transfer-type transfer-types)))))
     (block-drag-end event *move-to')))
@@ -3629,7 +3148,6 @@
         own-number-list? (:own-order-number-list? config)
         order-list? (boolean own-number-list?)
         children (ldb/get-children block)
-        db-based? (config/db-based-graph? repo)
         page-icon (when (:page-title? config)
                     (let [icon' (get block (pu/get-pid :logseq.property/icon))]
                       (when-let [icon (and (ldb/page? block)
@@ -3742,7 +3260,7 @@
 
      (when-not (:hide-title? config)
        [:div.block-main-container.flex.flex-row.gap-1
-        {:style (when (and db-based? (:page-title? config))
+        {:style (when (:page-title? config)
                   {:margin-left (cond
                                   (util/mobile?) 0
                                   page-icon -36
@@ -3790,11 +3308,10 @@
                                          :hide-block-refs-count? hide-block-refs-count?
                                          :*show-query? *show-query?}))])]
 
-         (when (and db-based? (not collapsed?) (not (or table? property?)))
+         (when (and (not collapsed?) (not (or table? property?)))
            (block-positioned-properties config block :block-below))]])
 
-     (when (and db-based?
-                (not (:library? config))
+     (when (and (not (:library? config))
                 (or (:tag-dialog? config)
                     (and
                      (not collapsed?)
@@ -3802,7 +3319,7 @@
        [:div (when-not (:page-title? config) {:style {:padding-left (if (util/mobile?) 12 45)}})
         (db-properties-cp config block {:in-block-container? true})])
 
-     (when (and db-based? show-query? (not (:table? config)))
+     (when (and show-query? (not (:table? config)))
        (let [query? (ldb/class-instance? (entity-plus/entity-memoized (db/get-db) :logseq.class/Query) block)
              query (:logseq.property/query block)
              advanced-query? (and query? (= :code (:logseq.property.node/display-type query)))]
@@ -3821,7 +3338,7 @@
          [:div.px-4.py-2.border.rounded.my-2.shadow-xs {:style {:margin-left 42}}
           (refs-cp block {})]))
 
-     (when (and db-based? (not collapsed?) (not (or table? property?))
+     (when (and (not collapsed?) (not (or table? property?))
                 (ldb/class-instance? (entity-plus/entity-memoized (db/get-db) :logseq.class/Query) block))
        (let [query-block (:logseq.property/query (db/entity (:db/id block)))
              query-block (if query-block (db/sub-block (:db/id query-block)) query-block)

+ 0 - 241
src/main/frontend/components/encryption.cljs

@@ -1,241 +0,0 @@
-(ns frontend.components.encryption
-  (:require [clojure.string :as string]
-            [frontend.context.i18n :refer [t]]
-            [frontend.handler.notification :as notification]
-            [frontend.fs.sync :as sync]
-            [frontend.state :as state]
-            [frontend.ui :as ui]
-            [frontend.util :as util]
-            [frontend.config :as config]
-            [cljs.core.async :as async]
-            [rum.core :as rum]))
-
-(rum/defc show-password-cp
-  [*show-password?]
-  [:div.flex.flex-row.items-center
-   [:label.px-1 {:for "show-password"}
-    (ui/checkbox {:value @*show-password?
-                  :on-change (fn [e]
-                               (reset! *show-password? (util/echecked? e)))
-                  :id        "show-password"})
-    [:span.text-sm.ml-1.opacity-80.select-none.px-1 "Show password"]]])
-
-(rum/defcs ^:large-vars/cleanup-todo input-password-inner < rum/reactive
-  (rum/local "" ::password)
-  (rum/local "" ::pw-confirm)
-  (rum/local false ::pw-confirm-focused?)
-  (rum/local false ::show-password?)
-  {:will-mount (fn [state]
-                 ;; try to close tour tips
-                 (some->> (state/sub :file-sync/jstour-inst)
-                          (.complete))
-                 state)}
-  [state repo-url close-fn {:keys [type GraphName GraphUUID init-graph-keys after-input-password]}]
-  (let [*password (get state ::password)
-        *pw-confirm (get state ::pw-confirm)
-        *pw-confirm-focused? (get state ::pw-confirm-focused?)
-        *show-password? (get state ::show-password?)
-        *input-ref-0 (rum/create-ref)
-        *input-ref-1 (rum/create-ref)
-        remote-pw? (= type :input-pwd-remote)
-        loading? (state/sub [:ui/loading? :set-graph-password])
-        pw-strength (when (and init-graph-keys
-                               (not (string/blank? @*password)))
-                      (util/check-password-strength @*password))
-        can-submit? #(if init-graph-keys
-                       (and (>= (count @*password) 6)
-                            (>= (:id pw-strength) 1))
-                       true)
-        set-remote-graph-pwd-result (state/sub [:file-sync/set-remote-graph-password-result])
-
-        submit-handler
-        (fn []
-          (let [value @*password]
-            (cond
-              (string/blank? value)
-              nil
-
-              (and init-graph-keys (not= @*password @*pw-confirm))
-              (notification/show! "The passwords are not matched." :error)
-
-              :else
-              (case type
-                (:create-pwd-remote :input-pwd-remote)
-                (do
-                  (state/set-state! [:ui/loading? :set-graph-password] true)
-                  (state/set-state! [:file-sync/set-remote-graph-password-result] {})
-                  (async/go
-                    (let [persist-r (async/<! (sync/encrypt+persist-pwd! @*password GraphUUID))]
-                      (if (instance? js/Error persist-r)
-                        (js/console.error persist-r)
-                        (when (fn? after-input-password)
-                          (after-input-password @*password)
-                          ;; TODO: it's better if based on sync state
-                          (when init-graph-keys
-                            (js/setTimeout #(state/pub-event! [:file-sync/maybe-onboarding-show :sync-learn]) 10000)))))))))))
-
-        cancel-handler
-        (fn []
-          (state/set-state! [:file-sync/set-remote-graph-password-result] {})
-          (close-fn))
-
-        enter-handler
-        (fn [^js e]
-          (when-let [^js input (and e (= 13 (.-which e)) (.-target e))]
-            (when-not (string/blank? (.-value input))
-              (let [input-0? (= (util/safe-lower-case (.-placeholder input)) "password")]
-                (if init-graph-keys
-                  ;; setup mode
-                  (if input-0?
-                    (.select (rum/deref *input-ref-1))
-                    (submit-handler))
-
-                  ;; unlock mode
-                  (submit-handler))))))]
-
-    [:div.encryption-password.max-w-2xl.-mb-2
-     [:div.cp__file-sync-related-normal-modal
-      [:div.flex.justify-center.pb-4 [:span.icon-wrap (ui/icon "lock-access" {:size 28})]]
-
-      [:div.mt-3.text-center.sm:mt-0.sm:text-left
-       [:h1#modal-headline.text-2xl.font-bold.text-center
-        (if init-graph-keys
-          (if remote-pw?
-            "Secure graph!"
-            "Encrypt graph")
-          (if remote-pw?
-            "Unlock graph!"
-            "Decrypt graph"))]]
-
-      ;; decrypt remote graph with one password
-      (when (and remote-pw? (not init-graph-keys))
-        [:<>
-
-         [:div.folder-tip.flex.flex-col.items-center
-          [:h3
-           [:span.flex.space-x-2.leading-none.pb-1
-            (ui/icon "cloud-lock" {:size 20})
-            [:span GraphName]
-            [:span.scale-75 (ui/icon "arrow-right")]
-            [:span (ui/icon "folder")]]]
-          [:h4.px-2.-mb-1.5 (config/get-string-repo-dir repo-url)]]
-
-         [:div.input-hints.text-sm.py-2.px-3.rounded.mb-2.mt-2.flex.items-center
-          (if-let [display-str (:fail set-remote-graph-pwd-result)]
-            [:<>
-             [:span.flex.pr-1.text-error (ui/icon "alert-circle" {:class "text-md mr-1"})]
-             [:span.text-error display-str]]
-            [:<>
-             [:span.flex.pr-1 (ui/icon "bulb" {:class "text-md mr-1"})]
-             [:span "Please enter the password for this graph to continue syncing."]])]])
-
-      ;; secure this remote graph
-      (when (and remote-pw? init-graph-keys)
-        (let [pattern-ok? #(>= (count @*password) 6)]
-          [:<>
-           [:h2.text-center.opacity-70.text-sm.py-2
-            "Each graph you want to synchronize via Logseq needs its own password for end-to-end encryption."]
-           [:div.input-hints.text-sm.py-2.px-3.rounded.mb-3.mt-4.flex.items-center
-            (if (or (not (string/blank? @*password))
-                    (not (string/blank? @*pw-confirm)))
-              (if (or (not (pattern-ok?))
-                      (not= @*password @*pw-confirm))
-                [:span.flex.pr-1.text-error (ui/icon "alert-circle" {:class "text-md mr-1"})]
-                [:span.flex.pr-1.text-success (ui/icon "circle-check" {:class "text-md mr-1"})])
-              [:span.flex.pr-1 (ui/icon "bulb" {:class "text-md mr-1"})])
-
-            (if (not (string/blank? @*password))
-              (if-not (pattern-ok?)
-                [:span "Password can't be less than 6 characters"]
-                (if (not (string/blank? @*pw-confirm))
-                  (if (not= @*pw-confirm @*password)
-                    [:span "Password fields are not matching!"]
-                    [:span "Password fields are matching!"])
-                  [:span "Enter your chosen password again!"]))
-              [:span "Choose a strong and hard to guess password!"])
-            ]
-
-           ;; password strength checker
-           (when-not (string/blank? @*password)
-             [:<>
-              [:div.input-hints.text-sm.py-2.px-3.rounded.mb-2.-mt-1.5.flex.items-center.sm:space-x-3.strength-wrap
-               (let [included-set (set (:contains pw-strength))]
-                 (for [i ["lowercase" "uppercase" "number" "symbol"]
-                       :let [included? (contains? included-set i)]]
-                   [:span.strength-item
-                    {:key i
-                     :class (when included? "included")}
-                    (ui/icon (if included? "check" "x") {:class "mr-1"})
-                    [:span.capitalize i]
-                    ]))]
-
-              [:div.input-pw-strength
-               [:div.indicator.flex
-                (for [i (range 4)
-                      :let [title (get ["Too weak" "Weak" "Medium" "Strong"] i)]]
-                  [:i {:key i
-                       :title title
-                       :class (when (>= (int (:id pw-strength)) i) "active")} i])]]])]))
-
-      [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2
-       {:type        (if @*show-password? "text" "password")
-        :ref         *input-ref-0
-        :placeholder "Password"
-        :auto-focus  true
-        :disabled    loading?
-        :on-key-up   enter-handler
-        :on-change   (fn [^js e]
-                       (reset! *password (util/evalue e))
-                       (when (:fail set-remote-graph-pwd-result)
-                         (state/set-state! [:file-sync/set-remote-graph-password-result] {})))}]
-
-      (when init-graph-keys
-        [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2
-         {:type        (if @*show-password? "text" "password")
-          :ref         *input-ref-1
-          :placeholder "Re-enter the password"
-          :on-focus    #(reset! *pw-confirm-focused? true)
-          :on-blur     #(reset! *pw-confirm-focused? false)
-          :disabled    loading?
-          :on-key-up   enter-handler
-          :on-change   (fn [^js e]
-                         (reset! *pw-confirm (util/evalue e)))}])
-
-      (show-password-cp *show-password?)
-
-      (when init-graph-keys
-        [:div.init-remote-pw-tips.space-x-4.pt-2.hidden.sm:flex
-         [:div.flex-1.flex.items-center
-          [:span.px-3.flex (ui/icon "key")]
-          [:p.dark:text-gray-100
-           [:span "Please make sure you "]
-           "remember the password you have set, as we are unable to reset or retrieve it in case you forget it, "
-           [:span "and we recommend you "]
-           "keep a secure backup "
-           [:span "of the password."]]]
-
-         [:div.flex-1.flex.items-center
-          [:span.px-3.flex (ui/icon "lock")]
-          [:p.dark:text-gray-100
-           "If you lose your password, all of your data in the cloud can’t be decrypted. "
-           [:span "You will still be able to access the local version of your graph."]]]])]
-
-     [:div.mt-5.sm:mt-4.flex.justify-center.sm:justify-end.space-x-3
-      (ui/button (t :cancel) :background "gray" :disabled loading? :class "opacity-60" :on-click cancel-handler)
-      (ui/button [:span.inline-flex.items-center.leading-none
-                  [:span (t :submit)]
-                  (when loading?
-                    [:span.ml-1 (ui/loading "" {:class "w-4 h-4"})])]
-
-                 :disabled (or (not (can-submit?)) loading?)
-                 :on-click submit-handler)]]))
-
-(defn input-password
-  ([repo-url close-fn] (input-password repo-url close-fn {:type :local}))
-  ([repo-url close-fn opts]
-   (fn [close-fn']
-     (let [close-fn' (if (fn? close-fn)
-                       #(do (close-fn %)
-                            (close-fn'))
-                       close-fn')]
-       (input-password-inner repo-url close-fn' opts)))))

+ 0 - 152
src/main/frontend/components/file_based/block.cljs

@@ -1,152 +0,0 @@
-(ns frontend.components.file-based.block
-  (:require [clojure.string :as string]
-            [frontend.commands :as commands]
-            [frontend.components.file-based.datetime :as datetime-comp]
-            [frontend.handler.editor :as editor-handler]
-            [frontend.handler.file-based.repeated :as repeated]
-            [frontend.state :as state]
-            [frontend.ui :as ui]
-            [frontend.util :as util]
-            [frontend.util.file-based.clock :as clock]
-            [frontend.util.file-based.drawer :as drawer]
-            [logseq.shui.hooks :as hooks]
-            [logseq.shui.ui :as shui]
-            [reitit.frontend.easy :as rfe]
-            [rum.core :as rum]))
-
-(defn marker-switch
-  [{:block/keys [marker] :as block}]
-  (when (contains? #{"NOW" "LATER" "TODO" "DOING"} marker)
-    (let [set-marker-fn (fn [new-marker]
-                          (fn [e]
-                            (util/stop e)
-                            (editor-handler/set-marker block new-marker)))
-          next-marker (case marker
-                        "NOW" "LATER"
-                        "LATER" "NOW"
-                        "TODO" "DOING"
-                        "DOING" "TODO")]
-      [:a
-       {:class (str "marker-switch block-marker " marker)
-        :title (util/format "Change from %s to %s" marker next-marker)
-        :on-pointer-down (set-marker-fn next-marker)}
-       marker])))
-
-(defn block-checkbox
-  [block class]
-  (let [marker (:block/marker block)
-        [class checked?] (cond
-                           (nil? marker)
-                           nil
-                           (contains? #{"NOW" "LATER" "DOING" "IN-PROGRESS" "TODO" "WAIT" "WAITING"} marker)
-                           [class false]
-                           (= "DONE" marker)
-                           [(str class " checked") true])]
-    (when class
-      (ui/checkbox {:class class
-                    :style {:margin-right 5}
-                    :checked checked?
-                    :on-pointer-down (fn [e]
-                                       (util/stop-propagation e))
-                    :on-change (fn [_e]
-                                 (if checked?
-                                   (editor-handler/uncheck block)
-                                   (editor-handler/check block)))}))))
-
-(defn marker-cp
-  [{:block/keys [pre-block? marker] :as _block}]
-  (when-not pre-block?
-    (when (contains? #{"IN-PROGRESS" "WAIT" "WAITING"} marker)
-      [:span {:class (str "task-status block-marker " (string/lower-case marker))
-              :style {:margin-right 3.5}}
-       (string/upper-case marker)])))
-
-(rum/defc set-priority
-  [block priority]
-  [:div
-   (let [priorities (sort (remove #(= priority %) ["A" "B" "C"]))]
-     (for [p priorities]
-       [:a.mr-2.text-base.tooltip-priority {:key (str (random-uuid))
-                                            :priority p
-                                            :on-click (fn [] (editor-handler/set-priority block p))}]))])
-
-(rum/defc priority-text
-  [priority]
-  [:a.opacity-50.hover:opacity-100
-   {:class "priority"
-    :href (rfe/href :page {:name priority})
-    :style {:margin-right 3.5}}
-   (util/format "[#%s]" (str priority))])
-
-(defn priority-cp
-  [{:block/keys [pre-block? priority] :as block}]
-  (when (and (not pre-block?) priority)
-    (ui/tooltip
-     (priority-text priority)
-     (set-priority block priority))))
-
-(defn clock-summary-cp
-  [block body]
-  (when (and (state/enable-timetracking?)
-             (or (= (:block/marker block) "DONE")
-                 (contains? #{"TODO" "LATER"} (:block/marker block))))
-    (let [summary (clock/clock-summary body true)]
-      (when (and summary
-                 (not= summary "0m")
-                 (not (string/blank? summary)))
-        [:div {:style {:max-width 100}}
-         (ui/tooltip
-          [:div.text-sm.time-spent.ml-1 {:style {:padding-top 3}}
-           [:a.fade-link
-            summary]]
-
-          (when-let [logbook (drawer/get-logbook body)]
-            (let [clocks (->> (last logbook)
-                              (filter #(string/starts-with? % "CLOCK:"))
-                              (remove string/blank?))]
-              [:div.p-4
-               [:div.font-bold.mb-2 "LOGBOOK:"]
-               [:ul
-                (for [clock (take 10 (reverse clocks))]
-                  [:li clock])]])))]))))
-
-(rum/defc timestamp-editor
-  [ast *show-datapicker?]
-  (let [*trigger-ref (rum/use-ref nil)]
-    (hooks/use-effect!
-     (fn []
-       (let [pid (shui/popup-show!
-                  (.closest (rum/deref *trigger-ref) "a")
-                  (datetime-comp/date-picker nil nil (repeated/timestamp->map ast))
-                  {:id :timestamp-editor
-                   :align :start
-                   :root-props {:onOpenChange #(reset! *show-datapicker? %)}
-                   :content-props {:onEscapeKeyDown #(reset! *show-datapicker? false)}})]
-         #(do (shui/popup-hide! pid)
-              (reset! *show-datapicker? false))))
-     [])
-    [:i {:ref *trigger-ref}]))
-
-(rum/defcs timestamp-cp
-  < rum/reactive
-  (rum/local false ::show-datepicker?)
-  [state block typ ast]
-  (let [ts-block-id (get-in (state/sub [:editor/set-timestamp-block]) [:block :block/uuid])
-        _active? (= (get block :block/uuid) ts-block-id)
-        *show-datapicker? (get state ::show-datepicker?)]
-    [:div.flex.flex-col.gap-4.timestamp
-     [:div.text-sm.flex.flex-row
-      [:div.opacity-50.font-medium.timestamp-label
-       (str typ ": ")]
-      [:a.opacity-80.hover:opacity-100
-       {:on-pointer-down (fn [e]
-                           (util/stop e)
-                           (state/clear-editor-action!)
-                           (editor-handler/escape-editing)
-                           (reset! *show-datapicker? true)
-                           (reset! commands/*current-command typ)
-                           (state/set-timestamp-block! {:block block
-                                                        :typ typ}))}
-       [:span.time-start "<"] [:time (repeated/timestamp->text ast)] [:span.time-stop ">"]
-       (when (and _active? @*show-datapicker?)
-         (timestamp-editor ast *show-datapicker?))]]]))

+ 0 - 82
src/main/frontend/components/file_based/git.cljs

@@ -1,82 +0,0 @@
-(ns frontend.components.file-based.git
-  (:require [clojure.string :as string]
-            [frontend.handler.file-based.file :as file-handler]
-            [frontend.handler.shell :as shell]
-            [frontend.state :as state]
-            [frontend.ui :as ui]
-            [frontend.util :as util]
-            [logseq.shui.hooks :as hooks]
-            [promesa.core :as p]
-            [rum.core :as rum]))
-
-(rum/defcs set-git-username-and-email <
-  (rum/local "" ::username)
-  (rum/local "" ::email)
-  [state]
-  (let [username (get state ::username)
-        email (get state ::email)]
-    [:div.container
-     [:div.text-lg.mb-4 "Git requires to setup your username and email address to commit, both of them will be stored locally."]
-     [:div.sm:flex.sm:items-start
-      [:div.mt-3.text-center.sm:mt-0.sm:text-left
-       [:h3#modal-headline.leading-6.font-medium
-        "Your username:"]]]
-
-     [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2.mb-4
-      {:auto-focus true
-       :on-change (fn [e]
-                    (reset! username (util/evalue e)))}]
-
-     [:div.sm:flex.sm:items-start
-      [:div.mt-3.text-center.sm:mt-0.sm:text-left
-       [:h3#modal-headline.leading-6.font-medium
-        "Your email address:"]]]
-
-     [:input.form-input.block.w-full.sm:text-sm.sm:leading-5.my-2
-      {:on-change (fn [e]
-                    (reset! email (util/evalue e)))}]
-
-     [:div.mt-5.sm:mt-4.flex
-      (ui/button
-       "Submit"
-       {:on-click (fn []
-                    (let [username @username
-                          email @email]
-                      (when (and (not (string/blank? username))
-                                 (not (string/blank? email)))
-                        (shell/set-git-username-and-email username email))))})]]))
-
-(rum/defc file-version-selector
-  [versions path get-content]
-  (let [[content set-content!] (rum/use-state  nil)
-        [hash  set-hash!] (rum/use-state "HEAD")]
-    (hooks/use-effect!
-     (fn []
-       (p/let [c (get-content hash path)]
-         (set-content! c)))
-     [hash path])
-    [:div.flex.overflow-y-auto {:class "max-h-[calc(85vh_-_4rem)]"}
-     [:div.overflow-y-auto {:class "w-48 max-h-[calc(85vh_-_4rem)] "}
-      [:div.font-bold "File history - " path]
-      (for [line  versions]
-        (let [[hash title time] (string/split line "$$$")
-              hash (subs hash 8)]
-          [:div.my-4 {:key hash}
-           [:hr]
-           [:div.mb-2
-            [:a.font-medium.mr-1.block
-             {:on-click (fn []  (set-hash!  hash))}
-             hash]
-            title]
-           [:div.opacity-50.text-sm time]]))]
-     [:div.flex-1.p-4
-      [:div.w-full.sm:max-w-lg {:style {:width 700}}
-       [:div.font-bold.mb-4 (str path (util/format " (%s)" hash))]
-       [:pre content]
-       (ui/button "Revert"
-                  :on-click (fn []
-                              (file-handler/alter-file (state/get-current-repo)
-                                                       path
-                                                       content
-                                                       {:re-render-root? true
-                                                        :skip-compare? true})))]]]))

+ 0 - 66
src/main/frontend/components/file_based/hierarchy.cljs

@@ -1,66 +0,0 @@
-(ns frontend.components.file-based.hierarchy
-  (:require [clojure.string :as string]
-            [frontend.components.block :as block]
-            [frontend.db :as db]
-            [frontend.db.file-based.model :as file-model]
-            [frontend.state :as state]
-            [frontend.ui :as ui]
-            [frontend.util :as util]
-            [logseq.graph-parser.text :as text]
-            [medley.core :as medley]
-            [rum.core :as rum]))
-
-(defn- get-relation
-  "Get all parent pages along the namespace hierarchy path.
-   If there're aliases, only use the first namespaced alias."
-  [page]
-  (when-let [page (or (text/get-nested-page-name page) page)]
-    (let [repo (state/get-current-repo)
-          page-entity (db/get-page page)
-          aliases (when-let [page-id (:db/id page-entity)]
-                    (db/get-page-alias-names repo page-id))
-          all-page-names (conj aliases page)]
-      (when-let [page (or (first (filter text/namespace-page? all-page-names))
-                          (when (:block/_namespace (db/entity [:block/name (util/page-name-sanity-lc page)]))
-                            page))]
-        (let [namespace-pages (file-model/get-namespace-pages repo page)
-              parent-routes (file-model/get-page-namespace-routes repo page)
-              pages (->> (concat namespace-pages parent-routes)
-                         (distinct)
-                         (sort-by :block/name)
-                         (map (fn [page]
-                                (or (:block/title page) (:block/name page))))
-                         (map #(string/split % "/")))
-              page-namespace (file-model/get-page-namespace repo page)
-              page-namespace (util/get-page-title page-namespace)]
-          (cond
-            (seq pages)
-            {:namespaces pages
-             :namespace-pages namespace-pages}
-
-            page-namespace
-            {:namespaces [(string/split page-namespace "/")]
-             :namespace-pages namespace-pages}
-
-            :else
-            nil))))))
-
-(rum/defc structures
-  [page]
-  (let [{:keys [namespaces]} (get-relation page)]
-    (when (seq namespaces)
-      [:div.page-hierarchy
-       (ui/foldable
-        [:h2.font-bold.opacity-30 "Hierarchy"]
-        [:ul.namespaces {:style {:margin "12px 24px"}}
-         (for [namespace namespaces]
-           [:li.my-2
-            (->>
-             (for [[idx page] (medley/indexed namespace)]
-               (when (and (string? page) page)
-                 (let [full-page (->> (take (inc idx) namespace)
-                                      util/string-join-path)]
-                   (block/page-reference {} full-page page))))
-             (interpose [:span.mx-2.opacity-30 "/"]))])]
-        {:default-collapsed? false
-         :title-trigger? true})])))

+ 0 - 80
src/main/frontend/components/file_based/query.cljs

@@ -1,80 +0,0 @@
-(ns frontend.components.file-based.query
-  (:require [frontend.components.file-based.query-table :as query-table]
-            [frontend.components.query.result :as query-result]
-            [frontend.db.query-dsl :as query-dsl]
-            [frontend.handler.property :as property-handler]
-            [frontend.state :as state]
-            [frontend.ui :as ui]
-            [frontend.util :as util]
-            [rum.core :as rum]))
-
-(rum/defc query-refresh-button
-  [query-time {:keys [on-pointer-down full-text-search?]}]
-  (ui/tooltip
-   [:a.fade-link.flex
-    {:on-pointer-down on-pointer-down}
-    (ui/icon "refresh" {:style {:font-size 20}})]
-   [:div
-    [:p
-     (if full-text-search?
-       [:span "Full-text search results will not be refreshed automatically."]
-       [:span (str "This query takes " (int query-time) "ms to finish, it's a bit slow so that auto refresh is disabled.")])]
-    [:p
-     "Click the refresh button instead if you want to see the latest result."]]))
-
-;; Custom query header only used by file graphs
-(rum/defc custom-query-header
-  [{:keys [dsl-query?] :as config}
-   {:keys [title query] :as q}
-   {:keys [collapsed? *result result table? current-block view-f page-list? query-error-atom]}]
-  (let [dsl-page-query? (and dsl-query?
-                             (false? (:blocks? (query-dsl/parse-query query))))
-        full-text-search? (and dsl-query?
-                               (string? query)
-                               (re-matches #"\".*\"" query))
-        query-time (:query-time (meta result))
-        current-block-uuid (:block/uuid current-block)]
-    [:div.th
-     {:title (str "Query: " query)}
-     (if dsl-query?
-       [:div.flex.flex-1.flex-row
-        (ui/icon "search" {:size 14})
-        [:div.ml-1 (str "Live query" (when dsl-page-query? " for pages"))]]
-       [:div {:style {:font-size "initial"}} title])
-
-     (when (or (not dsl-query?) (not collapsed?))
-       [:div.flex.flex-row.items-center.fade-in
-        (when (> (count result) 0)
-          [:span.results-count.pl-2
-           (let [result-count (if (and (not table?) (map? result))
-                                (apply + (map (comp count val) result))
-                                (count result))]
-             (str result-count (if (> result-count 1) " results" " result")))])
-
-        (when (and current-block (not view-f) (not page-list?))
-          (if table?
-            [:a.flex.ml-1.fade-link {:title "Switch to list view"
-                                     :on-click (fn [] (property-handler/set-block-property! (state/get-current-repo) current-block-uuid
-                                                                                            :query-table
-                                                                                            false))}
-             (ui/icon "list" {:style {:font-size 20}})]
-            [:a.flex.ml-1.fade-link {:title "Switch to table view"
-                                     :on-click (fn [] (property-handler/set-block-property! (state/get-current-repo) current-block-uuid
-                                                                                            :query-table
-                                                                                            true))}
-             (ui/icon "table" {:style {:font-size 20}})]))
-
-        [:a.flex.ml-1.fade-link
-         {:title "Setting properties"
-          :on-click (fn []
-                      (let [all-keys (query-table/get-all-columns-for-result result page-list?)]
-                        (state/pub-event! [:modal/set-query-properties current-block all-keys])))}
-         (ui/icon "settings" {:style {:font-size 20}})]
-
-        [:div.ml-1
-         (when (or full-text-search?
-                   (and query-time (> query-time 50)))
-           (query-refresh-button query-time {:full-text-search? full-text-search?
-                                             :on-pointer-down (fn [e]
-                                                                (util/stop e)
-                                                                (query-result/run-custom-query config q *result query-error-atom))}))]])]))

+ 0 - 263
src/main/frontend/components/file_based/query_table.cljs

@@ -1,263 +0,0 @@
-(ns frontend.components.file-based.query-table
-  (:require [clojure.string :as string]
-            [frontend.components.svg :as svg]
-            [frontend.date :as date]
-            [frontend.db :as db]
-            [frontend.db.query-dsl :as query-dsl]
-            [frontend.format.block :as block]
-            [frontend.handler.common :as common-handler]
-            [frontend.handler.file-based.property :as file-property-handler]
-            [frontend.handler.property :as property-handler]
-            [frontend.state :as state]
-            [frontend.util :as util]
-            [frontend.util.file-based.clock :as clock]
-            [logseq.graph-parser.text :as text]
-            [medley.core :as medley]
-            [promesa.core :as p]
-            [rum.core :as rum]))
-
-;; Util fns
-;; ========
-(defn- attach-clock-property
-  [result]
-  ;; FIXME: Look up by property id if still supporting clock-time
-  (let [ks [:block/properties :clock-time]
-        result (map (fn [b]
-                      (let [b (update (block/parse-title-and-body b)
-                                      :block/properties (fn [properties] (if (map? properties) properties {})))]
-                        (try (assoc-in b ks (or (clock/clock-summary (:block.temp/ast-body b) false) 0))
-                             (catch :default _e
-                               b))))
-                    result)]
-    (if (every? #(zero? (get-in % ks)) result)
-      (map #(medley/dissoc-in % ks) result)
-      result)))
-
-(defn- sort-by-fn [sort-by-column item {:keys [page?]}]
-  (case sort-by-column
-    :created-at
-    (:block/created-at item)
-    :updated-at
-    (:block/updated-at item)
-    :block
-    (:block/title item)
-    :page
-    (if page? (:block/name item) (get-in item [:block/page :block/name]))
-    (get-in item [:block/properties sort-by-column])))
-
-(defn- locale-compare
-  "Use locale specific comparison for strings and general comparison for others."
-  [x y]
-  (if (and (number? x) (number? y))
-    (< x y)
-    (.localeCompare (str x) (str y) (state/sub :preferred-language) #js {:numeric true})))
-
-(defn- sort-result [result {:keys [sort-by-column sort-desc? sort-nlp-date? page?]}]
-  (if (some? sort-by-column)
-    (let [comp-fn (if sort-desc? #(locale-compare %2 %1) locale-compare)]
-      (sort-by (fn [item]
-                 (block/normalize-block (sort-by-fn sort-by-column item {:page? page?})
-                                        sort-nlp-date?))
-               comp-fn
-               result))
-    result))
-
-(defn- get-sort-state
-  "Return current sort direction and column being sorted, respectively
-  :sort-desc? and :sort-by-column. :sort-by-column is nil if no sorting is to be
-  done"
-  [current-block]
-  (let [properties (:block/properties current-block)
-        p-desc? (:query-sort-desc properties)
-        desc? (if (some? p-desc?) p-desc? true)
-        properties (:block/properties current-block)
-        query-sort-by (:query-sort-by properties)
-        ;; Starting with #6105, we started putting properties under namespaces.
-        nlp-date? (:logseq.query/nlp-date properties)
-        sort-by-column (or (keyword query-sort-by)
-                           (if (query-dsl/query-contains-filter? (:block/title current-block) "sort-by")
-                             nil
-                             :updated-at))]
-    {:sort-desc? desc?
-     :sort-by-column sort-by-column
-     :sort-nlp-date? nlp-date?}))
-
-;; Components
-;; ==========
-(rum/defc sortable-title
-  [title column {:keys [sort-by-column sort-desc?]} block-id]
-  (let [repo (state/get-current-repo)]
-    [:th.whitespace-nowrap
-     [:a {:on-click (fn []
-                      (p/do!
-                       (property-handler/set-block-property! repo block-id
-                                                             :query-sort-by
-                                                             (name column))
-                       (property-handler/set-block-property! repo block-id
-                                                             :query-sort-desc
-                                                             (not sort-desc?))))}
-      [:div.flex.items-center
-       [:span.mr-1 title]
-       (when (= sort-by-column column)
-         [:span
-          (if sort-desc? (svg/caret-down) (svg/caret-up))])]]]))
-
-(defn get-all-columns-for-result
-  "Gets all possible columns for a given result. Property names are keywords"
-  [result page?]
-  (let [hidden-properties (conj (file-property-handler/built-in-properties) :template)
-        prop-keys* (->> (distinct (mapcat keys (map :block/properties result)))
-                        (remove hidden-properties))
-        prop-keys (cond-> (if page? (cons :page prop-keys*) (concat '(:block :page) prop-keys*))
-                    page?
-                    (concat [:created-at :updated-at]))]
-    prop-keys))
-
-(defn get-columns [current-block result {:keys [page?]}]
-  (let [properties (:block/properties current-block)
-        query-properties (some-> (:query-properties properties)
-                                 (common-handler/safe-read-string "Parsing query properties failed"))
-        query-properties (if page? (remove #{:block} query-properties) query-properties)
-        columns (if (seq query-properties)
-                  query-properties
-                  (get-all-columns-for-result result page?))]
-    (distinct columns)))
-
-(defn- build-column-value
-  "Builds a column's tuple value for a query table given a row, column and
-  options"
-  [row column {:keys [page? ->elem map-inline comma-separated-property?]}]
-  (case column
-    :page
-    [:string (if page?
-               (or (:block/title row)
-                   (:block/name row))
-               (or (get-in row [:block/page :block/title])
-                   (get-in row [:block/page :block/name])))]
-
-    :block       ; block title
-    (let [content (:block/title row)
-          uuid (:block/uuid row)
-          {:block/keys [title]} (block/parse-title-and-body
-                                 (:block/uuid row)
-                                 (get row :block/format :markdown)
-                                 (:block/pre-block? row)
-                                 content)]
-      (if (seq title)
-        [:element (->elem :div (map-inline {:block/uuid uuid} title))]
-        [:string content]))
-
-    :created-at
-    [:string (when-let [created-at (:block/created-at row)]
-               (date/int->local-time-2 created-at))]
-
-    :updated-at
-    [:string (when-let [updated-at (:block/updated-at row)]
-               (date/int->local-time-2 updated-at))]
-
-    [:string
-     (let [value (get-in row [:block/properties column])]
-       (if (or comma-separated-property? (coll? value))
-         ;; Return original properties since comma properties need to
-         ;; return collections for display purposes
-         value
-         (or (get-in row [:block/properties-text-values column])
-           ;; Fallback to original properties for page blocks
-             value)))]))
-
-(defn- render-column-value
-  [{:keys [row-block row-format cell-format value]} page-cp inline-text]
-  (cond
-    ;; elements should be rendered as they are provided
-    (= :element cell-format) value
-    (coll? value) (->> (map #(page-cp {} {:block/name %}) value)
-                       (interpose [:span ", "]))
-    ;; boolean values need to first be stringified
-    (boolean? value) (str value)
-    ;; string values will attempt to be rendered as pages, falling back to
-    ;; inline-text when no page entity is found
-    (string? value) (if-let [page (and (string? value) (db/get-page value))]
-                      (page-cp {:stop-event-propagation? true} page)
-                      (inline-text row-block row-format value))
-    ;; anything else should just be rendered as provided
-    :else value))
-
-(rum/defc table-row
-  [row columns config page? select? *mouse-down? map-inline page-cp ->elem inline-text]
-  (let [format (get row :block/format :markdown)
-        property-separated-by-commas? (partial text/separated-by-commas? (state/get-config))]
-    [:tr.cursor
-     (for [column columns]
-       (let [[cell-format value] (build-column-value row
-                                                     column
-                                                     {:page? page?
-                                                      :->elem ->elem
-                                                      :map-inline map-inline
-                                                      :config config
-                                                      :comma-separated-property? (property-separated-by-commas? column)})]
-         [:td.whitespace-nowrap
-          {:data-key (pr-str column)
-           :on-pointer-down (fn []
-                              (reset! *mouse-down? true)
-                              (reset! select? false))
-           :on-mouse-move (fn [] (reset! select? true))
-           :on-pointer-up (fn []
-                            (when (and @*mouse-down? (not @select?))
-                              (state/sidebar-add-block!
-                               (state/get-current-repo)
-                               (:db/id row)
-                               :block)
-                              (reset! *mouse-down? false)))}
-          (when (some? value)
-            (render-column-value {:row-block row
-                                  :row-format format
-                                  :cell-format cell-format
-                                  :value value}
-                                 page-cp
-                                 inline-text))]))]))
-
-(rum/defcs result-table-v1 < rum/reactive
-  (rum/local false ::select?)
-  (rum/local false ::mouse-down?)
-  [state config current-block sort-result' sort-state columns {:keys [page?]} map-inline page-cp ->elem inline-text]
-  (let [select? (get state ::select?)
-        *mouse-down? (::mouse-down? state)
-        clock-time-total (when-not page?
-                           (->> (map #(get-in % [:block/properties :clock-time] 0) sort-result')
-                                (apply +)))]
-    [:div.overflow-x-auto {:on-pointer-down (fn [e] (.stopPropagation e))
-                           :style {:width "100%"}
-                           :class "query-table"}
-     [:table.table-auto
-      [:thead
-       [:tr.cursor
-        (for [column columns]
-          (let [title (if (and (= column :clock-time) (integer? clock-time-total))
-                        (util/format "clock-time(total: %s)" (clock/seconds->days:hours:minutes:seconds
-                                                              clock-time-total))
-                        (name column))]
-            (sortable-title title column sort-state (:block/uuid current-block))))]]
-      [:tbody
-       (for [row sort-result']
-         (table-row row columns config page? select? *mouse-down? map-inline page-cp ->elem inline-text))]]]))
-
-(rum/defc result-table < rum/reactive
-  [config current-block result {:keys [page?] :as options} map-inline page-cp ->elem inline-text]
-  (when current-block
-    (let [result (map (fn [item]
-                        (if (and (map? item) (:db/id item))
-                          (if-let [entity-title (:block/title (db/entity (:db/id item)))]
-                            (assoc item :block/title entity-title)
-                            (update item :block/title (fn [title]
-                                                        (some-> title
-                                                                (string/replace "$pfts_2lqh>$" "")
-                                                                (string/replace "$<pfts_2lqh$" "")))))
-                          item))
-                      result)
-          result' (if page? result (attach-clock-property result))
-          columns (get-columns current-block result' {:page? page?})
-          ;; Sort state needs to be in sync between final result and sortable title
-          ;; as user needs to know if there result is sorted
-          sort-state (get-sort-state current-block)
-          sort-result' (sort-result result' (assoc sort-state :page? page?))]
-      (result-table-v1 config current-block sort-result' sort-state columns options map-inline page-cp ->elem inline-text))))

+ 0 - 858
src/main/frontend/components/file_sync.cljs

@@ -1,858 +0,0 @@
-(ns frontend.components.file-sync
-  (:require [cljs-time.coerce :as tc]
-            [cljs-time.core :as t]
-            [cljs.core.async :as async]
-            [cljs.core.async.interop :refer [p->c]]
-            [clojure.string :as string]
-            [electron.ipc :as ipc]
-            [frontend.components.lazy-editor :as lazy-editor]
-            [frontend.components.onboarding.quick-tour :as quick-tour]
-            [frontend.components.page :as page]
-            [frontend.config :as config]
-            [frontend.db.file-based.model :as file-model]
-            [frontend.db.model :as db-model]
-            [frontend.fs :as fs]
-            [frontend.fs.sync :as fs-sync]
-            [frontend.handler.file-based.native-fs :as nfs-handler]
-            [frontend.handler.file-sync :refer [*beta-unavailable?] :as file-sync-handler]
-            [frontend.handler.notification :as notification]
-            [frontend.handler.page :as page-handler]
-            [frontend.handler.repo :as repo-handler]
-            [frontend.handler.user :as user-handler]
-            [frontend.mobile.util :as mobile-util]
-            [frontend.state :as state]
-            [frontend.storage :as storage]
-            [frontend.ui :as ui]
-            [frontend.util :as util]
-            [frontend.util.fs :as fs-util]
-            [frontend.util.persist-var :as persist-var]
-            [goog.functions :refer [debounce]]
-            [logseq.common.util :as common-util]
-            [logseq.shui.hooks :as hooks]
-            [logseq.shui.ui :as shui]
-            [promesa.core :as p]
-            [reitit.frontend.easy :as rfe]
-            [rum.core :as rum]))
-
-(declare maybe-onboarding-show)
-(declare open-icloud-graph-clone-picker)
-
-(rum/defc clone-local-icloud-graph-panel
-  [repo graph-name close-fn]
-
-  (hooks/use-effect!
-   #(some->> (state/sub :file-sync/jstour-inst)
-             (.complete))
-   [])
-
-  (let [graph-dir      (config/get-repo-dir repo)
-        [selected-path set-selected-path] (rum/use-state "")
-        selected-path? (and (not (string/blank? selected-path))
-                            (not (mobile-util/in-iCloud-container-path? selected-path)))
-        on-confirm     (fn []
-                         (when-let [dest-dir (and selected-path?
-                                                  ;; avoid using `util/node-path.join` to join mobile path since it replaces `file:///abc` to `file:/abc`
-                                                  (str (string/replace selected-path #"/+$" "") "/" graph-name))]
-                           (-> (cond
-                                 (util/electron?)
-                                 (ipc/ipc :copyDirectory graph-dir dest-dir)
-
-                                 (mobile-util/native-ios?)
-                                 (fs/copy! repo graph-dir dest-dir)
-
-                                 :else
-                                 nil)
-                               (.then #(do
-                                         (notification/show! (str "Cloned to => " dest-dir) :success)
-                                         (nfs-handler/ls-dir-files-with-path! dest-dir)
-                                         (repo-handler/remove-repo! {:url repo})
-                                         (close-fn)))
-                               (.catch #(js/console.error %)))))]
-
-    [:div.cp__file-sync-related-normal-modal
-     [:div.flex.justify-center.pb-4 [:span.icon-wrap (ui/icon "folders")]]
-
-     [:h1.text-xl.font-semibold.opacity-90.text-center.py-2
-      "Clone your local graph away from " [:strong "☁️"] " iCloud!"]
-     [:h2.text-center.opacity-70.text-xs.leading-5
-      "Unfortunately, Logseq Sync and iCloud don't work perfectly together at the moment. To make sure"
-      [:br]
-      "You can always delete the remote graph at a later point."]
-
-     [:div.folder-tip.flex.flex-col.items-center
-      [:h3
-       [:span (ui/icon "folder") [:label.pl-0.5 (common-util/safe-decode-uri-component graph-name)]]]
-      [:h4.px-6 (config/get-string-repo-dir repo)]
-
-      (when (not (string/blank? selected-path))
-        [:h5.text-xs.pt-1.-mb-1.flex.items-center.leading-none
-         (if (mobile-util/in-iCloud-container-path? selected-path)
-           [:span.inline-block.pr-1.text-error.scale-75 (ui/icon "alert-circle")]
-           [:span.inline-block.pr-1.text-success.scale-75 (ui/icon "circle-check")])
-         selected-path])
-
-      [:div.out-icloud
-       (ui/button
-        [:span.inline-flex.items-center.leading-none.opacity-90
-         "Select new parent folder outside of iCloud" (ui/icon "arrow-right")]
-
-        :on-click
-        (fn []
-          ;; TODO: support mobile
-          (cond
-            (util/electron?)
-            (p/let [path (ipc/ipc "openDialog")]
-              (set-selected-path path))
-
-            (mobile-util/native-ios?)
-            (p/let [{:keys [path _localDocumentsPath]}
-                    (p/chain
-                     (.pickFolder mobile-util/folder-picker)
-                     #(js->clj % :keywordize-keys true))]
-              (set-selected-path path))
-
-            :else
-            nil)))]]
-
-     [:p.flex.items-center.space-x-2.pt-6.flex.justify-center.sm:justify-end.-mb-2
-      (ui/button "Cancel" :background "gray" :class "opacity-50" :on-click close-fn)
-      (ui/button "Clone graph" :disabled (not selected-path?) :on-click on-confirm)]]))
-
-(rum/defc create-remote-graph-panel
-  [repo graph-name close-fn]
-
-  (hooks/use-effect!
-   #(some->> (state/sub :file-sync/jstour-inst)
-             (.complete))
-   [])
-
-  (let [on-confirm
-        (fn []
-          (async/go
-            (close-fn)
-            (if (mobile-util/in-iCloud-container-path? repo)
-              (open-icloud-graph-clone-picker repo)
-              (do
-                (state/set-state! [:ui/loading? :graph/create-remote?] true)
-                (when-let [GraphUUID (get (async/<! (file-sync-handler/create-graph graph-name)) 2)]
-                  (async/<! (fs-sync/<sync-start))
-                  (state/set-state! [:ui/loading? :graph/create-remote?] false)
-                  ;; update both local && remote graphs
-                  (state/add-remote-graph! {:GraphUUID GraphUUID
-                                            :GraphName graph-name})
-                  (state/set-repos! (map (fn [r]
-                                           (if (= (:url r) repo)
-                                             (assoc r
-                                                    :GraphUUID GraphUUID
-                                                    :GraphName graph-name
-                                                    :remote? true)
-                                             r))
-                                         (state/get-repos))))))))]
-
-    [:div.cp__file-sync-related-normal-modal
-     [:div.flex.justify-center.pb-4 [:span.icon-wrap (ui/icon "cloud-upload" {:size 20})]]
-
-     [:h1.text-xl.font-semibold.opacity-90.text-center.py-2
-      "Are you sure you want to create a new remote graph?"]
-     [:h2.text-center.opacity-70.text-xs
-      "By continuing this action you will create an encrypted cloud version of your current local graph." [:br]
-      "You can always delete the remote graph at a later point."]
-
-     [:div.folder-tip.flex.flex-col.items-center
-      [:h3
-       [:span (ui/icon "folder") [:label.pl-0.5 graph-name]]
-       [:span.opacity-50.scale-75 (ui/icon "arrow-right")]
-       [:span (ui/icon "cloud-lock")]]
-      [:h4.px-4 (config/get-string-repo-dir repo)]]
-
-     [:p.flex.items-center.space-x-2.pt-6.flex.justify-center.sm:justify-end.-mb-2
-      (ui/button "Cancel" :background "gray" :class "opacity-50" :on-click close-fn)
-      (ui/button "Create remote graph" :on-click on-confirm)]]))
-
-(rum/defc last-synced-cp < rum/reactive
-  []
-  (let [last-synced-at (state/sub [:file-sync/graph-state
-                                   (state/get-current-file-sync-graph-uuid)
-                                   :file-sync/last-synced-at])
-        last-synced-at (if last-synced-at
-                         (util/human-time (tc/from-long (* last-synced-at 1000)))
-                         "just now")]
-    [:div.cl
-     [:span.opacity-60 "Last change was"]
-     [:span.pl-1 last-synced-at]]))
-
-(rum/defc sync-now
-  []
-  (ui/button "Sync now"
-             :class "block cursor-pointer"
-             :small? true
-             :on-click #(async/offer! fs-sync/immediately-local->remote-chan true)
-             :style {:color "#ffffff"}))
-
-(def *last-calculated-time (atom nil))
-
-(rum/defc ^:large-vars/cleanup-todo indicator-progress-pane
-  [sync-state sync-progress
-   {:keys [idle? syncing? no-active-files? online? history-files? queuing?]}]
-
-  (hooks/use-effect!
-   (fn []
-     #(reset! *last-calculated-time nil))
-   [])
-
-  (let [uploading-files        (:current-local->remote-files sync-state)
-        downloading-files      (:current-remote->local-files sync-state)
-        uploading?             (seq uploading-files)
-        downloading?           (seq downloading-files)
-
-        progressing?           (or uploading? downloading?)
-
-        full-upload-files      (:full-local->remote-files sync-state)
-        full-download-files    (:full-remote->local-files sync-state)
-        calc-progress-total    #(cond
-                                  uploading? (count full-upload-files)
-                                  downloading? (count full-download-files)
-                                  :else 0)
-        calc-progress-finished (fn []
-                                 (let [current-sync-files (set
-                                                           (->> (or (seq full-upload-files) (seq full-download-files))
-                                                                (map :path)))]
-                                   (count (filter #(and (= (:percent (second %)) 100)
-                                                        (contains? current-sync-files (first %))) sync-progress))))
-        calc-time-left         (fn [] (let [last-calculated-at (:calculated-at @*last-calculated-time)
-                                            now                (tc/to-epoch (t/now))]
-                                        (if (and last-calculated-at (< (- now last-calculated-at) 10))
-                                          (:result @*last-calculated-time)
-                                          (let [result (file-sync-handler/calculate-time-left sync-state sync-progress)]
-                                            (reset! *last-calculated-time {:calculated-at now
-                                                                           :result        result})
-                                            result))))
-
-        p-total                (if syncing? (calc-progress-total) 0)
-        p-finished             (if syncing? (calc-progress-finished) 0)
-        tip-b&p                (if (and syncing? progressing?)
-                                 [[:span (util/format "%s of %s files" p-finished p-total)]
-                                  [:div.progress-bar [:i {:style
-                                                          {:width (str (if (> p-total 0)
-                                                                         (* (/ p-finished p-total) 100) 0) "%")}}]]]
-                                 [[:span.opacity-60 "all file edits"]
-                                  (last-synced-cp)])
-        *el-ref                (rum/use-ref nil)
-        [list-active?, set-list-active?] (rum/use-state
-                                          (-> (storage/get :ui/file-sync-active-file-list?)
-                                              (#(if (nil? %) true %))))]
-
-    (hooks/use-effect!
-     (fn []
-       (when-let [^js outer-class-list
-                  (some-> (rum/deref *el-ref)
-                          (.closest ".menu-links-outer")
-                          (.-classList))]
-         (->> "is-list-active"
-              (#(if list-active?
-                  (.add outer-class-list %)
-                  (.remove outer-class-list %))))
-         (storage/set :ui/file-sync-active-file-list? list-active?)))
-     [list-active?])
-
-    (let [idle-&-no-active? (and idle? no-active-files?)
-          waiting? (not (or (not online?)
-                            idle-&-no-active?
-                            syncing?))]
-      [:div.cp__file-sync-indicator-progress-pane
-       {:ref *el-ref
-        :class (when (and syncing? progressing?) "is-progress-active")}
-       [:div.a
-        [:div.al
-         [:strong
-          {:class (when idle-&-no-active? "is-no-active")}
-          (cond
-            (not online?) (ui/icon "wifi-off")
-            uploading? (ui/icon "arrow-up")
-            downloading? (ui/icon "arrow-down")
-            :else (ui/icon "thumb-up"))]
-         [:span
-          (cond
-            (not online?) "Currently having connection issues..."
-            idle-&-no-active? "Everything is synced!"
-            syncing? "Currently syncing your graph..."
-            :else "Waiting...")]]
-        [:div.ar
-         (when queuing? (sync-now))]]
-
-       (when-not waiting?
-         [:div.b.dark:text-gray-200
-          [:div.bl
-           [:span.flex.items-center
-            (if no-active-files?
-              [:span.opacity-100.pr-1 "Successfully processed"]
-              [:span.opacity-60.pr-1 "Processed"])]
-
-           (first tip-b&p)]
-
-          [:div.br
-           [:small.opacity-50
-            (when syncing?
-              (calc-time-left))]]])
-
-       [:div.c
-        {:class (when waiting? "pt-2")}
-        (second tip-b&p)
-        (when (or history-files? (not no-active-files?))
-          [:span.inline-flex.pl-2.active:opacity-50
-           {:on-click #(set-list-active? (not list-active?))}
-           (if list-active?
-             (ui/icon "chevron-up" {:style {:font-size 24}})
-             (ui/icon "chevron-left" {:style {:font-size 24}}))])]])))
-
-(defn- sort-files
-  [files]
-  (sort-by (fn [f] (or (:size f) 0)) > files))
-
-(rum/defcs ^:large-vars/cleanup-todo indicator <
-  rum/reactive
-  {:key-fn #(identity "file-sync-indicator")}
-  {:will-mount   (fn [state]
-                   (let [unsub-fn (file-sync-handler/setup-file-sync-event-listeners)]
-                     (assoc state ::unsub-events unsub-fn)))
-   :will-unmount (fn [state]
-                   (apply (::unsub-events state) nil)
-                   state)}
-  [_state]
-  (let [_                       (state/sub :auth/id-token)
-        online?                 (state/sub :network/online?)
-        enabled-progress-panel? true
-        current-repo            (state/get-current-repo)
-        creating-remote-graph?  (state/sub [:ui/loading? :graph/create-remote?])
-        current-graph-id        (state/sub-current-file-sync-graph-uuid)
-        sync-state              (state/sub-file-sync-state current-graph-id)
-        sync-progress           (state/sub [:file-sync/graph-state
-                                            current-graph-id
-                                            :file-sync/progress])
-        _                       (rum/react file-sync-handler/refresh-file-sync-component)
-        synced-file-graph?      (file-sync-handler/synced-file-graph? current-repo)
-        uploading-files         (sort-files (:current-local->remote-files sync-state))
-        downloading-files       (sort-files (:current-remote->local-files sync-state))
-        queuing-files           (:queued-local->remote-files sync-state)
-        history-files           (:history sync-state)
-        status                  (:state sync-state)
-        status                  (or (nil? status) (keyword (name status)))
-        off?                    (fs-sync/sync-off? sync-state)
-        full-syncing?           (contains? #{:local->remote-full-sync :remote->local-full-sync} status)
-        syncing?                (or full-syncing? (contains? #{:local->remote :remote->local} status))
-        idle?                   (contains? #{:idle} status)
-        need-password?          (and (contains? #{:need-password} status)
-                                     (not (fs-sync/graph-encrypted?)))
-        queuing?                (and idle? (boolean (seq queuing-files)))
-        no-active-files?        (empty? (concat downloading-files queuing-files uploading-files))
-        create-remote-graph-fn  #(when (and current-repo (not (config/demo-graph? current-repo)))
-                                   (let [graph-name
-                                         (js/decodeURI (util/node-path.basename current-repo))
-
-                                         confirm-fn
-                                         (fn [{:keys [close]}]
-                                           (create-remote-graph-panel current-repo graph-name close))]
-
-                                     (shui/dialog-open! confirm-fn {:center? true :close-btn? false})))
-        turn-on                 (->
-                                 (fn []
-                                   (when-not (file-sync-handler/current-graph-sync-on?)
-                                     (async/go
-                                       (let [graphs-txid fs-sync/graphs-txid]
-                                         (async/<! (p->c (persist-var/-load graphs-txid)))
-                                         (cond
-                                           @*beta-unavailable?
-                                           (state/pub-event! [:file-sync/onboarding-tip :unavailable])
-
-                                           ;; current graph belong to other user, do nothing
-                                           (let [user-uuid (async/<! (user-handler/<user-uuid))
-                                                 user-uuid (when-not (instance? ExceptionInfo user-uuid) user-uuid)]
-                                             (and (first @graphs-txid)
-                                                  user-uuid
-                                                  (not (fs-sync/check-graph-belong-to-current-user
-                                                        user-uuid
-                                                        (first @graphs-txid)))))
-                                           nil
-
-                                           (and (second @graphs-txid)
-                                                (fs-sync/graph-sync-off? (second @graphs-txid))
-                                                (async/<! (fs-sync/<check-remote-graph-exists (second @graphs-txid))))
-                                           (fs-sync/<sync-start)
-
-                                           ;; remote graph already has been deleted, clear repos first, then create-remote-graph
-                                           (second @graphs-txid) ; <check-remote-graph-exists -> false
-                                           (do (state/set-repos!
-                                                (map (fn [r]
-                                                       (if (= (:url r) current-repo)
-                                                         (dissoc r :GraphUUID :GraphName :remote?)
-                                                         r))
-                                                     (state/get-repos)))
-                                               (create-remote-graph-fn))
-
-                                           (second @graphs-txid) ; sync not started yet
-                                           nil
-
-                                           :else
-                                           (create-remote-graph-fn))))))
-                                 (debounce 1500))]
-    (if creating-remote-graph?
-      (ui/loading "")
-      [:div.cp__file-sync-indicator
-       {:class (util/classnames
-                [{:is-enabled-progress-pane enabled-progress-panel?
-                  :has-active-files         (not no-active-files?)}
-                 (str "status-of-" (and (keyword? status) (name status)))])}
-       (when (and (not config/publishing?)
-                  (user-handler/logged-in?))
-         (ui/dropdown-with-links
-          ;; trigger
-          (fn [{:keys [toggle-fn]}]
-            (if (not off?)
-              [:a.button.cloud.on
-               {:on-click toggle-fn
-                :class    (util/classnames [{:syncing syncing?
-                                             :is-full full-syncing?
-                                             :queuing queuing?
-                                             :idle    (and (not queuing?) idle?)}])}
-               [:span.flex.items-center
-                (ui/icon "cloud" {:size ui/icon-size})]]
-
-              [:a.button.cloud.off
-               {:on-click turn-on}
-               (ui/icon "cloud-off" {:size ui/icon-size})]))
-
-          ;; links
-          (cond-> (vec
-                   (when-not (and no-active-files? idle?)
-                     (cond
-                       need-password?
-                       [{:title   [:div.file-item.flex.items-center.leading-none.pt-3
-                                   {:style {:margin-left -8}}
-                                   (ui/icon "lock" {:size 20}) [:span.pl-1.font-semibold "Password is required"]]
-                         :options {:on-click fs-sync/sync-need-password!}}]
-
-                       ;; head of upcoming sync
-                       (not no-active-files?)
-                       [{:title   [:div.file-item.is-first ""]
-                         :options {:class "is-first-placeholder"}}])))
-            synced-file-graph?
-            (concat
-             (map (fn [f] {:title [:div.file-item
-                                   {:key (str "downloading-" f)}
-                                   f]
-                           :key   (str "downloading-" f)
-                           :icon  (if enabled-progress-panel?
-                                    (let [progress (get sync-progress f)
-                                          percent (or (:percent progress) 0)]
-                                      (if (and (number? percent)
-                                               (< percent 100))
-                                        (ui/indicator-progress-pie percent)
-                                        (ui/icon "circle-check")))
-                                    (ui/icon "arrow-narrow-down"))}) downloading-files)
-
-             (map (fn [e] (let [icon (case (.-type e)
-                                       "add" "plus"
-                                       "unlink" "minus"
-                                       "edit")
-                                path (fs-sync/relative-path e)]
-                            {:title [:div.file-item
-                                     {:key (str "queue-" path)}
-                                     path]
-                             :key   (str "queue-" path)
-                             :icon  (ui/icon icon)})) (take 10 queuing-files))
-
-             (map (fn [f] {:title [:div.file-item
-                                   {:key (str "uploading-" f)}
-                                   f]
-                           :key   (str "uploading-" f)
-                           :icon  (if enabled-progress-panel?
-                                    (let [progress (get sync-progress f)
-                                          percent (or (:percent progress) 0)]
-                                      (if (and (number? percent)
-                                               (< percent 100))
-                                        (ui/indicator-progress-pie percent)
-                                        (ui/icon "circle-check")))
-                                    (ui/icon "arrow-up"))}) uploading-files)
-
-             (when (seq history-files)
-               (map-indexed (fn [i f] (:time f)
-                              (when-let [path (:path f)]
-                                (let [full-path   (util/node-path.join (config/get-repo-dir current-repo) path)
-                                      page-name   (file-model/get-file-page full-path)]
-                                  {:title [:div.files-history.cursor-pointer
-                                           {:key      i :class (when (= i 0) "is-first")
-                                            :on-click (fn []
-                                                        (if page-name
-                                                          (rfe/push-state :page {:name page-name})
-                                                          (rfe/push-state :file {:path full-path})))}
-                                           [:span.file-sync-item (:path f)]
-                                           [:div.opacity-50 (ui/humanity-time-ago (:time f) nil)]]})))
-                            (take 10 history-files)))))
-
-          ;; options
-          {:outer-header
-           [:<>
-            (indicator-progress-pane
-             sync-state sync-progress
-             {:idle?            idle?
-              :syncing?         syncing?
-              :need-password?   need-password?
-              :full-sync?       full-syncing?
-              :online?          online?
-              :queuing?         queuing?
-              :no-active-files? no-active-files?
-              :history-files?   (seq history-files)})
-
-            (when (and
-                   (not enabled-progress-panel?)
-                   synced-file-graph? queuing?)
-              [:div.head-ctls (sync-now)])]}))])))
-
-(rum/defc pick-local-graph-for-sync [graph]
-  [:div.cp__file-sync-related-normal-modal
-   [:div.flex.justify-center.pb-4
-    [:span.icon-wrap (ui/icon "cloud-download" {:size 22})]]
-
-   [:h1.mb-5.text-2xl.text-center.font-bold
-    (util/format "Sync graph \"%s\" to local" (:GraphName graph))]
-
-   (ui/button
-    "Open a local directory"
-    :class "block w-full mt-4"
-    :size :lg
-    :on-click #(do
-                 (state/close-modal!)
-                 (fs-sync/<sync-stop)
-                 (->
-                  (page-handler/ls-dir-files!
-                   (fn [{:keys [url]}]
-                     (file-sync-handler/init-remote-graph url graph)
-                     (js/setTimeout (fn [] (repo-handler/refresh-repos!)) 200))
-
-                   {:on-open-dir
-                    (fn [result]
-                      (prn ::on-open-dir result)
-                      (let [empty-dir? (not (seq (:files result)))
-                            root (:path result)]
-                        (cond
-                          (string/blank? root)
-                          (p/rejected (js/Error. nil))   ;; cancel pick a directory
-
-                          empty-dir?
-                          (p/resolved nil)
-
-                          :else                          ; dir is not empty
-                          (-> (if (util/electron?)
-                                (ipc/ipc :readGraphTxIdInfo root)
-                                (fs-util/read-graphs-txid-info root))
-                              (p/then (fn [^js info]
-                                        (when (or (nil? info)
-                                                  (nil? (second info))
-                                                  (not= (second info) (:GraphUUID graph)))
-                                          (if (js/confirm "This directory is not empty, are you sure to sync the remote graph to it? Make sure to back up the directory first.")
-                                            (p/resolved nil)
-                                            (p/rejected (js/Error. nil))))))))))}) ;; cancel pick a non-empty directory
-                  (p/catch (fn [])))))
-
-   [:div.text-xs.opacity-50.px-1.flex-row.flex.items-center.p-2
-    (ui/icon "alert-circle")
-    [:span.ml-1 " An empty directory or an existing remote graph!"]]])
-
-(defn pick-dest-to-sync-panel [graph]
-  (fn []
-    (pick-local-graph-for-sync graph)))
-
-(rum/defc page-history-list
-  [graph-uuid page-entity set-list-ready? set-page]
-
-  (let [[version-files set-version-files] (rum/use-state nil)
-        [current-page set-current-page] (rum/use-state nil)
-        [loading? set-loading?] (rum/use-state false)
-
-        set-page-fn     (fn [page-meta]
-                          (set-current-page page-meta)
-                          (set-page page-meta))
-
-        get-version-key #(or (:VersionUUID %) (:relative-path %))]
-
-    ;; fetch version files
-    (hooks/use-effect!
-     (fn []
-       (when-not loading?
-         (async/go
-           (set-loading? true)
-           (try
-             (let [files (async/<! (file-sync-handler/<fetch-page-file-versions graph-uuid page-entity))]
-               (set-version-files files)
-               (set-page-fn (first files))
-               (set-list-ready? true))
-             (finally (set-loading? false)))))
-       #())
-     [])
-
-    [:div.version-list
-     (if loading?
-       [:div.p-4 (ui/loading)]
-       (for [version version-files]
-         (let [version-uuid (get-version-key version)
-               local?       (some? (:relative-path version))]
-           [:div.version-list-item {:key version-uuid}
-            [:a.item-link.block.fade-link.flex.justify-between
-             {:title    version-uuid
-              :class    (util/classnames
-                         [{:active (and current-page (= version-uuid (get-version-key current-page)))}])
-              :on-click #(set-page-fn version)}
-
-             [:div.text-sm.pt-1
-              (ui/humanity-time-ago
-               (or (:CreateTime version)
-                   (:create-time version)) nil)]
-             [:small.opacity-50.translate-y-1.flex.items-center.space-x-1
-              (if local?
-                [:<> (ui/icon "git-commit") [:span "local"]]
-                [:<> (ui/icon "cloud") [:span "remote"]])]]])))]))
-
-(rum/defc pick-page-histories-for-sync
-  [repo-url graph-uuid page-name page-entity]
-  (let [[selected-page set-selected-page] (rum/use-state nil)
-        get-version-key    #(or (:VersionUUID %) (:relative-path %))
-        file-uuid          (:FileUUID selected-page)
-        version-uuid       (:VersionUUID selected-page)
-        [version-content set-version-content] (rum/use-state nil)
-        [list-ready? set-list-ready?] (rum/use-state false)
-        [content-ready? set-content-ready?] (rum/use-state false)
-        *ref-contents      (rum/use-ref (atom {}))
-        original-page-name (or (:block/title page-entity) page-name)]
-
-    (hooks/use-effect!
-     #(when selected-page
-        (set-content-ready? false)
-        (let [k               (get-version-key selected-page)
-              loaded-contents @(rum/deref *ref-contents)]
-          (if (contains? loaded-contents k)
-            (do
-              (set-version-content (get loaded-contents k))
-              (js/setTimeout (fn [] (set-content-ready? true)) 100))
-
-            ;; without cache
-            (let [load-file' (fn [repo-url file]
-                               (-> (fs-util/read-repo-file repo-url file)
-                                   (p/then
-                                    (fn [content]
-                                      (set-version-content content)
-                                      (set-content-ready? true)
-                                      (swap! (rum/deref *ref-contents) assoc k content)))))]
-              (if (and file-uuid version-uuid)
-                ;; read remote content
-                (async/go
-                  (let [downloaded-path (async/<! (file-sync-handler/download-version-file graph-uuid file-uuid version-uuid true))]
-                    (when downloaded-path
-                      (load-file' repo-url downloaded-path))))
-
-                ;; read local content
-                (when-let [relative-path (:relative-path selected-page)]
-                  (load-file' repo-url relative-path)))))))
-     [selected-page])
-
-    (hooks/use-effect!
-     (fn []
-       (state/update-state! :editor/hidden-editors #(conj % page-name))
-
-       ;; clear effect
-       (fn []
-         (state/update-state! :editor/hidden-editors #(disj % page-name))))
-     [page-name])
-
-    [:div.cp__file-sync-page-histories.flex-wrap
-     {:class (util/classnames [{:is-list-ready list-ready?}])}
-
-     [:h1.absolute.top-0.left-0.text-xl.px-4.py-4.leading-4
-      (ui/icon "history")
-      " History for page "
-      [:span.font-medium original-page-name]]
-
-     ;; history versions
-     [:div.cp__file-sync-page-histories-left.flex-wrap
-      ;; sidebar lists
-      (page-history-list graph-uuid page-entity set-list-ready? set-selected-page)
-
-      ;; content detail
-      [:article
-       (when-let [inst-id (and selected-page (get-version-key selected-page))]
-         (if content-ready?
-           [:div.relative.raw-content-editor
-            (lazy-editor/editor
-             nil inst-id {:data-lang "markdown"}
-             version-content {:lineWrapping true :readOnly true :lineNumbers true})
-            [:div.absolute.top-1.right-1.opacity-50.hover:opacity-100
-             (ui/button "Restore"
-                        :small? true
-                        :on-click #(state/pub-event! [:file-sync-graph/restore-file (state/get-current-repo) page-entity version-content]))]]
-           [:span.flex.p-15.items-center.justify-center (ui/loading "")]))]]
-
-     ;; current version
-     [:div.cp__file-sync-page-histories-right
-      [:h1.title.text-xl
-       "Current version"]
-      (page/page-blocks-cp page-entity nil)]
-
-     ;; ready loading
-     [:div.flex.items-center.h-full.justify-center.w-full.absolute.ready-loading
-      (ui/loading)]]))
-
-(defn pick-page-histories-panel [graph-uuid page-name]
-  (fn []
-    (if-let [page-entity (db-model/get-page page-name)]
-      (pick-page-histories-for-sync (state/get-current-repo) graph-uuid page-name page-entity)
-      (ui/admonition :warning (str "The page (" page-name ") does not exist!")))))
-
-(rum/defc onboarding-welcome-logseq-sync
-  [close-fn]
-
-  (let [[loading? set-loading?] (rum/use-state false)]
-    [:div.cp__file-sync-welcome-logseq-sync
-     [:span.head-bg
-
-      [:strong "CLOSED BETA"]]
-
-     [:h1.text-2xl.font-bold.flex-col.sm:flex-row
-      [:span.opacity-80 "Welcome to "]
-      [:span.pl-2.dark:text-white.text-gray-800 "Logseq Sync! 👋"]]
-
-     [:h2
-      "No more cloud storage worries. With Logseq's encrypted file syncing, "
-      [:br]
-      "you'll always have your notes backed up and available in real-time on any device."]
-
-     [:div.pt-6.flex.justify-center.space-x-2.sm:justify-end
-      (ui/button "Later" :on-click close-fn :background "gray" :class "opacity-60")
-      (ui/button "Start syncing"
-                 :disabled loading?
-                 :on-click (fn []
-                             (set-loading? true)
-                             (let [result  (:user/info @state/state)
-                                   ex-time (:ExpireTime result)]
-                               (if (and (number? ex-time)
-                                        (< (* ex-time 1000) (js/Date.now)))
-                                 (do
-                                   (vreset! *beta-unavailable? true)
-                                   (maybe-onboarding-show :unavailable))
-
-                                 ;; Logseq sync available
-                                 (maybe-onboarding-show :sync-initiate))
-                               (close-fn)
-                               (set-loading? false))))]]))
-
-(rum/defc onboarding-unavailable-file-sync
-  [close-fn]
-
-  [:div.cp__file-sync-unavailable-logseq-sync
-   [:span.head-bg]
-
-   [:h1.text-2xl.font-bold
-    [:span.pr-2.dark:text-white.text-gray-800 "Logseq Sync"]
-    [:span.opacity-80 "is not yet available for you. 😔 "]]
-
-   [:h2
-    "Thanks for creating an account! To ensure that our file syncing service runs well when we release it"
-    [:br]
-    "to our users, we need a little more time to test it. That’s why we decided to first roll it out only to our "
-    [:br]
-    "charitable OpenCollective sponsors and backers. We can notify you once it becomes available for you."]
-
-   [:div.pt-6.flex.justify-end.space-x-2
-    (ui/button "Close" :on-click close-fn :background "gray" :class "opacity-60")]])
-
-(rum/defc onboarding-congrats-successful-sync
-  [close-fn]
-
-  [:div.cp__file-sync-related-normal-modal
-   [:div.flex.justify-center.pb-4 [:span.icon-wrap (ui/icon "checkup-list" {:size 28})]]
-
-   [:h1.text-xl.font-semibold.opacity-90.text-center.py-2
-    [:span.dark:opacity-80 "Congrats on your first successful sync!"]]
-
-   [:h2.text-center.dark:opacity-70.text-sm.opacity-90
-    [:div "By using this graph with Logseq Sync you can now transition seamlessly between your different "]
-    [:div
-     [:span "devices. Go to the "]
-     [:span.dark:text-white "All Graphs "]
-     [:span "pages to manage your remote graph or switch to another local graph "]]
-    [:div "and sync it as well."]]
-
-   [:div.cloud-tip.rounded-md.mt-6.py-4
-    [:div.items-center.opacity-90.flex.justify-center
-     [:span.pr-2.flex (ui/icon "bell-ringing" {:class "font-semibold"})]
-     [:strong "Logseq Sync is still in Beta and we're working on a Pro plan!"]]]
-
-    ;; [:ul.flex.py-6.px-4
-    ;;  [:li.it
-    ;;   [:h1.dark:text-white "10"]
-    ;;   [:h2 "Remote Graphs"]]
-    ;;  [:li.it
-    ;;   [:h1.dark:text-white "5G"]
-    ;;   [:h2 "Storage per Graph"]]
-
-    ;;  [:li.it
-    ;;   [:h1.dark:text-white "50G"]
-    ;;   [:h2 "Total Storage"]]]
-
-   [:div.pt-6.flex.justify-end.space-x-2
-    (ui/button "Done" :on-click close-fn)]])
-
-(defn open-icloud-graph-clone-picker
-  ([] (open-icloud-graph-clone-picker (state/get-current-repo)))
-  ([repo]
-   (when (and repo (mobile-util/in-iCloud-container-path? repo))
-     (shui/dialog-open!
-      (fn [close-fn]
-        (clone-local-icloud-graph-panel repo (util/node-path.basename repo) close-fn))
-      {:close-btn? false}))))
-
-(defn make-onboarding-panel
-  [type]
-
-  (fn [{:keys [close]}]
-
-    (case type
-      :welcome
-      (onboarding-welcome-logseq-sync close)
-
-      :unavailable
-      (onboarding-unavailable-file-sync close)
-
-      :congrats
-      (onboarding-congrats-successful-sync close)
-
-      [:p
-       [:h1.text-xl.font-bold "Not handled!"]
-       [:a.button {:on-click close} "Got it!"]])))
-
-(defn maybe-onboarding-show
-  [type]
-  (when-not (get (state/sub :file-sync/onboarding-state) (keyword type))
-    (try
-      (let [current-repo (state/get-current-repo)
-            demo-repo?  (= current-repo config/demo-repo)
-            login?       (boolean (state/sub :auth/id-token))]
-
-        (when login?
-          (case type
-
-            :welcome
-            (when (or demo-repo?
-                      (:GraphUUID (repo-handler/get-detail-graph-info current-repo)))
-              (throw (js/Error. "current repo have been local or remote graph")))
-
-            (:sync-initiate :sync-learn :sync-history)
-            (do (quick-tour/ready
-                 (fn []
-                   (quick-tour/start-file-sync type)
-                   (state/set-state! [:file-sync/onboarding-state type] true)))
-                (throw (js/Error. nil)))
-            :default)
-
-          (state/pub-event! [:file-sync/onboarding-tip type])
-          (state/set-state! [:file-sync/onboarding-state (keyword type)] true)))
-      (catch :default e
-        (js/console.warn "[onboarding SKIP] " (name type) e)))))

+ 0 - 7
src/main/frontend/components/header.cljs

@@ -7,7 +7,6 @@
             [frontend.common.missionary :as c.m]
             [frontend.components.block :as component-block]
             [frontend.components.export :as export]
-            [frontend.components.file-sync :as fs-sync]
             [frontend.components.page-menu :as page-menu]
             [frontend.components.plugins :as plugins]
             [frontend.components.right-sidebar :as sidebar]
@@ -436,12 +435,6 @@
        (when db-based?
          (semantic-search-progressing current-repo))
 
-       (when (and current-repo
-                  (not (config/demo-graph? current-repo))
-                  (not db-based?)
-                  (user-handler/alpha-or-beta-user?))
-         (fs-sync/indicator))
-
        (when (and (not= (state/get-current-route) :home)
                   (not custom-home-page?))
          (home-button))

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

@@ -7,7 +7,6 @@
             [frontend.components.content :as content]
             [frontend.components.db-based.page :as db-page]
             [frontend.components.editor :as editor]
-            [frontend.components.file-based.hierarchy :as hierarchy]
             [frontend.components.icon :as icon-component]
             [frontend.components.library :as library]
             [frontend.components.objects :as objects]
@@ -724,10 +723,6 @@
                                              :refs-count (:refs-count option)})
                  (str title "-refs"))])
 
-            (when-not block-or-whiteboard?
-              (when (and (not journal?) (not db-based?))
-                (hierarchy/structures (:block/title page))))
-
             (when-not (or whiteboard? unlinked-refs?
                           sidebar?
                           tag-dialog?

+ 9 - 55
src/main/frontend/components/page_menu.cljs

@@ -7,11 +7,8 @@
             [frontend.db :as db]
             [frontend.handler.common.developer :as dev-common-handler]
             [frontend.handler.db-based.page :as db-page-handler]
-            [frontend.handler.file-sync :as file-sync-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.page :as page-handler]
-            [frontend.handler.shell :as shell]
-            [frontend.handler.user :as user-handler]
             [frontend.mobile.util :as mobile-util]
             [frontend.state :as state]
             [frontend.util :as util]
@@ -37,9 +34,7 @@
          {:title [:h3.text-lg.leading-6.font-medium.flex.gap-2.items-center
                   [:span.top-1.relative
                    (shui/tabler-icon "alert-triangle")]
-                  (if (config/db-based-graph? (state/get-current-repo))
-                    (t :page/db-delete-confirmation)
-                    (t :page/delete-confirmation))]
+                  (t :page/db-delete-confirmation)]
           :content [:p.opacity-60 (str "- " (:block/title page))]
           :outside-cancel? true})
         (p/then #(delete-page! page))
@@ -49,24 +44,16 @@
   [page]
   (when-let [page-name (and page (db/page? page) (:block/name page))]
     (let [repo (state/sub :git/current-repo)
-          db-based? (config/db-based-graph? repo)
-          page-title (if db-based? (str (:block/uuid page)) (:block/title page))
+          page-title (str (:block/uuid page))
           whiteboard? (ldb/whiteboard? page)
           block? (and page (util/uuid-string? page-name) (not whiteboard?))
           contents? (= page-name "contents")
-          public? (if db-based?
-                    (:logseq.property/publishing-public? page)
-                    (get-in page [:block/properties :public]))
+          public? (:logseq.property/publishing-public? page)
           _favorites-updated? (state/sub :favorites/updated?)
           favorited? (page-handler/favorited? page-title)
           developer-mode? (state/sub [:ui/developer-mode?])
           file-rpath (when (util/electron?) (page-util/get-page-file-rpath page-name))
-          _ (state/sub :auth/id-token)
-          file-sync-graph-uuid (and (user-handler/logged-in?)
-                                    (file-sync-handler/enable-sync?)
-                                    ;; FIXME: Sync state is not cleared when switching to a new graph
-                                    (file-sync-handler/current-graph-sync-on?)
-                                    (file-sync-handler/get-current-graph-uuid))]
+          _ (state/sub :auth/id-token)]
       (when (not block?)
         (->>
          [(when-not config/publishing?
@@ -79,30 +66,14 @@
                            (page-handler/<unfavorite-page! page-title)
                            (page-handler/<favorite-page! page-title)))}})
 
-          (when (and (or (util/electron?) file-sync-graph-uuid) (not db-based?))
-            {:title   (t :page/version-history)
-             :options {:on-click
-                       (fn []
-                         (cond
-                           file-sync-graph-uuid
-                           (state/pub-event! [:graph/pick-page-histories file-sync-graph-uuid page-name])
-
-                           (util/electron?)
-                           (shell/get-file-latest-git-log page 100)
-
-                           :else
-                           nil))
-                       :class "cp__btn_history_version"}})
-
           (when (or (util/electron?)
                     (mobile-util/native-platform?))
             {:title   (t :page/copy-page-url)
-             :options {:on-click #(page-handler/copy-page-url (if db-based? (:block/uuid page) page-title))}})
+             :options {:on-click #(page-handler/copy-page-url (:block/uuid page))}})
 
           (when-not (or contents?
                         config/publishing?
-                        (and db-based?
-                             (:logseq.property/built-in? page)))
+                        (:logseq.property/built-in? page))
             {:title   (t :page/delete)
              :options {:on-click #(delete-page-confirm! page)}})
 
@@ -135,25 +106,18 @@
                           page
                           (if public? false true)))}})
 
-          (when (and (util/electron?) file-rpath
-                     (not (file-sync-handler/synced-file-graph? repo)))
-            {:title   (t :page/open-backup-directory)
-             :options {:on-click
-                       (fn []
-                         (ipc/ipc "openFileBackupDir" (config/get-local-dir repo) file-rpath))}})
-
           (when config/lsp-enabled?
             (for [[_ {:keys [label] :as cmd} action pid] (state/get-plugins-commands-with-type :page-menu-item)]
               {:title label
                :options {:on-click #(commands/exec-plugin-simple-command!
                                      pid (assoc cmd :page page-name) action)}}))
 
-          (when (and db-based? (ldb/internal-page? page) (not (:logseq.property/built-in? page)))
+          (when (and (ldb/internal-page? page) (not (:logseq.property/built-in? page)))
             {:title (t :page/convert-to-tag)
              :options {:on-click (fn []
                                    (db-page-handler/convert-page-to-tag! page))}})
 
-          (when (and db-based? (ldb/class? page) (not (:logseq.property/built-in? page)))
+          (when (and (ldb/class? page) (not (:logseq.property/built-in? page)))
             {:title (t :page/convert-tag-to-page)
              :options {:on-click (fn []
                                    (db-page-handler/convert-tag-to-page! page))}})
@@ -161,16 +125,6 @@
           (when developer-mode?
             {:title   (t :dev/show-page-data)
              :options {:on-click (fn []
-                                   (dev-common-handler/show-entity-data (:db/id page)))}})
-
-          (when (and developer-mode?
-                     ;; Remove when we have an easy way to fetch file content for a DB graph
-                     (not db-based?))
-            {:title   (t :dev/show-page-ast)
-             :options {:on-click (fn []
-                                   (let [page (db/pull '[:block/format {:block/file [:file/content]}] (:db/id page))]
-                                     (dev-common-handler/show-content-ast
-                                      (get-in page [:block/file :file/content])
-                                      (get page :block/format :markdown))))}})]
+                                   (dev-common-handler/show-entity-data (:db/id page)))}})]
          (flatten)
          (remove nil?))))))

+ 7 - 45
src/main/frontend/components/query.cljs

@@ -1,16 +1,12 @@
 (ns frontend.components.query
   (:require [clojure.string :as string]
-            [frontend.components.file-based.query :as file-query]
-            [frontend.components.file-based.query-table :as query-table]
             [frontend.components.query.result :as query-result]
             [frontend.components.query.view :as query-view]
-            [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.db-mixins :as db-mixins]
             [frontend.db.react :as react]
             [frontend.extensions.sci :as sci]
-            [frontend.handler.editor :as editor-handler]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
@@ -24,17 +20,14 @@
     (when (seq queries)
       (boolean (some #(= % title) (map :title queries))))))
 
-;; TODO: Split this into file and DB graph versions. DB graph needlessly coupled to file graph args
 (rum/defcs custom-query-inner < rum/static
-  [state {:keys [db-graph? dsl-query?] :as config} {:keys [query breadcrumb-show?]}
+  [state {:keys [dsl-query?] :as config} {:keys [query breadcrumb-show?]}
    {:keys [query-error-atom
            current-block
-           table?
-           page-list?
            view-f
            result
            group-by-page?]}]
-  (let [{:keys [->hiccup ->elem inline-text page-cp map-inline]} config
+  (let [{:keys [->hiccup]} config
         *query-error query-error-atom
         only-blocks? (:block/uuid (first result))
         blocks-grouped-by-page? (and group-by-page?
@@ -60,7 +53,7 @@
                            (str error)]))]
            (util/hiccup-keywordize result))
 
-         (and db-graph? (not (:built-in-query? config)))
+         (not (:built-in-query? config))
          (when-let [query-block (:logseq.property/query current-block)]
            (when-not (string/blank? (:block/title query-block))
              (query-view/query-result (assoc config
@@ -68,10 +61,6 @@
                                              :query query)
                                       current-block result)))
 
-         (and (not db-graph?)
-              (or page-list? table?))
-         (query-table/result-table config current-block result {:page? page-list?} map-inline page-cp ->elem inline-text)
-
          ;; Normally displays built-in-query results
          (and (seq result) (or only-blocks? blocks-grouped-by-page?))
          (->hiccup result
@@ -136,7 +125,7 @@
              (assoc state
                     ::result (atom nil)
                     ::collapsed? (atom (or (:collapsed? q) (:collapsed? config))))))}
-  [state {:keys [*query-error db-graph? dsl-query? built-in-query? table? current-block] :as config}
+  [state {:keys [*query-error dsl-query? built-in-query? table? current-block] :as config}
    {:keys [builder query view _collapsed?] :as q}]
   (let [*result (::result state)
         *collapsed? (::collapsed? state)
@@ -169,18 +158,6 @@
       [:code (if dsl-query? (str "Results for " (pr-str query)) "Advanced query results")]
       (when-not (and built-in-query? (empty? result))
         [:div.custom-query (get config :attr {})
-         (when (and (not db-graph?) (not built-in-query?))
-           (file-query/custom-query-header config
-                                           q
-                                           {:query-error-atom *query-error
-                                            :current-block current-block
-                                            :table? table?
-                                            :view-f view-f
-                                            :page-list? page-list?
-                                            :*result *result
-                                            :result result
-                                            :collapsed? collapsed?}))
-
          (when (and dsl-query? builder) builder)
 
          (if built-in-query?
@@ -198,38 +175,23 @@
 
 (rum/defcs custom-query < rum/static
   {:init (fn [state]
-           (let [db-graph? (config/db-based-graph? (state/get-current-repo))
-                 [{:keys [dsl-query? built-in-query?] :as config}
-                  {:keys [collapsed?]}] (:rum/args state)]
-             ;; collapsed? not needed for db graphs
-             (when (not db-graph?)
-               (when-not (or built-in-query? dsl-query?)
-                 (when collapsed?
-                   (editor-handler/collapse-block! (or (:block/uuid (:block config))
-                                                       (:block/uuid config)))))))
            (assoc state :query-error (atom nil)))}
   [state {:keys [built-in-query?] :as config}
-   {:keys [query collapsed?] :as q}]
+   {:keys [collapsed?] :as q}]
   (ui/catch-error
    (ui/block-error "Query Error:" {:content (:query q)})
    (let [*query-error (:query-error state)
-         db-graph? (config/db-based-graph? (state/get-current-repo))
          current-block-uuid (or (:block/uuid (:block config))
                                 (:block/uuid config))
          current-block (db/entity [:block/uuid current-block-uuid])
         ;; Get query result
-         collapsed?' (calculate-collapsed? current-block current-block-uuid {:collapsed? (if-not db-graph? collapsed? false)})
+         collapsed?' (calculate-collapsed? current-block current-block-uuid {:collapsed? false})
          built-in-collapsed? (and collapsed? built-in-query?)
-         table? (when-not db-graph?
-                  (or (get-in current-block [:block/properties :query-table])
-                      (and (string? query) (string/ends-with? (string/trim query) "table"))))
          config' (assoc config
-                        :db-graph? db-graph?
                         :current-block current-block
                         :current-block-uuid current-block-uuid
                         :collapsed? collapsed?'
-                        :table? table?
                         :built-in-query? (built-in-custom-query? (:title q))
                         :*query-error *query-error)]
-     (when (or built-in-collapsed? (not db-graph?) (not collapsed?'))
+     (when (or built-in-collapsed? (not collapsed?'))
        (custom-query* config' q)))))

+ 28 - 112
src/main/frontend/components/repo.cljs

@@ -1,20 +1,16 @@
 (ns frontend.components.repo
   (:require [clojure.string :as string]
-            [frontend.common.async-util :as async-util]
             [frontend.components.rtc.indicator :as rtc-indicator]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.handler.db-based.rtc :as rtc-handler]
             [frontend.handler.db-based.rtc-flows :as rtc-flows]
-            [frontend.handler.file-based.native-fs :as nfs-handler]
-            [frontend.handler.file-sync :as file-sync]
             [frontend.handler.graph :as graph]
             [frontend.handler.notification :as notification]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.route :as route-handler]
             [frontend.handler.user :as user-handler]
-            [frontend.mobile.util :as mobile-util]
             [frontend.state :as state]
             [frontend.ui :as ui]
             [frontend.util :as util]
@@ -29,22 +25,15 @@
             [rum.core :as rum]))
 
 (rum/defc normalized-graph-label
-  [{:keys [url remote? graph-e2ee? GraphName GraphUUID] :as graph} on-click]
-  (let [db-based? (config/db-based-graph? url)]
-    (when graph
-      [:span.flex.items-center
-       (if (or (config/local-file-based-graph? url)
-               db-based?)
-         (let [local-dir (config/get-local-dir url)
-               graph-name (text-util/get-graph-name-from-path url)]
-           [:a.flex.items-center {:title local-dir
-                                  :on-click #(on-click graph)}
-            [:span graph-name (when (and GraphName (not db-based?)) [:strong.pl-1 "(" GraphName ")"])]
-            (when remote? [:strong.px-1.flex.items-center (ui/icon (if graph-e2ee? "lock" "cloud"))])])
-         [:a.flex.items-center {:title GraphUUID
-                                :on-click #(on-click graph)}
-          (db/get-repo-path (or url GraphName))
-          (when remote? [:strong.pl-1.flex.items-center (ui/icon "cloud")])])])))
+  [{:keys [url remote? graph-e2ee?] :as graph} on-click]
+  (when graph
+    [:span.flex.items-center
+     (let [local-dir (config/get-local-dir url)
+           graph-name (text-util/get-graph-name-from-path url)]
+       [:a.flex.items-center {:title local-dir
+                              :on-click #(on-click graph)}
+        [:span graph-name]
+        (when remote? [:strong.px-1.flex.items-center (ui/icon (if graph-e2ee? "lock" "cloud"))])])]))
 
 (defn sort-repos-with-metadata-local
   [repos]
@@ -68,8 +57,7 @@
   [repos]
   (for [{:keys [root url remote? GraphUUID GraphSchemaVersion GraphName created-at last-seen-at] :as repo}
         (sort-repos-with-metadata-local repos)
-        :let [db-based? (config/db-based-graph? url)
-              graph-name (if db-based? (config/db-graph-name url) GraphName)]]
+        :let [graph-name (config/db-graph-name url)]]
     [:div.flex.justify-between.mb-2.items-center.group {:key (or url GraphUUID)
                                                         "data-testid" url}
      [:div
@@ -81,7 +69,7 @@
                                      root ; exists locally
                                      (state/pub-event! [:graph/switch url])
 
-                                     (and db-based? remote?)
+                                     remote?
                                      (state/pub-event! [:rtc/download-remote-graph GraphName GraphUUID GraphSchemaVersion])
 
                                      :else
@@ -97,8 +85,7 @@
           {:on-click #(util/open-url (str "file://" root))}
           (shui/tabler-icon "folder-pin") [:span.pl-1 root]])
 
-       (let [db-graph? (config/db-based-graph? url)
-             manager? (and db-graph? (user-handler/manager? url))]
+       (let [manager? (user-handler/manager? url)]
          (shui/dropdown-menu
           (shui/dropdown-menu-trigger
            {:asChild true}
@@ -114,20 +101,16 @@
               {:key "delete-locally"
                :class "delete-local-graph-menu-item"
                :on-click (fn []
-                           (let [prompt-str (if db-based?
-                                              (str "Are you sure you want to permanently delete the graph \"" graph-name "\" from Logseq?")
-                                              (str "Are you sure you want to unlink the graph \"" url "\" from local folder?"))]
+                           (let [prompt-str (str "Are you sure you want to permanently delete the graph \"" graph-name "\" from Logseq?")]
                              (-> (shui/dialog-confirm!
                                   [:p.font-medium.-my-4 prompt-str
                                    [:span.my-2.flex.font-normal.opacity-75
-                                    (if db-based?
-                                      [:small "⚠️ Notice that we can't recover this graph after being deleted. Make sure you have backups before deleting it."]
-                                      [:small "⚠️ It won't remove your local files!"])]])
+                                    [:small "⚠️ Notice that we can't recover this graph after being deleted. Make sure you have backups before deleting it."]]])
                                  (p/then (fn []
                                            (repo-handler/remove-repo! repo)
                                            (state/pub-event! [:graph/unlinked repo (state/get-current-repo)]))))))}
               "Delete local graph"))
-           (when (and db-based? root
+           (when (and root
                       (user-handler/logged-in?)
                       (user-handler/rtc-group?)
                       (not remote?)
@@ -155,33 +138,8 @@
                                    (rtc-flows/trigger-rtc-start repo)
                                    (rtc-handler/<get-remote-graphs)))))))}
               "Use Logseq sync (Alpha testing)"))
-           (when (and remote? (or (and db-based? manager?) (not db-based?)))
-             (shui/dropdown-menu-item
-              {:key "delete-remotely"
-               :class "delete-remote-graph-menu-item"
-               :on-click (fn []
-                           (let [prompt-str (str "Are you sure you want to permanently delete the graph \"" graph-name "\" from our server?")]
-                             (-> (shui/dialog-confirm!
-                                  [:p.font-medium.-my-4 prompt-str
-                                   [:span.my-2.flex.font-normal.opacity-75
-                                    [:small "⚠️ Notice that we can't recover this graph after being deleted. Make sure you have backups before deleting it."]]])
-                                 (p/then
-                                  (fn []
-                                    (when (or manager? (not db-graph?))
-                                      (let [<delete-graph (if db-graph?
-                                                            rtc-handler/<rtc-delete-graph!
-                                                            (fn [graph-uuid _graph-schema-version]
-                                                              (async-util/c->p (file-sync/<delete-graph graph-uuid))))]
-                                        (state/set-state! [:file-sync/remote-graphs :loading] true)
-                                        (when (= (state/get-current-repo) repo)
-                                          (state/<invoke-db-worker :thread-api/rtc-stop))
-                                        (p/do! (<delete-graph GraphUUID GraphSchemaVersion)
-                                               (state/delete-remote-graph! repo)
-                                               (state/set-state! [:file-sync/remote-graphs :loading] false)
-                                               (rtc-handler/<get-remote-graphs)))))))))}
-              "Delete from server"))
 
-           (when (and remote? db-based? (not manager?))
+           (when (and remote? (not manager?))
              (shui/dropdown-menu-item
               {:key "leave-shared-graph"
                :class "leave-shared-graph-menu-item"
@@ -209,9 +167,7 @@
   (let [login? (boolean (state/sub :auth/id-token))
         repos (state/sub [:me :repos])
         repos (util/distinct-by :url repos)
-        remotes (concat
-                 (state/sub :rtc/graphs)
-                 (state/sub [:file-sync/remote-graphs :graphs]))
+        remotes (state/sub :rtc/graphs)
         remotes-loading? (state/sub [:file-sync/remote-graphs :loading])
         repos (if (and login? (seq remotes))
                 (repo-handler/combine-local-&-remote-graphs repos remotes) repos)
@@ -241,8 +197,7 @@
        (when (seq local-graphs)
          (repos-inner local-graphs))]
 
-      (when (and (or (file-sync/enable-sync?)
-                     (user-handler/rtc-group?))
+      (when (and (user-handler/rtc-group?)
                  (seq remote-graphs)
                  login?)
         [:<>
@@ -257,10 +212,7 @@
                 (when remotes-loading? [:small.pl-2 (ui/loading nil)])]
                :background "gray"
                :disabled remotes-loading?
-               :on-click (fn []
-                           (when-not (util/capacitor?)
-                             (file-sync/load-session-graphs))
-                           (rtc-handler/<get-remote-graphs)))]]
+               :on-click (fn [] (rtc-handler/<get-remote-graphs)))]]
             (repos-inner own-graphs)])
 
          (when (seq shared-graphs)
@@ -275,15 +227,8 @@
                        (remove (fn [repo] (= current-repo (:url repo))) repos) repos) ; exclude current repo
         repo-links (mapv
                     (fn [{:keys [url remote? graph-e2ee? rtc-graph? GraphName GraphSchemaVersion GraphUUID] :as graph}]
-                      (let [local? (config/local-file-based-graph? url)
-                            db-only? (config/db-based-graph? url)
-                            repo-url (cond
-                                       local? (db/get-repo-name url)
-                                       db-only? url
-                                       :else GraphName)
-                            short-repo-name (if (or local? db-only?)
-                                              (text-util/get-graph-name-from-path repo-url)
-                                              GraphName)
+                      (let [repo-url url
+                            short-repo-name (text-util/get-graph-name-from-path repo-url)
                             downloading? (and downloading-graph-id (= GraphUUID downloading-graph-id))]
                         (when short-repo-name
                           {:title [:span.flex.items-center.title-wrap short-repo-name
@@ -315,34 +260,10 @@
                     switch-repos)]
     (->> repo-links (remove nil?))))
 
-(defn- repos-footer [multiple-windows? db-based?]
+(defn- repos-footer []
   [:div.cp__repos-quick-actions
    {:on-click #(shui/popup-hide!)}
 
-   (when (and (not db-based?)
-              (not (config/demo-graph?)))
-     [:<>
-      (shui/button {:size :sm :variant :ghost
-                    :title (t :sync-from-local-files-detail)
-                    :on-click (fn []
-                                (state/pub-event! [:graph/ask-for-re-fresh]))}
-                   (shui/tabler-icon "file-report") [:span (t :sync-from-local-files)])
-
-      (shui/button {:size :sm :variant :ghost
-                    :title (t :re-index-detail)
-                    :on-click (fn []
-                                (state/pub-event! [:graph/ask-for-re-index multiple-windows? nil]))}
-                   (shui/tabler-icon "folder-bolt") [:span (t :re-index)])])
-
-   (when (util/electron?)
-     (shui/button {:size :sm :variant :ghost
-                   :on-click (fn []
-                               (if (or (nfs-handler/supported?) (mobile-util/native-platform?))
-                                 (state/pub-event! [:graph/setup-a-repo])
-                                 (route-handler/redirect-to-all-graphs)))}
-                  (shui/tabler-icon "folder-plus")
-                  [:span (t :new-graph)]))
-
    (when-not config/publishing?
      (shui/button
       {:size :sm :variant :ghost
@@ -368,25 +289,22 @@
 (rum/defcs repos-dropdown-content < rum/reactive
   [_state & {:keys [contentid footer?] :as opts
              :or {footer? true}}]
-  (let [multiple-windows? false
-        current-repo (state/sub :git/current-repo)
+  (let [current-repo (state/sub :git/current-repo)
         login? (boolean (state/sub :auth/id-token))
         repos (state/sub [:me :repos])
-        remotes (state/sub [:file-sync/remote-graphs :graphs])
         rtc-graphs (state/sub :rtc/graphs)
         downloading-graph-id (state/sub :rtc/downloading-graph-uuid)
         remotes-loading? (state/sub [:file-sync/remote-graphs :loading])
-        db-based? (config/db-based-graph? current-repo)
         repos (sort-repos-with-metadata-local repos)
         repos (distinct
-               (if (and (or (seq remotes) (seq rtc-graphs)) login?)
-                 (repo-handler/combine-local-&-remote-graphs repos (concat remotes rtc-graphs)) repos))
+               (if (and (seq rtc-graphs) login?)
+                 (repo-handler/combine-local-&-remote-graphs repos rtc-graphs) repos))
         items-fn #(repos-dropdown-links repos current-repo downloading-graph-id opts)
         header-fn #(when (> (count repos) 1) ; show switch to if there are multiple repos
                      [:div.font-medium.md:text-sm.md:opacity-50.p-2.flex.flex-row.justify-between.items-center
                       [:h4.pb-1 (t :left-side-bar/switch)]
 
-                      (when (and (file-sync/enable-sync?) login?)
+                      (when login?
                         (if remotes-loading?
                           (ui/loading "")
                           (shui/button
@@ -395,7 +313,6 @@
                             :title "Refresh remote graphs"
                             :class "!h-6 !px-1 relative right-[-4px]"
                             :on-click (fn []
-                                        (file-sync/load-session-graphs)
                                         (rtc-handler/<get-remote-graphs))}
                            (ui/icon "refresh" {:size 15}))))])
         _remote? (and current-repo (:remote? (first (filter #(= current-repo (:url %)) repos))))
@@ -426,7 +343,7 @@
                    [:span.flex.items-center.gap-1.w-full
                     icon [:div title]]))))))]
      (when footer?
-       (repos-footer multiple-windows? db-based?))]))
+       (repos-footer))]))
 
 (rum/defcs graphs-selector < rum/reactive
   [_state]
@@ -434,7 +351,6 @@
         user-repos (state/get-repos)
         current-repo' (some->> user-repos (medley/find-first #(= current-repo (:url %))))
         repo-name (when current-repo (db/get-repo-name current-repo))
-        db-based? (config/db-based-graph? current-repo)
         remote? (:remote? current-repo')
         short-repo-name (if current-repo
                           (db/get-short-repo-name repo-name)
@@ -448,7 +364,7 @@
                                      {:as-dropdown? true
                                       :content-props {:class "repos-list"}
                                       :align :start}))}
-      [:span.thumb (shui/tabler-icon (if remote? "cloud" (if db-based? "topology-star" "folder")) {:size 16})]
+      [:span.thumb (shui/tabler-icon (if remote? "cloud" "topology-star") {:size 16})]
       [:strong short-repo-name]
       (shui/tabler-icon "selector" {:size 18})]]))
 

+ 3 - 73
src/main/frontend/components/settings.cljs

@@ -5,7 +5,6 @@
             [frontend.colors :as colors]
             [frontend.common.missionary :as c.m]
             [frontend.components.assets :as assets]
-            [frontend.components.file-sync :as fs]
             [frontend.components.shortcut :as shortcut]
             [frontend.components.svg :as svg]
             [frontend.config :as config]
@@ -16,7 +15,6 @@
             [frontend.handler.config :as config-handler]
             [frontend.handler.db-based.rtc :as rtc-handler]
             [frontend.handler.db-based.vector-search-flows :as vector-search-flows]
-            [frontend.handler.file-sync :as file-sync-handler]
             [frontend.handler.global-config :as global-config-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.plugin :as plugin-handler]
@@ -843,34 +841,6 @@
      ;;  [:p (t :settings-page/clear-cache-warning)])
      ]))
 
-(rum/defc sync-enabled-switcher
-  [enabled?]
-  (ui/toggle enabled?
-             (fn []
-               (file-sync-handler/set-sync-enabled! (not enabled?)))
-             true))
-
-(rum/defc sync-diff-merge-enabled-switcher
-  [enabled?]
-  (ui/toggle enabled?
-             (fn []
-               (file-sync-handler/set-sync-diff-merge-enabled! (not enabled?)))
-             true))
-
-(defn sync-switcher-row [enabled?]
-  (row-with-button-action
-   {:left-label (t :settings-page/sync)
-    :action (sync-enabled-switcher enabled?)}))
-
-(defn sync-diff-merge-switcher-row [enabled?]
-  (row-with-button-action
-   {:left-label (str (t :settings-page/sync-diff-merge) " (Experimental!)") ;; Not included in i18n to avoid outdating translations
-    :action (sync-diff-merge-enabled-switcher enabled?)
-    :desc (ui/tooltip [:span.inline-flex.px-1 (svg/info)]
-                      [:div
-                       [:div (t :settings-page/sync-diff-merge-desc)]
-                       [:div (t :settings-page/sync-diff-merge-warn)]])}))
-
 (rum/defc whiteboards-enabled-switcher
   [enabled?]
   (ui/toggle enabled?
@@ -928,9 +898,7 @@
 
 (rum/defc ^:large-vars/cleanup-todo settings-account < rum/reactive
   []
-  (let [current-graph-uuid (state/sub-current-file-sync-graph-uuid)
-        graph-usage (state/get-remote-graph-usage)
-        current-graph-is-remote? ((set (map :uuid graph-usage)) current-graph-uuid)
+  (let [graph-usage (state/get-remote-graph-usage)
         logged-in? (user-handler/logged-in?)
         user-info (state/get-user-info)
         paid-user? (#{"active" "on_trial" "cancelled"} (:LemonStatus user-info))
@@ -963,16 +931,7 @@
                                          :on-click user-handler/upgrade})
               :else nil)]
            (settings-account-usage-graphs pro-account? graph-usage)
-           (settings-account-usage-description pro-account? graph-usage)
-           (if current-graph-is-remote?
-             (ui/button "Deactivate syncing" {:class "p-1 h-8 justify-center"
-                                              :disabled true
-                                              :background "gray"
-                                              :icon "cloud-off"})
-             (ui/button "Activate syncing" {:class "p-1 h-8 justify-center"
-                                            :background "blue"
-                                            :icon "cloud"
-                                            :on-click #(fs/maybe-onboarding-show :sync-initiate)}))]]
+           (settings-account-usage-description pro-account? graph-usage)]]
          (when has-subscribed?
            [:<>
             [:div "Billing"]
@@ -1078,8 +1037,6 @@
   (let [current-repo (state/get-current-repo)
         enable-journals? (state/enable-journals? current-repo)
         enable-flashcards? (state/enable-flashcards? current-repo)
-        enable-sync? (state/enable-sync?)
-        enable-sync-diff-merge? (state/enable-sync-diff-merge?)
         db-based? (config/db-based-graph? current-repo)
         enable-whiteboards? (state/enable-whiteboards? current-repo)
         logged-in? (user-handler/logged-in?)]
@@ -1120,34 +1077,7 @@
                                   :on-click (fn []
                                               (state/close-settings!)
                                               (state/pub-event! [:user/login]))})
-           [:p.text-sm.opacity-50 (t :settings-page/login-prompt)]])])
-
-     (when-not web-platform?
-       [:<>
-        [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
-         [:label.flex.font-medium.leading-5.self-start.mt-1
-          (ui/icon  (if logged-in? "lock-open" "lock") {:class "mr-1"}) (t :settings-page/beta-features)]]
-        [:div.flex.flex-col.gap-4
-         {:class (when-not user-handler/alpha-or-beta-user? "opacity-50 pointer-events-none cursor-not-allowed")}
-         (sync-switcher-row enable-sync?)
-         (when (and enable-sync? (not (config/db-based-graph? current-repo)))
-           (sync-diff-merge-switcher-row enable-sync-diff-merge?))
-         [:div.text-sm
-          (t :settings-page/sync-desc-1)
-          [:a.mx-1 {:href "https://blog.logseq.com/how-to-setup-and-use-logseq-sync/"
-                    :target "_blank"}
-           (t :settings-page/sync-desc-2)]
-          (t :settings-page/sync-desc-3)]]])]))
-
-     ;; (when-not web-platform?
-     ;;   [:<>
-     ;;    [:hr]
-;;    [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center
-     ;;     [:label.flex.font-medium.leading-5.self-start.mt-1 (ui/icon  (if logged-in? "lock-open" "lock") {:class "mr-1"}) (t :settings-page/alpha-features)]]
-     ;;    [:div.flex.flex-col.gap-4
-     ;;     {:class (when-not user-handler/alpha-user? "opacity-50 pointer-events-none cursor-not-allowed")}
-     ;;     ;; features
-     ;;     ]])
+           [:p.text-sm.opacity-50 (t :settings-page/login-prompt)]])])]))
 
 (def DEFAULT-ACTIVE-TAB-STATE (if config/ENABLE-SETTINGS-ACCOUNT-TAB [:account :account] [:general :general]))
 

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

@@ -6,7 +6,6 @@
             [frontend.common-keywords]
             [frontend.components.plugins :as plugins]
             [frontend.config :as config]
-            [frontend.fs.sync :as sync]
             [frontend.handler :as handler]
             [frontend.handler.db-based.rtc-background-tasks]
             [frontend.handler.db-based.vector-search-background-tasks]
@@ -62,10 +61,7 @@
 
     (.render ^js root (page/current-page))
 
-    (display-welcome-message)
-    ;; NO repo state here, better not add init logic here
-    (when config/dev?
-      (js/setTimeout #(sync/<sync-start) 1000))))
+    (display-welcome-message)))
 
 (comment
   (def d-entity-count (volatile! 0))
@@ -96,8 +92,6 @@
   ;; stop is called before any code is reloaded
   ;; this is controlled by :before-load in the config
   (handler/stop!)
-  (when config/dev?
-    (sync/<sync-stop))
   (js/console.log "stop"))
 
 (defn ^:export delay-remount

+ 0 - 3322
src/main/frontend/fs/sync.cljs

@@ -1,3322 +0,0 @@
-(ns frontend.fs.sync
-  "Main ns for providing file sync functionality"
-  (:require ["path" :as node-path]
-            [cljs-http.client :as http]
-            [cljs-time.coerce :as tc]
-            [cljs-time.core :as t]
-            [cljs-time.format :as tf]
-            [cljs.core.async :as async :refer [<! >! chan go go-loop offer!
-                                               poll! timeout]]
-            [cljs.core.async.impl.channels]
-            [cljs.core.async.interop :refer [p->c]]
-            [cljs.spec.alpha :as s]
-            [clojure.pprint :as pp]
-            [clojure.set :as set]
-            [clojure.string :as string]
-            [electron.ipc :as ipc]
-            [frontend.common.async-util :as async-util]
-            [frontend.config :as config]
-            [frontend.context.i18n :refer [t]]
-            [frontend.db :as db]
-            [frontend.debug :as debug]
-            [frontend.diff :as diff]
-            [frontend.encrypt :as encrypt]
-            [frontend.fs :as 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.pubsub :as pubsub]
-            [frontend.state :as state]
-            [frontend.util :as util]
-            [frontend.util.fs :as fs-util]
-            [frontend.util.persist-var :as persist-var]
-            [goog.string :as gstring]
-            [lambdaisland.glogi :as log]
-            [logseq.common.path :as path]
-            [logseq.common.util :as common-util]
-            [medley.core :refer [dedupe-by]]
-            [promesa.core :as p]
-            [rum.core :as rum]))
-
-;;; ### Commentary
-;; file-sync related local files/dirs:
-;; - logseq/graphs-txid.edn
-;;   this file contains [user-uuid graph-uuid transaction-id]
-;;   graph-uuid: the unique identifier of the graph on the server
-;;   transaction-id: sync progress of local files
-;; - logseq/version-files
-;;   downloaded version-files
-;; files included by `get-ignored-files` will not be synchronized.
-;;
-;; sync strategy:
-;; - when toggle file-sync on,
-;;   trigger remote->local-full-sync first, then local->remote-full-sync
-;;   local->remote-full-sync will compare local-files with remote-files (by md5),
-;;   and upload new-added-files to remote server.
-;; - if local->remote sync(normal-sync or full-sync) return :need-sync-remote,
-;;   then trigger a remote->local sync
-;; - if remote->local sync return :need-remote->local-full-sync,
-;;   then we need a remote->local-full-sync,
-;;   which compare local-files with remote-files, sync diff-remote-files to local
-;; - local->remote-full-sync will be triggered after 20mins of idle
-;; - every 10s, flush local changes, and sync to remote
-
-;; TODO: use access-token instead of id-token
-;; TODO: a remote delete-diff cause local related-file deleted, then trigger a `FileChangeEvent`,
-;;       and re-produce a new same-file-delete diff.
-
-;;; ### specs
-(s/def ::state #{;; do following jobs when ::starting:
-                 ;; - wait seconds for file-change-events from file-watcher
-                 ;; - drop redundant file-change-events
-                 ;; - setup states in `frontend.state`
-                 ::starting
-                 ::need-password
-                 ::idle
-                 ;; sync local-changed files
-                 ::local->remote
-                 ;; sync remote latest-transactions
-                 ::remote->local
-                 ;; local->remote full sync
-                 ::local->remote-full-sync
-                 ;; remote->local full sync
-                 ::remote->local-full-sync
-                 ;; snapshot state when switching between apps on iOS
-                 ::pause
-                 ::stop})
-(s/def ::path string?)
-(s/def ::time t/date?)
-(s/def ::remote->local-type #{:delete :update
-                              ;; :rename=:delete+:update
-                              })
-(s/def ::current-syncing-graph-uuid (s/or :nil nil? :graph-uuid string?))
-(s/def ::recent-remote->local-file-item (s/keys :req-un [::remote->local-type ::checksum ::path]))
-(s/def ::current-local->remote-files (s/coll-of ::path :kind set?))
-(s/def ::current-remote->local-files (s/coll-of ::path :kind set?))
-(s/def ::recent-remote->local-files (s/coll-of ::recent-remote->local-file-item :kind set?))
-(s/def ::history-item (s/keys :req-un [::path ::time]))
-(s/def ::history (s/coll-of ::history-item :kind seq?))
-(s/def ::sync-state (s/keys :req-un [::current-syncing-graph-uuid
-                                     ::state
-                                     ::current-local->remote-files
-                                     ::current-remote->local-files
-                                     ::queued-local->remote-files
-                                     ;; Downloading files from remote will trigger filewatcher events,
-                                     ;; causes unreasonable information in the content of ::queued-local->remote-files,
-                                     ;; use ::recent-remote->local-files to filter such events
-                                     ::recent-remote->local-files
-                                     ::history]))
-
-;; diff
-(s/def ::TXId pos-int?)
-(s/def ::TXType #{"update_files" "delete_files" "rename_file"})
-(s/def ::TXContent-to-path string?)
-(s/def ::TXContent-from-path (s/or :some string? :none nil?))
-(s/def ::TXContent-checksum (s/or :some string? :none nil?))
-(s/def ::TXContent-item (s/tuple ::TXContent-to-path
-                                 ::TXContent-from-path
-                                 ::TXContent-checksum))
-(s/def ::TXContent (s/coll-of ::TXContent-item))
-(s/def ::diff (s/keys :req-un [::TXId ::TXType ::TXContent]))
-
-(s/def ::succ-map #(= {:succ true} %))
-(s/def ::unknown-map (comp some? :unknown))
-(s/def ::stop-map #(= {:stop true} %))
-(s/def ::pause-map #(= {:pause true} %))
-(s/def ::need-sync-remote #(= {:need-sync-remote true} %))
-(s/def ::graph-has-been-deleted #(= {:graph-has-been-deleted true} %))
-
-(s/def ::sync-local->remote!-result
-  (s/or :stop ::stop-map
-        :succ ::succ-map
-        :pause ::pause-map
-        :need-sync-remote ::need-sync-remote
-        :graph-has-been-deleted ::graph-has-been-deleted
-        :unknown ::unknown-map))
-
-(s/def ::sync-remote->local!-result
-  (s/or :succ ::succ-map
-        :need-remote->local-full-sync
-        #(= {:need-remote->local-full-sync true} %)
-        :stop ::stop-map
-        :pause ::pause-map
-        :unknown ::unknown-map))
-
-(s/def ::sync-local->remote-all-files!-result
-  (s/or :succ ::succ-map
-        :stop ::stop-map
-        :need-sync-remote ::need-sync-remote
-        :graph-has-been-deleted ::graph-has-been-deleted
-        :unknown ::unknown-map))
-
-;; sync-event type
-(s/def ::event #{:created-local-version-file
-                 :finished-local->remote
-                 :finished-remote->local
-                 :start
-                 :pause
-                 :resume
-                 :exception-decrypt-failed
-                 :remote->local-full-sync-failed
-                 :local->remote-full-sync-failed
-                 :get-remote-graph-failed
-                 :get-deletion-logs-failed})
-
-(s/def ::sync-event (s/keys :req-un [::event ::data]))
-
-(defonce download-batch-size 100)
-(defonce upload-batch-size 20)
-(def ^:private current-sm-graph-uuid (atom nil))
-
-;;; ### configs in config.edn
-;; - :file-sync/ignore-files
-
-(defn- get-ignored-files
-  []
-  (into #{#"logseq/graphs-txid.edn$"
-          #"logseq/pages-metadata.edn$"
-          #"logseq/version-files/"
-          #"logseq/bak/"
-          #"node_modules/"
-          ;; path starts with `.` in the root directory, e.g. .gitignore
-          #"^\.[^.]+"
-          ;; path includes `/.`, e.g. .git, .DS_store
-          #"/\."
-          ;; Emacs/Vim backup files end with `~` by default
-          #"~$"}
-        (map re-pattern)
-        (:file-sync/ignore-files (state/get-config))))
-
-;;; ### configs ends
-
-(defn- guard-ex
-  [x]
-  (when (instance? ExceptionInfo x) x))
-
-(def ws-addr config/WS-URL)
-
-;; Warning: make sure to `persist-var/-load` graphs-txid before using it.
-(defonce graphs-txid (persist-var/persist-var nil "graphs-txid"))
-
-(declare assert-local-txid<=remote-txid)
-(defn <update-graphs-txid!
-  [latest-txid graph-uuid user-uuid repo]
-  {:pre [(int? latest-txid) (>= latest-txid 0)]}
-  (-> (p/let [_ (persist-var/-reset-value! graphs-txid [user-uuid graph-uuid latest-txid] repo)
-              _ (persist-var/persist-save graphs-txid)]
-        (when (state/developer-mode?) (assert-local-txid<=remote-txid)))
-      p->c))
-
-(defn clear-graphs-txid! [repo]
-  (persist-var/-reset-value! graphs-txid nil repo)
-  (persist-var/persist-save graphs-txid))
-
-(defn- ws-ping-loop [ws]
-  (go-loop []
-    (let [state (.-readyState ws)]
-      ;; not closing or closed state
-      (when (not (contains? #{2 3} state))
-        (if (not= 1 state)
-          ;; when connecting, wait 1s
-          (do (<! (timeout 1000))
-              (recur))
-          (do (.send ws "PING")
-              ;; aws apigateway websocket
-              ;; Idle Connection Timeout: 10min
-              (<! (timeout (* 5 60 1000)))
-              (recur)))))))
-
-(defn- ws-stop! [*ws]
-  (when *ws
-    (swap! *ws (fn [o] (assoc o :stop true)))
-    (when-let [ws (:ws @*ws)]
-      (.close ws))))
-
-(def *ws-connect-retries (atom 0))
-(declare <sync-stop)
-(defn- ws-listen!*
-  [graph-uuid *ws remote-changes-chan]
-  (reset! *ws {:ws (js/WebSocket. (util/format ws-addr graph-uuid)) :stop false})
-  (ws-ping-loop (:ws @*ws))
-  (set! (.-onopen (:ws @*ws)) #(reset! *ws-connect-retries 0))
-  (set! (.-onclose (:ws @*ws)) (fn [_e]
-                                 (if (> @*ws-connect-retries 3)
-                                   (do (println "ws-listen! retry count > 3, stop retry")
-                                       (reset! *ws-connect-retries 0)
-                                       (swap! *ws (fn [o] (assoc o :stop true)))
-                                       (<sync-stop))
-                                   (when-not (true? (:stop @*ws))
-                                     (go
-                                       (timeout 1000)
-                                       (println "re-connecting graph" graph-uuid)
-                                       (swap! *ws-connect-retries inc)
-                                       (ws-listen!* graph-uuid *ws remote-changes-chan))))))
-  (set! (.-onmessage (:ws @*ws)) (fn [e]
-                                   (let [data (js->clj (js/JSON.parse (.-data e)) :keywordize-keys true)]
-                                     (when (some? (:txid data))
-                                       (if-let [v (poll! remote-changes-chan)]
-                                         (let [last-txid (:txid v)
-                                               current-txid (:txid data)]
-                                           (if (> last-txid current-txid)
-                                             (offer! remote-changes-chan v)
-                                             (offer! remote-changes-chan data)))
-                                         (offer! remote-changes-chan data)))))))
-
-(defn ws-listen!
-  "return channel which output messages from server"
-  [graph-uuid *ws]
-  (let [remote-changes-chan (chan (async/sliding-buffer 1))]
-    (ws-listen!* graph-uuid *ws remote-changes-chan)
-    remote-changes-chan))
-
-(defn- get-json-body [body]
-  (or (and (not (string? body)) body)
-      (or (string/blank? body) nil)
-      (try (js->clj (js/JSON.parse body) :keywordize-keys true)
-           (catch :default e
-             (prn :invalid-json body)
-             e))))
-
-(defn- get-resp-json-body [resp]
-  (-> resp (:body) (get-json-body)))
-
-(defn- <request-once [api-name body token]
-  (go
-    (let [resp (http/post (str "https://" config/API-DOMAIN "/file-sync/" api-name)
-                          {:oauth-token token
-                           :body (js/JSON.stringify (clj->js body))
-                           :with-credentials? false})]
-      {:resp (<! resp)
-       :api-name api-name
-       :body body})))
-
-;; For debug
-(def *on-flying-request
-  "requests not finished"
-  (atom #{}))
-
-(def stoppable-apis #{"get_all_files"})
-
-(defn- <request*
-  "max retry count is 5.
-  *stop: volatile var, stop retry-request when it's true,
-          and return :stop"
-  ([api-name body token *stop] (<request* api-name body token 0 *stop))
-  ([api-name body token retry-count *stop]
-   (go
-     (if (and *stop @*stop (contains? stoppable-apis api-name))
-       :stop
-       (let [resp (<! (<request-once api-name body token))]
-         (if (and
-              (= 401 (get-in resp [:resp :status]))
-              (= "Unauthorized" (:message (get-json-body (get-in resp [:resp :body])))))
-           (if (> retry-count 5)
-             (throw (js/Error. :file-sync-request))
-             (do (println "will retry after" (min 60000 (* 1000 retry-count)) "ms")
-                 (<! (timeout (min 60000 (* 1000 retry-count))))
-                 (<! (<request* api-name body token (inc retry-count) *stop))))
-           (:resp resp)))))))
-
-(defn <request [api-name & args]
-  (let [name (str api-name (.now js/Date))]
-    (go (swap! *on-flying-request conj name)
-        (let [r (<! (apply <request* api-name args))]
-          (swap! *on-flying-request disj name)
-          r))))
-
-(defn- remove-dir-prefix [dir path]
-  (let [r (string/replace path (js/RegExp. (str "^" (gstring/regExpEscape dir))) "")]
-    (if (string/starts-with? r "/")
-      (string/replace-first r "/" "")
-      r)))
-
-(defn- remove-user-graph-uuid-prefix
-  "<user-uuid>/<graph-uuid>/path -> path"
-  [path]
-  (let [parts (string/split path "/")]
-    (if (and (< 2 (count parts))
-             (= 36 (count (parts 0)))
-             (= 36 (count (parts 1))))
-      (util/string-join-path (drop 2 parts))
-      path)))
-
-(defprotocol IRelativePath
-  (-relative-path [this]))
-
-(defn relative-path [o]
-  (let [repo-dir (config/get-repo-dir (state/get-current-repo))]
-    (cond
-      (implements? IRelativePath o)
-      (-relative-path o)
-
-      ;; full path
-      (and (string? o) (string/starts-with? o repo-dir))
-      (string/replace o (str repo-dir "/") "")
-
-      (string? o)
-      (remove-user-graph-uuid-prefix o)
-
-      :else
-      (throw (js/Error. (str "unsupported type " (str o)))))))
-
-(defprotocol IChecksum
-  (-checksum [this]))
-
-(defprotocol IStoppable
-  (-stop! [this]))
-(defprotocol IStopped?
-  (-stopped? [this]))
-;from-path, to-path is relative path
-(deftype FileTxn [from-path to-path updated? deleted? txid checksum]
-  Object
-  (renamed? [_]
-    (not= from-path to-path))
-
-  IRelativePath
-  (-relative-path [_] (remove-user-graph-uuid-prefix to-path))
-
-  IEquiv
-  (-equiv [_ ^FileTxn other]
-    (and (= from-path (.-from-path other))
-         (= to-path (.-to-path other))
-         (= updated? (.-updated? other))
-         (= deleted? (.-deleted? other))))
-  IHash
-  (-hash [_] (hash [from-path to-path updated? deleted?]))
-
-  IComparable
-  (-compare [_ ^FileTxn other]
-    (compare txid (.-txid other)))
-
-  IPrintWithWriter
-  (-pr-writer [coll w _opts]
-    (write-all w "#FileTxn[\"" from-path "\" -> \"" to-path
-               "\" (updated? " updated? ", renamed? " (.renamed? coll) ", deleted? " deleted?
-               ", txid " txid ", checksum " checksum ")]")))
-
-(defn- assert-filetxns
-  [filetxns]
-  (every? true?
-          (mapv
-           (fn [^FileTxn filetxn]
-             (if (.-updated? filetxn)
-               (some? (-checksum filetxn))
-               true))
-           filetxns)))
-
-(defn- diff->filetxns
-  "convert diff(`<get-diff`) to `FileTxn`"
-  [{:keys [TXId TXType TXContent]}]
-  {:post [(assert-filetxns %)]}
-  (let [update? (= "update_files" TXType)
-        delete? (= "delete_files" TXType)
-        update-xf
-        (comp
-         (remove #(or (empty? (first %))
-                      (empty? (last %))))
-         (map #(->FileTxn (first %) (first %) update? delete? TXId (last %))))
-        delete-xf
-        (comp
-         (remove #(empty? (first %)))
-         (map #(->FileTxn (first %) (first %) update? delete? TXId nil)))
-        rename-xf
-        (comp
-         (remove #(or (empty? (first %))
-                      (empty? (second %))))
-         (map #(->FileTxn (second %) (first %) false false TXId nil)))
-        xf (case TXType
-             "delete_files" delete-xf
-             "update_files" update-xf
-             "rename_file" rename-xf)]
-    (sequence xf TXContent)))
-
-(defn- distinct-update-filetxns-xf
-  "transducer.
-  remove duplicate update&delete `FileTxn`s."
-  [rf]
-  (let [seen-update&delete-filetxns (volatile! #{})]
-    (fn
-      ([] (rf))
-      ([result] (rf result))
-      ([result ^FileTxn filetxn]
-       (if (and
-            (or (.-updated? filetxn) (.-deleted? filetxn))
-            (contains? @seen-update&delete-filetxns filetxn))
-         result
-         (do (vswap! seen-update&delete-filetxns conj filetxn)
-             (rf result filetxn)))))))
-
-(defn- remove-deleted-filetxns-xf
-  "transducer.
-  remove update&rename filetxns if they are deleted later(in greater txid filetxn)."
-  [rf]
-  (let [seen-deleted-paths (volatile! #{})]
-    (fn
-      ([] (rf))
-      ([result] (rf result))
-      ([result ^FileTxn filetxn]
-       (let [to-path (.-to-path filetxn)
-             from-path (.-from-path filetxn)]
-         (if (contains? @seen-deleted-paths to-path)
-           (do (when (not= to-path from-path)
-                 (vswap! seen-deleted-paths disj to-path)
-                 (vswap! seen-deleted-paths conj from-path))
-               result)
-           (do (vswap! seen-deleted-paths conj to-path)
-               (rf result filetxn))))))))
-
-(defn- partition-filetxns
-  "return transducer.
-  partition filetxns, at most N update-filetxns in each partition,
-  for delete and rename type, only one filetxn in each partition."
-  [n]
-  (comp
-   (partition-by #(.-updated? ^FileTxn %))
-   (map (fn [ts]
-          (if (some-> ^js (first ts) (.-updated?))
-            (partition-all n ts)
-            (map list ts))))
-   cat))
-
-(defn- contains-path? [regexps path]
-  (reduce #(when (re-find %2 path) (reduced true)) false regexps))
-
-(defn ignored?
-  "Whether file is ignored when syncing."
-  [path]
-  (->
-   (get-ignored-files)
-   (contains-path? (relative-path path))
-   (boolean)))
-
-(defn- filter-download-files-with-reserved-chars
-  "Skip downloading file paths with reserved chars."
-  [files]
-  (let [f #(and
-            (not (.-deleted? ^js %))
-            (fs-util/include-reserved-chars? (-relative-path %)))
-        reserved-files (filter f files)]
-    (when (seq reserved-files)
-      (state/pub-event! [:ui/notify-skipped-downloading-files
-                         (map -relative-path reserved-files)])
-      (prn "Skipped downloading those file paths with reserved chars: "
-           (map -relative-path reserved-files)))
-    (remove f files)))
-
-(defn- filter-upload-files-with-reserved-chars
-  "Remove upoading file paths with reserved chars."
-  [paths]
-  (let [path-string? (string? (first paths))
-        f (if path-string?
-            fs-util/include-reserved-chars?
-            #(fs-util/include-reserved-chars? (-relative-path %)))]
-    (vec (remove f paths))))
-
-(defn- diffs->filetxns
-  "transducer.
-  1. diff -> `FileTxn` , see also `<get-diff`
-  2. distinct redundant update type filetxns
-  3. remove update or rename filetxns if they are deleted in later filetxns.
-  NOTE: this xf should apply on reversed diffs sequence (sort by txid)"
-  []
-  (comp
-   (map diff->filetxns)
-   cat
-   (remove ignored?)
-   distinct-update-filetxns-xf
-   remove-deleted-filetxns-xf))
-
-(defn- diffs->partitioned-filetxns
-  "partition filetxns, each partition contains same type filetxns,
-   for update type, at most N items in each partition
-   for delete & rename type, only 1 item in each partition."
-  [n]
-  (comp
-   (diffs->filetxns)
-   (partition-filetxns n)))
-
-(defn- filepath+checksum->diff
-  [index {relative-path' :relative-path :keys [checksum user-uuid graph-uuid]}]
-  {:post [(s/valid? ::diff %)]}
-  {:TXId (inc index)
-   :TXType "update_files"
-   :TXContent [[(util/string-join-path [user-uuid graph-uuid relative-path']) nil checksum]]})
-
-(defn filepath+checksum-coll->partitioned-filetxns
-  "transducer.
-  1. filepath+checksum-coll -> diff
-  2. diffs->partitioned-filetxns
-  3. filter by config"
-  [n graph-uuid user-uuid]
-  (comp
-   (map (fn [p]
-          {:relative-path (first p) :user-uuid user-uuid :graph-uuid graph-uuid :checksum (second p)}))
-   (map-indexed filepath+checksum->diff)
-   (diffs->partitioned-filetxns n)))
-
-(deftype FileMetadata [size etag path encrypted-path last-modified remote? txid ^:mutable normalized-path]
-  Object
-  (get-normalized-path [_]
-    (assert (string? path) path)
-    (when-not normalized-path
-      (set! normalized-path
-            (cond-> path
-              (string/starts-with? path "/") (string/replace-first "/" "")
-              remote? (remove-user-graph-uuid-prefix))))
-    normalized-path)
-
-  IRelativePath
-  (-relative-path [_] path)
-
-  IEquiv
-  (-equiv [o ^FileMetadata other]
-    (and (= (.get-normalized-path o) (.get-normalized-path other))
-         (= etag (.-etag other))))
-
-  IHash
-  (-hash [_] (hash {:etag etag :path path}))
-
-  ILookup
-  (-lookup [o k] (-lookup o k nil))
-  (-lookup [_ k not-found]
-    (case k
-      :size size
-      :etag etag
-      :path path
-      :encrypted-path encrypted-path
-      :last-modified last-modified
-      :remote? remote?
-      :txid txid
-      not-found))
-
-  IPrintWithWriter
-  (-pr-writer [_ w _opts]
-    (write-all w (str {:size size :etag etag :path path :remote? remote? :txid txid :last-modified last-modified}))))
-
-(def ^:private higher-priority-remote-files
-  "when diff all remote files and local files, following remote files always need to download(when checksum not matched),
-  even local-file's last-modified > remote-file's last-modified.
-  because these files will be auto created when the graph created, we dont want them to re-write related remote files."
-  #{"pages/contents.md" "pages/contents.org"
-    "logseq/metadata.edn"})
-
-(def ^:private ignore-default-value-files
-  "when create a new local graph, some files will be created (config.edn, custom.css).
-  And related remote files wins if these files have default template value."
-  #{"logseq/config.edn" "logseq/custom.css"})
-
-(def ^:private empty-custom-css-md5 "d41d8cd98f00b204e9800998ecf8427e")
-
-;; TODO: use fn some to filter FileMetadata here, it cause too much loop
-(defn diff-file-metadata-sets
-  "Find the `FileMetadata`s that exists in s1 and does not exist in s2,
-  compare by path+checksum+last-modified,
-  if s1.path = s2.path & s1.checksum <> s2.checksum
-  (except some default created files),
-  keep this `FileMetadata` in result"
-  [s1 s2]
-  (reduce
-   (fn [result item]
-     (let [path (:path item)
-           lower-case-path (some-> path string/lower-case)
-           ;; encrypted-path (:encrypted-path item)
-           checksum (:etag item)
-           last-modified (:last-modified item)]
-       (if (some
-            #(cond
-               (not= lower-case-path (some-> (:path %) string/lower-case))
-               false
-               (= checksum (:etag %))
-               true
-               (>= last-modified (:last-modified %))
-               false
-               ;; these special files have higher priority in s1
-               (contains? higher-priority-remote-files path)
-               false
-               ;; higher priority in s1 when config.edn=default value or empty custom.css
-               (and (contains? ignore-default-value-files path)
-                    (#{config/config-default-content-md5 empty-custom-css-md5} (:etag %)))
-               false
-               ;; special handling for css & edn files
-               (and
-                (or (string/ends-with? lower-case-path ".css")
-                    (string/ends-with? lower-case-path ".edn"))
-                (< last-modified (:last-modified %)))
-               true)
-            s2)
-         result
-         (conj result item))))
-   #{} s1))
-
-(comment
-  (defn map->FileMetadata [m]
-    (apply ->FileMetadata ((juxt :size :etag :path :encrypted-path :last-modified :remote? (constantly nil)) m)))
-
-  (assert
-   (=
-    #{(map->FileMetadata {:size 1 :etag 2 :path 2 :encrypted-path 2 :last-modified 2})}
-    (diff-file-metadata-sets
-     (into #{}
-           (map map->FileMetadata)
-           [{:size 1 :etag 1 :path 1 :encrypted-path 1 :last-modified 1}
-            {:size 1 :etag 2 :path 2 :encrypted-path 2 :last-modified 2}])
-     (into #{}
-           (map map->FileMetadata)
-           [{:size 1 :etag 1 :path 1 :encrypted-path 1 :last-modified 1}
-            {:size 1 :etag 1 :path 2 :encrypted-path 2 :last-modified 1}])))))
-
-(extend-protocol IChecksum
-  FileMetadata
-  (-checksum [this] (.-etag this))
-  FileTxn
-  (-checksum [this] (.-checksum this)))
-
-(defn- sort-file-metadata-fn
-  ":recent-days-range > :favorite-pages > small-size pages > ...
-  :recent-days-range : [<min-inst-ms> <max-inst-ms>]
-"
-  [& {:keys [recent-days-range favorite-pages]}]
-  {:pre [(or (nil? recent-days-range)
-             (every? number? recent-days-range))]}
-  (let [favorite-pages* (set favorite-pages)]
-    (fn [^FileMetadata item]
-      (let [path (relative-path item)
-            journal-dir (node-path/join (config/get-journals-directory) node-path/sep)
-            journal? (string/starts-with? path journal-dir)
-            journal-day
-            (when journal?
-              (try
-                (tc/to-long
-                 (tf/parse (tf/formatter "yyyy_MM_dd")
-                           (-> path
-                               (string/replace-first journal-dir "")
-                               (string/replace-first ".md" ""))))
-                (catch :default _)))]
-        (cond
-          (and recent-days-range
-               journal-day
-               (<= (first recent-days-range)
-                   ^number journal-day
-                   (second recent-days-range)))
-          journal-day
-
-          (string/includes? path "logseq/")
-          9999
-
-          (string/includes? path "content.")
-          10000
-
-          (contains? favorite-pages* path)
-          (count path)
-
-          :else
-          (- (.-size item)))))))
-;;; ### path-normalize
-(def path-normalize
-
-  common-util/path-normalize)
-
-;;; ### APIs
-;; `RSAPI` call apis through rsapi package, supports operations on files
-
-(defprotocol IRSAPI
-  (rsapi-ready? [this graph-uuid] "return true when rsapi ready")
-  (<key-gen [this] "generate public+private keys")
-  (<set-env [this graph-uuid prod? private-key public-key] "set environment")
-  (<get-local-files-meta [this graph-uuid base-path filepaths] "get local files' metadata")
-  (<get-local-all-files-meta [this graph-uuid base-path] "get all local files' metadata")
-  (<rename-local-file [this graph-uuid base-path from to])
-  (<update-local-files [this graph-uuid base-path filepaths] "remote -> local")
-  (<fetch-remote-files [this graph-uuid base-path filepaths] "remote -> local version-db")
-  (<download-version-files [this graph-uuid base-path filepaths])
-  (<delete-local-files [this graph-uuid base-path filepaths])
-  (<update-remote-files [this graph-uuid base-path filepaths local-txid] "local -> remote, return err or txid")
-  (<delete-remote-files [this graph-uuid base-path filepaths local-txid] "return err or txid")
-  (<encrypt-fnames [this graph-uuid fnames])
-  (<decrypt-fnames [this graph-uuid fnames])
-  (<cancel-all-requests [this])
-  (<add-new-version [this repo path content]))
-
-(defprotocol IRemoteAPI
-  (<user-info [this] "user info")
-  (<get-remote-all-files-meta [this graph-uuid] "get all remote files' metadata")
-  (<get-remote-files-meta [this graph-uuid filepaths] "get remote files' metadata")
-  (<get-remote-graph [this graph-name-opt graph-uuid-opt] "get graph info by GRAPH-NAME-OPT or GRAPH-UUID-OPT")
-  (<get-remote-txid [this graph-uuid] "get remote graph's txid")
-  (<get-remote-file-versions [this graph-uuid filepath] "get file's version list")
-  (<list-remote-graphs [this] "list all remote graphs")
-  (<get-deletion-logs [this graph-uuid from-txid] "get deletion logs from FROM-TXID")
-  (<get-diff [this graph-uuid from-txid] "get diff from FROM-TXID, return [txns, latest-txid, min-txid]")
-  (<create-graph [this graph-name] "create graph")
-  (<delete-graph [this graph-uuid] "delete graph")
-  (<get-graph-salt [this graph-uuid] "return httpcode 410 when salt expired")
-  (<create-graph-salt [this graph-uuid] "return httpcode 409 when salt already exists and not expired yet")
-  (<get-graph-encrypt-keys [this graph-uuid])
-  (<upload-graph-encrypt-keys [this graph-uuid public-key encrypted-private-key]))
-
-(defprotocol IRemoteControlAPI
-  "api functions provided for outside the sync process"
-  (<delete-remote-files-control [this graph-uuid filepaths]))
-
-(defprotocol IToken
-  (<get-token [this]))
-
-(defn <case-different-local-file-exist?
-  "e.g. filepath=\"pages/Foo.md\"
-  found-filepath=\"pages/foo.md\"
-  it happens on macos (case-insensitive fs)
-
-  return canonicalized filepath if exists"
-  [graph-uuid irsapi base-path filepath]
-  (go
-    (let [r (<! (<get-local-files-meta irsapi graph-uuid base-path [filepath]))]
-      (when (some-> r first :path (not= filepath))
-        (-> r first :path)))))
-
-(defn <local-file-not-exist?
-  [graph-uuid irsapi base-path filepath]
-  (go
-    (let [r (<! (<get-local-files-meta irsapi graph-uuid base-path [filepath]))]
-
-      (or
-       ;; not found at all
-       (empty? r)
-       ;; or,
-       ;; e.g. filepath="pages/Foo.md"
-       ;; found-filepath="pages/foo.md"
-       ;; it happens on macos (case-insensitive fs)
-       (not= filepath (:path (first r)))))))
-
-(defn- <retry-rsapi [f]
-  (go-loop [n 3]
-    (let [r (<! (f))]
-      (when (instance? ExceptionInfo r)
-        (js/console.error "rsapi error:" (str (ex-cause r))))
-      (if (and (instance? ExceptionInfo r)
-               (string/index-of (str (ex-cause r)) "operation timed out")
-               (> n 0))
-        (do
-          (print (str "retry(" n ") ..."))
-          (recur (dec n)))
-        r))))
-
-(declare <rsapi-cancel-all-requests)
-
-(defn- <build-local-file-metadatas
-  [this graph-uuid r]
-  (go-loop [[[path metadata] & others] (js->clj r)
-            result #{}]
-    (if-not (and path metadata)
-      ;; finish
-      result
-      (let [normalized-path (path-normalize path)
-            encryptedFname  (if (not= path normalized-path)
-                              (first (<! (<encrypt-fnames this graph-uuid [normalized-path])))
-                              (get metadata "encryptedFname"))]
-        (recur others
-               (conj result
-                     (->FileMetadata (get metadata "size") (get metadata "md5") normalized-path
-                                     encryptedFname (get metadata "mtime") false nil nil)))))))
-
-(deftype RSAPI [^: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 (js->clj (<! (p->c (ipc/ipc "key-gen")))
-                             :keywordize-keys true)))
-  (<set-env [_ graph-uuid prod? private-key public-key]
-    (when (not-empty private-key)
-      (print (util/format "[%s] setting sync age-encryption passphrase..." graph-uuid)))
-    (set! graph-uuid' graph-uuid)
-    (set! private-key' private-key)
-    (set! public-key' public-key)
-    (p->c (ipc/ipc "set-env" graph-uuid (if prod? "prod" "dev") private-key public-key)))
-  (<get-local-all-files-meta [this graph-uuid base-path]
-    (go
-      (let [r (<! (<retry-rsapi #(p->c (ipc/ipc "get-local-all-files-meta" graph-uuid base-path))))]
-        (or (guard-ex r)
-            (<! (<build-local-file-metadatas this graph-uuid r))))))
-  (<get-local-files-meta [this graph-uuid base-path filepaths]
-    (go
-      (let [r (<! (<retry-rsapi #(p->c (ipc/ipc "get-local-files-meta" graph-uuid base-path filepaths))))]
-        (assert (not (instance? ExceptionInfo r)) "get-local-files-meta shouldn't return exception")
-        (<! (<build-local-file-metadatas this graph-uuid r)))))
-  (<rename-local-file [_ graph-uuid base-path from to]
-    (<retry-rsapi #(p->c (ipc/ipc "rename-local-file" graph-uuid base-path
-                                  (path-normalize from)
-                                  (path-normalize to)))))
-  (<update-local-files [this graph-uuid base-path filepaths]
-    (println "update-local-files" graph-uuid base-path filepaths)
-    (go
-      (<! (<rsapi-cancel-all-requests))
-      (let [token-or-exp (<! (<get-token this))]
-        (or (guard-ex token-or-exp)
-            (<! (p->c (ipc/ipc "update-local-files" graph-uuid base-path filepaths token-or-exp)))))))
-  (<fetch-remote-files [this graph-uuid base-path filepaths]
-    (go
-      (<! (<rsapi-cancel-all-requests))
-      (let [token-or-exp (<! (<get-token this))]
-        (or (guard-ex token-or-exp)
-            (<! (p->c (ipc/ipc "fetch-remote-files" graph-uuid base-path filepaths 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 (ipc/ipc "download-version-files" graph-uuid base-path filepaths token-or-exp))))))))
-
-  (<delete-local-files [_ graph-uuid base-path filepaths]
-    (let [normalized-filepaths (mapv path-normalize filepaths)]
-      (go
-        (println "delete-local-files" filepaths)
-        (let [r (<! (<retry-rsapi #(p->c (ipc/ipc "delete-local-files" graph-uuid base-path normalized-filepaths))))]
-          r))))
-
-  (<update-remote-files [this graph-uuid base-path filepaths local-txid]
-    (let [normalized-filepaths (mapv path-normalize filepaths)]
-      (go
-        (<! (<rsapi-cancel-all-requests))
-        (let [token-or-exp (<! (<get-token this))]
-          (or (guard-ex token-or-exp)
-              (<! (<retry-rsapi
-                   #(p->c (ipc/ipc "update-remote-files" graph-uuid base-path normalized-filepaths local-txid token-or-exp)))))))))
-
-  (<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))]
-          (or (guard-ex token-or-exp)
-              (<!
-               (<retry-rsapi
-                #(p->c
-                  (ipc/ipc "delete-remote-files" graph-uuid base-path normalized-filepaths local-txid token-or-exp)))))))))
-
-  (<encrypt-fnames [_ graph-uuid fnames] (go (js->clj (<! (p->c (ipc/ipc "encrypt-fnames" graph-uuid fnames))))))
-  (<decrypt-fnames [_ graph-uuid fnames] (go
-                                           (let [r (<! (p->c (ipc/ipc "decrypt-fnames" graph-uuid fnames)))]
-                                             (if (instance? ExceptionInfo r)
-                                               (ex-info "decrypt-failed" {:fnames fnames} (ex-cause r))
-                                               (js->clj r)))))
-  (<cancel-all-requests [_]
-    (p->c (ipc/ipc "cancel-all-requests")))
-
-  (<add-new-version [_this repo path content]
-    (p->c (ipc/ipc "addVersionFile" (config/get-local-dir repo) path content))))
-
-(def rsapi (cond
-             (util/electron?)
-             (->RSAPI nil nil nil)
-
-             :else
-             nil))
-
-(defn add-new-version-file
-  [repo path content]
-  (<add-new-version rsapi repo path content))
-
-(defn <rsapi-cancel-all-requests []
-  (go
-    (when rsapi
-      (<! (<cancel-all-requests rsapi)))))
-
-;;; ### remote & rs api exceptions
-(defn sync-stop-when-api-flying?
-  [exp]
-  (some-> (ex-data exp) :err (= :stop)))
-
-(defn storage-exceed-limit?
-  [exp]
-  (some->> (ex-data exp)
-           :err
-           ((juxt :status (comp :message :body)))
-           ((fn [[status msg]] (and (= 403 status) (= msg "storage-limit"))))))
-
-(defn graph-count-exceed-limit?
-  [exp]
-  (some->> (ex-data exp)
-           :err
-           ((juxt :status (comp :message :body)))
-           ((fn [[status msg]] (and (= 403 status) (= msg "graph-count-exceed-limit"))))))
-
-(defn decrypt-exp?
-  [exp]
-  (some-> exp ex-message #(= % "decrypt-failed")))
-
-;;; remote api exceptions ends
-
-;;; ### sync events
-
-(defn- put-sync-event!
-  [val]
-  (async/put! pubsub/sync-events-ch val))
-
-(def ^:private debug-print-sync-events-loop-stop-chan (chan 1))
-(defn debug-print-sync-events-loop
-  ([] (debug-print-sync-events-loop [:created-local-version-file
-                                     :finished-local->remote
-                                     :finished-remote->local
-                                     :pause
-                                     :resume
-                                     :exception-decrypt-failed
-                                     :remote->local-full-sync-failed
-                                     :local->remote-full-sync-failed]))
-  ([topics]
-   (util/drain-chan debug-print-sync-events-loop-stop-chan)
-   (let [topic&chs (map (juxt identity #(chan 10)) topics)
-         out-ch (chan 10)
-         out-mix (async/mix out-ch)]
-     (doseq [[topic ch] topic&chs]
-       (async/sub pubsub/sync-events-pub topic ch)
-       (async/admix out-mix ch))
-     (go-loop []
-       (let [{:keys [val stop]}
-             (async/alt!
-               debug-print-sync-events-loop-stop-chan {:stop true}
-               out-ch ([v] {:val v}))]
-         (cond
-           stop (do (async/unmix-all out-mix)
-                    (doseq [[topic ch] topic&chs]
-                      (async/unsub pubsub/sync-events-pub topic ch)))
-
-           val (do (pp/pprint [:debug :sync-event val])
-                   (recur))))))))
-
-(defn stop-debug-print-sync-events-loop
-  []
-  (offer! debug-print-sync-events-loop-stop-chan true))
-
-;;; sync events ends
-
-(defn- fire-file-sync-storage-exceed-limit-event!
-  [exp]
-  (when (storage-exceed-limit? exp)
-    (state/pub-event! [:file-sync/storage-exceed-limit])
-    true))
-
-(defn- fire-file-sync-graph-count-exceed-limit-event!
-  [exp]
-  (when (graph-count-exceed-limit? exp)
-    (state/pub-event! [:file-sync/graph-count-exceed-limit])
-    true))
-
-(deftype RemoteAPI [*stopped?]
-  Object
-
-  (<request [this api-name body]
-    (go
-      (let [token-or-exp (<! (<get-token this))]
-        (or (guard-ex token-or-exp)
-            (let [resp (<! (<request api-name body token-or-exp *stopped?))]
-              (if (http/unexceptional-status? (:status resp))
-                (get-resp-json-body resp)
-                (let [exp (ex-info "request failed"
-                                   {:err          resp
-                                    :body         (:body resp)
-                                    :api-name     api-name
-                                    :request-body body})]
-                  (fire-file-sync-storage-exceed-limit-event! exp)
-                  (fire-file-sync-graph-count-exceed-limit-event! exp)
-                  exp)))))))
-
-;; for test
-  (update-files [this graph-uuid txid files]
-    {:pre [(map? files)
-           (number? txid)]}
-    (.<request this "update_files" {:GraphUUID graph-uuid :TXId txid :Files files}))
-
-  IToken
-  (<get-token [_this]
-    (user/<wrap-ensure-id&access-token
-     (state/get-auth-id-token))))
-
-(defn- filter-files-with-unnormalized-path
-  [file-meta-list encrypted-path->path-map]
-  (let [path->encrypted-path-map (set/map-invert encrypted-path->path-map)
-        raw-paths (vals encrypted-path->path-map)
-        *encrypted-paths-to-drop (transient [])]
-    (loop [[raw-path & other-paths] raw-paths]
-      (when raw-path
-        (let [normalized-path (path-normalize raw-path)]
-          (when (not= normalized-path raw-path)
-            (println :filter-files-with-unnormalized-path raw-path)
-            (conj! *encrypted-paths-to-drop (get path->encrypted-path-map raw-path))))
-        (recur other-paths)))
-    (let [encrypted-paths-to-drop (set (persistent! *encrypted-paths-to-drop))]
-      (filterv #(not (contains? encrypted-paths-to-drop (:encrypted-path %))) file-meta-list))))
-
-(defn- filter-case-different-same-files
-  "filter case-different-but-same-name files, last-modified one wins"
-  [file-meta-list encrypted-path->path-map]
-  (let [seen (volatile! {})]
-    (loop [result-file-meta-list (transient {})
-           [f & others] file-meta-list]
-      (if f
-        (let [origin-path (get encrypted-path->path-map (:encrypted-path f))
-              _ (assert (some? origin-path) f)
-              path (string/lower-case origin-path)
-              last-modified (:last-modified f)
-              last-modified-seen (get @seen path)]
-          (cond
-            (or (and path (nil? last-modified-seen))
-                (and path (some? last-modified-seen) (> last-modified last-modified-seen)))
-            ;; 1. not found in seen
-            ;; 2. found in seen, but current f wins
-            (do (vswap! seen conj [path last-modified])
-                (recur (conj! result-file-meta-list [path f]) others))
-
-            (and path (some? last-modified-seen) (<= last-modified last-modified-seen))
-            ;; found in seen, and seen-f has more recent last-modified epoch
-            (recur result-file-meta-list others)
-
-            :else
-            (do (println :debug-filter-case-different-same-files:unreachable f path)
-                (recur result-file-meta-list others))))
-        (vals (persistent! result-file-meta-list))))))
-
-(extend-type RemoteAPI
-  IRemoteAPI
-  (<user-info [this]
-    (user/<wrap-ensure-id&access-token
-     (<! (.<request this "user_info" {}))))
-  (<get-remote-all-files-meta [this graph-uuid]
-    (user/<wrap-ensure-id&access-token
-     (let [file-meta-list      (transient #{})
-           encrypted-path-list (transient [])
-           exp-r
-           (<!
-            (go-loop [continuation-token nil]
-              (let [r (<! (.<request this "get_all_files"
-                                     (into
-                                      {}
-                                      (remove (comp nil? second)
-                                              {:GraphUUID graph-uuid :ContinuationToken continuation-token}))))]
-                (or (guard-ex r)
-                    (let [next-continuation-token (:NextContinuationToken r)
-                          objs                    (:Objects r)]
-                      (apply conj! encrypted-path-list (map (comp remove-user-graph-uuid-prefix :Key) objs))
-                      (apply conj! file-meta-list
-                             (map
-                              #(hash-map :checksum (:checksum %)
-                                         :encrypted-path (remove-user-graph-uuid-prefix (:Key %))
-                                         :size (:Size %)
-                                         :last-modified (:LastModified %)
-                                         :txid (:Txid %))
-                              objs))
-                      (when-not (empty? next-continuation-token)
-                        (recur next-continuation-token)))))))]
-       (or (guard-ex exp-r)
-           (let [file-meta-list*      (persistent! file-meta-list)
-                 encrypted-path-list* (persistent! encrypted-path-list)
-                 path-list-or-exp     (<! (<decrypt-fnames rsapi graph-uuid encrypted-path-list*))]
-             (or (guard-ex path-list-or-exp)
-                 (let [encrypted-path->path-map (zipmap encrypted-path-list* path-list-or-exp)]
-                   (set
-                    (mapv
-                     #(->FileMetadata (:size %)
-                                      (:checksum %)
-                                      (get encrypted-path->path-map (:encrypted-path %))
-                                      (:encrypted-path %)
-                                      (:last-modified %)
-                                      true
-                                      (:txid %)
-                                      nil)
-                     (-> file-meta-list*
-                         (filter-files-with-unnormalized-path encrypted-path->path-map)
-                         (filter-case-different-same-files encrypted-path->path-map)))))))))))
-
-  (<get-remote-files-meta [this graph-uuid filepaths]
-    {:pre [(coll? filepaths)]}
-    (user/<wrap-ensure-id&access-token
-     (let [encrypted-paths* (<! (<encrypt-fnames rsapi graph-uuid filepaths))
-           r                (<! (.<request this "get_files_meta" {:GraphUUID graph-uuid :Files encrypted-paths*}))]
-       (or (guard-ex r)
-           (let [encrypted-paths (mapv :FilePath r)
-                 paths-or-exp    (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths))]
-             (or (guard-ex paths-or-exp)
-                 (let [encrypted-path->path-map (zipmap encrypted-paths paths-or-exp)]
-                   (into #{}
-                         (comp
-                          (filter #(not= "filepath too long" (:Error %)))
-                          (map #(->FileMetadata (:Size %)
-                                                (:Checksum %)
-                                                (some->> (get encrypted-path->path-map (:FilePath %))
-                                                         path-normalize)
-                                                (:FilePath %)
-                                                (:LastModified %)
-                                                true
-                                                (:Txid %)
-                                                nil)))
-                         r))))))))
-
-  (<get-remote-graph [this graph-name-opt graph-uuid-opt]
-    {:pre [(or graph-name-opt graph-uuid-opt)]}
-    (user/<wrap-ensure-id&access-token
-     (<! (.<request this "get_graph" (cond-> {}
-                                       (seq graph-name-opt)
-                                       (assoc :GraphName graph-name-opt)
-                                       (seq graph-uuid-opt)
-                                       (assoc :GraphUUID graph-uuid-opt))))))
-
-  (<get-remote-txid [this graph-uuid]
-    (user/<wrap-ensure-id&access-token
-     (<! (.<request this "get_txid" {:GraphUUID graph-uuid}))))
-
-  (<get-remote-file-versions [this graph-uuid filepath]
-    (user/<wrap-ensure-id&access-token
-     (let [encrypted-path (first (<! (<encrypt-fnames rsapi graph-uuid [filepath])))]
-       (<! (.<request this "get_file_version_list" {:GraphUUID graph-uuid :File encrypted-path})))))
-
-  (<list-remote-graphs [this]
-    (user/<wrap-ensure-id&access-token
-     (<! (.<request this "list_graphs"))))
-
-  (<get-deletion-logs [this graph-uuid from-txid]
-    (user/<wrap-ensure-id&access-token
-     (let [r (<! (.<request this "get_deletion_log_v20221212" {:GraphUUID graph-uuid :FromTXId from-txid}))]
-       (or (guard-ex r)
-           (let [txns-with-encrypted-paths (mapv (fn [txn]
-                                                   (assoc txn :paths
-                                                          (mapv remove-user-graph-uuid-prefix (:paths txn))))
-                                                 (:Transactions r))
-                 encrypted-paths           (mapcat :paths txns-with-encrypted-paths)
-                 encrypted-path->path-map
-                 (zipmap
-                  encrypted-paths
-                  (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
-                 txns
-                 (mapv
-                  (fn [txn]
-                    (assoc txn :paths (mapv #(get encrypted-path->path-map %) (:paths txn))))
-                  txns-with-encrypted-paths)]
-             txns)))))
-
-  (<get-diff [this graph-uuid from-txid]
-    ;; TODO: path in transactions should be relative path(now s3 key, which includes graph-uuid and user-uuid)
-    (user/<wrap-ensure-id&access-token
-     (let [r (<! (.<request this "get_diff" {:GraphUUID graph-uuid :FromTXId from-txid}))]
-       (if (instance? ExceptionInfo r)
-         r
-         (let [txns-with-encrypted-paths (sort-by :TXId (:Transactions r))
-               txns-with-encrypted-paths*
-               (mapv
-                (fn [txn]
-                  (assoc txn :TXContent
-                         (mapv
-                          (fn [[to-path from-path checksum]]
-                            [(remove-user-graph-uuid-prefix to-path)
-                             (some-> from-path remove-user-graph-uuid-prefix)
-                             checksum])
-                          (:TXContent txn))))
-                txns-with-encrypted-paths)
-               encrypted-paths
-               (mapcat
-                (fn [txn]
-                  (remove
-                   #(or (nil? %) (not (string/starts-with? % "e.")))
-                   (mapcat
-                    (fn [[to-path from-path _checksum]] [to-path from-path])
-                    (:TXContent txn))))
-                txns-with-encrypted-paths*)
-               encrypted-path->path-map
-               (zipmap
-                encrypted-paths
-                (<! (<decrypt-fnames rsapi graph-uuid encrypted-paths)))
-               txns
-               (mapv
-                (fn [txn]
-                  (assoc
-                   txn :TXContent
-                   (mapv
-                    (fn [[to-path from-path checksum]]
-                      [(get encrypted-path->path-map to-path to-path)
-                       (some->> from-path (get encrypted-path->path-map))
-                       checksum])
-                    (:TXContent txn))))
-                txns-with-encrypted-paths*)]
-           [txns
-            (:TXId (last txns))
-            (:TXId (first txns))])))))
-
-  (<create-graph [this graph-name]
-    (user/<wrap-ensure-id&access-token
-     (<! (.<request this "create_graph" {:GraphName graph-name}))))
-
-  (<delete-graph [this graph-uuid]
-    (user/<wrap-ensure-id&access-token
-     (<! (.<request this "delete_graph" {:GraphUUID graph-uuid}))))
-
-  (<get-graph-salt [this graph-uuid]
-    (user/<wrap-ensure-id&access-token
-     (<! (.<request this "get_graph_salt" {:GraphUUID graph-uuid}))))
-
-  (<create-graph-salt [this graph-uuid]
-    (user/<wrap-ensure-id&access-token
-     (<! (.<request this "create_graph_salt" {:GraphUUID graph-uuid}))))
-
-  (<get-graph-encrypt-keys [this graph-uuid]
-    (user/<wrap-ensure-id&access-token
-     (<! (.<request this "get_graph_encrypt_keys" {:GraphUUID graph-uuid}))))
-
-  (<upload-graph-encrypt-keys [this graph-uuid public-key encrypted-private-key]
-    (user/<wrap-ensure-id&access-token
-     (<! (.<request this "upload_graph_encrypt_keys" {:GraphUUID             graph-uuid
-                                                      :public-key            public-key
-                                                      :encrypted-private-key encrypted-private-key})))))
-
-(extend-type RemoteAPI
-  IRemoteControlAPI
-  (<delete-remote-files-control [this graph-uuid filepaths]
-    (user/<wrap-ensure-id&access-token
-     (let [partitioned-files (partition-all 20 (<! (<encrypt-fnames rsapi graph-uuid filepaths)))]
-       (loop [[files & others] partitioned-files]
-         (when files
-           (let [r (<! (<get-remote-txid this graph-uuid))]
-             (or (guard-ex r)
-                 (let [current-txid (:TXId r)]
-                   (<! (.<request this "delete_files" {:GraphUUID graph-uuid :TXId current-txid :Files files}))
-                   (recur others))))))))))
-
-(comment
-  (declare remoteapi)
-  (<delete-remote-files-control remoteapi (second @graphs-txid) ["pages/aa.md"]))
-
-(def remoteapi (->RemoteAPI nil))
-
-(def ^:private *get-graph-salt-memoize-cache (atom {}))
-(defn update-graph-salt-cache [graph-uuid v]
-  {:pre [(map? v)
-         (= #{:value :expired-at} (set (keys v)))]}
-  (swap! *get-graph-salt-memoize-cache conj [graph-uuid v]))
-
-(defn <get-graph-salt-memoize [remoteapi' graph-uuid]
-  (go
-    (let [r          (get @*get-graph-salt-memoize-cache graph-uuid)
-          expired-at (:expired-at r)
-          now        (tc/to-long (t/now))]
-      (if (< now expired-at)
-        r
-        (let [r (<! (<get-graph-salt remoteapi' graph-uuid))]
-          (or (guard-ex r)
-              (do (swap! *get-graph-salt-memoize-cache conj [graph-uuid r])
-                  r)))))))
-
-(def ^:private *get-graph-encrypt-keys-memoize-cache (atom {}))
-(defn update-graph-encrypt-keys-cache [graph-uuid v]
-  {:pre [(map? v)
-         (= #{:public-key :encrypted-private-key} (set (keys v)))]}
-  (swap! *get-graph-encrypt-keys-memoize-cache conj [graph-uuid v]))
-
-(defn <get-graph-encrypt-keys-memoize [remoteapi' graph-uuid]
-  (go
-    (or (get @*get-graph-encrypt-keys-memoize-cache graph-uuid)
-        (let [{:keys [public-key encrypted-private-key] :as r}
-              (<! (<get-graph-encrypt-keys remoteapi' graph-uuid))]
-          (when (and public-key encrypted-private-key)
-            (swap! *get-graph-encrypt-keys-memoize-cache conj [graph-uuid r]))
-          r))))
-
-(defn- is-journals-or-pages?
-  [filetxn]
-  (let [rel-path (relative-path filetxn)]
-    (or (string/starts-with? rel-path (node-path/join (config/get-journals-directory) node-path/sep))
-        (string/starts-with? rel-path (node-path/join (config/get-pages-directory) node-path/sep)))))
-
-(defn- need-add-version-file?
-  "when we need to create a new version file:
-  1. when apply a 'update' filetxn, it already exists(same page name) locally and has delete diffs
-  2. when apply a 'delete' filetxn, its origin remote content and local content are different
-     - TODO: we need to store origin remote content md5 in server db
-  3. create version files only for files under 'journals/', 'pages/' dir"
-  [^FileTxn filetxn origin-db-content]
-  (go
-    (cond
-      (.renamed? ^js filetxn)
-      false
-      (.-deleted? filetxn)
-      false
-      (.-updated? filetxn)
-      (let [rpath (relative-path filetxn)
-            repo (state/get-current-repo)
-            repo-dir (config/get-repo-dir repo)
-            content (<! (p->c (-> (fs/file-exists? repo-dir rpath)
-                                  (p/then (fn [exists?]
-                                            (when exists?
-                                              (fs/read-file repo-dir rpath)))))))]
-        (and (seq origin-db-content)
-             (or (nil? content)
-                 (some :removed (diff/diff origin-db-content content))))))))
-
-(defn- <with-pause
-  [ch *paused]
-  (go-loop []
-    (if @*paused
-      {:pause true}
-      (let [{timeout' :timeout :keys [val]}
-            (async/alt! ch ([v] {:val v})
-                        (timeout 1000) {:timeout true})]
-        (cond
-          val val
-          timeout' (recur))))))
-
-(defn- assert-local-txid<=remote-txid
-  []
-  (when-let [local-txid (last @graphs-txid)]
-    (go (let [r (<! (<get-remote-txid remoteapi (second @graphs-txid)))]
-          (when-not (guard-ex r)
-            (let [remote-txid (:TXId r)]
-              (assert (<= local-txid remote-txid)
-                      [@graphs-txid local-txid remote-txid])))))))
-
-(defn- get-local-files-checksum
-  [graph-uuid base-path relative-paths]
-  (go
-    (into {}
-          (map (juxt #(.-path ^FileMetadata %) #(.-etag ^FileMetadata %)))
-          (<! (<get-local-files-meta rsapi graph-uuid base-path relative-paths)))))
-
-(declare sync-state--add-current-local->remote-files
-         sync-state--add-current-remote->local-files
-         sync-state--remove-current-local->remote-files
-         sync-state--remove-current-remote->local-files
-         sync-state--add-recent-remote->local-files
-         sync-state--remove-recent-remote->local-files
-         sync-state--stopped?)
-
-(defn- filetxns=>recent-remote->local-files
-  [filetxns]
-  (let [{:keys [update-filetxns delete-filetxns rename-filetxns]}
-        (group-by (fn [^FileTxn e]
-                    (cond
-                      (.-updated? e) :update-filetxns
-                      (.-deleted? e) :delete-filetxns
-                      (.renamed? e)  :rename-filetxns)) filetxns)
-        update-file-items (map
-                           (fn [filetxn]
-                             (let [path (relative-path filetxn)]
-                               {:remote->local-type :update
-                                :checksum (-checksum filetxn)
-                                :path path}))
-                           update-filetxns)
-        rename-file-items (mapcat
-                           (fn [^FileTxn filetxn]
-                             (let [to-path (relative-path filetxn)
-                                   from-path (.-from-path filetxn)]
-                               [{:remote->local-type :update
-                                 :checksum (-checksum filetxn)
-                                 :path to-path}
-                                {:remote->local-type :delete
-                                 :checksum nil
-                                 :path from-path}]))
-                           rename-filetxns)
-        delete-file-items (map
-                           (fn [filetxn]
-                             (let [path (relative-path filetxn)]
-                               {:remote->local-type :delete
-                                :checksum (-checksum filetxn)
-                                :path path}))
-                           delete-filetxns)]
-    (set (concat update-file-items rename-file-items delete-file-items))))
-
-(defn- <apply-remote-deletion
-  "Apply remote deletion, if the file is not deleted locally, delete it locally.
-   if the file is changed locally, leave the changed part.
-
-   To replace <delete-local-files"
-  [graph-uuid base-path relative-paths]
-  (go
-    (p->c (p/all (->> relative-paths
-                      (map (fn [rpath]
-                             (p/let [base-file (path/path-join "logseq/version-files/base" rpath)
-                                     current-change-file rpath
-                                     format (common-util/get-format current-change-file)
-                                     repo (state/get-current-repo)
-                                     repo-dir (config/get-repo-dir repo)
-                                     base-exists? (fs/file-exists? repo-dir base-file)]
-                               (if base-exists?
-                                 (p/let [base-content (fs/read-file repo-dir base-file)
-                                         current-content (-> (fs/read-file repo-dir current-change-file)
-                                                             (p/catch (fn [_] nil)))]
-                                   (if (= base-content current-content)
-                                     ;; base-content == current-content, delete current-change-file
-                                     (p/do!
-                                      (<delete-local-files rsapi graph-uuid base-path [rpath])
-                                      (fs/unlink! repo (path/path-join repo-dir base-file) {}))
-                                     ;; base-content != current-content, merge, do not delete
-                                     (p/let [merged-content (diff-merge/three-way-merge base-content "" current-content format)]
-                                       (fs/write-plain-text-file! repo repo-dir current-change-file merged-content {:skip-compare? true})
-                                       (file-handler/alter-file repo current-change-file merged-content {:re-render-root? true
-                                                                                                         :from-disk? true
-                                                                                                         :fs/event :fs/remote-file-change}))))
-
-                                 ;; no base-version, use legacy approach, delete it
-                                 (<delete-local-files rsapi graph-uuid base-path [rpath]))))))))))
-
-(defn- <fetch-remote-and-update-local-files
-  [graph-uuid base-path relative-paths]
-  (go
-    (let [fetched-file-rpaths-or-ex (<! (<fetch-remote-files rsapi graph-uuid base-path relative-paths))]
-      (if (instance? ExceptionInfo fetched-file-rpaths-or-ex)
-        fetched-file-rpaths-or-ex
-        (<!
-         (p->c (p/all (->> fetched-file-rpaths-or-ex
-                           (map (fn [rpath]
-                                  (p/let [incoming-file (path/path-join "logseq/version-files/incoming" rpath)
-                                          base-file (path/path-join "logseq/version-files/base" rpath)
-                                          current-change-file rpath
-                                          format (common-util/get-format current-change-file)
-                                          repo (state/get-current-repo)
-                                          repo-dir (config/get-repo-dir repo)
-                                          base-exists? (fs/file-exists? repo-dir base-file)]
-                                    (cond
-                                      base-exists?
-                                      (p/let [base-content (fs/read-file repo-dir base-file)
-                                              current-content (-> (fs/read-file repo-dir current-change-file)
-                                                                  (p/catch (fn [_] nil)))
-                                              incoming-content (fs/read-file repo-dir incoming-file)]
-                                        (if (= base-content current-content)
-                                          (do
-                                            (prn "[diff-merge]base=current, write directly")
-                                            (p/do!
-                                             (fs/copy! repo
-                                                       (path/path-join repo-dir incoming-file)
-                                                       (path/path-join repo-dir current-change-file))
-                                             (fs/copy! repo
-                                                       (path/path-join repo-dir incoming-file)
-                                                       (path/path-join repo-dir base-file))
-                                             (file-handler/alter-file repo current-change-file incoming-content {:re-render-root? true
-                                                                                                                 :from-disk? true
-                                                                                                                 :fs/event :fs/remote-file-change})))
-                                          (do
-                                            (prn "[diff-merge]base!=current, 3-way merge")
-                                            (p/let [current-content (or current-content "")
-                                                    incoming-content (fs/read-file repo-dir incoming-file)
-                                                    merged-content (diff-merge/three-way-merge base-content incoming-content current-content format)]
-                                              (when (seq merged-content)
-                                                (p/do!
-                                                 (fs/write-plain-text-file! repo repo-dir current-change-file merged-content {:skip-compare? true})
-                                                 (file-handler/alter-file repo current-change-file merged-content {:re-render-root? true
-                                                                                                                   :from-disk? true
-                                                                                                                   :fs/event :fs/remote-file-change})))))))
-
-                                      :else
-                                      (do
-                                        (prn "[diff-merge]no base found, failback")
-                                        (p/let [current-content (-> (fs/read-file repo-dir current-change-file)
-                                                                    (p/catch (fn [_] nil)))
-                                                current-content (or current-content "")
-                                                incoming-content (fs/read-file repo-dir incoming-file)
-                                                merged-content (diff-merge/three-way-merge "" incoming-content current-content format)]
-                                          (if (= incoming-content merged-content)
-                                            (p/do!
-                                             (fs/copy! repo
-                                                       (path/path-join repo-dir incoming-file)
-                                                       (path/path-join repo-dir current-change-file))
-                                             (fs/copy! repo
-                                                       (path/path-join repo-dir incoming-file)
-                                                       (path/path-join repo-dir base-file))
-                                             (file-handler/alter-file repo current-change-file merged-content {:re-render-root? true
-                                                                                                               :from-disk? true
-                                                                                                               :fs/event :fs/remote-file-change}))
-
-                                          ;; else
-                                            (p/do!
-                                             (fs/write-plain-text-file! repo repo-dir current-change-file merged-content {:skip-compare? true})
-                                             (file-handler/alter-file repo current-change-file merged-content {:re-render-root? true
-                                                                                                               :from-disk? true
-                                                                                                               :fs/event :fs/remote-file-change})))))))))))))))))
-
-(defn- apply-filetxns
-  [*sync-state graph-uuid base-path filetxns *paused]
-  (let [^FileTxn filetxn (first filetxns)]
-    (go
-      (cond
-        (.renamed? filetxn)
-        (let [from-path (.-from-path filetxn)
-              to-path (.-to-path filetxn)]
-          (assert (= 1 (count filetxns)))
-          (<! (<rename-local-file rsapi graph-uuid base-path
-                                  (relative-path from-path)
-                                  (relative-path to-path))))
-
-        (.-updated? filetxn)
-        (let [repo (state/get-current-repo)
-              txn->db-content-vec (->> filetxns
-                                       (mapv
-                                        #(when (is-journals-or-pages? %)
-                                           [% (db/get-file repo (relative-path %))]))
-                                       (remove nil?))]
-
-          (doseq [relative-p (map relative-path filetxns)]
-            (when-some [relative-p*
-                        (<! (<case-different-local-file-exist? graph-uuid rsapi base-path relative-p))]
-              (let [recent-remote->local-file-item {:remote->local-type :delete
-                                                    :checksum nil
-                                                    :path relative-p*}]
-                (println :debug "found case-different-same-local-file" relative-p relative-p*)
-                (swap! *sync-state sync-state--add-recent-remote->local-files
-                       [recent-remote->local-file-item])
-                (<! (<delete-local-files rsapi graph-uuid base-path [relative-p*]))
-                (go (<! (timeout 5000))
-                    (swap! *sync-state sync-state--remove-recent-remote->local-files
-                           [recent-remote->local-file-item])))))
-
-          (let [update-local-files-ch (if (state/enable-sync-diff-merge?)
-                                        (<fetch-remote-and-update-local-files graph-uuid base-path (map relative-path filetxns))
-                                        (<update-local-files rsapi graph-uuid base-path (map relative-path filetxns)))
-                r (<! (<with-pause update-local-files-ch *paused))]
-            (doseq [[filetxn origin-db-content] txn->db-content-vec]
-              (when (<! (need-add-version-file? filetxn origin-db-content))
-                (<! (<add-new-version rsapi repo (relative-path filetxn) origin-db-content))
-                (put-sync-event! {:event :created-local-version-file
-                                  :data {:graph-uuid graph-uuid
-                                         :repo repo
-                                         :path (relative-path filetxn)
-                                         :epoch (tc/to-epoch (t/now))}})))
-            r))
-
-        (.-deleted? filetxn)
-        (let [filetxn (first filetxns)]
-          (assert (= 1 (count filetxns)))
-          (if (<! (<local-file-not-exist? graph-uuid rsapi base-path (relative-path filetxn)))
-          ;; not exist, ignore
-            true
-            (let [r (<! (if (state/enable-sync-diff-merge?)
-                          (<apply-remote-deletion graph-uuid base-path [(relative-path filetxn)])
-                          (<delete-local-files rsapi graph-uuid base-path [(relative-path filetxn)])))]
-              (if (and (instance? ExceptionInfo r)
-                       (string/index-of (str (ex-cause r)) "No such file or directory"))
-                true
-                r))))))))
-
-(declare sync-state-reset-full-remote->local-files)
-(defn apply-filetxns-partitions
-  "won't call <update-graphs-txid! when *txid is nil"
-  [*sync-state user-uuid graph-uuid base-path filetxns-partitions repo *txid *stopped *paused full-sync?]
-  (assert (some? *sync-state))
-
-  (go-loop [filetxns-partitions* filetxns-partitions]
-    (cond
-      @*stopped {:stop true}
-      @*paused  {:pause true}
-      :else
-      (when (seq filetxns-partitions*)
-        (let [filetxns                        (first filetxns-partitions*)
-              paths                           (map relative-path filetxns)
-              recent-remote->local-file-items (filetxns=>recent-remote->local-files filetxns)
-              _ (when-not full-sync?
-                  (swap! *sync-state #(sync-state-reset-full-remote->local-files % recent-remote->local-file-items)))
-              ;; update recent-remote->local-files
-              _                               (swap! *sync-state sync-state--add-recent-remote->local-files
-                                                     recent-remote->local-file-items)
-              _                               (swap! *sync-state sync-state--add-current-remote->local-files paths)
-              r                               (<! (apply-filetxns *sync-state graph-uuid base-path filetxns *paused))
-              _                               (swap! *sync-state sync-state--remove-current-remote->local-files paths
-                                                     (not (instance? ExceptionInfo r)))]
-          ;; remove these recent-remote->local-file-items 5s later
-          (go (<! (timeout 5000))
-              (swap! *sync-state sync-state--remove-recent-remote->local-files
-                     recent-remote->local-file-items))
-          (cond
-            (instance? ExceptionInfo r) r
-            @*paused                    {:pause true}
-            :else
-            (let [latest-txid (apply max (and *txid @*txid) (map #(.-txid ^FileTxn %) filetxns))]
-              ;; update local-txid
-              (when (and *txid (number? latest-txid))
-                (reset! *txid latest-txid)
-                (<! (<update-graphs-txid! latest-txid graph-uuid user-uuid repo)))
-              (recur (next filetxns-partitions*)))))))))
-
-(defmulti need-sync-remote? (fn [v] (cond
-                                      (= :max v)
-                                      :max
-
-                                      (and (vector? v) (number? (first v)))
-                                      :txid
-
-                                      (instance? ExceptionInfo v)
-                                      :exceptional-response
-
-                                      (instance? cljs.core.async.impl.channels/ManyToManyChannel v)
-                                      :chan)))
-
-(defmethod need-sync-remote? :max [_] true)
-(defmethod need-sync-remote? :txid [[txid ^Remote->LocalSyncer remote->local-syncer]]
-  (let [remote-txid txid
-        local-txid (.-txid remote->local-syncer)]
-    (or (nil? local-txid)
-        (> remote-txid local-txid))))
-
-(defmethod need-sync-remote? :exceptional-response [resp]
-  (let [data (ex-data resp)
-        cause (ex-cause resp)]
-    (or
-     (and (= (:error data) :promise-error)
-          (when-let [r (re-find #"(\d+), txid_to_validate = (\d+)" (str cause))]
-            (> (nth r 1) (nth r 2))))
-     (= 409 (get-in data [:err :status])))))
-
-(defmethod need-sync-remote? :chan [c]
-  (go (need-sync-remote? (<! c))))
-(defmethod need-sync-remote? :default [_] false)
-
-(defn- need-reset-local-txid?
-  [r]
-  (when-let [cause (ex-cause r)]
-    (when-let [r (re-find #"(\d+), txid_to_validate = (\d+)" (str cause))]
-      (< (nth r 1) (nth r 2)))))
-
-(defn- graph-has-been-deleted?
-  [r]
-  (some->> (ex-cause r) str (re-find #"graph-not-exist")))
-
-(defn- stop-sync-by-rsapi-response?
-  [r]
-  (some->> (ex-cause r) str (re-find #"Request is not yet valid")))
-
-;; type = "change" | "add" | "unlink"
-(deftype FileChangeEvent [type dir path stat checksum]
-  IRelativePath
-  (-relative-path [_] (remove-dir-prefix dir path))
-
-  IEquiv
-  (-equiv [_ ^FileChangeEvent other]
-    (and (= dir (.-dir other))
-         (= type (.-type other))
-         (= path (.-path other))
-         (= checksum (.-checksum other))))
-
-  IHash
-  (-hash [_]
-    (hash {:dir dir
-           :type type
-           :path path
-           :checksum checksum}))
-
-  ILookup
-  (-lookup [o k] (-lookup o k nil))
-  (-lookup [_ k not-found]
-    (case k
-      :type type
-      :dir  dir
-      :path path
-      :stat stat
-      :checksum checksum
-      not-found))
-
-  IPrintWithWriter
-  (-pr-writer [_ w _opts]
-    (write-all w (str {:type type :base-path dir :path path :size (:size stat) :checksum checksum}))))
-
-(defn- <file-change-event=>recent-remote->local-file-item
-  "return nil when related local files not found"
-  [graph-uuid ^FileChangeEvent e]
-  (go
-    (let [tp (case (.-type e)
-               ("add" "change") :update
-               "unlink" :delete)
-          path (relative-path e)]
-      (when-let [path-etag-entry (first (<! (get-local-files-checksum graph-uuid (.-dir e) [path])))]
-        {:remote->local-type tp
-         :checksum (if (= tp :delete) nil
-                       (val path-etag-entry))
-         :path path}))))
-
-(defn- distinct-file-change-events-xf
-  "transducer.
-  distinct `FileChangeEvent`s by their path, keep the first one."
-  [rf]
-  (let [seen (volatile! #{})]
-    (fn
-      ([] (rf))
-      ([result] (rf result))
-      ([result ^FileChangeEvent e]
-       (if (contains? @seen (.-path e))
-         result
-         (do (vswap! seen conj (.-path e))
-             (rf result e)))))))
-
-(defn- distinct-file-change-events
-  "distinct `FileChangeEvent`s by their path, keep the last one."
-  [es]
-  (transduce distinct-file-change-events-xf conj '() (reverse es)))
-
-(defn- partition-file-change-events
-  "return transducer.
-  partition `FileChangeEvent`s, at most N file-change-events in each partition.
-  only one type in a partition."
-  [n]
-  (comp
-   (partition-by (fn [^FileChangeEvent e]
-                   (case (.-type e)
-                     ("add" "change") :add-or-change
-                     "unlink"         :unlink)))
-   (map #(partition-all n %))
-   cat))
-
-(declare sync-state--valid-to-accept-filewatcher-event?)
-(defonce local-changes-chan (chan (async/dropping-buffer 1000)))
-(defn file-watch-handler
-  "file-watcher callback"
-  [type {:keys [dir path _content stat] :as _payload}]
-  (when-let [current-graph (state/get-current-repo)]
-    (when (string/ends-with? current-graph dir)
-      (when-let [sync-state (state/get-file-sync-state (state/get-current-file-sync-graph-uuid))]
-        (when (sync-state--valid-to-accept-filewatcher-event? sync-state)
-          (when (or (:mtime stat) (= type "unlink"))
-            (go
-              (let [path (path-normalize path)
-                    files-meta (and (not= "unlink" type)
-                                    (<! (<get-local-files-meta
-                                         rsapi (:current-syncing-graph-uuid sync-state) dir [path])))
-                    checksum (and (coll? files-meta) (some-> files-meta first :etag))]
-                (>! local-changes-chan (->FileChangeEvent type dir path stat checksum))))))))))
-
-(defn local-changes-revised-chan-builder
-  "return chan"
-  [local-changes-chan' rename-page-event-chan]
-  (let [*rename-events (atom #{})
-        ch (chan 1000)]
-    (go-loop []
-      (let [{:keys [rename-event local-change]}
-            (async/alt!
-              rename-page-event-chan ([v] {:rename-event v}) ;; {:repo X :old-path X :new-path}
-              local-changes-chan' ([v] {:local-change v}))]
-        (cond
-          rename-event
-          (let [repo-dir (config/get-repo-dir (:repo rename-event))
-                remove-dir-prefix-fn #(remove-dir-prefix repo-dir %)
-                rename-event* (-> rename-event
-                                  (update :old-path remove-dir-prefix-fn)
-                                  (update :new-path remove-dir-prefix-fn))
-                k1 [:old-path (:old-path rename-event*) repo-dir]
-                k2 [:new-path (:new-path rename-event*) repo-dir]]
-            (swap! *rename-events conj k1 k2)
-            ;; remove rename-events after 2s
-            (go (<! (timeout 3000))
-                (swap! *rename-events disj k1 k2))
-            ;; add 2 simulated file-watcher events
-            (>! ch (->FileChangeEvent "unlink" repo-dir (:old-path rename-event*) nil nil))
-            (>! ch (->FileChangeEvent "add" repo-dir (:new-path rename-event*)
-                                      {:mtime (tc/to-long (t/now))
-                                       :size 1 ; add a fake size
-                                       }"fake-checksum"))
-            (recur))
-          local-change
-          (cond
-            (and (= "change" (.-type local-change))
-                 (or (contains? @*rename-events [:old-path (.-path local-change) (.-dir local-change)])
-                     (contains? @*rename-events [:new-path (.-path local-change) (.-dir local-change)])))
-            (do (println :debug "ignore" local-change)
-                ;; ignore
-                (recur))
-
-            (and (= "add" (.-type local-change))
-                 (contains? @*rename-events [:new-path (.-path local-change) (.-dir local-change)]))
-            ;; ignore
-            (do (println :debug "ignore" local-change)
-                (recur))
-            (and (= "unlink" (.-type local-change))
-                 (contains? @*rename-events [:old-path (.-path local-change) (.-dir local-change)]))
-            (do (println :debug "ignore" local-change)
-                (recur))
-            :else
-            (do (>! ch local-change)
-                (recur))))))
-    ch))
-
-(defonce local-changes-revised-chan
-  (local-changes-revised-chan-builder local-changes-chan (state/get-file-rename-event-chan)))
-
-;;; ### encryption
-(def pwd-map
-  "graph-uuid->{:pwd xxx :public-key xxx :private-key xxx}"
-  (atom {}))
-
-(defonce *pwd-map-changed-chan
-  (atom {}))
-
-(defn- get-graph-pwd-changed-chan
-  [graph-uuid]
-  (if-let [result (get @*pwd-map-changed-chan graph-uuid)]
-    result
-    (let [c (chan (async/sliding-buffer 1))]
-      (swap! *pwd-map-changed-chan assoc graph-uuid c)
-      c)))
-
-(defn- <encrypt-content
-  [content key*]
-  (p->c (encrypt/encrypt-with-passphrase key* content)))
-
-(defn- decrypt-content
-  [encrypted-content key*]
-  (go
-    (let [r (<! (p->c (encrypt/decrypt-with-passphrase key* encrypted-content)))]
-      (when-not (instance? ExceptionInfo r) r))))
-
-(defn- local-storage-pwd-path
-  [graph-uuid]
-  (str "encrypted-pwd/" graph-uuid))
-
-(defn- persist-pwd!
-  [pwd graph-uuid]
-  {:pre [(string? pwd)]}
-  (js/localStorage.setItem (local-storage-pwd-path graph-uuid) pwd))
-
-(defn- remove-pwd!
-  [graph-uuid]
-  (js/localStorage.removeItem (local-storage-pwd-path graph-uuid)))
-
-(defn get-pwd
-  [graph-uuid]
-  (js/localStorage.getItem (local-storage-pwd-path graph-uuid)))
-
-(defn remove-all-pwd!
-  []
-  (doseq [k (filter #(string/starts-with? % "encrypted-pwd/") (js->clj (js-keys js/localStorage)))]
-    (js/localStorage.removeItem k))
-  (reset! pwd-map {}))
-
-(defn encrypt+persist-pwd!
-  "- persist encrypted pwd at local-storage"
-  [pwd graph-uuid]
-  (go
-    (let [[value _expired-at gone?] ((juxt :value :expired-at #(-> % ex-data :err :status (= 410)))
-                                     (<! (<get-graph-salt-memoize remoteapi graph-uuid)))]
-      (if gone?
-        (let [r (<! (<create-graph-salt remoteapi graph-uuid))]
-          (or (guard-ex r)
-              (do (update-graph-salt-cache graph-uuid r)
-                  (let [[salt-value _expired-at] ((juxt :value :expired-at) r)
-                        encrypted-pwd (<! (<encrypt-content pwd salt-value))]
-                    (persist-pwd! encrypted-pwd graph-uuid)))))
-        (let [encrypted-pwd (<! (<encrypt-content pwd value))]
-          (persist-pwd! encrypted-pwd graph-uuid))))))
-
-(defn restore-pwd!
-  "restore pwd from persisted encrypted-pwd, update `pwd-map`"
-  [graph-uuid]
-  (go
-    (let [encrypted-pwd (get-pwd graph-uuid)]
-      (if (nil? encrypted-pwd)
-        {:restore-pwd-failed true}
-        (let [[salt-value _expired-at gone?]
-              ((juxt :value :expired-at #(-> % ex-data :err :status (= 410)))
-               (<! (<get-graph-salt-memoize remoteapi graph-uuid)))]
-          (if (or gone? (empty? salt-value))
-            {:restore-pwd-failed "expired salt"}
-            (let [pwd (<! (decrypt-content encrypted-pwd salt-value))]
-              (if (nil? pwd)
-                {:restore-pwd-failed (str "decrypt-pwd failed, salt: " salt-value)}
-                (swap! pwd-map assoc-in [graph-uuid :pwd] pwd)))))))))
-
-(defn- set-keys&notify
-  [graph-uuid public-key private-key]
-  (swap! pwd-map assoc-in [graph-uuid :public-key] public-key)
-  (swap! pwd-map assoc-in [graph-uuid :private-key] private-key)
-  (offer! (get-graph-pwd-changed-chan graph-uuid) true))
-
-(defn- <set-graph-encryption-keys!
-  [graph-uuid pwd public-key encrypted-private-key]
-  (go
-    (let [private-key (when (and pwd encrypted-private-key)
-                        (<! (decrypt-content encrypted-private-key pwd)))]
-      (when (and private-key (string/starts-with? private-key "AGE-SECRET-KEY"))
-        (set-keys&notify graph-uuid public-key private-key)))))
-
-(def <restored-pwd (chan (async/sliding-buffer 1)))
-(def <restored-pwd-pub (async/pub <restored-pwd :graph-uuid))
-
-(defn- <ensure-pwd-exists!
-  "return password or nil when restore pwd from localstorage failed"
-  [repo graph-uuid init-graph-keys]
-  (go
-    (let [{:keys [restore-pwd-failed]} (<! (restore-pwd! graph-uuid))
-          pwd (get-in @pwd-map [graph-uuid :pwd])]
-      (if restore-pwd-failed
-        (do (state/pub-event! [:modal/remote-encryption-input-pw-dialog repo
-                               (state/get-remote-graph-info-by-uuid graph-uuid)
-                               :input-pwd-remote
-                               {:GraphUUID graph-uuid
-                                :init-graph-keys init-graph-keys
-                                :after-input-password (fn [pwd]
-                                                        (when pwd
-                                                          (swap! pwd-map assoc-in [graph-uuid :pwd] pwd)
-                                                          (offer! <restored-pwd {:graph-uuid graph-uuid :value true})))}])
-            nil)
-        pwd))))
-
-(defn clear-pwd!
-  "- clear pwd in `pwd-map`
-  - remove encrypted-pwd in local-storage"
-  [graph-uuid]
-  (swap! pwd-map dissoc graph-uuid)
-  (remove-pwd! graph-uuid))
-
-(defn- <loop-ensure-pwd&keys
-  [graph-uuid repo *stopped?]
-  (let [<restored-pwd-sub-chan (chan 1)]
-    (async/sub <restored-pwd-pub graph-uuid <restored-pwd-sub-chan)
-    (go-loop []
-      (if @*stopped?
-        ::stop
-        (let [{:keys [public-key encrypted-private-key] :as r}
-              (<! (<get-graph-encrypt-keys-memoize remoteapi graph-uuid))
-              init-graph-keys (some-> (ex-data r) :err :status (= 404))
-              pwd (<! (<ensure-pwd-exists! repo graph-uuid init-graph-keys))]
-          (cond
-            (not pwd)
-            (do (println :debug "waiting password...")
-                (<! <restored-pwd-sub-chan)      ;loop to wait password
-                (println :debug "waiting password...DONE" graph-uuid)
-                (recur))
-
-            init-graph-keys
-            ;; when public+private keys not stored at server
-            ;; generate a new key pair and upload them
-            (let [next-state
-                  (let [{public-key :publicKey private-key :secretKey}
-                        (<! (<key-gen rsapi))
-                        _ (assert (and public-key private-key) (str :public-key public-key :private-key private-key))
-                        encrypted-private-key (<! (<encrypt-content private-key pwd))
-                        _ (assert (string? encrypted-private-key)
-                                  {:encrypted-private-key encrypted-private-key
-                                   :private-key private-key
-                                   :pwd pwd})
-                        upload-r (<! (<upload-graph-encrypt-keys remoteapi graph-uuid public-key encrypted-private-key))]
-                    (if (instance? ExceptionInfo upload-r)
-                      (do (js/console.log "upload-graph-encrypt-keys err" upload-r)
-                          ::stop)
-                      (do (update-graph-encrypt-keys-cache graph-uuid {:public-key public-key
-                                                                       :encrypted-private-key encrypted-private-key})
-                          :recur)))]
-              (if (= :recur next-state)
-                (recur)
-                next-state))
-
-            :else
-            ;; pwd, public-key, encrypted-private-key all exist
-            (do (assert (and pwd public-key encrypted-private-key) {:encrypted-private-key encrypted-private-key
-                                                                    :public-key public-key
-                                                                    :pwd pwd})
-                (<! (<set-graph-encryption-keys! graph-uuid pwd public-key encrypted-private-key))
-                (if (get-in @pwd-map [graph-uuid :private-key])
-                  (do (when (state/modal-opened?)
-                        (state/set-state! [:ui/loading? :set-graph-password] false)
-                        (state/close-modal!))
-                      ::idle)
-                  ;; bad pwd
-                  (do (when (state/modal-opened?)
-                        (when (state/sub [:ui/loading? :set-graph-password])
-                          (state/set-state! [:file-sync/set-remote-graph-password-result]
-                                            {:fail "Incorrect password. Please try again"}))
-                        (state/set-state! [:ui/loading? :set-graph-password] false))
-                      (clear-pwd! graph-uuid)
-                      (recur))))))))))
-
-(defn- <set-env&keys
-  [prod? graph-uuid]
-  (let [{:keys [private-key public-key]} (get @pwd-map graph-uuid)]
-    (assert (and private-key public-key) (pr-str :private-key private-key :public-key public-key
-                                                 :pwd-map @pwd-map))
-    (<set-env rsapi graph-uuid prod? private-key public-key)))
-
-(defn- <ensure-set-env&keys
-  [graph-uuid *stopped?]
-  (go-loop []
-    (let [{timeout' :timeout :keys [change]}
-          (async/alt! (get-graph-pwd-changed-chan graph-uuid) {:change true}
-                      (timeout 10000) {:timeout true})]
-      (cond
-        @*stopped? nil
-        change (<! (<set-env&keys config/FILE-SYNC-PROD? graph-uuid))
-        timeout' (recur)))))
-
-;;; ### chans to control sync process
-(def full-sync-chan
-  "offer `true` to this chan will trigger a local->remote full sync"
-  (chan 1))
-(def full-sync-mult (async/mult full-sync-chan))
-
-(def remote->local-sync-chan
-  "offer `true` to this chan will trigger a remote->local sync"
-  (chan 1))
-(def remote->local-sync-mult (async/mult remote->local-sync-chan))
-
-(def remote->local-full-sync-chan
-  "offer `true` to this chan will trigger a remote->local full sync"
-  (chan 1))
-(def remote->local-full-sync-mult (async/mult remote->local-full-sync-chan))
-
-(def immediately-local->remote-chan
-  "Immediately trigger upload of files in waiting queue"
-  (chan))
-(def immediately-local->remote-mult (async/mult immediately-local->remote-chan))
-
-(def pause-resume-chan
-  "false -> pause, true -> resume.
-  see also `*resume-state`"
-  (chan 1))
-(def pause-resume-mult (async/mult pause-resume-chan))
-
-(def recent-edited-chan
-  "Triggered when there is content editing"
-  (chan 1))
-(def recent-edited-mult (async/mult recent-edited-chan))
-(def last-input-time-atom (get @state/state :editor/last-input-time))
-(add-watch last-input-time-atom "sync"
-           (fn [_ _ _ _]
-             (offer! recent-edited-chan true)))
-
-;;; ### sync state
-
-(def *resume-state
-  "key: graph-uuid"
-  (atom {}))
-
-(defn resume-state--add-remote->local-state
-  [graph-uuid]
-  (swap! *resume-state assoc graph-uuid {:remote->local true}))
-
-(defn resume-state--add-remote->local-full-sync-state
-  [graph-uuid]
-  (swap! *resume-state assoc graph-uuid {:remote->local-full-sync true}))
-
-(defn resume-state--add-local->remote-state
-  [graph-uuid local-changes]
-  (swap! *resume-state assoc graph-uuid {:local->remote local-changes}))
-
-;; (defn resume-state--add-local->remote-full-sync-state
-;;   [graph-uuid]
-;;   (swap! *resume-state assoc graph-uuid {:local->remote-full-sync true}))
-
-(defn resume-state--reset
-  [graph-uuid]
-  (swap! *resume-state dissoc graph-uuid))
-
-(defn new-sync-state
-  "create a new sync-state"
-  []
-  {:post [(s/valid? ::sync-state %)]}
-  {:current-syncing-graph-uuid  nil
-   :state                       ::starting
-   :full-local->remote-files    #{}
-   :current-local->remote-files #{}
-   :full-remote->local-files    #{}
-   :current-remote->local-files #{}
-   :queued-local->remote-files  #{}
-   :recent-remote->local-files  #{}
-   :history                     '()})
-
-(defn- sync-state--update-current-syncing-graph-uuid
-  [sync-state graph-uuid]
-  {:pre  [(s/valid? ::sync-state sync-state)]
-   :post [(s/valid? ::sync-state %)]}
-  (assoc sync-state :current-syncing-graph-uuid graph-uuid))
-
-(defn- sync-state--update-state
-  [sync-state next-state]
-  {:pre  [(s/valid? ::state next-state)]
-   :post [(s/valid? ::sync-state %)]}
-  (assoc sync-state :state next-state))
-
-(defn sync-state--add-current-remote->local-files
-  [sync-state paths]
-  {:post [(s/valid? ::sync-state %)]}
-  (update sync-state :current-remote->local-files into paths))
-
-(defn sync-state--add-current-local->remote-files
-  [sync-state paths]
-  {:post [(s/valid? ::sync-state %)]}
-  (update sync-state :current-local->remote-files into paths))
-
-(defn sync-state--add-queued-local->remote-files
-  [sync-state event]
-  {:post [(s/valid? ::sync-state %)]}
-  (update sync-state :queued-local->remote-files
-          (fn [o event]
-            (->> (concat o [event])
-                 (util/distinct-by-last-wins (fn [e] (.-path e))))) event))
-
-(defn sync-state--remove-queued-local->remote-files
-  [sync-state event]
-  {:post [(s/valid? ::sync-state %)]}
-  (update sync-state :queued-local->remote-files
-          (fn [o event]
-            (remove #{event} o)) event))
-
-(defn sync-state-reset-queued-local->remote-files
-  [sync-state]
-  {:post [(s/valid? ::sync-state %)]}
-  (assoc sync-state :queued-local->remote-files nil))
-
-(defn sync-state--add-recent-remote->local-files
-  [sync-state items]
-  {:pre [(s/valid? (s/coll-of ::recent-remote->local-file-item) items)]
-   :post [(s/valid? ::sync-state %)]}
-  (update sync-state :recent-remote->local-files (partial apply conj) items))
-
-(defn sync-state--remove-recent-remote->local-files
-  [sync-state items]
-  {:post [(s/valid? ::sync-state %)]}
-  (update sync-state :recent-remote->local-files set/difference items))
-
-(defn sync-state-reset-full-local->remote-files
-  [sync-state events]
-  {:post [(s/valid? ::sync-state %)]}
-  (assoc sync-state :full-local->remote-files events))
-
-(defn sync-state-reset-full-remote->local-files
-  [sync-state events]
-  {:post [(s/valid? ::sync-state %)]}
-  (assoc sync-state :full-remote->local-files events))
-
-(defn- add-history-items
-  [history paths now]
-  (sequence
-   (comp
-    ;; only reserve the latest one of same-path-items
-    (dedupe-by :path)
-    ;; reserve the latest 20 history items
-    (take 20))
-   (into (filter (fn [o]
-                   (not (contains? (set paths) (:path o)))) history)
-         (map (fn [path] {:path path :time now}) paths))))
-
-(defn sync-state--remove-current-remote->local-files
-  [sync-state paths add-history?]
-  {:post [(s/valid? ::sync-state %)]}
-  (let [now (t/now)]
-    (cond-> sync-state
-      true         (update :current-remote->local-files set/difference paths)
-      add-history? (update :history add-history-items paths now))))
-
-(defn sync-state--remove-current-local->remote-files
-  [sync-state paths add-history?]
-  {:post [(s/valid? ::sync-state %)]}
-  (let [now (t/now)]
-    (cond-> sync-state
-      true         (update :current-local->remote-files set/difference paths)
-      add-history? (update :history add-history-items paths now))))
-
-(defn sync-state--stopped?
-  "Graph syncing is stopped"
-  [sync-state]
-  {:pre [(s/valid? ::sync-state sync-state)]}
-  (= ::stop (:state sync-state)))
-
-(defn sync-state--valid-to-accept-filewatcher-event?
-  [sync-state]
-  {:pre [(s/valid? ::sync-state sync-state)]}
-  (contains? #{::idle ::local->remote ::remote->local ::local->remote-full-sync ::remote->local-full-sync}
-             (:state sync-state)))
-
-;;; ### remote->local syncer & local->remote syncer
-
-(defprotocol IRemote->LocalSync
-  (stop-remote->local! [this])
-  (<sync-remote->local! [this] "return ExceptionInfo when error occurs")
-  (<sync-remote->local-all-files! [this] "sync all files, return ExceptionInfo when error occurs"))
-
-(defprotocol ILocal->RemoteSync
-  (setup-local->remote! [this])
-  (stop-local->remote! [this])
-  (<ratelimit [this from-chan] "get watched local file-change events from FROM-CHAN,
-  return chan returning events with rate limited")
-  (<sync-local->remote! [this es] "es is a sequence of `FileChangeEvent`, all items have same type.")
-  (<sync-local->remote-all-files! [this] "compare all local files to remote ones, sync when not equal.
-  if local-txid != remote-txid, return {:need-sync-remote true}"))
-
-(defrecord ^:large-vars/cleanup-todo
- Remote->LocalSyncer [user-uuid graph-uuid base-path repo *txid *txid-for-get-deletion-log *sync-state remoteapi'
-                      ^:mutable local->remote-syncer *stopped *paused]
-  Object
-  (set-local->remote-syncer! [_ s] (set! local->remote-syncer s))
-  (sync-files-remote->local!
-    [_ relative-filepath+checksum-coll latest-txid]
-    (go
-      (let [partitioned-filetxns
-            (sequence (filepath+checksum-coll->partitioned-filetxns
-                       download-batch-size graph-uuid user-uuid)
-                      relative-filepath+checksum-coll)
-            r
-            (if (empty? (flatten partitioned-filetxns))
-              {:succ true}
-              (do
-                (put-sync-event! {:event :start
-                                  :data  {:type       :full-remote->local
-                                          :graph-uuid graph-uuid
-                                          :full-sync? true
-                                          :epoch      (tc/to-epoch (t/now))}})
-                (<! (apply-filetxns-partitions
-                     *sync-state user-uuid graph-uuid base-path partitioned-filetxns repo
-                     nil *stopped *paused true))))]
-        (cond
-          (instance? ExceptionInfo r) {:unknown r}
-          @*stopped                   {:stop true}
-          @*paused                    {:pause true}
-          :else
-          (do
-            (swap! *sync-state #(sync-state-reset-full-remote->local-files % []))
-            (<! (<update-graphs-txid! latest-txid graph-uuid user-uuid repo))
-            (reset! *txid latest-txid)
-            {:succ true})))))
-
-  IRemote->LocalSync
-  (stop-remote->local! [_] (vreset! *stopped true))
-  (<sync-remote->local! [_]
-    (go
-      (let [r
-            (let [diff-r (<! (<get-diff remoteapi' graph-uuid @*txid))]
-              (or (guard-ex diff-r)
-                  (let [[diff-txns latest-txid min-txid] diff-r]
-                    (if (> (dec min-txid) @*txid) ;; min-txid-1 > @*txid, need to remote->local-full-sync
-                      (do (println "min-txid" min-txid "request-txid" @*txid)
-                          {:need-remote->local-full-sync true})
-
-                      (when (pos-int? latest-txid)
-                        (let [filtered-diff-txns (-> (transduce (diffs->filetxns) conj '() (reverse diff-txns))
-                                                     filter-download-files-with-reserved-chars)
-                              partitioned-filetxns (transduce (partition-filetxns download-batch-size)
-                                                              (completing (fn [r i] (conj r (reverse i)))) ;reverse
-                                                              '()
-                                                              filtered-diff-txns)]
-                          (put-sync-event! {:event :start
-                                            :data  {:type       :remote->local
-                                                    :graph-uuid graph-uuid
-                                                    :full-sync? false
-                                                    :epoch      (tc/to-epoch (t/now))}})
-                          (if (empty? (flatten partitioned-filetxns))
-                            (do
-                              (swap! *sync-state #(sync-state-reset-full-remote->local-files % []))
-                              (<! (<update-graphs-txid! latest-txid graph-uuid user-uuid repo))
-                              (reset! *txid latest-txid)
-                              {:succ true})
-                            (<! (apply-filetxns-partitions
-                                 *sync-state user-uuid graph-uuid base-path
-                                 partitioned-filetxns repo *txid *stopped *paused false)))))))))]
-        (cond
-          (instance? ExceptionInfo r)       {:unknown r}
-          @*stopped                         {:stop true}
-          @*paused                          {:pause true}
-          (:need-remote->local-full-sync r) r
-          :else                             {:succ true}))))
-
-  (<sync-remote->local-all-files! [this]
-    (go
-      (let [remote-all-files-meta-c      (<get-remote-all-files-meta remoteapi' graph-uuid)
-            local-all-files-meta-c       (<get-local-all-files-meta rsapi graph-uuid base-path)
-            remote-all-files-meta-or-exp (<! remote-all-files-meta-c)]
-        (if (or (storage-exceed-limit? remote-all-files-meta-or-exp)
-                (sync-stop-when-api-flying? remote-all-files-meta-or-exp)
-                (decrypt-exp? remote-all-files-meta-or-exp)
-                (instance? ExceptionInfo remote-all-files-meta-or-exp))
-          (do (put-sync-event! {:event :exception-decrypt-failed
-                                :data  {:graph-uuid graph-uuid
-                                        :exp        remote-all-files-meta-or-exp
-                                        :epoch      (tc/to-epoch (t/now))}})
-              {:stop true})
-          (let [remote-all-files-meta   remote-all-files-meta-or-exp
-                local-all-files-meta    (<! local-all-files-meta-c)
-                {diff-remote-files :result elapsed-time :time}
-                (util/with-time (diff-file-metadata-sets remote-all-files-meta local-all-files-meta))
-                _ (println ::diff-file-metadata-sets-elapsed-time elapsed-time "ms")
-                recent-10-days-range    ((juxt #(tc/to-long (t/minus % (t/days 10))) #(tc/to-long %)) (t/today))
-                sorted-diff-remote-files
-                (sort-by
-                 (sort-file-metadata-fn :recent-days-range recent-10-days-range) > diff-remote-files)
-                remote-txid-or-ex       (<! (<get-remote-txid remoteapi' graph-uuid))
-                latest-txid             (:TXId remote-txid-or-ex)]
-            (if (or (instance? ExceptionInfo remote-txid-or-ex) (nil? latest-txid))
-              (do (put-sync-event! {:event :get-remote-graph-failed
-                                    :data {:graph-uuid graph-uuid
-                                           :exp remote-txid-or-ex
-                                           :epoch (tc/to-epoch (t/now))}})
-                  {:stop true})
-              (do (println "[full-sync(remote->local)]" (count sorted-diff-remote-files) "files need to sync")
-                  (let [filtered-files (filter-download-files-with-reserved-chars sorted-diff-remote-files)]
-                    (swap! *sync-state #(sync-state-reset-full-remote->local-files % sorted-diff-remote-files))
-                    (<! (.sync-files-remote->local!
-                         this (map (juxt relative-path -checksum)
-                                   filtered-files)
-                         latest-txid)))))))))))
-
-(defn- <file-changed?
-  "return true when file changed compared with remote"
-  [graph-uuid file-path-without-base-path base-path]
-  {:pre [(string? file-path-without-base-path)]}
-  (go
-    (let [remote-meta-or-exp (<! (<get-remote-files-meta remoteapi graph-uuid [file-path-without-base-path]))
-          local-meta (first (<! (<get-local-files-meta rsapi graph-uuid base-path [file-path-without-base-path])))]
-      (if (instance? ExceptionInfo remote-meta-or-exp)
-        false
-        (not= (first remote-meta-or-exp) local-meta)))))
-
-(defn- <filter-local-changes-pred
-  "filter local-change events:
-  - for 'unlink' event
-    - when related file exists on local dir, ignore this event
-  - for 'add' | 'change' event
-    - when related file's content is same as remote file, ignore it"
-  [^FileChangeEvent e basepath graph-uuid]
-  (go
-    (let [r-path (relative-path e)]
-      (case (.-type e)
-        "unlink"
-        ;; keep this e when it's not found
-        (<! (<local-file-not-exist? graph-uuid rsapi basepath r-path))
-
-        ("add" "change")
-        ;; 1. local file exists
-        ;; 2. compare with remote file, and changed
-        (and (not (<! (<local-file-not-exist? graph-uuid rsapi basepath r-path)))
-             (<! (<file-changed? graph-uuid r-path basepath)))))))
-
-(defn- <filter-checksum-not-consistent
-  "filter out FileChangeEvents checksum changed,
-  compare checksum in FileChangeEvent and checksum calculated now"
-  [graph-uuid es]
-  {:pre [(or (nil? es) (coll? es))
-         (every? #(instance? FileChangeEvent %) es)]}
-  (go
-    (when (seq es)
-      (if (= "unlink" (.-type ^FileChangeEvent (first es)))
-        es
-        (let [base-path            (.-dir (first es))
-              files-meta           (<! (<get-local-files-meta
-                                        rsapi graph-uuid base-path (mapv relative-path es)))
-              current-checksum-map (when (coll? files-meta) (into {} (mapv (juxt :path :etag) files-meta)))
-              origin-checksum-map  (into {} (mapv (juxt relative-path #(.-checksum ^FileChangeEvent %)) es))
-              origin-map           (into {} (mapv (juxt relative-path identity) es))]
-          (->>
-           (merge-with
-            #(boolean (or (nil? %1) (= "fake-checksum" %1) (= %1 %2)))
-            origin-checksum-map current-checksum-map)
-           (filterv (comp true? second))
-           (mapv first)
-           (select-keys origin-map)
-           vals))))))
-
-(def ^:private file-size-limit (* 100 1000 1024)) ;100MB
-(defn- filter-too-huge-files-aux
-  [e]
-  {:post [(boolean? %)]}
-  (if (= "unlink" (.-type ^FileChangeEvent e))
-    true
-    (boolean
-     (when-some [size (:size (.-stat e))]
-       (< size file-size-limit)))))
-
-(defn- filter-too-huge-files
-  "filter out files > `file-size-limit`"
-  [es]
-  {:pre [(or (nil? es) (coll? es))
-         (every? #(instance? FileChangeEvent %) es)]}
-  (filterv filter-too-huge-files-aux es))
-
-(defn- filter-local-files-in-deletion-logs
-  [local-all-files-meta deletion-logs remote-all-files-meta]
-  (let [deletion-logs-map (into {}
-                                (mapcat
-                                 (fn [log]
-                                   (mapv
-                                    (fn [path] [path (select-keys log [:epoch :TXId])])
-                                    (:paths log))))
-                                deletion-logs)
-        remote-all-files-meta-map (into {} (map (juxt :path identity)) remote-all-files-meta)
-        *keep             (transient #{})
-        *delete           (transient #{})
-        filtered-deletion-logs-map
-        (loop [[deletion-log & others] deletion-logs-map
-               result {}]
-          (if-not deletion-log
-            result
-            (let [[deletion-log-path deletion-log-meta] deletion-log
-                  meta (get remote-all-files-meta-map deletion-log-path)
-                  meta-txid (:txid meta)
-                  deletion-txid (:TXId deletion-log-meta)]
-              (if (and meta-txid deletion-txid
-                       (> meta-txid deletion-txid))
-                (recur others result)
-                (recur others (into result [[deletion-log-path deletion-log-meta]]))))))]
-    (doseq [f local-all-files-meta]
-      (let [epoch-long (some-> (get filtered-deletion-logs-map (:path f))
-                               :epoch
-                               (* 1000))]
-        (if (and epoch-long (> epoch-long (:last-modified f)))
-          (conj! *delete f)
-          (conj! *keep f))))
-    {:keep   (persistent! *keep)
-     :delete (persistent! *delete)}))
-
-(defn- <filter-too-long-filename
-  [graph-uuid local-files-meta]
-  (go (let [origin-fnames    (mapv :path local-files-meta)
-            encrypted-fnames (<! (<encrypt-fnames rsapi graph-uuid origin-fnames))
-            fnames-map (zipmap origin-fnames encrypted-fnames)
-            local-files-meta-map (into {} (map (fn [meta] [(:path meta) meta])) local-files-meta)]
-        (sequence
-         (comp
-          (filter
-           (fn [[path _]]
-             ; 950 = (- 1024 36 36 2)
-             ; 1024 - length of 'user-uuid/graph-uuid/'
-             (<= (count (get fnames-map path)) 950)))
-          (map second))
-         local-files-meta-map))))
-
-(defrecord ^:large-vars/cleanup-todo
- Local->RemoteSyncer [user-uuid graph-uuid base-path repo *sync-state remoteapi'
-                      ^:mutable rate *txid *txid-for-get-deletion-log
-                      ^:mutable remote->local-syncer stop-chan *stopped *paused
-                       ;; control chans
-                      private-immediately-local->remote-chan private-recent-edited-chan]
-  Object
-  (filter-file-change-events-fn [_]
-    (fn [^FileChangeEvent e]
-      (go (and (instance? FileChangeEvent e)
-               (if-let [mtime (:mtime (.-stat e))]
-                 ;; if mtime is not nil, it should be after (- now 1min)
-                 ;; ignore events too early
-                 (> (* 1000 mtime) (tc/to-long (t/minus (t/now) (t/minutes 1))))
-                 true)
-               (or (string/starts-with? (.-dir e) base-path)
-                   (string/starts-with? (str "file://" (.-dir e)) base-path)) ; valid path prefix
-               (not (ignored? e))       ;not ignored
-               ;; download files will also trigger file-change-events, ignore them
-               (if (= "unlink" (:type e))
-                 true
-                 (when-some [recent-remote->local-file-item
-                             (<! (<file-change-event=>recent-remote->local-file-item
-                                  graph-uuid e))]
-                   (not (contains? (:recent-remote->local-files @*sync-state) recent-remote->local-file-item))))))))
-
-  (set-remote->local-syncer! [_ s] (set! remote->local-syncer s))
-
-  ILocal->RemoteSync
-  (setup-local->remote! [_]
-    (async/tap immediately-local->remote-mult private-immediately-local->remote-chan)
-    (async/tap recent-edited-mult private-recent-edited-chan))
-
-  (stop-local->remote! [_]
-    (async/untap immediately-local->remote-mult private-immediately-local->remote-chan)
-    (async/untap recent-edited-mult private-recent-edited-chan)
-    (async/close! stop-chan)
-    (vreset! *stopped true))
-
-  (<ratelimit [this from-chan]
-    (let [<fast-filter-e-fn (.filter-file-change-events-fn this)]
-      (async-util/<ratelimit
-       from-chan rate
-       :filter-fn
-       (fn [e]
-         (go
-           (and (rsapi-ready? rsapi graph-uuid)
-                (<! (<fast-filter-e-fn e))
-                (do
-                  (swap! *sync-state sync-state--add-queued-local->remote-files e)
-                  (let [v (<! (<filter-local-changes-pred e base-path graph-uuid))]
-                    (when-not v
-                      (swap! *sync-state sync-state--remove-queued-local->remote-files e))
-                    v)))))
-       :flush-fn #(swap! *sync-state sync-state-reset-queued-local->remote-files)
-       :stop-ch stop-chan
-       :distinct-key-fn identity
-       :flush-now-ch private-immediately-local->remote-chan
-       :refresh-timeout-ch private-recent-edited-chan)))
-
-  (<sync-local->remote! [_ es]
-    (if (empty? es)
-      (go {:succ true})
-      (let [type         (.-type ^FileChangeEvent (first es))
-            es->paths-xf (comp
-                          (map #(relative-path %))
-                          (remove ignored?))]
-        (go
-          (let [es*   (<! (<filter-checksum-not-consistent graph-uuid es))
-                _     (when (not= (count es*) (count es))
-                        (println :debug :filter-checksum-changed
-                                 (mapv relative-path (set/difference (set es) (set es*)))))
-                es**  (filter-too-huge-files es*)
-                _     (when (not= (count es**) (count es*))
-                        (println :debug :filter-too-huge-files
-                                 (mapv relative-path (set/difference (set es*) (set es**)))))
-                paths (cond-> (sequence es->paths-xf es**)
-                        (not= type "unlink")
-                        filter-upload-files-with-reserved-chars)
-                _     (println :sync-local->remote type paths)
-                r     (if (empty? paths)
-                        (go @*txid)
-                        (case type
-                          ("add" "change")
-                          (<with-pause (<update-remote-files rsapi graph-uuid base-path paths @*txid) *paused)
-
-                          "unlink"
-                          (<with-pause (<delete-remote-files rsapi graph-uuid base-path paths @*txid) *paused)))
-                _               (swap! *sync-state sync-state--add-current-local->remote-files paths)
-                r*              (<! r)
-                [succ? paused?] ((juxt number? :pause) r*)
-                _               (swap! *sync-state sync-state--remove-current-local->remote-files paths succ?)]
-            (cond
-              (need-sync-remote? r*)
-              (do (println :need-sync-remote r*)
-                  {:need-sync-remote true})
-
-              (need-reset-local-txid? r*) ;; TODO: this cond shouldn't be true,
-              ;; but some potential bugs cause local-txid > remote-txid
-              (let [remote-txid-or-ex (<! (<get-remote-txid remoteapi' graph-uuid))
-                    remote-txid             (:TXId remote-txid-or-ex)]
-                (if (or (instance? ExceptionInfo remote-txid-or-ex) (nil? remote-txid))
-                  (do (put-sync-event! {:event :get-remote-graph-failed
-                                        :data  {:graph-uuid graph-uuid
-                                                :exp        remote-txid-or-ex
-                                                :epoch      (tc/to-epoch (t/now))}})
-                      {:stop true})
-                  (do (<! (<update-graphs-txid! remote-txid graph-uuid user-uuid repo))
-                      (reset! *txid remote-txid)
-                      {:succ true})))
-
-              (graph-has-been-deleted? r*)
-              (do (println :graph-has-been-deleted r*)
-                  {:graph-has-been-deleted true})
-
-              (stop-sync-by-rsapi-response? r*)
-              (do (println :stop-sync-caused-by-rsapi-err-response r*)
-                  (notification/show! (t :file-sync/rsapi-cannot-upload-err) :warning false)
-                  {:stop true})
-
-              paused?
-              {:pause true}
-
-              succ?                     ; succ
-              (do
-                (println "sync-local->remote! update txid" r*)
-                ;; persist txid
-                (<! (<update-graphs-txid! r* graph-uuid user-uuid repo))
-                (reset! *txid r*)
-                {:succ true})
-
-              :else
-              (do
-                (println "sync-local->remote unknown:" r*)
-                {:unknown r*})))))))
-
-  (<sync-local->remote-all-files! [this]
-    (go
-      (let [remote-all-files-meta-c      (<get-remote-all-files-meta remoteapi' graph-uuid)
-            local-all-files-meta-c       (<get-local-all-files-meta rsapi graph-uuid base-path)
-            deletion-logs-c              (<get-deletion-logs remoteapi' graph-uuid @*txid-for-get-deletion-log)
-            remote-all-files-meta-or-exp (<! remote-all-files-meta-c)
-            deletion-logs-or-exp         (<! deletion-logs-c)]
-        (cond
-          (or (storage-exceed-limit? remote-all-files-meta-or-exp)
-              (sync-stop-when-api-flying? remote-all-files-meta-or-exp)
-              (decrypt-exp? remote-all-files-meta-or-exp)
-              (instance? ExceptionInfo remote-all-files-meta-or-exp))
-          (do (put-sync-event! {:event :get-remote-all-files-failed
-                                :data  {:graph-uuid graph-uuid
-                                        :exp        remote-all-files-meta-or-exp
-                                        :epoch      (tc/to-epoch (t/now))}})
-              {:stop true})
-
-          (instance? ExceptionInfo deletion-logs-or-exp)
-          (do (put-sync-event! {:event :get-deletion-logs-failed
-                                :data  {:graph-uuid graph-uuid
-                                        :exp        deletion-logs-or-exp
-                                        :epoch      (tc/to-epoch (t/now))}})
-              {:stop true})
-
-          :else
-          (let [remote-all-files-meta  remote-all-files-meta-or-exp
-                local-all-files-meta   (<! local-all-files-meta-c)
-                {local-all-files-meta :keep delete-local-files :delete}
-                (filter-local-files-in-deletion-logs local-all-files-meta deletion-logs-or-exp remote-all-files-meta)
-                recent-10-days-range   ((juxt #(tc/to-long (t/minus % (t/days 10))) #(tc/to-long %)) (t/today))
-                diff-local-files       (->> (diff-file-metadata-sets local-all-files-meta remote-all-files-meta)
-                                            (<filter-too-long-filename graph-uuid)
-                                            <!
-                                            (sort-by (sort-file-metadata-fn :recent-days-range recent-10-days-range) >))
-                change-events
-                (sequence
-                 (comp
-                                         ;; convert to FileChangeEvent
-                  (map #(->FileChangeEvent "change" base-path (.get-normalized-path ^FileMetadata %)
-                                           {:size (:size %)} (:etag %)))
-                  (remove ignored?))
-                 diff-local-files)
-                distinct-change-events (-> (distinct-file-change-events change-events)
-                                           filter-upload-files-with-reserved-chars)
-                _                      (swap! *sync-state #(sync-state-reset-full-local->remote-files % distinct-change-events))
-                change-events-partitions
-                (sequence
-                                        ;; partition FileChangeEvents
-                 (partition-file-change-events upload-batch-size)
-                 distinct-change-events)]
-            (println "[full-sync(local->remote)]"
-                     (count (flatten change-events-partitions)) "files need to sync and"
-                     (count delete-local-files) "local files need to delete")
-            (put-sync-event! {:event :start
-                              :data  {:type       :full-local->remote
-                                      :graph-uuid graph-uuid
-                                      :full-sync? true
-                                      :epoch      (tc/to-epoch (t/now))}})
-            ;; 1. delete local files
-            (loop [[f & fs] delete-local-files]
-              (when f
-                (let [relative-p (relative-path f)]
-                  (when-not (<! (<local-file-not-exist? graph-uuid rsapi base-path relative-p))
-                    (let [fake-recent-remote->local-file-item {:remote->local-type :delete
-                                                               :checksum           nil
-                                                               :path               relative-p}]
-                      (swap! *sync-state sync-state--add-recent-remote->local-files
-                             [fake-recent-remote->local-file-item])
-                      (<! (<delete-local-files rsapi graph-uuid base-path [(relative-path f)]))
-                      (go (<! (timeout 5000))
-                          (swap! *sync-state sync-state--remove-recent-remote->local-files
-                                 [fake-recent-remote->local-file-item])))))
-                (recur fs)))
-
-            ;; 2. upload local files
-            (let [r (loop [es-partitions change-events-partitions]
-                      (if @*stopped
-                        {:stop true}
-                        (if (empty? es-partitions)
-                          {:succ true}
-                          (let [{:keys [succ need-sync-remote graph-has-been-deleted unknown stop] :as r}
-                                (<! (<sync-local->remote! this (first es-partitions)))]
-                            (s/assert ::sync-local->remote!-result r)
-                            (cond
-                              succ
-                              (recur (next es-partitions))
-                              (or need-sync-remote graph-has-been-deleted unknown stop) r)))))]
-              ;; update *txid-for-get-deletion-log
-              (reset! *txid-for-get-deletion-log @*txid)
-              r)))))))
-
-;;; ### put all stuff together
-
-(defrecord ^:large-vars/cleanup-todo
- SyncManager [user-uuid graph-uuid base-path *sync-state
-              ^Local->RemoteSyncer local->remote-syncer ^Remote->LocalSyncer remote->local-syncer remoteapi'
-              ^:mutable ratelimit-local-changes-chan
-              *txid *txid-for-get-deletion-log
-              ^:mutable state ^:mutable remote-change-chan ^:mutable *ws *stopped? *paused?
-              ^:mutable ops-chan ^:mutable app-awake-from-sleep-chan
-               ;; control chans
-              private-full-sync-chan private-remote->local-sync-chan
-              private-remote->local-full-sync-chan private-pause-resume-chan]
-  Object
-  (schedule [this next-state args reason]
-    {:pre [(s/valid? ::state next-state)]}
-    (println "[SyncManager]"
-             (and state (name state)) "->" (and next-state (name next-state)) :reason reason :local-txid @*txid :args args :now (tc/to-string (t/now)))
-    (set! state next-state)
-    (swap! *sync-state sync-state--update-state next-state)
-    (go
-      (case state
-        ::need-password
-        (<! (.need-password this))
-        ::idle
-        (<! (.idle this))
-        ::local->remote
-        (<! (.local->remote this args))
-        ::remote->local
-        (<! (.remote->local this nil args))
-        ::local->remote-full-sync
-        (<! (.full-sync this))
-        ::remote->local-full-sync
-        (<! (.remote->local-full-sync this args))
-        ::pause
-        (<! (.pause this))
-        ::stop
-        (-stop! this))))
-
-  (start [this]
-    (set! ops-chan (chan (async/dropping-buffer 10)))
-    (set! app-awake-from-sleep-chan (chan (async/sliding-buffer 1)))
-    (set! *ws (atom nil))
-    (set! remote-change-chan (ws-listen! graph-uuid *ws))
-    (set! ratelimit-local-changes-chan (<ratelimit local->remote-syncer local-changes-revised-chan))
-    (setup-local->remote! local->remote-syncer)
-    (async/tap full-sync-mult private-full-sync-chan)
-    (async/tap remote->local-sync-mult private-remote->local-sync-chan)
-    (async/tap remote->local-full-sync-mult private-remote->local-full-sync-chan)
-    (async/tap pause-resume-mult private-pause-resume-chan)
-    (async/tap pubsub/app-wake-up-from-sleep-mult app-awake-from-sleep-chan)
-    (go-loop []
-      (let [{:keys [remote->local remote->local-full-sync local->remote-full-sync local->remote resume pause stop]}
-            (async/alt!
-              private-remote->local-full-sync-chan {:remote->local-full-sync true}
-              private-remote->local-sync-chan {:remote->local true}
-              private-full-sync-chan {:local->remote-full-sync true}
-              private-pause-resume-chan ([v] (if v {:resume true} {:pause true}))
-              remote-change-chan ([v] (println "remote change:" v) {:remote->local v})
-              ratelimit-local-changes-chan ([v]
-                                            (if (nil? v)
-                                              {:stop true}
-                                              (let [rest-v (util/drain-chan ratelimit-local-changes-chan)
-                                                    vs     (cons v rest-v)]
-                                                (println "local changes:" vs)
-                                                {:local->remote vs})))
-              app-awake-from-sleep-chan {:remote->local true}
-              (timeout (* 20 60 1000)) {:local->remote-full-sync true}
-              (timeout (* 10 60 1000)) {:remote->local true}
-              :priority true)]
-        (cond
-          stop
-          nil
-
-          remote->local-full-sync
-          (do (util/drain-chan ops-chan)
-              (>! ops-chan {:remote->local-full-sync true})
-              (recur))
-          remote->local
-          (let [txid
-                (if (true? remote->local)
-                  (let [r (<! (<get-remote-txid remoteapi' graph-uuid))]
-                    (when-not (guard-ex r) {:txid (:TXId r)}))
-                  remote->local)]
-            (when (some? txid)
-              (>! ops-chan {:remote->local txid}))
-            (recur))
-          local->remote
-          (do (>! ops-chan {:local->remote local->remote})
-              (recur))
-          local->remote-full-sync
-          (do (util/drain-chan ops-chan)
-              (>! ops-chan {:local->remote-full-sync true})
-              (recur))
-          resume
-          (do (>! ops-chan {:resume true})
-              (recur))
-          pause
-          (do (vreset! *paused? true)
-              (>! ops-chan {:pause true})
-              (recur)))))
-    (.schedule this ::need-password nil nil))
-
-  (need-password
-    [this]
-    (go
-      (let [next-state (<! (<loop-ensure-pwd&keys graph-uuid (state/get-current-repo) *stopped?))]
-        (assert (s/valid? ::state next-state) next-state)
-        (when (= next-state ::idle)
-          (<! (<ensure-set-env&keys graph-uuid *stopped?)))
-        (if @*stopped?
-          (.schedule this ::stop nil nil)
-          (.schedule this next-state nil nil)))))
-
-  (pause [this]
-    (go (<! (<rsapi-cancel-all-requests)))
-    (put-sync-event! {:event :pause
-                      :data  {:graph-uuid graph-uuid
-                              :epoch      (tc/to-epoch (t/now))}})
-    (go-loop []
-      (let [{:keys [resume] :as result} (<! ops-chan)]
-        (cond
-          resume
-          (let [{:keys [remote->local remote->local-full-sync local->remote local->remote-full-sync] :as resume-state}
-                (get @*resume-state graph-uuid)]
-            (resume-state--reset graph-uuid)
-            (vreset! *paused? false)
-            (cond
-              remote->local
-              (offer! private-remote->local-sync-chan true)
-              remote->local-full-sync
-              (offer! private-remote->local-full-sync-chan true)
-              local->remote
-              (>! ops-chan {:local->remote local->remote})
-              local->remote-full-sync
-              (offer! private-full-sync-chan true)
-              :else
-              ;; if resume-state = nil, try a remote->local to sync recent diffs
-              (offer! private-remote->local-sync-chan true))
-            (put-sync-event! {:event :resume
-                              :data  {:graph-uuid   graph-uuid
-                                      :resume-state resume-state
-                                      :epoch        (tc/to-epoch (t/now))}})
-            (<! (.schedule this ::idle nil :resume)))
-
-          (nil? result)
-          (<! (.schedule this ::stop nil nil))
-
-          :else
-          (recur)))))
-
-  (idle [this]
-    (go
-      (let [{:keys [stop remote->local local->remote local->remote-full-sync remote->local-full-sync pause resume] :as result}
-            (<! ops-chan)]
-        (cond
-          (or stop (nil? result))
-          (<! (.schedule this ::stop nil nil))
-          remote->local
-          (<! (.schedule this ::remote->local {:remote remote->local} {:remote-changed remote->local}))
-          local->remote
-          (<! (.schedule this ::local->remote {:local local->remote} {:local-changed local->remote}))
-          local->remote-full-sync
-          (<! (.schedule this ::local->remote-full-sync nil nil))
-          remote->local-full-sync
-          (<! (.schedule this ::remote->local-full-sync nil nil))
-          pause
-          (<! (.schedule this ::pause nil nil))
-          resume
-          (<! (.schedule this ::idle nil nil))
-          :else
-          (do
-            (state/pub-event! [:capture-error {:error (js/Error. "sync/wrong-ops-chan-when-idle")
-                                               :payload {:type :sync/wrong-ops-chan-when-idle
-                                                         :ops-chan-result result
-                                                         :user-id user-uuid
-                                                         :graph-id graph-uuid}}])
-            (<! (.schedule this ::idle nil nil)))))))
-
-  (full-sync [this]
-    (go
-      (let [{:keys [succ need-sync-remote graph-has-been-deleted unknown stop] :as r}
-            (<! (<sync-local->remote-all-files! local->remote-syncer))]
-        (s/assert ::sync-local->remote-all-files!-result r)
-        (cond
-          succ
-          (do
-            (swap! *sync-state #(sync-state-reset-full-local->remote-files % []))
-            (put-sync-event! {:event :finished-local->remote
-                              :data  {:graph-uuid graph-uuid
-                                      :full-sync? true
-                                      :epoch      (tc/to-epoch (t/now))}})
-            (.schedule this ::idle nil nil))
-          need-sync-remote
-          (do (util/drain-chan ops-chan)
-              (>! ops-chan {:remote->local true})
-              (>! ops-chan {:local->remote-full-sync true})
-              (.schedule this ::idle nil nil))
-
-          graph-has-been-deleted
-          (.schedule this ::stop nil :graph-has-been-deleted)
-
-          stop
-          (.schedule this ::stop nil nil)
-          unknown
-          (do
-            (state/pub-event! [:capture-error {:error unknown
-                                               :payload {:type :sync/unknown
-                                                         :event :local->remote-full-sync-failed
-                                                         :user-id user-uuid
-                                                         :graph-uuid graph-uuid}}])
-            (put-sync-event! {:event :local->remote-full-sync-failed
-                              :data  {:graph-uuid graph-uuid
-                                      :epoch      (tc/to-epoch (t/now))}})
-            (.schedule this ::idle nil nil))))))
-
-  (remote->local-full-sync [this {:keys [retry-count]}]
-    (go
-      (let [{:keys [succ unknown stop pause]}
-            (<! (<sync-remote->local-all-files! remote->local-syncer))]
-        (cond
-          succ
-          (do (put-sync-event! {:event :finished-remote->local
-                                :data  {:graph-uuid graph-uuid
-                                        :full-sync? true
-                                        :epoch      (tc/to-epoch (t/now))}})
-              (.schedule this ::idle nil nil))
-          stop
-          (.schedule this ::stop nil nil)
-          pause
-          (do (resume-state--add-remote->local-full-sync-state graph-uuid)
-              (.schedule this ::pause nil nil))
-          unknown
-          (do
-            (state/pub-event! [:capture-error {:error unknown
-                                               :payload {:event :remote->local-full-sync-failed
-                                                         :type :sync/unknown
-                                                         :graph-uuid graph-uuid
-                                                         :user-id user-uuid}}])
-            (put-sync-event! {:event :remote->local-full-sync-failed
-                              :data  {:graph-uuid graph-uuid
-                                      :exp        unknown
-                                      :epoch      (tc/to-epoch (t/now))}})
-            (let [next-state
-                  (cond
-                    (string/includes? (str (ex-cause unknown)) "404 Not Found")
-                    ;; TODO: this should never happen
-                    ::stop
-                    (> retry-count 3)
-                    ::stop
-
-                    :else ;; if any other exception occurred, re-exec remote->local-full-sync
-                    ::remote->local-full-sync)]
-              (.schedule this next-state
-                         (when (= ::remote->local-full-sync next-state) {:retry-count (inc retry-count)})
-                         nil)))))))
-
-  (remote->local [this _next-state {remote-val :remote}]
-    (go
-      (if (some-> remote-val :txid (<= @*txid))
-        (.schedule this ::idle nil nil)
-        (let [origin-txid @*txid
-              {:keys [succ unknown stop pause need-remote->local-full-sync] :as r}
-              (<! (<sync-remote->local! remote->local-syncer))]
-          (s/assert ::sync-remote->local!-result r)
-          (cond
-            need-remote->local-full-sync
-            (do (util/drain-chan ops-chan)
-                (>! ops-chan {:remote->local-full-sync true})
-                (>! ops-chan {:local->remote-full-sync true})
-                (.schedule this ::idle nil nil))
-            succ
-            (do (put-sync-event! {:event :finished-remote->local
-                                  :data  {:graph-uuid graph-uuid
-                                          :full-sync? false
-                                          :from-txid  origin-txid
-                                          :to-txid    @*txid
-                                          :epoch      (tc/to-epoch (t/now))}})
-                (.schedule this ::idle nil nil))
-            stop
-            (.schedule this ::stop nil nil)
-            pause
-            (do (resume-state--add-remote->local-state graph-uuid)
-                (.schedule this ::pause nil nil))
-            unknown
-            (do (prn "remote->local err" unknown)
-                (state/pub-event! [:capture-error {:error unknown
-                                                   :payload {:type :sync/unknown
-                                                             :event :remote->local
-                                                             :user-id user-uuid
-                                                             :graph-uuid graph-uuid}}])
-                (.schedule this ::idle nil nil)))))))
-
-  (local->remote [this {local-changes :local}]
-   ;; local-changes:: list of FileChangeEvent
-    (assert (some? local-changes) local-changes)
-    (go
-      (let [distincted-local-changes (distinct-file-change-events local-changes)
-            _ (swap! *sync-state #(sync-state-reset-full-local->remote-files % distincted-local-changes))
-            change-events-partitions
-            (sequence (partition-file-change-events upload-batch-size) distincted-local-changes)
-            _ (put-sync-event! {:event :start
-                                :data  {:type       :local->remote
-                                        :graph-uuid graph-uuid
-                                        :full-sync? false
-                                        :epoch      (tc/to-epoch (t/now))}})
-            {:keys [succ need-sync-remote graph-has-been-deleted unknown stop pause]}
-            (loop [es-partitions change-events-partitions]
-              (cond
-                @*stopped?             {:stop true}
-                @*paused?              {:pause true}
-                (empty? es-partitions) {:succ true}
-                :else
-                (let [{:keys [succ need-sync-remote graph-has-been-deleted pause unknown stop] :as r}
-                      (<! (<sync-local->remote! local->remote-syncer (first es-partitions)))]
-                  (s/assert ::sync-local->remote!-result r)
-                  (cond
-                    succ
-                    (recur (next es-partitions))
-                    (or need-sync-remote graph-has-been-deleted unknown pause stop) r))))]
-        (cond
-          succ
-          (do
-            (swap! *sync-state #(sync-state-reset-full-local->remote-files % []))
-            (put-sync-event! {:event :finished-local->remote
-                              :data  {:graph-uuid         graph-uuid
-                                      :full-sync?         false
-                                      :file-change-events distincted-local-changes
-                                      :epoch              (tc/to-epoch (t/now))}})
-            (.schedule this ::idle nil nil))
-
-          need-sync-remote
-          (do (util/drain-chan ops-chan)
-              (>! ops-chan {:remote->local true})
-              (>! ops-chan {:local->remote local-changes})
-              (.schedule this ::idle nil nil))
-
-          graph-has-been-deleted
-          (.schedule this ::stop nil :graph-has-been-deleted)
-
-          stop
-          (.schedule this ::stop nil nil)
-
-          pause
-          (do (resume-state--add-local->remote-state graph-uuid local-changes)
-              (.schedule this ::pause nil nil))
-
-          unknown
-          (do
-            (debug/pprint "local->remote" unknown)
-            (state/pub-event! [:capture-error {:error unknown
-                                               :payload {:event :local->remote
-                                                         :type :sync/unknown
-                                                         :user-id user-uuid
-                                                         :graph-uuid graph-uuid}}])
-            (.schedule this ::idle nil nil))))))
-  IStoppable
-  (-stop! [_]
-    (go
-      (when-not @*stopped?
-        (vreset! *stopped? true)
-        (ws-stop! *ws)
-        (async/untap full-sync-mult private-full-sync-chan)
-        (async/untap remote->local-sync-mult private-remote->local-sync-chan)
-        (async/untap remote->local-full-sync-mult private-remote->local-full-sync-chan)
-        (async/untap pause-resume-mult private-pause-resume-chan)
-        (async/untap pubsub/app-wake-up-from-sleep-mult app-awake-from-sleep-chan)
-        (when ops-chan (async/close! ops-chan))
-        (stop-local->remote! local->remote-syncer)
-        (stop-remote->local! remote->local-syncer)
-        (<! (<rsapi-cancel-all-requests))
-        (swap! *sync-state sync-state--update-state ::stop)
-        (reset! current-sm-graph-uuid nil)
-        (debug/pprint ["stop sync-manager, graph-uuid" graph-uuid "base-path" base-path]))))
-
-  IStopped?
-  (-stopped? [_]
-    @*stopped?))
-
-(defn sync-manager [user-uuid graph-uuid base-path repo txid *sync-state]
-  (let [*txid (atom txid)
-        *txid-for-get-deletion-log (atom txid)
-        *stopped? (volatile! false)
-        *paused? (volatile! false)
-        remoteapi-with-stop (->RemoteAPI *stopped?)
-        local->remote-syncer (->Local->RemoteSyncer user-uuid graph-uuid
-                                                    base-path
-                                                    repo *sync-state remoteapi-with-stop
-                                                    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
-                                                    repo *txid *txid-for-get-deletion-log *sync-state remoteapi-with-stop
-                                                    nil *stopped? *paused?)]
-    (.set-remote->local-syncer! local->remote-syncer remote->local-syncer)
-    (.set-local->remote-syncer! remote->local-syncer local->remote-syncer)
-    (swap! *sync-state sync-state--update-current-syncing-graph-uuid graph-uuid)
-    (->SyncManager user-uuid graph-uuid base-path *sync-state local->remote-syncer remote->local-syncer remoteapi-with-stop
-                   nil *txid *txid-for-get-deletion-log nil nil nil *stopped? *paused? nil nil (chan 1) (chan 1) (chan 1) (chan 1))))
-
-(defn sync-manager-singleton
-  [user-uuid graph-uuid base-path repo txid *sync-state]
-  (when-not @current-sm-graph-uuid
-    (reset! current-sm-graph-uuid graph-uuid)
-    (sync-manager user-uuid graph-uuid base-path repo txid *sync-state)))
-
-(defn <sync-stop []
-  (go
-    (when-let [sm ^SyncManager (state/get-file-sync-manager (state/get-current-file-sync-graph-uuid))]
-      (println "[SyncManager]" "stopping")
-
-      (state/clear-file-sync-state! (:graph-uuid sm))
-
-      (<! (-stop! sm))
-
-      (println "[SyncManager]" "stopped"))
-
-    (reset! current-sm-graph-uuid nil)))
-
-(defn sync-need-password!
-  []
-  (when-let [sm ^SyncManager (state/get-file-sync-manager (state/get-current-file-sync-graph-uuid))]
-    (.need-password sm)))
-
-(defn check-graph-belong-to-current-user
-  [current-user-uuid graph-user-uuid]
-  (cond
-    (nil? current-user-uuid)
-    false
-
-    (= current-user-uuid graph-user-uuid)
-    true
-
-    :else
-    (do (notification/show! (t :file-sync/other-user-graph) :warning false)
-        false)))
-
-(defn <check-remote-graph-exists
-  [local-graph-uuid]
-  {:pre [(util/uuid-string? local-graph-uuid)]}
-  (go
-    (let [r (<! (<list-remote-graphs remoteapi))
-          result
-          (or
-             ;; if api call failed, assume this remote graph still exists
-           (instance? ExceptionInfo r)
-           (and
-            (contains? r :Graphs)
-            (->> (:Graphs r)
-                 (mapv :GraphUUID)
-                 set
-                 (#(contains? % local-graph-uuid)))))]
-
-      (when-not result
-        (notification/show! (t :file-sync/graph-deleted) :warning false))
-      result)))
-
-(defn sync-off?
-  [sync-state]
-  (or (nil? sync-state) (sync-state--stopped? sync-state)))
-
-(defn graph-sync-off?
-  "Is sync not running for this `graph-uuid`?"
-  [graph-uuid]
-  (sync-off? (state/get-file-sync-state graph-uuid)))
-
-(defn graph-encrypted?
-  []
-  (when-let [graph-uuid (second @graphs-txid)]
-    (get-pwd graph-uuid)))
-
-(defn- <connectivity-testing
-  []
-  (go
-    (let [api-url (str "https://" config/API-DOMAIN "/logseq/version")
-          r1 (http/get api-url {:with-credentials? false})
-          r2 (http/get config/CONNECTIVITY-TESTING-S3-URL {:with-credentials? false})
-          r1* (<! r1)
-          r2* (<! r2)
-          ok? (and (= 200 (:status r1*))
-                   (= 200 (:status r2*))
-                   (= "OK" (:body r2*)))]
-      (when (user/logged-in?)
-        (if ok?
-          (notification/clear! :sync-connection-failed)
-          (notification/show! [:div
-                               (t :file-sync/connectivity-testing-failed)
-                               [:a {:href api-url} api-url]
-                               " and "
-                               [:a
-                                {:href config/CONNECTIVITY-TESTING-S3-URL}
-                                config/CONNECTIVITY-TESTING-S3-URL]]
-                              :warning
-                              false
-                              :sync-connection-failed)))
-      ok?)))
-
-(declare network-online-cursor)
-
-(def ^:private *sync-starting
-  "Avoid running multiple sync instances simultaneously."
-  (atom false))
-
-(defn- <should-start-sync?
-  []
-  (go
-    (and (state/enable-sync?)
-         @network-online-cursor     ;; is online
-         (user/has-refresh-token?)  ;; has refresh token, should bring up sync
-         (or (= ::stop (:state (state/get-file-sync-state))) ;; state=stopped
-             (nil? (state/get-file-sync-state)))  ;; the whole sync state not inited yet, happens when app starts without network
-         (<! (p->c (persist-var/-load graphs-txid))))))  ;; not a sync graph))
-
-(defn <sync-start
-  []
-  (go
-    (when-not (or @*sync-starting (util/mobile?) util/web-platform?)
-      (reset! *sync-starting true)
-      (if-not (and (<! (<should-start-sync?))
-                   (<! (<connectivity-testing)))
-        (reset! *sync-starting false)
-        (try
-          (let [*sync-state                 (atom (new-sync-state))
-                current-user-uuid           (<! (user/<user-uuid))
-              ;; put @graph-uuid & get-current-repo together,
-              ;; prevent to get older repo dir and current graph-uuid.
-                _                           (<! (p->c (persist-var/-load graphs-txid)))
-                [user-uuid graph-uuid txid] @graphs-txid
-                txid                        (or txid 0)
-                repo                        (state/get-current-repo)]
-            (when-not (instance? ExceptionInfo current-user-uuid)
-              (when (and repo
-                         @network-online-cursor
-                         user-uuid graph-uuid txid
-                         (graph-sync-off? graph-uuid)
-                         (user/logged-in?)
-                         (not (config/demo-graph? repo)))
-                (when-let [sm (sync-manager-singleton current-user-uuid graph-uuid
-                                                      (config/get-repo-dir repo) repo
-                                                      txid *sync-state)]
-                  (when (check-graph-belong-to-current-user current-user-uuid user-uuid)
-                    (if-not (<! (<check-remote-graph-exists graph-uuid)) ; remote graph has been deleted
-                      (clear-graphs-txid! repo)
-                      (do
-                        (state/set-file-sync-state graph-uuid @*sync-state)
-                        (state/set-file-sync-manager graph-uuid sm)
-
-                      ;; update global state when *sync-state changes
-                        (add-watch *sync-state ::update-global-state
-                                   (fn [_ _ _ n]
-                                     (state/set-file-sync-state graph-uuid n)))
-
-                        (state/set-state! [:file-sync/graph-state :current-graph-uuid] graph-uuid)
-
-                        (.start sm)
-
-                        (offer! remote->local-full-sync-chan true)
-                        (offer! full-sync-chan true))))))))
-          (catch :default e
-            (prn "Sync start error: ")
-            (log/error :exception e))
-          (finally
-            (reset! *sync-starting false)))))))
-
-(def finished-local->remote-chan (chan 1))
-
-;;; ### some add-watches
-
-;; TODO: replace this logic by pause/resume state
-(defonce network-online-cursor (rum/cursor state/state :network/online?))
-(add-watch network-online-cursor "sync-manage"
-           (fn [_k _r o n]
-             (cond
-               (and (true? o) (false? n))
-               (<sync-stop)
-
-               (and (false? o) (true? n))
-               (<sync-start)
-
-               :else
-               nil)))
-
-(defonce auth-id-token-cursor (rum/cursor state/state :auth/id-token))
-(add-watch auth-id-token-cursor "sync-manage"
-           (fn [_k _r _o n]
-             (when (nil? n)
-               (<sync-stop))))
-
-;; try to re-start sync when state=stopped every 1min
-(go-loop []
-  (<! (timeout 60000))
-  (when (<! (<should-start-sync?))
-    (println "trying to restart sync..." (tc/to-string (t/now)))
-    (<sync-start))
-  (recur))
-
-;;; ### some sync events handler
-
-;; re-exec remote->local-full-sync when it failed before
-(def re-remote->local-full-sync-chan (chan 1))
-(async/sub pubsub/sync-events-pub :remote->local-full-sync-failed re-remote->local-full-sync-chan)
-(go-loop []
-  (let [{{graph-uuid :graph-uuid} :data} (<! re-remote->local-full-sync-chan)
-        {:keys [current-syncing-graph-uuid]}
-        (state/get-file-sync-state graph-uuid)]
-    (when (= graph-uuid current-syncing-graph-uuid)
-      (offer! remote->local-full-sync-chan true))
-    (recur)))
-
-;; re-exec local->remote-full-sync when it failed
-(def re-local->remote-full-sync-chan (chan 1))
-(async/sub pubsub/sync-events-pub :local->remote-full-sync-failed re-local->remote-full-sync-chan)
-(go-loop []
-  (let [{{graph-uuid :graph-uuid} :data} (<! re-local->remote-full-sync-chan)
-        {:keys [current-syncing-graph-uuid]} (state/get-file-sync-state graph-uuid)]
-    (when (= graph-uuid current-syncing-graph-uuid)
-      (offer! full-sync-chan true))
-    (recur)))
-
-;;; add-tap
-(comment
-  (def *x (atom nil))
-  (add-tap (fn [v] (reset! *x v))))

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

@@ -22,7 +22,6 @@
             [frontend.handler.events :as events]
             [frontend.handler.events.rtc]
             [frontend.handler.events.ui]
-            [frontend.handler.file-based.events]
             [frontend.handler.file-based.file :as file-handler]
             [frontend.handler.global-config :as global-config-handler]
             [frontend.handler.page :as page-handler]

+ 2 - 27
src/main/frontend/handler/events.cljs

@@ -6,7 +6,6 @@
   (:require ["@sentry/react" :as Sentry]
             [cljs-bean.core :as bean]
             [clojure.core.async :as async]
-            [clojure.core.async.interop :refer [p->c]]
             [clojure.string :as string]
             [frontend.commands :as commands]
             [frontend.components.rtc.indicator :as indicator]
@@ -18,7 +17,6 @@
             [frontend.db.react :as react]
             [frontend.extensions.fsrs :as fsrs]
             [frontend.fs :as fs]
-            [frontend.fs.sync :as sync]
             [frontend.fs.watcher-handler :as fs-watcher]
             [frontend.handler.assets :as assets-handler]
             [frontend.handler.code :as code-handler]
@@ -47,7 +45,6 @@
             [frontend.quick-capture :as quick-capture]
             [frontend.state :as state]
             [frontend.util :as util]
-            [frontend.util.persist-var :as persist-var]
             [goog.dom :as gdom]
             [lambdaisland.glogi :as log]
             [logseq.db.frontend.schema :as db-schema]
@@ -58,30 +55,16 @@
 
 (defmulti handle first)
 
-(defn file-sync-restart! []
-  (async/go (async/<! (p->c (persist-var/load-vars)))
-            (async/<! (sync/<sync-stop))
-            (some-> (sync/<sync-start) async/<!)))
-
-(defn file-sync-stop! []
-  (async/go (async/<! (p->c (persist-var/load-vars)))
-            (async/<! (sync/<sync-stop))))
-
 (defmethod handle :graph/added [[_ repo {:keys [empty-graph?]}]]
   (search-handler/rebuild-indices!)
   (plugin-handler/hook-plugin-app :graph-after-indexed {:repo repo :empty-graph? empty-graph?})
   (route-handler/redirect-to-home!)
   (when-let [dir-name (and (not (config/db-based-graph? repo)) (config/get-repo-dir repo))]
-    (fs/watch-dir! dir-name))
-  (file-sync-restart!))
+    (fs/watch-dir! dir-name)))
 
 (defmethod handle :init/commands [_]
   (page-handler/init-commands!))
 
-(defmethod handle :graph/unlinked [repo current-repo]
-  (when (= (:url repo) current-repo)
-    (file-sync-restart!)))
-
 (defn- graph-switch
   [graph]
   (react/clear-query-state!)
@@ -92,10 +75,6 @@
     (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)})))
@@ -197,14 +176,10 @@
   (posthog/capture type payload))
 
 (defmethod handle :capture-error [[_ {:keys [error payload extra]}]]
-  (let [[user-uuid graph-uuid tx-id] @sync/graphs-txid
-        payload (merge
+  (let [payload (merge
                  {:schema-version (str db-schema/version)
                   :db-schema-version (when-let [db (db/get-db)]
                                        (str (:kv/value (db/entity db :logseq.kv/schema-version))))
-                  :user-id user-uuid
-                  :graph-id graph-uuid
-                  :tx-id tx-id
                   :db-based (config/db-based-graph? (state/get-current-repo))}
                  payload)]
     (Sentry/captureException error

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

@@ -3,6 +3,7 @@
   (:require [frontend.common.crypt :as crypt]
             [frontend.components.e2ee :as e2ee]
             [frontend.handler.events :as events]
+            [frontend.handler.notification :as notification]
             [frontend.state :as state]
             [lambdaisland.glogi :as log]
             [logseq.shui.ui :as shui]
@@ -39,3 +40,9 @@
                   (p/reject! password-promise (ex-info "cancelled" {}))
                   (shui/dialog-close!))})
     password-promise))
+
+(defmethod events/handle :rtc/storage-exceed-limit [[_]]
+  (notification/show! "Sync storage exceed limit" :warning false))
+
+(defmethod events/handle :rtc/graph-count-exceed-limit [[_]]
+  (notification/show! "Sync graph count exceed limit" :warning false))

+ 2 - 56
src/main/frontend/handler/events/ui.cljs

@@ -4,7 +4,6 @@
             [clojure.core.async.interop :refer [p->c]]
             [frontend.components.assets :as assets]
             [frontend.components.cmdk.core :as cmdk]
-            [frontend.components.file-sync :as file-sync]
             [frontend.components.page :as component-page]
             [frontend.components.plugins :as plugin]
             [frontend.components.property.dialog :as property-dialog]
@@ -17,16 +16,12 @@
             [frontend.components.user.login :as login]
             [frontend.components.whiteboard :as whiteboard]
             [frontend.config :as config]
-            [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
             [frontend.extensions.fsrs :as fsrs]
             [frontend.extensions.srs :as srs]
-            [frontend.fs.sync :as sync]
             [frontend.handler.db-based.rtc :as rtc-handler]
             [frontend.handler.editor :as editor-handler]
             [frontend.handler.events :as events]
-            [frontend.handler.file-based.native-fs :as nfs-handler]
-            [frontend.handler.file-sync :as file-sync-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.page :as page-handler]
             [frontend.handler.plugin :as plugin-handler]
@@ -36,7 +31,6 @@
             [frontend.mobile.util :as mobile-util]
             [frontend.modules.instrumentation.sentry :as sentry-event]
             [frontend.state :as state]
-            [frontend.ui :as ui]
             [frontend.util :as util]
             [goog.dom :as gdom]
             [logseq.common.util :as common-util]
@@ -165,23 +159,6 @@
      (plugin/perf-tip-content (.-id o) (.-name opts) (.-url opts))
      :warning false (.-id o))))
 
-(defn- refresh-cb []
-  (page-handler/create-today-journal!)
-  (events/file-sync-restart!))
-
-(defmethod events/handle :graph/ask-for-re-fresh [_]
-  (shui/dialog-open!
-   [:div {:style {:max-width 700}}
-    [:p (t :sync-from-local-changes-detected)]
-    [:div.flex.justify-end
-     (ui/button
-      (t :yes)
-      :autoFocus "on"
-      :class "ui__modal-enter"
-      :on-click (fn []
-                  (shui/dialog-close!)
-                  (nfs-handler/refresh! (state/get-current-repo) refresh-cb)))]]))
-
 (defn- editor-new-property [block target {:keys [selected-blocks popup-id] :as opts}]
   (let [editing-block (state/get-edit-block)
         pos (state/get-edit-pos)
@@ -291,9 +268,6 @@
   (state/set-state! :mobile/show-action-bar? false))
 
 (defmethod events/handle :user/logout [[_]]
-  (file-sync-handler/reset-session-graphs)
-  (sync/remove-all-pwd!)
-  (file-sync-handler/reset-user-state!)
   (login/sign-out!))
 
 (defmethod events/handle :user/login [[_ host-ui?]]
@@ -317,17 +291,10 @@
     :title (str (if asset-block "Edit" "Create") " asset")
     :center? true}))
 
-(defn- enable-beta-features!
-  []
-  (when-not (false? (state/enable-sync?)) ; user turns it off
-    (file-sync-handler/set-sync-enabled! true)))
-
-;; TODO: separate rtc and file-based implementation
 (defmethod events/handle :user/fetch-info-and-graphs [[_]]
   (state/set-state! [:ui/loading? :login] false)
   (async/go
-    (let [result (async/<! (sync/<user-info sync/remoteapi))
-          mobile-or-web? (or (util/mobile?) util/web-platform?)]
+    (let [result (async/<! (user-handler/<user-info user-handler/remoteapi))]
       (cond
         (instance? ExceptionInfo result)
         nil
@@ -338,29 +305,8 @@
             (sentry-event/set-user! uid))
           (let [status (if (user-handler/alpha-or-beta-user?) :welcome :unavailable)]
             (when (and (= status :welcome) (user-handler/logged-in?))
-              (enable-beta-features!)
               (async/<! (p->c (rtc-handler/<get-remote-graphs)))
-              (when-not mobile-or-web?
-                (async/<! (file-sync-handler/load-session-graphs)))
-              (p/let [repos (repo-handler/refresh-repos!)]
-                (when-let [repo (state/get-current-repo)]
-                  (when-not mobile-or-web?
-                    (when (some #(and (= (:url %) repo)
-                                      (vector? (:sync-meta %))
-                                      (util/uuid-string? (first (:sync-meta %)))
-                                      (util/uuid-string? (second (:sync-meta %)))) repos)
-                      (sync/<sync-start))))))
-            (when-not mobile-or-web?
-              (file-sync/maybe-onboarding-show status))))))))
-
-(defmethod events/handle :file-sync/onboarding-tip [[_ type opts]]
-  (let [type (keyword type)]
-    (when-not (config/db-based-graph? (state/get-current-repo))
-      (shui/dialog-open!
-       (file-sync/make-onboarding-panel type)
-       (merge {:close-btn? false
-               :center? true
-               :close-backdrop? (not= type :welcome)} opts)))))
+              (repo-handler/refresh-repos!))))))))
 
 (defmethod events/handle :dialog/show-block [[_ block option]]
   (shui/dialog-open!

+ 0 - 382
src/main/frontend/handler/file_based/events.cljs

@@ -1,382 +0,0 @@
-(ns frontend.handler.file-based.events
-  "Events that are only for file graphs"
-  (:require [clojure.core.async :as async]
-            [clojure.set :as set]
-            [clojure.string :as string]
-            [frontend.components.diff :as diff]
-            [frontend.components.encryption :as encryption]
-            [frontend.components.file-based.git :as git-component]
-            [frontend.components.file-sync :as file-sync]
-            [frontend.config :as config]
-            [frontend.context.i18n :refer [t]]
-            [frontend.db :as db]
-            [frontend.db.async :as db-async]
-            [frontend.fs :as fs]
-            [frontend.fs.sync :as sync]
-            [frontend.handler.common :as common-handler]
-            [frontend.handler.editor :as editor-handler]
-            [frontend.handler.events :as events]
-            [frontend.handler.file-based.file :as file-handler]
-            [frontend.handler.file-based.native-fs :as nfs-handler]
-            [frontend.handler.file-sync :as file-sync-handler]
-            [frontend.handler.notification :as notification]
-            [frontend.handler.page :as page-handler]
-            [frontend.handler.property :as property-handler]
-            [frontend.handler.repo :as repo-handler]
-            [frontend.handler.route :as route-handler]
-            [frontend.handler.ui :as ui-handler]
-            [frontend.handler.user :as user-handler]
-            [frontend.mobile.graph-picker :as graph-picker]
-            [frontend.mobile.util :as mobile-util]
-            [frontend.modules.shortcut.core :as st]
-            [frontend.state :as state]
-            [frontend.ui :as ui]
-            [frontend.util :as util]
-            [logseq.common.config :as common-config]
-            [logseq.shui.ui :as shui]
-            [promesa.core :as p]
-            [rum.core :as rum]))
-
-(defmethod events/handle :graph/ask-for-re-index [[_ *multiple-windows? ui]]
-  ;; *multiple-windows? - if the graph is opened in multiple windows, boolean atom
-  ;; ui - custom message to show on asking for re-index
-  (if (and (util/atom? *multiple-windows?) @*multiple-windows?)
-    (shui/dialog-open!
-     [:div
-      (when (not (nil? ui)) ui)
-      [:p (t :re-index-multiple-windows-warning)]])
-
-    (shui/dialog-open!
-     [:div {:style {:max-width 700}}
-      (when (not (nil? ui)) ui)
-      [:p (t :re-index-discard-unsaved-changes-warning)]
-      [:div.flex.justify-end.pt-2
-       (ui/button
-        (t :yes)
-        :autoFocus "on"
-        :class "ui__modal-enter"
-        :on-click (fn []
-                    (shui/dialog-close!)
-                    (state/pub-event! [:graph/re-index])))]])))
-
-(defmethod events/handle :graph/re-index [[_]]
-  ;; Ensure the graph only has ONE window instance
-  (when (config/local-file-based-graph? (state/get-current-repo))
-    (async/go
-      (async/<! (sync/<sync-stop))
-      (repo-handler/re-index!
-       nfs-handler/rebuild-index!
-       #(do (page-handler/create-today-journal!)
-            (events/file-sync-restart!))))))
-
-(defn set-block-query-properties!
-  [block-id all-properties key add?]
-  (when-let [block (db/entity [:block/uuid block-id])]
-    (let [query-properties (get-in block [:block/properties :query-properties])
-          repo (state/get-current-repo)
-          query-properties (some-> query-properties
-                                   (common-handler/safe-read-string "Parsing query properties failed"))
-          query-properties (if (seq query-properties)
-                             query-properties
-                             all-properties)
-          query-properties (if add?
-                             (distinct (conj query-properties key))
-                             (remove #{key} query-properties))
-          query-properties (vec query-properties)]
-      (if (seq query-properties)
-        (property-handler/set-block-property! repo block-id
-                                              :query-properties
-                                              (str query-properties))
-        (property-handler/remove-block-property! repo block-id :query-properties)))))
-
-(defonce *query-properties (atom {}))
-(rum/defc query-properties-settings-inner < rum/reactive
-  {:will-unmount (fn [state]
-                   (reset! *query-properties {})
-                   state)}
-  [block shown-properties all-properties]
-  (let [query-properties (rum/react *query-properties)]
-    [:div
-     [:h1.font-semibold.-mt-2.mb-2.text-lg (t :query/config-property-settings)]
-     [:a.flex
-      {:title "Refresh list of columns"
-       :on-click
-       (fn []
-         (reset! *query-properties {})
-         (property-handler/remove-block-property! (state/get-current-repo) (:block/uuid block) :query-properties))}
-      (ui/icon "refresh")]
-     (for [property all-properties]
-       (let [property-value (get query-properties property)
-             shown? (if (nil? property-value)
-                      (contains? shown-properties property)
-                      property-value)]
-         [:div.flex.flex-row.my-2.justify-between.align-items
-          [:div (name property)]
-          [:div.mt-1 (ui/toggle shown?
-                                (fn []
-                                  (let [value (not shown?)]
-                                    (swap! *query-properties assoc property value)
-                                    (set-block-query-properties!
-                                     (:block/uuid block)
-                                     all-properties
-                                     property
-                                     value)))
-                                true)]]))]))
-
-(defn query-properties-settings
-  [block shown-properties all-properties]
-  (fn [_close-fn]
-    (query-properties-settings-inner block shown-properties all-properties)))
-
-(defmethod events/handle :modal/set-query-properties [[_ block all-properties]]
-  (let [query-properties (get-in block [:block/properties :query-properties])
-        block-properties (some-> query-properties
-                                 (common-handler/safe-read-string "Parsing query properties failed"))
-        shown-properties (if (seq block-properties)
-                           (set block-properties)
-                           (set all-properties))
-        shown-properties (set/intersection (set all-properties) shown-properties)]
-    (shui/dialog-open!
-     (query-properties-settings block shown-properties all-properties)
-     {})))
-
-(defmethod events/handle :modal/set-git-username-and-email [[_ _content]]
-  (shui/dialog-open! git-component/set-git-username-and-email))
-
-(defmethod events/handle :file/not-matched-from-disk [[_ path disk-content db-content]]
-  (when-let [repo (state/get-current-repo)]
-    (shui/dialog-open!
-     #(diff/local-file repo path disk-content db-content)
-     {:label "diff__cp"})))
-
-(defmethod events/handle :modal/display-file-version-selector  [[_ versions path  get-content]]
-  (shui/dialog-open!
-   #(git-component/file-version-selector versions path get-content)))
-
-(defmethod events/handle :modal/remote-encryption-input-pw-dialog [[_ repo-url remote-graph-info type opts]]
-  (shui/dialog-open!
-   (encryption/input-password
-    repo-url nil (merge
-                  (assoc remote-graph-info
-                         :type (or type :create-pwd-remote)
-                         :repo repo-url)
-                  opts))
-   {:center? true :close-btn? false :close-backdrop? false}))
-
-(defmethod events/handle :graph/pull-down-remote-graph [[_ graph dir-name]]
-  (if (mobile-util/native-ios?)
-    (when-let [graph-name (or dir-name (:GraphName graph))]
-      (let [graph-name (util/safe-sanitize-file-name graph-name)]
-        (if (string/blank? graph-name)
-          (notification/show! "Illegal graph folder name.")
-
-          ;; Create graph directory under Logseq document folder (local)
-          (when-let [root (state/get-local-container-root-url)]
-            (let [graph-path (graph-picker/validate-graph-dirname root graph-name)]
-              (->
-               (p/let [exists? (fs/dir-exists? graph-path)]
-                 (let [overwrite? (if exists?
-                                    (js/confirm (str "There's already a directory with the name \"" graph-name "\", do you want to overwrite it? Make sure to backup it first if you're not sure about it."))
-                                    true)]
-                   (if overwrite?
-                     (p/let [_ (fs/mkdir-if-not-exists graph-path)]
-                       (nfs-handler/ls-dir-files-with-path!
-                        graph-path
-                        {:ok-handler (fn []
-                                       (file-sync-handler/init-remote-graph graph-path graph)
-                                       (js/setTimeout (fn [] (repo-handler/refresh-repos!)) 200))}))
-                     (let [graph-name (-> (js/prompt "Please specify a new directory name to download the graph:")
-                                          str
-                                          string/trim)]
-                       (when-not (string/blank? graph-name)
-                         (state/pub-event! [:graph/pull-down-remote-graph graph graph-name]))))))
-               (p/catch (fn [^js e]
-                          (notification/show! (str e) :error)
-                          (js/console.error e)))))))))
-    (when (:GraphName graph)
-      (shui/dialog-open!
-       (file-sync/pick-dest-to-sync-panel graph)))))
-
-(defmethod events/handle :graph/pick-page-histories [[_ graph-uuid page-name]]
-  (shui/dialog-open!
-   (file-sync/pick-page-histories-panel graph-uuid page-name)
-   {:id :page-histories :label "modal-page-histories"}))
-
-(defmethod events/handle :file-sync/maybe-onboarding-show [[_ type]]
-  (when-not util/web-platform?
-    (file-sync/maybe-onboarding-show type)))
-
-(defmethod events/handle :file-sync/storage-exceed-limit [[_]]
-  (notification/show! "file sync storage exceed limit" :warning false)
-  (events/file-sync-stop!))
-
-(defmethod events/handle :file-sync/graph-count-exceed-limit [[_]]
-  (notification/show! "file sync graph count exceed limit" :warning false)
-  (events/file-sync-stop!))
-
-(defmethod events/handle :graph/dir-gone [[_ dir]]
-  (state/pub-event! [:notification/show
-                     {:content (str "The directory " dir " has been renamed or deleted, the editor will be disabled for this graph, you can unlink the graph.")
-                      :status :error
-                      :clear? false}])
-  (state/update-state! :file/unlinked-dirs (fn [dirs] (conj dirs dir))))
-
-(defmethod events/handle :graph/dir-back [[_ repo dir]]
-  (when (contains? (:file/unlinked-dirs @state/state) dir)
-    (notification/clear-all!)
-    (state/pub-event! [:notification/show
-                       {:content (str "The directory " dir " has been back, you can edit your graph now.")
-                        :status :success
-                        :clear? true}])
-    (state/update-state! :file/unlinked-dirs (fn [dirs] (disj dirs dir)))
-    (when (= dir (config/get-repo-dir repo))
-      (fs/watch-dir! dir))))
-
-(defmethod events/handle :ui/notify-skipped-downloading-files [[_ paths]]
-  (notification/show!
-   [:div
-    [:div.mb-4
-     [:div.font-semibold.mb-4.text-xl "It seems that some of your filenames are in the outdated format."]
-     [:p
-      "The files below that have reserved characters can't be saved on this device."]
-     [:div.overflow-y-auto.max-h-96
-      [:ol.my-2
-       (for [path paths]
-         [:li path])]]
-
-     [:div
-      [:p
-       "Check " [:a {:href "https://docs.logseq.com/#/page/logseq%20file%20and%20folder%20naming%20rules"
-                     :target "_blank"}
-                 "Logseq file and folder naming rules"]
-       " for more details."]]]]
-   :warning
-   false))
-
-(defmethod events/handle :graph/setup-a-repo [[_ opts]]
-  (let [opts' (merge {:picked-root-fn #(state/close-modal!)
-                      :native-icloud? (not (string/blank? (state/get-icloud-container-root-url)))
-                      :logged?        (user-handler/logged-in?)} opts)]
-    (if (mobile-util/native-ios?)
-      (shui/dialog-open!
-       #(graph-picker/graph-picker-cp opts')
-       {:label "graph-setup"})
-      (page-handler/ls-dir-files! st/refresh! opts'))))
-
-(defmethod events/handle :file/alter [[_ repo path content]]
-  (p/let [_ (file-handler/alter-file repo path content {:from-disk? true})]
-    (ui-handler/re-render-root!)))
-
-(rum/defcs file-id-conflict-item <
-  (rum/local false ::resolved?)
-  [state repo file data]
-  (let [resolved? (::resolved? state)
-        id (last (:assertion data))]
-    [:li {:key file}
-     [:div
-      [:a {:on-click #(js/window.apis.openPath file)} file]
-      (if @resolved?
-        [:div.flex.flex-row.items-center
-         (ui/icon "circle-check" {:style {:font-size 20}})
-         [:div.ml-1 "Resolved"]]
-        [:div
-         [:p
-          (str "It seems that another whiteboard file already has the ID \"" id
-               "\". You can fix it by changing the ID in this file with another UUID.")]
-         [:p
-          "Or, let me"
-          (ui/button "Fix"
-                     :on-click (fn []
-                                 (let [dir (config/get-repo-dir repo)]
-                                   (p/let [content (fs/read-file dir file)]
-                                     (let [new-content (string/replace content (str id) (str (random-uuid)))]
-                                       (p/let [_ (fs/write-plain-text-file! repo
-                                                                            dir
-                                                                            file
-                                                                            new-content
-                                                                            {})]
-                                         (reset! resolved? true))))))
-                     :class "inline mx-1")
-          "it."]])]]))
-
-(defmethod events/handle :file/parse-and-load-error [[_ repo parse-errors]]
-  (state/pub-event! [:notification/show
-                     {:content
-                      [:div
-                       [:h2.title "Oops. These files failed to import to your graph:"]
-                       [:ol.my-2
-                        (for [[file error] parse-errors]
-                          (let [data (ex-data error)]
-                            (cond
-                              (and (common-config/whiteboard? file)
-                                   (= :transact/upsert (:error data))
-                                   (uuid? (last (:assertion data))))
-                              (rum/with-key (file-id-conflict-item repo file data) file)
-
-                              :else
-                              (do
-                                (state/pub-event! [:capture-error {:error error
-                                                                   :payload {:type :file/parse-and-load-error}}])
-                                [:li.my-1 {:key file}
-                                 [:a {:on-click #(js/window.apis.openPath file)} file]
-                                 [:p (.-message error)]]))))]
-                       [:p "Don't forget to re-index your graph when all the conflicts are resolved."]]
-                      :status :error}]))
-
-(defmethod events/handle :file-sync-graph/restore-file [[_ graph page-entity content]]
-  (when (db/get-db graph)
-    (let [file (:block/file page-entity)]
-      (when-let [path (:file/path file)]
-        (when (and (not= content (:file/content file))
-                   (:file/content file))
-          (sync/add-new-version-file graph path (:file/content file)))
-        (p/let [_ (file-handler/alter-file graph
-                                           path
-                                           content
-                                           {:re-render-root? true
-                                            :skip-compare? true})]
-          (state/close-modal!)
-          (route-handler/redirect! {:to :page
-                                    :path-params {:name (:block/name page-entity)}}))))))
-
-(defmethod events/handle :sync/create-remote-graph [[_ current-repo]]
-  (let [graph-name (js/decodeURI (util/node-path.basename current-repo))]
-    (async/go
-      (async/<! (sync/<sync-stop))
-      (state/set-state! [:ui/loading? :graph/create-remote?] true)
-      (when-let [GraphUUID (get (async/<! (file-sync-handler/create-graph graph-name)) 2)]
-        (async/<! (sync/<sync-start))
-        (state/set-state! [:ui/loading? :graph/create-remote?] false)
-        ;; update existing repo
-        (state/set-repos! (map (fn [r]
-                                 (if (= (:url r) current-repo)
-                                   (assoc r
-                                          :GraphUUID GraphUUID
-                                          :GraphName graph-name
-                                          :remote? true)
-                                   r))
-                               (state/get-repos)))))))
-
-(defmethod events/handle :journal/insert-template [[_ page-name]]
-  (let [page-name (util/page-name-sanity-lc page-name)]
-    (when-let [page (db/get-page page-name)]
-      (p/do!
-       (db-async/<get-block (state/get-current-repo) (:db/id page))
-       (when (db/page-empty? (state/get-current-repo) page-name)
-         (when-let [template (state/get-default-journal-template)]
-           (editor-handler/insert-template!
-            nil
-            template
-            {:target page})))))))
-
-(defmethod events/handle :graph/backup-file [[_ repo file-path db-content]]
-  (p/let [disk-content (fs/read-file "" file-path)]
-    (fs/backup-db-file! repo file-path db-content disk-content)))
-
-(defmethod events/handle :graph/notify-existing-file [[_ data]]
-  (let [{:keys [current-file-path file-path]} data
-        error (t :file/validate-existing-file-error current-file-path file-path)]
-    (state/pub-event! [:notification/show
-                       {:content error
-                        :status :error
-                        :clear? false}])))

+ 0 - 290
src/main/frontend/handler/file_based/native_fs.cljs

@@ -1,290 +0,0 @@
-(ns frontend.handler.file-based.native-fs
-  "Native fs including Electron and mobile"
-  (:require ["/frontend/utils" :as utils]
-            [clojure.set :as set]
-            [clojure.string :as string]
-            [frontend.config :as config]
-            [frontend.db :as db]
-            [frontend.fs :as fs]
-            [frontend.handler.common :as common-handler]
-            [frontend.handler.file-based.repo :as file-repo-handler]
-            [frontend.handler.global-config :as global-config-handler]
-            [frontend.handler.repo :as repo-handler]
-            [frontend.handler.route :as route-handler]
-            [frontend.idb :as idb]
-            [frontend.mobile.util :as mobile-util]
-            [frontend.persist-db :as persist-db]
-            [frontend.state :as state]
-            [frontend.util :as util]
-            [frontend.util.fs :as util-fs]
-            [goog.object :as gobj]
-            [lambdaisland.glogi :as log]
-            [logseq.common.util :as common-util]
-            [promesa.core :as p]))
-
-(defn remove-ignore-files
-  [files dir-name nfs?]
-  (let [files (remove (fn [f]
-                        (let [path (:file/path f)]
-                          (or (string/starts-with? path ".git/")
-                              (string/includes? path ".git/")
-                              (and (util-fs/ignored-path? (if nfs? "" dir-name) path)
-                                   (not= (:file/name f) ".gitignore")))))
-                      files)]
-    (if-let [ignore-file (some #(when (= (:file/name %) ".gitignore")
-                                  %) files)]
-      (if-let [file (:file/file ignore-file)]
-        (p/let [content (.text file)]
-          (when content
-            (let [paths (set (common-handler/ignore-files content (map :file/path files)))]
-              (when (seq paths)
-                (filter (fn [f] (contains? paths (:file/path f))) files)))))
-        (p/resolved files))
-      (p/resolved files))))
-
-(defn- ->db-files
-  [result nfs?]
-  (->>
-   (cond
-     (or (mobile-util/native-platform?)
-         (util/electron?)
-         nfs?)
-     (map (fn [{:keys [path content stat]}]
-            {:file/path             (common-util/path-normalize path)
-             :file/content          content
-             :stat                  stat})
-          result)
-
-     :else ;; NFS backend
-     result)
-   (sort-by :file/path)))
-
-(defn- filter-markup-and-built-in-files
-  [files]
-  (filter (fn [file]
-            (contains? (set/union config/markup-formats #{:css :edn})
-                       (keyword (util/get-file-ext (:file/path file)))))
-          files))
-
-(defn- precheck-graph-dir
-  "Check graph dir, notify user if:
-
-   - Graph dir contains a nested graph, which should be avoided
-   - Over 10000 files found in graph dir, which might cause performance issues"
-  [_dir files]
-  ;; disable this check for now
-  (when (some #(string/ends-with? (:path %) "/logseq/config.edn") files)
-    (state/pub-event!
-     [:notification/show {:content "It seems that you are trying to open a Logseq graph folder with nested graph. Please unlink this graph and choose a correct folder."
-                          :status :warning
-                          :clear? false}]))
-  (when (>= (count files) 10000)
-    (state/pub-event!
-     [:notification/show {:content "It seems that you are trying to open a Logseq graph folder that contains an excessive number of files, This might lead to performance issues."
-                          :status :warning
-                          :clear? true}])))
-
-;; TODO: extract code for `ls-dir-files` and `reload-dir!`
-(defn ls-dir-files-with-handler!
-  "Read files from directory and setup repo (for the first time setup a repo)"
-  ([ok-handler] (ls-dir-files-with-handler! ok-handler nil))
-  ([ok-handler {:keys [on-open-dir dir-result-fn picked-root-fn dir]}]
-   (let [electron? (util/electron?)
-         mobile-native? (mobile-util/native-platform?)
-         nfs? (and (not electron?)
-                   (not mobile-native?))
-         *repo (atom nil)]
-     ;; TODO: add ext filter to avoid loading .git or other ignored file handlers
-     (->
-      (p/let [result (if (fn? dir-result-fn)
-                       (dir-result-fn)
-                       (fs/open-dir dir))
-              _ (when (fn? on-open-dir)
-                  (on-open-dir result))
-              root-dir (:path result)
-              ;; calling when root picked
-              _ (when (fn? picked-root-fn) (picked-root-fn root-dir))
-              repo (str config/local-db-prefix root-dir)]
-        (state/set-loading-files! repo true)
-        (when-not (state/home?)
-          (route-handler/redirect-to-home! false))
-        (reset! *repo repo)
-        (when-not (string/blank? root-dir)
-          (p/let [files (:files result)
-                  _ (precheck-graph-dir root-dir (:files result))
-                  files (-> (->db-files files nfs?)
-                            ;; filter again, in case fs backend does not handle this
-                            (remove-ignore-files root-dir nfs?))
-                  markup-files (filter-markup-and-built-in-files files)]
-            (-> files
-                (p/then (fn [result]
-                          ;; handle graphs txid
-                          (p/let [files (mapv #(dissoc % :file/file) result)
-                                  graphs-txid-meta (util-fs/read-graphs-txid-info root-dir)
-                                  graph-uuid (and (vector? graphs-txid-meta) (second graphs-txid-meta))]
-                            (if-let [exists-graph (state/get-sync-graph-by-id graph-uuid)]
-                              (state/pub-event!
-                               [:notification/show
-                                {:content (str "This graph already exists in \"" (:root exists-graph) "\"")
-                                 :status :warning}])
-                              (p/do! (persist-db/<new repo {})
-                                     (repo-handler/start-repo-db-if-not-exists! repo)
-                                     (when (config/global-config-enabled?)
-                                       (global-config-handler/restore-global-config!))
-                                     (file-repo-handler/load-new-repo-to-db! repo
-                                                                             {:new-graph?   true
-                                                                              :empty-graph? (nil? (seq markup-files))
-                                                                              :file-objs    files})
-                                     (state/set-parsing-state! {:graph-loading? false})
-                                     (state/add-repo! {:url repo :nfs? true})
-                                     (persist-db/<export-db repo {})
-                                     (state/set-loading-files! repo false)
-                                     (when ok-handler (ok-handler {:url repo})))))))
-                (p/catch (fn [error]
-                           (log/error :fs/load-files-error repo)
-                           (log/error :exception error)))))))
-      (p/catch (fn [error]
-                 (log/error :exception error)
-                 (when mobile-native?
-                   (state/pub-event!
-                    [:notification/show {:content (str error) :status :error}]))
-                 (when (contains? #{"AbortError" "Error"} (gobj/get error "name"))
-                   (when @*repo (state/set-loading-files! @*repo false))
-                   (throw error))))
-      (p/finally
-        (fn []
-          (state/set-loading-files! @*repo false)))))))
-
-(defn ls-dir-files-with-path!
-  ([path] (ls-dir-files-with-path! path nil))
-  ([path opts]
-   (when-let [dir-result-fn
-              (and path (fn []
-                          (p/let [files-result (fs/open-dir path)]
-                            files-result)))]
-     (ls-dir-files-with-handler!
-      (:ok-handler opts)
-      (merge {:dir-result-fn dir-result-fn} opts)))))
-
-(defn- compute-diffs
-  [old-files new-files]
-  (let [ks [:file/path :file/last-modified-at :file/content]
-        ->set (fn [files ks]
-                (when (seq files)
-                  (->> files
-                       (map #(select-keys % ks))
-                       set)))
-        old-files (->set old-files ks)
-        new-files (->set new-files ks)
-        file-path-set-f (fn [col] (set (map :file/path col)))
-        get-file-f (fn [files path] (some #(when (= (:file/path %) path) %) files))
-        old-file-paths (file-path-set-f old-files)
-        new-file-paths (file-path-set-f new-files)
-        added (set/difference new-file-paths old-file-paths)
-        deleted (set/difference old-file-paths new-file-paths)
-        modified (->> (set/intersection new-file-paths old-file-paths)
-                      (filter (fn [path]
-                                (not= (:file/content (get-file-f old-files path))
-                                      (:file/content (get-file-f new-files path)))))
-                      (set))]
-    (prn ::compute-diffs :added (count added) :modified (count modified) :deleted (count deleted))
-    {:added    added
-     :modified modified
-     :deleted  deleted}))
-
-(defn- handle-diffs!
-  "Compute directory diffs and (re)load repo"
-  [repo nfs? old-files new-files re-index? ok-handler]
-  (let [get-last-modified-at (fn [path] (some (fn [file]
-                                                (when (= path (:file/path file))
-                                                  (:file/last-modified-at file)))
-                                              new-files))
-        get-file-f (fn [path files] (some #(when (= (:file/path %) path) %) files))
-        {:keys [added modified deleted]} (compute-diffs old-files new-files)
-        ;; Use the same labels as isomorphic-git
-        rename-f (fn [typ col] (mapv (fn [file] {:type typ :path file :last-modified-at (get-last-modified-at file)}) col))
-        added-or-modified (set (concat added modified))]
-    (-> (p/all (map (fn [path]
-                      (when-let [file (get-file-f path new-files)]
-                        (p/let [content (if nfs?
-                                          (.text (:file/file file))
-                                          (:file/content file))]
-                          (assoc file :file/content content)))) added-or-modified))
-        (p/then (fn [result]
-                  (let [files (map #(dissoc % :file/file) result)
-                        [modified-files modified] (if re-index?
-                                                    [files (set modified)]
-                                                    (let [modified-files (filter (fn [file] (contains? added-or-modified (:file/path file))) files)]
-                                                      [modified-files (set modified)]))
-                        diffs (concat
-                               (rename-f "remove" deleted)
-                               (rename-f "add" added)
-                               (rename-f "modify" modified))]
-                    (when (or (and (seq diffs) (seq modified-files))
-                              (seq diffs))
-                      (-> (file-repo-handler/load-repo-to-db! repo
-                                                              {:diffs     diffs
-                                                               :nfs-files modified-files
-                                                               :refresh? (not re-index?)
-                                                               :new-graph? re-index?})
-                          (p/then (fn [_state]
-                                    (ok-handler)))
-                          (p/catch (fn [error]
-                                     (js/console.error "load-repo-to-db" error)))))
-
-                    (when (and (util/electron?) (not re-index?))
-                      (db/transact! repo new-files))))))))
-
-(defn- reload-dir!
-  "Handle refresh and re-index"
-  [repo {:keys [re-index? ok-handler]
-         :or {re-index? false}}]
-  (when (and repo (config/local-file-based-graph? repo))
-    (let [old-files (db/get-files-full repo)
-          repo-dir (config/get-local-dir repo)
-          handle-path (str "handle/" repo-dir)
-          electron? (util/electron?)
-          mobile-native? (mobile-util/native-platform?)
-          nfs? (and (not electron?)
-                    (not mobile-native?))]
-      (when re-index?
-        (state/set-graph-syncing? true))
-      (->
-       (p/let [handle (when-not electron? (idb/get-item handle-path))]
-         (when (or handle electron? mobile-native?)
-           (p/let [local-files-result (fs/get-files repo-dir)
-                   _ (when (config/global-config-enabled?)
-                       ;; reload global config into state
-                       (global-config-handler/restore-global-config!))
-                   new-files (-> (->db-files (:files local-files-result) nfs?)
-                                 (remove-ignore-files repo-dir nfs?))]
-             (handle-diffs! repo nfs? old-files new-files re-index? ok-handler))))
-       (p/catch (fn [error]
-                  (log/error :fs/load-files-error repo)
-                  (log/error :exception error)))
-       (p/finally (fn [_]
-                    (state/set-graph-syncing? false)))))))
-
-(defn rebuild-index!
-  [repo ok-handler]
-  (let [graph-dir (config/get-repo-dir repo)]
-    (when repo
-      (p/do!
-       (repo-handler/remove-repo! {:url repo} :switch-graph? false)
-       (ls-dir-files-with-path! graph-dir {:re-index? true})
-       (when (fn? ok-handler) (ok-handler))))))
-
-;; TODO: move to frontend.handler.repo
-(defn refresh!
-  [repo ok-handler]
-  (let [ok-handler (fn []
-                     (ok-handler)
-                     (state/set-nfs-refreshing! false))]
-    (when (and repo
-               (not (state/unlinked-dir? (config/get-repo-dir repo))))
-      (state/set-nfs-refreshing! true)
-      (reload-dir! repo {:ok-handler ok-handler}))))
-
-(defn supported?
-  []
-  (or (utils/nfsSupported) (util/electron?)))

+ 0 - 267
src/main/frontend/handler/file_sync.cljs

@@ -1,267 +0,0 @@
-(ns frontend.handler.file-sync
-  "Provides util handler fns for file sync"
-  (:require ["path" :as node-path]
-            [cljs-time.coerce :as tc]
-            [cljs-time.core :as t]
-            [cljs-time.format :as tf]
-            [cljs.core.async :as async :refer [go <!]]
-            [cljs.core.async.interop :refer [p->c]]
-            [clojure.string :as string]
-            [frontend.config :as config]
-            [frontend.db :as db]
-            [frontend.fs :as fs]
-            [frontend.fs.sync :as sync]
-            [frontend.handler.notification :as notification]
-            [frontend.handler.user :as user]
-            [frontend.pubsub :as pubsub]
-            [frontend.state :as state]
-            [frontend.storage :as storage]
-            [frontend.util :as util]
-            [lambdaisland.glogi :as log]
-            [logseq.common.path :as path]))
-
-(def *beta-unavailable? (volatile! false))
-
-(def refresh-file-sync-component (atom false))
-
-(defn get-current-graph-uuid []
-  (state/get-current-file-sync-graph-uuid))
-
-(defn enable-sync?
-  []
-  (or (state/enable-sync?)
-      config/dev?))
-
-(defn current-graph-sync-on?
-  []
-  (when-let [sync-state (state/sub-file-sync-state (state/get-current-file-sync-graph-uuid))]
-    (not (sync/sync-state--stopped? sync-state))))
-
-(defn synced-file-graph?
-  [graph]
-  (some (fn [item] (and (= graph (:url item))
-                        (:GraphUUID item))) (state/get-repos)))
-
-(defn create-graph
-  [name]
-  (go
-    (let [r* (<! (sync/<create-graph sync/remoteapi name))
-          user-uuid-or-exp (<! (user/<user-uuid))
-          r (if (instance? ExceptionInfo r*) r*
-                (if (instance? ExceptionInfo user-uuid-or-exp)
-                  user-uuid-or-exp
-                  (:GraphUUID r*)))]
-      (when-not (instance? ExceptionInfo user-uuid-or-exp)
-        (if (and (not (instance? ExceptionInfo r))
-                 (string? r))
-          (let [tx-info [0 r user-uuid-or-exp (state/get-current-repo)]]
-            (<! (apply sync/<update-graphs-txid! tx-info))
-            (swap! refresh-file-sync-component not)
-            tx-info)
-          (do
-            (state/set-state! [:ui/loading? :graph/create-remote?] false)
-            (cond
-              ;; already processed this exception by events
-              ;; - :file-sync/storage-exceed-limit
-              ;; - :file-sync/graph-count-exceed-limit
-              (or (sync/storage-exceed-limit? r)
-                  (sync/graph-count-exceed-limit? r))
-              nil
-
-              (contains? #{400 404} (get-in (ex-data r) [:err :status]))
-              (notification/show! (str "Create graph failed: already existed graph: " name) :warning true nil 4000 nil)
-
-              :else
-              (notification/show! (str "Create graph failed: " (ex-message r)) :warning true nil 4000 nil))))))))
-
-(defn <delete-graph
-  [graph-uuid]
-  (go
-    (let [same-graph? (= graph-uuid (get-current-graph-uuid))]
-      (when same-graph?
-        (<! (sync/<sync-stop)))
-      (let [r (<! (sync/<delete-graph sync/remoteapi graph-uuid))]
-        (if (instance? ExceptionInfo r)
-          (notification/show! (str "Delete graph failed: " graph-uuid) :warning)
-          (do
-            (when same-graph?
-              (sync/clear-graphs-txid! graph-uuid)
-              (swap! refresh-file-sync-component not))
-            (notification/show! (str "Graph deleted") :success)))))))
-
-(defn <list-graphs
-  []
-  (go
-    (let [r (<! (sync/<list-remote-graphs sync/remoteapi))]
-      (if (instance? ExceptionInfo r)
-        r
-        (:Graphs r)))))
-
-(defn load-session-graphs
-  []
-  (when-not (state/sub [:file-sync/remote-graphs :loading])
-    (go
-      (when-not (or util/web-platform? (util/mobile?))
-        (state/set-state! [:file-sync/remote-graphs :loading] true)
-        (let [graphs-or-exp (<! (<list-graphs))]
-          (when-not (instance? ExceptionInfo graphs-or-exp)
-            (state/set-state! :file-sync/remote-graphs {:loading false :graphs graphs-or-exp})))))))
-
-(defn reset-session-graphs
-  []
-  (state/set-state! :file-sync/remote-graphs {:loading false :graphs nil}))
-
-(defn init-graph [graph-uuid]
-  (go
-    (let [repo (state/get-current-repo)
-          user-uuid-or-exp (<! (user/<user-uuid))]
-      (if (instance? ExceptionInfo user-uuid-or-exp)
-        (notification/show! (ex-message user-uuid-or-exp) :error)
-        (do
-          (state/set-state! :sync-graph/init? true)
-          (<! (sync/<update-graphs-txid! 0 graph-uuid user-uuid-or-exp repo))
-          (swap! refresh-file-sync-component not)
-          (state/pub-event! [:graph/switch repo {:persist? false}]))))))
-
-(defn download-version-file
-  ([graph-uuid file-uuid version-uuid]
-   (download-version-file graph-uuid file-uuid version-uuid false))
-  ([graph-uuid file-uuid version-uuid silent-download?]
-   (go
-     (let [key (node-path/join file-uuid version-uuid)
-           r   (<! (sync/<download-version-files
-                    sync/rsapi graph-uuid (config/get-repo-dir (state/get-current-repo)) [key]))]
-       (if (instance? ExceptionInfo r)
-         (notification/show! (ex-cause r) :error)
-         (when-not silent-download?
-           (notification/show! [:div
-                                [:div "Downloaded version file at: "]
-                                [:div key]] :success false)))
-       (when-not (instance? ExceptionInfo r)
-         (path/path-join "logseq" "version-files" key))))))
-
-(defn- <list-file-local-versions
-  [page]
-  (go
-    (when-let [path (-> page :block/file :file/path)]
-      (let [base-path          (config/get-repo-dir (state/get-current-repo))
-            rel-path           (string/replace-first path base-path "")
-            file-stem          (path/file-stem rel-path)
-            version-files-dir  (path/path-join base-path "logseq/version-files/local" (path/dirname rel-path) file-stem)
-            version-file-paths (<! (p->c (fs/readdir version-files-dir :path-only? true)))]
-        (when-not (instance? ExceptionInfo version-file-paths)
-          (when (seq version-file-paths)
-            (->>
-             (mapv
-              (fn [path]
-                (try
-                  (let [create-time
-                        (-> (node-path/parse path)
-                            (js->clj :keywordize-keys true)
-                            :name
-                            (#(tf/parse (tf/formatter "yyyy-MM-dd'T'HH_mm_ss.SSSZZ") %)))]
-                    {:create-time   create-time
-                     :path          path
-                     :relative-path (path/relative-path base-path path)})
-                  (catch :default e
-                    (log/error :page-history/parse-format-error e)
-                    nil)))
-              version-file-paths)
-             (remove nil?))))))))
-
-(defn <fetch-page-file-versions [graph-uuid page]
-  []
-  (let [file-id (:db/id (:block/file page))]
-    (go
-      (when-let [path (:file/path (db/entity file-id))]
-        (let [version-list       (:VersionList
-                                  (<! (sync/<get-remote-file-versions sync/remoteapi graph-uuid path)))
-              local-version-list (<! (<list-file-local-versions page))
-              all-version-list   (->> (concat version-list local-version-list)
-                                      (sort-by #(or (:CreateTime %)
-                                                    (:create-time %))
-                                               >))]
-          all-version-list)))))
-
-(defn init-remote-graph
-  [local-graph-dir graph]
-  (when (and local-graph-dir graph)
-    (notification/show!
-     (str "Start syncing the remote graph "
-          (:GraphName graph)
-          " to "
-          (config/get-string-repo-dir local-graph-dir))
-     :success)
-    (init-graph (:GraphUUID graph))
-    (state/close-modal!)))
-
-(defn setup-file-sync-event-listeners
-  []
-  (let [c     (async/chan 1)
-        p     pubsub/sync-events-pub
-        topics [:finished-local->remote :finished-remote->local :start]]
-    (doseq [topic topics]
-      (async/sub p topic c))
-
-    (async/go-loop []
-      (let [{:keys [event data]} (async/<! c)]
-        (case event
-          (list :finished-local->remote :finished-remote->local)
-          (when-let [current-uuid (state/get-current-file-sync-graph-uuid)]
-            (state/clear-file-sync-progress! current-uuid)
-            (state/set-state! [:file-sync/graph-state current-uuid :file-sync/last-synced-at] (:epoch data))
-            (when (= event :finished-local->remote)
-              (async/offer! sync/finished-local->remote-chan true)))
-
-          :start
-          (when-let [current-uuid (state/get-current-file-sync-graph-uuid)]
-            (state/set-state! [:file-sync/graph-state current-uuid :file-sync/start-time] data))
-
-          nil)
-
-        (when (and (:file-change-events data)
-                   (= :page (state/get-current-route)))
-          (state/pub-event! [:file-sync/maybe-onboarding-show :sync-history])))
-      (recur))
-
-    #(doseq [topic topics]
-       (async/unsub p topic c))))
-
-(defn reset-user-state! []
-  (vreset! *beta-unavailable? false)
-  (state/set-state! :file-sync/onboarding-state nil))
-
-(defn calculate-time-left
-  "This assumes that the network speed is stable which could be wrong sometimes."
-  [sync-state progressing]
-  (when-let [start-time (get-in @state/state
-                                [:file-sync/graph-state
-                                 (state/get-current-file-sync-graph-uuid)
-                                 :file-sync/start-time
-                                 :epoch])]
-    (let [now (tc/to-epoch (t/now))
-          diff-seconds (- now start-time)
-          finished (reduce + (map (comp :progress second) progressing))
-          local->remote-files (:full-local->remote-files sync-state)
-          remote->local-files (:full-remote->local-files sync-state)
-          total (if (seq remote->local-files)
-                  (reduce + (map (fn [m] (or (:size m) 0)) remote->local-files))
-                  (reduce + (map #(:size (.-stat %)) local->remote-files)))
-          mins (int (/ (* (/ total finished) diff-seconds) 60))]
-      (if (or (zero? total) (zero? finished))
-        "waiting"
-        (cond
-          (zero? mins) "soon"
-          (= mins 1) "1 min left"
-          (> mins 30) "calculating..."
-          :else (str mins " mins left"))))))
-
-(defn set-sync-enabled!
-  [value]
-  (storage/set :logseq-sync-enabled value)
-  (state/set-state! :feature/enable-sync? value))
-
-(defn set-sync-diff-merge-enabled!
-  [value]
-  (storage/set :logseq-sync-diff-merge-enabled value)
-  (state/set-state! :feature/enable-sync-diff-merge? value))

+ 0 - 13
src/main/frontend/handler/page.cljs

@@ -16,10 +16,8 @@
             [frontend.handler.db-based.page :as db-page-handler]
             [frontend.handler.db-based.property :as db-property-handler]
             [frontend.handler.editor :as editor-handler]
-            [frontend.handler.file-based.native-fs :as nfs-handler]
             [frontend.handler.file-based.page :as file-page-handler]
             [frontend.handler.file-based.page-property :as file-page-property]
-            [frontend.handler.graph :as graph-handler]
             [frontend.handler.notification :as notification]
             [frontend.handler.plugin :as plugin-handler]
             [frontend.handler.property :as property-handler]
@@ -163,17 +161,6 @@
         (let [templates (map string/lower-case templates)]
           (contains? (set templates) (string/lower-case title)))))))
 
-(defn ls-dir-files!
-  ([ok-handler] (ls-dir-files! ok-handler nil))
-  ([ok-handler opts]
-   (nfs-handler/ls-dir-files-with-handler!
-    (fn [e]
-      (init-commands!)
-      (when ok-handler
-        (ok-handler e))
-      (graph-handler/settle-metadata-to-local! {:created-at (js/Date.now)}))
-    opts)))
-
 (defn file-based-save-filter!
   [page filter-state]
   (property-handler/add-page-property! page :filters filter-state))

+ 111 - 1
src/main/frontend/handler/user.cljs

@@ -4,7 +4,7 @@
   (:require [cljs-http.client :as http]
             [cljs-time.coerce :as tc]
             [cljs-time.core :as t]
-            [cljs.core.async :as async :refer [<! go]]
+            [cljs.core.async :as async :refer [<! go timeout]]
             [clojure.set :as set]
             [clojure.string :as string]
             [frontend.common.missionary :as c.m]
@@ -347,6 +347,116 @@
           (when-not (http/unexceptional-status? status)
             (throw (ex-info "failed to upload avatar" {:resp resp}))))))))
 
+(defn- guard-ex
+  [x]
+  (when (instance? ExceptionInfo x) x))
+
+(defn- get-json-body [body]
+  (or (and (not (string? body)) body)
+      (when (string/blank? body) nil)
+      (try (js->clj (js/JSON.parse body) :keywordize-keys true)
+           (catch :default e
+             (prn :invalid-json body)
+             e))))
+
+(defn- get-resp-json-body [resp]
+  (-> resp (:body) (get-json-body)))
+
+(defn- <request-once [api-name body token]
+  (go
+    (let [resp (http/post (str "https://" config/API-DOMAIN "/file-sync/" api-name)
+                          {:oauth-token token
+                           :body (js/JSON.stringify (clj->js body))
+                           :with-credentials? false})]
+      {:resp (<! resp)
+       :api-name api-name
+       :body body})))
+
+(defn- <request*
+  "max retry count is 5.
+  *stop: volatile var, stop retry-request when it's true,
+          and return :stop"
+  ([api-name body token] (<request* api-name body token 0))
+  ([api-name body token retry-count]
+   (go
+     (let [resp (<! (<request-once api-name body token))]
+       (if (and
+            (= 401 (get-in resp [:resp :status]))
+            (= "Unauthorized" (:message (get-json-body (get-in resp [:resp :body])))))
+         (if (> retry-count 5)
+           (throw (js/Error. :file-sync-request))
+           (do (println "will retry after" (min 60000 (* 1000 retry-count)) "ms")
+               (<! (timeout (min 60000 (* 1000 retry-count))))
+               (<! (<request* api-name body token (inc retry-count)))))
+         (:resp resp))))))
+
+(defn <request [api-name & args]
+  (go (apply <request* api-name args)))
+
+(defn storage-exceed-limit?
+  [exp]
+  (some->> (ex-data exp)
+           :err
+           ((juxt :status (comp :message :body)))
+           ((fn [[status msg]] (and (= 403 status) (= msg "storage-limit"))))))
+
+(defn graph-count-exceed-limit?
+  [exp]
+  (some->> (ex-data exp)
+           :err
+           ((juxt :status (comp :message :body)))
+           ((fn [[status msg]] (and (= 403 status) (= msg "graph-count-exceed-limit"))))))
+
+(defn- fire-file-sync-storage-exceed-limit-event!
+  [exp]
+  (when (storage-exceed-limit? exp)
+    (state/pub-event! [:rtc/storage-exceed-limit])
+    true))
+
+(defn- fire-file-sync-graph-count-exceed-limit-event!
+  [exp]
+  (when (graph-count-exceed-limit? exp)
+    (state/pub-event! [:rtc/graph-count-exceed-limit])
+    true))
+
+(defprotocol IToken
+  (<get-token [this]))
+
+(deftype RemoteAPI [*stopped?]
+  Object
+
+  (<request [this api-name body]
+    (go
+      (let [token-or-exp (<! (<get-token this))]
+        (or (guard-ex token-or-exp)
+            (let [resp (<! (<request api-name body token-or-exp *stopped?))]
+              (if (http/unexceptional-status? (:status resp))
+                (get-resp-json-body resp)
+                (let [exp (ex-info "request failed"
+                                   {:err          resp
+                                    :body         (:body resp)
+                                    :api-name     api-name
+                                    :request-body body})]
+                  (fire-file-sync-storage-exceed-limit-event! exp)
+                  (fire-file-sync-graph-count-exceed-limit-event! exp)
+                  exp)))))))
+
+  IToken
+  (<get-token [_this]
+    (frontend.handler.user/<wrap-ensure-id&access-token
+     (state/get-auth-id-token))))
+
+(defprotocol IRemoteAPI
+  (<user-info [this] "user info"))
+
+(extend-type RemoteAPI
+  IRemoteAPI
+  (<user-info [this]
+    (frontend.handler.user/<wrap-ensure-id&access-token
+     (<! (.<request this "user_info" {})))))
+
+(def remoteapi (->RemoteAPI nil))
+
 (comment
   ;; We probably need this for some new features later
   (defonce feature-matrix {:file-sync :beta})

+ 0 - 158
src/main/frontend/mobile/graph_picker.cljs

@@ -1,158 +0,0 @@
-(ns frontend.mobile.graph-picker
-  (:require
-   [clojure.string :as string]
-   [frontend.components.svg :as svg]
-   [frontend.fs :as fs]
-   [frontend.handler.file-based.native-fs :as nfs-handler]
-   [frontend.handler.notification :as notification]
-   [frontend.handler.page :as page-handler]
-   [frontend.mobile.util :as mobile-util]
-   [frontend.modules.shortcut.core :as shortcut]
-   [frontend.state :as state]
-   [frontend.ui :as ui]
-   [frontend.util :as util]
-   [logseq.common.path :as path]
-   [logseq.shui.hooks :as hooks]
-   [logseq.shui.ui :as shui]
-   [promesa.core :as p]
-   [rum.core :as rum]))
-
-(defn validate-graph-dirname
-  [root dirname]
-  (path/path-join root dirname))
-
-(rum/defc toggle-item
-  [{:keys [on? title on-toggle]}]
-  (ui/button
-   [:span.flex.items-center.justify-between.w-full.py-1
-    [:strong title]
-    (ui/toggle on? (fn []) true)]
-   :class (str "toggle-item " (when on? "is-on"))
-   :intent "logseq"
-   :on-pointer-down #(util/stop %)
-   :on-click #(when (fn? on-toggle)
-                (on-toggle (not on?)))))
-
-(rum/defc ^:large-vars/cleanup-todo graph-picker-cp
-  [{:keys [onboarding-and-home? logged? native-icloud?] :as opts}]
-  (let [can-logseq-sync? (and logged? (state/enable-sync?))
-        [step set-step!] (rum/use-state :init)
-        [sync-mode set-sync-mode!] (rum/use-state
-                                    (cond
-                                      can-logseq-sync? :logseq-sync
-                                      native-icloud? :icloud-sync))
-        icloud-sync-on?  (= sync-mode :icloud-sync)
-        logseq-sync-on?  (= sync-mode :logseq-sync)
-        *input-ref       (rum/create-ref)
-        native-ios?      (mobile-util/native-ios?)
-        open-picker      #(page-handler/ls-dir-files! shortcut/refresh! opts)
-        on-create        (fn [input-val]
-                           (let [graph-name (util/safe-sanitize-file-name input-val)]
-                             (if (string/blank? graph-name)
-                               (notification/show! "Illegal graph folder name.")
-
-                               ;; create graph directory under Logseq document folder (local/icloud)
-                               (when-let [root (if icloud-sync-on?
-                                                 (state/get-icloud-container-root-url)
-                                                 (state/get-local-container-root-url))]
-                                 (let [graph-path (validate-graph-dirname root graph-name)]
-                                   (-> (fs/mkdir-if-not-exists graph-path)
-                                       ;; iCloud folder creation is slow, so we need to wait for it
-                                       (p/then (fn []
-                                                 (if icloud-sync-on?
-                                                   (js/Promise. (fn [resolve _reject]
-                                                                  (js/setTimeout (fn [] (resolve)) 1000)))
-                                                   (p/resolved nil))))
-                                       (p/then
-                                        (fn []
-                                          (nfs-handler/ls-dir-files-with-path!
-                                           graph-path (merge
-                                                       {:ok-handler
-                                                        (fn []
-                                                          (when logseq-sync-on?
-                                                            (state/pub-event! [:sync/create-remote-graph (state/get-current-repo)])))}
-                                                       opts))
-                                          (notification/show! (str "Create graph: " graph-name) :success)))
-                                       (p/catch (fn [^js e]
-                                                  (notification/show! (str e) :error)
-                                                  (js/console.error e)))))))))]
-
-    (hooks/use-effect!
-     (fn []
-       (when-let [^js input (and onboarding-and-home?
-                                 (rum/deref *input-ref))]
-         (let [handle (fn [] (js/setTimeout
-                              #(.scrollIntoView
-                                input #js {:behavior "smooth", :block "center", :inline "nearest"}) 100))]
-           (.addEventListener input "focus" handle)
-           (handle))))
-     [step])
-
-    [:div.cp__graph-picker.w-full
-     {:class (when onboarding-and-home? (util/hiccup->class "px-10.py-10"))}
-
-     (when-not onboarding-and-home?
-       [:h1.flex.items-center
-        [:span.scale-75 (svg/logo)]
-        [:span.pl-1 "Set up a graph"]])
-
-     (case step
-       ;; step 0
-       :init
-       [:div.flex.flex-col.w-full.space-y-6
-        (ui/button
-         [:span.flex.items-center.justify-between.w-full.py-1
-          [:strong "Create a new graph"]
-          (ui/icon "chevron-right")]
-
-         :on-click #(if (and native-ios?
-                             (some (fn [s] (not (string/blank? s)))
-                                   (vals (:mobile/container-urls @state/state))))
-                      (set-step! :new-graph)
-                      (open-picker)))
-
-        (ui/button
-         [:span.flex.items-center.justify-between.w-full.py-1
-          [:strong "Select an existing graph"]
-          (ui/icon "folder-plus")]
-
-         :intent "logseq"
-         :on-click (fn []
-                     (shui/dialog-close!)
-                     (page-handler/ls-dir-files! shortcut/refresh!
-                                                 {:dir (when native-ios?
-                                                         (or
-                                                          (state/get-icloud-container-root-url)
-                                                          (state/get-local-container-root-url)))})))]
-
-       ;; step 1
-       :new-graph
-       [:div.flex.flex-col.w-full.space-y-3.faster.fade-in
-        [:input.form-input.block
-         {:auto-focus  true
-          :ref         *input-ref
-          :placeholder "What's the graph name?"}]
-
-        [:div.flex.flex-col
-         (when can-logseq-sync?
-           (toggle-item {:title     "Logseq sync"
-                         :on?       logseq-sync-on?
-                         :on-toggle #(set-sync-mode! (if % :logseq-sync (if native-icloud? :icloud-sync nil)))}))
-
-         (when (and native-icloud? (not logseq-sync-on?))
-           (toggle-item {:title     "iCloud sync"
-                         :on?       icloud-sync-on?
-                         :on-toggle #(set-sync-mode! (if % :icloud-sync nil))}))]
-
-        [:div.flex.justify-between.items-center.pt-2
-         (ui/button [:span.flex.items-center
-                     (ui/icon "chevron-left" {:size 18}) "Back"]
-                    :intent "logseq"
-                    :on-click #(set-step! :init))
-
-         (ui/button "Create"
-                    :on-click
-                    #(let [val (util/trim-safe (.-value (rum/deref *input-ref)))]
-                       (if (string/blank? val)
-                         (.focus (rum/deref *input-ref))
-                         (on-create val))))]])]))

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

@@ -2169,44 +2169,6 @@ Similar to re-frame subscriptions"
 (defn get-auth-refresh-token []
   (:auth/refresh-token @state))
 
-(defn set-file-sync-manager [graph-uuid v]
-  (when (and graph-uuid v)
-    (set-state! [:file-sync/graph-state graph-uuid :file-sync/sync-manager] v)))
-
-(defn get-file-sync-manager [graph-uuid]
-  (get-in @state [:file-sync/graph-state graph-uuid :file-sync/sync-manager]))
-
-(defn clear-file-sync-state! [graph-uuid]
-  (set-state! [:file-sync/graph-state graph-uuid] nil))
-
-(defn clear-file-sync-progress! [graph-uuid]
-  (set-state! [:file-sync/graph-state
-               graph-uuid
-               :file-sync/progress]
-              nil))
-
-(defn set-file-sync-state [graph-uuid v]
-  (when v (s/assert :frontend.fs.sync/sync-state v))
-  (set-state! [:file-sync/graph-state graph-uuid :file-sync/sync-state] v))
-
-(defn get-current-file-sync-graph-uuid
-  []
-  (get-in @state [:file-sync/graph-state :current-graph-uuid]))
-
-(defn sub-current-file-sync-graph-uuid
-  []
-  (sub [:file-sync/graph-state :current-graph-uuid]))
-
-(defn get-file-sync-state
-  ([]
-   (get-file-sync-state (get-current-file-sync-graph-uuid)))
-  ([graph-uuid]
-   (get-in @state [:file-sync/graph-state graph-uuid :file-sync/sync-state])))
-
-(defn sub-file-sync-state
-  [graph-uuid]
-  (sub [:file-sync/graph-state graph-uuid :file-sync/sync-state]))
-
 (defn reset-parsing-state!
   []
   (set-state! [:graph/parsing-state (get-current-repo)] {}))

+ 0 - 398
src/test/frontend/fs/diff_merge_test.cljs

@@ -1,398 +0,0 @@
-(ns frontend.fs.diff-merge-test
-  (:require [cljs-bean.core :as bean]
-            [cljs.test :refer [are deftest is]]
-            [frontend.db.conn :as conn]
-            [frontend.fs.diff-merge :as fs-diff]
-            [logseq.graph-parser :as graph-parser]
-            [logseq.graph-parser.db :as gp-db]
-            [logseq.graph-parser.mldoc :as gp-mldoc]
-            [logseq.graph-parser.text :as text]))
-
-(defn test-db->diff-blocks
-  "A hijacked version of db->diff-blocks for testing.
-   It overwrites the internal db getter with the test db connection."
-  [conn & args]
-  (with-redefs [conn/get-db (constantly @conn)]
-    (apply fs-diff/db->diff-blocks args)))
-
-(defn org-text->diffblocks
-  [text]
-  (-> (gp-mldoc/->edn text (gp-mldoc/default-config :org))
-      (fs-diff/ast->diff-blocks text :org {:block-pattern "-"})))
-
-(deftest org->ast->diff-blocks-test
-  (are [text diff-blocks]
-       (= (org-text->diffblocks text)
-          diff-blocks)
-    ":PROPERTIES:
-:ID:       72289d9a-eb2f-427b-ad97-b605a4b8c59b
-:END:
-#+tItLe: Well parsed!"
-    [{:body ":PROPERTIES:\n:ID:       72289d9a-eb2f-427b-ad97-b605a4b8c59b\n:END:\n#+tItLe: Well parsed!"
-      :uuid "72289d9a-eb2f-427b-ad97-b605a4b8c59b"
-      :level 1}]
-
-    "#+title: Howdy"
-    [{:body "#+title: Howdy" :uuid nil :level 1}]
-
-    ":PROPERTIES:
-:fiction: [[aldsjfklsda]]
-:END:\n#+title: Howdy"
-    [{:body ":PROPERTIES:\n:fiction: [[aldsjfklsda]]\n:END:\n#+title: Howdy"
-      :uuid nil
-      :level 1}]))
-
-(deftest db<->ast-diff-blocks-test
-  (let [conn (gp-db/start-conn)
-        text                                    ":PROPERTIES:
-:ID:       72289d9a-eb2f-427b-ad97-b605a4b8c59b
-:END:
-#+tItLe: Well parsed!"]
-    (graph-parser/parse-file conn "foo.org" text {})
-    (is (= (test-db->diff-blocks conn "Well parsed!")
-           (org-text->diffblocks text)))))
-
-(defn text->diffblocks
-  [text]
-  (-> (gp-mldoc/->edn text (gp-mldoc/default-config :markdown))
-      (fs-diff/ast->diff-blocks text :markdown {:block-pattern "-"})))
-
-(deftest md->ast->diff-blocks-test
-  (are [text diff-blocks]
-       (= (text->diffblocks text)
-          diff-blocks)
-    "- a
-\t- b
-\t\t- c"
-    [{:body "a" :uuid nil :level 1}
-     {:body "b" :uuid nil :level 2}
-     {:body "c" :uuid nil :level 3}]
-
-    "- a
-\t- b
-\t\t- c
-\t\t  multiline
-- d"
-    [{:body "a" :uuid nil :level 1}
-     {:body "b" :uuid nil :level 2}
-     {:body "c\nmultiline" :uuid nil :level 3}
-     {:body "d" :uuid nil :level 1}]
-
-    "## hello
-\t- world
-\t\t- nice
-\t\t\t- nice
-\t\t\t- bingo
-\t\t\t- world"
-    [{:body "## hello" :uuid nil :level 1}
-     {:body "world" :uuid nil :level 2}
-     {:body "nice" :uuid nil :level 3}
-     {:body "nice" :uuid nil :level 4}
-     {:body "bingo" :uuid nil :level 4}
-     {:body "world" :uuid nil :level 4}]
-
-    "# a
-## b
-### c
-#### d
-### e
-- f
-\t- g
-\t\t- h
-\t- i
-- j"
-    [{:body "# a" :uuid nil :level 1}
-     {:body "## b" :uuid nil :level 1}
-     {:body "### c" :uuid nil :level 1}
-     {:body "#### d" :uuid nil :level 1}
-     {:body "### e" :uuid nil :level 1}
-     {:body "f" :uuid nil :level 1}
-     {:body "g" :uuid nil :level 2}
-     {:body "h" :uuid nil :level 3}
-     {:body "i" :uuid nil :level 2}
-     {:body "j" :uuid nil :level 1}]
-
-    "- a\n  id:: 63e25526-3612-4fb1-8cf9-f66db1254a58
-\t- b
-\t\t- c"
-    [{:body "a\nid:: 63e25526-3612-4fb1-8cf9-f66db1254a58"
-      :uuid "63e25526-3612-4fb1-8cf9-f66db1254a58" :level 1}
-     {:body "b" :uuid nil :level 2}
-     {:body "c" :uuid nil :level 3}]
-
-    "alias:: ⭐️\nicon:: ⭐️"
-    [{:body "alias:: ⭐️\nicon:: ⭐️", :level 1, :uuid nil}]))
-
-(defn text->diffblocks-alt
-  [text]
-  (-> (gp-mldoc/->edn text (gp-mldoc/default-config :markdown))
-      (#'fs-diff/ast->diff-blocks-alt text :markdown {:block-pattern "-"})))
-
-(deftest md->ast->diff-blocks-alt-test
-  (are [text diff-blocks]
-       (= (text->diffblocks-alt text)
-          diff-blocks)
-    "- a
-\t- b
-\t\t- c"
-    [{:body "a" :uuid nil :level 1 :meta {:raw-body "- a"}}
-     {:body "b" :uuid nil :level 2 :meta {:raw-body "\t- b"}}
-     {:body "c" :uuid nil :level 3 :meta {:raw-body "\t\t- c"}}]
-
-    "- a
-\t- b
-\t\t- c
-\t\t  multiline
-- d"
-    [{:body "a" :uuid nil :level 1 :meta {:raw-body "- a"}}
-     {:body "b" :uuid nil :level 2 :meta {:raw-body "\t- b"}}
-     {:body "c\nmultiline" :uuid nil :level 3 :meta {:raw-body "\t\t- c\n\t\t  multiline"}}
-     {:body "d" :uuid nil :level 1 :meta {:raw-body "- d"}}]
-
-    "## hello
-\t- world
-\t\t- nice
-\t\t\t- nice
-\t\t\t- bingo
-\t\t\t- world"
-    [{:body "## hello" :uuid nil :level 1 :meta {:raw-body "## hello"}}
-     {:body "world" :uuid nil :level 2 :meta {:raw-body "\t- world"}}
-     {:body "nice" :uuid nil :level 3 :meta {:raw-body "\t\t- nice"}}
-     {:body "nice" :uuid nil :level 4 :meta {:raw-body "\t\t\t- nice"}}
-     {:body "bingo" :uuid nil :level 4 :meta {:raw-body "\t\t\t- bingo"}}
-     {:body "world" :uuid nil :level 4 :meta {:raw-body "\t\t\t- world"}}]
-
-    "# a
-## b
-### c
-#### d
-### e
-- f
-\t- g
-\t\t- h
-\t- i
-- j"
-    [{:body "# a" :uuid nil :level 1 :meta {:raw-body "# a"}}
-     {:body "## b" :uuid nil :level 1 :meta {:raw-body "## b"}}
-     {:body "### c" :uuid nil :level 1 :meta {:raw-body "### c"}}
-     {:body "#### d" :uuid nil :level 1 :meta {:raw-body "#### d"}}
-     {:body "### e" :uuid nil :level 1 :meta {:raw-body "### e"}}
-     {:body "f" :uuid nil :level 1 :meta {:raw-body "- f"}}
-     {:body "g" :uuid nil :level 2 :meta {:raw-body "\t- g"}}
-     {:body "h" :uuid nil :level 3 :meta {:raw-body "\t\t- h"}}
-     {:body "i" :uuid nil :level 2 :meta {:raw-body "\t- i"}}
-     {:body "j" :uuid nil :level 1 :meta {:raw-body "- j"}}]
-
-    "- a\n  id:: 63e25526-3612-4fb1-8cf9-f66db1254a58
-\t- b
-\t\t- c"
-    [{:body "a\nid:: 63e25526-3612-4fb1-8cf9-f66db1254a58"
-      :uuid "63e25526-3612-4fb1-8cf9-f66db1254a58" :level 1 :meta {:raw-body "- a\n  id:: 63e25526-3612-4fb1-8cf9-f66db1254a58"}}
-     {:body "b" :uuid nil :level 2 :meta {:raw-body "\t- b"}}
-     {:body "c" :uuid nil :level 3 :meta {:raw-body "\t\t- c"}}]
-
-    "alias:: ⭐️\nicon:: ⭐️"
-    [{:body "alias:: ⭐️\nicon:: ⭐️", :meta {:raw-body "alias:: ⭐️\nicon:: ⭐️"}, :level 1, :uuid nil}]))
-
-(deftest diff-test
-  (are [text1 text2 diffs]
-       (= (bean/->clj (fs-diff/diff (text->diffblocks text1)
-                                    (text->diffblocks text2)))
-          diffs)
-    "## hello
-\t- world
-\t\t- nice
-\t\t\t- nice
-\t\t\t- bingo
-\t\t\t- world"
-    "## Halooooo
-\t- world
-\t\t- nice
-\t\t\t- nice
-\t\t\t- bingo
-\t\t\t- world"
-    ;; Empty op, because no insertion op before the first base block required
-    ;; See https://github.com/logseq/diff-merge#usage
-    [[]
-     [[-1 {:body "## hello"
-           :level 1
-           :uuid nil}]
-      [1  {:body "## Halooooo"
-           :level 1
-           :uuid nil}]]
-     [[0 {:body "world"
-          :level 2
-          :uuid nil}]]
-     [[0 {:body "nice"
-          :level 3
-          :uuid nil}]]
-     [[0 {:body "nice"
-          :level 4
-          :uuid nil}]]
-     [[0 {:body "bingo"
-          :level 4
-          :uuid nil}]]
-     [[0 {:body "world"
-          :level 4
-          :uuid nil}]]]
-
-    "## hello
-\t- world
-\t  id:: 63e25526-3612-4fb1-8cf9-abcd12354abc
-\t\t- nice
-\t\t\t- nice
-\t\t\t- bingo
-\t\t\t- world"
-    "## Halooooo
-\t- world
-\t\t- nice
-\t\t\t- nice
-\t\t\t- bingo
-\t\t\t- world"
-;; Empty op, because no insertion op before the first base block required
-;; See https://github.com/logseq/diff-merge#usage
-    [[]
-     [[-1 {:body "## hello"
-           :level 1
-           :uuid nil}]
-      [1  {:body "## Halooooo"
-           :level 1
-           :uuid nil}]
-      [1 {:body "world"
-          :level 2
-          :uuid nil}]]
-     [[-1 {:body "world\nid:: 63e25526-3612-4fb1-8cf9-abcd12354abc"
-           :level 2
-           :uuid "63e25526-3612-4fb1-8cf9-abcd12354abc"}]]
-     [[0 {:body "nice"
-          :level 3
-          :uuid nil}]]
-     [[0 {:body "nice"
-          :level 4
-          :uuid nil}]]
-     [[0 {:body "bingo"
-          :level 4
-          :uuid nil}]]
-     [[0 {:body "world"
-          :level 4
-          :uuid nil}]]]
-
-    ""
-    "- abc def"
-    [[[1 {:body "abc def"
-          :level 1
-          :uuid nil}]]]))
-
-(deftest db->diffblocks
-  (let [conn (gp-db/start-conn)]
-    (graph-parser/parse-file conn
-                             "foo.md"
-                             (str "- abc
-  id:: 11451400-0000-0000-0000-000000000000\n"
-                                  "- def
-  id:: 63246324-6324-6324-6324-632463246324\n")
-                             {})
-    (graph-parser/parse-file conn
-                             "bar.md"
-                             (str "- ghi
-  id:: 11451411-1111-1111-1111-111111111111\n"
-                                  "\t- jkl
-\t  id:: 63241234-1234-1234-1234-123412341234\n")
-                             {})
-    (are [page-name diff-blocks] (= (test-db->diff-blocks conn page-name)
-                                    diff-blocks)
-      "foo"
-      [{:body "abc\nid:: 11451400-0000-0000-0000-000000000000" :uuid  "11451400-0000-0000-0000-000000000000" :level 1}
-       {:body "def\nid:: 63246324-6324-6324-6324-632463246324" :uuid  "63246324-6324-6324-6324-632463246324" :level 1}]
-
-      "bar"
-      [{:body "ghi\nid:: 11451411-1111-1111-1111-111111111111" :uuid  "11451411-1111-1111-1111-111111111111" :level 1}
-       {:body "jkl\nid:: 63241234-1234-1234-1234-123412341234" :uuid  "63241234-1234-1234-1234-123412341234" :level 2}])
-
-    (are [page-name text new-uuids] (= (let [old-blks (test-db->diff-blocks conn page-name)
-                                             new-blks (text->diffblocks text)
-                                             diff-ops (fs-diff/diff old-blks new-blks)]
-                                         (bean/->clj (fs-diff/attachUUID diff-ops (bean/->js (map :uuid old-blks)) "NEW_ID")))
-                                       new-uuids)
-      "foo"
-      "- abc
-- def"
-      ["11451400-0000-0000-0000-000000000000"
-       "NEW_ID"]
-
-      "bar"
-      "- ghi
-\t- jkl"
-      ["11451411-1111-1111-1111-111111111111"
-       "NEW_ID"]
-
-      "non exist page"
-      "- k\n\t- l"
-      ["NEW_ID" "NEW_ID"]
-
-      "another non exist page"
-      ":PROPERTIES:
-:ID:       72289d9a-eb2f-427b-ad97-b605a4b8c59b
-:END:
-#+tItLe: Well parsed!"
-      ["72289d9a-eb2f-427b-ad97-b605a4b8c59b"])))
-
-(deftest ast->diff-blocks-test
-  (are [ast text diff-blocks]
-       (= (fs-diff/ast->diff-blocks ast text :org {:block-pattern "-"})
-          diff-blocks)
-    [[["Properties" [["TiTlE" "Howdy" []]]] nil]]
-    "#+title: Howdy"
-    [{:body "#+title: Howdy", :level 1, :uuid nil}])
-
-  (are [ast text diff-blocks]
-       (= (fs-diff/ast->diff-blocks ast text :org {:block-pattern "-" :user-config {:property-pages/enabled? true}})
-          diff-blocks)
-    [[["Property_Drawer" [["foo" "#bar" [["Tag" [["Plain" "bar"]]]]] ["baz" "#bing" [["Tag" [["Plain" "bing"]]]]]]] {:start_pos 0, :end_pos 22}]]
-    "foo:: #bar\nbaz:: #bing"
-    [{:body "foo:: #bar\nbaz:: #bing", :level 1, :uuid nil}]))
-
-(deftest ast-empty-diff-test
-  (are [ast text diff-ops]
-       (= (bean/->clj (->> (fs-diff/ast->diff-blocks ast text :org {:block-pattern "-" :user-config {:property-pages/enabled? true}})
-                           (fs-diff/diff [])))
-          diff-ops)
-    [[["Property_Drawer" [["foo" "#bar" [["Tag" [["Plain" "bar"]]]]] ["baz" "#bing" [["Tag" [["Plain" "bing"]]]]]]] {:start_pos 0, :end_pos 22}]]
-    "foo:: #bar\nbaz:: #bing"
-    [[[1 {:body "foo:: #bar\nbaz:: #bing", :level 1, :uuid nil}]]]))
-
-(deftest test-remove-indentation-spaces
-  (is (= "" (gp-mldoc/remove-indentation-spaces "" 0 false)))
-  (is (= "" (gp-mldoc/remove-indentation-spaces "" 3 true)))
-
-  (is (= "- nice\n  happy" (gp-mldoc/remove-indentation-spaces "\t\t\t- nice\n\t\t\t  happy" 3 true)))
-  (is (= "\t\t\t- nice\n  happy" (gp-mldoc/remove-indentation-spaces "\t\t\t- nice\n\t\t\t  happy" 3 false)))
-  (is (= "\t\t\t- nice\n\t\t\t  happy" (gp-mldoc/remove-indentation-spaces "\t\t\t- nice\n\t\t\t  happy" 0 true))))
-
-(deftest test-remove-level-spaces
-  ;; Test when `format` is nil
-  (is (= "nice\n\t\t\t  good" (text/remove-level-spaces "\t\t\t- nice\n\t\t\t  good" :markdown "-")))
-  (is (= "- nice" (text/remove-level-spaces "\t\t\t- nice" :markdown "")))
-  (is (= "nice" (text/remove-level-spaces "\t\t\t- nice" :markdown "-"))))
-
-(deftest test-three-way-merge
-  (is (= (fs-diff/three-way-merge
-          "- a\n  id:: 648ab5e6-5e03-4c61-95d4-dd904a0a007f\n- b"
-          "- a\n  id:: 648ab5e6-5e03-4c61-95d4-dd904a0a007f\n  aaa:: 111\n- b"
-          "- c"
-          :markdown)
-         "- a\n  id:: 648ab5e6-5e03-4c61-95d4-dd904a0a007f\n  aaa:: 111\n- c"))
-
-  (is (= (fs-diff/three-way-merge
-          "- a\n- b\n- c"
-          "- a\n- b\n- c\n- d"
-          "- a\n\t- b\n- c"
-          :markdown)
-         "- a\n\t- b\n- c\n- d"))
-
-  (is (= (fs-diff/three-way-merge
-          "- a\n- b\n- c"
-          "- a\n\t- b\n- c"
-          "- a\n- b\n- c\n- d"
-          :markdown)
-         "- a\n- b\n- c\n- d")))

+ 0 - 45
src/test/frontend/fs/sync_test.cljs

@@ -1,45 +0,0 @@
-(ns frontend.fs.sync-test
-  (:require [frontend.fs.sync :as sync]
-            [clojure.test :refer [deftest are]]))
-
-(deftest ignored?
-  (are [x y] (= y (sync/ignored? x))
-    ".git" true
-    ".gitignore" true
-    ".DS_store" true
-    "foo/.DS_store" true
-    "logseq/graphs-txid.edn" true
-    "logseq/version-files/1.md" true
-    "logseq/bak/1.md" true
-    "node_modules/test" true
-    "foo/node_modules/" true
-    "backup~" true
-    "foo/backup~" true
-    "foo/.test.md" true
-    "pages/test.md" false
-    "journals/2022_01_01.md" false
-    ))
-
-
-(deftest diff-file-metadata-sets
-  (are [x y z] (= x (sync/diff-file-metadata-sets y z))
-    #{}
-    #{(sync/->FileMetadata 1 2 "3" 4 5 nil nil nil)}
-    #{(sync/->FileMetadata 1 2 "3" 4 5 nil nil nil)}
-
-    #{(sync/->FileMetadata 1 2 "3" 4 5 nil nil nil)}
-    #{(sync/->FileMetadata 1 2 "3" 4 5 nil nil nil)}
-    #{(sync/->FileMetadata 1 22 "3" 4 6 nil nil nil)}
-
-    #{(sync/->FileMetadata 1 2 "3" 4 5 nil nil nil)}
-    #{(sync/->FileMetadata 1 2 "3" 4 5 nil nil nil)}
-    #{(sync/->FileMetadata 1 22 "3" 4 4 nil nil nil) 
-      (sync/->FileMetadata 1 22 "3" 44 5 nil nil nil)}
-
-    #{}
-    #{(sync/->FileMetadata 1 2 "3" 4 5 nil nil nil)}
-    #{(sync/->FileMetadata 1 2 "3" 4 4 nil nil nil) 
-      (sync/->FileMetadata 1 2 "3" 4 6 nil nil nil)}
-
-    )
-  )