I know there many similar questions with answers on StackOverflow and Google and I've tried many and none of them works. So I will post my own code below and hopefully someone can help. Thanks in advance.
Note: there might be typos in below codes as I manually typed this question. I've checked and apologise for any inconvenience if they confuse you.
The logic I'm trying to achieve in that
contenteditableDIV:
- User types in the DIV
- Meanwhile an event listener is checking the text user enters and filter with pre-defined keywords array such as
['apple', 'banana']. Filtered text will be wrapped in the HTML and write back to the DIV. - While achieving above, user doesn't lose mouse caret which means this scanning and replacing procedure should be constantly happening in the background and doesn't affect user's editing.
The code:
HTML (Before Process)
<div id="editor">
Mr Jenkins has an apple and a banana.
</div>
HTML (After Processed)
<div id="editor">
Mr Jenkins has an <u>apple</u> and a <u>banana</u>.
</div>
JavaScript (It's in vue so I skipped other parts here and display in vanilla way here)
Plan A to use pre-defined selection variable
This throws error "DOMException:
Failed to execute 'collapse' on 'Selection': There is no child at offset 7"
// Global variable to store the offset value when DOM content loaded
// The input event of the #editor element is listened but skipped here
var caretPosition,
selection = window.getSelection();
function scanText() {
let editor = document.getElementById('editor');
// Save the position
caretPosition = selection.focusOffset;
// Scan and replace text
editor.innerHTML = filterText(); // Function filterText will return HTML as in above example
// Restore the position
selection.collapse(editor, caretPosition);
}
Plan B to use get selection on the go
This doesn't throw any errors but caret position is weird
The focusOffset value is far smaller than the real focusOffset value.
For example, if focusOffset in Plan A is 100, it's 9 in Plan B.
So it always restore at the incorrect position
function scanText() {
let editor = document.getElementById('editor');
// Save the position
caretPosition = window.getSelection().focusOffset;
// Scan and replace text
editor.innerHTML = filterText(); // Function filterText will return HTML as in above example
// Restore the position
window.getSelection().collapse(editor, caretPosition);
}
Plan C to use range
"IndexSizeError: Failed to execute 'setStart' on 'Range': There is no child at offset 5."
function scanText() {
let editor = document.getElementById('editor'),
offsets, range = new Range();
// Save the range
// It starts at 5 and ends at 101 though I only placed cursor at the end of the text
if (window.getSelection().rangeCount) {
offsets = [
window.getSelection().getRangeAt(0).startOffset, // 5
window.getSelection().getRangeAt(0).endOffset // 101
];
}
// Scan and replace text
editor.innerHTML = filterText(); // Function filterText will return HTML as in above example
// Restore the range
range.setStart(editor, offsets[0]); // Set start container to editor and start offset as 5
range.setEnd(editor, offsets[1]); // Set end container to editor and end offset as 101
}