Ben Yorke 2 лет назад
Родитель
Сommit
24006b3d82

+ 1 - 1
deps/shui/src/logseq/shui/button/v2.cljs

@@ -10,7 +10,7 @@
 (rum/defc root 
   [{:keys [theme text depth size icon interactive shortcut tiled on-click] :or {theme :color depth 1 size :md interactive true}} context]
   (let [theme-class (str "shui__button-theme-" (name theme))
-        depth-class (str "shui__button-depth-" depth) 
+        depth-class (when-not (= :text theme) (str "shui__button-depth-" depth)) 
         color-class (str "shui__button-color-" (some-> context :state deref :ui/radix-color name))
         size-class  (str "shui__button-size-" (name size))
         tiled-class (when tiled "shui__button-tiled")]

+ 4 - 2
deps/shui/src/logseq/shui/list_item/v1.cljs

@@ -24,17 +24,19 @@
     (name key)))
 
 ;; result-item
-(rum/defc root [{:keys [icon icon-theme text info shortcut value-label value title highlighted on-highlight on-highlight-dep header]}]
+(rum/defc root [{:keys [icon icon-theme text info shortcut value-label value title highlighted on-highlight on-highlight-dep header on-click]}]
   (let [ref (rum/create-ref)]
     (rum/use-effect! 
       (fn [] 
         (when (and highlighted on-highlight) 
           (on-highlight ref)))
       [highlighted on-highlight-dep])
+
     [:div.flex.flex-col.px-6.gap-1.py-4 {:style {:background (if highlighted "var(--lx-gray-04-alpha)" "var(--lx-gray-02)")
                                                  :opacity (if highlighted 1 0.8)
                                                  :mix-blend-mode (if highlighted :normal :luminosity)}
-                                         :ref ref}
+                                         :ref ref
+                                         :on-click (when on-click on-click)}
      ;; header
      (when header
       [:div.text-xs.pl-8.font-light {:class "-mt-1"

+ 5 - 0
resources/css/shui.css

@@ -34,6 +34,11 @@
   @apply w-px h-full bg-white/10;
 }
 
+.shui__button-theme-text {
+  background: none;
+  box-shadow: none;
+}
+
 .shui__button-theme-color {
   background: or(--lx-accent-09, --rx-blue-09);
 }

+ 308 - 121
src/main/frontend/components/cmdk.cljs

@@ -5,6 +5,7 @@
     [frontend.components.command-palette :as cp]
     [frontend.components.page :as page]
     [frontend.context.i18n :refer [t]]
+    [frontend.date :as date]
     [frontend.db :as db]
     [frontend.db.model :as model]
     [frontend.handler.command-palette :as cp-handler]
@@ -17,6 +18,7 @@
     [frontend.state :as state]
     [frontend.ui :as ui]
     [frontend.util :as util]
+    [frontend.util.page :as page-util]
     [goog.functions :as gfun]
     [logseq.shui.context :refer [make-context]]
     [logseq.shui.core :as shui]
@@ -25,21 +27,56 @@
 
 ;; When CMDK opens, we have some default search actions we make avaialbe for quick access
 (def default-search-actions 
-  [{:text "Search only pages"     :info "Add filter to search"}
-   {:text "Search only blocks"    :info "Add filter to search"}
-   {:text "Create block"          :info "Add a block to today's journal page" :icon "block"         :icon-theme :color}
-   {:text "Generate short answer" :info "Ask a language model"                :icon "question-mark" :icon-theme :gradient}
-   {:text "Open settings"                                                     :icon "settings"      :icon-theme :gray}])
+  [{:text "Search only pages"        :info "Add filter to search" :icon-theme :gray :icon "page" :filter {:group :pages}}
+   {:text "Search only current page" :info "Add filter to search" :icon-theme :gray :icon "page" :filter {:group :current-page}}
+   {:text "Search only blocks"       :info "Add filter to search" :icon-theme :gray :icon "block" :filter {:group :blocks}}
+   {:text "Search only whiteboards"  :info "Add filter to search" :icon-theme :gray :icon "whiteboard" :filter {:group :whiteboards}}
+   {:text "Search only files"        :info "Add filter to search" :icon-theme :gray :icon "file" :filter {:group :files}}])
+   ; {:text "Create block"             :info "Add a block to today's journal page" :icon "block"         :icon-theme :color}
+   ; {:text "Generate short answer"    :info "Ask a language model"                :icon "question-mark" :icon-theme :gradient}
+
+(def default-commands 
+  [{:text "Open settings" :icon "settings"      :icon-theme :gray}])
 
 ;; The results are separated into groups, and loaded/fetched/queried separately
 (def default-results 
   {:search-actions {:status :success :show-more false :items default-search-actions} 
-   :commands       {:status :success :show-more false :items nil}
+   :commands       {:status :success :show-more false :items default-commands}
    :history        {:status :success :show-more false :items nil}
    :current-page   {:status :success :show-more false :items nil}
    :pages          {:status :success :show-more false :items nil}
    :blocks         {:status :success :show-more false :items nil} 
    :files          {:status :success :show-more false :items nil}})        
+
+;; Take the results, decide how many items to show, and order the results appropriately
+(defn state->results-ordered [state]
+  (let [results @(::results state)
+        visible-items (fn [group] 
+                        (let [{:keys [items show-more]} (get results group)]
+                          (if show-more items (take 5 items))))
+        results [["Search actions" :search-actions (visible-items :search-actions)]
+                 ["Current page"   :current-page   (visible-items :current-page)]
+                 ["Commands"       :commands       (visible-items :commands)]
+                 ["Pages"          :pages          (visible-items :pages)]
+                 ["Whiteboards"    :whiteboards    (visible-items :whiteboards)]
+                 ["Blocks"         :blocks         (visible-items :blocks)]]]
+    results))
+    ; (for [[group-name group-key group-items] results]
+    ;   [group-name group-key (mapv #(assoc % :item-index (vswap! index inc)) group-items)])))
+
+;; Take the ordered results and the highlight index and determine which item is highlighted
+; (defn state->highlighted-item 
+;   ([state] (state->highlighted-item state (state->results-ordered state)))
+;   ([state results-ordered] 
+;    (let [highlight-index @(::highlight-index state)
+;          items (mapcat last results-ordered)
+;          item-count (count items)
+;          normalized-index (cond 
+;                             (zero? item-count) nil 
+;                             (<= 0 (mod highlight-index item-count)) (mod highlight-index item-count) 
+;                             :else (- item-count (mod highlight-index item-count)))]
+;        (when normalized-index
+;          (nth items normalized-index nil)))))
                     
 ;; Each result gorup has it's own load-results function
 (defmulti load-results (fn [group state] group))
@@ -57,18 +94,20 @@
   (let [!input (::input state)
         !results (::results state)]
     (swap! !results assoc-in [group :status] :loading)
-    (->> (vals (cp-handler/get-commands-unique))
-         (filter #(string/includes? (string/lower-case (pr-str %)) (string/lower-case @!input)))
-         (map #(hash-map :icon "command" 
-                         :icon-theme :gray 
-                         :text (cp/translate t %)
-                         :value-label (pr-str (:id %))
-                         ; :info (pr-str (:id %))
-                         ; :info (:desc %) 
-                         :shortcut (:shortcut %)
-                         :source-command %))
-         (hash-map :status :success :items)
-         (swap! !results assoc group)) 
+    (if (empty? @!input)
+      (swap! !results assoc group {:status :success :items default-commands})
+      (->> (vals (cp-handler/get-commands-unique))
+           (filter #(string/includes? (string/lower-case (pr-str %)) (string/lower-case @!input)))
+           (map #(hash-map :icon "command" 
+                           :icon-theme :gray 
+                           :text (cp/translate t %)
+                           :value-label (pr-str (:id %))
+                           ; :info (pr-str (:id %))
+                           ; :info (:desc %) 
+                           :shortcut (:shortcut %)
+                           :source-command %))
+           (hash-map :status :success :items)
+           (swap! !results assoc group))) 
     (js/console.log "commands" (clj->js (get-in @!results [:commands :items])))))
 
 ;; The pages search action uses an existing handler
@@ -76,34 +115,66 @@
   (let [!input (::input state)
         !results (::results state)]
     (swap! !results assoc-in [group :status] :loading)
+    (swap! !results assoc-in [:whiteboards :status] :loading)
     (p/let [pages (search/page-search @!input)
-            items (map #(hash-map :icon "page" 
-                                  :icon-theme :gray 
-                                  :text % 
-                                  :source-page %) pages)]
-      (js/console.log "pages" (pr-str pages) (clj->js items))
-      (swap! !results assoc group {:status :success :items items}))))
+            whiteboards (filter model/whiteboard-page? pages)
+            non-boards (remove model/whiteboard-page? pages)
+            whiteboard-items (map #(hash-map :icon "page" 
+                                             :icon-theme :gray 
+                                             :text % 
+                                             :source-page %) whiteboards)
+            non-board-items (map #(hash-map :icon "page" 
+                                            :icon-theme :gray 
+                                            :text % 
+                                            :source-page %) non-boards)]
+      (js/console.log "pages" (clj->js whiteboards) (clj->js non-boards))
+      (swap! !results assoc group {:status :success :items non-board-items}
+      ; (swap! !results assoc :whiteboards {:status :success :items whiteboard-items}
+                            :whiteboards {:status :success :items whiteboard-items}))))
 
 ;; The blocks search action uses an existing handler
 (defmethod load-results :blocks [group state]
   (let [!input (::input state)
         !results (::results state)
         repo (state/get-current-repo)
+        current-page (page-util/get-current-page-id)
         opts {:limit 100}]
     (swap! !results assoc-in [group :status] :loading)
+    (swap! !results assoc-in [:current-page :status] :loading)
     (p/let [blocks (search/block-search repo @!input opts)
             items (map #(hash-map :icon "block" 
                                   :icon-theme :gray 
                                   :text (:block/content %) 
                                   :header (some-> % :block/page db/entity :block/name)
-                                  :source-block %) blocks)]
-                              
-      ; (js/console.log "blocks" (clj->js items) (map (comp pr-str :block/page) blocks))
+                                  :current-page? (some-> % :block/page #{current-page})
+                                  :source-block %) blocks)
+            items-on-other-pages (remove :current-page? items)
+            items-on-current-page (filter :current-page? items)]
+      (js/console.log "blocks" (clj->js items) current-page) 
       ; (js/console.log "blocks" (clj->js items) 
       ;                 (pr-str (map (comp pr-str :block/page) blocks)) 
       ;                 (pr-str (map (comp :block/name :block/page) blocks)) 
       ;                 (pr-str (map (comp :block/name db/entity :block/page) blocks)))
