Tienson Qin 1 год назад
Родитель
Сommit
6c653094dd

+ 0 - 10
deps/db/src/logseq/db.cljs

@@ -369,16 +369,6 @@
   []
   (common-uuid/gen-uuid))
 
-(defn get-tag-blocks
-  [db tag-name]
-  (d/q '[:find [?b ...]
-         :in $ ?tag
-         :where
-         [?e :block/name ?tag]
-         [?b :block/tags ?e]]
-       db
-       (common-util/page-name-sanity-lc tag-name)))
-
 (defn get-classes-with-property
   "Get classes which have given property as a class property"
   [db property-id]

+ 35 - 31
deps/db/src/logseq/db/frontend/entity_plus.cljc

@@ -23,43 +23,47 @@
   ([^Entity e k default-value]
    (when k
      (case k
-      :block/raw-content
-      (lookup-entity e :block/content default-value)
+       :block/raw-content
+       (lookup-entity e :block/content default-value)
 
-      :block/properties
-      (let [db (.-db e)]
-        (if (db-based-graph? db)
-          (lookup-entity e :block/properties
-                         (->> (into {} e)
-                              (filter (fn [[k _]] (db-property/property? k)))
-                              (into {})))
-          (lookup-entity e :block/properties nil)))
+       :block/properties
+       (let [db (.-db e)]
+         (if (db-based-graph? db)
+           (lookup-entity e :block/properties
+                          (->> (into {} e)
+                               (filter (fn [[k _]] (db-property/property? k)))
+                               (into {})))
+           (lookup-entity e :block/properties nil)))
 
-      :block/content
-      (or
-       (get (.-kv e) k)
-       (let [result (lookup-entity e k default-value)]
-         (or
-          (if (string? result)
-            (db-content/special-id-ref->page-ref result (:block/refs e))
-            result)
-          default-value)))
+       :block/content
+       (or
+        (get (.-kv e) k)
+        (let [result (lookup-entity e k default-value)]
+          (or
+           (if (string? result)
+             (db-content/special-id-ref->page-ref result (:block/refs e))
+             result)
+           default-value)))
 
-      :block/_parent
-      (->> (lookup-entity e k default-value)
-           (remove (fn [e] (or (:logseq.property/created-from-property e)
-                               (:block/closed-value-property e))))
-           seq)
+       :block/_parent
+       (->> (lookup-entity e k default-value)
+            (remove (fn [e] (or (:logseq.property/created-from-property e)
+                                (:block/closed-value-property e))))
+            seq)
 
-      :block/_raw-parent
-      (lookup-entity e :block/_parent default-value)
+       :block/_raw-parent
+       (lookup-entity e :block/_parent default-value)
 
-      :property/closed-values
-      (->> (lookup-entity e :block/_closed-value-property default-value)
-           (sort-by :block/order))
+       :property/closed-values
+       (->> (lookup-entity e :block/_closed-value-property default-value)
+            (sort-by :block/order))
 
-      (or (get (.-kv e) k)
-          (lookup-entity e k default-value))))))
+       :object/name
+       (or (lookup-entity e :block/original-name nil)
+           (lookup-entity e :block/content nil))
+
+       (or (get (.-kv e) k)
+           (lookup-entity e k default-value))))))
 
 #?(:org.babashka/nbb
    nil

+ 8 - 0
deps/outliner/src/logseq/outliner/property.cljs

@@ -349,6 +349,14 @@
             (recur (:class/parent current-parent))))))
     @*classes))
 
