You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
5.3 KiB
182 lines
5.3 KiB
import { AttributorStore, BlockBlot, EmbedBlot, LeafBlot, Scope } from 'parchment'; |
|
import Delta from 'quill-delta'; |
|
import Break from './break.js'; |
|
import Inline from './inline.js'; |
|
import TextBlot from './text.js'; |
|
const NEWLINE_LENGTH = 1; |
|
class Block extends BlockBlot { |
|
cache = {}; |
|
delta() { |
|
if (this.cache.delta == null) { |
|
this.cache.delta = blockDelta(this); |
|
} |
|
return this.cache.delta; |
|
} |
|
deleteAt(index, length) { |
|
super.deleteAt(index, length); |
|
this.cache = {}; |
|
} |
|
formatAt(index, length, name, value) { |
|
if (length <= 0) return; |
|
if (this.scroll.query(name, Scope.BLOCK)) { |
|
if (index + length === this.length()) { |
|
this.format(name, value); |
|
} |
|
} else { |
|
super.formatAt(index, Math.min(length, this.length() - index - 1), name, value); |
|
} |
|
this.cache = {}; |
|
} |
|
insertAt(index, value, def) { |
|
if (def != null) { |
|
super.insertAt(index, value, def); |
|
this.cache = {}; |
|
return; |
|
} |
|
if (value.length === 0) return; |
|
const lines = value.split('\n'); |
|
const text = lines.shift(); |
|
if (text.length > 0) { |
|
if (index < this.length() - 1 || this.children.tail == null) { |
|
super.insertAt(Math.min(index, this.length() - 1), text); |
|
} else { |
|
this.children.tail.insertAt(this.children.tail.length(), text); |
|
} |
|
this.cache = {}; |
|
} |
|
// TODO: Fix this next time the file is edited. |
|
// eslint-disable-next-line @typescript-eslint/no-this-alias |
|
let block = this; |
|
lines.reduce((lineIndex, line) => { |
|
// @ts-expect-error Fix me later |
|
block = block.split(lineIndex, true); |
|
block.insertAt(0, line); |
|
return line.length; |
|
}, index + text.length); |
|
} |
|
insertBefore(blot, ref) { |
|
const { |
|
head |
|
} = this.children; |
|
super.insertBefore(blot, ref); |
|
if (head instanceof Break) { |
|
head.remove(); |
|
} |
|
this.cache = {}; |
|
} |
|
length() { |
|
if (this.cache.length == null) { |
|
this.cache.length = super.length() + NEWLINE_LENGTH; |
|
} |
|
return this.cache.length; |
|
} |
|
moveChildren(target, ref) { |
|
super.moveChildren(target, ref); |
|
this.cache = {}; |
|
} |
|
optimize(context) { |
|
super.optimize(context); |
|
this.cache = {}; |
|
} |
|
path(index) { |
|
return super.path(index, true); |
|
} |
|
removeChild(child) { |
|
super.removeChild(child); |
|
this.cache = {}; |
|
} |
|
split(index) { |
|
let force = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; |
|
if (force && (index === 0 || index >= this.length() - NEWLINE_LENGTH)) { |
|
const clone = this.clone(); |
|
if (index === 0) { |
|
this.parent.insertBefore(clone, this); |
|
return this; |
|
} |
|
this.parent.insertBefore(clone, this.next); |
|
return clone; |
|
} |
|
const next = super.split(index, force); |
|
this.cache = {}; |
|
return next; |
|
} |
|
} |
|
Block.blotName = 'block'; |
|
Block.tagName = 'P'; |
|
Block.defaultChild = Break; |
|
Block.allowedChildren = [Break, Inline, EmbedBlot, TextBlot]; |
|
class BlockEmbed extends EmbedBlot { |
|
attach() { |
|
super.attach(); |
|
this.attributes = new AttributorStore(this.domNode); |
|
} |
|
delta() { |
|
return new Delta().insert(this.value(), { |
|
...this.formats(), |
|
...this.attributes.values() |
|
}); |
|
} |
|
format(name, value) { |
|
const attribute = this.scroll.query(name, Scope.BLOCK_ATTRIBUTE); |
|
if (attribute != null) { |
|
// @ts-expect-error TODO: Scroll#query() should return Attributor when scope is attribute |
|
this.attributes.attribute(attribute, value); |
|
} |
|
} |
|
formatAt(index, length, name, value) { |
|
this.format(name, value); |
|
} |
|
insertAt(index, value, def) { |
|
if (def != null) { |
|
super.insertAt(index, value, def); |
|
return; |
|
} |
|
const lines = value.split('\n'); |
|
const text = lines.pop(); |
|
const blocks = lines.map(line => { |
|
const block = this.scroll.create(Block.blotName); |
|
block.insertAt(0, line); |
|
return block; |
|
}); |
|
const ref = this.split(index); |
|
blocks.forEach(block => { |
|
this.parent.insertBefore(block, ref); |
|
}); |
|
if (text) { |
|
this.parent.insertBefore(this.scroll.create('text', text), ref); |
|
} |
|
} |
|
} |
|
BlockEmbed.scope = Scope.BLOCK_BLOT; |
|
// It is important for cursor behavior BlockEmbeds use tags that are block level elements |
|
|
|
function blockDelta(blot) { |
|
let filter = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true; |
|
return blot.descendants(LeafBlot).reduce((delta, leaf) => { |
|
if (leaf.length() === 0) { |
|
return delta; |
|
} |
|
return delta.insert(leaf.value(), bubbleFormats(leaf, {}, filter)); |
|
}, new Delta()).insert('\n', bubbleFormats(blot)); |
|
} |
|
function bubbleFormats(blot) { |
|
let formats = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; |
|
let filter = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true; |
|
if (blot == null) return formats; |
|
if ('formats' in blot && typeof blot.formats === 'function') { |
|
formats = { |
|
...formats, |
|
...blot.formats() |
|
}; |
|
if (filter) { |
|
// exclude syntax highlighting from deltas and getFormat() |
|
delete formats['code-token']; |
|
} |
|
} |
|
if (blot.parent == null || blot.parent.statics.blotName === 'scroll' || blot.parent.statics.scope !== blot.statics.scope) { |
|
return formats; |
|
} |
|
return bubbleFormats(blot.parent, formats, filter); |
|
} |
|
export { blockDelta, bubbleFormats, BlockEmbed, Block as default }; |
|
//# sourceMappingURL=block.js.map
|