-      (swap! !results assoc group {:status :success :items items}))))
+      ; (js/console.log "load-results/blocks" 
+      ;                 (clj->js blocks) 
+      ;                 (pr-str (first blocks)))
+      (swap! !results assoc group {:status :success :items items-on-other-pages}
+                            :current-page {:status :success :items items-on-current-page}))))
+
+; (defmethod load-results :whiteboards [group state]
+;   (let [!input (::input state)
+;         !results (::results state)
+;         repo (state/get-current-repo)]
+;     (swap! !results assoc-in [group :status] :loading)
+;     (p/let [whiteboards ()])))
+
+(defmethod load-results :files [group state]
+  (let [!input (::input state)
+        !results (::results state)
+        repo (state/get-current-repo)]
+    (swap! !results assoc-in [group :status] :loading)
+    (p/let [files (search/file-search @!input 99)]
+      (js/console.log "load-results/files" (clj->js files)))))
 
 ;; The default load-results function triggers all the other load-results function
 (defmethod load-results :default [_ state]
@@ -111,7 +182,9 @@
   (load-results :search-actions state)
   (load-results :commands state)
   (load-results :blocks state)
-  (load-results :pages state))
+  (load-results :pages state)
+  ; (load-results :whiteboards state)
+  (load-results :files state))
 
 ; (def search [query]
 ;   (load-results :search-actions state))
