Browse Source

feat: sort groups for views (#12046)

* Add two properties for groups sorting

1. :logseq.property.table/sort-groups-by
2. :logseq.property.table/sort-groups-desc?

* Add ui for groups sort by and order

* enhance: sort by journal day desc by default

* rename to :*.view/sort-groups-by-property to make it clear that

property type is :property.

* suggested by copilot

* fix: name

* Group by Page instead of "Block Page"
Tienson Qin 2 tháng trước cách đây
mục cha
commit
36de5397e1

+ 24 - 7
deps/db/src/logseq/db/common/view.cljs

@@ -473,20 +473,37 @@
                   (db-property/property-value-content pvalue))
                 pvalue)))
           result (if group-by-property-ident
-                   (->> filtered-entities
-                        (group-by readable-property-value-or-ent)
-                        (seq)
-                        (sort-by (fn [[by-value _]]
+                   (let [groups-sort-by-property-ident (or (:db/ident (:logseq.property.view/sort-groups-by-property view))
+                                                           :block/journal-day)
+                         desc? (:logseq.property.view/sort-groups-desc? view)
+                         result (->> filtered-entities
+                                     (group-by readable-property-value-or-ent)
+                                     (seq))
+                         keyfn (fn [groups-sort-by-property-ident]
+                                 (fn [[by-value _]]
                                    (cond
                                      group-by-page?
-                                     (:block/updated-at by-value)
+                                     (let [v (get by-value groups-sort-by-property-ident)]
+                                       (if (and (= groups-sort-by-property-ident :block/journal-day) (not desc?)
+                                                (nil? (:block/journal-day by-value)))
+                                         ;; Use MAX_SAFE_INTEGER so non-journal pages (without :block/journal-day) are sorted
+                                         ;; after all journal pages when sorting by journal date.
+                                         js/Number.MAX_SAFE_INTEGER
+                                         v))
                                      group-by-closed-values?
                                      (:block/order by-value)
                                      ref-property?
                                      (db-property/property-value-content by-value)
                                      :else
-                                     by-value))
-                                 (if group-by-page? #(compare %2 %1) compare)))
+                                     by-value)))]
+                     (sort (common-util/by-sorting
+                            (cond->
+                             [{:get-value (keyfn groups-sort-by-property-ident)
+                               :asc? (not desc?)}]
+                              (not= groups-sort-by-property-ident :block/title)
+                              (conj {:get-value (keyfn :block/title)
+                                     :asc? (not desc?)})))
+                           result))
                    (sort-entities db sorting filtered-entities))
           data' (if group-by-property-ident
                   (map

+ 33 - 16
deps/db/src/logseq/db/frontend/property.cljs

@@ -12,6 +12,14 @@
 ;; Main property vars
 ;; ==================
 
+;; Ignore this property when rtc,
+;; since users frequently click the sort button to view table content temporarily,
+;; but this action does not need to be synchronized with other clients.
+(def property-ignore-rtc
+  {:rtc/ignore-attr-when-init-upload true
+   :rtc/ignore-attr-when-init-download true
+   :rtc/ignore-attr-when-syncing true})
+
 (def ^:large-vars/data-var built-in-properties
   "Map of built in properties for db graphs with their :db/ident as keys.
    Each property has a config map with the following keys:
@@ -122,6 +130,12 @@
                                     :schema {:type :entity
                                              :public? false
                                              :hide? true}}
+     :block/journal-day    {:title "Journal date"
+                            :attribute :block/journal-day
+                            :schema {:type :raw-number
+                                     :public? false
+                                     :hide? true}
+                            :queryable? true}
      :block/created-at     {:title "Node created at"
                             :attribute :block/created-at
                             :schema {:type :datetime
@@ -420,17 +434,26 @@
        :hide? true}
       :queryable? true}
 
+     :logseq.property.view/sort-groups-by-property {:title "View sort groups by"
+                                                    :schema
+                                                    {:type :property
+                                                     :hide? true
+                                                     :public? false}
+                                                    :rtc property-ignore-rtc}
+     :logseq.property.view/sort-groups-desc? {:title "View sort groups DESC"
+                                              :schema
+                                              {:type :checkbox
+                                               :hide? true
+                                               :public? false}
+                                              :properties {:logseq.property/scalar-default-value true}
+                                              :rtc property-ignore-rtc}
+
      :logseq.property.table/sorting {:title "View sorting"
                                      :schema
                                      {:type :coll
                                       :hide? true
                                       :public? false}
-                                     ;; ignore this property when rtc,
-                                     ;; since users frequently click the sort button to view table content temporarily,
-                                     ;; but this action does not need to be synchronized with other clients.
-                                     :rtc {:rtc/ignore-attr-when-init-upload true
-                                           :rtc/ignore-attr-when-init-download true
-                                           :rtc/ignore-attr-when-syncing true}}
+                                     :rtc property-ignore-rtc}
 
      :logseq.property.table/filters {:title "View filters"
                                      :schema
@@ -485,17 +508,13 @@
                                              :schema {:type :raw-number
                                                       :hide? true
                                                       :public? false}
-                                             :rtc {:rtc/ignore-attr-when-init-upload true
-                                                   :rtc/ignore-attr-when-init-download true
-                                                   :rtc/ignore-attr-when-syncing true}}
+                                             :rtc property-ignore-rtc}
      :logseq.property.asset/remote-metadata {:title "File remote metadata"
                                              :schema
                                              {:type :map
                                               :hide? true
                                               :public? false}
-                                             :rtc {:rtc/ignore-attr-when-init-upload true
-                                                   :rtc/ignore-attr-when-init-download true
-                                                   :rtc/ignore-attr-when-syncing true}}
+                                             :rtc property-ignore-rtc}
      :logseq.property.asset/resize-metadata {:title "Asset resize metadata"
                                              :schema {:type :map
                                                       :hide? true
@@ -561,16 +580,14 @@
                                                        :schema {:type :datetime
                                                                 :public? false
                                                                 :hide? true}
-                                                       :rtc {:rtc/ignore-attr-when-init-upload true
-                                                             :rtc/ignore-attr-when-init-download true
-                                                             :rtc/ignore-attr-when-syncing true}})))
+                                                       :rtc property-ignore-rtc})))
 
 (def db-attribute-properties
   "Internal properties that are also db schema attributes"
   #{:block/alias :block/tags :block/parent
     :block/order :block/collapsed? :block/page
     :block/refs :block/path-refs :block/link
-    :block/title :block/closed-value-property
+    :block/title :block/closed-value-property :block/journal-day
     :block/created-at :block/updated-at})
 
 (assert (= db-attribute-properties

+ 1 - 1
deps/db/src/logseq/db/frontend/schema.cljs

@@ -37,7 +37,7 @@
          (map (juxt :major :minor)
               [(parse-schema-version x) (parse-schema-version y)])))
 
