Files
logseq/src/main/frontend/handler/command_palette.cljs
Tienson Qin db6fc6b7ee feat: simple query builder (#8774)
Simple query builder

---------

Co-authored-by: Gabriel Horner <gabriel@logseq.com>
Co-authored-by: charlie <xyhp915@qq.com>
2023-03-21 14:27:00 +08:00

115 lines
3.9 KiB
Clojure

(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/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}]
(if (:command/shortcut command)
(log/error :shortcut/missing (str "Shortcut is missing for " (:id command)))
(try
(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))))
;; Catch unexpected errors so that subsequent register calls pass
(catch :default e
(log/error :command/register {:msg "Unexpectedly failed to register command"
:id id
:error (str e)})))))
(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/"))}))