Skip to content
Snippets Groups Projects
Commit d933cd41 authored by Marius Dumitru Florea's avatar Marius Dumitru Florea
Browse files

XWIKI-22034: Realtime WYSIWYG editor is very slow even on medium size content

* Preserve the selection direction (e.g. when using Shift + Arrow Left to select text and a remote change is applied)
parent 0f5ff7ea
No related branches found
No related tags found
No related merge requests found
...@@ -31,12 +31,23 @@ ...@@ -31,12 +31,23 @@
// selection when the Source mode is active. In that case we take the selection directly from the plain text // selection when the Source mode is active. In that case we take the selection directly from the plain text
// area used for editing the source. We'll have to update this code when and if we'll add support for syntax // area used for editing the source. We'll have to update this code when and if we'll add support for syntax
// highlighting to the Source area. // highlighting to the Source area.
return editor.getSelection()?.getRanges().map(range => { const selection = editor.getSelection();
const domRanges = selection?.getRanges().map(range => {
const domRange = range.startContainer.$.ownerDocument.createRange(); const domRange = range.startContainer.$.ownerDocument.createRange();
domRange.setStart(range.startContainer.$, range.startOffset); domRange.setStart(range.startContainer.$, range.startOffset);
domRange.setEnd(range.endContainer.$, range.endOffset); domRange.setEnd(range.endContainer.$, range.endOffset);
return domRange; return domRange;
}) || []; }) || [];
// We have to indicate the selection direction (e.g. the focus node/offset is before the anchor node/offset when
// you use Shift + Left Arrow to select text), otherwise we wouldn't be able to restore it properly.
const nativeSelection = selection?.getNative();
const singleRange = domRanges.length === 1 && domRanges[0];
if (singleRange && !singleRange.collapsed && selection.getType() === CKEDITOR.SELECTION_TEXT &&
nativeSelection?.anchorNode === singleRange.endContainer &&
nativeSelection.anchorOffset === singleRange.endOffset) {
singleRange.reversed = true;
}
return domRanges;
} }
}, },
...@@ -109,12 +120,21 @@ ...@@ -109,12 +120,21 @@
// The editing area is focused so we can set the selection right away. // The editing area is focused so we can set the selection right away.
delete editor._.cachedSelectionRanges; delete editor._.cachedSelectionRanges;
// Convert the standard DOM ranges to CKEditor selection ranges. // Convert the standard DOM ranges to CKEditor selection ranges.
editor.getSelection()?.selectRanges(ranges.map(range => { const selection = editor.getSelection();
selection?.selectRanges(ranges.map(range => {
const ckRange = new CKEDITOR.dom.range(editor.editable()); const ckRange = new CKEDITOR.dom.range(editor.editable());
ckRange.setStart(new CKEDITOR.dom.node(range.startContainer), range.startOffset); ckRange.setStart(new CKEDITOR.dom.node(range.startContainer), range.startOffset);
ckRange.setEnd(new CKEDITOR.dom.node(range.endContainer), range.endOffset); ckRange.setEnd(new CKEDITOR.dom.node(range.endContainer), range.endOffset);
return ckRange; return ckRange;
})); }));
const nativeSelection = selection?.getNative();
const singleRange = ranges.length === 1 && ranges[0];
if (singleRange && !singleRange.collapsed && singleRange.reversed && nativeSelection) {
// CKEditor's selection API doesn't support setting the selection direction, so we have to use the native
// selection API to set the proper selection direction.
nativeSelection.setBaseAndExtent(singleRange.endContainer, singleRange.endOffset, singleRange.startContainer,
singleRange.startOffset);
}
} else { } else {
// The editing area is not focused. In order to set the selection inside the editing area the CKEditor needs to // The editing area is not focused. In order to set the selection inside the editing area the CKEditor needs to
// quickly steal the focus from the currently active element and give it back. This triggers the focus event on // quickly steal the focus from the currently active element and give it back. This triggers the focus event on
...@@ -258,7 +278,10 @@ define('textSelection', ['jquery', 'node-module!fast-diff', 'scrollUtils'], func ...@@ -258,7 +278,10 @@ define('textSelection', ['jquery', 'node-module!fast-diff', 'scrollUtils'], func
return { return {
text: beforeText + selectedText + afterText, text: beforeText + selectedText + afterText,
startOffset, startOffset,
endOffset: startOffset + selectedText.length endOffset: startOffset + selectedText.length,
// Remember the text selection direction. This is especially important when editing in realtime because the user
// might be selecting text backwards (e.g. using Shift + Left Arrow) when a remove change is applied.
reversed: range.reversed
}; };
} }
...@@ -367,6 +390,8 @@ define('textSelection', ['jquery', 'node-module!fast-diff', 'scrollUtils'], func ...@@ -367,6 +390,8 @@ define('textSelection', ['jquery', 'node-module!fast-diff', 'scrollUtils'], func
const range = root.ownerDocument.createRange(); const range = root.ownerDocument.createRange();
range.setStart(start.node, start.offset); range.setStart(start.node, start.offset);
range.setEnd(end.node, end.offset); range.setEnd(end.node, end.offset);
// Preserve the text selection direction.
range.reversed = textSelection.reversed;
return range; return range;
} }
...@@ -494,8 +519,11 @@ define('textSelection', ['jquery', 'node-module!fast-diff', 'scrollUtils'], func ...@@ -494,8 +519,11 @@ define('textSelection', ['jquery', 'node-module!fast-diff', 'scrollUtils'], func
scrollIntoView: true, scrollIntoView: true,
applyDOMRange: range => { applyDOMRange: range => {
const selection = element.ownerDocument.defaultView.getSelection(); const selection = element.ownerDocument.defaultView.getSelection();
selection.removeAllRanges(); if (range.reversed) {
selection.addRange(range); selection.setBaseAndExtent(range.endContainer, range.endOffset, range.startContainer, range.startOffset);
} else {
selection.setBaseAndExtent(range.startContainer, range.startOffset, range.endContainer, range.endOffset);
}
} }
}, options); }, options);
......
...@@ -252,7 +252,8 @@ define('xwiki-realtime-wysiwyg-patches', [ ...@@ -252,7 +252,8 @@ define('xwiki-realtime-wysiwyg-patches', [
this._editor.saveSelection(); this._editor.saveSelection();
// Save the selection as an array of relative ranges. // Save the selection as an array of relative ranges.
return this._editor.getSelection().map(range => { return this._editor.getSelection().map(range => {
const savedRange = {}; // Remember also the selection direction.
const savedRange = {reversed: range.reversed};
if (!range.startContainer.childNodes.length) { if (!range.startContainer.childNodes.length) {
savedRange.startContainer = range.startContainer; savedRange.startContainer = range.startContainer;
savedRange.startOffset = range.startOffset; savedRange.startOffset = range.startOffset;
...@@ -312,6 +313,7 @@ define('xwiki-realtime-wysiwyg-patches', [ ...@@ -312,6 +313,7 @@ define('xwiki-realtime-wysiwyg-patches', [
// The selected nodes are still in the DOM so we can restore the selection using the relative ranges. // The selected nodes are still in the DOM so we can restore the selection using the relative ranges.
this._editor.restoreSelection(selection.map(savedRange => { this._editor.restoreSelection(selection.map(savedRange => {
const range = this._editor.getContentWrapper().ownerDocument.createRange(); const range = this._editor.getContentWrapper().ownerDocument.createRange();
range.reversed = savedRange.reversed;
if (savedRange.startContainer?.isConnected) { if (savedRange.startContainer?.isConnected) {
range.setStart(savedRange.startContainer, savedRange.startOffset); range.setStart(savedRange.startContainer, savedRange.startOffset);
} else if (savedRange.startBefore?.isConnected) { } else if (savedRange.startBefore?.isConnected) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment