Browse Source

new shortcut module

Weihua Lu 4 năm trước cách đây
mục cha
commit
0b1f2f7cc4

+ 91 - 0
src/main/frontend/modules/shortcut/binding.cljc

@@ -0,0 +1,91 @@
+(ns frontend.modules.shortcut.binding
+  (:require [frontend.util :refer [mac?]]))
+
+(def default
+  {:date-picker/complete "enter"
+   :date-picker/prev-day "left"
+   :date-picker/next-day "right"
+   :date-picker/prev-week "up"
+   :date-picker/next-week "down"
+
+   :auto-complete/prev "up"
+   :auto-complete/next "down"
+   :auto-complete/complete "enter"
+
+   :block-selection/copy "mod+c"
+   :block-selection/cut "mod+x"
+   :block-selection/delete ["backspace" "delete"]
+
+   :editor/clear-selection "esc"
+   :editor/toggle-document-mode "t d"
+   :editor/toggle-settings (if mac? "t s" ["t s" "mod+,"])
+   :editor/undo "mod+z"
+   :editor/redo ["shift+mod+z" "mod+y"]
+   :editor/zoom-in (if mac? "mod+." "alt+right")
+   :editor/zoom-out (if mac? "mod+," "alt+left")
+   :editor/cycle-todo "mod+enter"
+   :editor/expand-block-children "mod+down"
+   :editor/collapse-block-children "mod+up"
+   :editor/follow-link "mod+o"
+   :editor/open-link-in-sidebar "mod+shift+o"
+   :editor/bold "mod+b"
+   :editor/italics "mod+i"
+   :editor/highlight "mod+shift+h"
+   :editor/insert-link "mod+shift+i"
+   :editor/select-all-blocks "mod+shift+a"
+   :editor/move-block-up (if mac? "mod+shift+up"  "alt+shift+up")
+   :editor/move-block-down (if mac? "mod+shift+down" "alt+shift+down")
+   :editor/save "mod+s"
+   :editor/open-block-first "down"
+   :editor/open-block-last "up"
+   :editor/select-block-up "shift+up"
+   :editor/select-block-down "shift+down"
+
+   :editor/new-block "enter"
+   :editor/new-line "shift+enter"
+   :editor/up "up"
+   :editor/down "down"
+   :editor/left "left"
+   :editor/right "right"
+   :editor/delete "backspace"
+   :editor/indent "tab"
+   :editor/outindent "shift+tab"
+
+   :ui/toggle-help "shift+/"
+   :ui/toggle-theme "t t"
+   :ui/toggle-right-sidebar "t r"
+   :ui/toggle-new-block "t e"
+   :ui/show-contents "t c"
+   :ui/toggle-wide-mode "t w"
+   :ui/toggle-between-page-and-file "s"
+   :ui/fold "tab"
+   :ui/un-fold "shift+tab"
+   :ui/toggle-brackets "mod+c mod+b"
+   :ui/refresh ["f5" "mod+r" "mod+shift+r"]
+
+   :go/search "mod+u"
+   :go/journals (if mac? "mod+j" "alt+j")
+
+   :git/commit "g c"
+
+   :search/re-index "mod+c mod+s"
+   :graph/re-index "mod+c mod+r"})
+
+(def custom
+  {:editor/new-block "enter"
+   :editor/new-line "shift+enter"
+   :editor/up ["ctrl+k" "up"]
+   :editor/down ["ctrl+j" "down"]
+   :editor/left ["ctrl+h" "left"]
+   :editor/right ["ctrl+l" "right"]
+   :editor/delete ["ctrl+d" "backspace"]
+
+   :date-picker/complete ["ctrl+a" "enter"]
+   :date-picker/prev-day ["ctrl+h" "left"]
+   :date-picker/next-day ["ctrl+l" "right"]
+   :date-picker/prev-week ["ctrl+k" "up"]
+   :date-picker/next-week ["ctrl+j" "down"]
+
+   :auto-complete/prev ["ctrl+k" "up"]
+   :auto-complete/next ["ctrl+j" "down"]
+   :auto-complete/complete ["ctrl+l" "enter"]})

+ 67 - 0
src/main/frontend/modules/shortcut/core.cljs