@@ -125,49 +198,76 @@
   ;           files (search/file-search q)
   ;           commands])))
 
+(defn close-unless-alt! [state]
+  (when-not (some-> state ::alt? deref)
+    (state/close-modal!)))
+
 (defmulti handle-action (fn [action _state _item _event] action))
 
-(defmethod handle-action :cancel [_ state item event]
+(defmethod handle-action :close [_ state item event]
   (js/console.log :handle-action/cancel)
   (state/close-modal!))
 
 (defmethod handle-action :copy-page-ref [_ state item event]
   (when-let [page-name (:source-page item)]
     (util/copy-to-clipboard! page-name)
-    (state/close-modal!)))
+    (close-unless-alt! state)))
     
 (defmethod handle-action :copy-block-ref [_ state item event]
   (when-let [block-uuid (some-> item :source-block :block/uuid uuid)]
     (editor-handler/copy-block-ref! block-uuid)
-    (state/close-modal!)))
+    (close-unless-alt! state)))
 
 (defmethod handle-action :open-page [_ state item event]
   (when-let [page-name (:source-page item)]
     (route-handler/redirect-to-page! page-name)
-    (state/close-modal!)))
+    (close-unless-alt! state)))
 
 (defmethod handle-action :open-block [_ state item event]
   (let [get-block-page (partial model/get-block-page (state/get-current-repo))]
     (when-let [page (some-> item :source-block :block/uuid uuid get-block-page :block/name model/get-redirect-page-name)]
       (route-handler/redirect-to-page! page)
-      (state/close-modal!))))
+      (close-unless-alt! state))))
 
 (defmethod handle-action :open-page-right [_ state item event]
   (when-let [page-uuid (some-> item :source-page model/get-page :block/uuid uuid)]
     (js/console.log "oepn-page-right" page-uuid) 
-    (editor-handler/open-block-in-sidebar! page-uuid)))
+    (editor-handler/open-block-in-sidebar! page-uuid)
+    (close-unless-alt! state)))
 
 (defmethod handle-action :open-block-right [_ state item event]
   (when-let [block-uuid (some-> item :source-block :block/uuid uuid)]
     (js/console.log "oepn-block-right" block-uuid) 
-    (editor-handler/open-block-in-sidebar! block-uuid)))
+    (editor-handler/open-block-in-sidebar! block-uuid)
+    (close-unless-alt! state)))
 
