Skip to content

Commit

Permalink
Merge branch 'inline-embed' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
jhchen committed Jun 20, 2017
2 parents b60cbe7 + 2fac8f4 commit e824bd5
Show file tree
Hide file tree
Showing 14 changed files with 171 additions and 41 deletions.
4 changes: 4 additions & 0 deletions assets/core.styl
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ resets(arr)
.ql-align-right
text-align: right

.ql-embed-selected
border: 1px solid #777
user-select: none

.ql-editor.ql-blank::before
color: rgba(0,0,0,0.6)
content: attr(data-placeholder)
Expand Down
4 changes: 2 additions & 2 deletions blots/block.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ class Block extends Parchment.Block {
this.cache = {};
}

optimize() {
super.optimize();
optimize(context) {
super.optimize(context);
this.cache = {};
}

Expand Down
29 changes: 16 additions & 13 deletions blots/cursor.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import Parchment from 'parchment';
import Embed from './embed';
import TextBlot from './text';
import Emitter from '../core/emitter';


class Cursor extends Embed {
Expand Down Expand Up @@ -58,8 +57,7 @@ class Cursor extends Embed {
}

restore() {
if (this.selection.composing) return;
if (this.parent == null) return;
if (this.selection.composing || this.parent == null) return;
let textNode = this.textNode;
let range = this.selection.getNativeRange();
let restoreText, start, end;
Expand All @@ -84,21 +82,26 @@ class Cursor extends Embed {
}
}
this.remove();
if (start == null) return;
this.selection.emitter.once(Emitter.events.SCROLL_OPTIMIZE, () => {
if (start != null) {
[start, end] = [start, end].map(function(offset) {
return Math.max(0, Math.min(restoreText.data.length, offset - 1));
});
this.selection.setNativeRange(restoreText, start, restoreText, end);
});
return {
startNode: restoreText,
startOffset: start,
endNode: restoreText,
endOffset: end
};
}
}

update(mutations) {
mutations.forEach((mutation) => {
if (mutation.type === 'characterData' && mutation.target === this.textNode) {
this.restore();
}
});
update(mutations, context) {
if (mutations.some((mutation) => {
return mutation.type === 'characterData' && mutation.target === this.textNode;
})) {
let range = this.restore();
if (range) context.range = range;
}
}

value() {
Expand Down
80 changes: 79 additions & 1 deletion blots/embed.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,83 @@
import Parchment from 'parchment';
import TextBlot from './text';

const GUARD_TEXT = "\uFEFF";


class Embed extends Parchment.Embed { }

export default Embed;

class InlineEmbed extends Embed {
constructor(node) {
super(node);
const wrapper = document.createElement('span');
wrapper.setAttribute('contenteditable', false);
[].slice.call(this.domNode.childNodes).forEach(function(childNode) {
wrapper.appendChild(childNode);
});
this.leftGuard = document.createTextNode(GUARD_TEXT);
this.rightGuard = document.createTextNode(GUARD_TEXT);
this.domNode.appendChild(this.leftGuard);
this.domNode.appendChild(wrapper);
this.domNode.appendChild(this.rightGuard);
}

index(node, offset) {
if (node === this.leftGuard) return 0;
if (node === this.rightGuard) return 1;
return super.index(node, offset);
}

restore(node) {
let range, text, textNode;
if (node === this.leftGuard) {
text = this.leftGuard.data.split(GUARD_TEXT).join('');
if (this.prev instanceof TextBlot) {
this.prev.insertAt(this.prev.length(), text);
range = {
startNode: this.prev.domNode,
startOffset: this.prev.domNode.data.length
};
} else {
textNode = document.createTextNode(text);
this.parent.insertBefore(Parchment.create(textNode), this);
range = {
startNode: textNode,
startOffset: text.length
};
}
this.leftGuard.data = GUARD_TEXT;
} else if (node === this.rightGuard) {
text = this.rightGuard.data.split(GUARD_TEXT).join('');
if (this.next instanceof TextBlot) {
this.next.insertAt(0, text);
range = {
startNode: this.next.domNode,
startOffset: text.length
}
} else {
textNode = document.createTextNode(text);
this.parent.insertBefore(Parchment.create(textNode), this.next);
range = {
startNode: textNode,
startOffset: text.length
};
}
this.rightGuard.data = GUARD_TEXT;
}
return range;
}

update(mutations, context) {
mutations.forEach((mutation) => {
if (mutation.type === 'characterData' &&
(mutation.target === this.leftGuard || mutation.target === this.rightGuard)) {
let range = this.restore(mutation.target);
if (range) context.range = range;
}
});
}
}


export { Embed as default, InlineEmbed };
4 changes: 2 additions & 2 deletions blots/inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ class Inline extends Parchment.Inline {
}
}

optimize() {
super.optimize();
optimize(context) {
super.optimize(context);
if (this.parent instanceof Inline &&
Inline.compare(this.statics.blotName, this.parent.statics.blotName) > 0) {
let parent = this.parent.isolate(this.offset(), this.length());
Expand Down
15 changes: 12 additions & 3 deletions blots/scroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ class Scroll extends Parchment.Scroll {
this.enable();
}

batchStart() {
this.batch = true;
}

batchEnd() {
this.batch = false;
this.optimize();
}

deleteAt(index, length) {
let [first, offset] = this.line(index);
let [last, ] = this.line(index + length);
Expand Down Expand Up @@ -109,11 +118,11 @@ class Scroll extends Parchment.Scroll {
return getLines(this, index, length);
}

optimize(mutations = []) {
optimize(mutations = [], context = {}) {
if (this.batch === true) return;
super.optimize(mutations);
super.optimize(mutations, context);
if (mutations.length > 0) {
this.emitter.emit(Emitter.events.SCROLL_OPTIMIZE, mutations);
this.emitter.emit(Emitter.events.SCROLL_OPTIMIZE, mutations, context);
}
}

Expand Down
5 changes: 2 additions & 3 deletions core/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class Editor {
let consumeNextNewline = false;
this.scroll.update();
let scrollLength = this.scroll.length();
this.scroll.batch = true;
this.scroll.batchStart();
delta = normalizeDelta(delta);
delta.reduce((index, op) => {
let length = op.retain || op.delete || op.insert.length || 1;
Expand Down Expand Up @@ -64,8 +64,7 @@ class Editor {
}
return index + (op.retain || op.insert.length || 1);
}, 0);
this.scroll.batch = false;
this.scroll.optimize();
this.scroll.batchEnd();
return this.update(delta);
}

Expand Down
1 change: 0 additions & 1 deletion core/quill.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ class Quill {
this.emitter = new Emitter();
this.scroll = Parchment.create(this.root, {
emitter: this.emitter,
scrollingContainer: this.scrollingContainer,
whitelist: this.options.formats
});
this.editor = new Editor(this.scroll);
Expand Down
43 changes: 43 additions & 0 deletions core/selection.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Parchment from 'parchment';
import { InlineEmbed } from '../blots/embed';
import clone from 'clone';
import equal from 'deep-equal';
import Emitter from './emitter';
Expand Down Expand Up @@ -37,6 +38,20 @@ class Selection {
setTimeout(this.update.bind(this, Emitter.sources.USER), 100);
});
});
this.root.addEventListener('click', (e) => {
const blot = Parchment.find(e.target, true);
if (blot instanceof Parchment.Embed) {
blot.domNode.classList.add('ql-embed-selected');
const range = new Range(blot.offset(scroll), blot.length());
this.setRange(range, Emitter.sources.USER);
e.stopPropagation();
} else {
const selectedNode = document.querySelector('.ql-embed-selected');
if (selectedNode) {
selectedNode.classList.remove('ql-embed-selected');
}
}
});
this.emitter.on(Emitter.events.EDITOR_CHANGE, (type, delta) => {
if (type === Emitter.events.TEXT_CHANGE && delta.length() > 0) {
this.update(Emitter.sources.SILENT);
Expand All @@ -54,9 +69,36 @@ class Selection {
} catch (ignored) {}
});
});
this.emitter.on(Emitter.events.SCROLL_OPTIMIZE, (mutations, context) => {
if (context.range) {
const { startNode, startOffset, endNode, endOffset } = context.range;
this.setNativeRange(startNode, startOffset, endNode, endOffset);
}
});
this.update(Emitter.sources.SILENT);
}

fixInlineEmbed(native) {
if (native == null) return;
const [start, end] = [native.start, native.end].map(function(pos) {
const blot = Parchment.find(pos.node, true);
if (blot instanceof InlineEmbed) {
let node, offset;
if (pos.node === blot.leftGuard && pos.offset === 1) {
[node, offset] = blot.position(blot.length());
return { node, offset };
} else if (pos.node === blot.rightGuard && pos.offset === 0) {
[node, offset] = blot.position(0);
return { node, offset };
}
}
return pos;
});
if (native.start !== start || native.end !== end) {
this.setNativeRange(start.node, start.offset, end.node, end.offset);
}
}

focus() {
if (this.hasFocus()) return;
this.root.focus();
Expand Down Expand Up @@ -291,6 +333,7 @@ class Selection {
update(source = Emitter.sources.USER) {
let oldRange = this.lastRange;
let [lastRange, nativeRange] = this.getRange();
this.fixInlineEmbed(nativeRange);
this.lastRange = lastRange;
if (this.lastRange != null) {
this.savedRange = this.lastRange;
Expand Down
4 changes: 2 additions & 2 deletions formats/bold.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ class Bold extends Inline {
return true;
}

optimize() {
super.optimize();
optimize(context) {
super.optimize(context);
if (this.domNode.tagName !== this.statics.tagName[0]) {
this.replaceWith(this.statics.blotName);
}
Expand Down
6 changes: 3 additions & 3 deletions formats/code.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,16 +81,16 @@ class CodeBlock extends Block {
}
}

optimize() {
optimize(context) {
if (!this.domNode.textContent.endsWith('\n')) {
this.appendChild(Parchment.create('text', '\n'));
}
super.optimize();
super.optimize(context);
let next = this.next;
if (next != null && next.prev === this &&
next.statics.blotName === this.statics.blotName &&
this.statics.formats(this.domNode) === next.statics.formats(next.domNode)) {
next.optimize();
next.optimize(context);
next.moveChildren(this);
next.remove();
}
Expand Down
4 changes: 2 additions & 2 deletions formats/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ class List extends Container {
}
}

optimize() {
super.optimize();
optimize(context) {
super.optimize(context);
let next = this.next;
if (next != null && next.prev === this &&
next.statics.blotName === this.statics.blotName &&
Expand Down
9 changes: 2 additions & 7 deletions modules/formula.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,21 @@
import Embed from '../blots/embed';
import { InlineEmbed } from '../blots/embed';
import Quill from '../core/quill';
import Module from '../core/module';


class FormulaBlot extends Embed {
class FormulaBlot extends InlineEmbed {
static create(value) {
let node = super.create(value);
if (typeof value === 'string') {
window.katex.render(value, node);
node.setAttribute('data-value', value);
}
node.setAttribute('contenteditable', false);
return node;
}

static value(domNode) {
return domNode.getAttribute('data-value');
}

index() {
return 1;
}
}
FormulaBlot.blotName = 'formula';
FormulaBlot.className = 'ql-formula';
Expand Down
4 changes: 2 additions & 2 deletions test/unit/blots/scroll.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ describe('Scroll', function() {
let scroll = this.initialize(Scroll, '<p>Hello World!</p>');
spyOn(scroll.emitter, 'emit').and.callThrough();
scroll.insertAt(5, '!');
expect(scroll.emitter.emit).toHaveBeenCalledWith(Emitter.events.SCROLL_OPTIMIZE, jasmine.any(Array));
expect(scroll.emitter.emit).toHaveBeenCalledWith(Emitter.events.SCROLL_OPTIMIZE, jasmine.any(Array), jasmine.any(Object));
});

it('user change', function(done) {
let scroll = this.initialize(Scroll, '<p>Hello World!</p>');
spyOn(scroll.emitter, 'emit').and.callThrough();
scroll.domNode.firstChild.appendChild(document.createTextNode('!'));
setTimeout(function() {
expect(scroll.emitter.emit).toHaveBeenCalledWith(Emitter.events.SCROLL_OPTIMIZE, jasmine.any(Array));
expect(scroll.emitter.emit).toHaveBeenCalledWith(Emitter.events.SCROLL_OPTIMIZE, jasmine.any(Array), jasmine.any(Object));
expect(scroll.emitter.emit).toHaveBeenCalledWith(Emitter.events.SCROLL_UPDATE, Emitter.sources.USER, jasmine.any(Array));
done();
}, 1);
Expand Down

0 comments on commit e824bd5

Please sign in to comment.