189 lines
4.8 KiB
TypeScript
189 lines
4.8 KiB
TypeScript
import ParchmentError from '../../error.js';
|
|
import Registry from '../../registry.js';
|
|
import Scope from '../../scope.js';
|
|
import type {
|
|
Blot,
|
|
BlotConstructor,
|
|
Formattable,
|
|
Parent,
|
|
Root,
|
|
} from './blot.js';
|
|
|
|
class ShadowBlot implements Blot {
|
|
public static blotName = 'abstract';
|
|
public static className: string;
|
|
public static requiredContainer: BlotConstructor;
|
|
public static scope: Scope;
|
|
public static tagName: string | string[];
|
|
|
|
public static create(rawValue?: unknown): Node {
|
|
if (this.tagName == null) {
|
|
throw new ParchmentError('Blot definition missing tagName');
|
|
}
|
|
let node: HTMLElement;
|
|
let value: string | number | undefined;
|
|
if (Array.isArray(this.tagName)) {
|
|
if (typeof rawValue === 'string') {
|
|
value = rawValue.toUpperCase();
|
|
if (parseInt(value, 10).toString() === value) {
|
|
value = parseInt(value, 10);
|
|
}
|
|
} else if (typeof rawValue === 'number') {
|
|
value = rawValue;
|
|
}
|
|
if (typeof value === 'number') {
|
|
node = document.createElement(this.tagName[value - 1]);
|
|
} else if (value && this.tagName.indexOf(value) > -1) {
|
|
node = document.createElement(value);
|
|
} else {
|
|
node = document.createElement(this.tagName[0]);
|
|
}
|
|
} else {
|
|
node = document.createElement(this.tagName);
|
|
}
|
|
if (this.className) {
|
|
node.classList.add(this.className);
|
|
}
|
|
return node;
|
|
}
|
|
|
|
public prev: Blot | null;
|
|
public next: Blot | null;
|
|
// @ts-expect-error Fix me later
|
|
public parent: Parent;
|
|
|
|
// Hack for accessing inherited static methods
|
|
get statics(): any {
|
|
return this.constructor;
|
|
}
|
|
constructor(
|
|
public scroll: Root,
|
|
public domNode: Node,
|
|
) {
|
|
Registry.blots.set(domNode, this);
|
|
this.prev = null;
|
|
this.next = null;
|
|
}
|
|
|
|
public attach(): void {
|
|
// Nothing to do
|
|
}
|
|
|
|
public clone(): Blot {
|
|
const domNode = this.domNode.cloneNode(false);
|
|
return this.scroll.create(domNode);
|
|
}
|
|
|
|
public detach(): void {
|
|
if (this.parent != null) {
|
|
this.parent.removeChild(this);
|
|
}
|
|
Registry.blots.delete(this.domNode);
|
|
}
|
|
|
|
public deleteAt(index: number, length: number): void {
|
|
const blot = this.isolate(index, length);
|
|
blot.remove();
|
|
}
|
|
|
|
public formatAt(
|
|
index: number,
|
|
length: number,
|
|
name: string,
|
|
value: any,
|
|
): void {
|
|
const blot = this.isolate(index, length);
|
|
if (this.scroll.query(name, Scope.BLOT) != null && value) {
|
|
blot.wrap(name, value);
|
|
} else if (this.scroll.query(name, Scope.ATTRIBUTE) != null) {
|
|
const parent = this.scroll.create(this.statics.scope) as Parent &
|
|
Formattable;
|
|
blot.wrap(parent);
|
|
parent.format(name, value);
|
|
}
|
|
}
|
|
|
|
public insertAt(index: number, value: string, def?: any): void {
|
|
const blot =
|
|
def == null
|
|
? this.scroll.create('text', value)
|
|
: this.scroll.create(value, def);
|
|
const ref = this.split(index);
|
|
this.parent.insertBefore(blot, ref || undefined);
|
|
}
|
|
|
|
public isolate(index: number, length: number): Blot {
|
|
const target = this.split(index);
|
|
if (target == null) {
|
|
throw new Error('Attempt to isolate at end');
|
|
}
|
|
target.split(length);
|
|
return target;
|
|
}
|
|
|
|
public length(): number {
|
|
return 1;
|
|
}
|
|
|
|
public offset(root: Blot = this.parent): number {
|
|
if (this.parent == null || this === root) {
|
|
return 0;
|
|
}
|
|
return this.parent.children.offset(this) + this.parent.offset(root);
|
|
}
|
|
|
|
public optimize(_context?: { [key: string]: any }): void {
|
|
if (
|
|
this.statics.requiredContainer &&
|
|
!(this.parent instanceof this.statics.requiredContainer)
|
|
) {
|
|
this.wrap(this.statics.requiredContainer.blotName);
|
|
}
|
|
}
|
|
|
|
public remove(): void {
|
|
if (this.domNode.parentNode != null) {
|
|
this.domNode.parentNode.removeChild(this.domNode);
|
|
}
|
|
this.detach();
|
|
}
|
|
|
|
public replaceWith(name: string | Blot, value?: any): Blot {
|
|
const replacement =
|
|
typeof name === 'string' ? this.scroll.create(name, value) : name;
|
|
if (this.parent != null) {
|
|
this.parent.insertBefore(replacement, this.next || undefined);
|
|
this.remove();
|
|
}
|
|
return replacement;
|
|
}
|
|
|
|
public split(index: number, _force?: boolean): Blot | null {
|
|
return index === 0 ? this : this.next;
|
|
}
|
|
|
|
public update(
|
|
_mutations: MutationRecord[],
|
|
_context: { [key: string]: any },
|
|
): void {
|
|
// Nothing to do by default
|
|
}
|
|
|
|
public wrap(name: string | Parent, value?: any): Parent {
|
|
const wrapper =
|
|
typeof name === 'string'
|
|
? (this.scroll.create(name, value) as Parent)
|
|
: name;
|
|
if (this.parent != null) {
|
|
this.parent.insertBefore(wrapper, this.next || undefined);
|
|
}
|
|
if (typeof wrapper.appendChild !== 'function') {
|
|
throw new ParchmentError(`Cannot wrap ${name}`);
|
|
}
|
|
wrapper.appendChild(this);
|
|
return wrapper;
|
|
}
|
|
}
|
|
|
|
export default ShadowBlot;
|