-(defmethod handle-action :trigger [_ state item event])
-(defmethod handle-action :return [_ state item event])
+(defmethod handle-action :trigger [_ state item event]
+  (js/console.log "handle-action/trigger" (clj->js (keys item)) (clj->js item))
+  (when-let [action (some-> item :source-command :action)]
+    (action)
+    (close-unless-alt! state)))
+
+(defmethod handle-action :filter [_ state item event]
+  (let [!filter (::filter state) 
+        filter (some-> item :filter)]
+    (if filter
+      (reset! !filter filter)
+      (reset! !filter nil))))
 
 (rum/defc result-group < rum/reactive 
-  [state title group visible-items highlighted-result]
-  (let [{:keys [show-more items]} (some-> state ::results deref group)] 
+  [state title group visible-items first-item]
+  (let [{:keys [show-more items]} (some-> state ::results deref group) 
+        toggle-show-more #(swap! (::results state) update-in [group :show-more] not)
+        highlighted-item (or @(::highlighted-item state) first-item)
+        cap-highlighted-item (fn []
+                               (js/console.log 
+                                 "testing-capping-the-highlighted-item"
+                                 (count items)
+                                 (clj->js (drop 5 items)) 
+                                 (clj->js highlighted-item)
+                                 (.indexOf items highlighted-item))
+                               (when (< 4 (.indexOf items highlighted-item))
+                                 (reset! (::highlighted-item state) (nth items 4 nil))))] 
     [:div {:class ""}
      [:div {:class "text-xs py-1.5 px-6 flex justify-between items-center gap-2" 
             :style {:color "var(--lx-gray-11)" 
@@ -181,42 +281,73 @@
          (count items))]
       [:div {:class "flex-1"}]
       (if show-more
-        [:div {:on-click #(swap! (::results state) update-in [group :show-more] not)} "Show less"]
-        [:div {:on-click #(swap! (::results state) update-in [group :show-more] not)} "Show more"])]
+        [:div {:on-click (fn [] (cap-highlighted-item) (toggle-show-more))} "Show less"]
+        [:div {:on-click (fn [] (toggle-show-more))} "Show more"])]
 
      [:div {:class ""}
-      (for [result visible-items
-            :let [highlighted? (= result highlighted-result)]]
-       (shui/list-item (assoc result 
+      (for [item visible-items
+            :let [highlighted? (= item highlighted-item)]]
+       (shui/list-item (assoc item 
                               :highlighted highlighted?
+                              ;; for some reason, the highlight effect does not always trigger on a 
+                              ;; boolean value change so manually pass in the dep
+                              :on-highlight-dep highlighted-item
+                              :on-click (fn []
+                                          (if highlighted?
+                                            (when-let [action (some-> state ::actions deref last)]
+                                              (handle-action action state item))
+                                            (reset! (::highlighted-item state) item)))
                               :on-highlight (fn [ref]  
-                                              (.. ref -current (scrollIntoView #js {:block "center" 
-                                                                                    :inline "nearest"
-                                                                                    :behavior "smooth"})) 
+                                              (when (and ref (.-current ref))
+                                                (.. ref -current (scrollIntoView #js {:block "center" 
+                                                                                      :inline "nearest"
+                                                                                      :behavior "smooth"}))) 
                                               (case group 
-                                                :search-actions (reset! (::actions state) [:cancel :return])
-                                                :commands       (reset! (::actions state) [:cancel :trigger])
-                                                :pages          (reset! (::actions state) [:cancel :copy-page-ref :open-page-right :open-page])
-                                                :blocks         (reset! (::actions state) [:cancel :copy-block-ref :open-block-right :open-block])
+                                                :search-actions (reset! (::actions state) [:close :filter])
+                                                :commands       (reset! (::actions state) [:close :trigger])
+                                                :pages          (reset! (::actions state) [:close :copy-page-ref :open-page-right :open-page])
+                                                :blocks         (reset! (::actions state) [:close :copy-block-ref :open-block-right :open-block])
                                                 nil)))))]]))
-                                                
-                                                  
+
+(defn move-highlight [state n]
+  (let [items (mapcat last (state->results-ordered state))
+        current-item-index (some->> state ::highlighted-item deref (.indexOf items))
+        next-item-index (some-> (or current-item-index 0) (+ n) (mod (count items)))]
+    (if-let [next-highlighted-item (nth items next-item-index nil)]
+      (reset! (::highlighted-item state) next-highlighted-item)
+      (reset! (::highglighted-item state) nil))))
 
 (defonce keydown-handler
   (fn [state e]
-    (when (#{"ArrowDown" "ArrowUp"} (.-key e))
-      (.preventDefault e))
-    (case (.-key e)
-      ; "Escape" (rum/dispatch! :close)
-      "ArrowDown" (swap! (::highlight-index state) inc)
-      "ArrowUp"   (swap! (::highlight-index state) dec)
-      ; "j" (when (.-metaKey e) 
-      ;       (if (.-shiftKey e)
-      ;         (swap! state update :current-engine prev-engine)
-      ;         (swap! state update :current-engine next-engine)))
-      ; "ArrowUp" (rum/dispatch! :highlight-prev)
-      ; "Enter" (rum/dispatch! :select)
-      (println (.-key e)))))
+    (let [shift? (.-shiftKey e)
+          alt? (.-altKey e)]
+      (reset! (::shift? state) shift?)
+      (reset! (::alt? state) alt?)
+      (when (#{"ArrowDown" "ArrowUp"} (.-key e))
+        (.preventDefault e))
+      (case (.-key e)
+        ; "Escape" (rum/dispatch! :close)
+        "ArrowDown" (move-highlight state 1)
+        "ArrowUp"   (move-highlight state -1)
+        "Enter" (if shift?
+                  (when-let [action (some #{:open-block-right :open-page-right} @(::actions state))]
+                    (handle-action action state @(::highlighted-item state) e))
+                  (when-let [action (some #{:open-block :open-page :filter :trigger} @(::actions state))]
+                    (handle-action action state @(::highlighted-item state) e)))
+        ; "j" (when (.-metaKey e) 
+        ;       (if (.-shiftKey e)
+        ;         (swap! state update :current-engine prev-engine)
+        ;         (swap! state update :current-engine next-engine)))
+        ; "ArrowUp" (rum/dispatch! :highlight-prev)
+        ; "Enter" (rum/dispatch! :select)
+        (println "keydown-handler did not capture key: " (.-key e))))))
+
+(defonce keyup-handler 
+  (fn [state e]
+    (let [shift? (.-shiftKey e) 
+          alt? (.-altKey e)]
+      (reset! (::shift? state) shift?)
+      (reset! (::alt? state) alt?))))
 
 (defn handle-input-change [state e]
   (let [input (.. e -target -value)
@@ -237,81 +368,138 @@
   (let [page-name (:source-page highlighted)]
     (page/page {:page-name (model/get-redirect-page-name page-name) :whiteboard? true})))
 
+(defn top-level-block 
+  ([block-uuid] (top-level-block block-uuid -1))
+  ([block-uuid max-depth]
+   (assert (uuid? block-uuid) "top-level-block expects block-uuid to be of type uuid")
+   (loop [entity (db/entity [:block/uuid block-uuid]) depth 0]
+     (cond
+       (= depth max-depth) entity
+       (some-> entity :block/parent :block/parent) (recur (:block/parent entity) (inc depth))
+       :else entity)))) 
+
 (rum/defc block-preview [state highlighted]
   (let [block (:source-block highlighted)
-        block-uuid-str (str (:block/uuid block))]
+        block-uuid-str (str (:block/uuid block))
+        top-level-block (top-level-block (uuid block-uuid-str))
+        top-level-block-uuid (str (:block/uuid top-level-block))]
     ; ((state/get-component :block/single-block) (uuid (:block/uuid block)))))
     ; ((state/get-component :block/container) block)
     ; ((state/get-component :block/embed) (uuid (:block/uuid block)))))
     ; (block/block-container {} block)))
-    (page/page {:parameters {:path {:name block-uuid-str}} 
+    (page/page {:parameters {:path {:name top-level-block-uuid}} 
                 :sidebar? true 
                 :repo (state/get-current-repo)})))
 
+(defn print-group-name [group]
+  (case group
+    :current-page "Search current page"
+    :blocks "Search blocks"
+    :pages "Search pages"
+    :whiteboards "Search whiteboards"
+    :commands "Search commands"
+    (str "Search " (name group))))
+
+(rum/defc filter-row [state filter]
+  [:div {:class "py-3 border-b flex flex-col gap-2" 
+         :style {:background "var(--lx-gray-02)" 
+                 :border-color "var(--lx-gray-07)"}}
+   [:div {:class "text-xs font-bold px-6"} "Filters"]
+   [:div {:class "flex items-center gap-2"}
+    [:div {:class "w-4 h-1"}]
+    (when-let [group (:group filter)]
+      [:div {:class "text-xs py-0.5 px-1.5 rounded bg-blue-500/20 hover:bg-blue-500/50 hover:cursor-pointer"
+             :on-click #(swap! (::filter state) dissoc :group)}
+        (print-group-name group)])]])
+
+(rum/defc input-row 
+  [state all-items]
+  (let [highlighted-item @(::highlighted-item state)
+        input @(::input state)]
+    ;; use-effect [results-ordered input] to check whether the highlighted item is still in the results,
+    ;; if not then clear that puppy out!
+    ;; This was moved to a fucntional component
+    (rum/use-effect! (fn [] 
+                       (when (= -1 (.indexOf all-items highlighted-item))
+                         (reset! (::highlighted-item state) nil)))
+                     [all-items])
+    [:div {:class ""
+           :style {:background "var(--lx-gray-02)"
+                   :border-bottom "1px solid var(--lx-gray-07)"}}
+     [:input {:class "text-xl bg-transparent border-none w-full outline-none px-4 py-3" 
+              :placeholder "What are you looking for?"
+              :ref #(when % (.focus %))
+              :on-change (partial handle-input-change state)
+              :value input}]]))
+
 (rum/defcs cmdk < 
   shortcut/disable-all-shortcuts 
   (rum/local "" ::input)
-  (rum/local 0 ::highlight-index)
+  ; (rum/local 0 ::highlight-index)
+  (rum/local false ::shift?)
+  (rum/local false ::alt?)
+  (rum/local nil ::highlighted-item)
   (rum/local nil ::keydown-handler)
+  (rum/local nil ::keyup-handler)
+  (rum/local nil ::filter)
   (rum/local default-results ::results)
   (rum/local nil ::load-results-throttled)
-  (rum/local [:cancel :return] ::actions)
+  (rum/local [:close :filter] ::actions) 
   {:did-mount (fn [state] 
-                (let [next-keydown-handler (partial keydown-handler state)]
+                (let [next-keydown-handler (partial keydown-handler state)
+                      next-keyup-handler (partial keyup-handler state)]
+                  ;; remove pre existing handlers
                   (when-let [prev-keydown-handler @(::keydown-handler state)]
                     (js/window.removeEventListener "keydown" prev-keydown-handler))
+                  (when-let [prev-keyup-handler @(::keyup-handler state)]
+                    (js/window.removeEventListener "keyup" prev-keyup-handler))
+                  ;; add new handlers
                   (js/window.addEventListener "keydown" next-keydown-handler)
-                  (reset! (::keydown-handler state) next-keydown-handler))
+                  (js/window.addEventListener "keyup" next-keyup-handler)
+                  ;; save references to functions for cleanup later
+                  (reset! (::keydown-handler state) next-keydown-handler)
+                  (reset! (::keyup-handler state) next-keyup-handler))
                 state)
    :will-unmount (fn [state] 
+                   ;; remove save references to key handlers
                    (when-let [current-keydown-handler (some-> state ::keydown-handler deref)] 
                      (js/window.removeEventListener "keydown" current-keydown-handler))
+                   (when-let [current-keyup-handler (some-> state ::keyup-handler deref)] 
+                     (js/window.removeEventListener "keyup" current-keyup-handler))
+                   ;; clear references to key handlers
                    (reset! (::keydown-handler state) nil)
+                   (reset! (::keyup-handler state) nil)
                    state)}
   [state {:keys []}]
   (let [input @(::input state)
         actions @(::actions state)
-        highlight-index @(::highlight-index state)
-        results @(::results state)
-        visible-items-for-group (fn [group] 
-                                  (let [{:keys [items show-more]} (get results group)]
-                                    (if show-more items (take 5 items))))
-        results-ordered [["Search actions" :search-actions (visible-items-for-group :search-actions)]
-                         ["Commands"       :commands       (visible-items-for-group :commands)]
-                         ["Pages"          :pages          (visible-items-for-group :pages)]
-                         ["Blocks"         :blocks         (visible-items-for-group :blocks)]]
-        results (mapcat last results-ordered)
-        result-count (count results)
-        highlighted-result-index (cond 
-                                   (zero? result-count) nil 
-                                   (<= 0 (mod highlight-index result-count)) (mod highlight-index result-count) 
-                                   :else (- result-count (mod highlight-index result-count)))
-        highlighted-result (when highlighted-result-index 
-                             (nth results highlighted-result-index nil))
-        preview? (or (:source-page highlighted-result) (:source-block highlighted-result))]
+        ; highlight-index @(::highlight-index state)
+        filter (not-empty @(::filter state))
+        group-filter (:group filter)
+        results-ordered (state->results-ordered state)
+        all-items (mapcat last results-ordered)
+        first-item (first all-items)
+        highlighted-item (or @(::highlighted-item state) first-item)
+        preview? (or (:source-page highlighted-item) (:source-block highlighted-item))
+        shift? @(::shift? state) 
+        alt? @(::alt? state)]
     [:div.cp__cmdk {:class "-m-8 max-w-[90dvw] max-h-[90dvh] w-[60rem] h-[30.7rem] "}
-     [:div {:class ""
-            :style {:background "var(--lx-gray-02)"
-                    :border-bottom "1px solid var(--lx-gray-07)"}}
-      [:input {:class "text-xl bg-transparent border-none w-full outline-none px-4 py-3" 
-               :placeholder "What are you looking for?"
-               :ref #(when % (.focus %))
-               :on-change (partial handle-input-change state)
-               :value input}]]
-
+     (input-row state all-items)
+     (when filter (filter-row state filter))
      [:div {:class (str "grid" (if preview? " grid-cols-2" " grid-cols-1"))}
       [:div {:class "pt-1 overflow-y-auto h-96"
              :style {:background "var(--lx-gray-02)"}}
        (for [[group-name group-key group-items] results-ordered
-             :when (not-empty group-items)]
-         (result-group state group-name group-key group-items highlighted-result))]
+             :when (not-empty group-items)
+             :when (if-not group-filter true (= group-filter group-key))]
+         (result-group state group-name group-key group-items first-item))]
       (when preview?
        [:div {:class "h-96 overflow-y-auto"} 
         (cond 
-         (:source-page highlighted-result)
-         (page-preview state highlighted-result)
-         (:source-block highlighted-result)
-         (block-preview state highlighted-result))])]
+         (:source-page highlighted-item)
+         (page-preview state highlighted-item)
+         (:source-block highlighted-item)
+         (block-preview state highlighted-item))])]
 
      [:div {:class "flex justify-between w-full px-4"
             :style {:background "var(--lx-gray-03)"
@@ -328,19 +516,18 @@
          [:div {:class ""} tab-name]])]
       [:div {:class "flex items-center py-3 gap-4"}
        (for [action actions
-             :let [on-click (partial handle-action action state highlighted-result)]]
+             :let [on-click (partial handle-action action state highlighted-item)
+                   str-alt #(if alt? (str % " (keep open)") %)]
+             :when (if shift? 
+                     (#{:open-page-right :open-block-right :trigger :filter :close} action) 
+                     (#{:open-page :open-block :copy-page-ref :copy-block-ref :trigger :filter :close} action))]
          (case action
-           :cancel           (shui/button {:text "Cancel"           :theme :gray  :on-click on-click :shortcut ["esc"]}) 
-           :copy-page-ref    (shui/button {:text "Copy"             :theme :gray  :on-click on-click :shortcut ["cmd" "c"]}) 
-           :copy-block-ref   (shui/button {:text "Copy"             :theme :gray  :on-click on-click :shortcut ["cmd" "c"]}) 
-           :open-page-right  (shui/button {:text "Open in sidebar"  :theme :gray  :on-click on-click :shortcut ["shift" "return"]})
-           :open-page        (shui/button {:text "Open"             :theme :color :on-click on-click :shortcut ["return"]})  
-           :open-block-right (shui/button {:text "Open in sidebar"  :theme :gray  :on-click on-click :shortcut ["shift" "return"]})
-           :open-block       (shui/button {:text "Open page"        :theme :color :on-click on-click :shortcut ["return"]})  
-           :trigger          (shui/button {:text "Trigger"          :theme :color :on-click on-click :shortcut ["return"]})  
-           :return           (shui/button {:text "Return"           :theme :color :on-click on-click :shortcut ["return"]})))]]]))  
-       ; (shui/button {:text "AI" :theme :gradient} (make-context {}))]]]))
-
-(->> (cp-handler/get-commands-unique)
-     vals
-     (map (juxt :shortcut :id)))
+           :copy-page-ref    (shui/button {:text (str-alt "Copy")             :theme :gray  :on-click on-click :shortcut ["cmd" "c"]}) 
+           :copy-block-ref   (shui/button {:text (str-alt "Copy")             :theme :gray  :on-click on-click :shortcut ["cmd" "c"]}) 
+           :open-page-right  (shui/button {:text (str-alt "Open in sidebar")  :theme :color :on-click on-click :shortcut ["return"]})
+           :open-page        (shui/button {:text (str-alt "Open")             :theme :color :on-click on-click :shortcut ["return"]})  
+           :open-block-right (shui/button {:text (str-alt "Open in sidebar")  :theme :color :on-click on-click :shortcut ["return"]})
+           :open-block       (shui/button {:text (str-alt "Open page")        :theme :color :on-click on-click :shortcut ["return"]})  
+           :trigger          (shui/button {:text (str-alt "Trigger")          :theme :color :on-click on-click :shortcut ["return"]})  
+           :close            (shui/button {:text "Close"                      :theme :text  :on-click on-click})
+           :filter           (shui/button {:text "Filter"                     :theme :color :on-click on-click :shortcut ["return"]})))]]]))  

