123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- import Delta from 'quill-delta';
- import {
- AttributorStore,
- BlockBlot,
- EmbedBlot,
- LeafBlot,
- Scope,
- } from 'parchment';
- import Break from './break';
- import Inline from './inline';
- import TextBlot from './text';
- const NEWLINE_LENGTH = 1;
- class Block extends BlockBlot {
- constructor(scroll, domNode) {
- super(scroll, domNode);
- this.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 = {};
- }
- let block = this;
- lines.reduce((lineIndex, line) => {
- 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, force = 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) {
- this.attributes.attribute(attribute, value);
- }
- }
- formatAt(index, length, name, value) {
- this.format(name, value);
- }
- insertAt(index, value, def) {
- if (typeof value === 'string' && value.endsWith('\n')) {
- const block = this.scroll.create(Block.blotName);
- this.parent.insertBefore(block, index === 0 ? this : this.next);
- block.insertAt(0, value.slice(0, -1));
- } else {
- super.insertAt(index, value, def);
- }
- }
- }
- BlockEmbed.scope = Scope.BLOCK_BLOT;
- // It is important for cursor behavior BlockEmbeds use tags that are block level elements
- function blockDelta(blot, filter = 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, formats = {}, filter = true) {
- if (blot == null) return formats;
- if (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 };
|