Skip to content

Commit 17eb6f1

Browse files
committed
fix(mdviewer): restore cursor position on Escape in link popover
Save selection when popover first shows (not just on edit mode entry) so Escape restores cursor to the correct position. Add stopPropagation to Escape handlers in link-popover and format-bar so bridge.js doesn't also forward the key to Phoenix. Simplify cancelEdit to always hide, restore selection, and refocus editor. Add integration test verifying Escape dismisses dialog with focus in md editor, second Escape moves focus to CM.
1 parent 15081f6 commit 17eb6f1

4 files changed

Lines changed: 77 additions & 27 deletions

File tree

src-mdviewer/src/components/format-bar.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ function buildBar() {
8888
exitLinkMode();
8989
} else if (e.key === "Escape") {
9090
e.preventDefault();
91+
e.stopPropagation();
9192
exitLinkMode();
9293
contentEl?.focus({ preventScroll: true });
9394
}

src-mdviewer/src/components/link-popover.js

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ function buildPopover() {
170170
applyLink();
171171
} else if (e.key === "Escape") {
172172
e.preventDefault();
173+
e.stopPropagation();
173174
cancelEdit();
174175
}
175176
};
@@ -227,18 +228,13 @@ function applyLink() {
227228
}
228229

229230
function cancelEdit() {
230-
if (createMode) {
231-
hide();
232-
restoreSelection();
233-
contentEl?.focus({ preventScroll: true });
234-
} else {
235-
exitEditMode();
236-
}
231+
hide();
232+
restoreSelection();
233+
contentEl?.focus({ preventScroll: true });
237234
}
238235

239236
function enterEditMode() {
240237
editMode = true;
241-
saveSelection();
242238
const viewDiv = popover.querySelector(".link-popover-view");
243239
const editDiv = popover.querySelector(".link-popover-edit");
244240
const textInput = popover.querySelector(".link-popover-text-input");
@@ -274,6 +270,7 @@ function show(anchorEl) {
274270
urlText.href = href;
275271
}
276272
exitEditMode();
273+
saveSelection();
277274
positionPopover(anchorEl);
278275
popover.classList.add("visible");
279276
}

src-mdviewer/to-create-tests.md

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,5 @@
11
# Markdown Viewer/Editor — Integration Tests To Create
22

3-
## Cursor/Scroll Sync
4-
- [x] Clicking in CM scrolls md viewer to corresponding element
5-
- [x] Clicking in md viewer scrolls CM to corresponding line (centered)
6-
- [x] Cursor sync toggle button disables/enables bidirectional sync
7-
- [x] Cursor sync toggle state preserved across toolbar re-renders (file switch, mode toggle)
8-
- [x] Content sync still works when cursor sync is disabled
9-
- [x] Cursor sync toggle works in both reader and edit mode
10-
- [x] Disabling cursor sync in reader mode prevents CM scroll on click
11-
- [x] Cursor sync works on newly edited elements after edit→reader switch
12-
- [x] Edit→reader switch re-renders from CM content (data-source-line attrs refreshed)
13-
- [x] Switching MD files preserves current edit/reader mode
14-
- [x] Edit mode not reset when switching between MD files
15-
163
## Edit Mode & Entitlement Gating
174
- [ ] Free user sees Edit button → clicking shows upsell dialog
185
- [ ] Pro user sees Edit button → clicking enters edit mode
@@ -51,10 +38,6 @@
5138
- [ ] Selected item wraps around (last → first, first → last)
5239
- [ ] Slash menu works at bottom of a long scrolled document
5340

54-
## Keyboard Shortcut Focus
55-
- [x] Ctrl+S saves file and keeps focus in md editor (not CM)
56-
- [x] Forwarded shortcuts refocus md editor after Phoenix handles them
57-
5841
## Code Block Editing
5942
- [ ] ArrowDown on last line of code block exits to paragraph below
6043
- [ ] ArrowDown on last line creates new `<p>` if none exists below
@@ -152,8 +135,6 @@
152135
- [ ] Backspace in middle of heading works normally (deletes character)
153136

