1
0
Эх сурвалжийг харах

Feat/cmdk radix tweaks (#10493)

* prefer --lx-gray-11

* Fix sidebar item color

* Update theme mode images

* Update shortcuts globally

* Assets updated to support radix

* fix: add missing keyboard-shortcut class

* fix: custom button background

* fix: lower case shortcuts

* chore: remove old shortcut list

* fix: shortcut ns

* enhance: shortcut accepts both sequence and string

* fix: shortcut remap ux

* fix: remove codemirror box shadow

---------

Co-authored-by: Ben Yorke <[email protected]>
Tienson Qin 1 жил өмнө
parent
commit
96693439d0

+ 1 - 1
deps/shui/src/logseq/shui/button/v2.cljs

@@ -38,7 +38,7 @@
                [:<>
                 (when (< 0 index)
                   [:div.shui__button__tile-separator])
-                [:div.shui__button__tile {:class ""} tile]]))
+                [:div.shui__button__tile tile]]))
 
      (when icon
        (icon/root icon icon-props))

+ 1 - 1
deps/shui/src/logseq/shui/core.cljs

@@ -14,7 +14,7 @@
 
 ;; shortcut
 (def shortcut shui.shortcut.v1/root)
-(def shortcut-v2 shui.shortcut.v1/root)
+(def shortcut-v1 shui.shortcut.v1/root)
 
 ;; button component
 (def button shui.button.v2/root)

+ 67 - 21
deps/shui/src/logseq/shui/shortcut/v1.cljs

@@ -1,11 +1,13 @@
 (ns logseq.shui.shortcut.v1
   (:require [clojure.string :as string]
             [logseq.shui.button.v2 :as button]
-            [rum.core :as rum]))
+            [rum.core :as rum]
+            [goog.userAgent]))
 
+(def mac? goog.userAgent/MAC)
 (defn print-shortcut-key [key]
   (case key
-    ("cmd" "command" "mod" "⌘") "⌘"
+    ("cmd" "command" "mod" "⌘" "meta") (if mac? "⌘" "⊞ win")
     ("return" "enter" "⏎") "⏎"
     ("shift" "⇧") "⇧"
     ("alt" "option" "opt" "⌥") "⌥"
@@ -15,9 +17,20 @@
     ("down" "↓") "↓"
     ("left" "←") "←"
     ("right" "→") "→"
-    ("disabled" "Disabled") ""
-    ("backspace" "delete") ""
-    ("tab") ""
+    ("tab") "⇥"
+    ("open-square-bracket") "["
+    ("close-square-bracket") "]"
+    ("dash") "-"
+    ("semicolon") ";"
+    ("equals") "="
+    ("single-quote") "'"
+    ("backslash") "\\"
+    ("comma") ","
+    ("period") "."
+    ("slash") "/"
+    ("grave-accent") "`"
+    ("page-up") ""
+    ("page-down") ""
     (nil) ""
     (name key)))
 
@@ -36,19 +49,52 @@
   [shortcut context & {:keys [tiled size]
                        :or {tiled true
                             size :sm}}]
