fix: debounce search results

This commit is contained in:
Tienson Qin
2026-05-06 20:50:37 +08:00
parent e47539398b
commit 5bc8fafb1b
7 changed files with 225 additions and 73 deletions

View File

@@ -197,7 +197,8 @@
group-key
(if (= group-key :create)
(count group-items)
(count (get-in results [group-key :items])))
(or (get-in results [group-key :matched-count])
(count (get-in results [group-key :items]))))
(mapv #(assoc % :group group-key :item-index (vswap! index inc)) group-items)])))
(defn state->highlighted-item
@@ -383,19 +384,31 @@
(= page-id current-page-uuid))
:source-block block}))
(defn- block-search-result->items
[result]
(if (map? result)
{:blocks (:items result)
:matched-count (or (:matched-count result)
(count (:items result)))}
{:blocks result
:matched-count (count result)}))
;; The blocks search action uses an existing handler
(defmethod load-results :nodes [group state]
(let [!input (::input state)
!results (::results state)
repo (state/get-current-repo)
current-page-uuid (page-util/get-current-page-uuid)
expanded? (::expanded? state)
opts (cmdk-state/cmdk-block-search-options
{:filter-group :nodes
:dev? config/dev?
:action (get-action)})]
:action (get-action)
:expanded? expanded?})]
(swap! !results assoc-in [group :status] :loading)
(swap! !results assoc-in [:current-page :status] :loading)
(p/let [blocks (search/block-search repo @!input opts)
(p/let [search-result (search/block-search repo @!input opts)
{:keys [blocks matched-count]} (block-search-result->items search-result)
blocks (remove nil? blocks)
items (keep (fn [block]
(if (:page? block)
@@ -403,8 +416,14 @@
(block-item repo block current-page-uuid @!input))) blocks)]
(if (= group :current-page)
(let [items-on-current-page (filter :current-page? items)]
(swap! !results update group merge {:status :success :items items-on-current-page}))
(swap! !results update group merge {:status :success :items items})))))
(swap! !results update group merge {:status :success
:items items-on-current-page
:matched-count (count items-on-current-page)
:has-more? false}))
(swap! !results update group merge {:status :success
:items items
:matched-count matched-count
:has-more? (> matched-count (count items))})))))
(defmethod load-results :codes [group state]
(let [!input (::input state)
@@ -478,13 +497,16 @@
(let [!results (::results state)
!input (::input state)
repo (state/get-current-repo)
expanded? (::expanded? state)
opts (cmdk-state/cmdk-block-search-options
{:filter-group :current-page
:dev? config/dev?
:page-uuid (:block/uuid current-page)})]
:page-uuid (:block/uuid current-page)
:expanded? expanded?})]
(swap! !results assoc-in [group :status] :loading)
(swap! !results assoc-in [:current-page :status] :loading)
(p/let [blocks (search/block-search repo @!input opts)
(p/let [search-result (search/block-search repo @!input opts)
{:keys [blocks matched-count]} (block-search-result->items search-result)
blocks (remove nil? blocks)
items (map (fn [block]
(let [id (if (uuid? (:block/uuid block))
@@ -497,7 +519,10 @@
:result-type (if (:page? block) :page :block)
:current-page? true
:source-block block})) blocks)]
(swap! !results update :current-page merge {:status :success :items items})))
(swap! !results update :current-page merge {:status :success
:items items
:matched-count matched-count
:has-more? (> matched-count (count items))})))
(reset! (::filter state) nil)))
;; The default load-results function triggers all the other load-results function
@@ -838,14 +863,20 @@
nil)]
[:div {:data-item-index item-idx}
(if (= group :nodes)
(ui/lazy-visible (fn [] item) {:root scroll-root
:root-margin "500px 0px"})
item)]))
(ui/lazy-visible (fn [] item) {:root scroll-root
:root-margin "500px 0px"})
item)]))
(defn- show-more-results!
[state group]
(swap! (::results state) assoc-in [group :show] :more)
(when (contains? #{:nodes :current-page} group)
(load-results group (assoc state ::expanded? true))))
(rum/defcs result-group
< rum/reactive
[state' state title group visible-items first-item sidebar?]
(let [{:keys [show items]} (some-> state ::results deref group)
(let [{:keys [show items matched-count has-more?]} (some-> state ::results deref group)
focus-source @(::focus-source state)
highlighted-item (or @(::highlighted-item state)
(when (= :keyboard focus-source) first-item))
@@ -853,9 +884,10 @@
input @(::input state)
filter' @(::filter state)
can-show-less? (< (get-group-limit group) (count visible-items))
can-show-more? (< (count visible-items) (count items))
can-show-more? (or has-more?
(< (count visible-items) (count items)))
show-less #(swap! (::results state) assoc-in [group :show] :less)
show-more #(swap! (::results state) assoc-in [group :show] :more)]
show-more #(show-more-results! state group)]
[:div {:class (if (= group :create)
"border-b border-gray-06 last:border-b-0"
"border-b border-gray-06 pb-1 last:border-b-0")}
@@ -864,15 +896,17 @@
[:div {:class "font-bold text-gray-11 pl-0.5 cursor-pointer select-none"
:on-click (fn [_e]
;; change :less to :more or :more to :less
(swap! (::results state) update-in [group :show] {:more :less
:less :more}))}
(if (= show :more)
(show-less)
(show-more)))}
title]
(when (not= group :create)
[:div {:class "pl-1.5 text-gray-12 rounded-full"
:style {:font-size "0.7rem"}}
(if (<= 100 (count items))
"99+"
(count items))])
(let [display-count (or matched-count (count items))]
[:div {:class "pl-1.5 text-gray-12 rounded-full"
:style {:font-size "0.7rem"}}
(if (<= 99 display-count)
"99+"
display-count)]))
[:div {:class "flex-1"}]
@@ -946,9 +980,15 @@
(reset! (::pending-scroll-item-idx state) nil)
(reset! (::highlighted-item state) nil)))))
(defn- refresh-results!
[state]
(persist-cmdk-query-state! state)
(load-results :default state))
(defn handle-input-change
([state e] (handle-input-change state e (.. e -target -value)))
([state e input]
([state e] (handle-input-change state e (.. e -target -value) true))
([state e input] (handle-input-change state e input true))
([state e input refresh?]
(let [composing? (util/native-event-is-composing? e)
e-type (gobj/getValueByKeys e "type")
composing-end? (= e-type "compositionend")
@@ -964,9 +1004,8 @@
(when container
(set! (.-scrollTop container) 0))
;; retrieve the load-results function and update all the results
(when (or (not composing?) composing-end?)
(persist-cmdk-query-state! state)
(load-results :default state)))))
(when (and refresh? (or (not composing?) composing-end?))
(refresh-results! state)))))
(defn- open-current-item-link
"Opens a link for the current item if a page or block. For pages, opens the
@@ -1013,7 +1052,7 @@
(swap! (::results state) assoc-in [highlighted-group :show] :less)))
show-more (fn []
(when highlighted-group
(swap! (::results state) assoc-in [highlighted-group :show] :more)))
(show-more-results! state highlighted-group)))
input @(::input state)
as-keydown? (or (= keyname "ArrowDown") (and ctrl? (= keyname "n")))
as-keyup? (or (= keyname "ArrowUp") (and ctrl? (= keyname "p")))]
@@ -1098,17 +1137,11 @@
(let [highlighted-item @(::highlighted-item state)
input @(::input state)
input-ref (::input-ref state)
debounced-on-change (hooks/use-callback
(gfun/debounce
(fn [e]
(let [new-value (.-value (.-target e))]
(handle-input-change state e)
(when-let [on-change (:on-input-change opts)]
(on-change new-value))))
200)
[])
debounced-composition-end (hooks/use-callback
(gfun/debounce (fn [e] (handle-input-change state e)) 100)
debounced-refresh-results (hooks/use-callback
(gfun/debounce
(fn []
(refresh-results! state))
150)
[])]
(hooks/use-effect! (fn []
(reset! (::all-items-cache state) (vec all-items))
@@ -1142,11 +1175,20 @@
:autoCapitalize "off"
:placeholder (input-placeholder)
:ref #(when-not @input-ref (reset! input-ref %))
:on-change debounced-on-change
:on-change (fn [e]
(let [new-value (.-value (.-target e))
composing? (util/native-event-is-composing? e)]
(handle-input-change state e new-value false)
(when-not composing?
(debounced-refresh-results))
(when-let [on-change (:on-input-change opts)]
(on-change new-value))))
:on-blur (fn [_e]
(when-let [on-blur (:on-input-blur opts)]
(on-blur input)))
:on-composition-end debounced-composition-end
:on-composition-end (fn [e]
(handle-input-change state e (.. e -target -value) false)
(debounced-refresh-results))
:default-value input}]]))
(defn- tip-with-shortcut

View File

@@ -70,21 +70,27 @@
(save-last-cmdk-search! repo input (:group filter-state))))
(defn cmdk-block-search-options
[{:keys [filter-group dev? action page-uuid]}]
(let [nodes-base {:limit 100
[{:keys [filter-group dev? action page-uuid expanded?]}]
(let [nodes-limit (if expanded? 100 10)
nodes-base {:limit nodes-limit
:search-limit 100
:dev? dev?
:built-in? true
:enable-snippet? true}]
:enable-snippet? true
:include-matched-count? true}]
(case filter-group
:code
(assoc nodes-base
:limit 20
;; larger limit for code search since most of the results will be filtered out
:search-limit 300
:code-only? true)
:current-page
(cond-> {:limit 100
:enable-snippet? true}
(cond-> {:limit nodes-limit
:search-limit 100
:enable-snippet? true
:include-matched-count? true}
page-uuid
(assoc :page (str page-uuid)))

View File

@@ -307,12 +307,23 @@
;; TODO: use rum/use-effect instead
(rum/defcs block-search-auto-complete < rum/reactive
{:init (fn [state]
(let [result (atom nil)]
(search-blocks! state result)
(assoc state ::result result)))
(let [result (atom nil)
[debounced-search stop-search!] (util/cancelable-debounce search-blocks! 150)]
(debounced-search state result)
(assoc state
::result result
::debounced-search debounced-search
::stop-search! stop-search!)))
:did-update (fn [state]
(search-blocks! state (::result state))
state)}
(let [[_edit-block _ _ q] (:rum/args state)]
(if (string/blank? q)
(reset! (::result state) nil)
((::debounced-search state) state (::result state))))
state)
:will-unmount (fn [state]
(when-let [stop-search! (::stop-search! state)]
(stop-search!))
state)}
[state _edit-block input id q format selected-text]
(let [result (->> (rum/react (get state ::result))
(remove (fn [b] (nil? (:block/uuid b)))))

View File

@@ -1542,6 +1542,8 @@
[q & [{:keys [nlp-pages? page-only?]}]]
(p/let [block (state/get-edit-block)
result (search/block-search (state/get-current-repo) q {:built-in? false
:limit 20
:search-limit 100
:enable-snippet? false
:page-only? page-only?})
matched (remove (fn [b] (= (:block/uuid b) (:block/uuid block))) result)

View File

@@ -584,21 +584,42 @@ DROP TRIGGER IF EXISTS blocks_au;
(when (include-search-block? conn block code-class option)
(let [display-title (if (:enable-snippet? option)
(ensure-highlighted-snippet snippet title q)
(or snippet title))]
{:db/id (:db/id block)
:block/uuid (:block/uuid block)
:block/title display-title
:block.temp/original-title (:block/title block)
:block/page (or
(:block/uuid (:block/page block))
(when (and page (common-util/uuid-string? page))
(uuid page)))
:block/parent (:db/id (:block/parent block))
:block/tags (seq (map :db/id (:block/tags block)))
:logseq.property/icon (:logseq.property/icon block)
:page? (ldb/page? block)
:alias (some-> (first (:block/_alias block))
(select-keys [:block/uuid :block/title]))})))))
(or snippet title))
block-page (or
(:block/uuid (:block/page block))
(when (and page (common-util/uuid-string? page))
(uuid page)))
parent-id (:db/id (:block/parent block))
tag-ids (seq (map :db/id (:block/tags block)))
icon (:logseq.property/icon block)
alias (some-> (first (:block/_alias block))
(select-keys [:block/uuid :block/title]))]
(cond-> {:db/id (:db/id block)
:block/uuid (:block/uuid block)
:block/title display-title
:block.temp/original-title (:block/title block)
:page? (ldb/page? block)}
block-page
(assoc :block/page block-page)
parent-id
(assoc :block/parent parent-id)
tag-ids
(assoc :block/tags tag-ids)
icon
(assoc :logseq.property/icon icon)
alias
(assoc :alias alias)))))))
(defn- search-result-visible?
[conn code-class option {:keys [id] :as result}]
(let [block-id (uuid id)]
(when-let [block (or (get result search-result-block-key)
(d/entity @conn [:block/uuid block-id]))]
(include-search-block? conn block code-class option))))
(defn search-blocks
"Options:
@@ -609,7 +630,7 @@ DROP TRIGGER IF EXISTS blocks_au;
* :dev? - Allow all nodes to be seen for development. Defaults to false
* :code-only? - Whether to return only code blocks. Defaults to false
* :built-in? - Whether to return public built-in nodes for db graphs. Defaults to false"
[conn search-db q {:keys [limit search-limit page enable-snippet? page-only? code-only?]
[conn search-db q {:keys [limit search-limit page enable-snippet? page-only? code-only? include-matched-count?]
:as option
:or {enable-snippet? true}}]
(when-not (string/blank? q)
@@ -639,10 +660,15 @@ DROP TRIGGER IF EXISTS blocks_au;
combined-result (combine-results @conn (concat fuzzy-result matched-result non-match-result))
code-class (when code-only?
(d/entity @conn :logseq.class/Code-block))
matched-count (when include-matched-count?
(count (filter #(search-result-visible? conn code-class option %) combined-result)))
result (->> combined-result
(common-util/distinct-by :id)
(keep #(search-result->block-result conn q code-class option %)))]
(take limit result))))
(if include-matched-count?
{:items (take limit result)
:matched-count matched-count}
(take limit result)))))
(defn truncate-table!
[db]