Skip to content

Commit 876d536

Browse files
committed
feat(mdviewer): forward unhandled keyboard shortcuts to Phoenix keybinding manager
Modifier shortcuts not handled by the md editor (bold, italic, undo, etc.) are forwarded to Phoenix via postMessage so keybindings like Ctrl+S, Ctrl+Shift+F, etc. work while editing markdown. Events dispatched on document.body where KeyBindingManager listens. Also adds to-create-tests.md with integration test checklist.
1 parent 7c0580c commit 876d536

3 files changed

Lines changed: 140 additions & 0 deletions

File tree

src-mdviewer/src/bridge.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ export function initBridge() {
150150

151151
// Intercept keyboard shortcuts in capture phase before the mdviewr editor handles them.
152152
// Undo/redo is routed through CM5's undo stack so both editors stay in sync.
153+
// Unhandled modifier shortcuts are forwarded to Phoenix's keybinding manager.
154+
const _mdEditorHandledKeys = new Set(["b", "i", "k", "u", "z", "y", "a", "c", "v", "x"]); // Ctrl/Cmd + key
155+
const _mdEditorHandledShiftKeys = new Set(["x", "X", "z"]); // Ctrl/Cmd + Shift + key
156+
153157
document.addEventListener("keydown", (e) => {
154158
if (e.key === "Escape") {
155159
sendToParent("embeddedEscapeKeyPressed", {});
@@ -170,6 +174,26 @@ export function initBridge() {
170174
e.preventDefault();
171175
e.stopImmediatePropagation();
172176
sendToParent("mdviewrRedo", {});
177+
return;
178+
}
179+
180+
// Forward unhandled modifier shortcuts to Phoenix keybinding manager
181+
if (getState().editMode) {
182+
const isHandled = e.shiftKey
183+
? _mdEditorHandledShiftKeys.has(e.key)
184+
: _mdEditorHandledKeys.has(e.key);
185+
if (!isHandled) {
186+
e.preventDefault();
187+
e.stopImmediatePropagation();
188+
sendToParent("mdviewrKeyboardShortcut", {
189+
key: e.key,
190+
code: e.code,
191+
ctrlKey: e.ctrlKey,
192+
metaKey: e.metaKey,
193+
shiftKey: e.shiftKey,
194+
altKey: e.altKey
195+
});
196+
}
173197
}
174198
}, true);
175199

src-mdviewer/to-create-tests.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Markdown Viewer/Editor — Integration Tests To Create
2+
3+
## Keyboard Shortcut Forwarding
4+
- [ ] Ctrl+S in edit mode triggers Phoenix save (not consumed by md editor)
5+
- [ ] Ctrl+P in edit mode opens Quick Open
6+
- [ ] Ctrl+Shift+F in edit mode opens Find in Files
7+
- [ ] Ctrl+B/I/U in edit mode applies bold/italic/underline (not forwarded)
8+
- [ ] Ctrl+K in edit mode opens link input (not forwarded)
9+
- [ ] Ctrl+Shift+X in edit mode applies strikethrough (not forwarded)
10+
- [ ] Ctrl+Z/Y in edit mode triggers undo/redo via CM (routed through Phoenix undo stack)
11+
- [ ] Ctrl+A/C/V/X in edit mode work natively (select all, copy, paste, cut)
12+
- [ ] Escape in edit mode sends focus back to Phoenix editor
13+
- [ ] F-key shortcuts (e.g. F8 for live preview toggle) work in edit mode
14+
15+
## Document Cache & File Switching
16+
- [ ] Switching between two MD files is instant (no re-render, DOM cached)
17+
- [ ] Scroll position preserved per-document on switch
18+
- [ ] Edit/reader mode preserved globally across file switches
19+
- [ ] Switching MD → HTML → MD reuses persistent md iframe (no reload)
20+
- [ ] Closing live preview panel and reopening preserves md iframe and cache
21+
- [ ] Project switch clears all cached documents
22+
- [ ] Working set changes sync to iframe (files removed from working set go to LRU)
23+
- [ ] LRU cache evicts beyond 20 non-working-set files
24+
- [ ] Reload button (in LP toolbar) re-renders current file, preserves scroll and edit mode
25+
26+
## Selection Sync (Bidirectional)
27+
- [ ] Selecting text in CM highlights corresponding block in md viewer
28+
- [ ] Selecting text in md viewer selects corresponding text in CM
29+
- [ ] Clicking in md viewer (no selection) clears CM selection
30+
- [ ] Clicking in CM clears md viewer highlight
31+
- [ ] Selection sync respects cursor sync toggle (disabled when sync off)
32+
33+
## Cursor/Scroll Sync
34+
- [ ] Clicking in CM scrolls md viewer to corresponding element
35+
- [ ] Clicking in md viewer scrolls CM to corresponding line (centered)
36+
- [ ] Cursor sync toggle button disables/enables bidirectional sync
37+
- [ ] Cursor sync toggle state preserved across toolbar re-renders (file switch, mode toggle)
38+
- [ ] Content sync still works when cursor sync is disabled
39+
40+
## Edit Mode & Entitlement Gating
41+
- [ ] Free user sees Edit button → clicking shows upsell dialog
42+
- [ ] Pro user sees Edit button → clicking enters edit mode
43+
- [ ] Entitlement change (free→pro) switches to edit mode automatically
44+
- [ ] Entitlement change (pro→free) switches to reader mode automatically
45+
- [ ] Edit/Reader toggle works correctly in the iframe toolbar
46+
47+
## Toolbar & UI
48+
- [ ] Phoenix play button and mode dropdown hidden for MD files
49+
- [ ] Phoenix play button and mode dropdown visible for HTML files
50+
- [ ] Phoenix play button and mode dropdown visible for custom server MD files
51+
- [ ] Progressive toolbar collapse: blocks collapse first, then lists, then text formatting
52+
- [ ] Toolbar collapse thresholds work at various widths
53+
- [ ] Dropdown menus in collapsed toolbar open and close correctly
54+
- [ ] Format buttons apply formatting and close dropdown
55+
- [ ] Tooltip delay is 800ms (not too aggressive)
56+
- [ ] Underline button has shortcut in tooltip (Ctrl+U / ⌘U)
57+
- [ ] Reader button has book-open icon and "Switch to reader mode" title
58+
- [ ] Edit button has pencil icon and "Switch to edit mode" title
59+
- [ ] 1px white line at bottom of preview not visible (box-sizing: border-box on toolbar)
60+
61+
## Checkbox (Task List) Sync
62+
- [ ] Clicking checkbox in edit mode toggles it and syncs to CM source ([x][ ])
63+
- [ ] Checkboxes enabled in edit mode, disabled in reader mode
64+
- [ ] Checkbox toggle creates an undo entry
65+
- [ ] Undo reverses checkbox toggle
66+
67+
## Format Bar & Link Popover
68+
- [ ] Format bar appears on text selection (single line, not wrapping)
69+
- [ ] Format bar includes underline button
70+
- [ ] Format bar dismissed on scroll
71+
- [ ] Link popover appears when clicking a link in edit mode
72+
- [ ] Link popover URL opens in default browser (not Electron window)
73+
- [ ] Link popover dismissed on scroll
74+
75+
## Scroll Behavior
76+
- [ ] Cursor sync scroll is instant (not smooth animated)
77+
- [ ] Scroll restore on file switch uses exact pixel position (no jump)
78+
- [ ] Scroll restore on reload uses source-line-based positioning
79+
- [ ] No progressive scroll-down on reload with many images (source-line approach)
80+
81+
## Border & Styling
82+
- [ ] Subtle bottom border on #mainNavBar (rgba(255,255,255,0.08))
83+
- [ ] Subtle bottom border on #live-preview-plugin-toolbar (rgba(255,255,255,0.08))
84+
- [ ] No medium-zoom magnifying glass cursor on images
85+
- [ ] Cursor sync icon is subtle (secondary text color, not accent blue)
86+
87+
## Translation (i18n)
88+
- [ ] en.json strings load correctly (toolbar.reader, format.underline, etc.)
89+
- [ ] Locale with region code (e.g. en-GB) falls back to base (en) if specific file missing
90+
- [ ] No "Failed to load locale" console warnings for valid locales
91+
- [ ] gulp translateStrings translates both Phoenix NLS and mdviewer locales
92+
- [ ] Translated locale files copied back to src-mdviewer/src/locales/

src/extensionsIntegrated/Phoenix-live-preview/MarkdownSync.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,9 @@ define(function (require, exports, module) {
114114
case "mdviewrCursorSyncToggle":
115115
_cursorSyncEnabled = !!data.enabled;
116116
break;
117+
case "mdviewrKeyboardShortcut":
118+
_forwardKeyboardShortcut(data);
119+
break;
117120
case "embeddedIframeFocusEditor":
118121
if (data.sourceLine != null) {
119122
_scrollCMToLine(data.sourceLine);
@@ -595,6 +598,27 @@ define(function (require, exports, module) {
595598
_syncingFromIframe = false;
596599
}
597600

601+
// --- Keyboard shortcut forwarding ---
602+
603+
/**
604+
* Forward an unhandled keyboard shortcut from the mdviewer iframe to Phoenix's
605+
* keybinding manager by dispatching a synthetic KeyboardEvent on the document.
606+
*/
607+
function _forwardKeyboardShortcut(data) {
608+
const event = new KeyboardEvent("keydown", {
609+
key: data.key,
610+
code: data.code,
611+
ctrlKey: data.ctrlKey,
612+
metaKey: data.metaKey,
613+
shiftKey: data.shiftKey,
614+
altKey: data.altKey,
615+
bubbles: true,
616+
cancelable: true
617+
});
618+
// KeyBindingManager listens on document.body, not document
619+
document.body.dispatchEvent(event);
620+
}
621+
598622
// --- Helpers ---
599623

600624
function _getIframeWindow() {

0 commit comments

Comments
 (0)