diff --git a/assets/js/markdown.js b/assets/js/markdown.js index 7bba4ee..b9653ce 100644 --- a/assets/js/markdown.js +++ b/assets/js/markdown.js @@ -354,6 +354,33 @@ window.renderMarkdownElements = renderMarkdownElements; // Rich Text Editor Toolbar Functions // ======================================== +/** + * Replace textarea.value.substring(selStart, selEnd) with replacementText, + * preserving the browser's native undo/redo stack via + * document.execCommand('insertText', ...) -- the same mechanism real typing + * uses -- instead of a direct .value assignment, which discards the entire + * undo history. Falls back to a direct assignment (losing undo, matching the + * old behavior) only if execCommand is unavailable or unsuccessful. + */ +function insertTextPreservingUndo(textarea, replacementText, selStart, selEnd) { + textarea.focus(); + textarea.setSelectionRange(selStart, selEnd); + + let inserted = false; + if (typeof document.execCommand === 'function') { + try { + inserted = document.execCommand('insertText', false, replacementText); + } catch (e) { + inserted = false; + } + } + + if (!inserted) { + const text = textarea.value; + textarea.value = text.substring(0, selStart) + replacementText + text.substring(selEnd); + } +} + /** * Insert markdown formatting around selection */ @@ -363,16 +390,13 @@ function insertMarkdownFormat(textareaId, prefix, suffix) { const start = textarea.selectionStart; const end = textarea.selectionEnd; - const text = textarea.value; - const selectedText = text.substring(start, end); + const selectedText = textarea.value.substring(start, end); - // Insert formatting - const newText = text.substring(0, start) + prefix + selectedText + suffix + text.substring(end); - textarea.value = newText; + insertTextPreservingUndo(textarea, prefix + selectedText + suffix, start, end); // Set cursor position if (selectedText) { - textarea.setSelectionRange(start + prefix.length, end + prefix.length); + textarea.setSelectionRange(start + prefix.length, start + prefix.length + selectedText.length); } else { textarea.setSelectionRange(start + prefix.length, start + prefix.length); } @@ -391,9 +415,10 @@ function insertMarkdownText(textareaId, text) { if (!textarea) return; const start = textarea.selectionStart; - const value = textarea.value; - textarea.value = value.substring(0, start) + text + value.substring(start); + // Matches the prior behavior: insert before the selection start without + // deleting any currently-selected text (a collapsed replace range). + insertTextPreservingUndo(textarea, text, start, start); textarea.setSelectionRange(start + text.length, start + text.length); textarea.focus(); @@ -453,7 +478,7 @@ function toolbarList(textareaId) { } // Insert list marker at beginning of line - textarea.value = text.substring(0, lineStart) + '- ' + text.substring(lineStart); + insertTextPreservingUndo(textarea, '- ', lineStart, lineStart); textarea.setSelectionRange(start + 2, start + 2); textarea.focus(); @@ -474,7 +499,7 @@ function toolbarHeading(textareaId) { } // Insert heading marker at beginning of line - textarea.value = text.substring(0, lineStart) + '## ' + text.substring(lineStart); + insertTextPreservingUndo(textarea, '## ', lineStart, lineStart); textarea.setSelectionRange(start + 3, start + 3); textarea.focus(); @@ -495,7 +520,7 @@ function toolbarQuote(textareaId) { } // Insert quote marker at beginning of line - textarea.value = text.substring(0, lineStart) + '> ' + text.substring(lineStart); + insertTextPreservingUndo(textarea, '> ', lineStart, lineStart); textarea.setSelectionRange(start + 2, start + 2); textarea.focus();