Просмотр исходного кода

enhance: page adds timestamps && all pages

Tienson Qin 4 лет назад
Родитель
Сommit
e2a047c1ac

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

@@ -8,6 +8,7 @@
             [promesa.core :as p]
             [electron.ipc :as ipc]
             [frontend.handler.notification :as notification]
+            [frontend.handler.metadata :as metadata-handler]
             [frontend.ui :as ui]))
 
 (defn listen-to-open-dir!
@@ -41,6 +42,8 @@
            (notification/show!
             (ui/loading "Logseq is saving the graphs to your local file system, please wait for several seconds.")
             :warning)
+           (doseq [repo repos]
+             (metadata-handler/set-pages-metadata! repo))
            (js/setTimeout
             (fn []
               (-> (p/all (map db/persist! repos))

+ 170 - 138
src/main/frontend/components/page.cljs

@@ -295,14 +295,14 @@
               [:div.flex.flex-row.space-between
                [:div.flex-1.flex-row
                 [:a.page-title {:on-click (fn [e]
-                                 (.preventDefault e)
-                                 (when (gobj/get e "shiftKey")
-                                   (when-let [page (db/pull repo '[*] [:block/name page-name])]
-                                     (state/sidebar-add-block!
-                                      repo
-                                      (:db/id page)
-                                      :page
-                                      {:page page}))))}
+                                            (.preventDefault e)
+                                            (when (gobj/get e "shiftKey")
+                                              (when-let [page (db/pull repo '[*] [:block/name page-name])]
+                                                (state/sidebar-add-block!
+                                                 repo
+                                                 (:db/id page)
+                                                 :page
+                                                 {:page page}))))}
                  [:h1.title {:style {:margin-left -2}}
                   (if page-original-name
                     (if (and (string/includes? page-original-name "[[")
@@ -316,66 +316,66 @@
                (when (not config/publishing?)
                  (let [contents? (= (string/lower-case (str page-name)) "contents")
                        links (fn [] (->>
-                                    [(when-not contents?
-                                       {:title   (t :page/add-to-favorites)
-                                        :options {:on-click (fn [] (page-handler/handle-add-page-to-contents! page-original-name))}})
-
-                                     {:title "Go to presentation mode"
-                                      :options {:on-click (fn []
-                                                            (state/sidebar-add-block!
-                                                             repo
-                                                             (:db/id page)
-                                                             :page-presentation
-                                                             {:page page}))}}
-                                     (when (and (not contents?)
-                                                (not journal?))
-                                       {:title   (t :page/rename)
-                                        :options {:on-click #(state/set-modal! (rename-page-dialog title page-name))}})
-
-                                     (when-let [file-path (and (util/electron?) (page-handler/get-page-file-path))]
-                                       [{:title   (t :page/open-in-finder)
-                                         :options {:on-click #(js/window.apis.showItemInFolder file-path)}}
-                                        {:title   (t :page/open-with-default-app)
-                                         :options {:on-click #(js/window.apis.openPath file-path)}}])
-
-                                     (when-not contents?
-                                       {:title   (t :page/delete)
-                                        :options {:on-click #(state/set-modal! (delete-page-dialog page-name))}})
-
-                                     (when (state/get-current-page)
-                                       {:title   (t :export)
-                                        :options {:on-click #(state/set-modal! export/export-page)}})
-
-                                     (when (util/electron?)
-                                       {:title   (t (if public? :page/make-private :page/make-public))
-                                        :options {:on-click
-                                                  (fn []
-                                                    (page-handler/update-public-attribute!
-                                                     page-name
-                                                     (if public? false true))
-                                                    (state/close-modal!))}})
-
-                                     (when plugin-handler/lsp-enabled?
-                                       (for [[_ {:keys [key label] :as cmd} action pid] (state/get-plugins-commands-with-type :page-menu-item)]
-                                         {:title label
-                                          :options {:on-click #(commands/exec-plugin-simple-command!
+                                     [(when-not contents?
+                                        {:title   (t :page/add-to-favorites)
+                                         :options {:on-click (fn [] (page-handler/handle-add-page-to-contents! page-original-name))}})
+
+                                      {:title "Go to presentation mode"
+                                       :options {:on-click (fn []
+                                                             (state/sidebar-add-block!
+                                                              repo
+                                                              (:db/id page)
+                                                              :page-presentation
+                                                              {:page page}))}}
+                                      (when (and (not contents?)
+                                                 (not journal?))
+                                        {:title   (t :page/rename)
+                                         :options {:on-click #(state/set-modal! (rename-page-dialog title page-name))}})
+
+                                      (when-let [file-path (and (util/electron?) (page-handler/get-page-file-path))]
+                                        [{:title   (t :page/open-in-finder)
+                                          :options {:on-click #(js/window.apis.showItemInFolder file-path)}}
+                                         {:title   (t :page/open-with-default-app)
+                                          :options {:on-click #(js/window.apis.openPath file-path)}}])
+
+                                      (when-not contents?
+                                        {:title   (t :page/delete)
+                                         :options {:on-click #(state/set-modal! (delete-page-dialog page-name))}})
+
+                                      (when (state/get-current-page)
+                                        {:title   (t :export)
+                                         :options {:on-click #(state/set-modal! export/export-page)}})
+
+                                      (when (util/electron?)
+                                        {:title   (t (if public? :page/make-private :page/make-public))
+                                         :options {:on-click
+                                                   (fn []
+                                                     (page-handler/update-public-attribute!
+                                                      page-name
+                                                      (if public? false true))
+                                                     (state/close-modal!))}})
+
+                                      (when plugin-handler/lsp-enabled?
+                                        (for [[_ {:keys [key 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 (state/get-current-page)) action)}}))
 
-                                     (when developer-mode?
-                                       {:title   "(Dev) Show page data"
-                                        :options {:on-click (fn []
-                                                              (let [page-data (with-out-str (pprint/pprint (db/pull (:db/id page))))]
-                                                                (println page-data)
-                                                                (notification/show!
-                                                                 [:div
-                                                                  [:pre.code page-data]
-                                                                  [:br]
-                                                                  (ui/button "Copy to clipboard"
-                                                                    :on-click #(.writeText js/navigator.clipboard page-data))]
-                                                                 :success
-                                                                 false)))}})]
-                                    (flatten)
-                                    (remove nil?)))]
+                                      (when developer-mode?
+                                        {:title   "(Dev) Show page data"
+                                         :options {:on-click (fn []
+                                                               (let [page-data (with-out-str (pprint/pprint (db/pull (:db/id page))))]
+                                                                 (println page-data)
+                                                                 (notification/show!
+                                                                  [:div
+                                                                   [:pre.code page-data]
+                                                                   [:br]
+                                                                   (ui/button "Copy to clipboard"
+                                                                     :on-click #(.writeText js/navigator.clipboard page-data))]
+                                                                  :success
+                                                                  false)))}})]
+                                     (flatten)
+                                     (remove nil?)))]
                    [:div.flex.flex-row
 
                     (when plugin-handler/lsp-enabled?
@@ -497,63 +497,63 @@
                   (util/format "%d page%s" c1 s1)
                   )]
                [:div.p-6
-               ;; [:div.flex.items-center.justify-between.mb-2
-               ;;  [:span "Layout"]
-               ;;  (ui/select
-               ;;    (mapv
-               ;;     (fn [item]
-               ;;       (if (= (:label item) layout)
-               ;;         (assoc item :selected "selected")
-               ;;         item))
-               ;;     [{:label "gForce"}
-               ;;      {:label "dagre"}])
-               ;;    (fn [value]
-               ;;      (set-setting! :layout value))
-               ;;    "graph-layout")]
-               [:div.flex.items-center.justify-between.mb-2
-                [:span "Journals"]
-                ;; FIXME: why it's not aligned well?
-                [:div.mt-1
-                 (ui/toggle journal?
-                            (fn []
-                              (let [value (not journal?)]
-                                (reset! *journal? value)
-                                (set-setting! :journal? value)))
-                            true)]]
-               [:div.flex.items-center.justify-between.mb-2
-                [:span "Orphan pages"]
-                [:div.mt-1
-                 (ui/toggle orphan-pages?
-                            (fn []
-                              (let [value (not orphan-pages?)]
-                                (reset! *orphan-pages? value)
-                                (set-setting! :orphan-pages? value)))
-                            true)]]
-               [:div.flex.items-center.justify-between.mb-2
-                [:span "Built-in pages"]
-                [:div.mt-1
-                 (ui/toggle builtin-pages?
-                            (fn []
-                              (let [value (not builtin-pages?)]
-                                (reset! *builtin-pages? value)
-                                (set-setting! :builtin-pages? value)))
-                            true)]]
-               (when (seq focus-nodes)
-                 [:div.flex.flex-col.mb-2
-                  [:p {:title "N hops from selected nodes"}
-                   "N hops from selected nodes"]
-                  (ui/tippy {:html [:div.pr-3 n-hops]}
-                            (ui/slider (or n-hops 10)
-                                       {:min 1
-                                        :max 10
-                                        :on-change #(reset! *n-hops (int %))}))])
-
-               [:a.opacity-70.opacity-100 {:on-click (fn []
-                                                       (swap! *graph-reset? not)
-                                                       (reset! *focus-nodes [])
-                                                       (reset! *n-hops nil)
-                                                       (state/clear-search-filters!))}
-                "Reset Graph"]]])))
+                ;; [:div.flex.items-center.justify-between.mb-2
+                ;;  [:span "Layout"]
+                ;;  (ui/select
+                ;;    (mapv
+                ;;     (fn [item]
+                ;;       (if (= (:label item) layout)
+                ;;         (assoc item :selected "selected")
+                ;;         item))
+                ;;     [{:label "gForce"}
+                ;;      {:label "dagre"}])
+                ;;    (fn [value]
+                ;;      (set-setting! :layout value))
+                ;;    "graph-layout")]
+                [:div.flex.items-center.justify-between.mb-2
+                 [:span "Journals"]
+                 ;; FIXME: why it's not aligned well?
+                 [:div.mt-1
+                  (ui/toggle journal?
+                             (fn []
+                               (let [value (not journal?)]
+                                 (reset! *journal? value)
+                                 (set-setting! :journal? value)))
+                             true)]]
+                [:div.flex.items-center.justify-between.mb-2
+                 [:span "Orphan pages"]
+                 [:div.mt-1
+                  (ui/toggle orphan-pages?
+                             (fn []
+                               (let [value (not orphan-pages?)]
+                                 (reset! *orphan-pages? value)
+                                 (set-setting! :orphan-pages? value)))
+                             true)]]
+                [:div.flex.items-center.justify-between.mb-2
+                 [:span "Built-in pages"]
+                 [:div.mt-1
+                  (ui/toggle builtin-pages?
+                             (fn []
+                               (let [value (not builtin-pages?)]
+                                 (reset! *builtin-pages? value)
+                                 (set-setting! :builtin-pages? value)))
+                             true)]]
+                (when (seq focus-nodes)
+                  [:div.flex.flex-col.mb-2
+                   [:p {:title "N hops from selected nodes"}
+                    "N hops from selected nodes"]
+                   (ui/tippy {:html [:div.pr-3 n-hops]}
+                             (ui/slider (or n-hops 10)
+                                        {:min 1
+                                         :max 10
+                                         :on-change #(reset! *n-hops (int %))}))])
+
+                [:a.opacity-70.opacity-100 {:on-click (fn []
+                                                        (swap! *graph-reset? not)
+                                                        (reset! *focus-nodes [])
+                                                        (reset! *n-hops nil)
+                                                        (state/clear-search-filters!))}
+                 "Reset Graph"]]])))
           (graph-filter-section
            [:span.font-medium "Search"]
            (fn [open?]
@@ -674,36 +674,68 @@
     (when (seq (:nodes graph))
       (page-graph-inner graph dark?))))
 
-(rum/defc all-pages < rum/reactive
+(defn- sort-pages-by
+  [by-item desc? pages]
+  (let [comp (if desc? > <)
+        by-item (if (= by-item :block/name)
+                  (fn [x] (string/lower-case (:block/name x)))
+                  by-item)]
+    (sort-by by-item comp pages)))
+
+(rum/defc sortable-title
+  [title key by-item desc?]
+  [:th
+   [:a {:on-click (fn []
+                    (reset! by-item key)
+                    (swap! desc? not))}
+    [:div.flex.items-center
+     [:span.mr-1 title]
+     (when (= @by-item key)
+       [:span
+        (if @desc? (svg/caret-down) (svg/caret-up))])]]])
+
+(rum/defcs all-pages < rum/reactive
+  (rum/local :block/updated-at ::sort-by-item)
+  (rum/local true ::desc?)
   ;; {:did-mount (fn [state]
   ;;               (let [current-repo (state/sub :git/current-repo)]
   ;;                 (js/setTimeout #(db/remove-orphaned-pages! current-repo) 0))
   ;;               state)}
-  []
-  (let [current-repo (state/sub :git/current-repo)]
+  [state]
+  (let [current-repo (state/sub :git/current-repo)
+        *sort-by-item (get state ::sort-by-item)
+        *desc? (get state ::desc?)]
     (rum/with-context [[t] i18n/*tongue-context*]
       [:div.flex-1
        [:h1.title (t :all-pages)]
        (when current-repo
-         (let [pages (page-handler/get-pages-with-modified-at current-repo)]
+         (let [pages (->> (page-handler/get-all-pages current-repo)
+                         (map (fn [page] (assoc page :block/backlinks (count (:block/_refs (db/entity (:db/id page)))))))
+                         (sort-pages-by @*sort-by-item @*desc?))]
            [:table.table-auto
             [:thead
              [:tr
-              [:th (t :block/name)]
-              [:th (t :file/last-modified-at)]]]
+              (sortable-title (t :block/name) :block/name *sort-by-item *desc?)
+              (sortable-title (t :page/backlinks) :block/backlinks  *sort-by-item *desc?)
+              (sortable-title (t :page/created-at) :block/created-at *sort-by-item *desc?)
+              (sortable-title (t :page/updated-at) :block/updated-at *sort-by-item *desc?)]]
             [:tbody
-             (for [page pages]
-               [:tr {:key page}
+             (for [{:block/keys [name created-at updated-at backlinks] :as page} pages]
+               [:tr {:key name}
                 [:td [:a {:on-click (fn [e]
-                                      (let [repo (state/get-current-repo)
-                                            page (db/pull repo '[*] [:block/name (string/lower-case page)])]
+                                      (let [repo (state/get-current-repo)]
                                         (when (gobj/get e "shiftKey")
                                           (state/sidebar-add-block!
                                            repo
                                            (:db/id page)
                                            :page
-                                           {:page page}))))
-                          :href (rfe/href :page {:name page})}
-                      page]]
-                [:td [:span.text-gray-500.text-sm
-                      (t :file/no-data)]]])]]))])))
+                                           {:page (:block/name page)}))))
+                          :href (rfe/href :page {:name (:block/name page)})}
+                      (block/page-cp {} page)]]
+                [:td [:span.text-gray-500.text-sm backlinks]]
+                [:td [:span.text-gray-500.text-sm (if created-at
+                                                    (date/int->local-time created-at)
+                                                    "Unknown")]]
+                [:td [:span.text-gray-500.text-sm (if updated-at
+                                                    (date/int->local-time updated-at)
+                                                    "Unknown")]]])]]))])))

+ 1 - 1
src/main/frontend/components/page.css

@@ -68,7 +68,7 @@
 /* Change to another cursor style if Shift key is active */
 [data-active-keystroke*="Shift" i]
 :is(.journal-title, .page-title,
-    .block-ref, .page-ref, a.tag, 
+    .block-ref, .page-ref, a.tag,
     .bullet-container.cursor) {
   cursor: e-resize;
 }

+ 11 - 0
src/main/frontend/components/svg.cljs

@@ -310,6 +310,17 @@
         c37.027-10.806,61.375,4.323,61.375,4.323C218.946,192.781,201.513,216.553,201.513,216.553z"}]])
 
 
+(rum/defc caret-up
+  []
+  [:svg.h-4.w-4
+   {:aria-hidden "true"
+    :version "1.1"
+    :view-box "0 0 320 512"
+    :fill "currentColor"
+    :display "inline-block"}
+   [:path {:d "M288.662 352H31.338c-17.818 0-26.741-21.543-14.142-34.142l128.662-128.662c7.81-7.81 20.474-7.81 28.284 0l128.662 128.662c12.6 12.599 3.676 34.142-14.142 34.142z"}]])
+
+
 (rum/defc caret-down
   []
   [:svg.h-4.w-4

+ 8 - 0
src/main/frontend/config.cljs

@@ -292,6 +292,7 @@
 (def config-file "config.edn")
 (def custom-css-file "custom.css")
 (def metadata-file "metadata.edn")
+(def pages-metadata-file "pages-metadata.edn")
 
 (def config-default-content (rc/inline "config.edn"))
 
@@ -369,6 +370,13 @@
    (when repo
      (get-file-path repo (str app-name "/" metadata-file)))))
 
+(defn get-pages-metadata-path
+  ([]
+   (get-pages-metadata-path (state/get-current-repo)))
+  ([repo]
+   (when repo
+     (get-file-path repo (str app-name "/" pages-metadata-file)))))
+
 (defn get-custom-css-path
   ([]
    (get-custom-css-path (state/get-current-repo)))

+ 6 - 0
src/main/frontend/date.cljs

@@ -191,6 +191,12 @@
   (when day
     (format (tf/parse (tf/formatter "yyyyMMdd") (str day)))))
 
+(defn journal-day->ts
+  [day]
+  (when day
+    (-> (tf/parse (tf/formatter "yyyyMMdd") (str day))
+        (tc/to-long))))
+
 (defn journal-title->long
   [journal-title]
   (journal-title-> journal-title #(tc/to-long %)))

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

@@ -178,6 +178,9 @@
         :file/last-modified-at "Last modified at"
         :file/no-data "No data"
         :file/format-not-supported "Format .{1} is not supported."
+        :page/created-at "Created At"
+        :page/updated-at "Updated At"
+        :page/backlinks "Back Links"
         :editor/block-search "Search for a block"
         :editor/image-uploading "Uploading"
         :draw/invalid-file "Could not load this invalid excalidraw file"

+ 1 - 2
src/main/frontend/extensions/graph.cljs

@@ -63,8 +63,7 @@
       (let [page-name (string/lower-case node)]
         (.unhoverNode ^js graph node)
         (route-handler/redirect! {:to :page
-                                  :path-params {:name page-name}})
-        ))))
+                                  :path-params {:name page-name}})))))
 
 (defn reset-graph!
   [^js graph]

+ 18 - 18
src/main/frontend/fs.cljs

@@ -61,20 +61,20 @@
   [repo dir path content opts]
   (when content
     (let [fs-record (get-fs dir)]
-      (p/let [metadata-or-css? (or (string/ends-with? path config/metadata-file)
-                                  (string/ends-with? path config/custom-css-file))
-             content (if metadata-or-css? content (encrypt/encrypt content))]
-       (->
-        (p/let [_ (protocol/write-file! (get-fs dir) repo dir path content opts)]
-          (when (= bfs-record fs-record)
-            (db/set-file-last-modified-at! repo (config/get-file-path repo path) (js/Date.))))
-        (p/catch (fn [error]
-                   (log/error :file/write-failed {:dir dir
-                                                  :path path
-                                                  :error error})
-                   ;; Disable this temporarily
-                   ;; (js/alert "Current file can't be saved! Please copy its content to your local file system and click the refresh button.")
-                   )))))))
+      (p/let [metadata-or-css? (or (string/ends-with? path (str "/" config/metadata-file))
+                                   (string/ends-with? path (str "/" config/custom-css-file)))
+              content (if metadata-or-css? content (encrypt/encrypt content))]
+        (->
+         (p/let [_ (protocol/write-file! (get-fs dir) repo dir path content opts)]
+           (when (= bfs-record fs-record)
+             (db/set-file-last-modified-at! repo (config/get-file-path repo path) (js/Date.))))
+         (p/catch (fn [error]
+                    (log/error :file/write-failed {:dir dir
+                                                   :path path
+                                                   :error error})
+                    ;; Disable this temporarily
+                    ;; (js/alert "Current file can't be saved! Please copy its content to your local file system and click the refresh button.")
+                    )))))))
 
 (defn read-file
   ([dir path]
@@ -90,7 +90,7 @@
 (defn rename!
   [repo old-path new-path]
   (cond
-    ; See https://github.com/isomorphic-git/lightning-fs/issues/41
+                                        ; See https://github.com/isomorphic-git/lightning-fs/issues/41
     (= old-path new-path)
     (p/resolved nil)
 
@@ -146,9 +146,9 @@
       (p/let [stat (stat dir path)]
         true)
       (p/catch
-       (fn [_error]
-         (p/let [_ (write-file! repo dir path initial-content nil)]
-           false)))))))
+          (fn [_error]
+            (p/let [_ (write-file! repo dir path initial-content nil)]
+              false)))))))
 
 (defn file-exists?
   [dir path]

+ 20 - 0
src/main/frontend/handler/common.cljs

@@ -193,3 +193,23 @@
   {:title page-name
    ;; :date (date/get-date-time-string)
    })
+
+(defn fix-pages-timestamps
+  [pages]
+  (map (fn [{:block/keys [name created-at updated-at journal-day] :as p}]
+         (cond->
+           p
+
+           (nil? created-at)
+           (assoc :block/created-at
+                  (if journal-day
+                    (date/journal-day->ts journal-day)
+                    (util/time-ms)))
+
+           (nil? updated-at)
+           (assoc :block/updated-at
+                  ;; Not exact true
+                  (if journal-day
+                    (date/journal-day->ts journal-day)
+                    (util/time-ms)))))
+    pages))

+ 11 - 0
src/main/frontend/handler/file.cljs

@@ -300,6 +300,17 @@
       (when-not file-exists?
         (reset-file! repo-url path default-content)))))
 
+(defn create-pages-metadata-file
+  [repo-url]
+  (let [repo-dir (config/get-repo-dir repo-url)
+        path (str config/app-name "/" config/pages-metadata-file)
+        file-path (str "/" path)
+        default-content "{}"]
+    (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" config/app-name))
+            file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
+      (when-not file-exists?
+        (reset-file! repo-url path default-content)))))
+
 (defn edn-file-set-key-value
   [path k v]
   (when-let [repo (state/get-current-repo)]

+ 21 - 2
src/main/frontend/handler/metadata.cljs

@@ -4,9 +4,13 @@
             [cljs.reader :as reader]
             [frontend.config :as config]
             [frontend.db :as db]
+            [frontend.fs :as fs]
             [datascript.db :as ddb]
             [clojure.string :as string]
-            [promesa.core :as p]))
+            [promesa.core :as p]
+            [frontend.util :as util]
+            [frontend.date :as date]
+            [frontend.handler.common :as common-handler]))
 
 (def default-metadata-str "{}")
 
@@ -33,10 +37,25 @@
               new-metadata (if encrypted?
                              (assoc new-metadata :db/encrypted? true)
                              new-metadata)
-              _ (prn "New metadata:\n" new-metadata)
               new-content (pr-str new-metadata)]
           (file-handler/set-file-content! repo path new-content))))))
 
+(defn set-pages-metadata!
+  [repo]
+  (let [path (config/get-pages-metadata-path)
+        all-pages (->> (db/get-all-pages repo)
+                       (common-handler/fix-pages-timestamps)
+                       (map #(select-keys % [:block/name :block/created-at :block/updated-at]))
+                       (vec))]
+    (-> (file-handler/create-pages-metadata-file repo)
+        (p/finally (fn []
+                     (let [new-content (pr-str all-pages)]
+                       (fs/write-file! repo
+                                       (config/get-repo-dir repo)
+                                       path
+                                       new-content
+                                       {})))))))
+
 (defn set-db-encrypted-secret!
   [encrypted-secret]
   (when-not (string/blank? encrypted-secret)

+ 7 - 6
src/main/frontend/handler/page.cljs

@@ -449,14 +449,15 @@
      (init-commands!)
      (shortcut/refresh!))))
 
-;; TODO: add use :file/last-modified-at
-(defn get-pages-with-modified-at
+(defn get-all-pages
   [repo]
-  (->> (db/get-modified-pages repo)
-       (remove util/file-page?)
-       (remove util/uuid-string?)
+  (->> (db/get-all-pages)
        (remove (fn [p]
-                 (db/built-in-pages-names (string/upper-case p))))))
+                 (let [name (:block/name p)]
+                   (or (util/file-page? name)
+                       (util/uuid-string? name)
+                       (db/built-in-pages-names (string/upper-case name))))))
+       (common-handler/fix-pages-timestamps)))
 
 (defn get-filters
   [page-name]

+ 26 - 0
src/main/frontend/handler/repo.cljs

@@ -184,6 +184,31 @@
                      (remove-non-exists-refs!))]
     (db/transact! repo-url all-data)))
 
+(defn- load-pages-metadata!
+  [repo file-paths files]
+  (try
+    (let [file (config/get-pages-metadata-path)]
+      (when (contains? (set file-paths) file)
+        (when-let [content (some #(when (= (:file/path %) file) (:file/content %)) files)]
+          (let [metadata (common-handler/safe-read-string content "Parsing pages metadata file failed: ")
+                pages (db/get-all-pages repo)
+                pages (zipmap (map :block/name pages) pages)
+                metadata (->>
+                          (filter (fn [{:block/keys [name created-at updated-at]}]
+                                    (when-let [page (get pages name)]
+                                      (and
+                                       (or
+                                        (nil? (:block/created-at page))
+                                        (>= created-at (:block/created-at page)))
+                                       (or
+                                        (nil? (:block/updated-at page))
+                                        (>= updated-at (:block/created-at page)))))) metadata)
+                          (remove nil?))]
+            (when (seq metadata)
+              (db/transact! repo metadata))))))
+    (catch js/Error e
+      (log/error :exception e))))
+
 (defn- parse-files-and-create-default-files-inner!
   [repo-url files delete-files delete-blocks file-paths first-clone? db-encrypted? re-render? re-render-opts metadata]
   (let [parsed-files (filter
@@ -200,6 +225,7 @@
                                     (:file/content %)) files)]
           (file-handler/restore-config! repo-url content true))))
     (reset-contents-and-blocks! repo-url files blocks-pages delete-files delete-blocks)
+    (load-pages-metadata! repo-url file-paths files)
     (when first-clone?
       (if (and (not db-encrypted?) (state/enable-encryption? repo-url))
         (state/pub-event! [:modal/encryption-setup-dialog repo-url

+ 15 - 28
src/main/frontend/modules/outliner/core.cljs

@@ -60,24 +60,6 @@
      (outliner-state/get-by-parent-id repo [:block/uuid id])
      (mapv block))))
 
-;; TODO: we might need to store created-at and updated-at as datom attributes
-;; instead of being attributes of properties.
-;; which might improve the db performance, we can improve it later
-(defn- with-timestamp
-  [m]
-  (let [updated-at (util/time-ms)
-        properties (assoc (:block/properties m)
-                          :id (:block/uuid m)
-                          :updated-at updated-at)
-        page-id (or (get-in m [:block/page :db/id])
-                    (:db/id (:block/page (db/entity (:db/id m)))))
-        page (db/entity page-id)
-        page-tx {:db/id page-id
-                 :block/updated-at updated-at
-                 :block/created-at (or (:block/created-at page)
-                                       updated-at)}]
-    [m page-tx]))
-
 (defn- update-block-unordered
   [block]
   (let [parent (:block/parent block)
@@ -153,26 +135,31 @@
                 (dissoc :block/children :block/meta)
                 (util/remove-nils))
           m (if (state/enable-block-timestamps?) (block-with-timestamps m) m)
-          other-tx (:db/other-tx m)]
+          other-tx (:db/other-tx m)
+          id (:db/id (:data this))]
       (when (seq other-tx)
         (swap! txs-state (fn [txs]
                            (vec (concat txs other-tx)))))
 
-      (when-let [id (:db/id (:data this))]
+      (when id
         (swap! txs-state (fn [txs]
                            (vec
                             (concat txs
                                     (map (fn [attribute]
                                            [:db/retract id attribute])
-                                      db-schema/retract-attributes))))))
+                                      db-schema/retract-attributes)))))
+
+        (when-let [e (:block/page (db/entity id))]
+          (let [m {:db/id (:db/id e)
+                   :block/updated-at (util/time-ms)}
+                m (if (:block/created-at e)
+                    m
+                    (assoc m :block/created-at (util/time-ms)))]
+            (swap! txs-state conj m))))
+
       (swap! txs-state conj (dissoc m :db/other-tx))
-      this
-      ;; TODO: enable for the database-only version
-      ;; (let [[m page-tx] (with-timestamp (:data this))]
-      ;;  (swap! txs-state conj m page-tx)
-      ;;  m)
-      )
-    )
+
+      this))
 
   (-del [this txs-state children?]
     (assert (ds/outliner-txs-state? txs-state)