+(defn ^:api get-class-properties
+  [class]
+  (let [class-parents (get-class-parents [class])]
+    (->> (mapcat (fn [class]
+                   (:class/schema.properties class)) (concat [class] class-parents))
+         (common-util/distinct-by :db/id)
+         (ldb/sort-by-order))))
+
 (defn ^:api get-block-classes-properties
   [db eid]
   (let [block (d/entity db eid)

+ 2 - 2
deps/shui/src/logseq/shui/table/core.cljc

@@ -122,7 +122,7 @@
 (rum/defc table-head < rum/static
   [& prop-and-children]
   (let [[prop children] (get-prop-and-children prop-and-children)]
-    [:th (merge {:class "h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0"}
+    [:th (merge {:class "px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0"}
                 prop)
      children]))
 
@@ -136,6 +136,6 @@
 (rum/defc table-cell < rum/static
   [& prop-and-children]
   (let [[prop children] (get-prop-and-children prop-and-children)]
-    [:td (merge {:class "p-4 align-middle [&:has([role=checkbox])]:pr-0"}
+    [:td (merge {:class "px-4 py-1 align-middle [&:has([role=checkbox])]:pr-0"}
                    prop)
      children]))

+ 0 - 1
src/main/frontend/components/all_pages.cljs

@@ -95,7 +95,6 @@
     :name "Updated At"
     :header header-cp
     :cell timestamp-cell-cp}])
-
 (defn- get-all-pages
   []
   (->> (page-handler/get-all-pages (state/get-current-repo))

+ 29 - 24
src/main/frontend/components/block.cljs

@@ -2387,7 +2387,7 @@
           (cond
             (:block/name block)
             [:div.flex.flex-row.items-center.gap-1
-             (icon/get-page-icon block {})
+             (when-not (:table? config) (icon/get-page-icon block {}))
              (page-cp config block)]
 
             :else
@@ -2474,7 +2474,7 @@
   (let [*hide-block-refs? (get state ::hide-block-refs?)
         *refs-count (get state ::refs-count)
         hide-block-refs? (rum/react *hide-block-refs?)
-        editor-box (get config :editor-box)
+        editor-box (state/get-component :editor/box)
         editor-id (str "editor-" edit-input-id)
         slide? (:slide? config)
         block-reference-only? (some->
@@ -2486,9 +2486,10 @@
         db-based? (config/db-based-graph? repo)
         refs-count (if (seq (:block/_refs block))
                      (count (:block/_refs block))
-                     (rum/react *refs-count))]
+                     (rum/react *refs-count))
+        table? (:table? config)]
     [:div.block-content-or-editor-wrap
-     (when db-based? (block-positioned-properties config block :block-left))
+     (when (and db-based? (not table?)) (block-positioned-properties config block :block-left))
      [:div.flex.flex-1.flex-col
       [:div.flex.flex-1.flex-row.gap-1.items-start
        (if (and edit? editor-box)
@@ -2514,7 +2515,7 @@
                                           (state/set-editing! edit-input-id content block "" {:container-id (:container-id config)}))}})
            (block-content config block edit-input-id block-id slide?))
 
-          (when db-based? (block-positioned-properties config block :block-right))
+          (when (and db-based? (not table?)) (block-positioned-properties config block :block-right))
 
           (when (and (not hide-block-refs-count?)
                      (not named?))
@@ -2535,14 +2536,15 @@
                                     (editor-handler/edit-block! block :max))}
                 svg/edit])])])
 
-       (when-not (:block-ref? config)
+       (when-not (or (:block-ref? config) (:table? config))
          [:div.flex.flex-row.items-center.gap-1.h-6
           (when (and db-based? (seq (:block/tags block)))
             (tags config block))])
 
-       (block-refs-count block refs-count *hide-block-refs?)]
+       (when-not (:table? config)
+         (block-refs-count block refs-count *hide-block-refs?))]
 
-      (when (and (not hide-block-refs?) (> refs-count 0))
+      (when (and (not (:table? config)) (not hide-block-refs?) (> refs-count 0))
         (when-let [refs-cp (state/get-component :block/linked-references)]
           (refs-cp uuid)))]]))
 
@@ -2887,8 +2889,9 @@
                                (str (:block/uuid block))))
         edit-input-id (str "edit-block-" (:block/uuid block))
         container-id (:container-id config*)
-        edit? (or (state/sub-editing? [container-id (:block/uuid block)])
+        editing? (or (state/sub-editing? [container-id (:block/uuid block)])
                   (state/sub-editing? [:unknown-container (:block/uuid block)]))
+        table? (:table? config*)
         custom-query? (boolean (:custom-query? config*))
         ref-or-custom-query? (or ref? custom-query?)
         *navigating-block (get container-state ::navigating-block)
@@ -2954,20 +2957,20 @@
        custom-query?
        (assoc :data-query true))
 