-  [:<>
-   (for [[index option] (map-indexed vector (string/split shortcut #" \| "))]
-     [:<>
-      (when (< 0 index)
-        [:div.text-gray-11.text-sm "|"])
-      (for [sequence (string/split option #" ")
-            :let [text (->> (string/split sequence #"\+")
-                            (map print-shortcut-key)
-                            (apply str))]]
-        (button/root {:theme :gray
-                      :interactive false
-                      :text (string/upper-case (to-string text))
-                      :tiled tiled
-                      :size size
-                      :mused true}
-                     context))])])
+  (when (seq shortcut)
+    (if (coll? shortcut)
+      (let [texts (map print-shortcut-key shortcut)
+            tiled? (every? #(= (count %) 1) texts)]
+        (if tiled?
+          [:div.flex.flex-row
+           (for [text texts]
+             (button/root {:theme :gray
+                           :interactive false
+                           :text (to-string text)
+                           :tiled tiled?
+                           :size size
+                           :mused true}
+                          context))]
+          (let [text' (string/join " " texts)]
+            (button/root {:theme :gray
+                          :interactive false
+                          :text text'
+                          :tiled false
+                          :size size
+                          :mused true}
+                         context))))
+      [:<>
+       (for [[index option] (map-indexed vector (string/split shortcut #" \| "))]
+         [:<>
+          (when (< 0 index)
+            [:div.text-gray-11.text-sm "|"])
+          (let [[system-default option] (if (.startsWith option "system default: ")
+                                          [true (subs option 16)]
+                                          [false option])]
+            [:<>
+             (when system-default
+               [:div.mr-1.text-xs "System default: "])
+             (for [sequence (string/split option #" ")
+                   :let [text (->> (string/split sequence #"\+")
+                                   (map print-shortcut-key)
+                                   (apply str))]]
+               (let [tiled? (if (contains?
+                                 #{"backspace" "delete" "home" "end" "insert"}
+                                 (string/lower-case text))
+                              false
+                              tiled)]
+                 (button/root {:theme :gray
+                               :interactive false
+                               :text (to-string text)
+                               :tiled tiled?
+                               :size size
+                               :mused true}
+                              context)))])])])))

+ 1 - 1
resources/css/codemirror.lsradix.css

@@ -17,7 +17,7 @@ http://ethanschoonover.com/lsradix/img/lsradix-palette.png
 .lsradix.base0         { color: or(--lx-gray-09, #839496); }
 .lsradix.base1         { color: or(--lx-gray-10, #93a1a1); }
 .lsradix.base2         { color: or(--lx-gray-11, #eee8d5); }
-.lsradix.base3         { color: or(--lx-gray-12, #fdf6e3); }
+.lsradix.base3         { color: or(--lx-gray-11, #fdf6e3); }
 .lsradix.solar-yellow  { color: or(--rx-yellow-11, #b58900); }
 .lsradix.solar-orange  { color: or(--rx-orange-11, #cb4b16); }
 .lsradix.solar-red     { color: or(--rx-red-11, #dc322f); }

+ 1 - 2
resources/css/shui.css

@@ -245,9 +245,8 @@
 .shui__button-theme-color.shui__button-color-custom  { background-color: hsl(var(--ls-button-background-hsl) / 0.9);
                                                        color: white; }
 
-.shui__button-color-custom:hover, .dark .shui__button-color-custom:hover {
+.shui__button-theme-color.shui__button-color-custom:hover {
     background: var(--ls-button-background);
-    color: white;
 }
 
 .shui__button-theme-color.shui__button-color-lime    { color: white; background-color: var(--rx-lime-09); &:hover { background-color: var(--rx-lime-10); } &:active { background-color: var(--rx-lime-08); }}

BIN
resources/img/dark-theme.png


BIN
resources/img/file-sync-unavailale-nonbacker-dark.png


BIN
resources/img/file-sync-unavailale-nonbacker-light.png


BIN
resources/img/file-sync-welcome-backer-dark.png


BIN
resources/img/file-sync-welcome-backer-light.png


BIN
resources/img/light-theme.png


BIN
resources/img/system-theme.png


BIN
resources/img/whiteboard-welcome-dark.png


BIN
resources/img/whiteboard-welcome-light.png


+ 1 - 1
src/main/frontend/common.css

@@ -529,7 +529,7 @@ i.ti {
 /* region FIXME: override elements (?) */
 h1.title {
   margin-bottom: 1.5rem;
-  color: or(--ls-journal-title-color, --lx-gray-12, --ls-title-text-color, #222);
+  color: or(--ls-journal-title-color, --lx-gray-11, --ls-title-text-color, #222);
   font-size: var(--ls-page-title-size, 36px);
   font-weight: 500;
 }

+ 2 - 2
src/main/frontend/components/container.css

@@ -16,7 +16,7 @@
 
 #root {
   > div {
-    color: or(--ls-document-text-color, --lx-gray-12, --ls-primary-text-color, #24292e);
+    color: or(--ls-document-text-color, --lx-gray-11, --ls-primary-text-color, #24292e);
     font-size: var(--ls-page-text-size);
   }
 }
@@ -170,7 +170,7 @@
     }
 
     &.active, &:active {
-      background-color: or(--ls-left-sidebar-active-background, --lx-gray-05, --ls-quaternary-background-color);
+      background-color: or(--ls-left-sidebar-active-background, --lx-gray-04, --ls-quaternary-background-color);
       color: or(--ls-left-sidebar-active-text-color, --lx-gray-12);
       .ui__icon {
         opacity: .9;

+ 2 - 2
src/main/frontend/components/right_sidebar.cljs

@@ -4,7 +4,7 @@
             [frontend.components.block :as block]
             [frontend.components.onboarding :as onboarding]
             [frontend.components.page :as page]
-            [frontend.components.shortcut :as shortcut]
+            [frontend.components.shortcut-help :as shortcut-help]
             [frontend.components.cmdk :as cmdk]
             [frontend.context.i18n :refer [t]]
             [frontend.date :as date]
@@ -56,7 +56,7 @@
 (rum/defc shortcut-settings
   []
   [:div.contents.flex-col.flex.ml-3
-   (shortcut/shortcut-page {:show-title? false})])
+   (shortcut-help/shortcut-page {:show-title? false})])
 
 (defn- block-with-breadcrumb
   [repo block idx sidebar-key ref?]

+ 7 - 6
src/main/frontend/components/settings.cljs

@@ -26,7 +26,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.components.shortcut :as shortcut]
             [frontend.spec.storage :as storage-spec]
             [frontend.state :as state]
             [frontend.storage :as storage]
@@ -302,13 +302,14 @@
                              :action     action})))
 
 (defn theme-modes-row [t switch-theme system-theme? dark?]
-  (let [pick-theme [:ul.theme-modes-options
+  (let [color-accent (state/sub :ui/radix-color)
+        pick-theme [:ul.theme-modes-options
                     [:li {:on-click (partial state/use-theme-mode! "light")
-                          :class    (classnames [{:active (and (not system-theme?) (not dark?))}])} [:i.mode-light] [:strong (t :settings-page/theme-light)]]
+                          :class    (classnames [{:active (and (not system-theme?) (not dark?))}])} [:i.mode-light {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-light)]]
                     [:li {:on-click (partial state/use-theme-mode! "dark")
-                          :class    (classnames [{:active (and (not system-theme?) dark?)}])} [:i.mode-dark] [:strong (t :settings-page/theme-dark)]]
+                          :class    (classnames [{:active (and (not system-theme?) dark?)}])} [:i.mode-dark {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-dark)]]
                     [:li {:on-click (partial state/use-theme-mode! "system")
-                          :class    (classnames [{:active system-theme?}])} [:i.mode-system] [:strong (t :settings-page/theme-system)]]]]
+                          :class    (classnames [{:active system-theme?}])} [:i.mode-system {:class (when color-accent "radix")}] [:strong (t :settings-page/theme-system)]]]]
     (row-with-button-action {:left-label (t :right-side-bar/switch-theme (string/capitalize switch-theme))
                              :-for       "toggle_theme"
                              :action     pick-theme
@@ -1177,7 +1178,7 @@
          (settings-editor current-repo)
 
          :keymap
-         (shortcut2/shortcut-keymap-x)
+         (shortcut/shortcut-keymap-x)
 
          :version-control
          (settings-git)

+ 12 - 0
src/main/frontend/components/settings.css

@@ -222,6 +222,18 @@
           &.mode-system {
             background-position-x: -194px;
           }
+
+          &.mode-dark.radix {
+            background: url('../img/dark-theme.png') no-repeat center / cover;
+          }
+
+          &.mode-light.radix {
+            background: url('../img/light-theme.png') no-repeat center / cover;
+          }
+
+          &.mode-system.radix {
+            background: url('../img/system-theme.png') no-repeat center / cover;
+          }
         }
 
         > strong {

+ 470 - 213
src/main/frontend/components/shortcut.cljs

@@ -1,221 +1,478 @@
 (ns frontend.components.shortcut
-  "This namespace will be replaced later with frontend.components.shortcut2."
-  (:require [clojure.string :as str]
+  (: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.state :as state]
-            [frontend.ui :as ui]
-            [frontend.extensions.latex :as latex]
-            [frontend.extensions.highlight :as highlight]
-            [logseq.graph-parser.util.block-ref :as block-ref]
-            [logseq.graph-parser.util.page-ref :as page-ref]
-            [rum.core :as rum]))
-
-(rum/defcs customize-shortcut-dialog-inner <
-  (rum/local "")
-  (rum/local nil :rum/action)
-  (shortcut/record!)
-  [state k action-name current-binding]
-  (let [*keypress         (:rum/local state)
-        *action           (:rum/action state)
-        keypressed?       (not= "" @*keypress)
-        keyboard-shortcut (if-not keypressed? current-binding @*keypress)]
-    [:<>
-     [:div.sm:w-lsm
-      [:p.mb-4 "Press any sequence of keys to set the shortcut for the " [:b action-name] " action."]
-      [:p.mb-4.mt-4
-       (ui/render-keyboard-shortcut (-> keyboard-shortcut
-                                        (str/trim)
-                                        (str/lower-case)
-                                        (str/split  #" |\+")))
-       " "
+            [frontend.modules.shortcut.config :as shortcut-config]
+            [logseq.shui.core :as shui]
+            [frontend.shui :refer [make-shui-context]])
+  (: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.text-sm
-          {:style    {:margin-left "12px"}
-           :on-click (fn []
-                       (dh/remove-shortcut k)
-                       (shortcut/refresh!)
-                       (swap! *keypress (constantly ""))          ;; Clear local state
-                       )}
-          "Reset"])]]
-     [:div.cancel-save-buttons.text-right.mt-4
-      (ui/button "Save" :on-click (fn []
-                                    (reset! *action :save)
-                                    (state/close-modal!)))
-      [:a.ml-4
-       {:on-click (fn []
-                    (reset! *keypress (dh/binding-for-storage current-binding))
-                    (reset! *action :cancel)
-                    (state/close-modal!))} "Cancel"]]]))
-
-(defn customize-shortcut-dialog [k action-name displayed-binding]
-  (fn [_]
-    (customize-shortcut-dialog-inner k action-name displayed-binding)))
-
-(rum/defc shortcut-col [_category k binding configurable? action-name]
-  (let [conflict?         (dh/potential-conflict? k)
-        displayed-binding (dh/binding-for-display k binding)
-        disabled?         (str/includes? displayed-binding "system default")]
-    (if (not configurable?)
-      [:td.text-right displayed-binding]
-      [:td.text-right
+         [: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
+              :when (string? x)]
+          [: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
+      (if (and dirty? (or user-binding binding))
+        [:a.flex.items-center.space-x-1.text-sm.fade-link
+         {:on-click #(set-current-binding! (or user-binding binding))}
+         (t :keymap/restore-to-default)
+         (for [it (some->> (or binding user-binding) (map #(some->> % (dh/mod-key) (shortcut-utils/decorate-binding))))]
+           [:span.keyboard-shortcut.ml-1 [:code it]])]
+        [:div])
+
+      [:div.flex.flex-row.items-center.gap-2
        (ui/button
-        displayed-binding
-        :class "text-sm p-1"
-        :style {:cursor (if disabled? "not-allowed" "pointer")}
-        :title (if conflict?
-                 "Shortcut conflict!"
-                 (if disabled? "Cannot override system default" "Click to modify"))
-        :background (if conflict? "pink" (when disabled? "gray"))
-        :on-click (when-not disabled?
-                    #(state/set-sub-modal!
-                       (customize-shortcut-dialog k action-name displayed-binding)
-                       {:center? true})))])))
-
-(rum/defcs shortcut-table
-  < rum/reactive
-    (rum/local true ::folded?)
-    {:will-mount (fn [state]
-                   (let [name (first (:rum/args state))]
-                     (cond-> state
-                             (contains? #{:shortcut.category/basics}
-                                        name)
-                             (-> ::folded? (reset! false) (do state)))))}
-  [state category configurable?]
-  (let [*folded? (::folded? state)
-        plugin?  (= category :shortcut.category/plugins)
-        _        (state/sub [:config (state/get-current-repo) :shortcuts])]
-    [:div.cp__shortcut-table-wrap
-     [:a.fold
-      {:on-click #(reset! *folded? (not @*folded?))}
-      (ui/icon (if @*folded? "chevron-left" "chevron-down"))]
-     [:table
-      [:thead
-       [:tr
-        [:th.text-left [:b (t category)]]
-        [:th.text-right]]]
-      (when-not @*folded?
-        [:tbody
-         (map (fn [[k {:keys [binding]}]]
-                (let [cmd   (dh/shortcut-cmd k)
-                      label (cond
-                              (string? (:desc cmd))
-                              [:<>
-                               [:code.text-xs (namespace k)]
-                               [:small.pl-1 (:desc cmd)]]
-
-                              (not plugin?) (-> k (shortcut-utils/decorate-namespace) (t))
-                              :else (str k))]
-                  [:tr {:key (str k)}
-                   [:td.text-left.flex.items-center label]
-                   (shortcut-col category k binding configurable? label)]))
-              (dh/binding-by-category category))])]]))
-
-(rum/defc trigger-table []
-  [:table
-   [:thead
-    [:tr
-     [:th.text-left [:b (t :help/shortcuts-triggers)]]
-     [:th.text-right [:b (t :help/shortcut)]]]]
-   [:tbody
-    [:tr
-     [:td.text-left (t :help/slash-autocomplete)]
-     [:td.text-right [:code "/"]]]
-    [:tr
-     [:td.text-left (t :help/block-content-autocomplete)]
-     [:td.text-right [:code "<"]]]
-    [:tr
-     [:td.text-left (t :help/reference-autocomplete)]
-     [:td.text-right [:code page-ref/left-and-right-brackets]]]
-    [:tr
-     [:td.text-left (t :help/block-reference)]
-     [:td.text-right [:code block-ref/left-and-right-parens]]]
-    [:tr
-     [:td.text-left (t :help/open-link-in-sidebar)]
-     [:td.text-right (ui/render-keyboard-shortcut ["shift" "click"])]]
-    [:tr
-     [:td.text-left (t :help/context-menu)]
-     [:td.text-right (ui/render-keyboard-shortcut ["right" "click"])]]]])
-
-(defn markdown-and-orgmode-syntax []
-  (let [list [:bold :italics :del :mark :latex :code :link :pre :img]
-
-        preferred-format (state/get-preferred-format) ; markdown/org
-
-        title (case preferred-format
-                :markdown (t :help/markdown-syntax)
-                :org (t :help/org-mode-syntax))
-
-        learn-more (case preferred-format
-                     :markdown "https://www.markdownguide.org/basic-syntax"
-                     :org "https://orgmode.org/worg/dev/org-syntax.html")
-
-        raw (case preferred-format
-              :markdown {:bold (str "**" (t :bold) "**")
-                         :italics (str "_" (t :italics) "_")
-                         :link "[Link](https://www.example.com)"
-                         :del (str "~~" (t :strikethrough) "~~")
-                         :mark (str "^^" (t :highlight) "^^")
-                         :latex "$$E = mc^2$$"
-                         :code (str "`" (t :code) "`")
-                         :pre "```clojure\n  (println \"Hello world!\")\n```"
-                         :img "![image](https://asset.logseq.com/static/img/logo.png)"}
-              :org {:bold (str "*" (t :bold) "*")
-                    :italics (str "/" (t :italics) "/")
-                    :del (str "+" (t :strikethrough) "+")
-                    :pre [:pre "#+BEGIN_SRC clojure\n  (println \"Hello world!\")\n#+END_SRC"]
-                    :link "[[https://www.example.com][Link]]"
-                    :mark (str "^^" (t :highlight) "^^")
-                    :latex "$$E = mc^2$$"
-                    :code "~Code~"
-                    :img "[[https://asset.logseq.com/static/img/logo.png][image]]"})
-
-        rendered {:italics [:i (t :italics)]
-                  :bold [:b (t :bold)]
-                  :link [:a {:href "https://www.example.com"} "Link"]
-                  :del [:del (t :strikethrough)]
-                  :mark [:mark (t :highlight)]
-                  :latex (latex/latex "help-latex" "E = mc^2" true false)
-                  :code [:code (t :code)]
-                  :pre (highlight/highlight "help-highlight" {:data-lang "clojure"} "(println \"Hello world!\")")
-                  :img [:img {:style {:float "right" :width 32 :height 32}
-                              :src "https://asset.logseq.com/static/img/logo.png"
-                              :alt "image"}]}]
-
-    [:table
-     [:thead
-      [:tr
-       [:th.text-left [:b title]]
-       [:th.text-right [:a {:href learn-more} "Learn more →"]]]]
-     [:tbody
-      (map (fn [name]
-             [:tr
-              [:td.text-left [(if (= :pre name) :pre :code) (get raw name)]]
-              [:td.text-right (get rendered name)]])
-        list)]]))
-
-(rum/defc keymap-tables
+        (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))))))]]]))
+
+(defn build-categories-map
+  []
+  (->> categories
+       (map #(vector % (into (sorted-map) (dh/binding-by-category %))))))
+
+(rum/defc ^:large-vars/cleanup-todo shortcut-keymap-x
   []
-  [:div.cp__keymap-tables
-   (shortcut-table :shortcut.category/basics true)
-   (shortcut-table :shortcut.category/navigating true)
-   (shortcut-table :shortcut.category/block-editing true)
-   (shortcut-table :shortcut.category/block-command-editing true)
-   (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))
-   (shortcut-table :shortcut.category/plugins true)
-   (shortcut-table :shortcut.category/others true)])
-
-(rum/defc shortcut-page
-  [{:keys [show-title?]
-    :or {show-title? true}}]
-  [:div.cp__shortcut-page
-   (when show-title? [:h1.title (t :help/shortcut-page-title)])
-   (trigger-table)
-   (markdown-and-orgmode-syntax)
-   (keymap-tables)])
+  (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?)
+                                      (or (= user-binding [])
+                                          (and (= binding [])
+                                               (nil? 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 (and id (not disabled?))
+                                   #(open-customize-shortcut-dialog! id))}
+
+                      (cond
+                        (or unset? 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?)
+                        [:code.flex.items-center.bg-transparent
+                         (shui/shortcut-v1 (string/join " | " (map #(dh/binding-for-display id %) binding))
+                                           (make-shui-context)
+                                           {:size :md})])]]))))])])]]))

+ 9 - 10
src/main/frontend/components/shortcut.css

@@ -163,16 +163,6 @@
         @apply rounded-[3px];
       }
     }
-
-    .reset-btn {
-      @apply ml-4 opacity-50 cursor-default;
-    }
-
-    &.dirty {
-      .reset-btn {
-        @apply opacity-100 cursor-pointer;
-      }
-    }
   }
 }
 
@@ -191,3 +181,12 @@
     }
   }
 }
+
+.sidebar-item .cp__shortcut-page-x {
+    padding: 12px 0 0 0;
+    background-color: var(--color-level-2);
+}
+
+.sidebar-item article {
+    max-height: unset;
+}

+ 0 - 480
src/main/frontend/components/shortcut2.cljs

@@ -1,480 +0,0 @@
-(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
-              :when (string? x)]
-          [: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?)
-                                      (or (= user-binding [])
-                                          (and (= binding [])
-                                               (nil? 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 (and id (not disabled?))
-                                   #(open-customize-shortcut-dialog! id))}
-
-                      (cond
-                        (or unset? 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)]))
-                      ]]))))])])]]))

