Ver código fonte

feat(chrome-native-fs): save block even in editing mode

Tienson Qin 4 anos atrás
pai
commit
2ba37cdd6c

+ 4 - 0
src/main/frontend/components/editor.cljs

@@ -698,6 +698,10 @@
                           current-pos (:pos (util/get-caret-pos (gdom/getElement id)))]
                       (state/set-edit-content! id value)
                       (state/set-edit-pos! current-pos)
+                      (when-let [repo (or (:block/repo block)
+                                          (state/get-current-repo))]
+                        (state/set-editor-last-input-time! repo (util/time-ms))
+                        (db/clear-repo-persistent-job! repo))
                       (let [input (gdom/getElement id)
                             native-e (gobj/get e "nativeEvent")
                             last-input-char (util/nth-safe value (dec current-pos))]

+ 6 - 6
src/main/frontend/components/reference.cljs

@@ -69,11 +69,11 @@
 
 (rum/defcs unlinked-references-aux
   < rum/reactive db-mixins/query
-    {:will-mount (fn [state]
-                   (let [[page-name n-ref] (:rum/args state)
-                         ref-blocks (db/get-page-unlinked-references page-name)]
-                     (reset! n-ref (count ref-blocks))
-                     (assoc state ::ref-blocks ref-blocks)))}
+  {:will-mount (fn [state]
+                 (let [[page-name n-ref] (:rum/args state)
+                       ref-blocks (db/get-page-unlinked-references page-name)]
+                   (reset! n-ref (count ref-blocks))
+                   (assoc state ::ref-blocks ref-blocks)))}
   [state page-name n-ref]
   (let [ref-blocks (::ref-blocks state)]
     [:div.references-blocks
@@ -100,5 +100,5 @@
             (if @n-ref
               (str @n-ref " Unlinked References")
               "Unlinked References")]
-            (fn [] (unlinked-references-aux page-name n-ref))
+           (fn [] (unlinked-references-aux page-name n-ref))
            true)]]))))

+ 85 - 82
src/main/frontend/components/repo.cljs

