Skip to content

Commit 301ad8a

Browse files
committed
fix(mdviewer): use single undo stack via CM with cursor preservation
Route all undo/redo through CM (single source of truth). Save/restore cursor offset around innerHTML re-render only when md editor has focus, preventing CM cursor interference. Fix Ctrl+Shift+Z redo detection.
1 parent 7f85b27 commit 301ad8a

File tree

2 files changed

+19
-8
lines changed

2 files changed

+19
-8
lines changed

src-mdviewer/src/bridge.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { getState, setState } from "./core/state.js";
88
import { setLocale } from "./core/i18n.js";
99
import { marked } from "marked";
1010
import * as docCache from "./core/doc-cache.js";
11+
import { getCursorOffset, restoreCursor } from "./components/editor.js";
1112

1213
let _syncId = 0;
1314
let _lastReceivedSyncId = -1;
@@ -152,7 +153,7 @@ export function initBridge() {
152153
// Undo/redo is routed through CM5's undo stack so both editors stay in sync.
153154
// Unhandled modifier shortcuts are forwarded to Phoenix's keybinding manager.
154155
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+
const _mdEditorHandledShiftKeys = new Set(["x", "X", "z", "Z"]); // Ctrl/Cmd + Shift + key
156157

157158
document.addEventListener("keydown", (e) => {
158159
if (e.key === "Escape") {
@@ -196,14 +197,14 @@ export function initBridge() {
196197
const isMod = _isMac ? e.metaKey : e.ctrlKey;
197198
if (!isMod) return;
198199

199-
if (e.key === "z" && !e.shiftKey) {
200+
if ((e.key === "z" || e.key === "Z") && !e.shiftKey) {
200201
e.preventDefault();
201202
e.stopImmediatePropagation();
202203
sendToParent("mdviewrUndo", {});
203204
return;
204205
}
205206

206-
if ((e.key === "z" && e.shiftKey) || e.key === "y") {
207+
if (((e.key === "z" || e.key === "Z") && e.shiftKey) || e.key === "y") {
207208
e.preventDefault();
208209
e.stopImmediatePropagation();
209210
sendToParent("mdviewrRedo", {});
@@ -378,17 +379,27 @@ function handleUpdateContent(data) {
378379
if (entry) {
379380
entry.mdSrc = markdown;
380381
entry.parseResult = parseResult;
381-
// Don't replace innerHTML — let file:rendered handle it
382-
// since the editor may be active
383382
}
384383
}
385384

386385
setState({
387386
currentContent: markdown,
388387
parseResult: parseResult
389388
});
389+
390+
// In edit mode, save/restore cursor around re-render
391+
const content = document.getElementById("viewer-content");
392+
let savedCursorOffset = null;
393+
if (getState().editMode && content && document.activeElement === content) {
394+
savedCursorOffset = getCursorOffset(content);
395+
}
396+
390397
emit("file:rendered", parseResult);
391398

399+
if (savedCursorOffset !== null && content) {
400+
restoreCursor(content, savedCursorOffset);
401+
}
402+
392403
_suppressContentChange = false;
393404
}
394405

src-mdviewer/src/components/editor.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ let lastChangeType = "";
4242
let currentInputType = "";
4343
let isPerformingUndoRedo = false;
4444

45-
function getCursorOffset(el) {
45+
export function getCursorOffset(el) {
4646
const sel = window.getSelection();
4747
if (!sel || !sel.rangeCount) return 0;
4848
const range = sel.getRangeAt(0);
@@ -52,7 +52,7 @@ function getCursorOffset(el) {
5252
return pre.toString().length;
5353
}
5454

55-
function restoreCursor(el, offset) {
55+
export function restoreCursor(el, offset) {
5656
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
5757
let remaining = offset;
5858
let node;
@@ -1308,7 +1308,7 @@ function enterEditMode(content) {
13081308
setState({ isDirty: undoStack.length > 0 });
13091309
return;
13101310
}
1311-
if (mod && ((e.key === "z" && e.shiftKey) || e.key === "y")) {
1311+
if (mod && (((e.key === "z" || e.key === "Z") && e.shiftKey) || e.key === "y")) {
13121312
e.preventDefault();
13131313
performRedo(content);
13141314
setState({ isDirty: true });

0 commit comments

Comments
 (0)