+ 104 - 0
src/main/frontend/components/shortcut_help.cljs

@@ -0,0 +1,104 @@
+(ns frontend.components.shortcut-help
+  "Shortcut help"
+  (:require [frontend.context.i18n :refer [t]]
+            [frontend.state :as state]
+            [frontend.ui :as ui]
+            [frontend.extensions.latex :as latex]
+            [frontend.extensions.highlight :as highlight]
+            [logseq.graph-parser.util.block-ref :as block-ref]
+            [logseq.graph-parser.util.page-ref :as page-ref]
+            [rum.core :as rum]
+            [frontend.components.shortcut :as shortcut]))
+
+(rum/defc trigger-table []
+  [:table
+   [:thead
+    [:tr
+     [:th.text-left [:b (t :help/shortcuts-triggers)]]
+     [:th.text-right [:b (t :help/shortcut)]]]]
+   [:tbody
+    [:tr
+     [:td.text-left (t :help/slash-autocomplete)]
+     [:td.text-right [:code "/"]]]
+    [:tr
+     [:td.text-left (t :help/block-content-autocomplete)]
+     [:td.text-right [:code "<"]]]
+    [:tr
+     [:td.text-left (t :help/reference-autocomplete)]
+     [:td.text-right [:code page-ref/left-and-right-brackets]]]
+    [:tr
+     [:td.text-left (t :help/block-reference)]
+     [:td.text-right [:code block-ref/left-and-right-parens]]]
+    [:tr
+     [:td.text-left (t :help/open-link-in-sidebar)]
+     [:td.text-right [:code "Shift Click"]]]
+    [:tr
+     [:td.text-left (t :help/context-menu)]
+     [:td.text-right [:code "Right Click"]]]]])
+
+(defn markdown-and-orgmode-syntax []
+  (let [list [:bold :italics :del :mark :latex :code :link :pre :img]
+
+        preferred-format (state/get-preferred-format) ; markdown/org
+
+        title (case preferred-format
+                :markdown (t :help/markdown-syntax)
+                :org (t :help/org-mode-syntax))
+
+        learn-more (case preferred-format
+                     :markdown "https://www.markdownguide.org/basic-syntax"
+                     :org "https://orgmode.org/worg/dev/org-syntax.html")
+
+        raw (case preferred-format
+              :markdown {:bold (str "**" (t :bold) "**")
+                         :italics (str "_" (t :italics) "_")
+                         :link "[Link](https://www.example.com)"
+                         :del (str "~~" (t :strikethrough) "~~")
+                         :mark (str "^^" (t :highlight) "^^")
+                         :latex "$$E = mc^2$$"
+                         :code (str "`" (t :code) "`")
+                         :pre "```clojure\n  (println \"Hello world!\")\n```"
+                         :img "![image](https://asset.logseq.com/static/img/logo.png)"}
+              :org {:bold (str "*" (t :bold) "*")
+                    :italics (str "/" (t :italics) "/")
+                    :del (str "+" (t :strikethrough) "+")
+                    :pre [:pre "#+BEGIN_SRC clojure\n  (println \"Hello world!\")\n#+END_SRC"]
+                    :link "[[https://www.example.com][Link]]"
+                    :mark (str "^^" (t :highlight) "^^")
+                    :latex "$$E = mc^2$$"
+                    :code "~Code~"
+                    :img "[[https://asset.logseq.com/static/img/logo.png][image]]"})
+
+        rendered {:italics [:i (t :italics)]
+                  :bold [:b (t :bold)]
+                  :link [:a {:href "https://www.example.com"} "Link"]
+                  :del [:del (t :strikethrough)]
+                  :mark [:mark (t :highlight)]
+                  :latex (latex/latex "help-latex" "E = mc^2" true false)
+                  :code [:code (t :code)]
+                  :pre (highlight/highlight "help-highlight" {:data-lang "clojure"} "(println \"Hello world!\")")
+                  :img [:img {:style {:float "right" :width 32 :height 32}
+                              :src "https://asset.logseq.com/static/img/logo.png"
+                              :alt "image"}]}]
+
+    [:table
+     [:thead
+      [:tr
+       [:th.text-left [:b title]]
+       [:th.text-right [:a {:href learn-more} "Learn more →"]]]]
+     [:tbody
+      (map (fn [name]
+             [:tr
+              [:td.text-left [(if (= :pre name) :pre :code) (get raw name)]]
+              [:td.text-right (get rendered name)]])
+        list)]]))
+
+
+(rum/defc shortcut-page
+  [{:keys [show-title?]
+    :or {show-title? true}}]
+  [:div.cp__shortcut-page
+   (when show-title? [:h1.title (t :help/shortcut-page-title)])
+   (trigger-table)
+   (markdown-and-orgmode-syntax)
+   (shortcut/shortcut-keymap-x)])