@@ -0,0 +1,67 @@
+(ns frontend.modules.shortcut.core
+  (:require [clojure.string :as str]
+            [frontend.modules.shortcut.binding :as binding]
+            [frontend.modules.shortcut.handler :refer [handler]]
+            [frontend.state :as state]
+            [frontend.util :as util]
+            [goog.events :as events]
+            [goog.ui.KeyboardShortcutHandler.EventType :as EventType]
+            [lambdaisland.glogi :as log])
+  (:import [goog.ui KeyboardShortcutHandler]))
+
+(def installed (atom []))
+(def binding-profile (atom [binding/default binding/custom]))
+
+(defn- mod-key [shortcut]
+  (str/replace shortcut #"(?i)mod"
+               (if util/mac? "meta" "ctrl")))
+(defn shortcut-binding
+  [id]
+  (let [shortcut (or (state/get-shortcut id)
+                     (get (apply merge @binding-profile) id))]
+    (when-not shortcut
+      (log/error :shortcut/binding-not-found {:id id}))
+    (->>
+     (if (string? shortcut)
+       [shortcut]
+       shortcut)
+     (mapv mod-key))))
+
+(defn install-shortcut!
+  [shortcut-map]
+  (let [handler (new KeyboardShortcutHandler js/window)]
+    ;; default is false, set it to true to deal with arrow keys, etc
+    (.setAllShortcutsAreGlobal handler true)
+    ;; default is true, set it to false here
+    (.setAlwaysPreventDefault handler false)
+
+    ;; register shortcuts
+    (doseq [[id _] shortcut-map]
+      (log/info :shortcut/install-shortcut {:id id :shortcut (shortcut-binding id)})
+      (doseq [k (shortcut-binding id)]
+        (.registerShortcut handler (util/keyname id) k)))
+
+    (let [f (fn [e]
+              (let [dispatch-fn (get shortcut-map (keyword (.-identifier e)))]
+                ;; trigger fn
+                (dispatch-fn e)))
+          unlisten-fn (fn [] (.dispose handler))]
+
+      (events/listen handler EventType/SHORTCUT_TRIGGERED f)
+
+      ;; return deregister fn
+      (fn []
+        (log/info :shortcut/dispose (into [] (keys shortcut-map)))
+        (unlisten-fn)))))
+
+(defn install-shortcuts!
+  []
+  (let [result (->> handler
+                    (map #(install-shortcut! %))
+                    doall)]
+    (reset! installed result)))
+
+(defn uninstall-shortcuts!
+  []
+  (doseq [f @installed]
+    (f)))

+ 99 - 0
src/main/frontend/modules/shortcut/handler.cljs

@@ -0,0 +1,99 @@
+(ns frontend.modules.shortcut.handler
+  (:require [frontend.components.commit :as commit]
+            [frontend.handler.config :as config-handler]
+            [frontend.handler.editor :as editor-handler]
+            [frontend.handler.git :as git-handler]
+            [frontend.handler.repo :as repo-handler]
+            [frontend.handler.route :as route-handler]
+            [frontend.handler.search :as search-handler]
+            [frontend.handler.ui :as ui-handler]
+            [frontend.handler.web.nfs :as nfs-handler]
+            [frontend.modules.shortcut.mixin :refer :all]
+            [frontend.state :as state]
+            [frontend.ui.date-picker :as date-picker]))
+
+(def handler
+[;; editing only
+ (before
+  enable-when-block-editing!
+  (let [state-fn (state-f :component/box)]
+    {:editor/new-block (editor-handler/keydown-new-block-handler state-fn)
+     :editor/new-line (editor-handler/keydown-new-line-handler state-fn)
+     :editor/up (editor-handler/keydown-up-down-handler :up)
+     :editor/down (editor-handler/keydown-up-down-handler :down)
+     :editor/left (editor-handler/keydown-arrow-handler :left)
+     :editor/right (editor-handler/keydown-arrow-handler :right)
+     :editor/delete (editor-handler/keydown-backspace-handler state-fn)
+     :editor/indent (editor-handler/keydown-tab-handler state-fn :right)
+     :editor/outindent (editor-handler/keydown-tab-handler state-fn :left)
+     :editor/zoom-in  editor-handler/zoom-in!
+     :editor/zoom-out  editor-handler/zoom-out!
+     :editor/cycle-todo editor-handler/cycle-todo!
+     :editor/expand-block-children editor-handler/expand!
+     :editor/collapse-block-children editor-handler/collapse!
+     :editor/follow-link editor-handler/follow-link-under-cursor!
+     :editor/open-link-in-sidebar editor-handler/open-link-in-sidebar!
+     :editor/bold editor-handler/bold-format!
+     :editor/italics editor-handler/italics-format!
+     :editor/highlight editor-handler/highlight-format!
+     :editor/insert-link editor-handler/html-link-format!
+     :editor/select-all-blocks editor-handler/select-all-blocks!
+     :editor/move-block-up (editor-handler/move-up-down true)
+     :editor/move-block-down (editor-handler/move-up-down false)}))
+
+ ;; global
+ (before
+  prevent-default-behavior
+  {:editor/select-block-up (editor-handler/on-select-block :up)
+   :editor/select-block-down (editor-handler/on-select-block :down)
+
+   :editor/save editor-handler/save!
+
+   :ui/toggle-brackets config-handler/toggle-ui-show-brackets!
+   :go/search route-handler/go-to-search!
+   :go/journals route-handler/go-to-journals!
+
+   :search/re-index search-handler/rebuild-indices!
+   :graph/re-index #(repo-handler/re-index! nfs-handler/rebuild-index!)})
+
+ ;; non-editing only
+ (before
+  enable-when-not-editing-mode!
+  {:editor/toggle-document-mode state/toggle-document-mode!
+   :editor/toggle-settings ui-handler/toggle-settings-modal!
+
+   :editor/open-block-first (editor-handler/open-block! true)
+   :editor/open-block-last (editor-handler/open-block! false)
+
+   :ui/toggle-right-sidebar ui-handler/toggle-right-sidebar!
+   :ui/toggle-help ui-handler/toggle-help!
+   :ui/toggle-theme state/toggle-theme!
+   :ui/toggle-new-block state/toggle-new-block-shortcut!
+   :ui/show-contents ui-handler/toggle-contents!
+   :ui/toggle-wide-mode ui-handler/toggle-wide-mode!
+   :ui/toggle-between-page-and-file route-handler/toggle-between-page-and-file!
+   :ui/fold (editor-handler/on-tab :right)
+   :ui/un-fold (editor-handler/on-tab :left)
+
+   :git/commit (git-handler/show-commit-modal! commit/add-commit-message)})
+
+ (before
+  (enable-when-component! :component/auto-complete)
+  (let [state-fn (state-f :component/auto-complete)]
+    {:auto-complete/prev (ui-handler/auto-complete-prev state-fn)
+     :auto-complete/next (ui-handler/auto-complete-next state-fn)
+     :auto-complete/complete (ui-handler/auto-complete-complete state-fn)}))
+
+ (before
+  enable-when-selection!
+  {:block-selection/copy editor-handler/shortcut-copy-selection
+   :block-selection/cut editor-handler/shortcut-cut-selection
+   :block-selection/delete editor-handler/shortcut-delete-selection})
+
+ (before
+  (enable-when-component! :component/date-picker)
+  {:date-picker/complete (date-picker/shortcut-complete (state-f :component/date-picker))
+   :date-picker/prev-day date-picker/shortcut-prev-day
+   :date-picker/next-day date-picker/shortcut-next-day
+   :date-picker/prev-week date-picker/shortcut-prev-week
+   :date-picker/next-week date-picker/shortcut-next-week})])

+ 78 - 0
src/main/frontend/modules/shortcut/mixin.cljs

@@ -0,0 +1,78 @@
+(ns frontend.modules.shortcut.mixin
+  (:require [frontend.config :as config]
+            [frontend.state :as state]
+            [frontend.util :as util]))
+
+(def state-store (atom {}))
+
+(defn state-f [k]
+  (fn [] (get @state-store k)))
+
+(defn bind-state [k]
+  {:after-render
+   (fn [state]
+     (js/setTimeout
+      (fn []
+        (swap! state-store assoc k state))
+      100)
+     state)
+
+   :did-remount
+   (fn [_ new-state]
+     (swap! state-store assoc k new-state)
+     new-state)
+
+   :will-unmount
+   (fn [state]
+     (swap! state-store dissoc k)
+     state)})
+
+(defn before [f shortcut-map]
+  (reduce-kv (fn [r k v]
+               (assoc r k (f v)))
+             {}
+             shortcut-map))
+
+;; middleware for before function
+(defn prevent-default-behavior
+  [f]
+  (fn [e]
+    (f e)
+    ;; return false to prevent default browser behavior
+    ;; and stop event from bubbling
+    false))
+
+(defn enable-when-not-editing-mode!
+  [f]
+  (fn [e]
+    (when-not (or (state/editing?)
+                  (util/input? (.-target e)))
+      (f e)
+      false)))
+
+(defn only-enable-when-dev!
+  [_]
+  (boolean config/dev?))
+
+(defn enable-when-block-editing!
+  [f]
+  (fn [e]
+    (when (state/editing?)
+      (f e)
+      false)))
+
+(defn enable-when-selection!
+  [f]
+  (fn [e]
+    (when (and (state/in-selection-mode?)
+               (seq (state/get-selection-blocks)))
+      (f e)
+      false)))
+
+(defn enable-when-component!
+  [component-k]
+  (fn [f]
+    (fn [e]
+      (when ((state-f component-k))
+        (f e)
+        false))))