123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107 |
- (ns frontend.handler.command-palette
- "System-component-like ns for command palette's functionality"
- (:require [cljs.spec.alpha :as s]
- [frontend.modules.shortcut.data-helper :as shortcut-helper]
- [frontend.spec :as spec]
- [frontend.state :as state]
- [lambdaisland.glogi :as log]
- [frontend.storage :as storage]))
- (s/def :command/id keyword?)
- (s/def :command/desc string?)
- (s/def :command/action fn?)
- (s/def :command/shortcut string?)
- (s/def :command/tag vector?)
- (s/def :command/command
- (s/keys :req-un [:command/id :command/action]
- ;; :command/desc is optional for internal commands since view
- ;; checks translation ns first
- :opt-un [:command/desc :command/shortcut :command/tag]))
- (defn global-shortcut-commands []
- (->> [:shortcut.handler/editor-global
- :shortcut.handler/global-prevent-default
- :shortcut.handler/global-non-editing-only]
- (mapcat shortcut-helper/shortcuts->commands)))
- (defn get-commands []
- (->> (get @state/state :command-palette/commands)
- (sort-by :id)))
- (defn get-commands-unique []
- (reduce #(assoc %1 (:id %2) %2) {}
- (get @state/state :command-palette/commands)))
- (defn history
- ([] (or (storage/get "commands-history") []))
- ([vals] (storage/set "commands-history" vals)))
- (defn- assoc-invokes [cmds]
- (let [invokes (->> (history)
- (map :id)
- (frequencies))]
- (mapv (fn [{:keys [id] :as cmd}]
- (if (contains? invokes id)
- (assoc cmd :invokes-count (get invokes id))
- cmd))
- cmds)))
- (defn add-history [{:keys [id]}]
- (storage/set "commands-history" (conj (history) {:id id :timestamp (.getTime (js/Date.))})))
- (defn invoke-command [{:keys [action] :as cmd}]
- (add-history cmd)
- (state/set-state! :ui/command-palette-open? false)
- (state/close-modal!)
- (action))
- (defn top-commands [limit]
- (->> (get-commands)
- (assoc-invokes)
- (sort-by :invokes-count)
- (reverse)
- (take limit)))
- (defn register
- "Register a global command searchable by command palette.
- `id` is defined as a global unique namespaced key :scope/command-name
- `action` must be a zero arity function
- Example:
- ```clojure
- (register
- {:id :document/open-logseq-doc
- :desc \"Document: open Logseq documents\"
- :action (fn [] (js/window.open \"https://docs.logseq.com/\"))})
- ```
- To add i18n support, prefix `id` with command and put that item in dict.
- Example: {:zh-CN {:command.document/open-logseq-doc \"打开文档\"}}"
- [{:keys [id] :as command}]
- (spec/validate :command/command command)
- (let [cmds (get-commands)]
- (if (some (fn [existing-cmd] (= (:id existing-cmd) id)) cmds)
- (log/error :command/register {:msg "Failed to register command. Command with same id already exist"
- :id id})
- (state/set-state! :command-palette/commands (conj cmds command)))))
- (defn unregister
- [id]
- (let [id (keyword id)
- cmds (get-commands-unique)]
- (when (contains? cmds id)
- (state/set-state! :command-palette/commands (vals (dissoc cmds id)))
- ;; clear history
- (history (filter #(not= (:id %) id) (history))))))
- (defn register-global-shortcut-commands []
- (let [cmds (global-shortcut-commands)]
- (doseq [cmd cmds] (register cmd))))
- (comment
- ;; register custom command example
- (register
- {:id :document/open-logseq-doc
- :desc "Document: open Logseq documents"
- :action (fn [] (js/window.open "https://docs.logseq.com/"))}))
|