diff --git a/blots/cursor.js b/blots/cursor.js
index 320dd1920b..feca853adf 100644
--- a/blots/cursor.js
+++ b/blots/cursor.js
@@ -59,7 +59,8 @@ class Cursor extends EmbedBlot {
restore() {
if (this.selection.composing || this.parent == null) return null;
const range = this.selection.getNativeRange();
- // Link format will insert text outside of anchor tag
+ // Browser may push down styles/nodes inside the cursor blot.
+ // https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#push-down-values
while (
this.domNode.lastChild != null &&
this.domNode.lastChild !== this.textNode
@@ -144,6 +145,31 @@ class Cursor extends EmbedBlot {
}
}
+ // Avoid .ql-cursor being a descendant of ``.
+ // The reason is Safari pushes down `` on text insertion.
+ // That will make DOM nodes not sync with the model.
+ //
+ // For example ({I} is the caret), given the markup:
+ // \uFEFF{I}
+ // When typing a char "x", `` will be pushed down inside the `` first:
+ // \uFEFF{I}
+ // And then "x" will be inserted after ``:
+ // \uFEFFd{I}
+ optimize(context) {
+ super.optimize(context);
+
+ let { parent } = this;
+ while (parent) {
+ if (parent.domNode.tagName === 'A') {
+ this.savedLength = Cursor.CONTENTS.length;
+ parent.isolate(this.offset(parent), this.length()).unwrap();
+ this.savedLength = 0;
+ break;
+ }
+ parent = parent.parent;
+ }
+ }
+
value() {
return '';
}
diff --git a/modules/clipboard.js b/modules/clipboard.js
index 8eadd406a6..fc5a81d088 100644
--- a/modules/clipboard.js
+++ b/modules/clipboard.js
@@ -155,7 +155,8 @@ class Clipboard extends Module {
if (!html && files.length > 0) {
this.quill.uploader.upload(range, files);
return;
- } else if (html && files.length > 0) {
+ }
+ if (html && files.length > 0) {
const doc = new DOMParser().parseFromString(html, 'text/html');
if (
doc.body.childElementCount === 1 &&
diff --git a/test/unit/core/selection.js b/test/unit/core/selection.js
index 164f5f8bed..1419c8d161 100644
--- a/test/unit/core/selection.js
+++ b/test/unit/core/selection.js
@@ -441,6 +441,40 @@ describe('Selection', function() {
01${Cursor.CONTENTS}23
`);
});
+
+ describe('unlink cursor', function() {
+ const cursorHTML = `${Cursor.CONTENTS}`;
+
+ it('one level', function() {
+ this.setup(
+ 'link
',
+ 4,
+ );
+ this.selection.format('bold', false);
+ expect(this.container).toEqualHTML(`
+ link${cursorHTML}
+ `);
+ });
+
+ it('nested formats', function() {
+ this.setup(
+ 'bold
',
+ 4,
+ );
+ this.selection.format('italic', false);
+ expect(this.container).toEqualHTML(`
+ bold${cursorHTML}
+ `);
+ });
+
+ it('ignore link format', function() {
+ this.setup('bold
', 4);
+ this.selection.format('link', 'https://example.com');
+ expect(this.container).toEqualHTML(`
+ bold${cursorHTML}
+ `);
+ });
+ });
});
describe('getBounds()', function() {