mirror of
https://github.com/logseq/logseq.git
synced 2026-06-01 19:01:22 +00:00
Enhance/shortcuts (#9803)
* refactor(shortcuts): simplify to build handler category map * fix(shortcuts): redundant re-mount for the pdf shortcuts * refactor(shortcuts): simplify names * refactor(shortcuts): simplify user keynames * fix(shortcuts): persist inited state for dev mode * refactor(shortcuts): simplify handlers installation * refactor(shortcuts): optimize shortcuts mixin * fix: incorrect function ref * refactor(shortcuts): shortcuts mixin * fix(shortcuts): incorrect initialization for the pdf shortcut handler * refactor(shortcuts): optimize binding keys map * refactor(shortcuts): optimize shortcuts conflicts detection * refactor(shortcuts): optimize binding ids map * refactor(shortcuts): WIP the new keymap page * refactor(shortcuts): WIP the new keymap related components * feat(shortcuts): WIP fuzzy search for the shortcuts * refactor(shortcuts): WIP the new keymap related components * feat(shortcuts): WIP the new shorcuts record component * feat(shortcuts): WIP the new shorcuts record component * feat(shortcuts): WIP check shortcut conflicts component * feat(shortcuts): WIP the new shorcuts record component * refactor(shortcuts): WIP persist user shortcuts * fix(shortcuts): detection for the conflicts * feat(shortcuts): WIP detection for the conflicts * feat(shortcuts): WIP persist user shortcuts * refactor(shortcuts): add unit tests * enhance(ux): search pane for the shortcuts * feat(shortcuts): remove the existent shortcut item * feat(shortcuts): fold/unfold categories * feat(shortcuts): add shortcuts filters * enhance(shortcuts): resove binding map description * enhance(shortcuts): reactive category shortcuts * enhance(shortcuts): register api for plugins * feat(shortcuts): add keyboard shortcuts filters * feat(shortcuts): impl keyboard shortcuts filters * enhance(shortcuts): leader keys for the shortcut conflicts detection * enhance(tests): leader keys conflicts for the shortucts * enhance(shortcuts): parse conflicts from current binding list * enhance(ui): polish the component of the restore shortcut action * enhance(shortcuts): get conflicts with specific handler id * enhance(shortcuts): polish the confilts component * enhance(shortcuts): polish keymap conflicts component * enhance(shortcuts): ux for handling shorcuts conflicts * enhance(ui): polish notifications cp * fix(shortcuts): remove reduplicate shortcuts for category * enhance(shortcuts): polish ux for handling shorcuts conflicts * chore(plugin): build libs core * enhance(plugin): support shortcut command lifecycle hooks * enhance(plugin): support shortcut command lifecycle hooks * chore(plugin): build libs core * enhance(shortcuts): support shortcuts saved to global config * enhance(shortcuts): support shortcuts be saved to global config * feat(shortcuts): support keymap manager to global settings * enhance(shortcuts): shortcut to open keymap settings * fix(units): tests * fix: lints * enhance(shortcuts): unlisten all shortcuts * fix: lints * fix: lints * fix(units): tests * fix(units): tests * fix(units): tests * enhance(shortcuts): unlisten/listen all shortcuts * enhance(shortcuts): polish conflicts component * fix(ui): modal size * fix(ui): modal panel container * enhance(shortcuts): i18n * enhance(ui): layout of the shortcuts recorder component * fix(lint): i18n * enhance(ui): keyboard icon for the keymap settings tab * fix(shortcuts): incorrect filters for the collaspsed shortcuts * enhance(ui): polish details for the keymap settings * enhance(ui): polish details for the keymap settings * fix(shortcuts): get shortcut description error when the associated handler-id not exist * fix(ui): the shortcut disabled label overlaps with section headers. * refactor(shortcuts): names * enhance(ui): filter icons
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
(ns frontend.components.command-palette
|
||||
(:require [frontend.handler.command-palette :as cp]
|
||||
[frontend.modules.shortcut.core :as shortcut]
|
||||
[frontend.modules.shortcut.data-helper :as shortcut-helper]
|
||||
[frontend.modules.shortcut.utils :as shortcut-utils]
|
||||
[frontend.context.i18n :refer [t]]
|
||||
[frontend.search :as search]
|
||||
[frontend.ui :as ui]
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
(defn translate [t {:keys [id desc]}]
|
||||
(when id
|
||||
(let [desc-i18n (t (shortcut-helper/decorate-namespace id))]
|
||||
(let [desc-i18n (t (shortcut-utils/decorate-namespace id))]
|
||||
(if (string/starts-with? desc-i18n "{Missing key")
|
||||
desc
|
||||
desc-i18n))))
|
||||
|
||||
@@ -609,7 +609,7 @@
|
||||
|
||||
.cp__settings-inner {
|
||||
aside {
|
||||
@apply max-h-[70vh] overflow-auto mb-[-17px] p-3;
|
||||
@apply max-h-[70vh] overflow-auto p-3;
|
||||
|
||||
ul {
|
||||
@apply list-none p-0 m-0;
|
||||
@@ -991,7 +991,7 @@ html[data-theme='dark'] {
|
||||
.ui__modal[label=plugins-dashboard] {
|
||||
.panel-content {
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 100px);
|
||||
max-height: calc(100vh - 50px);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
[val {:keys [key type title default description inputAs]} update-setting!]
|
||||
|
||||
[:div.desc-item.as-input
|
||||
{:data-key key}
|
||||
{:data-key key :key key}
|
||||
[:h2 [:code key] (ui/icon "caret-right") [:strong title]]
|
||||
|
||||
[:label.form-control
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
[frontend.mobile.util :as mobile-util]
|
||||
[frontend.modules.instrumentation.core :as instrument]
|
||||
[frontend.modules.shortcut.data-helper :as shortcut-helper]
|
||||
[frontend.components.shortcut2 :as shortcut2]
|
||||
[frontend.spec.storage :as storage-spec]
|
||||
[frontend.state :as state]
|
||||
[frontend.storage :as storage]
|
||||
@@ -578,13 +579,13 @@
|
||||
(rum/defc user-proxy-settings
|
||||
[{:keys [type protocol host port] :as agent-opts}]
|
||||
(ui/button [:span.flex.items-center
|
||||
[:strong.pr-1
|
||||
[:span.pr-1
|
||||
(case type
|
||||
"system" "System Default"
|
||||
"direct" "Direct"
|
||||
(and protocol host port (str protocol "://" host ":" port)))]
|
||||
(ui/icon "edit")]
|
||||
:small? true
|
||||
:class "text-sm p-1"
|
||||
:on-click #(state/set-sub-modal!
|
||||
(fn [_] (plugins/user-proxy-settings-panel agent-opts))
|
||||
{:id :https-proxy-panel :center? true})))
|
||||
@@ -1037,18 +1038,41 @@
|
||||
|
||||
(def DEFAULT-ACTIVE-TAB-STATE (if config/ENABLE-SETTINGS-ACCOUNT-TAB [:account :account] [:general :general]))
|
||||
|
||||
(rum/defc settings-effect
|
||||
< rum/static
|
||||
[active]
|
||||
|
||||
(rum/use-effect!
|
||||
(fn []
|
||||
(let [active (and (sequential? active) (name (first active)))
|
||||
^js ds (.-dataset js/document.body)]
|
||||
(if active
|
||||
(set! (.-settingsTab ds) active)
|
||||
(js-delete ds "settingsTab"))
|
||||
#(js-delete ds "settingsTab")))
|
||||
[active])
|
||||
|
||||
[:<>])
|
||||
|
||||
(rum/defcs settings
|
||||
< (rum/local DEFAULT-ACTIVE-TAB-STATE ::active)
|
||||
{:will-mount
|
||||
(fn [state]
|
||||
(state/load-app-user-cfgs)
|
||||
state)
|
||||
:did-mount
|
||||
(fn [state]
|
||||
(let [active-tab (first (:rum/args state))
|
||||
*active (::active state)]
|
||||
(when (keyword? active-tab)
|
||||
(reset! *active [active-tab nil])))
|
||||
state)
|
||||
:will-unmount
|
||||
(fn [state]
|
||||
(state/close-settings!)
|
||||
state)}
|
||||
rum/reactive
|
||||
[state]
|
||||
[state _active-tab]
|
||||
(let [current-repo (state/sub :git/current-repo)
|
||||
;; enable-block-timestamps? (state/enable-block-timestamps?)
|
||||
_installed-plugins (state/sub :plugin/installed-plugins)
|
||||
@@ -1056,9 +1080,8 @@
|
||||
*active (::active state)]
|
||||
|
||||
[:div#settings.cp__settings-main
|
||||
|
||||
(settings-effect @*active)
|
||||
[:div.cp__settings-inner
|
||||
|
||||
[:aside.md:w-64 {:style {:min-width "10rem"}}
|
||||
[:header.cp__settings-header
|
||||
(ui/icon "settings")
|
||||
@@ -1069,6 +1092,7 @@
|
||||
[:account "account" (t :settings-page/tab-account) (ui/icon "user-circle")])
|
||||
[:general "general" (t :settings-page/tab-general) (ui/icon "adjustments")]
|
||||
[:editor "editor" (t :settings-page/tab-editor) (ui/icon "writing")]
|
||||
[:keymap "keymap" (t :settings-page/tab-keymap) (ui/icon "keyboard")]
|
||||
|
||||
(when (util/electron?)
|
||||
[:version-control "git" (t :settings-page/tab-version-control) (ui/icon "history")])
|
||||
@@ -1114,6 +1138,9 @@
|
||||
:editor
|
||||
(settings-editor current-repo)
|
||||
|
||||
:keymap
|
||||
(shortcut2/shortcut-keymap-x)
|
||||
|
||||
:version-control
|
||||
(settings-git)
|
||||
|
||||
|
||||
@@ -1,23 +1,35 @@
|
||||
.cp__settings {
|
||||
&-main {
|
||||
aside {
|
||||
&-inner {
|
||||
@apply flex flex-col md:flex-row;
|
||||
|
||||
> aside {
|
||||
@apply bg-gray-400/5 p-4;
|
||||
}
|
||||
|
||||
article {
|
||||
@apply p-4 flex-1 min-h-[12rem] w-auto overflow-y-auto;
|
||||
@apply md:max-h-[70vh] md:w-[40rem];
|
||||
/* margin-right: -17px; */
|
||||
/* margin-bottom: -17px; */
|
||||
> ul > li {
|
||||
> a {
|
||||
@apply mb-2;
|
||||
|
||||
@screen md {
|
||||
/* max-height: 70vh; */
|
||||
/* width: 680px; */
|
||||
> strong {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
padding-left: 5px;
|
||||
opacity: .9;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--ls-quaternary-background-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
aside > .cp__settings-header,
|
||||
article > .cp__settings-header {
|
||||
> article {
|
||||
@apply p-4 flex-1 min-h-[12rem] w-auto overflow-y-auto;
|
||||
@apply md:max-h-[70vh] md:w-[40rem];
|
||||
}
|
||||
|
||||
> aside > .cp__settings-header,
|
||||
> article > .cp__settings-header {
|
||||
@apply h-10 py-2 flex flex-row items-center justify-start gap-2;
|
||||
}
|
||||
|
||||
@@ -41,13 +53,13 @@
|
||||
@apply text-xl lowercase;
|
||||
}
|
||||
|
||||
h1.cp__settings-modal-title:first-letter,
|
||||
h1.cp__settings-modal-title:first-letter,
|
||||
h1.cp__settings-category-title:first-letter {
|
||||
@apply uppercase;
|
||||
}
|
||||
|
||||
.settings-menu {
|
||||
@apply p-0 m-0 mt-4 pr-3;
|
||||
@apply p-0 m-0 mt-4;
|
||||
}
|
||||
|
||||
.settings-menu-item {
|
||||
@@ -56,46 +68,10 @@
|
||||
}
|
||||
|
||||
.settings-menu-link {
|
||||
@apply px-2 py-1.5 select-none;
|
||||
@apply px-2 py-1.5 select-none;
|
||||
color: var(--ls-primary-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
&-inner {
|
||||
@apply flex flex-col md:flex-row;
|
||||
|
||||
> aside {
|
||||
|
||||
ul {
|
||||
|
||||
> li {
|
||||
|
||||
> a {
|
||||
|
||||
> i {
|
||||
overflow: hidden;
|
||||
opacity: .9;
|
||||
}
|
||||
|
||||
> strong {
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
padding-left: 5px;
|
||||
margin-top: 2px;
|
||||
opacity: .9;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--ls-quaternary-background-color);
|
||||
|
||||
i {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.no-aside {
|
||||
> article {
|
||||
@@ -392,7 +368,7 @@
|
||||
z-index: 1;
|
||||
width: 100px;
|
||||
max-height: 180px;
|
||||
border:1px solid var(--ls-border-color);
|
||||
border: 1px solid var(--ls-border-color);
|
||||
border-radius: 4px;
|
||||
overflow: auto;
|
||||
overflow: overlay;
|
||||
@@ -465,3 +441,15 @@ svg.git {
|
||||
svg.cmd {
|
||||
margin-left: -1px;
|
||||
}
|
||||
|
||||
body[data-settings-tab=keymap] {
|
||||
.cp__settings-inner {
|
||||
> article {
|
||||
@apply md:w-[70vw] xl:max-w-[850px] p-0;
|
||||
|
||||
> header {
|
||||
@apply p-4 pb-2 h-auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
[frontend.context.i18n :refer [t]]
|
||||
[frontend.modules.shortcut.core :as shortcut]
|
||||
[frontend.modules.shortcut.data-helper :as dh]
|
||||
[frontend.modules.shortcut.utils :as shortcut-utils]
|
||||
[frontend.state :as state]
|
||||
[frontend.ui :as ui]
|
||||
[frontend.extensions.latex :as latex]
|
||||
@@ -104,7 +105,7 @@
|
||||
[:code.text-xs (namespace k)]
|
||||
[:small.pl-1 (:desc cmd)]]
|
||||
|
||||
(not plugin?) (-> k (dh/decorate-namespace) (t))
|
||||
(not plugin?) (-> k (shortcut-utils/decorate-namespace) (t))
|
||||
:else (str k))]
|
||||
[:tr {:key (str k)}
|
||||
[:td.text-left.flex.items-center label]
|
||||
@@ -204,23 +205,11 @@
|
||||
(shortcut-table :shortcut.category/block-selection true)
|
||||
(shortcut-table :shortcut.category/formatting true)
|
||||
(shortcut-table :shortcut.category/toggle true)
|
||||
(when (state/enable-whiteboards?) (shortcut-table :shortcut.category/whiteboard true))
|
||||
(when (state/enable-whiteboards?)
|
||||
(shortcut-table :shortcut.category/whiteboard true))
|
||||
(shortcut-table :shortcut.category/plugins true)
|
||||
(shortcut-table :shortcut.category/others true)])
|
||||
|
||||
(rum/defc keymap-pane
|
||||
[]
|
||||
(let [[ready?, set-ready!] (rum/use-state false)]
|
||||
(rum/use-effect!
|
||||
(fn [] (js/setTimeout #(set-ready! true) 32))
|
||||
[])
|
||||
|
||||
[:div.cp__keymap-pane
|
||||
[:h1.pb-2.text-3xl.pt-2 "Keymap"]
|
||||
(if ready?
|
||||
(keymap-tables)
|
||||
[:p.flex.justify-center.py-20 (ui/loading "")])]))
|
||||
|
||||
(rum/defc shortcut-page
|
||||
[{:keys [show-title?]
|
||||
:or {show-title? true}}]
|
||||
|
||||
@@ -25,4 +25,169 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp__shortcut-page-x {
|
||||
@apply relative;
|
||||
|
||||
&-pane-controls {
|
||||
@apply flex space-x-3 absolute top-[-4px] right-4 items-center;
|
||||
|
||||
.search-input-wrap {
|
||||
@apply pr-1 relative;
|
||||
|
||||
a.x {
|
||||
@apply flex items-center absolute right-1 top-0 py-[7px] px-1 opacity-60
|
||||
hover:opacity-90;
|
||||
}
|
||||
}
|
||||
|
||||
input.form-input {
|
||||
@apply py-1;
|
||||
}
|
||||
|
||||
a.icon-link {
|
||||
@apply opacity-80 hover:opacity-100 active:opacity-40 select-none;
|
||||
|
||||
color: var(--ls-secondary-text-color);
|
||||
}
|
||||
|
||||
.keyboard-filter {
|
||||
.dropdown-wrapper {
|
||||
@apply shadow-lg w-[18rem];
|
||||
}
|
||||
|
||||
.keyboard-filter-record {
|
||||
> h2 {
|
||||
@apply flex items-center justify-between px-1.5 py-1;
|
||||
|
||||
background-color: var(--ls-secondary-background-color);
|
||||
|
||||
> strong {
|
||||
@apply text-[12px] opacity-80;
|
||||
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> header {
|
||||
@apply px-4 pb-4 pt-2;
|
||||
|
||||
> h2 {
|
||||
@apply relative top-[-6px];
|
||||
}
|
||||
}
|
||||
|
||||
> article {
|
||||
@apply relative pb-4 overflow-y-auto;
|
||||
|
||||
max-height: calc(70vh - 100px);
|
||||
overflow-y: overlay;
|
||||
|
||||
> ul {
|
||||
@apply px-4 m-0 py-0;
|
||||
|
||||
li {
|
||||
@apply text-[15px] px-1;
|
||||
|
||||
&.th {
|
||||
@apply rounded mb-2 sticky top-0 cursor-pointer
|
||||
select-none active:opacity-80 px-2 py-1 z-[1];
|
||||
|
||||
background-color: var(--ls-tertiary-background-color);
|
||||
}
|
||||
|
||||
.label-wrap {
|
||||
@apply flex flex-1;
|
||||
}
|
||||
|
||||
.action-wrap {
|
||||
@apply flex space-x-2 items-center flex-nowrap
|
||||
select-none active:opacity-70;
|
||||
|
||||
&.disabled {
|
||||
@apply opacity-60 cursor-default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-record-dialog-inner {
|
||||
@apply py-[28px] m-[-30px] px-[20px];
|
||||
|
||||
h1 {
|
||||
@apply relative top-[-8px];
|
||||
}
|
||||
|
||||
&:active, &:focus, &:focus-within {
|
||||
outline: burlywood hidden medium;
|
||||
}
|
||||
|
||||
.shortcuts-keys-wrap {
|
||||
@apply flex items-center my-4 flex-wrap;
|
||||
|
||||
.shortcut-record-control {
|
||||
@apply flex space-x-1 items-center select-none
|
||||
rounded border-[2px] py-[2px] px-[2px];
|
||||
}
|
||||
|
||||
.keyboard-shortcut {
|
||||
> code {
|
||||
@apply relative select-none tracking-wider;
|
||||
|
||||
a.x {
|
||||
@apply hidden absolute right-[-8px] top-[-6px] h-[16px] w-[16px]
|
||||
rounded-full bg-red-500 text-white leading-none items-center
|
||||
justify-center cursor-pointer opacity-80 hover:opacity-100 active:opacity-50;
|
||||
}
|
||||
|
||||
&:hover a.x {
|
||||
@apply flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.keypressed {
|
||||
.shortcut-record-control {
|
||||
@apply pt-0
|
||||
}
|
||||
}
|
||||
|
||||
.action-btns {
|
||||
.keyboard-shortcut code {
|
||||
@apply rounded-[3px];
|
||||
}
|
||||
}
|
||||
|
||||
.reset-btn {
|
||||
@apply ml-4 opacity-50 cursor-default;
|
||||
}
|
||||
|
||||
&.dirty {
|
||||
.reset-btn {
|
||||
@apply opacity-100 cursor-pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.cp__shortcut-conflicts-list {
|
||||
&-wrap {
|
||||
> section {
|
||||
@apply bg-gray-3 border-[2px] mb-3 dark:bg-transparent;
|
||||
|
||||
> ul {
|
||||
@apply px-2 pb-2 m-0 list-none;
|
||||
}
|
||||
|
||||
> h2 {
|
||||
@apply flex items-center p-2 text-red-9 text-sm space-x-1 font-extrabold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
476
src/main/frontend/components/shortcut2.cljs
Normal file
476
src/main/frontend/components/shortcut2.cljs
Normal file
@@ -0,0 +1,476 @@
|
||||
(ns frontend.components.shortcut2
|
||||
(:require [clojure.string :as string]
|
||||
[rum.core :as rum]
|
||||
[frontend.context.i18n :refer [t]]
|
||||
[cljs-bean.core :as bean]
|
||||
[frontend.state :as state]
|
||||
[frontend.search :as search]
|
||||
[frontend.ui :as ui]
|
||||
[frontend.rum :as r]
|
||||
[goog.events :as events]
|
||||
[promesa.core :as p]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.modules.shortcut.core :as shortcut]
|
||||
[frontend.modules.shortcut.data-helper :as dh]
|
||||
[frontend.util :as util]
|
||||
[frontend.modules.shortcut.utils :as shortcut-utils]
|
||||
[frontend.modules.shortcut.config :as shortcut-config])
|
||||
(:import [goog.events KeyHandler]))
|
||||
|
||||
(defonce categories
|
||||
(vector :shortcut.category/basics
|
||||
:shortcut.category/navigating
|
||||
:shortcut.category/block-editing
|
||||
:shortcut.category/block-command-editing
|
||||
:shortcut.category/block-selection
|
||||
:shortcut.category/formatting
|
||||
:shortcut.category/toggle
|
||||
:shortcut.category/whiteboard
|
||||
:shortcut.category/plugins
|
||||
:shortcut.category/others))
|
||||
|
||||
(defonce *refresh-sentry (atom 0))
|
||||
(defn refresh-shortcuts-list! [] (reset! *refresh-sentry (inc @*refresh-sentry)))
|
||||
(defonce *global-listener-setup? (atom false))
|
||||
(defonce *customize-modal-life-sentry (atom 0))
|
||||
|
||||
(defn- to-vector [v]
|
||||
(when-not (nil? v)
|
||||
(if (sequential? v) (vec v) [v])))
|
||||
|
||||
(declare customize-shortcut-dialog-inner)
|
||||
|
||||
(rum/defc keyboard-filter-record-inner
|
||||
[keystroke set-keystroke! close-fn]
|
||||
|
||||
(let [keypressed? (not= "" keystroke)]
|
||||
|
||||
(rum/use-effect!
|
||||
(fn []
|
||||
(let [key-handler (KeyHandler. js/document)]
|
||||
;; setup
|
||||
(util/profile
|
||||
"[shortcuts] unlisten*"
|
||||
(shortcut/unlisten-all! true))
|
||||
(events/listen key-handler "key"
|
||||
(fn [^js e]
|
||||
(.preventDefault e)
|
||||
(set-keystroke! #(util/trim-safe (str % (shortcut/keyname e))))))
|
||||
|
||||
;; teardown
|
||||
#(do
|
||||
(util/profile
|
||||
"[shortcuts] listen*"
|
||||
(shortcut/listen-all!))
|
||||
(.dispose key-handler))))
|
||||
[])
|
||||
|
||||
[:div.keyboard-filter-record
|
||||
[:h2
|
||||
[:strong (t :keymap/keystroke-filter)]
|
||||
[:span.flex.space-x-2
|
||||
(when keypressed?
|
||||
[:a.flex.items-center
|
||||
{:on-click #(set-keystroke! "")} (ui/icon "zoom-reset" {:size 12})])
|
||||
[:a.flex.items-center
|
||||
{:on-click #(do (close-fn) (set-keystroke! ""))} (ui/icon "x" {:size 12})]]]
|
||||
[:div.wrap.p-2
|
||||
(if-not keypressed?
|
||||
[:small (t :keymap/keystroke-record-desc)]
|
||||
(when-not (string/blank? keystroke)
|
||||
(ui/render-keyboard-shortcut [keystroke])))]]))
|
||||
|
||||
(rum/defc pane-controls
|
||||
[q set-q! filters set-filters! keystroke set-keystroke! toggle-categories-fn]
|
||||
(let [*search-ref (rum/use-ref nil)]
|
||||
[:div.cp__shortcut-page-x-pane-controls
|
||||
[:a.flex.items-center.icon-link
|
||||
{:on-click toggle-categories-fn
|
||||
:title "Toggle categories pane"}
|
||||
(ui/icon "fold")]
|
||||
|
||||
[:a.flex.items-center.icon-link
|
||||
{:on-click refresh-shortcuts-list!
|
||||
:title "Refresh all"}
|
||||
(ui/icon "refresh")]
|
||||
|
||||
[:span.search-input-wrap
|
||||
[:input.form-input.is-small
|
||||
{:placeholder (t :keymap/search)
|
||||
:ref *search-ref
|
||||
:value (or q "")
|
||||
:auto-focus true
|
||||
:on-key-down #(when (= 27 (.-keyCode %))
|
||||
(util/stop %)
|
||||
(if (string/blank? q)
|
||||
(some-> (rum/deref *search-ref) (.blur))
|
||||
(set-q! "")))
|
||||
:on-change #(let [v (util/evalue %)]
|
||||
(set-q! v))}]
|
||||
|
||||
(when-not (string/blank? q)
|
||||
[:a.x
|
||||
{:on-click (fn []
|
||||
(set-q! "")
|
||||
(js/setTimeout #(some-> (rum/deref *search-ref) (.focus)) 50))}
|
||||
(ui/icon "x" {:size 14})])]
|
||||
|
||||
;; keyboard filter
|
||||
(ui/dropdown
|
||||
(fn [{:keys [toggle-fn]}]
|
||||
[:a.flex.items-center.icon-link
|
||||
{:on-click toggle-fn} (ui/icon "keyboard")
|
||||
|
||||
(when-not (string/blank? keystroke)
|
||||
(ui/point "bg-red-600.absolute" 4 {:style {:right -2 :top -2}}))])
|
||||
(fn [{:keys [close-fn]}]
|
||||
(keyboard-filter-record-inner keystroke set-keystroke! close-fn))
|
||||
{:outside? true
|
||||
:trigger-class "keyboard-filter"})
|
||||
|
||||
;; other filter
|
||||
(ui/dropdown-with-links
|
||||
(fn [{:keys [toggle-fn]}]
|
||||
[:a.flex.items-center.icon-link.relative
|
||||
{:on-click toggle-fn}
|
||||
(ui/icon "filter")
|
||||
|
||||
(when (seq filters)
|
||||
(ui/point "bg-red-600.absolute" 4 {:style {:right -2 :top -2}}))])
|
||||
|
||||
(for [k [:All :Disabled :Unset :Custom]
|
||||
:let [all? (= k :All)
|
||||
checked? (or (contains? filters k) (and all? (nil? (seq filters))))]]
|
||||
|
||||
{:title (if all? (t :keymap/all) (t (keyword :keymap (string/lower-case (name k)))))
|
||||
:icon (ui/icon (if checked? "checkbox" "square"))
|
||||
:options {:on-click #(set-filters! (if all? #{} (let [f (if checked? disj conj)] (f filters k))))}})
|
||||
|
||||
nil)]))
|
||||
|
||||
(rum/defc shortcut-desc-label
|
||||
[id binding-map]
|
||||
(when-let [id' (and id binding-map (some-> (str id) (string/replace "plugin." "")))]
|
||||
[:span {:title (str id' "#" (some-> (:handler-id binding-map) (name)))}
|
||||
[:span.pl-1 (dh/get-shortcut-desc (assoc binding-map :id id))]
|
||||
[:small.pl-1 [:code.text-xs (str id')]]]))
|
||||
|
||||
(defn- open-customize-shortcut-dialog!
|
||||
[id]
|
||||
(when-let [{:keys [binding user-binding] :as m} (dh/shortcut-item id)]
|
||||
(let [binding (to-vector binding)
|
||||
user-binding (and user-binding (to-vector user-binding))
|
||||
modal-id (str :customize-shortcut id)
|
||||
label (shortcut-desc-label id m)
|
||||
args [id label binding user-binding
|
||||
{:saved-cb (fn [] (-> (p/delay 500) (p/then refresh-shortcuts-list!)))
|
||||
:modal-id modal-id}]]
|
||||
(state/set-sub-modal!
|
||||
(fn [] (apply customize-shortcut-dialog-inner args))
|
||||
{:center? true
|
||||
:id modal-id
|
||||
:payload args}))))
|
||||
|
||||
(rum/defc shortcut-conflicts-display
|
||||
[_k conflicts-map]
|
||||
|
||||
[:div.cp__shortcut-conflicts-list-wrap
|
||||
(for [[g ks] conflicts-map]
|
||||
[:section.relative
|
||||
[:h2 (ui/icon "alert-triangle" {:size 15})
|
||||
[:span (t :keymap/conflicts-for-label)]
|
||||
[:code (shortcut-utils/decorate-binding g)]]
|
||||
[:ul
|
||||
(for [v (vals ks)
|
||||
:let [k (first v)
|
||||
vs (second v)]]
|
||||
(for [[id' handler-id] vs
|
||||
:let [m (dh/shortcut-item id')]
|
||||
:when (not (nil? m))]
|
||||
[:li
|
||||
{:key (str id')}
|
||||
[:a.select-none.hover:underline
|
||||
{:on-click #(open-customize-shortcut-dialog! id')
|
||||
:title (str handler-id)}
|
||||
[:code.inline-block.mr-1.text-xs
|
||||
(shortcut-utils/decorate-binding k)]
|
||||
[:span
|
||||
(dh/get-shortcut-desc m)
|
||||
(ui/icon "external-link" {:size 18})]
|
||||
[:code [:small (str id')]]]]))]])])
|
||||
|
||||
(rum/defc ^:large-vars/cleanup-todo customize-shortcut-dialog-inner
|
||||
[k action-name binding user-binding {:keys [saved-cb modal-id]}]
|
||||
(let [*ref-el (rum/use-ref nil)
|
||||
[modal-life _] (r/use-atom *customize-modal-life-sentry)
|
||||
[keystroke set-keystroke!] (rum/use-state "")
|
||||
[current-binding set-current-binding!] (rum/use-state (or user-binding binding))
|
||||
[key-conflicts set-key-conflicts!] (rum/use-state nil)
|
||||
|
||||
handler-id (rum/use-memo #(dh/get-group k))
|
||||
dirty? (not= (or user-binding binding) current-binding)
|
||||
keypressed? (not= "" keystroke)
|
||||
|
||||
save-keystroke-fn!
|
||||
(fn []
|
||||
;; parse current binding conflicts
|
||||
(if-let [current-conflicts (seq (dh/parse-conflicts-from-binding current-binding keystroke))]
|
||||
(notification/show!
|
||||
(str "Shortcut conflicts from existing binding: "
|
||||
(pr-str (some->> current-conflicts (map #(shortcut-utils/decorate-binding %)))))
|
||||
:error true :shortcut-conflicts/warning 5000)
|
||||
|
||||
;; get conflicts from the existed bindings map
|
||||
(let [conflicts-map (dh/get-conflicts-by-keys keystroke handler-id)]
|
||||
(if-not (seq conflicts-map)
|
||||
(do (set-current-binding! (conj current-binding keystroke))
|
||||
(set-keystroke! "")
|
||||
(set-key-conflicts! nil))
|
||||
|
||||
;; show conflicts
|
||||
(set-key-conflicts! conflicts-map)))))]
|
||||
|
||||
(rum/use-effect!
|
||||
(fn []
|
||||
(let [mid (state/sub :modal/id)
|
||||
mid' (some-> (state/sub :modal/subsets) (last) (:modal/id))
|
||||
el (rum/deref *ref-el)]
|
||||
(when (or (and (not mid') (= mid modal-id))
|
||||
(= mid' modal-id))
|
||||
(some-> el (.focus))
|
||||
(js/setTimeout
|
||||
#(some-> (.querySelector el ".shortcut-record-control a.submit")
|
||||
(.click)) 200))))
|
||||
[modal-life])
|
||||
|
||||
(rum/use-effect!
|
||||
(fn []
|
||||
(let [^js el (rum/deref *ref-el)
|
||||
key-handler (KeyHandler. el)
|
||||
|
||||
teardown-global!
|
||||
(when-not @*global-listener-setup?
|
||||
(shortcut/unlisten-all! true)
|
||||
(reset! *global-listener-setup? true)
|
||||
(fn []
|
||||
(shortcut/listen-all!)
|
||||
(reset! *global-listener-setup? false)))]
|
||||
|
||||
;; setup
|
||||
(events/listen key-handler "key"
|
||||
(fn [^js e]
|
||||
(.preventDefault e)
|
||||
(set-key-conflicts! nil)
|
||||
(set-keystroke! #(util/trim-safe (str % (shortcut/keyname e))))))
|
||||
|
||||
;; active
|
||||
(.focus el)
|
||||
|
||||
;; teardown
|
||||
#(do (some-> teardown-global! (apply nil))
|
||||
(.dispose key-handler)
|
||||
(swap! *customize-modal-life-sentry inc))))
|
||||
[])
|
||||
|
||||
[:div.cp__shortcut-page-x-record-dialog-inner
|
||||
{:class (util/classnames [{:keypressed keypressed? :dirty dirty?}])
|
||||
:tab-index -1
|
||||
:ref *ref-el}
|
||||
[:div.sm:w-lsm
|
||||
[:h1.text-2xl.pb-2
|
||||
(t :keymap/customize-for-label)]
|
||||
|
||||
[:p.mb-4.text-md [:b action-name]]
|
||||
|
||||
[:div.shortcuts-keys-wrap
|
||||
[:span.keyboard-shortcut.flex.flex-wrap.mr-2.space-x-2
|
||||
(for [x current-binding]
|
||||
[:code.tracking-wider
|
||||
(-> x (string/trim) (string/lower-case) (shortcut-utils/decorate-binding))
|
||||
[:a.x {:on-click (fn [] (set-current-binding!
|
||||
(->> current-binding (remove #(= x %)) (into []))))}
|
||||
(ui/icon "x" {:size 12})]])]
|
||||
|
||||
;; add shortcut
|
||||
[:div.shortcut-record-control
|
||||
;; keypressed state
|
||||
(if keypressed?
|
||||
[:<>
|
||||
(when-not (string/blank? keystroke)
|
||||
(ui/render-keyboard-shortcut [keystroke]))
|
||||
|
||||
[:a.flex.items-center.active:opacity-90.submit
|
||||
{:on-click save-keystroke-fn!}
|
||||
(ui/icon "check" {:size 14})]
|
||||
[:a.flex.items-center.text-red-600.hover:text-red-700.active:opacity-90.cancel
|
||||
{:on-click (fn []
|
||||
(set-keystroke! "")
|
||||
(set-key-conflicts! nil))}
|
||||
(ui/icon "x" {:size 14})]]
|
||||
|
||||
[:code.flex.items-center
|
||||
[:small.pr-1 (t :keymap/keystroke-record-setup-label)] (ui/icon "keyboard" {:size 14})])]]]
|
||||
|
||||
;; conflicts results
|
||||
(when (seq key-conflicts)
|
||||
(shortcut-conflicts-display k key-conflicts))
|
||||
|
||||
[:div.action-btns.text-right.mt-6.flex.justify-between.items-center
|
||||
;; restore default
|
||||
(when (sequential? binding)
|
||||
[:a.flex.items-center.space-x-1.text-sm.opacity-70.hover:opacity-100
|
||||
{:on-click #(set-current-binding! binding)}
|
||||
(t :keymap/restore-to-default)
|
||||
(for [it (some->> binding (map #(some->> % (dh/mod-key) (shortcut-utils/decorate-binding))))]
|
||||
[:span.keyboard-shortcut.ml-1 [:code it]])])
|
||||
|
||||
[:span
|
||||
(ui/button
|
||||
(t :save)
|
||||
:disabled (not dirty?)
|
||||
:on-click (fn []
|
||||
;; TODO: check conflicts for the single same leader key
|
||||
(let [binding' (if (nil? current-binding) [] current-binding)
|
||||
conflicts (dh/get-conflicts-by-keys binding' handler-id {:exclude-ids #{k}})]
|
||||
(if (seq conflicts)
|
||||
(set-key-conflicts! conflicts)
|
||||
(let [binding' (if (= binding binding') nil binding')]
|
||||
(shortcut/persist-user-shortcut! k binding')
|
||||
;(notification/show! "Saved!" :success)
|
||||
(state/close-modal!)
|
||||
(saved-cb))))))
|
||||
|
||||
[:a.reset-btn
|
||||
{:on-click (fn [] (set-current-binding! (or user-binding binding)))}
|
||||
(t :reset)]]]]))
|
||||
|
||||
(defn build-categories-map
|
||||
[]
|
||||
(->> categories
|
||||
(map #(vector % (into (sorted-map) (dh/binding-by-category %))))))
|
||||
|
||||
(rum/defc ^:large-vars/cleanup-todo shortcut-keymap-x
|
||||
[]
|
||||
(let [_ (r/use-atom shortcut-config/*category)
|
||||
_ (r/use-atom *refresh-sentry)
|
||||
[ready?, set-ready!] (rum/use-state false)
|
||||
[filters, set-filters!] (rum/use-state #{})
|
||||
[keystroke, set-keystroke!] (rum/use-state "")
|
||||
[q set-q!] (rum/use-state nil)
|
||||
|
||||
categories-list-map (build-categories-map)
|
||||
all-categories (into #{} (map first categories-list-map))
|
||||
in-filters? (boolean (seq filters))
|
||||
in-query? (not (string/blank? (util/trim-safe q)))
|
||||
in-keystroke? (not (string/blank? keystroke))
|
||||
|
||||
[folded-categories set-folded-categories!] (rum/use-state #{})
|
||||
|
||||
matched-list-map
|
||||
(when (and in-query? (not in-keystroke?))
|
||||
(->> categories-list-map
|
||||
(map (fn [[c binding-map]]
|
||||
[c (search/fuzzy-search
|
||||
binding-map q
|
||||
:extract-fn
|
||||
#(let [[id m] %]
|
||||
(str (name id) " " (dh/get-shortcut-desc (assoc m :id id)))))]))))
|
||||
|
||||
result-list-map (or matched-list-map categories-list-map)
|
||||
toggle-categories! #(if (= folded-categories all-categories)
|
||||
(set-folded-categories! #{})
|
||||
(set-folded-categories! all-categories))]
|
||||
|
||||
(rum/use-effect!
|
||||
(fn []
|
||||
(js/setTimeout #(set-ready! true) 100))
|
||||
[])
|
||||
|
||||
[:div.cp__shortcut-page-x
|
||||
[:header.relative
|
||||
[:h2.text-xs.opacity-70
|
||||
(str (t :keymap/total)
|
||||
" "
|
||||
(if ready?
|
||||
(apply + (map #(count (second %)) result-list-map))
|
||||
" ..."))]
|
||||
|
||||
(pane-controls q set-q! filters set-filters! keystroke set-keystroke! toggle-categories!)]
|
||||
|
||||
[:article
|
||||
(when-not ready?
|
||||
[:p.py-8.flex.justify-center (ui/loading "")])
|
||||
|
||||
(when ready?
|
||||
[:ul.list-none.m-0.py-3
|
||||
(for [[c binding-map] result-list-map
|
||||
:let [folded? (contains? folded-categories c)]]
|
||||
[:<>
|
||||
;; category row
|
||||
(when (and (not in-query?)
|
||||
(not in-filters?)
|
||||
(not in-keystroke?))
|
||||
[:li.flex.justify-between.th
|
||||
{:key (str c)
|
||||
:on-click #(let [f (if folded? disj conj)]
|
||||
(set-folded-categories! (f folded-categories c)))}
|
||||
[:strong.font-semibold (t c)]
|
||||
[:i.flex.items-center
|
||||
(ui/icon (if folded? "chevron-left" "chevron-down"))]])
|
||||
|
||||
;; binding row
|
||||
(when (or in-query? in-filters? (not folded?))
|
||||
(for [[id {:keys [binding user-binding] :as m}] binding-map
|
||||
:let [binding (to-vector binding)
|
||||
user-binding (and user-binding (to-vector user-binding))
|
||||
label (shortcut-desc-label id m)
|
||||
custom? (not (nil? user-binding))
|
||||
disabled? (or (false? user-binding)
|
||||
(false? (first binding)))
|
||||
unset? (and (not disabled?)
|
||||
(= user-binding []))]]
|
||||
|
||||
(when (or (nil? (seq filters))
|
||||
(when (contains? filters :Custom) custom?)
|
||||
(when (contains? filters :Disabled) disabled?)
|
||||
(when (contains? filters :Unset) unset?))
|
||||
|
||||
;; keystrokes filter
|
||||
(when (or (not in-keystroke?)
|
||||
(and (not disabled?)
|
||||
(not unset?)
|
||||
(let [binding' (or user-binding binding)
|
||||
keystroke' (some-> (shortcut-utils/safe-parse-string-binding keystroke) (bean/->clj))]
|
||||
(when (sequential? binding')
|
||||
(some #(when-let [s (some-> % (dh/mod-key) (shortcut-utils/safe-parse-string-binding) (bean/->clj))]
|
||||
(or (= s keystroke')
|
||||
(and (sequential? s) (sequential? keystroke')
|
||||
(apply = (map first [s keystroke']))))) binding')))))
|
||||
|
||||
[:li.flex.items-center.justify-between.text-sm
|
||||
{:key (str id)}
|
||||
[:span.label-wrap label]
|
||||
|
||||
[:a.action-wrap
|
||||
{:class (util/classnames [{:disabled disabled?}])
|
||||
:on-click (when-not disabled?
|
||||
#(open-customize-shortcut-dialog! id))}
|
||||
|
||||
(cond
|
||||
(or user-binding (false? user-binding))
|
||||
[:code.dark:bg-green-800.bg-green-300
|
||||
(if unset?
|
||||
(t :keymap/unset)
|
||||
(str (t :keymap/custom) ": "
|
||||
(if disabled?
|
||||
(t :keymap/disabled)
|
||||
(bean/->js
|
||||
(map #(if (false? %)
|
||||
(t :keymap/disabled)
|
||||
(shortcut-utils/decorate-binding %)) user-binding)))))]
|
||||
|
||||
(not unset?)
|
||||
(for [x binding]
|
||||
[:code.tracking-wide
|
||||
{:key (str x)}
|
||||
(dh/binding-for-display id x)]))]]))))])])]]))
|
||||
@@ -92,7 +92,7 @@
|
||||
(rum/use-effect!
|
||||
#(state/set-modal!
|
||||
(when settings-open?
|
||||
(fn [] [:div.settings-modal (settings/settings)])))
|
||||
(fn [] [:div.settings-modal (settings/settings settings-open?)])))
|
||||
[settings-open?])
|
||||
|
||||
(rum/use-effect!
|
||||
|
||||
@@ -292,7 +292,7 @@
|
||||
(tldraw-app page-name block-id)]))
|
||||
|
||||
(rum/defc whiteboard-route <
|
||||
(shortcut/mixin :shortcut.handler/whiteboard)
|
||||
(shortcut/mixin :shortcut.handler/whiteboard false)
|
||||
[route-match]
|
||||
(let [name (get-in route-match [:parameters :path :name])
|
||||
{:keys [block-id]} (get-in route-match [:parameters :query])]
|
||||
|
||||
@@ -988,6 +988,11 @@
|
||||
{:set-dirty-hls! set-dirty-hls!
|
||||
:set-hls-extra! set-hls-extra!}) "pdf-viewer")])))])))
|
||||
|
||||
(rum/defc pdf-container-outer
|
||||
< (shortcut/mixin :shortcut.handler/pdf false)
|
||||
[child]
|
||||
[:<> child])
|
||||
|
||||
(rum/defc pdf-container
|
||||
[{:keys [identity] :as pdf-current}]
|
||||
(let [[prepared set-prepared!] (rum/use-state false)
|
||||
@@ -1029,8 +1034,7 @@
|
||||
|
||||
(rum/defcs default-embed-playground
|
||||
< rum/static rum/reactive
|
||||
(shortcut/mixin :shortcut.handler/pdf)
|
||||
[]
|
||||
[state]
|
||||
(let [pdf-current (state/sub :pdf/current)
|
||||
system-win? (state/sub :pdf/system-win?)]
|
||||
[:div.extensions__pdf-playground
|
||||
@@ -1040,8 +1044,9 @@
|
||||
|
||||
(when (and (not system-win?) pdf-current)
|
||||
(js/ReactDOM.createPortal
|
||||
(pdf-container pdf-current)
|
||||
(js/document.querySelector "#app-single-container")))]))
|
||||
(pdf-container-outer
|
||||
(pdf-container pdf-current))
|
||||
(js/document.querySelector "#app-single-container")))]))
|
||||
|
||||
(rum/defcs system-embed-playground
|
||||
< rum/reactive
|
||||
|
||||
@@ -512,7 +512,7 @@
|
||||
[:div.my-3 (ui/button "Review cards" :small? true)])]))))
|
||||
|
||||
(rum/defc view-modal <
|
||||
(shortcut/mixin :shortcut.handler/cards)
|
||||
(shortcut/mixin :shortcut.handler/cards false)
|
||||
[blocks option card-index]
|
||||
[:div#cards-modal
|
||||
(if (seq blocks)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"System-component-like ns for command palette's functionality"
|
||||
(:require [cljs.spec.alpha :as s]
|
||||
[frontend.modules.shortcut.data-helper :as shortcut-helper]
|
||||
[frontend.handler.plugin :as plugin-handler]
|
||||
[frontend.spec :as spec]
|
||||
[frontend.state :as state]
|
||||
[lambdaisland.glogi :as log]
|
||||
@@ -50,10 +51,10 @@
|
||||
(defn add-history [{:keys [id]}]
|
||||
(storage/set "commands-history" (conj (history) {:id id :timestamp (.getTime (js/Date.))})))
|
||||
|
||||
(defn invoke-command [{:keys [action] :as cmd}]
|
||||
(defn invoke-command [{:keys [id action] :as cmd}]
|
||||
(add-history cmd)
|
||||
(state/close-modal!)
|
||||
(action))
|
||||
(plugin-handler/hook-lifecycle-fn! id action))
|
||||
|
||||
(defn top-commands [limit]
|
||||
(->> (get-commands)
|
||||
|
||||
@@ -18,6 +18,9 @@
|
||||
(repo-config-handler/read-repo-config content)
|
||||
(let [result (parse-repo-config content)
|
||||
ks (if (vector? k) k [k])
|
||||
v (cond->> v
|
||||
(map? v)
|
||||
(reduce-kv (fn [a k v] (rewrite/assoc a k v)) (rewrite/parse-string "{}")))
|
||||
new-result (rewrite/assoc-in result ks v)
|
||||
new-content (str new-result)]
|
||||
(file-handler/set-file-content! repo path new-content) nil))))
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
[frontend.components.shell :as shell]
|
||||
[frontend.components.whiteboard :as whiteboard]
|
||||
[frontend.components.user.login :as login]
|
||||
[frontend.components.shortcut :as shortcut]
|
||||
[frontend.config :as config]
|
||||
[frontend.context.i18n :refer [t]]
|
||||
[frontend.db :as db]
|
||||
@@ -457,8 +456,8 @@
|
||||
(commands/exec-plugin-simple-command! pid cmd action))
|
||||
|
||||
(defmethod handle :shortcut-handler-refreshed [[_]]
|
||||
(when-not @st/*inited?
|
||||
(reset! st/*inited? true)
|
||||
(when-not @st/*pending-inited?
|
||||
(reset! st/*pending-inited? true)
|
||||
(st/consume-pending-shortcuts!)))
|
||||
|
||||
(defmethod handle :mobile/keyboard-will-show [[_ keyboard-height]]
|
||||
@@ -936,10 +935,8 @@
|
||||
(defmethod handle :editor/quick-capture [[_ args]]
|
||||
(quick-capture/quick-capture args))
|
||||
|
||||
(defmethod handle :modal/keymap-manager [[_]]
|
||||
(state/set-modal!
|
||||
#(shortcut/keymap-pane)
|
||||
{:label "keymap-manager"}))
|
||||
(defmethod handle :modal/keymap [[_]]
|
||||
(state/open-settings! :keymap))
|
||||
|
||||
(defmethod handle :editor/toggle-own-number-list [[_ blocks]]
|
||||
(let [batch? (sequential? blocks)
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
[shadow.resource :as rc]
|
||||
[clojure.edn :as edn]
|
||||
[electron.ipc :as ipc]
|
||||
[borkdude.rewrite-edn :as rewrite]
|
||||
[logseq.common.path :as path]))
|
||||
|
||||
;; Use defonce to avoid broken state on dev reload
|
||||
@@ -38,7 +39,7 @@
|
||||
(defn set-global-config-state!
|
||||
[content]
|
||||
(let [config (edn/read-string content)]
|
||||
(state/set-global-config! config)
|
||||
(state/set-global-config! config content)
|
||||
config))
|
||||
|
||||
(def default-content (rc/inline "templates/global-config.edn"))
|
||||
@@ -59,6 +60,22 @@
|
||||
(p/let [config-content (fs/read-file nil config-path)]
|
||||
(set-global-config-state! config-content))))
|
||||
|
||||
(defn set-global-config-kv!
|
||||
[k v]
|
||||
(let [result (rewrite/parse-string
|
||||
(or (state/get-global-config-str-content) "{}"))
|
||||
ks (if (sequential? k) k [k])
|
||||
v (cond->> v
|
||||
(map? v)
|
||||
(reduce-kv (fn [a k v] (rewrite/assoc a k v)) (rewrite/parse-string "{}")))
|
||||
new-result (if (and (= 1 (count ks))
|
||||
(nil? v))
|
||||
(rewrite/dissoc result (first ks))
|
||||
(rewrite/assoc-in result ks v))
|
||||
new-str-content (str new-result)]
|
||||
(fs/write-file! nil nil (global-config-path) new-str-content {:skip-compare? true})
|
||||
(state/set-global-config! (rewrite/sexpr new-result) new-str-content)))
|
||||
|
||||
(defn start
|
||||
"This component has four responsibilities on start:
|
||||
- Fetch root-dir for later use with config paths
|
||||
|
||||
@@ -18,11 +18,13 @@
|
||||
([content]
|
||||
(show! content :info true nil 2000 nil))
|
||||
([content status]
|
||||
(show! content status true nil 1500 nil))
|
||||
(show! content status (not= status :error) nil 1500 nil))
|
||||
([content status clear?]
|
||||
(show! content status clear? nil 1500 nil))
|
||||
([content status clear? uid]
|
||||
(show! content status clear? uid 1500 nil))
|
||||
([content status clear? uid timeout]
|
||||
(show! content status clear? uid timeout nil))
|
||||
([content status clear? uid timeout close-cb]
|
||||
(let [contents (state/get-notification-contents)
|
||||
uid (or uid (keyword (util/unique-id)))]
|
||||
@@ -31,7 +33,7 @@
|
||||
:status status
|
||||
:close-cb close-cb}))
|
||||
|
||||
(when (and clear? (not= status :error))
|
||||
(js/setTimeout #(clear! uid) (or timeout 1500)))
|
||||
(when (and clear? (or timeout (not= status :error)))
|
||||
(js/setTimeout #(clear! uid) (or timeout 2000)))
|
||||
|
||||
uid)))
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
[logseq.graph-parser.mldoc :as gp-mldoc]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.handler.common.plugin :as plugin-common-handler]
|
||||
[frontend.modules.shortcut.utils :as shortcut-utils]
|
||||
[frontend.storage :as storage]
|
||||
[camel-snake-kebab.core :as csk]
|
||||
[frontend.state :as state]
|
||||
@@ -175,7 +176,7 @@
|
||||
|
||||
(defn has-setting-schema?
|
||||
[id]
|
||||
(when-let [pl (and id (get-plugin-inst (name id)))]
|
||||
(when-let [^js pl (and id (get-plugin-inst (name id)))]
|
||||
(boolean (.-settingsSchema pl))))
|
||||
|
||||
(defn get-enabled-plugins-if-setting-schema
|
||||
@@ -297,7 +298,7 @@
|
||||
(let [id (keyword (str "plugin." pid "/" key))
|
||||
binding (:binding keybinding)
|
||||
binding (some->> (if (string? binding) [binding] (seq binding))
|
||||
(map util/normalize-user-keyname))
|
||||
(map shortcut-utils/undecorate-binding))
|
||||
binding (if util/mac?
|
||||
(or (:mac keybinding) binding) binding)
|
||||
mode (or (:mode keybinding) :global)
|
||||
@@ -658,6 +659,15 @@
|
||||
:remove disj)]
|
||||
(save-plugin-preferences! {:pinnedToolbarItems (op-fn pinned (name key))}))))
|
||||
|
||||
(defn hook-lifecycle-fn!
|
||||
[type f & args]
|
||||
(when (and type (fn? f))
|
||||
(when config/lsp-enabled?
|
||||
(hook-plugin-app (str :before-command-invoked type) nil))
|
||||
(apply f args)
|
||||
(when config/lsp-enabled?
|
||||
(hook-plugin-app (str :after-command-invoked type) nil))))
|
||||
|
||||
;; components
|
||||
(rum/defc lsp-indicator < rum/reactive
|
||||
[]
|
||||
@@ -788,7 +798,6 @@
|
||||
(callback)
|
||||
(init-plugins! callback)))
|
||||
|
||||
|
||||
(comment
|
||||
{:pending (count (:plugin/updates-pending @state/state))
|
||||
:auto-checking? (boolean (:plugin/updates-auto-checking? @state/state))
|
||||
|
||||
@@ -29,31 +29,33 @@
|
||||
|
||||
(defn hide-when-esc-or-outside
|
||||
[state & {:keys [on-hide node visibilitychange? outside?]}]
|
||||
(try
|
||||
(let [dom-node (rum/dom-node state)]
|
||||
(when-let [dom-node (or node dom-node)]
|
||||
(let [click-fn (fn [e]
|
||||
(let [target (.. e -target)]
|
||||
;; If the click target is outside of current node
|
||||
(when (and
|
||||
(not (dom/contains dom-node target))
|
||||
(not (.contains (.-classList target) "ignore-outside-event")))
|
||||
(on-hide state e :click))))]
|
||||
(when-not (false? outside?)
|
||||
(listen state js/window "mousedown" click-fn)))
|
||||
(listen state js/window "keydown"
|
||||
(fn [e]
|
||||
(case (.-keyCode e)
|
||||
;; Esc
|
||||
27 (on-hide state e :esc)
|
||||
nil)))
|
||||
(when visibilitychange?
|
||||
(listen state js/window "visibilitychange"
|
||||
(let [opts (last (:rum/args state))
|
||||
outside? (cond-> opts (nil? outside?) (:outside?))]
|
||||
(try
|
||||
(let [dom-node (rum/dom-node state)]
|
||||
(when-let [dom-node (or node dom-node)]
|
||||
(let [click-fn (fn [e]
|
||||
(let [target (.. e -target)]
|
||||
;; If the click target is outside of current node
|
||||
(when (and
|
||||
(not (dom/contains dom-node target))
|
||||
(not (.contains (.-classList target) "ignore-outside-event")))
|
||||
(on-hide state e :click))))]
|
||||
(when-not (false? outside?)
|
||||
(listen state js/window "mousedown" click-fn)))
|
||||
(listen state js/window "keydown"
|
||||
(fn [e]
|
||||
(on-hide state e :visibilitychange))))))
|
||||
(catch :default _e
|
||||
;; TODO: Unable to find node on an unmounted component.
|
||||
nil)))
|
||||
(case (.-keyCode e)
|
||||
;; Esc
|
||||
27 (on-hide state e :esc)
|
||||
nil)))
|
||||
(when visibilitychange?
|
||||
(listen state js/window "visibilitychange"
|
||||
(fn [e]
|
||||
(on-hide state e :visibilitychange))))))
|
||||
(catch :default _e
|
||||
;; TODO: Unable to find node on an unmounted component.
|
||||
nil))))
|
||||
|
||||
(defn on-enter
|
||||
[state & {:keys [on-enter node]}]
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,12 @@
|
||||
(ns frontend.modules.shortcut.core
|
||||
(:require [clojure.string :as str]
|
||||
[frontend.handler.config :as config-handler]
|
||||
[frontend.handler.global-config :as global-config-handler]
|
||||
[frontend.handler.plugin :as plugin-handler]
|
||||
[frontend.handler.notification :as notification]
|
||||
[frontend.modules.shortcut.data-helper :as dh]
|
||||
[frontend.modules.shortcut.config :as shortcut-config]
|
||||
[frontend.modules.shortcut.utils :as shortcut-utils]
|
||||
[frontend.state :as state]
|
||||
[frontend.util :as util]
|
||||
[goog.events :as events]
|
||||
@@ -13,15 +16,15 @@
|
||||
(:import [goog.events KeyCodes KeyHandler KeyNames]
|
||||
[goog.ui KeyboardShortcutHandler]))
|
||||
|
||||
(def *installed (atom {}))
|
||||
(def *inited? (atom false))
|
||||
(def *pending (atom []))
|
||||
(defonce *installed-handlers (atom {}))
|
||||
(defonce *pending-inited? (atom false))
|
||||
(defonce *pending-shortcuts (atom []))
|
||||
|
||||
(def global-keys #js
|
||||
[KeyCodes/TAB
|
||||
KeyCodes/ENTER
|
||||
KeyCodes/BACKSPACE KeyCodes/DELETE
|
||||
KeyCodes/UP KeyCodes/LEFT KeyCodes/DOWN KeyCodes/RIGHT])
|
||||
[KeyCodes/TAB
|
||||
KeyCodes/ENTER
|
||||
KeyCodes/BACKSPACE KeyCodes/DELETE
|
||||
KeyCodes/UP KeyCodes/LEFT KeyCodes/DOWN KeyCodes/RIGHT])
|
||||
|
||||
(def key-names (js->clj KeyNames))
|
||||
|
||||
@@ -29,16 +32,25 @@
|
||||
|
||||
(defn consume-pending-shortcuts!
|
||||
[]
|
||||
(when (and @*inited? (seq @*pending))
|
||||
(doseq [[handler-id id shortcut] @*pending]
|
||||
(when (and @*pending-inited? (seq @*pending-shortcuts))
|
||||
(doseq [[handler-id id shortcut] @*pending-shortcuts]
|
||||
(register-shortcut! handler-id id shortcut))
|
||||
(reset! *pending [])))
|
||||
(reset! *pending-shortcuts [])))
|
||||
|
||||
(defn- get-handler-by-id
|
||||
[handler-id]
|
||||
(-> (filter #(= (:group %) handler-id) (vals @*installed))
|
||||
first
|
||||
:handler))
|
||||
(->> (vals @*installed-handlers)
|
||||
(filter #(= (:group %) handler-id))
|
||||
first
|
||||
:handler))
|
||||
|
||||
(defn- get-installed-ids-by-handler-id
|
||||
[handler-id]
|
||||
(some->> @*installed-handlers
|
||||
(filter #(= (:group (second %)) handler-id))
|
||||
(map first)
|
||||
(remove nil?)
|
||||
(vec)))
|
||||
|
||||
(defn register-shortcut!
|
||||
"Register a shortcut, notice the id need to be a namespaced keyword to avoid
|
||||
@@ -50,14 +62,14 @@
|
||||
([handler-id id]
|
||||
(register-shortcut! handler-id id nil))
|
||||
([handler-id id shortcut-map]
|
||||
(if (and (keyword? handler-id) (not @*inited?))
|
||||
(swap! *pending conj [handler-id id shortcut-map])
|
||||
(when-let [handler (if (or (string? handler-id) (keyword? handler-id))
|
||||
(let [handler-id (keyword handler-id)]
|
||||
(get-handler-by-id handler-id))
|
||||
(if (and (keyword? handler-id) (not @*pending-inited?))
|
||||
(swap! *pending-shortcuts conj [handler-id id shortcut-map])
|
||||
(when-let [^js handler (if (or (string? handler-id) (keyword? handler-id))
|
||||
(let [handler-id (keyword handler-id)]
|
||||
(get-handler-by-id handler-id))
|
||||
|
||||
;; handler
|
||||
handler-id)]
|
||||
;; as Handler instance
|
||||
handler-id)]
|
||||
|
||||
(when shortcut-map
|
||||
(shortcut-config/add-shortcut! handler-id id shortcut-map))
|
||||
@@ -66,7 +78,7 @@
|
||||
(doseq [k (dh/shortcut-binding id)]
|
||||
(try
|
||||
(log/debug :shortcut/register-shortcut {:id id :binding k})
|
||||
(.registerShortcut handler (util/keyname id) (util/normalize-user-keyname k))
|
||||
(.registerShortcut handler (util/keyname id) (shortcut-utils/undecorate-binding k))
|
||||
(catch :default e
|
||||
(log/error :shortcut/register-shortcut {:id id
|
||||
:binding k
|
||||
@@ -81,15 +93,17 @@
|
||||
(when-let [handler (get-handler-by-id handler-id)]
|
||||
(when-let [ks (dh/shortcut-binding shortcut-id)]
|
||||
(doseq [k ks]
|
||||
(.unregisterShortcut ^js handler (util/normalize-user-keyname k))))
|
||||
(.unregisterShortcut ^js handler (shortcut-utils/undecorate-binding k))))
|
||||
(shortcut-config/remove-shortcut! handler-id shortcut-id)))
|
||||
|
||||
(defn uninstall-shortcut-handler!
|
||||
[install-id]
|
||||
(when-let [handler (-> (get @*installed install-id)
|
||||
:handler)]
|
||||
(.dispose ^js handler)
|
||||
(swap! *installed dissoc install-id)))
|
||||
([install-id] (uninstall-shortcut-handler! install-id false))
|
||||
([install-id refresh?]
|
||||
(when-let [handler (-> (get @*installed-handlers install-id)
|
||||
:handler)]
|
||||
(.dispose ^js handler)
|
||||
(js/console.debug "[shortcuts]" "uninstall handler" (-> @*installed-handlers (get install-id) :group str) (if refresh? "*" ""))
|
||||
(swap! *installed-handlers dissoc install-id))))
|
||||
|
||||
(defn install-shortcut-handler!
|
||||
[handler-id {:keys [set-global-keys?
|
||||
@@ -97,11 +111,15 @@
|
||||
state]
|
||||
:or {set-global-keys? true
|
||||
prevent-default? false}}]
|
||||
(when-let [install-id (get-handler-by-id handler-id)]
|
||||
(uninstall-shortcut-handler! install-id))
|
||||
|
||||
;; force uninstall existed handler
|
||||
(some->>
|
||||
(get-installed-ids-by-handler-id handler-id)
|
||||
(map #(uninstall-shortcut-handler! % true))
|
||||
(doall))
|
||||
|
||||
(let [shortcut-map (dh/shortcut-map handler-id state)
|
||||
handler (new KeyboardShortcutHandler js/window)]
|
||||
handler (new KeyboardShortcutHandler js/window)]
|
||||
;; set arrows enter, tab to global
|
||||
(when set-global-keys?
|
||||
(.setGlobalKeys handler global-keys))
|
||||
@@ -114,66 +132,109 @@
|
||||
(register-shortcut! handler id))
|
||||
|
||||
(let [f (fn [e]
|
||||
(let [shortcut-map (dh/shortcut-map handler-id state)
|
||||
dispatch-fn (get shortcut-map (keyword (.-identifier e)))]
|
||||
(let [id (keyword (.-identifier e))
|
||||
shortcut-map (dh/shortcut-map handler-id state) ;; required to get shortcut map dynamically
|
||||
dispatch-fn (get shortcut-map id)]
|
||||
;; trigger fn
|
||||
(when dispatch-fn (dispatch-fn e))))
|
||||
(when dispatch-fn
|
||||
(plugin-handler/hook-lifecycle-fn! id dispatch-fn e))))
|
||||
install-id (random-uuid)
|
||||
data {install-id
|
||||
{:group handler-id
|
||||
:dispatch-fn f
|
||||
:handler handler}}]
|
||||
data {install-id
|
||||
{:group handler-id
|
||||
:dispatch-fn f
|
||||
:handler handler}}]
|
||||
|
||||
(.listen handler EventType/SHORTCUT_TRIGGERED f)
|
||||
|
||||
(swap! *installed merge data)
|
||||
(js/console.debug "[shortcuts] install handler" (str handler-id))
|
||||
(swap! *installed-handlers merge data)
|
||||
|
||||
install-id)))
|
||||
|
||||
(defn- install-shortcuts!
|
||||
[]
|
||||
(->> [:shortcut.handler/misc
|
||||
:shortcut.handler/editor-global
|
||||
:shortcut.handler/global-non-editing-only
|
||||
:shortcut.handler/global-prevent-default]
|
||||
[handler-ids]
|
||||
(->> (or (seq handler-ids)
|
||||
[:shortcut.handler/misc
|
||||
:shortcut.handler/editor-global
|
||||
:shortcut.handler/global-non-editing-only
|
||||
:shortcut.handler/global-prevent-default])
|
||||
(map #(install-shortcut-handler! % {}))
|
||||
doall))
|
||||
|
||||
(defn mixin [handler-id]
|
||||
(defn mixin
|
||||
([handler-id] (mixin handler-id true))
|
||||
([handler-id remount-reinstall?]
|
||||
(cond->
|
||||
{:did-mount
|
||||
(fn [state]
|
||||
(let [install-id (install-shortcut-handler! handler-id {:state state})]
|
||||
(assoc state ::install-id install-id)))
|
||||
|
||||
:will-unmount
|
||||
(fn [state]
|
||||
(when-let [install-id (::install-id state)]
|
||||
(uninstall-shortcut-handler! install-id))
|
||||
state)}
|
||||
|
||||
remount-reinstall?
|
||||
(assoc
|
||||
:will-remount
|
||||
(fn [old-state new-state]
|
||||
(util/profile "[shortcuts] reinstalled:"
|
||||
(uninstall-shortcut-handler! (::install-id old-state))
|
||||
(when-let [install-id (install-shortcut-handler! handler-id {:state new-state})]
|
||||
(assoc new-state ::install-id install-id))))))))
|
||||
|
||||
(defn mixin*
|
||||
"This is an optimized version compared to (mixin).
|
||||
And the shortcuts will not be frequently loaded and unloaded.
|
||||
As well as ensuring unnecessary updates of components."
|
||||
[handler-id]
|
||||
{:did-mount
|
||||
(fn [state]
|
||||
(let [install-id (install-shortcut-handler! handler-id {:state state})]
|
||||
(assoc state ::install-id install-id)))
|
||||
(let [*state (volatile! state)
|
||||
install-id (install-shortcut-handler! handler-id {:state *state})]
|
||||
(assoc state ::install-id install-id
|
||||
::*state *state)))
|
||||
|
||||
:will-remount
|
||||
(fn [old-state new-state]
|
||||
(when-let [*state (::*state old-state)]
|
||||
(vreset! *state new-state))
|
||||
new-state)
|
||||
|
||||
:will-remount (fn [old-state new-state]
|
||||
(uninstall-shortcut-handler! (::install-id old-state))
|
||||
(when-let [install-id (install-shortcut-handler! handler-id {:state new-state})]
|
||||
(assoc new-state ::install-id install-id)))
|
||||
:will-unmount
|
||||
(fn [state]
|
||||
(when-let [install-id (::install-id state)]
|
||||
(uninstall-shortcut-handler! install-id))
|
||||
(uninstall-shortcut-handler! install-id)
|
||||
(some-> (::*state state) (vreset! nil)))
|
||||
state)})
|
||||
|
||||
(defn unlisten-all []
|
||||
(doseq [{:keys [handler group]} (vals @*installed)
|
||||
:when (not= group :shortcut.handler/misc)]
|
||||
(.removeAllListeners handler)))
|
||||
(defn unlisten-all!
|
||||
([] (unlisten-all! false))
|
||||
([dispose?]
|
||||
(doseq [{:keys [handler group dispatch-fn]} (vals @*installed-handlers)
|
||||
:when (not= group :shortcut.handler/misc)]
|
||||
(if dispose?
|
||||
(.dispose handler)
|
||||
(events/unlisten handler EventType/SHORTCUT_TRIGGERED dispatch-fn)))))
|
||||
|
||||
(defn listen-all []
|
||||
(doseq [{:keys [handler group dispatch-fn]} (vals @*installed)
|
||||
(defn listen-all! []
|
||||
(doseq [{:keys [handler group dispatch-fn]} (vals @*installed-handlers)
|
||||
:when (not= group :shortcut.handler/misc)]
|
||||
(events/listen handler EventType/SHORTCUT_TRIGGERED dispatch-fn)))
|
||||
(if (.isDisposed handler)
|
||||
(install-shortcut-handler! group {})
|
||||
(events/listen handler EventType/SHORTCUT_TRIGGERED dispatch-fn))))
|
||||
|
||||
(def disable-all-shortcuts
|
||||
{:will-mount
|
||||
(fn [state]
|
||||
(unlisten-all)
|
||||
(unlisten-all!)
|
||||
state)
|
||||
|
||||
:will-unmount
|
||||
(fn [state]
|
||||
(listen-all)
|
||||
(listen-all!)
|
||||
state)})
|
||||
|
||||
(defn refresh-internal!
|
||||
@@ -182,27 +243,29 @@
|
||||
(when-not (:ui/shortcut-handler-refreshing? @state/state)
|
||||
(state/set-state! :ui/shortcut-handler-refreshing? true)
|
||||
|
||||
(doseq [id (keys @*installed)]
|
||||
(uninstall-shortcut-handler! id))
|
||||
(install-shortcuts!)
|
||||
(let [ids (keys @*installed-handlers)
|
||||
_handler-ids (set (map :group (vals @*installed-handlers)))]
|
||||
(doseq [id ids] (uninstall-shortcut-handler! id))
|
||||
;; TODO: should re-install existed handlers
|
||||
(install-shortcuts! nil))
|
||||
(state/pub-event! [:shortcut-handler-refreshed])
|
||||
(state/set-state! :ui/shortcut-handler-refreshing? false)))
|
||||
|
||||
(def refresh! (debounce refresh-internal! 1000))
|
||||
|
||||
(defn- name-with-meta [e]
|
||||
(let [ctrl (.-ctrlKey e)
|
||||
alt (.-altKey e)
|
||||
meta (.-metaKey e)
|
||||
shift (.-shiftKey e)
|
||||
(let [ctrl (.-ctrlKey e)
|
||||
alt (.-altKey e)
|
||||
meta (.-metaKey e)
|
||||
shift (.-shiftKey e)
|
||||
keyname (get key-names (str (.-keyCode e)))]
|
||||
(cond->> keyname
|
||||
ctrl (str "ctrl+")
|
||||
alt (str "alt+")
|
||||
meta (str "meta+")
|
||||
shift (str "shift+"))))
|
||||
ctrl (str "ctrl+")
|
||||
alt (str "alt+")
|
||||
meta (str "meta+")
|
||||
shift (str "shift+"))))
|
||||
|
||||
(defn- keyname [e]
|
||||
(defn keyname [e]
|
||||
(let [name (get key-names (str (.-keyCode e)))]
|
||||
(case name
|
||||
nil nil
|
||||
@@ -215,7 +278,7 @@
|
||||
(let [handler (KeyHandler. js/document)
|
||||
keystroke (:rum/local state)]
|
||||
|
||||
(doseq [id (keys @*installed)]
|
||||
(doseq [id (keys @*installed-handlers)]
|
||||
(uninstall-shortcut-handler! id))
|
||||
|
||||
(events/listen handler "key"
|
||||
@@ -240,6 +303,27 @@
|
||||
(when-let [^js handler (::key-record-handler state)]
|
||||
(.dispose handler))
|
||||
|
||||
;; force re-install shortcut handlers
|
||||
(js/setTimeout #(refresh!) 500)
|
||||
|
||||
(dissoc state ::key-record-handler))})
|
||||
|
||||
(defn persist-user-shortcut!
|
||||
[id binding]
|
||||
(let [graph-shortcuts (or (:shortcuts (state/get-graph-config)) {})
|
||||
global-shortcuts (or (:shortcuts (state/get-global-config)) {})
|
||||
global? true]
|
||||
(letfn [(into-shortcuts [shortcuts]
|
||||
(cond-> shortcuts
|
||||
(nil? binding)
|
||||
(dissoc id)
|
||||
|
||||
(and global?
|
||||
(or (string? binding)
|
||||
(vector? binding)
|
||||
(boolean? binding)))
|
||||
(assoc id binding)))]
|
||||
;; TODO: exclude current graph config shortcuts
|
||||
(when (nil? binding)
|
||||
(config-handler/set-config! :shortcuts (into-shortcuts graph-shortcuts)))
|
||||
(global-config-handler/set-global-config-kv! :shortcuts (into-shortcuts global-shortcuts)))))
|
||||
@@ -1,11 +1,14 @@
|
||||
(ns frontend.modules.shortcut.data-helper
|
||||
(:require [borkdude.rewrite-edn :as rewrite]
|
||||
[clojure.set :refer [rename-keys] :as set]
|
||||
[clojure.string :as str]
|
||||
[clojure.set :refer [rename-keys]]
|
||||
[cljs-bean.core :as bean]
|
||||
[frontend.context.i18n :refer [t]]
|
||||
[frontend.config :as config]
|
||||
[frontend.db :as db]
|
||||
[frontend.handler.file :as file]
|
||||
[frontend.modules.shortcut.config :as shortcut-config]
|
||||
[frontend.modules.shortcut.utils :as shortcut-utils]
|
||||
[frontend.state :as state]
|
||||
[frontend.util :as util]
|
||||
[lambdaisland.glogi :as log]
|
||||
@@ -13,29 +16,74 @@
|
||||
[frontend.handler.config :as config-handler])
|
||||
(:import [goog.ui KeyboardShortcutHandler]))
|
||||
|
||||
(declare get-group)
|
||||
|
||||
;; function vals->bindings is too time-consuming. Here we cache the results.
|
||||
(defn- flatten-key-bindings
|
||||
[config]
|
||||
(->> config
|
||||
(into {})
|
||||
(map (fn [[k {:keys [binding]}]]
|
||||
{k binding}))
|
||||
(defn- flatten-bindings-by-id
|
||||
[config user-shortcuts binding-only?]
|
||||
(->> (vals config)
|
||||
(apply merge)
|
||||
(map (fn [[id {:keys [binding] :as opts}]]
|
||||
{id (if binding-only?
|
||||
(get user-shortcuts id binding)
|
||||
(assoc opts :user-binding (get user-shortcuts id)
|
||||
:handler-id (get-group id)
|
||||
:id id))}))
|
||||
(into {})))
|
||||
|
||||
(def m-flatten-key-bindings (util/memoize-last flatten-key-bindings))
|
||||
(defn- flatten-bindings-by-key
|
||||
[config user-shortcuts]
|
||||
(reduce-kv
|
||||
(fn [r handler-id vs]
|
||||
(reduce-kv
|
||||
(fn [r id {:keys [binding]}]
|
||||
(if-let [ks (get user-shortcuts id binding)]
|
||||
(let [ks (if (sequential? ks) ks [ks])]
|
||||
(reduce (fn [a k]
|
||||
(let [k (shortcut-utils/undecorate-binding k)
|
||||
k' (shortcut-utils/safe-parse-string-binding k)
|
||||
k' (bean/->clj k')]
|
||||
(-> a
|
||||
(assoc-in [k' :key] k)
|
||||
(assoc-in [k' :refs id] handler-id)))) r ks))
|
||||
r)) r vs))
|
||||
{} config))
|
||||
|
||||
(def m-flatten-bindings-by-id
|
||||
(util/memoize-last flatten-bindings-by-id))
|
||||
|
||||
(def m-flatten-bindings-by-key
|
||||
(util/memoize-last flatten-bindings-by-key))
|
||||
|
||||
(defn get-bindings
|
||||
[]
|
||||
(m-flatten-key-bindings (vals @shortcut-config/config)))
|
||||
(m-flatten-bindings-by-id @shortcut-config/*config (state/shortcuts) true))
|
||||
|
||||
(defn- mod-key [shortcut]
|
||||
(str/replace shortcut #"(?i)mod"
|
||||
(if util/mac? "meta" "ctrl")))
|
||||
(defn get-bindings-keys-map
|
||||
[]
|
||||
(m-flatten-bindings-by-key @shortcut-config/*config (state/shortcuts)))
|
||||
|
||||
(defn get-bindings-ids-map
|
||||
[]
|
||||
(m-flatten-bindings-by-id @shortcut-config/*config (state/shortcuts) false))
|
||||
|
||||
(defn get-shortcut-desc
|
||||
[binding-map]
|
||||
(let [{:keys [id desc cmd]} binding-map
|
||||
desc (or desc (:desc cmd) (some-> id (shortcut-utils/decorate-namespace) (t)))]
|
||||
(if (or (nil? desc)
|
||||
(and (string? desc) (str/starts-with? desc "{Missing")))
|
||||
(str id) desc)))
|
||||
|
||||
(defn mod-key [shortcut]
|
||||
(when (string? shortcut)
|
||||
(str/replace shortcut #"(?i)mod"
|
||||
(if util/mac? "meta" "ctrl"))))
|
||||
|
||||
(defn shortcut-binding
|
||||
"override by user custom binding"
|
||||
[id]
|
||||
(let [shortcut (get (state/shortcuts) id
|
||||
(get (get-bindings) id))]
|
||||
(let [shortcut (get (get-bindings) id)]
|
||||
(cond
|
||||
(nil? shortcut)
|
||||
(log/warn :shortcut/binding-not-found {:id id})
|
||||
@@ -47,62 +95,48 @@
|
||||
|
||||
:else
|
||||
(->>
|
||||
(if (string? shortcut)
|
||||
[shortcut]
|
||||
shortcut)
|
||||
(mapv mod-key)))))
|
||||
(if (string? shortcut)
|
||||
[shortcut]
|
||||
shortcut)
|
||||
(mapv mod-key)))))
|
||||
|
||||
(defn shortcut-cmd
|
||||
[id]
|
||||
(get @shortcut-config/*shortcut-cmds id))
|
||||
|
||||
(defn shortcut-item
|
||||
[id]
|
||||
(get (get-bindings-ids-map) id))
|
||||
|
||||
;; returns a vector to preserve order
|
||||
(defn binding-by-category [name]
|
||||
(let [dict (->> (vals @shortcut-config/config)
|
||||
(apply merge)
|
||||
(map (fn [[k _]]
|
||||
{k {:binding (shortcut-binding k)}}))
|
||||
(into {}))
|
||||
(let [dict (get-bindings-ids-map)
|
||||
plugin? (= name :shortcut.category/plugins)]
|
||||
(->> (if plugin?
|
||||
(->> (keys dict) (filter #(str/starts-with? (str %) ":plugin.")))
|
||||
(shortcut-config/category name))
|
||||
(mapv (fn [k] [k (k dict)])))))
|
||||
(shortcut-config/get-category-shortcuts name))
|
||||
(mapv (fn [k] [k (assoc (get dict k) :category name)])))))
|
||||
|
||||
(defn shortcut-map
|
||||
([handler-id]
|
||||
(shortcut-map handler-id nil))
|
||||
([handler-id state]
|
||||
(let [raw (get @shortcut-config/config handler-id)
|
||||
(let [raw (get @shortcut-config/*config handler-id)
|
||||
handler-m (->> raw
|
||||
(map (fn [[k {:keys [fn]}]]
|
||||
{k fn}))
|
||||
(into {}))
|
||||
before (-> raw meta :before)]
|
||||
before (-> raw meta :before)]
|
||||
(cond->> handler-m
|
||||
state (reduce-kv (fn [r k handle-fn]
|
||||
(assoc r k (partial handle-fn state)))
|
||||
{})
|
||||
before (reduce-kv (fn [r k v]
|
||||
(assoc r k (before v)))
|
||||
{})))))
|
||||
|
||||
(defn decorate-namespace [k]
|
||||
(let [n (name k)
|
||||
ns (namespace k)]
|
||||
(keyword (str "command." ns) n)))
|
||||
|
||||
(defn decorate-binding [binding]
|
||||
(-> (if (string? binding) binding (str/join "+" binding))
|
||||
(str/replace "mod" (if util/mac? "⌘" "ctrl"))
|
||||
(str/replace "alt" (if util/mac? "opt" "alt"))
|
||||
(str/replace "shift+/" "?")
|
||||
(str/replace "left" "←")
|
||||
(str/replace "right" "→")
|
||||
(str/replace "shift" "⇧")
|
||||
(str/replace "open-square-bracket" "[")
|
||||
(str/replace "close-square-bracket" "]")
|
||||
(str/lower-case)))
|
||||
state (reduce-kv (fn [r k handle-fn]
|
||||
(let [handle-fn' (if (volatile? state)
|
||||
(fn [*state & args] (apply handle-fn (cons @*state args)))
|
||||
handle-fn)]
|
||||
(assoc r k (partial handle-fn' state))))
|
||||
{})
|
||||
before (reduce-kv (fn [r k v]
|
||||
(assoc r k (before v)))
|
||||
{})))))
|
||||
|
||||
;; if multiple bindings, gen seq for first binding only for now
|
||||
(defn gen-shortcut-seq [id]
|
||||
@@ -111,24 +145,24 @@
|
||||
[]
|
||||
(-> bindings
|
||||
first
|
||||
(str/split #" |\+")))))
|
||||
(str/split #" |\+")))))
|
||||
|
||||
(defn binding-for-display [k binding]
|
||||
(let [tmp (cond
|
||||
(false? binding)
|
||||
(cond
|
||||
(and util/mac? (= k :editor/kill-line-after)) "system default: ctrl+k"
|
||||
(and util/mac? (= k :editor/kill-line-after)) "system default: ctrl+k"
|
||||
(and util/mac? (= k :editor/beginning-of-block)) "system default: ctrl+a"
|
||||
(and util/mac? (= k :editor/end-of-block)) "system default: ctrl+e"
|
||||
(and util/mac? (= k :editor/end-of-block)) "system default: ctrl+e"
|
||||
(and util/mac? (= k :editor/backward-kill-word)) "system default: opt+delete"
|
||||
:else "disabled")
|
||||
:else (t :keymap/disabled))
|
||||
|
||||
(string? binding)
|
||||
(decorate-binding binding)
|
||||
(shortcut-utils/decorate-binding binding)
|
||||
|
||||
:else
|
||||
(->> binding
|
||||
(map decorate-binding)
|
||||
(map shortcut-utils/decorate-binding)
|
||||
(str/join " | ")))]
|
||||
|
||||
;; Display "cmd" rather than "meta" to the user to describe the Mac
|
||||
@@ -157,26 +191,92 @@
|
||||
"Given shortcut key, return handler group
|
||||
eg: :editor/new-line -> :shortcut.handler/block-editing-only"
|
||||
[k]
|
||||
(->> @shortcut-config/config
|
||||
(->> @shortcut-config/*config
|
||||
(filter (fn [[_ v]] (contains? v k)))
|
||||
(map key)
|
||||
(first)))
|
||||
|
||||
(defn potential-conflict? [k]
|
||||
(if-not (shortcut-binding k)
|
||||
(defn should-be-included-to-global-handler
|
||||
[from-handler-id]
|
||||
(if (contains? #{:shortcut.handler/pdf} from-handler-id)
|
||||
#{from-handler-id :shortcut.handler/global-prevent-default}
|
||||
#{from-handler-id}))
|
||||
|
||||
(defn get-conflicts-by-keys
|
||||
([ks] (get-conflicts-by-keys ks :shortcut.handler/global-prevent-default {:group-global? true}))
|
||||
([ks handler-id] (get-conflicts-by-keys ks handler-id {:group-global? true}))
|
||||
([ks handler-id {:keys [exclude-ids group-global?]}]
|
||||
(let [global-handlers #{:shortcut.handler/editor-global
|
||||
:shortcut.handler/global-non-editing-only
|
||||
:shortcut.handler/global-prevent-default
|
||||
:shortcut.handler/misc}
|
||||
ks-bindings (get-bindings-keys-map)
|
||||
handler-ids (should-be-included-to-global-handler handler-id)
|
||||
global? (when group-global? (seq (set/intersection global-handlers handler-ids)))]
|
||||
(->> (if (string? ks) [ks] ks)
|
||||
(map (fn [k]
|
||||
(when-let [k' (shortcut-utils/undecorate-binding k)]
|
||||
(let [k (shortcut-utils/safe-parse-string-binding k')
|
||||
k (bean/->clj k)
|
||||
|
||||
same-leading-key?
|
||||
(fn [[k' _]]
|
||||
(when (sequential? k)
|
||||
(or (= k k')
|
||||
(and (> (count k') (count k))
|
||||
(= (first k) (first k'))))))
|
||||
|
||||
into-conflict-refs
|
||||
(fn [[k o]]
|
||||
(when-let [{:keys [key refs]} o]
|
||||
[k [key (reduce-kv (fn [r id handler-id']
|
||||
(if (and
|
||||
(not (contains? exclude-ids id))
|
||||
(or (= handler-ids #{handler-id'})
|
||||
(and (set? handler-ids) (contains? handler-ids handler-id'))
|
||||
(and global? (contains? global-handlers handler-id'))))
|
||||
(assoc r id handler-id')
|
||||
r)
|
||||
) {} refs)]]))]
|
||||
|
||||
[k' (->> ks-bindings
|
||||
(filterv same-leading-key?)
|
||||
(mapv into-conflict-refs)
|
||||
(remove #(empty? (second (second %1))))
|
||||
(into {}))]
|
||||
))))
|
||||
(remove #(empty? (vals (second %1))))
|
||||
(into {})))))
|
||||
|
||||
(defn parse-conflicts-from-binding
|
||||
[from-binding target]
|
||||
(when-let [from-binding (and (string? target)
|
||||
(sequential? from-binding)
|
||||
(seq from-binding))]
|
||||
(when-let [target (some-> target (mod-key) (shortcut-utils/safe-parse-string-binding) (bean/->clj))]
|
||||
(->> from-binding
|
||||
(filterv
|
||||
#(when-let [from (some-> % (mod-key) (shortcut-utils/safe-parse-string-binding) (bean/->clj))]
|
||||
(or (= from target)
|
||||
(and (or (= (count from) 1)
|
||||
(= (count target) 1))
|
||||
(= (first target) (first from))))))))))
|
||||
|
||||
(defn potential-conflict? [shortcut-id]
|
||||
(if-not (shortcut-binding shortcut-id)
|
||||
false
|
||||
(let [handler-id (get-group k)
|
||||
shortcut-m (shortcut-map handler-id)
|
||||
(let [handler-id (get-group shortcut-id)
|
||||
shortcut-m (shortcut-map handler-id)
|
||||
parse-shortcut #(try
|
||||
(KeyboardShortcutHandler/parseStringShortcut %)
|
||||
(catch :default e
|
||||
(js/console.error "[shortcut/parse-error]" (str % " - " (.-message e)))))
|
||||
bindings (->> (shortcut-binding k)
|
||||
(map mod-key)
|
||||
(map parse-shortcut)
|
||||
(map js->clj))
|
||||
(KeyboardShortcutHandler/parseStringShortcut %)
|
||||
(catch :default e
|
||||
(js/console.error "[shortcut/parse-error]" (str % " - " (.-message e)))))
|
||||
bindings (->> (shortcut-binding shortcut-id)
|
||||
(map mod-key)
|
||||
(map parse-shortcut)
|
||||
(map js->clj))
|
||||
rest-bindings (->> (map key shortcut-m)
|
||||
(remove #{k})
|
||||
(remove #{shortcut-id})
|
||||
(map shortcut-binding)
|
||||
(filter vector?)
|
||||
(mapcat identity)
|
||||
@@ -188,16 +288,16 @@
|
||||
|
||||
(defn shortcut-data-by-id [id]
|
||||
(let [binding (shortcut-binding id)
|
||||
data (->> (vals @shortcut-config/config)
|
||||
(into {})
|
||||
id)]
|
||||
data (->> (vals @shortcut-config/*config)
|
||||
(into {})
|
||||
id)]
|
||||
(assoc
|
||||
data
|
||||
:binding
|
||||
(binding-for-display id binding))))
|
||||
|
||||
(defn shortcuts->commands [handler-id]
|
||||
(let [m (get @shortcut-config/config handler-id)]
|
||||
(let [m (get @shortcut-config/*config handler-id)]
|
||||
(->> m
|
||||
(map (fn [[id _]] (-> (shortcut-data-by-id id)
|
||||
(assoc :id id :handler-id handler-id)
|
||||
|
||||
58
src/main/frontend/modules/shortcut/utils.cljs
Normal file
58
src/main/frontend/modules/shortcut/utils.cljs
Normal file
@@ -0,0 +1,58 @@
|
||||
(ns frontend.modules.shortcut.utils
|
||||
(:require [clojure.string :as str]
|
||||
[frontend.util :as util])
|
||||
(:import [goog.ui KeyboardShortcutHandler]))
|
||||
|
||||
(defn safe-parse-string-binding
|
||||
[binding]
|
||||
(try
|
||||
(KeyboardShortcutHandler/parseStringShortcut binding)
|
||||
(catch js/Error e
|
||||
(js/console.warn "[shortcuts] parse key error: " e) binding)))
|
||||
|
||||
(defn mod-key [binding]
|
||||
(str/replace binding #"(?i)mod"
|
||||
(if util/mac? "meta" "ctrl")))
|
||||
|
||||
(defn undecorate-binding
|
||||
[binding]
|
||||
(when (string? binding)
|
||||
(let [keynames {";" "semicolon"
|
||||
"=" "equals"
|
||||
"-" "dash"
|
||||
"[" "open-square-bracket"
|
||||
"]" "close-square-bracket"
|
||||
"'" "single-quote"
|
||||
"(" "shift+9"
|
||||
")" "shift+0"
|
||||
"~" "shift+`"
|
||||
"⇧" "shift"
|
||||
"←" "left"
|
||||
"→" "right"}]
|
||||
(-> binding
|
||||
(str/replace #"[;=-\[\]'\(\)\~\→\←\⇧]" #(get keynames %))
|
||||
(str/replace #"\s+" " ")
|
||||
(mod-key)
|
||||
(str/lower-case)))))
|
||||
|
||||
(defn decorate-namespace [k]
|
||||
(let [n (name k)
|
||||
ns (namespace k)]
|
||||
(keyword (str "command." ns) n)))
|
||||
|
||||
(defn decorate-binding [binding]
|
||||
(when (or (string? binding)
|
||||
(sequential? binding))
|
||||
(-> (if (string? binding) binding (str/join "+" binding))
|
||||
(str/replace "mod" (if util/mac? "⌘" "ctrl"))
|
||||
(str/replace "meta" (if util/mac? "⌘" "⊞ win"))
|
||||
(str/replace "alt" (if util/mac? "opt" "alt"))
|
||||
(str/replace "shift+/" "?")
|
||||
(str/replace "left" "←")
|
||||
(str/replace "right" "→")
|
||||
(str/replace "shift" "⇧")
|
||||
(str/replace "open-square-bracket" "[")
|
||||
(str/replace "close-square-bracket" "]")
|
||||
(str/replace "equals" "=")
|
||||
(str/replace "semicolon" ";")
|
||||
(str/lower-case))))
|
||||
@@ -9,7 +9,6 @@
|
||||
[frontend.components.repo :as repo]
|
||||
[frontend.components.search :as search]
|
||||
[frontend.components.settings :as settings]
|
||||
[frontend.components.shortcut :as shortcut]
|
||||
[frontend.components.whiteboard :as whiteboard]
|
||||
[frontend.extensions.zotero :as zotero]
|
||||
[frontend.components.bug-report :as bug-report]
|
||||
@@ -69,10 +68,6 @@
|
||||
{:name :settings
|
||||
:view settings/settings}]
|
||||
|
||||
["/settings/shortcut"
|
||||
{:name :shortcut-setting
|
||||
:view shortcut/shortcut-page}]
|
||||
|
||||
["/settings/zotero"
|
||||
{:name :zotero-setting
|
||||
:view zotero/settings}]
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
:modal/label ""
|
||||
:modal/show? false
|
||||
:modal/panel-content nil
|
||||
:modal/payload nil
|
||||
:modal/fullscreen? false
|
||||
:modal/close-btn? nil
|
||||
:modal/close-backdrop? true
|
||||
@@ -344,6 +345,18 @@
|
||||
(merge current new)
|
||||
new)))))
|
||||
|
||||
(defn get-global-config
|
||||
[]
|
||||
(get-in @state [:config ::global-config]))
|
||||
|
||||
(defn get-global-config-str-content
|
||||
[]
|
||||
(get-in @state [:config ::global-config-str-content]))
|
||||
|
||||
(defn get-graph-config
|
||||
([] (get-graph-config (get-current-repo)))
|
||||
([repo-url] (get-in @state [:config repo-url])))
|
||||
|
||||
(defn get-config
|
||||
"User config for the given repo or current repo if none given. All config fetching
|
||||
should be done through this fn in order to get global config and config defaults"
|
||||
@@ -352,8 +365,8 @@ should be done through this fn in order to get global config and config defaults
|
||||
([repo-url]
|
||||
(merge-configs
|
||||
default-config
|
||||
(get-in @state [:config ::global-config])
|
||||
(get-in @state [:config repo-url]))))
|
||||
(get-global-config)
|
||||
(get-graph-config repo-url))))
|
||||
|
||||
(defonce publishing? (atom nil))
|
||||
|
||||
@@ -1341,7 +1354,7 @@ Similar to re-frame subscriptions"
|
||||
([panel-content]
|
||||
(set-sub-modal! panel-content
|
||||
{:close-btn? true}))
|
||||
([panel-content {:keys [id label close-btn? close-backdrop? show? center?] :as opts}]
|
||||
([panel-content {:keys [id label payload close-btn? close-backdrop? show? center?] :as opts}]
|
||||
(if (not (modal-opened?))
|
||||
(set-modal! panel-content opts)
|
||||
(let [modals (:modal/subsets @state)
|
||||
@@ -1351,6 +1364,7 @@ Similar to re-frame subscriptions"
|
||||
#(not (nil? %1))
|
||||
{:modal/id id
|
||||
:modal/label (or label (if center? "ls-modal-align-center" ""))
|
||||
:modal/payload payload
|
||||
:modal/show? (if (boolean? show?) show? true)
|
||||
:modal/panel-content panel-content
|
||||
:modal/close-btn? close-btn?
|
||||
@@ -1380,7 +1394,7 @@ Similar to re-frame subscriptions"
|
||||
(set-modal! modal-panel-content
|
||||
{:fullscreen? false
|
||||
:close-btn? true}))
|
||||
([modal-panel-content {:keys [id label fullscreen? close-btn? close-backdrop? center?]}]
|
||||
([modal-panel-content {:keys [id label payload fullscreen? close-btn? close-backdrop? center?]}]
|
||||
(let [opened? (modal-opened?)]
|
||||
(when opened?
|
||||
(close-modal!))
|
||||
@@ -1395,6 +1409,7 @@ Similar to re-frame subscriptions"
|
||||
:modal/label (or label (if center? "ls-modal-align-center" ""))
|
||||
:modal/show? (boolean modal-panel-content)
|
||||
:modal/panel-content modal-panel-content
|
||||
:modal/payload payload
|
||||
:modal/fullscreen? fullscreen?
|
||||
:modal/close-btn? close-btn?
|
||||
:modal/close-backdrop? (if (boolean? close-backdrop?) close-backdrop? true))))
|
||||
@@ -1408,6 +1423,7 @@ Similar to re-frame subscriptions"
|
||||
(swap! state assoc
|
||||
:modal/id nil
|
||||
:modal/label ""
|
||||
:modal/payload nil
|
||||
:modal/show? false
|
||||
:modal/fullscreen? false
|
||||
:modal/panel-content nil
|
||||
@@ -1477,9 +1493,11 @@ Similar to re-frame subscriptions"
|
||||
(when value (set-state! [:config repo-url] value)))
|
||||
|
||||
(defn set-global-config!
|
||||
[value]
|
||||
[value str-content]
|
||||
;; Placed under :config so cursors can work seamlessly
|
||||
(when value (set-config! ::global-config value)))
|
||||
(when value
|
||||
(set-config! ::global-config value)
|
||||
(set-config! ::global-config-str-content str-content)))
|
||||
|
||||
(defn get-wide-mode?
|
||||
[]
|
||||
@@ -1499,13 +1517,13 @@ Similar to re-frame subscriptions"
|
||||
|
||||
(defn get-plugins-commands-with-type
|
||||
[type]
|
||||
(filterv #(= (keyword (first %)) (keyword type))
|
||||
(apply concat (vals (:plugin/simple-commands @state)))))
|
||||
(->> (apply concat (vals (:plugin/simple-commands @state)))
|
||||
(filterv #(= (keyword (first %)) (keyword type)))))
|
||||
|
||||
(defn get-plugins-ui-items-with-type
|
||||
[type]
|
||||
(filterv #(= (keyword (first %)) (keyword type))
|
||||
(apply concat (vals (:plugin/installed-ui-items @state)))))
|
||||
(->> (apply concat (vals (:plugin/installed-ui-items @state)))
|
||||
(filterv #(= (keyword (first %)) (keyword type)))))
|
||||
|
||||
(defn get-plugin-resources-with-type
|
||||
[pid type]
|
||||
@@ -1734,8 +1752,8 @@ Similar to re-frame subscriptions"
|
||||
(set-state! :ui/settings-open? false))
|
||||
|
||||
(defn open-settings!
|
||||
[]
|
||||
(set-state! :ui/settings-open? true))
|
||||
([] (open-settings! true))
|
||||
([active-tab] (set-state! :ui/settings-open? active-tab)))
|
||||
|
||||
;; TODO: Move those to the uni `state`
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
[frontend.mobile.util :as mobile-util]
|
||||
[frontend.modules.shortcut.config :as shortcut-config]
|
||||
[frontend.modules.shortcut.core :as shortcut]
|
||||
[frontend.modules.shortcut.data-helper :as shortcut-helper]
|
||||
[frontend.modules.shortcut.utils :as shortcut-utils]
|
||||
[frontend.rum :as r]
|
||||
[frontend.state :as state]
|
||||
[frontend.storage :as storage]
|
||||
@@ -167,7 +167,7 @@
|
||||
sequence)]
|
||||
[:span.keyboard-shortcut
|
||||
(map-indexed (fn [i key]
|
||||
(let [key' (shortcut-helper/decorate-binding (str key))]
|
||||
(let [key' (shortcut-utils/decorate-binding (str key))]
|
||||
[:code {:key i}
|
||||
;; Display "cmd" rather than "meta" to the user to describe the Mac
|
||||
;; mod key, because that's what the Mac keyboards actually say.
|
||||
@@ -507,7 +507,7 @@
|
||||
|
||||
(rum/defcs auto-complete <
|
||||
(rum/local 0 ::current-idx)
|
||||
(shortcut/mixin :shortcut.handler/auto-complete)
|
||||
(shortcut/mixin* :shortcut.handler/auto-complete)
|
||||
[state
|
||||
matched
|
||||
{:keys [on-chosen
|
||||
@@ -567,9 +567,10 @@
|
||||
:aria-hidden "true"}]]]))
|
||||
|
||||
(defn keyboard-shortcut-from-config [shortcut-name]
|
||||
(let [default-binding (:binding (get shortcut-config/all-default-keyboard-shortcuts shortcut-name))
|
||||
custom-binding (when (state/shortcuts) (get (state/shortcuts) shortcut-name))]
|
||||
(or custom-binding default-binding)))
|
||||
(let [built-in-binding (:binding (get shortcut-config/all-built-in-keyboard-shortcuts shortcut-name))
|
||||
custom-binding (when (state/shortcuts) (get (state/shortcuts) shortcut-name))
|
||||
binding (or custom-binding built-in-binding)]
|
||||
(shortcut-utils/decorate-binding binding)))
|
||||
|
||||
(rum/defc modal-overlay
|
||||
[state close-fn close-backdrop?]
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
{:init (fn [state]
|
||||
(reset! *internal-model (first (:rum/args state)))
|
||||
state)}
|
||||
(shortcut/mixin :shortcut.handler/date-picker)
|
||||
(shortcut/mixin :shortcut.handler/date-picker false)
|
||||
[_model {:keys [on-change disabled? start-of-week class style attr]
|
||||
:or {start-of-week (state/get-start-of-week)} ;; Default to Sunday
|
||||
:as args}]
|
||||
|
||||
@@ -67,19 +67,6 @@
|
||||
[parts]
|
||||
(string/join "/" parts))
|
||||
|
||||
(defn normalize-user-keyname
|
||||
[k]
|
||||
(let [keynames {";" "semicolon"
|
||||
"=" "equals"
|
||||
"-" "dash"
|
||||
"[" "open-square-bracket"
|
||||
"]" "close-square-bracket"
|
||||
"'" "single-quote"}]
|
||||
(some-> (str k)
|
||||
(string/lower-case)
|
||||
(string/replace #"[;=-\[\]']" (fn [s]
|
||||
(get keynames s))))))
|
||||
|
||||
#?(:cljs
|
||||
(defn safe-re-find
|
||||
{:malli/schema [:=> [:cat :any :string] [:or :nil :string [:vector [:maybe :string]]]]}
|
||||
|
||||
@@ -367,9 +367,9 @@
|
||||
(if palette?
|
||||
(palette-handler/invoke-command palette-cmd)
|
||||
(action')))
|
||||
[handler-id id shortcut-map] (update shortcut-args 2 assoc :fn dispatch-cmd :cmd palette-cmd)]
|
||||
(println :shortcut/register-shortcut [handler-id id shortcut-map])
|
||||
(st/register-shortcut! handler-id id shortcut-map)))))))
|
||||
[mode-id id shortcut-map] (update shortcut-args 2 merge cmd {:fn dispatch-cmd :cmd palette-cmd})]
|
||||
(println :shortcut/register-shortcut [mode-id id shortcut-map])
|
||||
(st/register-shortcut! mode-id id shortcut-map)))))))
|
||||
|
||||
(defn ^:export unregister_plugin_simple_command
|
||||
[pid]
|
||||
@@ -422,7 +422,7 @@
|
||||
(util/safe-lower-case)
|
||||
(keyword)))]
|
||||
(when-let [action (get-in (palette-handler/get-commands-unique) [id :action])]
|
||||
(apply action args)))))
|
||||
(apply plugin-handler/hook-lifecycle-fn! id action args)))))
|
||||
|
||||
;; flag - boolean | 'toggle'
|
||||
(def ^:export set_left_sidebar_visible
|
||||
|
||||
@@ -320,6 +320,7 @@
|
||||
:settings-page/current-version "Current version"
|
||||
:settings-page/tab-general "General"
|
||||
:settings-page/tab-editor "Editor"
|
||||
:settings-page/tab-keymap "Keymap"
|
||||
:settings-page/tab-version-control "Version control"
|
||||
:settings-page/tab-account "Account"
|
||||
:settings-page/tab-advanced "Advanced"
|
||||
@@ -359,6 +360,7 @@
|
||||
:close "Close"
|
||||
:delete "Delete"
|
||||
:save "Save"
|
||||
:reset "Reset"
|
||||
:type "Type"
|
||||
:host "Host"
|
||||
:port "Port"
|
||||
@@ -636,9 +638,23 @@
|
||||
:shortcut.category/block-command-editing "Block command editing"
|
||||
:shortcut.category/block-selection "Block selection (press Esc to quit selection)"
|
||||
:shortcut.category/toggle "Toggle"
|
||||
:shortcut.category/whiteboard "Whiteboard"
|
||||
:shortcut.category/others "Others"
|
||||
:shortcut.category/plugins "Plugins"
|
||||
:shortcut.category/whiteboard "Whiteboard"
|
||||
|
||||
:keymap/all "All"
|
||||
:keymap/disabled "Disabled"
|
||||
:keymap/unset "Unset"
|
||||
:keymap/custom "Custom"
|
||||
:keymap/search "Search"
|
||||
:keymap/total "Total shortcuts"
|
||||
:keymap/keystroke-filter "Keystroke filter"
|
||||
:keymap/keystroke-record-desc "Press any sequence of keys to filter shortcuts"
|
||||
:keymap/keystroke-record-setup-label "Press any sequence of keys to set a shortcut"
|
||||
:keymap/restore-to-default "Restore to system default"
|
||||
:keymap/customize-for-label "Customize shortcuts"
|
||||
:keymap/conflicts-for-label "Keymap conflicts for"
|
||||
|
||||
:window/minimize "Minimize"
|
||||
:window/maximize "Maximize"
|
||||
:window/restore "Restore"
|
||||
|
||||
@@ -233,6 +233,7 @@
|
||||
:settings-page/show-full-blocks "显示块引用的所有行"
|
||||
:settings-page/tab-general "常规"
|
||||
:settings-page/tab-editor "编辑器"
|
||||
:settings-page/tab-keymap "快捷键"
|
||||
:settings-page/tab-assets "附件设置"
|
||||
:settings-page/tab-advanced "高级设置"
|
||||
:settings-page/tab-features "更多功能"
|
||||
@@ -293,6 +294,7 @@
|
||||
:close "关闭"
|
||||
:delete "删除"
|
||||
:save "保存"
|
||||
:reset "重设"
|
||||
:type "类型"
|
||||
:host "主机"
|
||||
:port "端口"
|
||||
@@ -486,6 +488,22 @@
|
||||
:shortcut.category/block-selection "块选择操作"
|
||||
:shortcut.category/toggle "切换"
|
||||
:shortcut.category/others "其他"
|
||||
:shortcut.category/plugins "插件"
|
||||
:shortcut.category/whiteboard "白板"
|
||||
|
||||
:keymap/all "全部"
|
||||
:keymap/disabled "已禁用"
|
||||
:keymap/unset "未设置"
|
||||
:keymap/custom "自定义"
|
||||
:keymap/search "搜索"
|
||||
:keymap/total "共计条目"
|
||||
:keymap/keystroke-filter "按键过滤"
|
||||
:keymap/keystroke-record-desc "请敲击键盘提供按键组合, 以过滤快捷键。"
|
||||
:keymap/keystroke-record-setup-label "请敲击键盘提供按键组合"
|
||||
:keymap/restore-to-default "恢复到默认映射"
|
||||
:keymap/customize-for-label "自定义快捷键"
|
||||
:keymap/conflicts-for-label "发现映射冲突 "
|
||||
|
||||
:command.auto-complete/complete "自动完成:选择当前项"
|
||||
:command.auto-complete/next "自动完成:选择下一项"
|
||||
:command.auto-complete/open-link "自动完成:在浏览器中打开当前项"
|
||||
|
||||
49
src/test/frontend/modules/shortcut/core_test.cljs
Normal file
49
src/test/frontend/modules/shortcut/core_test.cljs
Normal file
@@ -0,0 +1,49 @@
|
||||
(ns frontend.modules.shortcut.core-test
|
||||
(:require [cljs.test :refer [deftest is testing]]
|
||||
[clojure.string :as string]
|
||||
[frontend.modules.shortcut.data-helper :as dh]
|
||||
[frontend.util :as util]))
|
||||
|
||||
(deftest test-core-basic
|
||||
(testing "get handler id"
|
||||
(is (= (dh/get-group :editor/copy) :shortcut.handler/editor-global))))
|
||||
|
||||
(deftest test-shortcut-conflicts-detection
|
||||
(testing "get conflicts with shortcut id")
|
||||
|
||||
(testing "get conflicts with binding keys"
|
||||
(is (= (count (dh/get-conflicts-by-keys "mod+c")) 1))
|
||||
|
||||
(is (contains?
|
||||
(->> (dh/get-conflicts-by-keys
|
||||
"mod+c" :shortcut.handler/editor-global
|
||||
{:exclude-ids #{:editor/copy} :group-global? true})
|
||||
(vals) (mapcat #(vals %)) (some #(when (= (first %) (if util/mac? "meta+c" "ctrl+c")) (second %))))
|
||||
:misc/copy))
|
||||
|
||||
(is (->> (dh/get-conflicts-by-keys ["t"])
|
||||
(vals)
|
||||
(first)
|
||||
(vals)
|
||||
(map first)
|
||||
(every? #(string/starts-with? % "t")))
|
||||
"get the conflicts from the only leader key")
|
||||
|
||||
(is (nil? (seq (dh/get-conflicts-by-keys ["g"] :shortcut.handler/cards)))
|
||||
"specific handler with the global conflicting key"))
|
||||
|
||||
(testing "parse conflicts from the string binding list"
|
||||
(is (= (dh/parse-conflicts-from-binding ["g" "g t"] "g")
|
||||
["g" "g t"]))
|
||||
|
||||
(is (= (dh/parse-conflicts-from-binding ["g" "g t" "t r"] "g t")
|
||||
["g" "g t"]))
|
||||
|
||||
(is (= (dh/parse-conflicts-from-binding ["g" "g t" "t r"] "g x")
|
||||
["g"]))
|
||||
|
||||
(is (= (dh/parse-conflicts-from-binding ["meta+x" "meta+x t" "t r"] "meta+x x")
|
||||
["meta+x"]))))
|
||||
|
||||
(comment
|
||||
(cljs.test/run-tests))
|
||||
@@ -25,9 +25,9 @@
|
||||
(deftest test-memoize-last
|
||||
(testing "memoize-last add test"
|
||||
(let [actual-ops (atom 0)
|
||||
m+ (util/memoize-last (fn [x1 x2]
|
||||
(swap! actual-ops inc) ;; side effect for counting
|
||||
(+ x1 x2)))]
|
||||
m+ (util/memoize-last (fn [x1 x2]
|
||||
(swap! actual-ops inc) ;; side effect for counting
|
||||
(+ x1 x2)))]
|
||||
(is (= (m+ 1 1) 2))
|
||||
(is (= @actual-ops 1))
|
||||
(is (= (m+ 1 1) 2))
|
||||
@@ -44,58 +44,58 @@
|
||||
|
||||
(testing "memoize-last nested mapping test"
|
||||
(let [actual-ops (atom 0)
|
||||
flatten-f (util/memoize-last (fn [& args]
|
||||
(swap! actual-ops inc) ;; side effect for counting
|
||||
(apply #'shortcut-data-helper/flatten-key-bindings args)))
|
||||
target (atom {:part1 {:date-picker/complete {:binding "enter"
|
||||
:fn "ui-handler/shortcut-complete"}
|
||||
:date-picker/prev-day {:binding "left"
|
||||
:fn "ui-handler/shortcut-prev-day"}}
|
||||
:part2 {:date-picker/next-day {:binding "right"
|
||||
:fn "ui-handler/shortcut-next-day"}
|
||||
:date-picker/prev-week {:binding ["up" "ctrl+p"]
|
||||
:fn "ui-handler/shortcut-prev-week"}}})]
|
||||
(is (= (flatten-f (vals @target)) {:date-picker/complete "enter"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]}))
|
||||
flatten-f (util/memoize-last (fn [& args]
|
||||
(swap! actual-ops inc) ;; side effect for counting
|
||||
(apply #'shortcut-data-helper/flatten-bindings-by-id (conj (vec args) nil true))))
|
||||
target (atom {:part1 {:date-picker/complete {:binding "enter"
|
||||
:fn "ui-handler/shortcut-complete"}
|
||||
:date-picker/prev-day {:binding "left"
|
||||
:fn "ui-handler/shortcut-prev-day"}}
|
||||
:part2 {:date-picker/next-day {:binding "right"
|
||||
:fn "ui-handler/shortcut-next-day"}
|
||||
:date-picker/prev-week {:binding ["up" "ctrl+p"]
|
||||
:fn "ui-handler/shortcut-prev-week"}}})]
|
||||
(is (= (flatten-f @target) {:date-picker/complete "enter"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]}))
|
||||
(is (= @actual-ops 1))
|
||||
(is (= (flatten-f (vals @target)) {:date-picker/complete "enter"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]}))
|
||||
(is (= (flatten-f @target) {:date-picker/complete "enter"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]}))
|
||||
(is (= @actual-ops 1))
|
||||
;; edit value
|
||||
(swap! target assoc-in [:part1 :date-picker/complete :binding] "tab")
|
||||
(is (= (flatten-f (vals @target)) {:date-picker/complete "tab"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]}))
|
||||
(is (= (flatten-f @target) {:date-picker/complete "tab"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]}))
|
||||
(is (= @actual-ops 2))
|
||||
(is (= (flatten-f (vals @target)) {:date-picker/complete "tab"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]}))
|
||||
(is (= (flatten-f @target) {:date-picker/complete "tab"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]}))
|
||||
(is (= @actual-ops 2))
|
||||
(is (= (flatten-f (vals @target)) {:date-picker/complete "tab"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]}))
|
||||
(is (= (flatten-f @target) {:date-picker/complete "tab"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]}))
|
||||
(is (= @actual-ops 2))
|
||||
;; edit key
|
||||
(swap! target assoc :part3 {:date-picker/next-week {:binding "down"
|
||||
:fn "ui-handler/shortcut-next-week"}})
|
||||
(is (= (flatten-f (vals @target)) {:date-picker/complete "tab"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]
|
||||
:date-picker/next-week "down"}))
|
||||
(is (= (flatten-f @target) {:date-picker/complete "tab"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]
|
||||
:date-picker/next-week "down"}))
|
||||
(is (= @actual-ops 3))
|
||||
(is (= (flatten-f (vals @target)) {:date-picker/complete "tab"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]
|
||||
:date-picker/next-week "down"}))
|
||||
(is (= (flatten-f @target) {:date-picker/complete "tab"
|
||||
:date-picker/prev-day "left"
|
||||
:date-picker/next-day "right"
|
||||
:date-picker/prev-week ["up" "ctrl+p"]
|
||||
:date-picker/next-week "down"}))
|
||||
(is (= @actual-ops 3)))))
|
||||
|
||||
(deftest test-media-format-from-input
|
||||
|
||||
Reference in New Issue
Block a user