mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Add global keyboard EventRouter and unify publish (M6, §01D)
One module owns the document keydown listener and dispatches the shortcut map (Cmd/Ctrl+Shift+N / +K / +S / +, / Escape), platform-aware, via the single-source focus-utils interactive-context gate. Escape and Cmd/Ctrl+S run before the gate so save works while editing; the rest are suppressed mid-typing. Publish + its toast move into services/snippet-actions so the button and Cmd/Ctrl+S behave identically. Removes the ad-hoc keydown handler from App and Monaco's Cmd+S command.
This commit is contained in:
@@ -320,7 +320,17 @@ function onKeyDown(e: KeyboardEvent): void {
|
||||
|
||||
const mod = isMac ? e.metaKey : e.ctrlKey;
|
||||
|
||||
// --- Shortcuts: never fire while typing in an input or Monaco ----------
|
||||
// Cmd/Ctrl + S -> publish current draft. Checked BEFORE the interactive-context
|
||||
// gate: it is the canonical "save" shortcut (spec §01D mandates it override the
|
||||
// browser default), and publishing happens *while editing the draft in Monaco* —
|
||||
// gating it behind "not typing" would defeat its purpose.
|
||||
if (mod && !e.shiftKey && e.key.toLowerCase() === 's') {
|
||||
e.preventDefault(); // override the browser "save page" dialog
|
||||
useSnippetStore.getState().publishDraft();
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Remaining shortcuts: never fire while typing in an input or Monaco ----
|
||||
if (isInInteractiveContext()) return;
|
||||
|
||||
// Cmd/Ctrl + Shift + N -> new snippet
|
||||
@@ -336,16 +346,12 @@ function onKeyDown(e: KeyboardEvent): void {
|
||||
toggleDatasets();
|
||||
return;
|
||||
}
|
||||
// Cmd/Ctrl + S -> publish current draft
|
||||
if (mod && e.key.toLowerCase() === 's') {
|
||||
e.preventDefault(); // override the browser "save page" dialog
|
||||
useSnippetStore.getState().publishDraft();
|
||||
return;
|
||||
}
|
||||
// Cmd/Ctrl + , -> settings (through the coordinator: snapshot + URL sync)
|
||||
// Cmd/Ctrl + , -> open the editor settings popover. Settings are distributed to
|
||||
// per-pane disclosure popovers, not a modal (spec §07), so this opens the editor
|
||||
// cluster (openSettingsPopover) — there is no 'settings' modal to open.
|
||||
if (mod && e.key === ',') {
|
||||
e.preventDefault();
|
||||
openModal('settings');
|
||||
openSettingsPopover('editor-settings');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -392,9 +398,13 @@ by inserting a rung at the right priority — never by sprinkling
|
||||
`e.preventDefault()` so Cmd/Ctrl+S does not trigger "save page", Cmd/Ctrl+K
|
||||
does not focus the browser search bar, etc.
|
||||
|
||||
Note the asymmetry: **Escape is checked before the interactive-context gate**
|
||||
(you want Escape to dismiss a modal even while focus is in the editor), whereas
|
||||
all other shortcuts are checked **after** the gate (so they don't fire mid-typing).
|
||||
Note the asymmetry: **Escape and Cmd/Ctrl+S are checked before the
|
||||
interactive-context gate.** Escape, so it dismisses a modal even while focus is in
|
||||
the editor; Cmd/Ctrl+S, because publishing the draft is something you do _while_
|
||||
editing it — gating "save" behind "not typing" would defeat it (and it must
|
||||
override the browser's "save page" regardless). The remaining shortcuts (new
|
||||
snippet, toggle Datasets, settings) are checked **after** the gate, so they never
|
||||
fire mid-typing or steal a key the editor wants (e.g. Monaco's own Cmd+K chord).
|
||||
|
||||
### 2.2 The single-source helper: `orchestration/focus-utils.ts`
|
||||
|
||||
|
||||
@@ -65,6 +65,13 @@ nature of the message, not by convenience.
|
||||
_inline on the control_ ("Copied") with a polite `aria-live` announcement for assistive
|
||||
tech, never a toast-per-copy. This refines the spec's earlier blanket "every action
|
||||
toasts" (spec §01F/§02/§05, reconciled).
|
||||
- **An action's confirmation belongs with the action, not its call site.** When an action is
|
||||
reachable from more than one trigger (e.g. publish is both a toolbar button _and_
|
||||
Cmd/Ctrl+S), pair the store mutation and its toast in **one `services/` helper**
|
||||
(`publishActiveSnippet`) that every trigger calls — otherwise the toast rides one path and
|
||||
the other publishes silently (the exact inconsistency this rule prevents). The shortcut is
|
||||
owned globally by the EventRouter (arch 04), so the helper is the only place the outcome is
|
||||
confirmed.
|
||||
- **The same failure can light up two channels.** An unrenderable spec shows the _same_
|
||||
message inline in both the editor (§03E) and the preview (§04) — one producer
|
||||
(`PreviewStore`), two subscribers. That's intentional, not duplication.
|
||||
|
||||
@@ -67,6 +67,10 @@ Notes:
|
||||
|
||||
- Cmd/Ctrl + K is a toggle: if the Datasets manager is already open it closes it; otherwise it opens it.
|
||||
- Escape only acts when a modal is open; with no modal open it does nothing.
|
||||
- Cmd/Ctrl + S publishes the active draft regardless of where focus is — **including
|
||||
while the editor has focus** — so it behaves as a "save" you reach for mid-edit. The
|
||||
other shortcuts (new snippet, toggle Datasets, settings) are suppressed while the
|
||||
user is typing in the editor or an input, so they don't interrupt text entry.
|
||||
- The shortcut actions override the browser's default behavior for those key combinations.
|
||||
|
||||
## E. Navigation & Shareable URL State
|
||||
|
||||
Reference in New Issue
Block a user