Explorar o código

enhance: dynamic shortcut configuration

Tienson Qin %!s(int64=4) %!d(string=hai) anos
pai
achega
24f48ccf08

+ 409 - 398
src/main/frontend/modules/shortcut/config.cljs

@@ -14,417 +14,418 @@
             [frontend.modules.shortcut.before :as m]
             [frontend.state :as state]
             [frontend.util :refer [mac?] :as util]
-            [frontend.commands :as commands]))
+            [frontend.commands :as commands]
+            [medley.core :as medley]))
 
 ;; Note – when you change this file, you will need to do a hard reset.
 ;; The commands are registered when the Clojurescript code runs for the first time.
 
-;; TODO: how to extend this for plugins usage? An atom?
-(def default-config
-  {:shortcut.handler/date-picker
-   {:date-picker/complete
-    {:desc    "Date picker: Choose selected day"
-     :binding "enter"
-     :fn      ui-handler/shortcut-complete}
-    :date-picker/prev-day
-    {:desc    "Date picker: Select previous day"
-     :binding "left"
-     :fn      ui-handler/shortcut-prev-day}
-    :date-picker/next-day
-    {:desc    "Date picker: Select next day"
-     :binding "right"
-     :fn      ui-handler/shortcut-next-day}
-    :date-picker/prev-week
-    {:desc    "Date picker: Select previous week"
-     :binding "up"
-     :fn      ui-handler/shortcut-prev-week}
-    :date-picker/next-week
-    {:desc    "Date picker: Select next week"
-     :binding "down"
-     :fn      ui-handler/shortcut-next-week}}
+(defonce config
+  (atom
+   {:shortcut.handler/date-picker
+    {:date-picker/complete
+     {:desc    "Date picker: Choose selected day"
+      :binding "enter"
+      :fn      ui-handler/shortcut-complete}
+     :date-picker/prev-day
+     {:desc    "Date picker: Select previous day"
+      :binding "left"
+      :fn      ui-handler/shortcut-prev-day}
+     :date-picker/next-day
+     {:desc    "Date picker: Select next day"
+      :binding "right"
+      :fn      ui-handler/shortcut-next-day}
+     :date-picker/prev-week
+     {:desc    "Date picker: Select previous week"
+      :binding "up"
+      :fn      ui-handler/shortcut-prev-week}
+     :date-picker/next-week
+     {:desc    "Date picker: Select next week"
+      :binding "down"
+      :fn      ui-handler/shortcut-next-week}}
 
-   :shortcut.handler/pdf
-   ^{:before m/prevent-default-behavior}
-   {:pdf/previous-page
-    {:desc    "Previous page of current pdf doc"
-     :binding "alt+p"
-     :fn      pdf-utils/prev-page}
-    :pdf/next-page
-    {:desc    "Next page of current pdf doc"
-     :binding "alt+n"
-     :fn      pdf-utils/next-page}}
+    :shortcut.handler/pdf
+    ^{:before m/enable-when-not-editing-mode!}
+    {:pdf/previous-page
+     {:desc    "Previous page of current pdf doc"
+      :binding "alt+p"
+      :fn      pdf-utils/prev-page}
+     :pdf/next-page
+     {:desc    "Next page of current pdf doc"
+      :binding "alt+n"
+      :fn      pdf-utils/next-page}}
 
-   :shortcut.handler/auto-complete
-   {:auto-complete/complete
-    {:desc    "Auto-complete: Choose selected item"
-     :binding "enter"
-     :fn      ui-handler/auto-complete-complete}
-    :auto-complete/prev
-    {:desc    "Auto-complete: Select previous item"
-     :binding "up"
-     :fn      ui-handler/auto-complete-prev}
-    :auto-complete/next
-    {:desc    "Auto-complete: Select next item"
-     :binding "down"
-     :fn      ui-handler/auto-complete-next}
-    :auto-complete/shift-complete
-    {:desc    "Auto-complete: Open selected item in sidebar"
-     :binding "shift+enter"
-     :fn      ui-handler/auto-complete-shift-complete}}
+    :shortcut.handler/auto-complete
+    {:auto-complete/complete
+     {:desc    "Auto-complete: Choose selected item"
+      :binding "enter"
+      :fn      ui-handler/auto-complete-complete}
+     :auto-complete/prev
+     {:desc    "Auto-complete: Select previous item"
+      :binding "up"
+      :fn      ui-handler/auto-complete-prev}
+     :auto-complete/next
+     {:desc    "Auto-complete: Select next item"
+      :binding "down"
+      :fn      ui-handler/auto-complete-next}
+     :auto-complete/shift-complete
+     {:desc    "Auto-complete: Open selected item in sidebar"
+      :binding "shift+enter"
+      :fn      ui-handler/auto-complete-shift-complete}}
 
-   :shortcut.handler/cards
-   {:cards/toggle-answers
-    {:desc    "Cards: show/hide answers/clozes"
-     :binding "s"
-     :fn      srs/toggle-answers}
-    :cards/next-card
-    {:desc    "Cards: next card"
-     :binding "n"
-     :fn      srs/next-card}
-    :cards/forgotten
-    {:desc    "Cards: forgotten"
-     :binding "f"
-     :fn      srs/forgotten}
-    :cards/remembered
-    {:desc    "Cards: remembered"
-     :binding "r"
-     :fn      srs/remembered}
-    :cards/recall
-    {:desc    "Cards: take a while to recall"
-     :binding "t"
-     :fn      srs/recall}}
+    :shortcut.handler/cards
+    {:cards/toggle-answers
+     {:desc    "Cards: show/hide answers/clozes"
+      :binding "s"
+      :fn      srs/toggle-answers}
+     :cards/next-card
+     {:desc    "Cards: next card"
+      :binding "n"
+      :fn      srs/next-card}
+     :cards/forgotten
+     {:desc    "Cards: forgotten"
+      :binding "f"
+      :fn      srs/forgotten}
+     :cards/remembered
+     {:desc    "Cards: remembered"
+      :binding "r"
+      :fn      srs/remembered}
+     :cards/recall
+     {:desc    "Cards: take a while to recall"
+      :binding "t"
+      :fn      srs/recall}}
 
-   :shortcut.handler/block-editing-only
-   ^{:before m/enable-when-editing-mode!}
-   {:editor/escape-editing
-    {:desc    "Escape editing (remap to ctrl+open-square-bracket for example)"
-     :binding false
-     :fn      (fn [_ _] (editor-handler/escape-editing))}
-    :editor/backspace
-    {:desc    "Backspace / Delete backwards"
-     :binding "backspace"
-     :fn      editor-handler/editor-backspace}
-    :editor/delete
-    {:desc    "Delete / Delete forwards"
-     :binding "delete"
-     :fn      editor-handler/editor-delete}
-    :editor/new-block
-    {:desc    "Create new block"
-     :binding "enter"
-     :fn      editor-handler/keydown-new-block-handler}
-    :editor/new-line
-    {:desc    "New line in current block"
-     :binding "shift+enter"
-     :fn      editor-handler/keydown-new-line-handler}
-    :editor/cycle-todo
-    {:desc    "Rotate the TODO state of the current item"
-     :binding "mod+enter"
-     :fn      editor-handler/cycle-todo!}
-    :editor/follow-link
-    {:desc    "Follow link under cursor"
-     :binding "mod+o"
-     :fn      editor-handler/follow-link-under-cursor!}
-    :editor/open-link-in-sidebar
-    {:desc    "Open link in sidebar"
-     :binding "mod+shift+o"
-     :fn      editor-handler/open-link-in-sidebar!}
-    :editor/bold
-    {:desc    "Bold"
-     :binding "mod+b"
-     :fn      editor-handler/bold-format!}
-    :editor/italics
-    {:desc    "Italics"
-     :binding "mod+i"
-     :fn      editor-handler/italics-format!}
-    :editor/highlight
-    {:desc    "Highlight"
-     :binding "mod+shift+h"
-     :fn      editor-handler/highlight-format!}
-    :editor/strike-through
-    {:desc    "Strikethrough"
-     :binding "mod+shift+s"
-     :fn      editor-handler/strike-through-format!}
-    :editor/clear-block
-    {:desc    "Delete entire block content"
-     :binding (if mac? "ctrl+l" "alt+l")
-     :fn      editor-handler/clear-block-content!}
-    :editor/kill-line-before
-    {:desc    "Delete line before cursor position"
-     :binding (if mac? "ctrl+u" "alt+u")
-     :fn      editor-handler/kill-line-before!}
-    :editor/kill-line-after
-    {:desc    "Delete line after cursor position"
-     :binding (if mac? false "alt+k")
-     :fn      editor-handler/kill-line-after!}
-    :editor/beginning-of-block
-    {:desc    "Move cursor to the beginning of a block"
-     :binding (if mac? false "alt+a")
-     :fn      editor-handler/beginning-of-block}
-    :editor/end-of-block
-    {:desc    "Move cursor to the end of a block"
-     :binding (if mac? false "alt+e")
-     :fn      editor-handler/end-of-block}
-    :editor/forward-word
-    {:desc    "Move cursor forward a word"
-     :binding (if mac? "ctrl+shift+f" "alt+f")
-     :fn      editor-handler/cursor-forward-word}
-    :editor/backward-word
-    {:desc    "Move cursor backward a word"
-     :binding (if mac? "ctrl+shift+b" "alt+b")
-     :fn      editor-handler/cursor-backward-word}
-    :editor/forward-kill-word
-    {:desc    "Delete a word forwards"
-     :binding (if mac? "ctrl+w" "alt+d")
-     :fn      editor-handler/forward-kill-word}
-    :editor/backward-kill-word
-    {:desc    "Delete a word backwards"
-     :binding (if mac? false "alt+w")
-     :fn      editor-handler/backward-kill-word}
-    :editor/replace-block-reference-at-point
-    {:desc    "Replace block reference with its content at point"
-     :binding "mod+shift+r"
-     :fn      editor-handler/replace-block-reference-with-content-at-point}
-    :editor/paste-text-in-one-block-at-point
-    {:desc    "Paste text into one block at point"
-     :binding "mod+shift+v"
-     :fn      editor-handler/paste-text-in-one-block-at-point}
-    :editor/insert-youtube-timestamp
-    {:desc    "Insert youtube timestamp"
-     :binding "mod+shift+y"
-     :fn      commands/insert-youtube-timestamp}}
+    :shortcut.handler/block-editing-only
+    ^{:before m/enable-when-editing-mode!}
+    {:editor/escape-editing
+     {:desc    "Escape editing (remap to ctrl+open-square-bracket for example)"
+      :binding false
+      :fn      (fn [_ _] (editor-handler/escape-editing))}
+     :editor/backspace
+     {:desc    "Backspace / Delete backwards"
+      :binding "backspace"
+      :fn      editor-handler/editor-backspace}
+     :editor/delete
+     {:desc    "Delete / Delete forwards"
+      :binding "delete"
+      :fn      editor-handler/editor-delete}
+     :editor/new-block
+     {:desc    "Create new block"
+      :binding "enter"
+      :fn      editor-handler/keydown-new-block-handler}
+     :editor/new-line
+     {:desc    "New line in current block"
+      :binding "shift+enter"
+      :fn      editor-handler/keydown-new-line-handler}
+     :editor/cycle-todo
+     {:desc    "Rotate the TODO state of the current item"
+      :binding "mod+enter"
+      :fn      editor-handler/cycle-todo!}
+     :editor/follow-link
+     {:desc    "Follow link under cursor"
+      :binding "mod+o"
+      :fn      editor-handler/follow-link-under-cursor!}
+     :editor/open-link-in-sidebar
+     {:desc    "Open link in sidebar"
+      :binding "mod+shift+o"
+      :fn      editor-handler/open-link-in-sidebar!}
+     :editor/bold
+     {:desc    "Bold"
+      :binding "mod+b"
+      :fn      editor-handler/bold-format!}
+     :editor/italics
+     {:desc    "Italics"
+      :binding "mod+i"
+      :fn      editor-handler/italics-format!}
+     :editor/highlight
+     {:desc    "Highlight"
+      :binding "mod+shift+h"
+      :fn      editor-handler/highlight-format!}
+     :editor/strike-through
+     {:desc    "Strikethrough"
+      :binding "mod+shift+s"
+      :fn      editor-handler/strike-through-format!}
+     :editor/clear-block
+     {:desc    "Delete entire block content"
+      :binding (if mac? "ctrl+l" "alt+l")
+      :fn      editor-handler/clear-block-content!}
+     :editor/kill-line-before
+     {:desc    "Delete line before cursor position"
+      :binding (if mac? "ctrl+u" "alt+u")
+      :fn      editor-handler/kill-line-before!}
+     :editor/kill-line-after
+     {:desc    "Delete line after cursor position"
+      :binding (if mac? false "alt+k")
+      :fn      editor-handler/kill-line-after!}
+     :editor/beginning-of-block
+     {:desc    "Move cursor to the beginning of a block"
+      :binding (if mac? false "alt+a")
+      :fn      editor-handler/beginning-of-block}
+     :editor/end-of-block
+     {:desc    "Move cursor to the end of a block"
+      :binding (if mac? false "alt+e")
+      :fn      editor-handler/end-of-block}
+     :editor/forward-word
+     {:desc    "Move cursor forward a word"
+      :binding (if mac? "ctrl+shift+f" "alt+f")
+      :fn      editor-handler/cursor-forward-word}
+     :editor/backward-word
+     {:desc    "Move cursor backward a word"
+      :binding (if mac? "ctrl+shift+b" "alt+b")
+      :fn      editor-handler/cursor-backward-word}
+     :editor/forward-kill-word
+     {:desc    "Delete a word forwards"
+      :binding (if mac? "ctrl+w" "alt+d")
+      :fn      editor-handler/forward-kill-word}
+     :editor/backward-kill-word
+     {:desc    "Delete a word backwards"
+      :binding (if mac? false "alt+w")
+      :fn      editor-handler/backward-kill-word}
+     :editor/replace-block-reference-at-point
+     {:desc    "Replace block reference with its content at point"
+      :binding "mod+shift+r"
+      :fn      editor-handler/replace-block-reference-with-content-at-point}
+     :editor/paste-text-in-one-block-at-point
+     {:desc    "Paste text into one block at point"
+      :binding "mod+shift+v"
+      :fn      editor-handler/paste-text-in-one-block-at-point}
+     :editor/insert-youtube-timestamp
+     {:desc    "Insert youtube timestamp"
+      :binding "mod+shift+y"
+      :fn      commands/insert-youtube-timestamp}}
 
-   :shortcut.handler/editor-global
-   ^{:before m/enable-when-not-component-editing!}
-   {:editor/up
-    {:desc    "Move cursor up / Select up"
-     :binding "up"
-     :fn      (editor-handler/shortcut-up-down :up)}
-    :editor/down
-    {:desc    "Move cursor down / Select down"
-     :binding "down"
-     :fn      (editor-handler/shortcut-up-down :down)}
-    :editor/left
-    {:desc    "Move cursor left / Open selected block at beginning"
-     :binding "left"
-     :fn      (editor-handler/shortcut-left-right :left)}
-    :editor/right
-    {:desc    "Move cursor right / Open selected block at end"
-     :binding "right"
-     :fn      (editor-handler/shortcut-left-right :right)}
-    :editor/move-block-up
-    {:desc    "Move block up"
-     :binding (if mac? "mod+shift+up" "alt+shift+up")
-     :fn      (editor-handler/move-up-down true)}
-    :editor/move-block-down
-    {:desc    "Move block down"
-     :binding (if mac? "mod+shift+down" "alt+shift+down")
-     :fn      (editor-handler/move-up-down false)}
-    ;; FIXME
-    ;; add open edit in non-selection mode
-    :editor/open-edit
-    {:desc    "Edit selected block"
-     :binding "enter"
-     :fn      (partial editor-handler/open-selected-block! :right)}
-    :editor/select-block-up
-    {:desc    "Select block above"
-     :binding "shift+up"
-     :fn      (editor-handler/on-select-block :up)}
-    :editor/select-block-down
-    {:desc    "Select block below"
-     :binding "shift+down"
-     :fn      (editor-handler/on-select-block :down)}
-    :editor/delete-selection
-    {:desc    "Delete selected blocks"
-     :binding ["backspace" "delete"]
-     :fn      editor-handler/delete-selection}
-    :editor/expand-block-children
-    {:desc    "Expand"
-     :binding "mod+down"
-     :fn      editor-handler/expand!}
-    :editor/collapse-block-children
-    {:desc    "Collapse"
-     :binding "mod+up"
-     :fn      editor-handler/collapse!}
-    :editor/indent
-    {:desc    "Indent block"
-     :binding "tab"
-     :fn      (editor-handler/keydown-tab-handler :right)}
-    :editor/outdent
-    {:desc    "Outdent block"
-     :binding "shift+tab"
-     :fn      (editor-handler/keydown-tab-handler :left)}
-    :editor/copy
-    {:desc    "Copy (copies either selection, or block reference)"
-     :binding "mod+c"
-     :fn      editor-handler/shortcut-copy}
-    :editor/cut
-    {:desc    "Cut"
-     :binding "mod+x"
-     :fn      editor-handler/shortcut-cut}
-    :editor/undo
-    {:desc    "Undo"
-     :binding "mod+z"
-     :fn      history/undo!}
-    :editor/redo
-    {:desc    "Redo"
-     :binding ["shift+mod+z" "mod+y"]
-     :fn      history/redo!}}
+    :shortcut.handler/editor-global
+    ^{:before m/enable-when-not-component-editing!}
+    {:editor/up
+     {:desc    "Move cursor up / Select up"
+      :binding "up"
+      :fn      (editor-handler/shortcut-up-down :up)}
+     :editor/down
+     {:desc    "Move cursor down / Select down"
+      :binding "down"
+      :fn      (editor-handler/shortcut-up-down :down)}
+     :editor/left
+     {:desc    "Move cursor left / Open selected block at beginning"
+      :binding "left"
+      :fn      (editor-handler/shortcut-left-right :left)}
+     :editor/right
+     {:desc    "Move cursor right / Open selected block at end"
+      :binding "right"
+      :fn      (editor-handler/shortcut-left-right :right)}
+     :editor/move-block-up
+     {:desc    "Move block up"
+      :binding (if mac? "mod+shift+up" "alt+shift+up")
+      :fn      (editor-handler/move-up-down true)}
+     :editor/move-block-down
+     {:desc    "Move block down"
+      :binding (if mac? "mod+shift+down" "alt+shift+down")
+      :fn      (editor-handler/move-up-down false)}
+     ;; FIXME
+     ;; add open edit in non-selection mode
+     :editor/open-edit
+     {:desc    "Edit selected block"
+      :binding "enter"
+      :fn      (partial editor-handler/open-selected-block! :right)}
+     :editor/select-block-up
+     {:desc    "Select block above"
+      :binding "shift+up"
+      :fn      (editor-handler/on-select-block :up)}
+     :editor/select-block-down
+     {:desc    "Select block below"
+      :binding "shift+down"
+      :fn      (editor-handler/on-select-block :down)}
+     :editor/delete-selection
+     {:desc    "Delete selected blocks"
+      :binding ["backspace" "delete"]
+      :fn      editor-handler/delete-selection}
+     :editor/expand-block-children
+     {:desc    "Expand"
+      :binding "mod+down"
+      :fn      editor-handler/expand!}
+     :editor/collapse-block-children
+     {:desc    "Collapse"
+      :binding "mod+up"
+      :fn      editor-handler/collapse!}
+     :editor/indent
+     {:desc    "Indent block"
+      :binding "tab"
+      :fn      (editor-handler/keydown-tab-handler :right)}
+     :editor/outdent
+     {:desc    "Outdent block"
+      :binding "shift+tab"
+      :fn      (editor-handler/keydown-tab-handler :left)}
+     :editor/copy
+     {:desc    "Copy (copies either selection, or block reference)"
+      :binding "mod+c"
+      :fn      editor-handler/shortcut-copy}
+     :editor/cut
+     {:desc    "Cut"
+      :binding "mod+x"
+      :fn      editor-handler/shortcut-cut}
+     :editor/undo
+     {:desc    "Undo"
+      :binding "mod+z"
+      :fn      history/undo!}
+     :editor/redo
+     {:desc    "Redo"
+      :binding ["shift+mod+z" "mod+y"]
+      :fn      history/redo!}}
 
-   :shortcut.handler/global-prevent-default
-   ^{:before m/prevent-default-behavior}
-   {:editor/insert-link
-    {:desc    "HTML Link"
-     :binding "mod+l"
-     :fn      editor-handler/html-link-format!}
-    :editor/select-all-blocks
-    {:desc    "Select all blocks"
-     :binding "mod+shift+a"
-     :fn      editor-handler/select-all-blocks!}
-    :editor/zoom-in
-    {:desc    "Zoom in editing block / Forwards otherwise"
-     :binding (if mac? "mod+." "alt+right")
-     :fn      editor-handler/zoom-in!}
-    :editor/zoom-out
-    {:desc    "Zoom out editing block / Backwards otherwise"
-     :binding (if mac? "mod+," "alt+left")
-     :fn      editor-handler/zoom-out!}
-    :ui/toggle-brackets
-    {:desc    "Toggle whether to display brackets"
-     :binding "mod+c mod+b"
-     :fn      config-handler/toggle-ui-show-brackets!}
-    :go/search-in-page
-    {:desc    "Search in the current page"
-     :binding ["mod+shift+k" "mod+shift+u"]
-     :fn      #(route-handler/go-to-search! :page)}
-    :go/search
-    {:desc    "Full text search"
-     :binding ["mod+k" "mod+u"]
-     :fn      #(route-handler/go-to-search! :global)}
-    :go/journals
-    {:desc    "Jump to journals"
-     :binding (if mac? "mod+j" "alt+j")
-     :fn      route-handler/go-to-journals!}
-    :go/backward
-    {:desc    "Backwards"
-     :binding "mod+open-square-bracket"
-     :fn      (fn [_] (js/window.history.back))}
-    :go/forward
-    {:desc    "Forwards"
-     :binding "mod+close-square-bracket"
-     :fn      (fn [_] (js/window.history.forward))}
-    :search/re-index
-    {:desc    "Rebuild search index"
-     :binding "mod+c mod+s"
-     :fn      search-handler/rebuild-indices!}
-    :sidebar/open-today-page
-    {:desc    "Open today's page in the right sidebar"
-     :binding (if mac? "mod+shift+j" "alt+shift+j")
-     :fn      page-handler/open-today-in-sidebar}
-    :sidebar/clear
-    {:desc    "Clear all in the right sidebar"
-     :binding "mod+c mod+c"
-     :fn      #(do
-                 (state/clear-sidebar-blocks!)
-                 (state/hide-right-sidebar!))}}
+    :shortcut.handler/global-prevent-default
+    ^{:before m/prevent-default-behavior}
+    {:editor/insert-link
+     {:desc    "HTML Link"
+      :binding "mod+l"
+      :fn      editor-handler/html-link-format!}
+     :editor/select-all-blocks
+     {:desc    "Select all blocks"
+      :binding "mod+shift+a"
+      :fn      editor-handler/select-all-blocks!}
+     :editor/zoom-in
+     {:desc    "Zoom in editing block / Forwards otherwise"
+      :binding (if mac? "mod+." "alt+right")
+      :fn      editor-handler/zoom-in!}
+     :editor/zoom-out
+     {:desc    "Zoom out editing block / Backwards otherwise"
+      :binding (if mac? "mod+," "alt+left")
+      :fn      editor-handler/zoom-out!}
+     :ui/toggle-brackets
+     {:desc    "Toggle whether to display brackets"
+      :binding "mod+c mod+b"
+      :fn      config-handler/toggle-ui-show-brackets!}
+     :go/search-in-page
+     {:desc    "Search in the current page"
+      :binding ["mod+shift+k" "mod+shift+u"]
+      :fn      #(route-handler/go-to-search! :page)}
+     :go/search
+     {:desc    "Full text search"
+      :binding ["mod+k" "mod+u"]
+      :fn      #(route-handler/go-to-search! :global)}
+     :go/journals
+     {:desc    "Jump to journals"
+      :binding (if mac? "mod+j" "alt+j")
+      :fn      route-handler/go-to-journals!}
+     :go/backward
+     {:desc    "Backwards"
+      :binding "mod+open-square-bracket"
+      :fn      (fn [_] (js/window.history.back))}
+     :go/forward
+     {:desc    "Forwards"
+      :binding "mod+close-square-bracket"
+      :fn      (fn [_] (js/window.history.forward))}
+     :search/re-index
+     {:desc    "Rebuild search index"
+      :binding "mod+c mod+s"
+      :fn      search-handler/rebuild-indices!}
+     :sidebar/open-today-page
+     {:desc    "Open today's page in the right sidebar"
+      :binding (if mac? "mod+shift+j" "alt+shift+j")
+      :fn      page-handler/open-today-in-sidebar}
+     :sidebar/clear
+     {:desc    "Clear all in the right sidebar"
+      :binding "mod+c mod+c"
+      :fn      #(do
+                  (state/clear-sidebar-blocks!)
+                  (state/hide-right-sidebar!))}}
 
-   :shortcut.handler/misc
-   ;; always overrides the copy due to "mod+c mod+s"
-   {:misc/copy
-    {:binding "mod+c"
-     :fn      (fn [] (js/document.execCommand "copy"))}
+    :shortcut.handler/misc
+    ;; always overrides the copy due to "mod+c mod+s"
+    {:misc/copy
+     {:binding "mod+c"
+      :fn      (fn [] (js/document.execCommand "copy"))}
 
-    :command-palette/toggle
-    {:desc    "Toggle command palette"
-     :binding "mod+shift+p"
-     :fn      (fn [] (state/toggle! :ui/command-palette-open?))}}
+     :command-palette/toggle
+     {:desc    "Toggle command palette"
+      :binding "mod+shift+p"
+      :fn      (fn [] (state/toggle! :ui/command-palette-open?))}}
 
-   :shortcut.handler/global-non-editing-only
-   ^{:before m/enable-when-not-editing-mode!}
-   {:command/run
-    {:desc    "Run git command"
-     :binding "mod+shift+1"
-     :fn      #(state/pub-event! [:command/run])}
-    :go/home
-    {:desc    "Go to home"
-     :binding "g h"
-     :fn      #(route-handler/redirect-to-home!)}
-    :go/keyboard-shortcuts
-    {:desc    "Go to keyboard shortcuts"
-     :binding "g s"
-     :fn      #(route-handler/redirect! {:to :shortcut-setting})}
-    :go/tomorrow
-    {:desc    "Go to tomorrow"
-     :binding "g t"
-     :fn      journal-handler/go-to-tomorrow!}
-    :go/next-journal
-    {:desc    "Go to next journal"
-     :binding "g n"
-     :fn      journal-handler/go-to-next-journal!}
-    :go/prev-journal
-    {:desc    "Go to previous journal"
-     :binding "g p"
-     :fn      journal-handler/go-to-prev-journal!}
-    :ui/toggle-document-mode
-    {:desc    "Toggle document mode"
-     :binding "t d"
-     :fn      state/toggle-document-mode!}
-    :ui/toggle-settings
-    {:desc    "Toggle settings"
-     :binding (if mac? "t s" ["t s" "mod+,"])
-     :fn      ui-handler/toggle-settings-modal!}
-    :ui/toggle-right-sidebar
-    {:desc    "Toggle right sidebar"
-     :binding "t r"
-     :fn      ui-handler/toggle-right-sidebar!}
-    :ui/toggle-left-sidebar
-    {:desc    "Toggle left sidebar"
-     :binding "t l"
-     :fn      ui-handler/toggle-left-sidebar!}
-    :ui/toggle-help
-    {:desc    "Toggle help"
-     :binding "shift+/"
-     :fn      ui-handler/toggle-help!}
-    :ui/toggle-theme
-    {:desc    "Toggle between dark/light theme"
-     :binding "t t"
-     :fn      state/toggle-theme!}
-    :ui/toggle-contents
-    {:desc    "Toggle Favorites in sidebar"
-     :binding "t f"
-     :fn      ui-handler/toggle-contents!}
-    :editor/open-file-in-default-app
-    {:desc    "Open file in default app"
-     :binding "o f"
-     :fn      page-handler/open-file-in-default-app}
-    :editor/open-file-in-directory
-    {:desc    "Open file in parent directory"
-     :binding "o d"
-     :fn      page-handler/open-file-in-directory}
-    :ui/toggle-wide-mode
-    {:desc    "Toggle wide mode"
-     :binding "t w"
-     :fn      ui-handler/toggle-wide-mode!}
-    :ui/select-theme-color
-    {:desc    "Select available theme colors"
-     :binding "t i"
-     :fn      plugin-handler/show-themes-modal!}
-    :ui/goto-plugins
-    {:desc    "Go to plugins dashboard"
-     :binding "t p"
-     :fn      plugin-handler/goto-plugins-dashboard!}
-    :editor/toggle-open-blocks
-    {:desc    "Toggle open blocks (collapse or expand all blocks)"
-     :binding "t o"
-     :fn      editor-handler/toggle-open!}
-    :ui/toggle-cards
-    {:desc    "Toggle cards"
-     :binding "t c"
-     :fn      ui-handler/toggle-cards!}
-    ;; :ui/toggle-between-page-and-file route-handler/toggle-between-page-and-file!
-    :git/commit
-    {:desc    "Git commit message"
-     :binding "c"
-     :fn      commit/show-commit-modal!}}})
+    :shortcut.handler/global-non-editing-only
+    ^{:before m/enable-when-not-editing-mode!}
+    {:command/run
+     {:desc    "Run git command"
+      :binding "mod+shift+1"
+      :fn      #(state/pub-event! [:command/run])}
+     :go/home
+     {:desc    "Go to home"
+      :binding "g h"
+      :fn      #(route-handler/redirect-to-home!)}
+     :go/keyboard-shortcuts
+     {:desc    "Go to keyboard shortcuts"
+      :binding "g s"
+      :fn      #(route-handler/redirect! {:to :shortcut-setting})}
+     :go/tomorrow
+     {:desc    "Go to tomorrow"
+      :binding "g t"
+      :fn      journal-handler/go-to-tomorrow!}
+     :go/next-journal
+     {:desc    "Go to next journal"
+      :binding "g n"
+      :fn      journal-handler/go-to-next-journal!}
+     :go/prev-journal
+     {:desc    "Go to previous journal"
+      :binding "g p"
+      :fn      journal-handler/go-to-prev-journal!}
+     :ui/toggle-document-mode
+     {:desc    "Toggle document mode"
+      :binding "t d"
+      :fn      state/toggle-document-mode!}
+     :ui/toggle-settings
+     {:desc    "Toggle settings"
+      :binding (if mac? "t s" ["t s" "mod+,"])
+      :fn      ui-handler/toggle-settings-modal!}
+     :ui/toggle-right-sidebar
+     {:desc    "Toggle right sidebar"
+      :binding "t r"
+      :fn      ui-handler/toggle-right-sidebar!}
+     :ui/toggle-left-sidebar
+     {:desc    "Toggle left sidebar"
+      :binding "t l"
+      :fn      ui-handler/toggle-left-sidebar!}
+     :ui/toggle-help
+     {:desc    "Toggle help"
+      :binding "shift+/"
+      :fn      ui-handler/toggle-help!}
+     :ui/toggle-theme
+     {:desc    "Toggle between dark/light theme"
+      :binding "t t"
+      :fn      state/toggle-theme!}
+     :ui/toggle-contents
+     {:desc    "Toggle Favorites in sidebar"
+      :binding "t f"
+      :fn      ui-handler/toggle-contents!}
+     :editor/open-file-in-default-app
+     {:desc    "Open file in default app"
+      :binding "o f"
+      :fn      page-handler/open-file-in-default-app}
+     :editor/open-file-in-directory
+     {:desc    "Open file in parent directory"
+      :binding "o d"
+      :fn      page-handler/open-file-in-directory}
+     :ui/toggle-wide-mode
+     {:desc    "Toggle wide mode"
+      :binding "t w"
+      :fn      ui-handler/toggle-wide-mode!}
+     :ui/select-theme-color
+     {:desc    "Select available theme colors"
+      :binding "t i"
+      :fn      plugin-handler/show-themes-modal!}
+     :ui/goto-plugins
+     {:desc    "Go to plugins dashboard"
+      :binding "t p"
+      :fn      plugin-handler/goto-plugins-dashboard!}
+     :editor/toggle-open-blocks
+     {:desc    "Toggle open blocks (collapse or expand all blocks)"
+      :binding "t o"
+      :fn      editor-handler/toggle-open!}
+     :ui/toggle-cards
+     {:desc    "Toggle cards"
+      :binding "t c"
+      :fn      ui-handler/toggle-cards!}
+     ;; :ui/toggle-between-page-and-file route-handler/toggle-between-page-and-file!
+     :git/commit
+     {:desc    "Git commit message"
+      :binding "c"
+      :fn      commit/show-commit-modal!}}}))
 
 
 ;; Categories for docs purpose
@@ -525,6 +526,8 @@
     :go/tomorrow
     :go/next-journal
     :go/prev-journal
+    :pdf/previous-page
+    :pdf/next-page
     :command/run
     :command-palette/toggle
     :sidebar/clear
@@ -540,3 +543,11 @@
     :date-picker/prev-week
     :date-picker/next-week
     :date-picker/complete]})
+
+(defn add-shortcut!
+  [handler-id id shortcut-map]
+  (swap! config assoc-in [handler-id id] shortcut-map))
+
+(defn remove-shortcut!
+  [handler-id id]
+  (swap! config medley/dissoc-in [handler-id id]))

+ 54 - 19
src/main/frontend/modules/shortcut/core.cljs

@@ -3,6 +3,7 @@
             [frontend.handler.config :as config]
             [frontend.handler.notification :as notification]
             [frontend.modules.shortcut.data-helper :as dh]
+            [frontend.modules.shortcut.config :as shortcut-config]
             [frontend.state :as state]
             [frontend.util :as util]
             [goog.events :as events]
@@ -22,6 +23,49 @@
 
 (def key-names (js->clj KeyNames))
 
+(defn register-shortcut!
+  "Register a shortcut, notice the id need to be a namespaced keyword to avoid
+  conflicts.
+  Example:
+  (register-shortcut! :shortcut.handler/misc :foo/bar {:binding \"mod+shift+8\"
+     :fn (fn []
+     (js/alert \"test shortcut\"))})"
+  ([handler-id id]
+   (register-shortcut! handler-id id nil))
+  ([handler-id id shortcut-map]
+   (when-let [handler (if (or (string? handler-id) (keyword? handler-id))
+                        (let [handler-id (keyword handler-id)]
+                          (-> (get @*installed handler-id)
+                              :handler))
+                        ;; handler
+                        handler-id)]
+
+     (when shortcut-map
+       (shortcut-config/add-shortcut! handler-id id shortcut-map))
+
+     (when-not (false? (dh/shortcut-binding id))
+       (doseq [k (dh/shortcut-binding id)]
+         (try
+           (log/debug :shortcut/register-shortcut {:id id :binding k})
+           (.registerShortcut handler (util/keyname id) k)
+           (catch js/Object e
+             (log/error :shortcut/register-shortcut {:id id
+                                                     :binding k
+                                                     :error e})
+             (notification/show! (str/join " " [id k (.-message e)]) :error false))))))))
+
+(defn unregister-shortcut!
+  "Unregister a shortcut.
+  Example:
+  (unregister-shortcut! :shortcut.handler/misc :foo/bar)"
+  [handler-id shortcut-id]
+  (when-let [handler (-> (get @*installed handler-id)
+                         :handler)]
+    (when shortcut-id
+      (let [k (dh/shortcut-binding shortcut-id)]
+        (.unregisterShortcut ^js handler k))
+      (shortcut-config/remove-shortcut! handler-id shortcut-id))))
+
 (defn install-shortcut!
   [handler-id {:keys [set-global-keys?
                       prevent-default?
@@ -41,22 +85,14 @@
     ;; register shortcuts
     (doseq [[id _] shortcut-map]
       ;; (log/info :shortcut/install-shortcut {:id id :shortcut (dh/shortcut-binding id)})
-      (when-not (false? (dh/shortcut-binding id))
-        (doseq [k (dh/shortcut-binding id)]
-          (try
-            (log/debug :shortcut/register-shortcut {:id id :binding k})
-            (.registerShortcut handler (util/keyname id) k)
-            (catch js/Object e
-              (log/error :shortcut/register-shortcut {:id id
-                                                      :binding k
-                                                      :error e})
-              (notification/show! (str/join " " [id k (.-message e)]) :error false))))))
+      (register-shortcut! handler id))
 
     (let [f (fn [e]
-              (let [dispatch-fn (get shortcut-map (keyword (.-identifier e)))]
+              (let [shortcut-map (dh/shortcut-map handler-id state)
+                    dispatch-fn (get shortcut-map (keyword (.-identifier e)))]
                 ;; trigger fn
-                (dispatch-fn e)))
-          install-id (medley/random-uuid)
+                (when dispatch-fn (dispatch-fn e))))
+          install-id handler-id
           data       {install-id
                       {:group      handler-id
                        :dispatch-fn f
@@ -78,13 +114,12 @@
        (map #(install-shortcut! % {}))
        doall))
 
-(defn- uninstall-shortcut! [install-id]
-  (when-let
-      [handler (-> (get @*installed install-id)
-                   :handler)]
+(defn uninstall-shortcut!
+  [handler-id]
+  (when-let [handler (-> (get @*installed handler-id)
+                         :handler)]
     (.dispose ^js handler)
-    (swap! *installed dissoc install-id)))
-
+    (swap! *installed dissoc handler-id)))
 
 (defn- uninstall-shortcut-aux!
   [state handler-id]

+ 10 - 9
src/main/frontend/modules/shortcut/data_helper.cljs

@@ -12,8 +12,9 @@
             [frontend.handler.common :as common-handler])
   (:import [goog.ui KeyboardShortcutHandler]))
 
-(defonce default-binding
-  (->> (vals config/default-config)
+(defn get-bindings
+  []
+  (->> (vals @config/config)
        (into {})
        (map (fn [[k {:keys [binding]}]]
               {k binding}))
@@ -26,7 +27,7 @@
 (defn shortcut-binding
   [id]
   (let [shortcut (get (state/shortcuts) id
-                      (get default-binding id))]
+                      (get (get-bindings) id))]
     (cond
       (nil? shortcut)
       (log/error :shortcut/binding-not-found {:id id})
@@ -45,7 +46,7 @@
 
 ;; returns a vector to preserve order
 (defn binding-by-category [name]
-  (let [dict (->> (vals config/default-config)
+  (let [dict (->> (vals @config/config)
                   (apply merge)
                   (map (fn [[k _]]
                          {k {:binding (shortcut-binding k)}}))
@@ -57,7 +58,7 @@
   ([handler-id]
    (shortcut-map handler-id nil))
   ([handler-id state]
-   (let [raw       (get config/default-config handler-id)
+   (let [raw       (get @config/config handler-id)
          handler-m (->> raw
                         (map (fn [[k {:keys [fn]}]]
                                {k fn}))
@@ -77,7 +78,7 @@
     (keyword (str "command." ns) n)))
 
 (defn desc-helper []
-  (->> (vals config/default-config)
+  (->> (vals @config/config)
        (apply merge)
        (map (fn [[k {:keys [desc]}]]
               {(decorate-namespace k) desc}))
@@ -155,7 +156,7 @@
   "Given shortcut key, return handler group
   eg: :editor/new-line -> :shortcut.handler/block-editing-only"
   [k]
-  (->> config/default-config
+  (->> @config/config
        (filter (fn [[_ v]] (contains? v k)))
        (map key)
        (first)))
@@ -182,7 +183,7 @@
 
 (defn shortcut-data-by-id [id]
   (let [binding (shortcut-binding id)
-        data    (->> (vals config/default-config)
+        data    (->> (vals @config/config)
                      (into  {})
                      id)]
     (when binding
@@ -192,7 +193,7 @@
        (binding-for-display id binding)))))
 
 (defn shortcuts->commands [handler-id]
-  (let [m (get config/default-config handler-id)]
+  (let [m (get @config/config handler-id)]
     (->> m
          (map (fn [[id _]] (-> (shortcut-data-by-id id)
                                (assoc :id id)