-(def version (parse-schema-version "65.9"))
+(def version (parse-schema-version "65.10"))
 
 (defn major-version
   "Return a number.

+ 54 - 2
src/main/frontend/components/views.cljs

@@ -424,6 +424,53 @@
         columns)))
     columns))
 
+(defonce groups-sort-by-options
+  [["Journal date" :block/journal-day]
+   ["Page name" :block/title]
+   ["Page updated date" :block/updated-at]
+   ["Page created date" :block/created-at]])
+(defonce groups-sort-by-name->property-identity
+  (into {} groups-sort-by-options))
+(defonce groups-sort-by-property-identity->name
+  (set/map-invert groups-sort-by-name->property-identity))
+
+(rum/defc groups-sort
+  [view-entity sort-by-value]
+  (let [property-ident (or (:db/ident sort-by-value) :block/journal-day)]
+    (shui/dropdown-menu-sub
+     (shui/dropdown-menu-sub-trigger
+      "Sort groups by")
+     (shui/dropdown-menu-sub-content
+      (for [[option _] groups-sort-by-options]
+        (shui/dropdown-menu-checkbox-item
+         {:key option
+          :checked (= option (groups-sort-by-property-identity->name property-ident))
+          :onCheckedChange (fn [checked?]
+                             (let [property-id (:db/id (db/entity (groups-sort-by-name->property-identity option)))]
+                               (if checked?
+                                 (db-property-handler/set-block-property! (:db/id view-entity) :logseq.property.view/sort-groups-by-property
+                                                                          property-id)
+                                 (db-property-handler/remove-block-property! (:db/id view-entity) :logseq.property.view/sort-groups-by-property))))
+          :onSelect (fn [e] (.preventDefault e))}
+         option))))))
+
+(rum/defc groups-sort-order
+  [view-entity desc?]
+  (shui/dropdown-menu-sub
+   (shui/dropdown-menu-sub-trigger
+    "Sort groups order")
+   (shui/dropdown-menu-sub-content
+    (for [option ["Descending" "Ascending"]]
+      (shui/dropdown-menu-checkbox-item
+       {:key option
+        :checked (= option (if desc? "Descending" "Ascending"))
+        :onCheckedChange (fn [checked?]
+                           (db-property-handler/set-block-property! (:db/id view-entity) :logseq.property.view/sort-groups-desc?
+                                                                    (or (and checked? (= "Descending" option))
+                                                                        (and (not checked?) (not= "Descending" option)))))
+        :onSelect (fn [e] (.preventDefault e))}
+       option)))))
+
 (rum/defc more-actions
   [view-entity columns {:keys [column-visible? rows column-toggle-visibility]} {:keys [group-by-property-ident]}]
   (let [display-type (:db/ident (:logseq.property.view/type view-entity))
@@ -433,13 +480,14 @@
                                                    (:logseq.property.view/feature-type view-entity))
                                         (:logseq.property/query view-entity))
                                    [{:id :block/page
-                                     :name "Block Page"}])
+                                     :name "Page"}])
                                  (filter (fn [column]
                                            (when (:id column)
                                              (when-let [p (db/entity (:id column))]
                                                (and (not (db-property/many? p))
                                                     (contains? #{:default :number :checkbox :url :node :date}
-                                                               (:logseq.property/type p)))))) columns))]
+                                                               (:logseq.property/type p)))))) columns))
+        group-by-page? (some #{:block/page} (map :id group-by-columns))]
     (shui/dropdown-menu
      (shui/dropdown-menu-trigger
       {:asChild true}
@@ -482,6 +530,10 @@
                                     (db-property-handler/remove-block-property! (:db/id view-entity) :logseq.property.view/group-by-property)))
                :onSelect (fn [e] (.preventDefault e))}
               (:name column))))))