+ 20 - 20
src/main/frontend/db/react.cljs

@@ -300,26 +300,26 @@
   (when (or skip-query-time-check?
             (<= (or query-time 0) 80))
     (let [new-result (->
-                     (cond
-                       query-fn
-                       (let [result (query-fn db tx result)]
-                         (if (coll? result)
-                           (doall result)
-                           result))
-
-                       inputs-fn
-                       (let [inputs (inputs-fn)]
-                         (apply d/q query db inputs))
-
-                       (keyword? query)
-                       (db-utils/get-key-value graph query)
-
-                       (seq inputs)
-                       (apply d/q query db inputs)
-
-                       :else
-                       (d/q query db))
-                     transform-fn)]
+                      (cond
+                        query-fn
+                        (let [result (query-fn db tx result)]
+                          (if (coll? result)
+                            (doall result)
+                            result))
+
+                        inputs-fn
+                        (let [inputs (inputs-fn)]
+                          (apply d/q query db inputs))
+
+                        (keyword? query)
+                        (db-utils/get-key-value graph query)
+
+                        (seq inputs)
+                        (apply d/q query db inputs)
+
+                        :else
+                        (d/q query db))
+                      transform-fn)]
      (when-not (= new-result result)
        (set-new-result! k new-result tx)))))
 

