first commit
This commit is contained in:
349
public/assets/quill/blots/scroll.js
Normal file
349
public/assets/quill/blots/scroll.js
Normal file
@@ -0,0 +1,349 @@
|
||||
import { ContainerBlot, LeafBlot, Scope, ScrollBlot } from 'parchment';
|
||||
import Delta, { AttributeMap, Op } from 'quill-delta';
|
||||
import Emitter from '../core/emitter.js';
|
||||
import Block, { BlockEmbed, bubbleFormats } from './block.js';
|
||||
import Break from './break.js';
|
||||
import Container from './container.js';
|
||||
function isLine(blot) {
|
||||
return blot instanceof Block || blot instanceof BlockEmbed;
|
||||
}
|
||||
function isUpdatable(blot) {
|
||||
return typeof blot.updateContent === 'function';
|
||||
}
|
||||
class Scroll extends ScrollBlot {
|
||||
static blotName = 'scroll';
|
||||
static className = 'ql-editor';
|
||||
static tagName = 'DIV';
|
||||
static defaultChild = Block;
|
||||
static allowedChildren = [Block, BlockEmbed, Container];
|
||||
constructor(registry, domNode, _ref) {
|
||||
let {
|
||||
emitter
|
||||
} = _ref;
|
||||
super(registry, domNode);
|
||||
this.emitter = emitter;
|
||||
this.batch = false;
|
||||
this.optimize();
|
||||
this.enable();
|
||||
this.domNode.addEventListener('dragstart', e => this.handleDragStart(e));
|
||||
}
|
||||
batchStart() {
|
||||
if (!Array.isArray(this.batch)) {
|
||||
this.batch = [];
|
||||
}
|
||||
}
|
||||
batchEnd() {
|
||||
if (!this.batch) return;
|
||||
const mutations = this.batch;
|
||||
this.batch = false;
|
||||
this.update(mutations);
|
||||
}
|
||||
emitMount(blot) {
|
||||
this.emitter.emit(Emitter.events.SCROLL_BLOT_MOUNT, blot);
|
||||
}
|
||||
emitUnmount(blot) {
|
||||
this.emitter.emit(Emitter.events.SCROLL_BLOT_UNMOUNT, blot);
|
||||
}
|
||||
emitEmbedUpdate(blot, change) {
|
||||
this.emitter.emit(Emitter.events.SCROLL_EMBED_UPDATE, blot, change);
|
||||
}
|
||||
deleteAt(index, length) {
|
||||
const [first, offset] = this.line(index);
|
||||
const [last] = this.line(index + length);
|
||||
super.deleteAt(index, length);
|
||||
if (last != null && first !== last && offset > 0) {
|
||||
if (first instanceof BlockEmbed || last instanceof BlockEmbed) {
|
||||
this.optimize();
|
||||
return;
|
||||
}
|
||||
const ref = last.children.head instanceof Break ? null : last.children.head;
|
||||
// @ts-expect-error
|
||||
first.moveChildren(last, ref);
|
||||
// @ts-expect-error
|
||||
first.remove();
|
||||
}
|
||||
this.optimize();
|
||||
}
|
||||
enable() {
|
||||
let enabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
|
||||
this.domNode.setAttribute('contenteditable', enabled ? 'true' : 'false');
|
||||
}
|
||||
formatAt(index, length, format, value) {
|
||||
super.formatAt(index, length, format, value);
|
||||
this.optimize();
|
||||
}
|
||||
insertAt(index, value, def) {
|
||||
if (index >= this.length()) {
|
||||
if (def == null || this.scroll.query(value, Scope.BLOCK) == null) {
|
||||
const blot = this.scroll.create(this.statics.defaultChild.blotName);
|
||||
this.appendChild(blot);
|
||||
if (def == null && value.endsWith('\n')) {
|
||||
blot.insertAt(0, value.slice(0, -1), def);
|
||||
} else {
|
||||
blot.insertAt(0, value, def);
|
||||
}
|
||||
} else {
|
||||
const embed = this.scroll.create(value, def);
|
||||
this.appendChild(embed);
|
||||
}
|
||||
} else {
|
||||
super.insertAt(index, value, def);
|
||||
}
|
||||
this.optimize();
|
||||
}
|
||||
insertBefore(blot, ref) {
|
||||
if (blot.statics.scope === Scope.INLINE_BLOT) {
|
||||
const wrapper = this.scroll.create(this.statics.defaultChild.blotName);
|
||||
wrapper.appendChild(blot);
|
||||
super.insertBefore(wrapper, ref);
|
||||
} else {
|
||||
super.insertBefore(blot, ref);
|
||||
}
|
||||
}
|
||||
insertContents(index, delta) {
|
||||
const renderBlocks = this.deltaToRenderBlocks(delta.concat(new Delta().insert('\n')));
|
||||
const last = renderBlocks.pop();
|
||||
if (last == null) return;
|
||||
this.batchStart();
|
||||
const first = renderBlocks.shift();
|
||||
if (first) {
|
||||
const shouldInsertNewlineChar = first.type === 'block' && (first.delta.length() === 0 || !this.descendant(BlockEmbed, index)[0] && index < this.length());
|
||||
const delta = first.type === 'block' ? first.delta : new Delta().insert({
|
||||
[first.key]: first.value
|
||||
});
|
||||
insertInlineContents(this, index, delta);
|
||||
const newlineCharLength = first.type === 'block' ? 1 : 0;
|
||||
const lineEndIndex = index + delta.length() + newlineCharLength;
|
||||
if (shouldInsertNewlineChar) {
|
||||
this.insertAt(lineEndIndex - 1, '\n');
|
||||
}
|
||||
const formats = bubbleFormats(this.line(index)[0]);
|
||||
const attributes = AttributeMap.diff(formats, first.attributes) || {};
|
||||
Object.keys(attributes).forEach(name => {
|
||||
this.formatAt(lineEndIndex - 1, 1, name, attributes[name]);
|
||||
});
|
||||
index = lineEndIndex;
|
||||
}
|
||||
let [refBlot, refBlotOffset] = this.children.find(index);
|
||||
if (renderBlocks.length) {
|
||||
if (refBlot) {
|
||||
refBlot = refBlot.split(refBlotOffset);
|
||||
refBlotOffset = 0;
|
||||
}
|
||||
renderBlocks.forEach(renderBlock => {
|
||||
if (renderBlock.type === 'block') {
|
||||
const block = this.createBlock(renderBlock.attributes, refBlot || undefined);
|
||||
insertInlineContents(block, 0, renderBlock.delta);
|
||||
} else {
|
||||
const blockEmbed = this.create(renderBlock.key, renderBlock.value);
|
||||
this.insertBefore(blockEmbed, refBlot || undefined);
|
||||
Object.keys(renderBlock.attributes).forEach(name => {
|
||||
blockEmbed.format(name, renderBlock.attributes[name]);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if (last.type === 'block' && last.delta.length()) {
|
||||
const offset = refBlot ? refBlot.offset(refBlot.scroll) + refBlotOffset : this.length();
|
||||
insertInlineContents(this, offset, last.delta);
|
||||
}
|
||||
this.batchEnd();
|
||||
this.optimize();
|
||||
}
|
||||
isEnabled() {
|
||||
return this.domNode.getAttribute('contenteditable') === 'true';
|
||||
}
|
||||
leaf(index) {
|
||||
const last = this.path(index).pop();
|
||||
if (!last) {
|
||||
return [null, -1];
|
||||
}
|
||||
const [blot, offset] = last;
|
||||
return blot instanceof LeafBlot ? [blot, offset] : [null, -1];
|
||||
}
|
||||
line(index) {
|
||||
if (index === this.length()) {
|
||||
return this.line(index - 1);
|
||||
}
|
||||
// @ts-expect-error TODO: make descendant() generic
|
||||
return this.descendant(isLine, index);
|
||||
}
|
||||
lines() {
|
||||
let index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
|
||||
let length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Number.MAX_VALUE;
|
||||
const getLines = (blot, blotIndex, blotLength) => {
|
||||
let lines = [];
|
||||
let lengthLeft = blotLength;
|
||||
blot.children.forEachAt(blotIndex, blotLength, (child, childIndex, childLength) => {
|
||||
if (isLine(child)) {
|
||||
lines.push(child);
|
||||
} else if (child instanceof ContainerBlot) {
|
||||
lines = lines.concat(getLines(child, childIndex, lengthLeft));
|
||||
}
|
||||
lengthLeft -= childLength;
|
||||
});
|
||||
return lines;
|
||||
};
|
||||
return getLines(this, index, length);
|
||||
}
|
||||
optimize() {
|
||||
let mutations = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
||||
let context = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
||||
if (this.batch) return;
|
||||
super.optimize(mutations, context);
|
||||
if (mutations.length > 0) {
|
||||
this.emitter.emit(Emitter.events.SCROLL_OPTIMIZE, mutations, context);
|
||||
}
|
||||
}
|
||||
path(index) {
|
||||
return super.path(index).slice(1); // Exclude self
|
||||
}
|
||||
remove() {
|
||||
// Never remove self
|
||||
}
|
||||
update(mutations) {
|
||||
if (this.batch) {
|
||||
if (Array.isArray(mutations)) {
|
||||
this.batch = this.batch.concat(mutations);
|
||||
}
|
||||
return;
|
||||
}
|
||||
let source = Emitter.sources.USER;
|
||||
if (typeof mutations === 'string') {
|
||||
source = mutations;
|
||||
}
|
||||
if (!Array.isArray(mutations)) {
|
||||
mutations = this.observer.takeRecords();
|
||||
}
|
||||
mutations = mutations.filter(_ref2 => {
|
||||
let {
|
||||
target
|
||||
} = _ref2;
|
||||
const blot = this.find(target, true);
|
||||
return blot && !isUpdatable(blot);
|
||||
});
|
||||
if (mutations.length > 0) {
|
||||
this.emitter.emit(Emitter.events.SCROLL_BEFORE_UPDATE, source, mutations);
|
||||
}
|
||||
super.update(mutations.concat([])); // pass copy
|
||||
if (mutations.length > 0) {
|
||||
this.emitter.emit(Emitter.events.SCROLL_UPDATE, source, mutations);
|
||||
}
|
||||
}
|
||||
updateEmbedAt(index, key, change) {
|
||||
// Currently it only supports top-level embeds (BlockEmbed).
|
||||
// We can update `ParentBlot` in parchment to support inline embeds.
|
||||
const [blot] = this.descendant(b => b instanceof BlockEmbed, index);
|
||||
if (blot && blot.statics.blotName === key && isUpdatable(blot)) {
|
||||
blot.updateContent(change);
|
||||
}
|
||||
}
|
||||
handleDragStart(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
deltaToRenderBlocks(delta) {
|
||||
const renderBlocks = [];
|
||||
let currentBlockDelta = new Delta();
|
||||
delta.forEach(op => {
|
||||
const insert = op?.insert;
|
||||
if (!insert) return;
|
||||
if (typeof insert === 'string') {
|
||||
const splitted = insert.split('\n');
|
||||
splitted.slice(0, -1).forEach(text => {
|
||||
currentBlockDelta.insert(text, op.attributes);
|
||||
renderBlocks.push({
|
||||
type: 'block',
|
||||
delta: currentBlockDelta,
|
||||
attributes: op.attributes ?? {}
|
||||
});
|
||||
currentBlockDelta = new Delta();
|
||||
});
|
||||
const last = splitted[splitted.length - 1];
|
||||
if (last) {
|
||||
currentBlockDelta.insert(last, op.attributes);
|
||||
}
|
||||
} else {
|
||||
const key = Object.keys(insert)[0];
|
||||
if (!key) return;
|
||||
if (this.query(key, Scope.INLINE)) {
|
||||
currentBlockDelta.push(op);
|
||||
} else {
|
||||
if (currentBlockDelta.length()) {
|
||||
renderBlocks.push({
|
||||
type: 'block',
|
||||
delta: currentBlockDelta,
|
||||
attributes: {}
|
||||
});
|
||||
}
|
||||
currentBlockDelta = new Delta();
|
||||
renderBlocks.push({
|
||||
type: 'blockEmbed',
|
||||
key,
|
||||
value: insert[key],
|
||||
attributes: op.attributes ?? {}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
if (currentBlockDelta.length()) {
|
||||
renderBlocks.push({
|
||||
type: 'block',
|
||||
delta: currentBlockDelta,
|
||||
attributes: {}
|
||||
});
|
||||
}
|
||||
return renderBlocks;
|
||||
}
|
||||
createBlock(attributes, refBlot) {
|
||||
let blotName;
|
||||
const formats = {};
|
||||
Object.entries(attributes).forEach(_ref3 => {
|
||||
let [key, value] = _ref3;
|
||||
const isBlockBlot = this.query(key, Scope.BLOCK & Scope.BLOT) != null;
|
||||
if (isBlockBlot) {
|
||||
blotName = key;
|
||||
} else {
|
||||
formats[key] = value;
|
||||
}
|
||||
});
|
||||
const block = this.create(blotName || this.statics.defaultChild.blotName, blotName ? attributes[blotName] : undefined);
|
||||
this.insertBefore(block, refBlot || undefined);
|
||||
const length = block.length();
|
||||
Object.entries(formats).forEach(_ref4 => {
|
||||
let [key, value] = _ref4;
|
||||
block.formatAt(0, length, key, value);
|
||||
});
|
||||
return block;
|
||||
}
|
||||
}
|
||||
function insertInlineContents(parent, index, inlineContents) {
|
||||
inlineContents.reduce((index, op) => {
|
||||
const length = Op.length(op);
|
||||
let attributes = op.attributes || {};
|
||||
if (op.insert != null) {
|
||||
if (typeof op.insert === 'string') {
|
||||
const text = op.insert;
|
||||
parent.insertAt(index, text);
|
||||
const [leaf] = parent.descendant(LeafBlot, index);
|
||||
const formats = bubbleFormats(leaf);
|
||||
attributes = AttributeMap.diff(formats, attributes) || {};
|
||||
} else if (typeof op.insert === 'object') {
|
||||
const key = Object.keys(op.insert)[0]; // There should only be one key
|
||||
if (key == null) return index;
|
||||
parent.insertAt(index, key, op.insert[key]);
|
||||
const isInlineEmbed = parent.scroll.query(key, Scope.INLINE) != null;
|
||||
if (isInlineEmbed) {
|
||||
const [leaf] = parent.descendant(LeafBlot, index);
|
||||
const formats = bubbleFormats(leaf);
|
||||
attributes = AttributeMap.diff(formats, attributes) || {};
|
||||
}
|
||||
}
|
||||
}
|
||||
Object.keys(attributes).forEach(key => {
|
||||
parent.formatAt(index, length, key, attributes[key]);
|
||||
});
|
||||
return index + length;
|
||||
}, index);
|
||||
}
|
||||
export default Scroll;
|
||||
//# sourceMappingURL=scroll.js.map
|
||||
Reference in New Issue
Block a user