diff --git a/src/main/frontend/components/selection.cljs b/src/main/frontend/components/selection.cljs index 7176b78cb4..f121fbd841 100644 --- a/src/main/frontend/components/selection.cljs +++ b/src/main/frontend/components/selection.cljs @@ -1,13 +1,58 @@ (ns frontend.components.selection "Block selection" - (:require [frontend.db :as db] + (:require [frontend.components.icon :as icon-component] + [frontend.db :as db] + [frontend.handler.db-based.property :as db-property-handler] [frontend.handler.editor :as editor-handler] + [frontend.handler.property :as property-handler] [frontend.state :as state] [frontend.ui :as ui] [frontend.util :as util] [logseq.shui.ui :as shui] [rum.core :as rum])) +(defn- icon-data-for-block + "Compute per-block icon-data. For avatar/text, replace the picker's + preview-derived :value with initials derived from each block's own + title — otherwise every batched row would show the first row's initials." + [icon block] + (when icon + (let [block-title (:block/title block)] + (cond + (= :avatar (:type icon)) + (let [colors (select-keys (:data icon) [:backgroundColor :color])] + {:type :avatar + :data (cond-> colors + block-title (assoc :value (icon-component/derive-avatar-initials block-title)))}) + + (= :text (:type icon)) + (let [colors (select-keys (:data icon) [:color])] + {:type :text + :data (cond-> colors + block-title (assoc :value (icon-component/derive-initials block-title)))}) + + (= :image (:type icon)) + {:type :image :data (:data icon)} + + :else (select-keys icon [:type :id :color]))))) + +(defn- batch-write-icon! + [blocks icon] + (if (or (nil? icon) (contains? #{:avatar :text} (:type icon))) + ;; Per-block writes (avatar/text need per-row initials; clear/nil applies uniformly) + (doseq [block blocks] + (if (nil? icon) + (property-handler/remove-block-property! (:db/id block) :logseq.property/icon) + (db-property-handler/set-block-property! + (:db/id block) + :logseq.property/icon + (icon-data-for-block icon block)))) + ;; Uniform value across blocks (icon, emoji, image): single batch transaction + (property-handler/batch-set-block-property! + (map :db/id blocks) + :logseq.property/icon + (icon-data-for-block icon (first blocks))))) + (rum/defc action-bar < rum/reactive [& {:keys [on-cut on-copy selected-blocks hide-dots? button-border? view-parent] :or {on-cut #(editor-handler/cut-selection-blocks true)}}] @@ -35,6 +80,31 @@ :on-dialog-close #(state/pub-event! [:editor/hide-action-bar])}]))) (ui/tooltip (ui/icon "hash" {:size 13}) "Set tag" {:trigger-props {:class "flex"}})) + ;; set icon (batch) + (shui/button + (assoc button-opts + :on-pointer-down (fn [^js e] + (util/stop e) + (let [target (.-target e) + first-title (some-> selected-blocks first :block/title)] + (shui/popup-show! + target + (fn [{:keys [id]}] + (icon-component/icon-search + {:on-chosen (fn [_e icon-value keep-popup?] + (batch-write-icon! selected-blocks icon-value) + (when-not (true? keep-popup?) + (shui/popup-hide! id) + (state/pub-event! [:editor/hide-action-bar]))) + :icon-value nil + :page-title first-title + :del-btn? false})) + {:align :start + :id :ls-icon-picker + :content-props {:class "ls-icon-picker" + :onEscapeKeyDown #(.preventDefault %)}})))) + (ui/tooltip (ui/icon "mood-smile" {:size 13}) "Set icon" + {:trigger-props {:class "flex"}})) (shui/button (assoc button-opts :on-pointer-down (fn [e] diff --git a/src/main/frontend/components/table.css b/src/main/frontend/components/table.css index 10e6b736a7..7560b26b00 100644 --- a/src/main/frontend/components/table.css +++ b/src/main/frontend/components/table.css @@ -46,6 +46,14 @@ .multi-values { @apply !flex-nowrap; } + + .table-row-icon { + @apply cursor-pointer rounded transition-colors duration-100; + + &:hover { + background: var(--rx-gray-04); + } + } } .ls-table-header { diff --git a/src/main/frontend/components/views.cljs b/src/main/frontend/components/views.cljs index c7b6a50899..761f15666e 100644 --- a/src/main/frontend/components/views.cljs +++ b/src/main/frontend/components/views.cljs @@ -241,7 +241,7 @@ (rum/defc ^:large-vars/cleanup-todo block-title < rum/static "Used on table view" - [block* {:keys [create-new-block width row property]}] + [block* {:keys [create-new-block width row property property-ident]}] (let [*ref (hooks/use-ref nil) [opacity set-opacity!] (hooks/use-state 0) [focus-timeout set-focus-timeout!] (hooks/use-state nil) @@ -303,9 +303,45 @@ (save-block-and-focus *ref set-focus-timeout! false))}) (editor-handler/edit-block! block :max {:container-id :unknown-container})))))))} (if block - [:div.flex.flex-row + [:div.flex.flex-row.items-center.gap-2.min-w-0 + (when (and (= property-ident :block/title) (not many?)) + (when-let [icon-el (icon-component/get-node-icon-cp block* {:size 16 :color? true})] + [:div.table-row-icon.flex-shrink-0.flex.items-center.justify-center + {:style {:width 20 :height 20} + :on-click (fn [^js e] + (util/stop-propagation e) + (let [own-icon (:logseq.property/icon block*)] + (shui/popup-show! + (.-currentTarget e) + (fn [{:keys [id]}] + (icon-component/icon-search + {:on-chosen (fn [_e icon-value keep-popup?] + (let [icon-data (when icon-value + (cond + (= :text (:type icon-value)) {:type :text :data (:data icon-value)} + (= :avatar (:type icon-value)) {:type :avatar :data (:data icon-value)} + (= :image (:type icon-value)) {:type :image :data (:data icon-value)} + :else (select-keys icon-value [:type :id :color])))] + (if icon-data + (property-handler/set-block-property! + (:db/id block*) + :logseq.property/icon + icon-data) + (property-handler/remove-block-property! + (:db/id block*) + :logseq.property/icon))) + (when-not (true? keep-popup?) + (shui/popup-hide! id))) + :icon-value own-icon + :page-title (:block/title block*) + :del-btn? (some? own-icon)})) + {:align :start + :id :ls-icon-picker + :content-props {:class "ls-icon-picker" + :onEscapeKeyDown #(.preventDefault %)}})))} + icon-el])) (let [render (fn [block] - [:div + [:div.min-w-0 (inline-title {:table? true :block/uuid (:block/uuid block)}