-     (when (and ref? breadcrumb-show?)
+     (when (and ref? breadcrumb-show? (not table?))
        (breadcrumb config repo uuid {:show-page? false
                                      :indent? true
                                      :navigating-block *navigating-block}))
 
      ;; only render this for the first block in each container
-     (when top?
+     (when (and top? (not table?))
        (dnd-separator-wrapper block children block-id slide? true false))
 
      [:div.block-main-container.flex.flex-row.pr-2.gap-1
       {:data-has-heading (some-> block :block/properties (pu/lookup :logseq.property/heading))
        :on-touch-start (fn [event uuid] (block-handler/on-touch-start event uuid))
        :on-touch-move (fn [event]
-                        (block-handler/on-touch-move event block uuid edit? *show-left-menu? *show-right-menu?))
+                        (block-handler/on-touch-move event block uuid editing? *show-left-menu? *show-right-menu?))
        :on-touch-end (fn [event]
                        (block-handler/on-touch-end event block uuid *show-left-menu? *show-right-menu?))
        :on-touch-cancel (fn [_e]
@@ -2976,8 +2979,8 @@
                         (block-mouse-over e *control-show? block-id doc-mode?))
        :on-mouse-leave (fn [e]
                          (block-mouse-leave e *control-show? block-id doc-mode?))}
-      (when (and (not slide?) (not in-whiteboard?))
-        (let [edit? (or edit?
+      (when (and (not slide?) (not in-whiteboard?) (not table?))
+        (let [edit? (or editing?
                         (= uuid (:block/uuid (state/get-edit-block))))]
           (block-control config block
                          {:uuid uuid
@@ -2986,7 +2989,7 @@
                           :*control-show? *control-show?
                           :edit? edit?})))
 
-      (when (and @*show-left-menu? (not in-whiteboard?))
+      (when (and @*show-left-menu? (not in-whiteboard?) (not table?))
         (block-left-menu config block))
 
       (if whiteboard-block?
@@ -2994,29 +2997,31 @@
         ;; Not embed self
         [:div.flex.flex-col.w-full
          (let [block (merge block (block/parse-title-and-body uuid (:block/format block) pre-block? content))
-               hide-block-refs-count? (and (:embed? config)
-                                           (= (:block/uuid block) (:embed-id config)))]
+               hide-block-refs-count? (or (and (:embed? config)
+                                           (= (:block/uuid block) (:embed-id config)))
+                                          table?)]
            (block-content-or-editor config block
                                     {:edit-input-id edit-input-id
                                      :block-id block-id
-                                     :edit? edit?
+                                     :edit? editing?
                                      :hide-block-refs-count? hide-block-refs-count?}))])
 
-      (when (and @*show-right-menu? (not in-whiteboard?))
-        (block-right-menu config block edit?))]
+      (when (and @*show-right-menu? (not in-whiteboard?) (not table?))
+        (block-right-menu config block editing?))]
 
-     (when db-based? (block-positioned-properties config block :block-below))
+     (when (and db-based? (not table?))
+       (block-positioned-properties config block :block-below))
 
-     (when (and db-based? (not collapsed?))
+     (when (and db-based? (not collapsed?) (not table?))
        [:div {:style {:padding-left 29}}
         (db-properties-cp config block edit-input-id {:in-block-container? true})])
 
-     (when-not (or (:hide-children? config) in-whiteboard?)
+     (when-not (or (:hide-children? config) in-whiteboard? table?)
        (let [config' (-> (update config :level inc)
                          (dissoc :original-block :data :first-journal?))]
          (block-children config' block children collapsed?)))
 
-     (when-not in-whiteboard? (dnd-separator-wrapper block children block-id slide? false false))]))
+     (when-not (or in-whiteboard? table?) (dnd-separator-wrapper block children block-id slide? false false))]))
 
 (defn- block-changed?
   [old-block new-block]

+ 2 - 2
src/main/frontend/components/editor.cljs

@@ -148,8 +148,8 @@
                           (empty? matched-pages)
                           (when-not (db/page-exists? q)
                             (if db-tag?
-                              (concat [(str (t :new-page) " " q)
-                                       (str (t :new-class) " " q)]
+                              (concat [(str (t :new-class) " " q)
+                                       (str (t :new-page) " " q)]
                                       matched-pages)
                               (cons (str (t :new-page) " " q)
                                     matched-pages)))

+ 411 - 0
src/main/frontend/components/objects.cljs

@@ -0,0 +1,411 @@
+(ns frontend.components.objects
+  "Tagged objects"
+  (:require [logseq.shui.ui :as shui]
+            [rum.core :as rum]
+            [frontend.util :as util]
+            [frontend.ui :as ui]
+            [clojure.string :as string]
+            [frontend.components.block :as component-block]
+            [frontend.components.property.value :as pv]
+            [frontend.components.select :as select]
+            [frontend.state :as state]
+            [frontend.date :as date]
+            [goog.object :as gobj]
+            [goog.dom :as gdom]
+            [cljs-bean.core :as bean]
+            [promesa.core :as p]
+            [logseq.db :as ldb]
+            [frontend.db :as db]
+            [frontend.search.fuzzy :as fuzzy-search]
+            [logseq.outliner.property :as outliner-property]
+            [frontend.mixins :as mixins]
+            [logseq.db.frontend.property :as db-property]
+            [logseq.db.frontend.property.type :as db-property-type]
+            [clojure.set :as set]))
+
+(defn header-checkbox [{:keys [selected-all? selected-some? toggle-selected-all!]}]
+  (shui/checkbox
+   {:checked (or selected-all? (and selected-some? "indeterminate"))
+    :on-checked-change toggle-selected-all!
+    :aria-label "Select all"}))
+
+(defn row-checkbox [{:keys [row-selected? row-toggle-selected!]} row _column]
+  (shui/checkbox
+   {:checked (row-selected? row)
+    :on-checked-change (fn [v] (row-toggle-selected! row v))
+    :aria-label "Select row"}))
+
+(defn- header-cp
+  [{:keys [column-toggle-sorting! state]} column]
+  (let [sorting (:sorting state)
+        [asc?] (some (fn [item] (when (= (:id item) (:id column))
+                                  (when-some [asc? (:asc? item)]
+                                    [asc?]))) sorting)]
+    (shui/button
+     {:variant "text"
+      :class "!pl-0 !py-0 hover:text-foreground"
+      :onClick #(column-toggle-sorting! column)}
+     (:name column)
+     (case asc?
+       true
+       (ui/icon "arrow-up")
+       false
+       (ui/icon "arrow-down")
+       nil))))
+
+(defn- timestamp-cell-cp
+  [_table row column]
+  (some-> (get row (:id column))
+          date/int->local-time-2))
+
+(defn- get-property-value-for-search
+  [block property]
+  (let [type (get-in property [:block/schema :type])
+        many? (= :db.cardinality/many (get property :db/cardinality))
+        ref-types (into db-property-type/value-ref-property-types #{:entity})
+        number-type? (= :number type)
+        v (get block (:db/ident property))
+        v' (if many? v [v])
+        col (->> (if (ref-types type) (map db-property/property-value-content v') v')
+                 (remove nil?))]
+    (if number-type?
+      (reduce + (filter number? col))
+      (string/join ", " col))))
+
+(defn- build-columns
+  [class config]
+  (let [properties (outliner-property/get-class-properties class)]
+    (concat
+     [{:id :select
+       :name "Select"
+       :header (fn [table _column] (header-checkbox table))
+       :cell (fn [table row column] (row-checkbox table row column))
+       :column-list? false}
+      {:id :object/name
+       :name "Name"
+       :header header-cp
+       :cell (fn [_table row _column]
+               [:div.primary-cell
+                (component-block/block-container (assoc config :table? true) row)])}]
+     (map
+      (fn [property]
+        {:id (:db/ident property)
+         :name (:block/original-name property)
+         :header header-cp
+         :cell (fn [_table row _column]
+                 (pv/property-value row property (get row (:db/ident property)) {}))
+         :get-value (fn [row] (get-property-value-for-search row property))})
+      properties)
+
+     [{:id :block/created-at
+       :name "Created At"
+       :header header-cp
+       :cell timestamp-cell-cp}
+      {:id :block/updated-at
+       :name "Updated At"
+       :header header-cp
+       :cell timestamp-cell-cp}])))
+
+(defn- get-all-objects
+  [class]
+  ;; FIXME: async
+  (:block/_tags class))
+
+(rum/defc more-actions
+  [columns {:keys [column-visible? column-toggle-visiblity]}]
+  (shui/dropdown-menu
+   (shui/dropdown-menu-trigger
+    {:asChild true}
+    (shui/button
+     {:variant "ghost"
+      :class "text-muted-foreground !px-1"
+      :size :sm
+      :on-click #()}
+     (ui/icon "dots")))
+   (shui/dropdown-menu-content
+    {:align "end"}
+    (shui/dropdown-menu-group
+     (shui/dropdown-menu-sub
+      (shui/dropdown-menu-sub-trigger
+       "Properties")
+      (shui/dropdown-menu-sub-content
+       (for [column (remove #(false? (:column-list? %)) columns)]
+         (shui/dropdown-menu-checkbox-item
+          {:key (str (:id column))
+           :className "capitalize"
+           :checked (column-visible? column)
+           :onCheckedChange #(column-toggle-visiblity column %)}
+          (:name column)))))))))
+
+(defn table-header
+  [table columns]
+  (shui/table-row
+   {:class "bg-gray-01 shadow"}
+   (for [column columns]
+     (let [style (case (:id column)
+                   :block/original-name
+                   {}
+                   :select
+                   {:width 32}
+                   {:width 180})]
+       (shui/table-head
+        {:key (str (:id column))
+         :style style}
+        (let [header-fn (:header column)]
+          (if (fn? header-fn)
+            (header-fn table column)
+            header-fn)))))))
+
+(defn table-row
+  [{:keys [row-selected?] :as table} rows columns props]
+  (let [idx (gobj/get props "data-index")
+        row (nth rows idx)]
+    (shui/table-row
+     (merge
+      (bean/->clj props)
+      {:key (str (:id row))
+       :data-state (when (row-selected? row) "selected")})
+     (for [column columns]
+       (let [id (str (:id row) "-" (:id column))
+             render (get column :cell)]
+         (shui/table-cell
+          {:key id}
+          (render table row column)))))))
+
+(rum/defc search
+  [input {:keys [on-change]}]
+  (let [[show-input? set-show-input!] (rum/use-state false)]
+    (if show-input?
+      (shui/input
+       {:placeholder "Type to search"
+        :auto-focus true
+        :value input
+        :onChange (fn [e]
+                    (let [value (util/evalue e)]
+                      (on-change value)))
+        :on-key-down (fn [e]
+                       (when (= "Escape" (util/ekey e))
+                         (set-show-input! false)))
+        :class "max-w-sm !h-7 !py-0 border-none"})
+      (shui/button
+       {:variant "ghost"
+        ;; FIXME: remove ring when focused
+        :class "text-muted-foreground !px-1"
+        :size :sm
+        :on-click #(set-show-input! true)}
+       (ui/icon "search")))))
+
+(defn- property-ref-type?
+  [property]
+  (let [schema (:block/schema property)
+        type (:type schema)]
+    (db-property-type/ref-property-types type)))
+
+(defn- get-property-value-content
+  [e property]
+  (if (property-ref-type? property)
+    (db-property/property-value-content e)
+    (str e)))
+
+(rum/defc filter-property < rum/static
+  [columns {:keys [rows data-fns] :as table}]
+  (let [[property-ident set-property-ident!] (rum/use-state nil)
+        set-filters! (:set-filters! data-fns)
+        property (when property-ident (db/entity property-ident))
+        filters (get-in table [:state :filters])
+        columns (remove #(false? (:column-list? %)) columns)
+        items (map (fn [column]
+                     {:label (:name column)
+                      :value (:id column)}) columns)
+        option {:input-default-placeholder "Filter"
+                :input-opts {:class "!px-3 !py-1"}
+                :items items
+                :extract-fn :label
+                :extract-chosen-fn :value
+                :on-chosen (fn [value]
+                             (set-property-ident! value))}
+        option (if property
+                 (let [items (let [values (->> (mapcat (fn [e] (let [v (get e property-ident)]
+                                                                 (if (set? v) v #{v}))) rows)
+                                               (remove nil?)
+                                               (distinct))]
+                               (->>
+                                (map (fn [e]
+                                       {:label (get-property-value-content e property)
+                                        :value e})
+                                     values)
+                                (sort-by :label)))]
+                   (merge option
+                          {:items items
+                           :input-default-placeholder "Select"
+                           :multiple-choices? true
+                           :on-chosen (fn [_value _selected? selected]
+                                        (when (seq selected)
+                                          (let [filters' (conj filters [property :is selected])]
+                                            (set-filters! filters'))))}))
+                 option)]
+    (select/select option)))
+
+(rum/defc filter-properties < rum/static
+  [columns table]
+  (shui/button
+   {:variant "ghost"
+    :class "text-muted-foreground !px-1"
+    :size :sm
+    :on-click (fn [e]
+                (shui/popup-show! (.-target e)
+                                  (fn []
+                                    (filter-property columns table))
+                                  {:align :end
+                                   :as-dropdown? true
+                                   :auto-focus? true}))}
+   (ui/icon "filter")))
+
+(rum/defc filters-row < rum/static
+  [{:keys [data-fns] :as table}]
+  (let [filters (get-in table [:state :filters])
+        {:keys [set-filters!]} data-fns]
+    (when (seq filters)
+      [:div.filters-row.flex.flex-row.items-center.gap-4.flex-wrap.pb-2
+       (map-indexed
+        (fn [idx filter]
+          (let [[property operator value] filter]
+            (when (seq value)
+              (let [is? (= :is operator)]
+                [:div.flex.flex-row.items-center.border.rounded
+                 (shui/button
+                  {:class "!px-2 rounded-none border-r"
+                   :variant "ghost"
+                   :size :sm
+                   :disabled true}
+                  [:span.text-xs (:block/original-name property)])
+                 (shui/button
+                  {:class "!px-2 rounded-none border-r"
+                   :variant "ghost"
+                   :size :sm
+                   :on-click (fn [_e]
+                               (let [new-filters (update filters idx
+                                                         (fn [[property operator value]]
+                                                           ;; TODO: support more operators like `contains`, `between` for different types
+                                                           ;; and switch to use dropdown instead of toggle
+                                                           (let [operator' (if (= operator :is) :not :is)]
+                                                             [property operator' value])))]
+                                 (set-filters! new-filters)))}
+                  [:span.text-xs (if is? "is" "is not")])
+                 (shui/button
+                  {:class "!px-2 rounded-none border-r"
+                   :variant "ghost"
+                   :size :sm}
+                  [:div.flex.flex-row.items-center.gap-1.text-xs
+                   (->> (map (fn [v] [:div (get-property-value-content v property)]) value)
+                        (interpose [:div "or"]))])
+                 (shui/button
+                  {:class "!px-1 rounded-none"
+                   :variant "ghost"
+                   :size :sm
+                   :on-click (fn [e]
+                               (let [new-filters (vec (remove #{filter} filters))]
+                                 (set-filters! new-filters)))}
+                  (ui/icon "x"))]))))
+        filters)])))
+
+(defn- row-matched?
+  [row input filters]
+  (and
+   ;; full-text-search match
+   (if (string/blank? input)
+     true
+     (when row
+       (pos? (fuzzy-search/score (string/lower-case input) (:object/name row)))))
+   ;; filters check
+   (every?
+    (fn [[property operator match]]
+      (let [value (get row (:db/ident property))
+            value' (cond
+                     (set? value) value
+                     (nil? value) #{}
+                     :else #{value})]
+        (case operator
+          :is
+          (boolean (seq (set/intersection value' match)))
+          :not
+          (boolean (empty? (set/intersection value' match)))
+          true)))
+    filters)))
+
+(rum/defc objects-inner < rum/static
+  [config class]
+  (let [[input set-input!] (rum/use-state "")
+        ;; TODO: block.temp/tagged-at
+        [sorting set-sorting!] (rum/use-state [{:id :block/updated-at, :asc? false}])
+        [filters set-filters!] (rum/use-state [])
+        [row-filter set-row-filter!] (rum/use-state nil)
+        [visible-columns set-visible-columns!] (rum/use-state {})
+        [row-selection set-row-selection!] (rum/use-state {})
+        [data set-data!] (rum/use-state (get-all-objects class))
+        _ (rum/use-effect!
+           (fn []
+             ;; (when-let [^js worker @state/*db-worker]
+             ;;   (p/let [result-str (.get-page-refs-count worker (state/get-current-repo))
+             ;;           result (ldb/read-transit-str result-str)
+             ;;           data (map (fn [row] (assoc row :block.temp/refs-count (get result (:db/id row) 0))) data)]
+             ;;     (set-data! data)))
+             )
+           [])
+        columns (build-columns class config)
+        table (shui/table-option {:data data
+                                  :columns columns
+                                  :state {:sorting sorting
+                                          :filters filters
+                                          :row-filter row-filter
+                                          :row-selection row-selection
+                                          :visible-columns visible-columns}
+                                  :data-fns {:set-filters! set-filters!
+                                             :set-sorting! set-sorting!
+                                             :set-visible-columns! set-visible-columns!
+                                             :set-row-selection! set-row-selection!}})
+        selected-rows (shui/table-get-selection-rows row-selection (:rows table))
+        selected-rows-count (count selected-rows)
+        selected? (pos? selected-rows-count)]
+    (rum/use-effect!
+     (fn []
+       (set-row-filter! (fn []
+                          (fn [row]
+                            (row-matched? row input filters)))))
+     [input filters])
+    [:div.w-full.flex.flex-col.gap-2
+     [:div.flex.items-center.justify-between
+      [:div.flex.flex-row.items-center.gap-2
+       [:div.font-medium (str (count data) " Objects")]]
+      [:div.flex.items-center.gap-1
+
+       (filter-properties columns table)
+
+       (search input {:on-change set-input!})
+
+       (more-actions columns table)]]
+
+     (filters-row table)
+
+     (let [columns' (:columns table)
+           rows (:rows table)]
+       [:div.rounded-md.border.content
+        (ui/virtualized-table
+         {:custom-scroll-parent (gdom/getElement "main-content-container")
+          :total-count (count rows)
+          :fixedHeaderContent (fn [] (table-header table columns'))
+          :components {:Table (fn [props]
+                                (shui/table {}
+                                            (.-children props)))
+                       :TableRow (fn [props] (table-row table rows columns' props))}})])
+
+     (let [rows-count (count (:rows table))]
+       [:div.flex.items-center.justify-end.space-x-2.py-4
+        [:div.flex-1.text-sm.text-muted-foreground
+         (if (pos? selected-rows-count)
+           (str selected-rows-count " of " rows-count " row(s) selected.")
+           (str "Total: " rows-count))]])]))
+
+(rum/defcs objects < mixins/container-id
+  [state class]
+  (objects-inner {:container-id (:container-id state)} class))

+ 11 - 7
src/main/frontend/components/page.cljs

@@ -11,8 +11,8 @@
             [frontend.components.icon :as icon-component]
             [frontend.components.db-based.page :as db-page]
             [frontend.components.class :as class-component]
-            [frontend.handler.db-based.property :as db-property-handler]
             [frontend.components.svg :as svg]
+            [frontend.components.objects :as objects]
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
@@ -23,6 +23,7 @@
             [frontend.extensions.graph :as graph]
             [frontend.extensions.pdf.utils :as pdf-utils]
             [frontend.format.mldoc :as mldoc]
+            [frontend.handler.db-based.property :as db-property-handler]
             [frontend.handler.common :as common-handler]
             [frontend.handler.config :as config-handler]
             [frontend.handler.dnd :as dnd]
@@ -474,10 +475,10 @@
   (rum/local false ::main-ready?)
   {:did-mount (fn [state]
                 (assoc state ::main-ready-timer
-                  (js/setTimeout #(reset! (::main-ready? state) true) 32)))
+                       (js/setTimeout #(reset! (::main-ready? state) true) 32)))
    :will-unmount (fn [state]
                    (some-> (::main-ready-timer state)
-                     (js/clearTimeout))
+                           (js/clearTimeout))
                    state)}
   [state {:keys [repo page-name preview? sidebar? linked-refs? unlinked-refs? config] :as option}]
   (when-let [path-page-name (get-path-page-name state page-name)]
@@ -568,9 +569,13 @@
             (when today?
               (scheduled/scheduled-and-deadlines page-name))
 
-            (when-not block?
+            (when (and (not block?) (not db-based?))
               (tagged-pages repo page page-original-name))
 
+            (when (and db-based? (ldb/class? page))
+              [:div.mt-8
+               (objects/objects page)])
+
             ;; referenced blocks
             (when-not block-or-whiteboard?
               (when (and page (not (false? linked-refs?)))
@@ -587,10 +592,9 @@
                 (hierarchy/structures route-page-name)))
 
             (when (and (not (false? unlinked-refs?))
-                    (not (or block-or-whiteboard? sidebar? home?)))
+                       (not (or block-or-whiteboard? sidebar? home?)))
               [:div {:key "page-unlinked-references"}
-               (reference/unlinked-references page)])])
-         ]))))
+               (reference/unlinked-references page)])])]))))
 
 (rum/defcs page-aux < rum/reactive db-mixins/query
   {:init (fn [state]

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

@@ -6,6 +6,7 @@
             [frontend.config :as config]
             [frontend.context.i18n :refer [t]]
             [frontend.db :as db]
+            [logseq.db :as ldb]
             [frontend.db-mixins :as db-mixins]
             [frontend.db.async :as db-async]
             [frontend.db.utils :as db-utils]
@@ -64,7 +65,7 @@
   [page-entity page-name *filters total filter-n filtered-ref-blocks *ref-pages]
   (let [filters @*filters
         threshold (state/get-linked-references-collapsed-threshold)
-        default-collapsed? (>= total threshold)
+        default-collapsed? (or (>= total threshold) (ldb/class? page-entity))
         *collapsed? (atom nil)]
     (ui/foldable
      [:div.flex.flex-row.flex-1.justify-between.items-center
@@ -79,10 +80,10 @@
         :on-pointer-down (fn [e]
                            (util/stop-propagation e)
                            (shui/popup-show! (.-target e)
-                            (fn []
-                              [:div.p-4
-                               (filters/filter-dialog page-entity *filters *ref-pages)])
-                            {:align "end"}))}
+                                             (fn []
+                                               [:div.p-4
+                                                (filters/filter-dialog page-entity *filters *ref-pages)])
+                                             {:align "end"}))}
        (ui/icon "filter" {:class (cond
                                    (and (empty? (:included filters)) (empty? (:excluded filters)))
                                    "opacity-60 hover:opacity-100"

+ 3 - 3
src/main/frontend/components/select.cljs

@@ -139,15 +139,15 @@
                                                        (if (selected-choices chosen)
                                                          (do
                                                            (swap! *selected-choices disj chosen)
-                                                           (when on-chosen (on-chosen chosen false)))
+                                                           (when on-chosen (on-chosen chosen false @*selected-choices)))
                                                          (do
                                                            (swap! *selected-choices conj chosen)
-                                                           (when on-chosen (on-chosen chosen true))))
+                                                           (when on-chosen (on-chosen chosen true @*selected-choices))))
                                                        (do
                                                          (when (and close-modal? (not multiple-choices?))
                                                            (state/close-modal!))
                                                          (when on-chosen
-                                                           (on-chosen chosen true))))))
+                                                           (on-chosen chosen true @*selected-choices))))))
                               :empty-placeholder (empty-placeholder t)})]
 
                            (when (and multiple-choices? (fn? on-apply))

+ 2 - 96
src/main/frontend/components/table.css

@@ -1,97 +1,3 @@
-.cp__shortcut-page, .cp__all_pages-content, .block-content {
-  div.table-wrapper {
-    overflow: auto;
-  }
-
-  table {
-    width: 100%;
-    border-collapse: collapse;
-    text-align: left;
-    margin: 1rem 0;
-  }
-
-  th {
-    font-size: 14px;
-    font-weight: 400;
-    color: var(--ls-primary-text-color);
-    border-bottom: 2px solid var(--ls-border-color, var(--rx-gray-05));
-    padding: 10px 8px;
-  }
-
-  td {
-    padding: 6px 8px;
-    text-align: left;
-  }
-
-  tr:nth-child(even) {
-    background: var(--ls-table-tr-even-background-color, var(--rx-gray-03));
-  }
-
-  tr:nth-child(odd) {
-    background: var(--ls-primary-background-color, var(--rx-gray-01));
-  }
-
-  caption {
-    margin-bottom: 0.3em;
-
-    &.t-above {
-      caption-side: top;
-    }
-
-    &.t-bottom {
-      caption-side: bottom;
-    }
-  }
-
-  figcaption {
-    margin-top: 0.3em;
-  }
-
-  .org-right {
-    text-align: right;
-  }
-
-  .org-left {
-    text-align: left;
-  }
-
-  .org-center {
-    text-align: center;
-  }
-}
-
-.cp__all_pages_table {
-  td {
-    @apply whitespace-nowrap;
-
-    &.name {
-      @apply whitespace-pre-wrap;
-    }
-
-    &.backlinks {
-      @apply text-center;
-    }
-  }
-}
-
-.dark-theme {
-  .cp__shortcut-page, .cp__all_pages-content, .block-content {
-    th {
-      color: var(--ls-primary-text-color);
-      border-bottom: 2px solid var(--ls-border-color);
-    }
-
-    tr:nth-child(even) {
-      background: var(--ls-table-tr-even-background-color);
-    }
-
-    tr:nth-child(odd) {
-      background: var(--ls-primary-background-color);
-    }
-
-    td,
-    tr {
-      border-bottom: none;
-    }
-  }
+.primary-cell {
+    @apply min-w-[140px];
 }

+ 9 - 0
src/main/frontend/ui.cljs

@@ -1111,6 +1111,15 @@
       :intent "link"
       :small? true)]]))
 
+(rum/defc tooltip
+  [trigger tooltip-content]
+  (shui/tooltip-provider
+   (shui/tooltip
+    (shui/tooltip-trigger
+     trigger)
+    (shui/tooltip-content
+     tooltip-content))))
+
 (comment
   (rum/defc emoji-picker
    [opts]

+ 0 - 10
src/test/frontend/db/db_based_model_test.cljs

@@ -72,16 +72,6 @@
       (is (= (set (map :db/id classes))
              #{(:db/id class1) (:db/id class2)})))))
 
-(deftest get-tag-blocks-test
-  (let [opts {:redirect? false :create-first-block? false :class? true}
-        _ (test-helper/create-page! "class1" opts)
-        _ (test-helper/save-block! repo fbid "Block 1" {:tags ["class1"]})
-        _ (test-helper/save-block! repo sbid "Block 2" {:tags ["class1"]})]
-    (is
-     (= (ldb/get-tag-blocks (db/get-db) "class1")
-        [(:db/id (db/entity [:block/uuid fbid]))
-         (:db/id (db/entity [:block/uuid sbid]))]))))
-
 (deftest hidden-page-test
   (let [opts {:redirect? false :create-first-block? false}
         _ (test-helper/create-page! "page 1" opts)]