@@ -67,88 +67,91 @@
                 state)}
   []
   (when-let [repo (state/get-current-repo)]
-    (when-not (= repo config/local-repo)
-      (if (and
-           (nfs-handler/supported?)
-           (config/local-db? repo))
-        (let [syncing? (state/sub :graph/syncing?)]
-          [:div.ml-2.mr-1.opacity-70.hover:opacity-100 {:class (if syncing? "loader" "initial")}
-           [:a
-            {:on-click #(nfs-handler/refresh! repo)
-             :title (str "Sync files with the local directory: " (config/get-local-dir repo))}
-            svg/refresh]])
-        (let [changed-files (state/sub [:repo/changed-files repo])
-              should-push? (seq changed-files)
-              git-status (state/sub [:git/status repo])
-              pushing? (= :pushing git-status)
-              pulling? (= :pulling git-status)
-              push-failed? (= :push-failed git-status)
-              last-pulled-at (db/sub-key-value repo :git/last-pulled-at)
-              editing? (seq (state/sub :editor/editing?))]
-          [:div.flex-row.flex.items-center
-           (when pushing?
-             [:span.lds-dual-ring.mt-1])
-           (ui/dropdown
-            (fn [{:keys [toggle-fn]}]
-              [:div.cursor.w-2.h-2.sync-status.mr-2
-               {:class (cond
-                         push-failed?
-                         "bg-red-500"
-                         (or editing? should-push? pushing?)
-                         "bg-orange-400"
-                         :else
-                         "bg-green-600")
-                :style {:border-radius "50%"
-                        :margin-top 2}
-                :on-mouse-over
-                (fn [e]
-                  (toggle-fn)
-                  (js/setTimeout common-handler/check-changed-files-status 0))}])
-            (fn [{:keys [toggle-fn]}]
-              (rum/with-context [[t] i18n/*tongue-context*]
-                [:div.p-2.rounded-md.shadow-xs.bg-base-3.flex.flex-col.sync-content
-                 {:on-mouse-leave toggle-fn}
-                 [:div
-                  [:div
-                   (cond
-                     push-failed?
-                     [:p (t :git/push-failed)]
-                     (and should-push? (seq changed-files))
-                     [:div.changes
-                      [:ul
-                       (for [file changed-files]
-                         [:li {:key (str "sync-" file)}
-                          [:div.flex.flex-row.justify-between.align-items
-                           [:a {:href (rfe/href :file {:path file})}
-                            file]
-                           [:a.ml-4.text-sm.mt-1
-                            {:on-click (fn [e]
-                                         (export-handler/download-file! file))}
-                            [:span (t :download)]]]])]]
-                     :else
-                     [:p (t :git/local-changes-synced)])]
-                 ;; [:a.text-sm.font-bold {:href "/diff"} "Check diff"]
-                  [:div.flex.flex-row.justify-between.align-items.mt-2
-                   (ui/button (t :git/push)
-                              :on-click (fn [] (state/set-modal! commit/add-commit-message)))
-                   (if pushing?
-                     [:span.lds-dual-ring.mt-1])]]
-                 [:hr]
-                 [:div
-                  (when-not (string/blank? last-pulled-at)
-                    [:p {:style {:font-size 12}} (t :git/last-pull)
-                     (str ": " last-pulled-at)])
-                  [:div.flex.flex-row.justify-between.align-items
-                   (ui/button (t :git/pull)
-                              :on-click (fn [] (repo-handler/pull-current-repo)))
-                   (if pulling?
-                     [:span.lds-dual-ring.mt-1])]
-                  [:a.mt-5.text-sm.opacity-50.block
-                   {:on-click (fn []
-                                (export-handler/export-repo-as-zip! repo))}
-                   (t :repo/download-zip)]
-                  [:p.pt-2.text-sm.opacity-50
-                   (t :git/version) (str " " version/version)]]])))])))))
+    (let [nfs-repo? (config/local-db? repo)]
+      (when-not (= repo config/local-repo)
+        (if (and nfs-repo? (nfs-handler/supported?))
+          (let [syncing? (state/sub :graph/syncing?)]
+            [:div.ml-2.mr-1.opacity-70.hover:opacity-100 {:class (if syncing? "loader" "initial")}
+             [:a
+              {:on-click #(nfs-handler/refresh! repo)
+               :title (str "Sync files with the local directory: " (config/get-local-dir repo))}
+              svg/refresh]])
+          (let [changed-files (state/sub [:repo/changed-files repo])
+                should-push? (seq changed-files)
+                git-status (state/sub [:git/status repo])
+                pushing? (= :pushing git-status)
+                pulling? (= :pulling git-status)
+                push-failed? (= :push-failed git-status)
+                last-pulled-at (db/sub-key-value repo :git/last-pulled-at)
+                ;; db-persisted? (state/sub [:db/persisted? repo])
+                editing? (seq (state/sub :editor/editing?))]
+            [:div.flex-row.flex.items-center
+             (when pushing?
+               [:span.lds-dual-ring.mt-1])
+             (ui/dropdown
+              (fn [{:keys [toggle-fn]}]
+                [:div.cursor.w-2.h-2.sync-status.mr-2
+                 {:class (cond
+                           push-failed?
+                           "bg-red-500"
+                           (or
+                            ;; (not db-persisted?)
+                            editing?
+                            should-push? pushing?)
+                           "bg-orange-400"
+                           :else
+                           "bg-green-600")
+                  :style {:border-radius "50%"
+                          :margin-top 2}
+                  :on-mouse-over
+                  (fn [e]
+                    (toggle-fn)
+                    (js/setTimeout common-handler/check-changed-files-status 0))}])
+              (fn [{:keys [toggle-fn]}]
+                (rum/with-context [[t] i18n/*tongue-context*]
+                  [:div.p-2.rounded-md.shadow-xs.bg-base-3.flex.flex-col.sync-content
+                   {:on-mouse-leave toggle-fn}
+                   [:div
+                    [:div
+                     (cond
+                       push-failed?
+                       [:p (t :git/push-failed)]
+                       (and should-push? (seq changed-files))
+                       [:div.changes
+                        [:ul
+                         (for [file changed-files]
+                           [:li {:key (str "sync-" file)}
+                            [:div.flex.flex-row.justify-between.align-items
+                             [:a {:href (rfe/href :file {:path file})}
+                              file]
+                             [:a.ml-4.text-sm.mt-1
+                              {:on-click (fn [e]
+                                           (export-handler/download-file! file))}
+                              [:span (t :download)]]]])]]
+                       :else
+                       [:p (t :git/local-changes-synced)])]
+                   ;; [:a.text-sm.font-bold {:href "/diff"} "Check diff"]
+                    [:div.flex.flex-row.justify-between.align-items.mt-2
+                     (ui/button (t :git/push)
+                                :on-click (fn [] (state/set-modal! commit/add-commit-message)))
+                     (if pushing?
+                       [:span.lds-dual-ring.mt-1])]]
+                   [:hr]
+                   [:div
+                    (when-not (string/blank? last-pulled-at)
+                      [:p {:style {:font-size 12}} (t :git/last-pull)
+                       (str ": " last-pulled-at)])
+                    [:div.flex.flex-row.justify-between.align-items
+                     (ui/button (t :git/pull)
+                                :on-click (fn [] (repo-handler/pull-current-repo)))
+                     (if pulling?
+                       [:span.lds-dual-ring.mt-1])]
+                    [:a.mt-5.text-sm.opacity-50.block
+                     {:on-click (fn []
+                                  (export-handler/export-repo-as-zip! repo))}
+                     (t :repo/download-zip)]
+                    [:p.pt-2.text-sm.opacity-50
+                     (t :git/version) (str " " version/version)]]])))]))))))
 
 (rum/defc repos-dropdown < rum/reactive
   [head? on-click]

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

@@ -118,8 +118,6 @@
       [:div.cp__sidebar-main-content
        {:data-is-global-graph-pages global-graph-pages?
         :data-is-full-width (or global-graph-pages?
-                                (and (not logged?)
-                                     home?)
                                 (contains? #{:all-files :all-pages} route-name))}
        (cond
          (not indexeddb-support?)

+ 53 - 10
src/main/frontend/db.cljs

@@ -100,12 +100,14 @@
 (defn string->db [s]
   (dt/read-transit-str s))
 
-;; persisting DB between page reloads
-(defn persist [repo db files-db?]
-  (let [key (if files-db?
-              (datascript-files-db repo)
-              (datascript-db repo))]
-    (idb/set-item! key (db->string db))))
+;; persisting DBs between page reloads
+(defn persist! [repo]
+  (let [file-key (datascript-files-db repo)
+        non-file-key (datascript-db repo)
+        file-db (d/db (get-files-conn repo))
+        non-file-db (d/db (get-conn repo false))]
+    (p/let [_ (idb/set-item! file-key (db->string file-db))]
+      (idb/set-item! non-file-key (db->string non-file-db)))))
 
 (defn reset-conn! [conn db]
   (reset! conn db))
@@ -543,8 +545,7 @@
                         (remove nil?))]
        (when (seq tx-data)
          (when-let [conn (get-conn repo-url false)]
-           (let [tx-report (d/transact! conn (vec tx-data))]
-             (state/mark-repo-as-changed! repo-url (get-tx-id tx-report)))))))))
+           (d/transact! conn (vec tx-data))))))))
 
 (defn transact-files-db!
   ([tx-data]
@@ -588,7 +589,6 @@
                               (get-conn repo-url false)))]
         (when (and (seq tx-data) (get-conn))
           (let [tx-result (profile "Transact!" (d/transact! (get-conn) (vec tx-data)))
-                _ (state/mark-repo-as-changed! repo-url (get-tx-id tx-result))
                 db (:db-after tx-result)
                 handler-keys (get-handler-keys handler-opts)]
             (doseq [handler-key handler-keys]
@@ -1872,6 +1872,45 @@
       (state/set-config! repo-url config)
       config)))
 
+(defonce persistent-jobs (atom {}))
+
+(defn clear-repo-persistent-job!
+  [repo]
+  (when-let [old-job (get @persistent-jobs repo)]
+    (js/clearTimeout old-job)))
+
+(defn- persist-if-idle!
+  [repo]
+  (clear-repo-persistent-job! repo)
+  (let [job (js/setTimeout
+             (fn []
+               (if (and (state/input-idle? repo)
+                        (state/db-idle? repo))
+                 (do
+                   (persist! repo)
+                   ;; (state/set-db-persisted! repo true)
+)
+                 (let [job (get persistent-jobs repo)]
+                   (persist-if-idle! repo))))
+             5000)]
+    (swap! persistent-jobs assoc repo job)))
+
+;; only save when user's idle
+(defn- repo-listen-to-tx!
+  [repo conn files-db?]
+  (d/listen! conn :persistence
+             (fn [tx-report]
+               (state/set-last-transact-time! repo (util/time-ms))
+               ;; (state/set-db-persisted! repo false)
+               (persist-if-idle! repo))))
+
+(defn- listen-and-persist!
+  [repo]
+  (when-let [conn (get-files-conn repo)]
+    (repo-listen-to-tx! repo conn true))
+  (when-let [conn (get-conn repo false)]
+    (repo-listen-to-tx! repo conn false)))
+
 (defn start-db-conn!
   ([me repo]
    (start-db-conn! me repo {}))
@@ -1886,7 +1925,9 @@
                              db-type
                              (assoc :db/type db-type))])
      (when me
-       (d/transact! db-conn [(me-tx (d/db db-conn) me)])))))
+       (d/transact! db-conn [(me-tx (d/db db-conn) me)]))
+
+     (listen-and-persist! repo))))
 
 (defn restore!
   [{:keys [repos] :as me} restore-config-handler]
@@ -1894,6 +1935,7 @@
     (doall
      (for [{:keys [url]} repos]
        (let [repo url
+
              db-name (datascript-files-db repo)
              db-conn (d/create-conn db-schema/files-db-schema)]
          (swap! conns assoc db-name db-conn)
@@ -1917,6 +1959,7 @@
                        (reset-conn! db-conn attached-db))
                      (when logged?
                        (d/transact! db-conn [(me-tx (d/db db-conn) me)])))]
+           (listen-and-persist! repo)
            (restore-config-handler repo)))))))
 
 (defn- build-edges

+ 4 - 45
src/main/frontend/handler.cljs

@@ -13,6 +13,7 @@
             [frontend.handler.page :as page-handler]
             [frontend.handler.repo :as repo-handler]
             [frontend.handler.file :as file-handler]
+            [frontend.handler.editor :as editor-handler]
             [frontend.handler.ui :as ui-handler]
             [frontend.handler.export :as export-handler]
             [frontend.handler.web.nfs :as nfs]
@@ -112,54 +113,13 @@
                                (fn []
                                  (js/console.error "Failed to request GitHub app tokens."))))
 
-                            (nfs/trigger-check! watch-for-date!)))
+                            (watch-for-date!)))
                          (p/catch (fn [error]
                                     (log/error :db/restore-failed error))))))]
     ;; clear this interval
     (let [interval-id (js/setInterval inner-fn 50)]
       (reset! interval interval-id))))
 
-(defn persist-repo-to-indexeddb!
-  ([]
-   (persist-repo-to-indexeddb! false))
-  ([force?]
-   (let [status (state/get-repo-persist-status)]
-     (doseq [[repo {:keys [last-stored-at last-modified-at] :as repo-status}] status]
-       (when (and (> last-modified-at last-stored-at)
-                  (or force?
-                      (and (state/get-edit-input-id)
-                           (> (- (util/time-ms) last-stored-at) (* 5 60 1000)) ; 5 minutes
-)
-                      (nil? (state/get-edit-input-id))))
-         (p/let [_ (repo-handler/persist-repo! repo)]
-           (state/update-repo-last-stored-at! repo)))))))
-
-(defn periodically-persist-repo-to-indexeddb!
-  []
-  (js/setInterval persist-repo-to-indexeddb! (* 5 1000)))
-
-(defn set-save-before-unload! []
-  (.addEventListener js/window "beforeunload"
-                     (fn [e]
-                       (when (and (not config/dev?) (state/repos-need-to-be-stored?))
-                         (let [notification-id (atom nil)]
-                           (let [id (notification/show!
-                                     [:div
-                                      [:p "It seems that you have some unsaved changes!"]
-                                      (ui/button "Save"
-                                                 :on-click (fn [e]
-                                                             (persist-repo-to-indexeddb!)
-                                                             (notification/show!
-                                                              "Saved successfully!"
-                                                              :success)
-                                                             (and @notification-id (notification/clear! @notification-id))))]
-                                     :warning
-                                     false)]
-                             (reset! notification-id id)))
-                         (let [message "\\o/"]
-                           (set! (.-returnValue (or e js/window.event)) message)
-                           message)))))
-
 (defn- handle-connection-change
   [e]
   (let [online? (= (gobj/get e "type") "online")]
@@ -201,6 +161,5 @@
                       :example? true}])]
         (state/set-repos! repos)
         (restore-and-setup! me repos logged?)))
-    (periodically-persist-repo-to-indexeddb!)
-    (db/run-batch-txs!))
-  (set-save-before-unload!))
+    (db/run-batch-txs!)
+    (editor-handler/periodically-save!)))

+ 39 - 7
src/main/frontend/handler/editor.cljs

@@ -1320,17 +1320,38 @@
               nil)
             (state/conj-selection-block! element up?)))))))
 
+(defn save-block-aux!
+  [block value format]
+  (let [value (text/remove-level-spaces value format true)
+        new-value (block/with-levels value format block)
+        properties (with-timetracking-properties block value)]
+    ;; FIXME: somehow frontend.components.editor's will-unmount event will loop forever
+    ;; maybe we shouldn't save the block/file in "will-unmount" event?
+    (save-block-if-changed! block new-value
+                            {:custom-properties properties})))
+
 (defn save-block!
   [{:keys [format block id repo dummy?] :as state} value]
   (when (or (:db/id (db/entity repo [:block/uuid (:block/uuid block)]))
             dummy?)
-    (let [value (text/remove-level-spaces value format true)
-          new-value (block/with-levels value format block)
-          properties (with-timetracking-properties block value)]
-      ;; FIXME: somehow frontend.components.editor's will-unmount event will loop forever
-      ;; maybe we shouldn't save the block/file in "will-unmount" event?
-      (save-block-if-changed! block new-value
-                              {:custom-properties properties}))))
+    (save-block-aux! block value format)))
+
+(defn save-current-block-when-idle!
+  []
+  (when-let [repo (state/get-current-repo)]
+    (when (state/input-idle? repo)
+      (let [input-id (state/get-edit-input-id)
+            block (state/get-edit-block)
+            elem (and input-id (gdom/getElement input-id))
+            db-block (db/entity [:block/uuid (:block/uuid block)])
+            db-content (:block/content db-block)
+            db-content-without-heading (and db-content
+                                            (util/safe-subs db-content (:block/level db-block)))
+            value (and elem (gobj/get elem "value"))]
+        (when (and block value db-content-without-heading
+                   (not= (string/trim db-content-without-heading)
+                         (string/trim value)))
+          (save-block-aux! block value (:block/format block)))))))
 
 (defn on-up-down
   [state e up?]
@@ -1957,3 +1978,14 @@
           (state/set-editor-show-block-search! false)
           (state/set-editor-show-page-search! false)
           (state/set-editor-show-page-search-hashtag! false))))))
+
+(defn periodically-save!
+  []
+  (js/setInterval save-current-block-when-idle! 5000))
+
+(defn get-current-input-value
+  []
+  (let [edit-input-id (state/get-edit-input-id)
+        input (and edit-input-id (gdom/getElement edit-input-id))]
+    (when input
+      (gobj/get input "value"))))

+ 3 - 33
src/main/frontend/handler/repo.cljs

@@ -219,14 +219,6 @@
                                          (assoc options :re-render-opts {:clear-all-query-state? true}))
             (load-contents add-or-modify-files options)))))))
 
-(defn persist-repo!
-  [repo]
-  (spec/validate :repos/url repo)
-  (when-let [files-conn (db/get-files-conn repo)]
-    (db/persist repo @files-conn true))
-  (when-let [db (db/get-conn repo)]
-    (db/persist repo db false)))
-
 (defn load-db-and-journals!
   [repo-url diffs first-clone?]
   (spec/validate :repos/url repo-url)
@@ -251,24 +243,6 @@
   (when (seq files)
     (file-handler/alter-files repo files)))
 
-; FIXME: Unused
-(defn persist-repo-metadata!
-  [repo]
-  (spec/validate :repos/url repo)
-  (let [files (db/get-files repo)]
-    (when (seq files)
-      (let [data (db/get-sync-metadata repo)
-            data-str (pr-str data)]
-        (file-handler/alter-file repo
-                                 (str config/app-name "/" config/metadata-file)
-                                 data-str
-                                 {:reset? false})))))
-
-(defn periodically-persist-app-metadata
-  [repo-url]
-  (js/setInterval #(persist-repo-metadata! repo-url)
-                  (* 5 60 1000)))
-
 (declare push)
 
 (defn get-diff-result
@@ -373,7 +347,7 @@
   (let [status (db/get-key-value repo-url :git/status)]
     (if (and
          (db/cloned? repo-url)
-         (not (state/get-edit-input-id))
+         (state/input-idle? repo-url)
          (not= status :pushing))
       (-> (p/let [files (js/window.workerThread.getChangedFiles (util/get-repo-dir (state/get-current-repo)))]
             (when (or (seq files) merge-push-no-diff?)
@@ -543,9 +517,7 @@
    (p/let [_ (clone repo-url)
            _ (git-handler/git-set-username-email! repo-url (state/get-me))]
      (load-db-and-journals! repo-url nil true)
-     (periodically-pull-and-push repo-url {:pull-now? false})
-     ;; (periodically-persist-app-metadata repo-url)
-)
+     (periodically-pull-and-push repo-url {:pull-now? false}))
    (p/catch (fn [error]
               (js/console.error error)))))
 
@@ -562,9 +534,7 @@
                    (db/cloned? repo))
             (do
               (git-handler/git-set-username-email! repo me)
-              (periodically-pull-and-push repo {:pull-now? true})
-              ;; (periodically-persist-app-metadata repo)
-)
+              (periodically-pull-and-push repo {:pull-now? true}))
             (clone-and-pull repo)))))
     (js/setTimeout (fn []
                      (clone-and-pull-repos me))

+ 9 - 17
src/main/frontend/handler/web/nfs.cljs

@@ -119,23 +119,13 @@
     (when handle
       (utils/verifyPermission handle true))))
 
-(defn ask-permission
-  [repo cb]
-  (fn [close-fn]
-    [:div
-     [:p.text-gray-700
-      "Grant native filesystem permission for directory: "
-      [:b (config/get-local-dir repo)]]
-     (ui/button
-      "Grant"
-      :on-click (fn []
-                  (p/let [_ (check-directory-permission! repo)]
-                    (cb))
-                  (close-fn)))]))
-
 (defn trigger-check! [cb]
-  (when-let [repo (get-local-repo)]
-    (state/set-modal! (ask-permission repo cb))))
+  (let [repo (state/get-current-repo)
+        nfs-repo? (config/local-db? repo)]
+    (if nfs-repo?
+      (p/let [_ (check-directory-permission! repo)]
+        (cb))
+      (cb))))
 
 (defn- compute-diffs
   [old-files new-files]
@@ -226,7 +216,9 @@
 
 (defn- refresh!
   [repo]
-  (when repo (reload-dir! repo)))
+  (when repo
+    (p/let [_ (check-directory-permission! repo)]
+      (reload-dir! repo))))
 
 (defn supported?
   []

+ 36 - 24
src/main/frontend/state.cljs

@@ -28,11 +28,6 @@
     ;; TODO: how to detect the network reliably?
     :network/online? true
     :indexeddb/support? true
-    ;; TODO: save in local storage so that if :changed? is true when user
-    ;; reloads the browser, the app should re-index the repo (another way
-    ;; is to save all the tx data since :last-stored-at)
-    ;; repo -> {:last-stored-at :last-modified-at}
-    :repo/persist-status {}
     :me nil
     :git/current-repo (storage/get :git/current-repo)
     :git/status {}
@@ -78,6 +73,10 @@
     :editor/block nil
     :editor/block-dom-id nil
     :editor/set-timestamp-block nil
+    :editor/last-input-time nil
+    :db/last-transact-time {}
+    ;; whether database is persisted
+    :db/persisted? {}
     :cursor-range nil
 
     :selection/mode false
@@ -784,18 +783,6 @@
          :modal/show? false
          :modal/panel-content nil))
 
-(defn update-repo-last-stored-at!
-  [repo]
-  (swap! state assoc-in [:repo/persist-status repo :last-stored-at] (util/time-ms)))
-
-(defn get-repo-persist-status
-  []
-  (:repo/persist-status @state))
-
-(defn mark-repo-as-changed!
-  [repo _tx-id]
-  (swap! state assoc-in [:repo/persist-status repo :last-modified-at] (util/time-ms)))
-
 (defn get-db-batch-txs-chan
   []
   (:db/batch-txs @state))
@@ -807,13 +794,6 @@
     (when-let [chan (get-db-batch-txs-chan)]
       (async/put! chan f))))
 
-(defn repos-need-to-be-stored?
-  []
-  (let [status (vals (get-repo-persist-status))]
-    (some (fn [{:keys [last-stored-at last-modified-at]}]
-            (> last-modified-at last-stored-at))
-          status)))
-
 (defn get-left-sidebar-open?
   []
   (get-in @state [:ui/left-sidebar-open?]))
@@ -900,6 +880,38 @@
   [value]
   (set-state! :repo/importing-to-db? value))
 
+(defn set-editor-last-input-time!
+  [repo time]
+  (swap! state assoc-in [:editor/last-input-time repo] time))
+
+(defn set-last-transact-time!
+  [repo time]
+  (swap! state assoc-in [:db/last-transact-time repo] time)
+
+  ;; THINK: new block, indent/outdent, drag && drop, etc.
+  (set-editor-last-input-time! repo time))
+
+(defn set-db-persisted!
+  [repo value]
+  (swap! state assoc-in [:db/persisted? repo] value))
+
+(defn db-idle?
+  [repo]
+  (when repo
+    (when-let [last-time (get-in @state [:db/last-transact-time repo])]
+      (let [now (util/time-ms)]
+        (>= (- now last-time) 5000)))))
+
+(defn input-idle?
+  [repo]
+  (when repo
+    (or
+     (when-let [last-time (get-in @state [:editor/last-input-time repo])]
+       (let [now (util/time-ms)]
+         (>= (- now last-time) 5000)))
+     ;; not in editing mode
+     (not (get-edit-input-id)))))
+
 ;; TODO: Move those to the uni `state`
 
 (defonce editor-op (atom nil))