mirror of
https://github.com/logseq/logseq.git
synced 2026-02-01 22:47:36 +00:00
improve animations
This commit is contained in:
136
deps/shui/src/logseq/shui/shortcut/v2.cljs
vendored
136
deps/shui/src/logseq/shui/shortcut/v2.cljs
vendored
@@ -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)])))))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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})])]]))))])])]]))
|
||||
|
||||
@@ -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))))
|
||||
|
||||
@@ -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)]))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user