+       (when group-by-page?
+         (groups-sort view-entity (:logseq.property.view/sort-groups-by-property view-entity)))
+       (when group-by-property-ident
+         (groups-sort-order view-entity (:logseq.property.view/sort-groups-desc? view-entity)))
        (shui/dropdown-menu-item
         {:key "export-edn"
          :on-click #(db-export-handler/export-view-nodes-data rows {:group-by? (some? group-by-property-ident)})}

+ 2 - 1
src/main/frontend/db/model.cljs

@@ -426,7 +426,8 @@ independent of format as format specific heading characters are stripped"
 (defn journal-page?
   "sanitized page-name only"
   [page-name]
-  (ldb/journal? (ldb/get-page (conn/get-db) page-name)))
+  (when (string? page-name)
+    (ldb/journal? (ldb/get-page (conn/get-db) page-name))))
 
 (defn get-all-referenced-blocks-uuid
   "Get all uuids of blocks with any back link exists."

+ 2 - 1
src/main/frontend/worker/db/migrate.cljs

@@ -367,7 +367,8 @@
    ["65.6" {:fix update-extends-to-cardinality-many}]
    ["65.7" {:fix add-quick-add-page}]
    ["65.8" {:fix add-missing-page-name}]
-   ["65.9" {:properties [:logseq.property.embedding/hnsw-label-updated-at]}]])
+   ["65.9" {:properties [:logseq.property.embedding/hnsw-label-updated-at]}]
+   ["65.10" {:properties [:block/journal-day :logseq.property.view/sort-groups-by-property :logseq.property.view/sort-groups-desc?]}]])
 
 (let [[major minor] (last (sort (map (comp (juxt :major :minor) db-schema/parse-schema-version first)
                                      schema-version->updates)))]