+ 4 - 0
src/main/frontend/extensions/code.css

@@ -69,3 +69,7 @@
     cursor: pointer;
   }
 }
+
+.cm-s-solarized.CodeMirror {
+    box-shadow: none;
+}

+ 5 - 5
src/main/frontend/modules/shortcut/data_helper.cljs

@@ -151,10 +151,10 @@
   (let [tmp (cond
               (false? binding)
               (cond
-                (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/backward-kill-word)) "system default: opt+delete"
+                (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/backward-kill-word)) "system default: opt delete"
                 :else (t :keymap/disabled))
 
               (string? binding)
@@ -244,7 +244,7 @@
                               (mapv into-conflict-refs)
                               (remove #(empty? (second (second %1))))
                               (into {}))]))))
-                     
+
           (remove #(empty? (vals (second %1))))
           (into {})))))
 

+ 1 - 9
src/main/frontend/ui.cljs

@@ -178,15 +178,7 @@
                        (string/split  #" "))
                    sequence)]
     [:span.keyboard-shortcut
-     (map-indexed (fn [i 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.
-                       (if (= "meta" key')
-                        (util/meta-key-name)
-                        key')]))
-                  sequence)]))
+     (shui/shortcut-v1 sequence (make-shui-context))]))
 
 (rum/defc menu-link
   [{:keys [only-child? no-padding? class shortcut] :as options} child]