Skip to content

Commit e5ec39d

Browse files
committed
keep cursor postion alinged after undo in custom markdown editor
1 parent 44b955d commit e5ec39d

3 files changed

Lines changed: 52 additions & 6 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"icon": "images/icon.png",
44
"displayName": "Ultraview – All in One VS Code Extension",
55
"description": "All-in-one VS Code extension: database viewer, markdown editor, interactive code graph, Git account manager, and cross-IDE project sync — all inside VS Code with zero setup.",
6-
"version": "0.2.89",
6+
"version": "0.2.93",
77
"publisher": "Da3n0n",
88
"engines": {
99
"vscode": "^1.85.0"

src/editor/editorScript.ts

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,77 @@ export function getEditorScript(): string {
2020
const previewRedoStack = [];
2121
let previewUndoLocked = false;
2222
23+
// Returns the caret position as a plain character offset within \`root\`.
24+
function getCaretOffset(root) {
25+
const sel = window.getSelection();
26+
if (!sel || sel.rangeCount === 0) return null;
27+
const range = sel.getRangeAt(0);
28+
// Clamp the range to inside \`root\`
29+
if (!root.contains(range.startContainer)) return null;
30+
const pre = document.createRange();
31+
pre.setStart(root, 0);
32+
pre.setEnd(range.startContainer, range.startOffset);
33+
return pre.toString().length;
34+
}
35+
36+
// Restores the caret to a character offset within \`root\`.
37+
function setCaretOffset(root, offset) {
38+
if (offset === null || offset === undefined) return;
39+
try {
40+
const iter = document.createNodeIterator(root, NodeFilter.SHOW_TEXT);
41+
let remaining = offset;
42+
let node;
43+
while ((node = iter.nextNode())) {
44+
const len = node.nodeValue.length;
45+
if (remaining <= len) {
46+
const range = document.createRange();
47+
range.setStart(node, remaining);
48+
range.collapse(true);
49+
const sel = window.getSelection();
50+
sel.removeAllRanges();
51+
sel.addRange(range);
52+
return;
53+
}
54+
remaining -= len;
55+
}
56+
// Offset beyond end – place caret at the very end
57+
const range = document.createRange();
58+
range.selectNodeContents(root);
59+
range.collapse(false);
60+
const sel = window.getSelection();
61+
sel.removeAllRanges();
62+
sel.addRange(range);
63+
} catch (_) { /* ignore */ }
64+
}
65+
2366
function snapshotPreview() {
2467
if (previewUndoLocked) return;
25-
previewUndoStack.push({ html: preview.innerHTML, sel: null });
68+
previewUndoStack.push({ html: preview.innerHTML, sel: getCaretOffset(preview) });
2669
previewRedoStack.length = 0;
2770
if (previewUndoStack.length > 200) previewUndoStack.shift();
2871
}
2972
3073
function previewUndo() {
3174
if (previewUndoStack.length === 0) return;
32-
previewRedoStack.push({ html: preview.innerHTML });
75+
previewRedoStack.push({ html: preview.innerHTML, sel: getCaretOffset(preview) });
3376
const snap = previewUndoStack.pop();
3477
previewUndoLocked = true;
3578
preview.innerHTML = snap.html;
3679
previewUndoLocked = false;
80+
// Restore caret to where it was when the snapshot was taken
81+
setCaretOffset(preview, snap.sel);
3782
updateRawFromPreview();
3883
autoSave();
3984
}
4085
4186
function previewRedo() {
4287
if (previewRedoStack.length === 0) return;
43-
previewUndoStack.push({ html: preview.innerHTML });
88+
previewUndoStack.push({ html: preview.innerHTML, sel: getCaretOffset(preview) });
4489
const snap = previewRedoStack.pop();
4590
previewUndoLocked = true;
4691
preview.innerHTML = snap.html;
4792
previewUndoLocked = false;
93+
setCaretOffset(preview, snap.sel);
4894
updateRawFromPreview();
4995
autoSave();
5096
}

0 commit comments

Comments
 (0)