+ 18 - 10
src/main/frontend/search.cljs

@@ -4,6 +4,7 @@
   (:require [cljs-bean.core :as bean]
             [clojure.string :as string]
             [logseq.graph-parser.config :as gp-config]
+            [frontend.config :as config]
             [frontend.db :as db]
             [frontend.db.model :as db-model]
             [frontend.regex :as regex]
@@ -65,16 +66,16 @@
                       (if (<= 0 (.indexOf ostr oquery)) MAX-STRING-LENGTH 0))
         (empty? s) 0
         :else (if (= (first q) (first s))
-                   (recur (rest q)
-                          (rest s)
-                          (inc mult) ;; increase the multiplier as more query chars are matched
-                          (dec idx) ;; decrease idx so score gets lowered the further into the string we match
-                          (+ mult score)) ;; score for this match is current multiplier * idx
-                   (recur q
-                          (rest s)
-                          1 ;; when there is no match, reset multiplier to one
-                          (dec idx)
-                          score))))))
+                  (recur (rest q)
+                         (rest s)
+                         (inc mult) ;; increase the multiplier as more query chars are matched
+                         (dec idx) ;; decrease idx so score gets lowered the further into the string we match
+                         (+ mult score)) ;; score for this match is current multiplier * idx
+                  (recur q
+                         (rest s)
+                         1 ;; when there is no match, reset multiplier to one
+                         (dec idx)
+                         score))))))
 
 (defn fuzzy-search
   [data query & {:keys [limit extract-fn]
@@ -105,6 +106,13 @@
       (when-not (string/blank? q)
         (protocol/query-page engine q option)))))
 
+(defn asset-search
+  "Based off of frontend.handler.editor/ensure-assets-dir!"
+  [repo q]
+  (p/let [repo-dir (config/get-repo-dir repo)
+          assets-dir "assets"
+          [repo-dir assets-dir] ( repo)]))
+
 (defn- transact-blocks!
   [repo data]
   (when-let [engine (get-engine repo)]