瀏覽代碼

enhance(ux): search support both recent search and updates

Tienson Qin 4 月之前
父節點
當前提交
26113f47c7

+ 9 - 8
deps/db/src/logseq/db/common/initial_data.cljs

@@ -302,14 +302,15 @@
 
 (defn get-recent-updated-pages
   [db]
-  (some->>
-   (d/datoms db :avet :block/updated-at)
-   rseq
-   (keep (fn [datom]
-           (let [e (d/entity db (:e datom))]
-             (when (and (common-entity-util/page? e) (not (entity-util/hidden? e)))
-               e))))
-   (take 30)))
+  (when db
+    (some->>
+     (d/datoms db :avet :block/updated-at)
+     rseq
+     (keep (fn [datom]
+             (let [e (d/entity db (:e datom))]
+               (when (and (common-entity-util/page? e) (not (entity-util/hidden? e)))
+                 e))))
+     (take 30))))
 
 (defn get-initial-data
   "Returns current database schema and initial data.

+ 73 - 5
src/main/capacitor/components/search.cljs

@@ -5,9 +5,14 @@
             [clojure.string :as string]
             [dommy.core :as dom]
             [frontend.components.cmdk.core :as cmdk]
+            [frontend.db :as db]
+            [frontend.handler.block :as block-handler]
+            [frontend.handler.search :as search-handler]
             [frontend.search :as search]
             [frontend.state :as fstate]
             [frontend.ui :as ui]
+            [frontend.util :as util]
+            [logseq.db :as ldb]
             [logseq.shui.hooks :as hooks]
             [promesa.core :as p]
             [rum.core :as rum]))
@@ -26,10 +31,47 @@
                           (cmdk/block-item repo block nil input))) blocks)]
     items))
 
+(defn- get-recent-pages
+  []
+  (let [recent-pages (->> (ldb/get-recent-updated-pages (db/get-db))
+                          (remove ldb/built-in?))]
+    (map (fn [block]
+           (let [text (block-handler/block-unique-title block)
+                 icon (cmdk/get-page-icon block)]
+             {:icon icon
+              :icon-theme :gray
+              :text text
+              :source-block block}))
+         recent-pages)))
+
 (rum/defc search
   []
   (let [*ref (hooks/use-ref nil)
-        [search-result set-search-result!] (hooks/use-state nil)]
+        [input set-input!] (hooks/use-state "")
+        [search-result set-search-result!] (hooks/use-state nil)
+        [last-input-at set-last-input-at!] (hooks/use-state nil)
+        [recents set-recents!] (hooks/use-state (search-handler/get-recents))
+        result (if (string/blank? input)
+                 (get-recent-pages)
+                 search-result)]
+    (hooks/use-effect!
+     (fn []
+       (let [*timeout (atom nil)]
+         (when-not (string/blank? input)
+           (p/let [result (search-blocks input)]
+             (set-search-result! result)
+             (when (seq result)
+               (reset! *timeout
+                       (js/setTimeout
+                        (fn []
+                          (let [now (util/time-ms)]
+                            (when (and last-input-at (>= (- now last-input-at) 2000))
+                              (search-handler/add-recent! input)
+                              (set-recents! (search-handler/get-recents)))))
+                        2000)))))
+         #(when-let [timeout @*timeout]
+            (js/clearTimeout timeout))))
+     [(hooks/use-debounced-value input 150)])
     (ion/page
      {:id "search-tab"}
      (ion/header
@@ -38,14 +80,40 @@
         {:ref *ref
          :slot "start"
          :placeholder "Search"
+         :value input
          :on-ion-input (fn [^js e]
                          (let [input (.-value (.-detail e))]
-                           (when-not (string/blank? input)
-                             (p/let [result (search-blocks input)]
-                               (set-search-result! result)))))})))
+                           (set-input! input)
+                           (set-last-input-at! (util/time-ms))))})))
      (ion/content
+      (when (string/blank? input)
+        [:<>
+         [:div.mb-4
+          [:div.px-4.text-sm.text-muted-foreground.border-b
+           [:div.flex.flex-item.items-center.justify-between
+            [:div "Recent search"]
+            (ion/button
+             {:fill "clear"
+              :size "small"
+              :mode "ios"
+              :class "text-muted-foreground"
+              :on-click (fn []
+                          (search-handler/clear-recents!)
+                          (set-recents! nil))}
+             "Clear all")]]
+          (ion/list
+           (for [item recents]
+             (ion/item
+              {:on-click #(set-input! item)}
+              [:div.flex.flex-row.items-center.gap-1
+               (ui/icon "search" {:size 14
+                                  :class "text-muted-foreground"})
+               item])))]
+
+         [:div.px-4.py-2.text-sm.text-muted-foreground.border-b
+          "Recent updates"]])
       (ion/list
-       (for [{:keys [icon text header source-page source-block]} search-result]
+       (for [{:keys [icon text header source-page source-block]} result]
          (let [block (or source-page source-block)]
            (ion/item
             {:on-click (fn []

+ 1 - 1
src/main/frontend/components/cmdk/core.cljs

@@ -194,7 +194,7 @@
 ;; Each result group has it's own load-results function
 (defmulti load-results (fn [group _state] group))
 
-(defn- get-page-icon
+(defn get-page-icon
   [entity]
   (cond
     (ldb/class? entity)

+ 22 - 6
src/main/frontend/handler/search.cljs

@@ -1,17 +1,18 @@
 (ns frontend.handler.search
   "Provides util handler fns for search"
   (:require [clojure.string :as string]
-            [frontend.config :as config]
+            [dommy.core :as dom]
+            [electron.ipc :as ipc]
             [frontend.common.search-fuzzy :as fuzzy]
+            [frontend.config :as config]
             [frontend.db :as db]
             [frontend.handler.notification :as notification]
             [frontend.search :as search]
             [frontend.state :as state]
+            [frontend.storage :as storage]
             [frontend.util :as util]
-            [promesa.core :as p]
             [logseq.graph-parser.text :as text]
-            [electron.ipc :as ipc]
-            [dommy.core :as dom]))
+            [promesa.core :as p]))
 
 (defn sanity-search-content
   "Convert a block to the display contents for searching"
@@ -84,9 +85,9 @@
 (defn loop-find-in-page!
   [backward?]
   (if (and (get-in @state/state [:ui/find-in-page :active?])
-             (not (state/editing?)))
+           (not (state/editing?)))
     (do (state/set-state! [:ui/find-in-page :backward?] backward?)
-      (debounced-search))
+        (debounced-search))
     ;; return false to skip prevent default event behavior (Enter key)
     false))
 
@@ -157,3 +158,18 @@
                                         result)))
                              (conj result [:span content])))]
             [:p {:class "m-0"} elements]))))))
+
+(defn get-recents
+  []
+  (storage/get :recent-search-items))
+
+(defn add-recent!
+  [item]
+  (when-not (string/blank? item)
+    (let [recents (get-recents)]
+      (storage/set :recent-search-items
+                   (distinct (take 20 (cons item recents)))))))
+
+(defn clear-recents!
+  []
+  (storage/remove :recent-search-items))