From 9df5f8b0aa06e13b48fcca586f2f332e5a660a82 Mon Sep 17 00:00:00 2001 From: scheinriese Date: Fri, 28 Nov 2025 04:07:26 +0100 Subject: [PATCH] improve animations --- deps/shui/src/logseq/shui/shortcut/v2.cljs | 136 ++++++++++++------- src/main/frontend/components/cmdk/core.cljs | 3 + src/main/frontend/components/settings.cljs | 8 +- src/main/frontend/components/shortcut.cljs | 4 +- src/main/frontend/modules/shortcut/core.cljs | 15 +- src/main/frontend/ui.cljs | 17 ++- 6 files changed, 127 insertions(+), 56 deletions(-) diff --git a/deps/shui/src/logseq/shui/shortcut/v2.cljs b/deps/shui/src/logseq/shui/shortcut/v2.cljs index 89453f44fc..12fe13b412 100644 --- a/deps/shui/src/logseq/shui/shortcut/v2.cljs +++ b/deps/shui/src/logseq/shui/shortcut/v2.cljs @@ -81,32 +81,42 @@ (defn- normalize-binding "Normalizes a shortcut binding to a string format for data attributes. - Examples: 'cmd+k', 'shift+cmd+k', 'cmd up'" + Examples: 'cmd+k', 'shift+cmd+k', 'cmd up' + Handles 'mod' -> 'meta' on Mac, 'ctrl' on Windows/Linux for consistency with shortcut system." [binding] - (cond - (string? binding) - (string/lower-case (string/trim binding)) - - (coll? binding) - (let [first-item (first binding) - keys (flatten-keys binding) - normalize-key (fn [k] - (cond - (string? k) (string/lower-case k) - (keyword? k) (name k) - (symbol? k) (name k) - (number? k) (str k) - :else (str k)))] - (string/join "+" (map normalize-key keys))) - - (keyword? binding) - (name binding) - - (symbol? binding) - (name binding) - - :else - (str binding))) + (let [normalize-key (fn [k] + (cond + (string? k) (let [lowered (string/lower-case k)] + ;; Convert 'mod' to 'meta' on Mac, 'ctrl' on Windows/Linux + ;; to match what the shortcut system uses + (if (= lowered "mod") + (if mac? "meta" "ctrl") + lowered)) + (keyword? k) (name k) + (symbol? k) (name k) + (number? k) (str k) + :else (str k)))] + (cond + (string? binding) + (let [trimmed (string/trim binding) + normalized (string/lower-case trimmed)] + ;; Handle 'mod' in strings like "mod+k" + (if (string/includes? normalized "mod") + (string/replace normalized "mod" (if mac? "meta" "ctrl")) + normalized)) + + (coll? binding) + (let [keys (flatten-keys binding)] + (string/join "+" (map normalize-key keys))) + + (keyword? binding) + (name binding) + + (symbol? binding) + (name binding) + + :else + (str binding)))) (defn- detect-style "Automatically detects style from shortcut format. @@ -140,7 +150,7 @@ (defn shortcut-press! "Central helper to trigger key press animation. - Finds all nodes with matching data-shortcut-binding and toggles pressed class. + Finds all nodes with matching data-shortcut-binding and animates individual keys. Optionally highlights parent row. Args: @@ -150,24 +160,46 @@ ([binding highlight-row?] (let [normalized (normalize-binding binding) selector (str "[data-shortcut-binding=\"" normalized "\"]") - elements (.querySelectorAll js/document selector)] - (doseq [^js el (array-seq elements)] - (.add (.-classList el) "shui-shortcut-key-pressed") - (when highlight-row? - (let [^js row (or (.closest el ".shui-shortcut-row") - (.-parentElement el))] - (when row - (.add (.-classList row) "shui-shortcut-row--pressed")))) - ;; Auto-reset after animation duration - (js/setTimeout - (fn [] - (.remove (.-classList el) "shui-shortcut-key-pressed") - (when highlight-row? - (let [^js row (or (.closest el ".shui-shortcut-row") - (.-parentElement el))] - (when row - (.remove (.-classList row) "shui-shortcut-row--pressed"))))) - 160))))) + containers (.querySelectorAll js/document selector)] + (doseq [^js container (array-seq containers)] + ;; Find all individual keys within this container + (let [keys (.querySelectorAll container "kbd.shui-shortcut-key")] + (if (> (.-length keys) 0) + ;; Animate individual keys + (doseq [^js key-el (array-seq keys)] + (.add (.-classList key-el) "shui-shortcut-key-pressed") + (when highlight-row? + (let [^js row (or (.closest container ".shui-shortcut-row") + (.-parentElement container))] + (when row + (.add (.-classList row) "shui-shortcut-row--pressed")))) + ;; Auto-reset after animation duration + (js/setTimeout + (fn [] + (.remove (.-classList key-el) "shui-shortcut-key-pressed") + (when highlight-row? + (let [^js row (or (.closest container ".shui-shortcut-row") + (.-parentElement container))] + (when row + (.remove (.-classList row) "shui-shortcut-row--pressed"))))) + 160)) + ;; Fallback: animate container if no keys found + (do + (.add (.-classList container) "shui-shortcut-key-pressed") + (when highlight-row? + (let [^js row (or (.closest container ".shui-shortcut-row") + (.-parentElement container))] + (when row + (.add (.-classList row) "shui-shortcut-row--pressed")))) + (js/setTimeout + (fn [] + (.remove (.-classList container) "shui-shortcut-key-pressed") + (when highlight-row? + (let [^js row (or (.closest container ".shui-shortcut-row") + (.-parentElement container))] + (when row + (.remove (.-classList row) "shui-shortcut-row--pressed"))))) + 160)))))))) (rum/defc combo-keys "Renders combo keys (simultaneous key combinations) with separator." @@ -252,8 +284,10 @@ - :aria-label - accessibility label for container - :aria-hidden? - if true, hides from screen readers (default: false for decorative hints) - :animate-on-press? - if true, enables press animation (default: true) - - :glow? - if true, adds inner glow effect to combo/separate keys (default: true)" - [shortcut & {:keys [style size theme interactive? aria-label aria-hidden? animate-on-press? glow?] + - :glow? - if true, adds inner glow effect to combo/separate keys (default: true) + - :raw-binding - raw binding format for data-shortcut-binding (for animation matching). + If not provided, will normalize from shortcut prop." + [shortcut & {:keys [style size theme interactive? aria-label aria-hidden? animate-on-press? glow? raw-binding] :or {style :auto size :xs interactive? false @@ -293,6 +327,14 @@ :else [(str binding)]) + ;; Use raw-binding if provided, otherwise normalize from binding + binding-for-data (if raw-binding + (if (coll? raw-binding) + (if (= (count raw-binding) 1) + (first raw-binding) + raw-binding) + raw-binding) + binding) render-fn (case detected-style :combo combo-keys :separate separate-keys @@ -307,5 +349,5 @@ (when (< 0 index) [:span.text-gray-11.text-sm {:key (str "sep-" index) :style {:margin "0 4px"}} "|"]) - (render-fn keys binding opts)]))))) + (render-fn keys binding-for-data opts)]))))) diff --git a/src/main/frontend/components/cmdk/core.cljs b/src/main/frontend/components/cmdk/core.cljs index cda90078c1..6c53a90942 100644 --- a/src/main/frontend/components/cmdk/core.cljs +++ b/src/main/frontend/components/cmdk/core.cljs @@ -826,6 +826,9 @@ (util/stop-propagation e)) (and meta? (= keyname "o")) (open-current-item-link state) + (= keyname "/") (do + (shui/shortcut-press! "/" true) + nil) ; Don't prevent default, allow typing :else nil))) (defn- keyup-handler diff --git a/src/main/frontend/components/settings.cljs b/src/main/frontend/components/settings.cljs index bee8357663..d1b4cc3f4f 100644 --- a/src/main/frontend/components/settings.cljs +++ b/src/main/frontend/components/settings.cljs @@ -219,7 +219,7 @@ true)]] (when (not (or (util/mobile?) (mobile-util/native-platform?))) [:div {:style {:text-align "right"}} - (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-brackets))])]) + (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-brackets) :shortcut-id :ui/toggle-brackets)])]) (defn toggle-wide-mode-row [t wide-mode?] [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center @@ -233,7 +233,7 @@ true)]] (when (not (or (util/mobile?) (mobile-util/native-platform?))) [:div {:style {:text-align "right"}} - (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-wide-mode))])]) + (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-wide-mode) :shortcut-id :ui/toggle-wide-mode)])]) (defn editor-font-family-row [t font] [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4 @@ -370,7 +370,7 @@ (row-with-button-action {:left-label (t :right-side-bar/switch-theme (string/capitalize switch-theme)) :-for "toggle_theme" :action pick-theme - :desc (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-theme))}))) + :desc (ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq :ui/toggle-theme) :shortcut-id :ui/toggle-theme)}))) (rum/defc accent-color-row < rum/reactive [_in-modal?] @@ -426,7 +426,7 @@ (accent-color-row true)]) (defn date-format-row [t preferred-date-format] - [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center + [:div.it.sm:grid.sm:grid-cols-3.sm:gap-4.sm:items-center [:label.block.text-sm.font-medium.leading-5.opacity-70 {:for "custom_date_format"} (t :settings-page/custom-date-format) diff --git a/src/main/frontend/components/shortcut.cljs b/src/main/frontend/components/shortcut.cljs index 15586af722..750ad8d812 100644 --- a/src/main/frontend/components/shortcut.cljs +++ b/src/main/frontend/components/shortcut.cljs @@ -482,4 +482,6 @@ :white-space "nowrap"}} (shui/shortcut (string/join " | " (map #(dh/binding-for-display id %) binding)) - {:size :md :interactive? true})])]]))))])])]])) + {:size :md + :interactive? true + :raw-binding binding})])]]))))])])]])) diff --git a/src/main/frontend/modules/shortcut/core.cljs b/src/main/frontend/modules/shortcut/core.cljs index dc8f9cafcf..7872668086 100644 --- a/src/main/frontend/modules/shortcut/core.cljs +++ b/src/main/frontend/modules/shortcut/core.cljs @@ -12,7 +12,8 @@ [frontend.util :as util] [goog.events :as events] [goog.ui.KeyboardShortcutHandler.EventType :as EventType] - [lambdaisland.glogi :as log]) + [lambdaisland.glogi :as log] + [logseq.shui.ui :as shui]) (:import [goog.events KeyCodes KeyNames] [goog.ui KeyboardShortcutHandler])) @@ -135,8 +136,18 @@ (let [f (fn [e] (let [id (keyword (.-identifier e)) shortcut-map (dh/shortcuts-map-by-handler-id handler-id state) ;; required to get shortcut map dynamically - dispatch-fn (get shortcut-map id)] + dispatch-fn (get shortcut-map id) + binding (dh/shortcut-binding id)] (state/set-state! :editor/latest-shortcut id) + ;; Trigger animation for visible shortcuts + (when binding + (let [bindings (if (coll? binding) binding [binding])] + (doseq [b bindings] + (when b + ;; Use the raw binding, not decorated, for matching data attributes + (try + (shui/shortcut-press! b true) + (catch :default _e nil)))))) ;; trigger fn (when dispatch-fn (plugin-handler/hook-lifecycle-fn! id dispatch-fn e)))) diff --git a/src/main/frontend/ui.cljs b/src/main/frontend/ui.cljs index aeeb6a079e..87cef4ba23 100644 --- a/src/main/frontend/ui.cljs +++ b/src/main/frontend/ui.cljs @@ -21,6 +21,7 @@ [frontend.mobile.util :as mobile-util] [frontend.modules.shortcut.config :as shortcut-config] [frontend.modules.shortcut.core :as shortcut] + [frontend.modules.shortcut.data-helper :as shortcut-dh] [frontend.modules.shortcut.utils :as shortcut-utils] [frontend.rum :as r] [frontend.state :as state] @@ -201,15 +202,27 @@ (dropdown-content-wrapper dropdown-state close-fn modal-content modal-class {:z-index z-index}))))])) ;; `sequence` can be a list of symbols, a list of strings, or a string -(defn render-keyboard-shortcut [sequence & {:as opts}] +;; If `shortcut-id` is provided, uses raw binding from shortcut system for data attribute matching +(defn render-keyboard-shortcut [sequence & {:keys [shortcut-id] :as opts}] (let [sequence (if (string? sequence) (-> sequence ;; turn string into sequence (string/trim) (string/lower-case) (string/split #" ")) sequence) + ;; Get raw binding for data attribute matching + raw-binding (if shortcut-id + ;; Get raw binding from shortcut system + (shortcut-dh/shortcut-binding shortcut-id) + ;; Otherwise use sequence as-is (it's already the raw format) + (if (and (coll? sequence) (every? string? sequence)) + sequence + (if (string? sequence) + [sequence] + sequence))) opts (merge {:interactive? false - :aria-hidden? true} opts)] + :aria-hidden? true + :raw-binding raw-binding} opts)] [:span.keyboard-shortcut (shui/shortcut sequence opts)]))