mirror of
https://github.com/logseq/logseq.git
synced 2026-05-20 19:02:23 +00:00
Fix chord sequence display, add polish and resilience improvements
- Add chord-sequence-keys component to shui shortcut for multi-step bindings (e.g. ⌘C then ⌘R) that were previously truncated to just the first key. Detects nested binding groups and renders each combo with a "then" separator and proper glow styling. - Update normalize-binding to handle chord sequences by joining groups with spaces instead of flattening into a single combo string. - Cap keystroke recording at 5 keys to prevent app crashes from excessively long bindings being persisted to localStorage. - Add try/catch around localStorage shortcut reads for resilience against corrupt data on startup. - Fix vertical centering of shortcut badges using display:contents wrappers and removing padding-top from label-wrap/action-wrap. - Style Reset button with accent color and underline-on-hover. - Add white-space:nowrap to button reset block to prevent text wrapping. - Use gap instead of margin for shortcut-input-binding spacing. - Minor CSS refinements: search input padding, keystroke border color, empty state sizing, toolbar action specificity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
59
deps/shui/src/logseq/shui/shortcut.cljs
vendored
59
deps/shui/src/logseq/shui/shortcut.cljs
vendored
@@ -107,8 +107,14 @@
|
||||
normalized))
|
||||
|
||||
(coll? binding)
|
||||
(let [keys (flatten-keys binding)]
|
||||
(string/join "+" (map normalize-key keys)))
|
||||
(if (and (coll? (first binding)) (> (count binding) 1) (every? coll? binding))
|
||||
;; Chord sequence: normalize each group separately, join with space
|
||||
(string/join " " (map (fn [group]
|
||||
(string/join "+" (map normalize-key (flatten-keys group))))
|
||||
binding))
|
||||
;; Single combo group
|
||||
(let [keys (flatten-keys binding)]
|
||||
(string/join "+" (map normalize-key keys))))
|
||||
|
||||
(keyword? binding)
|
||||
(name binding)
|
||||
@@ -261,6 +267,44 @@
|
||||
:margin-right "2px"}}
|
||||
key-text])]))
|
||||
|
||||
(rum/defc chord-sequence-keys
|
||||
"Renders a chord sequence (multi-step key combinations) with 'then' separators.
|
||||
E.g., [['⌘' 'c'] ['⌘' 'r']] renders as: [⌘ C] then [⌘ R]"
|
||||
[groups binding {:keys [aria-label aria-hidden? glow?]}]
|
||||
(let [normalized-binding (normalize-binding binding)
|
||||
container-attrs (cond-> {:class "shui-shortcut-chord"
|
||||
:data-shortcut-binding normalized-binding
|
||||
:style {:white-space "nowrap"
|
||||
:display "inline-flex"
|
||||
:align-items "center"
|
||||
:gap "8px"}}
|
||||
aria-label (assoc :aria-label aria-label)
|
||||
aria-hidden? (assoc :aria-hidden "true"))]
|
||||
[:div container-attrs
|
||||
(for [[gi group] (map-indexed vector groups)]
|
||||
(list
|
||||
(when (> gi 0)
|
||||
[:span.shui-shortcut-chord-sep
|
||||
{:key (str "chord-sep-" gi)
|
||||
:style {:font-size "10px"
|
||||
:opacity 0.45}}
|
||||
"then"])
|
||||
(let [key-elements (map print-shortcut-key group)]
|
||||
[:span
|
||||
{:key (str "chord-group-" gi)
|
||||
:class (str "shui-shortcut-combo" (when glow? " shui-shortcut-glow"))
|
||||
:style {:display "inline-flex"
|
||||
:align-items "center"
|
||||
:white-space "nowrap"}}
|
||||
(for [[ki key-text] (map-indexed vector key-elements)]
|
||||
(list
|
||||
(when (< 0 ki)
|
||||
[:span.shui-shortcut-separator {:key (str "gsep-" gi "-" ki)}])
|
||||
[:kbd.shui-shortcut-key
|
||||
{:key (str "chord-key-" gi "-" ki)
|
||||
:aria-hidden (if aria-label "true" "false")}
|
||||
key-text]))])))]))
|
||||
|
||||
(rum/defc root
|
||||
"Main shortcut component with automatic style detection.
|
||||
|
||||
@@ -287,7 +331,11 @@
|
||||
:aria-hidden? aria-hidden?
|
||||
:glow? glow?}]
|
||||
(for [[index binding] (map-indexed vector shortcuts)]
|
||||
(let [detected-style (if (= style :auto)
|
||||
(let [;; Chord sequence: multiple nested groups like [["⌘" "c"] ["⌘" "r"]]
|
||||
chord-sequence? (and (coll? binding)
|
||||
(> (count binding) 1)
|
||||
(every? coll? binding))
|
||||
detected-style (if (= style :auto)
|
||||
(detect-style binding)
|
||||
style)
|
||||
keys (cond
|
||||
@@ -330,10 +378,11 @@
|
||||
{:key (str "shortcut-" index)
|
||||
:style {:display "inline-flex"
|
||||
:align-items "center"
|
||||
:min-height "20px"
|
||||
:white-space "nowrap"}}
|
||||
(when (< 0 index)
|
||||
[:span.text-gray-11.text-sm {:key (str "sep-" index)
|
||||
:style {:margin "0 4px"}} "|"])
|
||||
(render-fn keys binding-for-data opts)])))))
|
||||
(if chord-sequence?
|
||||
(chord-sequence-keys binding binding-for-data opts)
|
||||
(render-fn keys binding-for-data opts))])))))
|
||||
|
||||
|
||||
@@ -617,11 +617,15 @@
|
||||
(set-rec-state! :recording)
|
||||
(set-keystroke! (util/trim-safe kn)))
|
||||
|
||||
;; Recording + key => accumulate
|
||||
;; Recording + key => accumulate (max 5 keys)
|
||||
(= state :recording)
|
||||
(when-let [kn (shortcut/keyname e)]
|
||||
(set-key-conflicts! nil)
|
||||
(set-keystroke! #(util/trim-safe (str % kn))))))))
|
||||
(let [cur (rum/deref *keystroke-ref)
|
||||
parts (string/split (string/trim cur) #" ")
|
||||
at-limit? (and (seq (first parts)) (>= (count parts) 5))]
|
||||
(when-not at-limit?
|
||||
(set-key-conflicts! nil)
|
||||
(set-keystroke! #(util/trim-safe (str % kn))))))))))
|
||||
|
||||
(js/setTimeout #(.focus el) 128)
|
||||
|
||||
@@ -749,7 +753,7 @@
|
||||
;; Reset (only when changed from default)
|
||||
(when (and (#{:idle :accepted :removed} rec-state)
|
||||
(not= current-binding binding))
|
||||
[:button.shortcut-toolbar-action
|
||||
[:button.shortcut-toolbar-action.shortcut-toolbar-reset
|
||||
{:on-click reset-fn!}
|
||||
(ui/icon "rotate" {:size 12})
|
||||
[:span "Reset"]])]
|
||||
@@ -908,8 +912,8 @@
|
||||
|
||||
(when (and ready? no-results?)
|
||||
[:div.shortcut-empty-state
|
||||
(ui/icon "search" {:size 24})
|
||||
[:span "No matching shortcuts"]])
|
||||
(ui/icon "list-search" {:size 24})
|
||||
[:span.text-sm "No matching shortcuts"]])
|
||||
|
||||
(when (and ready? (not no-results?))
|
||||
[:ul.list-none.m-0.py-3
|
||||
@@ -983,12 +987,12 @@
|
||||
[:span.shortcut-status-label (t :keymap/disabled)]
|
||||
(for [b user-binding
|
||||
:when (string? b)]
|
||||
[:span {:key b}
|
||||
[:span {:key b :style {:display "contents"}}
|
||||
(shui/shortcut b)]))]
|
||||
|
||||
:else
|
||||
(for [b binding
|
||||
:when (string? b)]
|
||||
[:span {:key b}
|
||||
[:span {:key b :style {:display "contents"}}
|
||||
(shui/shortcut (dh/binding-for-display id b)
|
||||
{:raw-binding [b]})]))]])))))])])]]))
|
||||
|
||||
@@ -28,6 +28,7 @@ button.shortcut-feedback-action {
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cp__shortcut-page-x {
|
||||
@@ -50,7 +51,7 @@ button.shortcut-feedback-action {
|
||||
}
|
||||
|
||||
input.form-input {
|
||||
@apply w-full pl-7 text-sm mt-0;
|
||||
@apply w-full pl-7 pr-7 text-sm mt-0;
|
||||
border-radius: 6px;
|
||||
|
||||
&:focus {
|
||||
@@ -59,7 +60,7 @@ button.shortcut-feedback-action {
|
||||
}
|
||||
}
|
||||
|
||||
a.x {
|
||||
.x {
|
||||
@apply flex items-center absolute right-1 px-1 cursor-pointer;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
@@ -130,7 +131,7 @@ button.shortcut-feedback-action {
|
||||
padding: 0 10px;
|
||||
min-width: 140px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--lx-gray-06, var(--ls-quaternary-background-color, var(--rx-gray-06)));
|
||||
border: 1px solid var(--lx-gray-07, var(--ls-quaternary-background-color, var(--rx-gray-07)));
|
||||
background-color: var(--lx-gray-02, var(--ls-secondary-background-color, var(--rx-gray-02)));
|
||||
color: var(--lx-gray-12, var(--rx-gray-12));
|
||||
transition: background-color 150ms ease;
|
||||
@@ -183,7 +184,8 @@ button.shortcut-feedback-action {
|
||||
}
|
||||
|
||||
.shortcut-empty-state {
|
||||
@apply flex flex-col items-center justify-center gap-2 py-16 select-none;
|
||||
@apply flex flex-col items-center justify-center gap-2 select-none;
|
||||
min-height: calc(60dvh - var(--shortcut-header-h, 120px));
|
||||
color: var(--lx-gray-09, var(--rx-gray-09));
|
||||
}
|
||||
|
||||
@@ -225,7 +227,6 @@ button.shortcut-feedback-action {
|
||||
.label-wrap {
|
||||
@apply flex flex-1;
|
||||
min-width: 120px;
|
||||
padding-top: 1px;
|
||||
}
|
||||
|
||||
.action-wrap {
|
||||
@@ -234,7 +235,6 @@ button.shortcut-feedback-action {
|
||||
column-gap: 16px;
|
||||
row-gap: 6px;
|
||||
max-width: 55%;
|
||||
padding-top: 1px;
|
||||
|
||||
&.disabled {
|
||||
@apply opacity-60 cursor-default;
|
||||
@@ -333,11 +333,12 @@ button.shortcut-feedback-action {
|
||||
/* Each binding grouped in a subtle container */
|
||||
.shortcut-input-binding {
|
||||
@apply inline-flex items-center flex-wrap rounded-md p-1;
|
||||
gap: 4px;
|
||||
background-color: var(--lx-gray-04-alpha, var(--rx-gray-04-alpha));
|
||||
max-width: 100%;
|
||||
|
||||
.shortcut-binding-remove {
|
||||
@apply flex items-center ml-1 cursor-pointer select-none;
|
||||
@apply flex items-center cursor-pointer select-none;
|
||||
color: var(--lx-gray-10, var(--rx-gray-10));
|
||||
|
||||
&:hover {
|
||||
@@ -432,9 +433,19 @@ button.shortcut-feedback-action {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.shortcut-toolbar-action {
|
||||
@apply cursor-pointer flex items-center gap-1;
|
||||
&:hover { color: var(--lx-gray-12, var(--rx-gray-12)); }
|
||||
.shortcut-toolbar button.shortcut-toolbar-action {
|
||||
gap: 4px;
|
||||
&:hover {
|
||||
color: var(--lx-gray-12, var(--rx-gray-12));
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.shortcut-toolbar button.shortcut-toolbar-reset {
|
||||
color: var(--lx-accent-11, var(--ls-link-text-color, hsl(var(--primary) / 0.8)));
|
||||
&:hover {
|
||||
color: var(--lx-accent-12, var(--ls-link-text-hover-color, hsl(var(--primary))));
|
||||
}
|
||||
}
|
||||
|
||||
.shortcut-toolbar-hint {
|
||||
|
||||
@@ -475,7 +475,8 @@ should be done through this fn in order to get global config and config defaults
|
||||
"MMM do, yyyy"))
|
||||
|
||||
(defn custom-shortcuts []
|
||||
(merge (storage/get :ls-shortcuts)
|
||||
(merge (try (storage/get :ls-shortcuts)
|
||||
(catch :default _ nil))
|
||||
(:shortcuts (get-config))))
|
||||
|
||||
(defn get-commands
|
||||
|
||||
Reference in New Issue
Block a user