154137
## Undo/Redo
155-
- [x] Ctrl+Z undoes change in both md editor and CM (single undo stack)
156-
- [x] Ctrl+Shift+Z / Ctrl+Y redoes change in both
157138
- [ ] Cursor restored to correct block element (source-line) after undo
158139
- [ ] Cursor restored to correct offset within block after undo
159140
- [ ] Undo/redo cursor works when editing at different positions in document

test/spec/md-editor-integ-test.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1768,6 +1768,77 @@ define(function (require, exports, module) {
17681768
await awaitsForDone(CommandManager.execute(Commands.FILE_CLOSE, { _forceClose: true }),
17691769
"force close doc2.md");
17701770
}, 15000);
1771+
1772+
it("should Escape in link edit dialog dismiss dialog and keep focus in md editor", async function () {
1773+
await _openMdFile("doc2.md");
1774+
await _enterEditMode();
1775+
await _focusMdContent();
1776+
1777+
const mdDoc = _getMdIFrameDoc();
1778+
const content = mdDoc.getElementById("viewer-content");
1779+
1780+
// Click on existing link to trigger popover
1781+
const link = content.querySelector("a[href*='test-link-doc2']");
1782+
expect(link).not.toBeNull();
1783+
const range = mdDoc.createRange();
1784+
range.selectNodeContents(link);
1785+
range.collapse(true);
1786+
_getMdIFrameWin().getSelection().removeAllRanges();
1787+
_getMdIFrameWin().getSelection().addRange(range);
1788+
content.dispatchEvent(new KeyboardEvent("keyup", {
1789+
key: "ArrowRight", code: "ArrowRight", bubbles: true
1790+
}));
1791+
1792+
// Wait for link popover to appear
1793+
await awaitsFor(() => {
1794+
const popover = mdDoc.getElementById("link-popover");
1795+
return popover && popover.classList.contains("visible");
1796+
}, "link popover to appear");
1797+
1798+
// Click Edit button to enter edit mode in popover
1799+
const popover = mdDoc.getElementById("link-popover");
1800+
const editBtn = popover.querySelector(".link-popover-edit-btn");
1801+
expect(editBtn).not.toBeNull();
1802+
editBtn.click();
1803+
1804+
// Wait for edit inputs to be visible
1805+
await awaitsFor(() => {
1806+
const editDiv = popover.querySelector(".link-popover-edit");
1807+
return editDiv && editDiv.style.display !== "none";
1808+
}, "link popover edit mode to be active");
1809+
1810+
// Press Escape on the edit input — should dismiss dialog only
1811+
const popoverInput = popover.querySelector(".link-popover-input");
1812+
expect(popoverInput).not.toBeNull();
1813+
popoverInput.dispatchEvent(new KeyboardEvent("keydown", {
1814+
key: "Escape", code: "Escape", bubbles: true
1815+
}));
1816+
1817+
// Popover should be dismissed
1818+
await awaitsFor(() => {
1819+
return !popover.classList.contains("visible");
1820+
}, "link popover to be dismissed after Escape");
1821+
1822+
// Focus should remain in md editor, NOT switch to CM
1823+
await awaitsFor(() => {
1824+
return mdDoc.activeElement === content || content.contains(mdDoc.activeElement);
1825+
}, "focus to remain in md editor after dismissing link dialog");
1826+
1827+
// Now press Escape again — this time focus should switch to CM editor
1828+
content.dispatchEvent(new KeyboardEvent("keydown", {
1829+
key: "Escape", code: "Escape", bubbles: true
1830+
}));
1831+
1832+
await awaitsFor(() => {
1833+
const activeEl = testWindow.document.activeElement;
1834+
return activeEl && (activeEl.classList.contains("CodeMirror") ||
1835+
activeEl.tagName === "TEXTAREA" ||
1836+
(activeEl.closest && activeEl.closest(".CodeMirror")));
1837+
}, "focus to switch to CM editor after second Escape");
1838+
1839+
await awaitsForDone(CommandManager.execute(Commands.FILE_CLOSE, { _forceClose: true }),
1840+
"force close doc2.md");
1841+
}, 15000);
17711842
});
17721843

17731844
describe("Empty Line Placeholder", function () {

0 commit comments

Comments
 (0)