diff --git a/assets/editor.js b/assets/editor.js index a356792..390b3e1 100644 --- a/assets/editor.js +++ b/assets/editor.js @@ -6,44 +6,55 @@ // --- DOM helpers --- + // Route every edit through execCommand so the browser's native undo/redo + // stack is preserved. Direct assignment to textarea.value would wipe it. + function replaceRange(start, end, text) { + textarea.focus(); + textarea.selectionStart = start; + textarea.selectionEnd = end; + document.execCommand('insertText', false, text); + } + function wrap(before, after, placeholder) { var start = textarea.selectionStart; var end = textarea.selectionEnd; - var selected = textarea.value.slice(start, end) || placeholder; - var replacement = before + selected + after; - textarea.value = textarea.value.slice(0, start) + replacement + textarea.value.slice(end); - if (selected === placeholder) { + var hadSelection = end > start; + var selected = hadSelection ? textarea.value.slice(start, end) : placeholder; + replaceRange(start, end, before + selected + after); + if (!hadSelection) { textarea.selectionStart = start + before.length; textarea.selectionEnd = start + before.length + placeholder.length; - } else { - textarea.selectionStart = start + replacement.length; - textarea.selectionEnd = start + replacement.length; } - textarea.focus(); } function linePrefix(prefix) { var start = textarea.selectionStart; var lineStart = textarea.value.lastIndexOf('\n', start - 1) + 1; - textarea.value = textarea.value.slice(0, lineStart) + prefix + textarea.value.slice(lineStart); + replaceRange(lineStart, lineStart, prefix); textarea.selectionStart = textarea.selectionEnd = start + prefix.length; - textarea.focus(); } function insertAtCursor(s) { - var start = textarea.selectionStart; - var end = textarea.selectionEnd; - textarea.value = textarea.value.slice(0, start) + s + textarea.value.slice(end); - textarea.selectionStart = textarea.selectionEnd = start + s.length; - textarea.dispatchEvent(new Event('input')); - textarea.focus(); + replaceRange(textarea.selectionStart, textarea.selectionEnd, s); } function applyResult(result) { - textarea.value = result.text; + var oldText = textarea.value; + var newText = result.text; + var prefixLen = 0; + var maxPrefix = Math.min(oldText.length, newText.length); + while (prefixLen < maxPrefix && oldText.charCodeAt(prefixLen) === newText.charCodeAt(prefixLen)) { + prefixLen++; + } + var oldEnd = oldText.length; + var newEnd = newText.length; + while (oldEnd > prefixLen && newEnd > prefixLen + && oldText.charCodeAt(oldEnd - 1) === newText.charCodeAt(newEnd - 1)) { + oldEnd--; + newEnd--; + } + replaceRange(prefixLen, oldEnd, newText.slice(prefixLen, newEnd)); textarea.selectionStart = textarea.selectionEnd = result.cursor; - textarea.dispatchEvent(new Event('input')); - textarea.focus(); } function applyTableOp(fn, arg) { diff --git a/assets/editor/movie.js b/assets/editor/movie.js index 5040666..6cee02d 100644 --- a/assets/editor/movie.js +++ b/assets/editor/movie.js @@ -49,22 +49,28 @@ window.EditorMovie = (function () { return out.join('\n'); } + function replaceRange(ta, start, end, text) { + ta.focus(); + ta.selectionStart = start; + ta.selectionEnd = end; + document.execCommand('insertText', false, text); + } + function insertOrReplace(ta, markup) { var t = ta.value || ''; var b = t.indexOf(BEGIN); var e = t.indexOf(END); if (b !== -1 && e !== -1 && e > b) { - ta.value = t.slice(0, b) + markup + t.slice(e + END.length); + replaceRange(ta, b, e + END.length, markup); } else { var h = t.match(/^#{1,6}\s+.+?\s*$/m); if (h) { var idx = t.indexOf(h[0]) + h[0].length; - ta.value = t.slice(0, idx) + '\n\n' + markup + t.slice(idx); + replaceRange(ta, idx, idx, '\n\n' + markup); } else { - ta.value = markup + (t ? '\n\n' + t : ''); + replaceRange(ta, 0, 0, t ? markup + '\n\n' : markup); } } - ta.dispatchEvent(new Event('input')); } function fetchMovie(title, year) {