import Attributor from './attributor/attributor'; import { Blot, Formattable } from './blot/abstract/blot'; export interface BlotConstructor { blotName: string; new (node: Node, value?: any): Blot; create(value?: any): Node; } export class ParchmentError extends Error { message: string; name: string; stack!: string; constructor(message: string) { message = '[Parchment] ' + message; super(message); this.message = message; this.name = (this.constructor).name; } } let attributes: { [key: string]: Attributor } = {}; let classes: { [key: string]: BlotConstructor } = {}; let tags: { [key: string]: BlotConstructor } = {}; let types: { [key: string]: Attributor | BlotConstructor } = {}; export const DATA_KEY = '__blot'; export enum Scope { TYPE = (1 << 2) - 1, // 0011 Lower two bits LEVEL = ((1 << 2) - 1) << 2, // 1100 Higher two bits ATTRIBUTE = (1 << 0) | LEVEL, // 1101 BLOT = (1 << 1) | LEVEL, // 1110 INLINE = (1 << 2) | TYPE, // 0111 BLOCK = (1 << 3) | TYPE, // 1011 BLOCK_BLOT = BLOCK & BLOT, // 1010 INLINE_BLOT = INLINE & BLOT, // 0110 BLOCK_ATTRIBUTE = BLOCK & ATTRIBUTE, // 1001 INLINE_ATTRIBUTE = INLINE & ATTRIBUTE, // 0101 ANY = TYPE | LEVEL, } export function create(input: Node | string | Scope, value?: any): Blot { let match = query(input); if (match == null) { throw new ParchmentError(`Unable to create ${input} blot`); } let BlotClass = match; let node = // @ts-ignore input instanceof Node || input['nodeType'] === Node.TEXT_NODE ? input : BlotClass.create(value); return new BlotClass(node, value); } export function find(node: Node | null, bubble: boolean = false): Blot | null { if (node == null) return null; // @ts-ignore if (node[DATA_KEY] != null) return node[DATA_KEY].blot; if (bubble) return find(node.parentNode, bubble); return null; } export function query( query: string | Node | Scope, scope: Scope = Scope.ANY, ): Attributor | BlotConstructor | null { let match; if (typeof query === 'string') { match = types[query] || attributes[query]; // @ts-ignore } else if (query instanceof Text || query['nodeType'] === Node.TEXT_NODE) { match = types['text']; } else if (typeof query === 'number') { if (query & Scope.LEVEL & Scope.BLOCK) { match = types['block']; } else if (query & Scope.LEVEL & Scope.INLINE) { match = types['inline']; } } else if (query instanceof HTMLElement) { let names = (query.getAttribute('class') || '').split(/\s+/); for (let i in names) { match = classes[names[i]]; if (match) break; } match = match || tags[query.tagName]; } if (match == null) return null; // @ts-ignore if (scope & Scope.LEVEL & match.scope && scope & Scope.TYPE & match.scope) return match; return null; } export function register(...Definitions: any[]): any { if (Definitions.length > 1) { return Definitions.map(function(d) { return register(d); }); } let Definition = Definitions[0]; if (typeof Definition.blotName !== 'string' && typeof Definition.attrName !== 'string') { throw new ParchmentError('Invalid definition'); } else if (Definition.blotName === 'abstract') { throw new ParchmentError('Cannot register abstract class'); } types[Definition.blotName || Definition.attrName] = Definition; if (typeof Definition.keyName === 'string') { attributes[Definition.keyName] = Definition; } else { if (Definition.className != null) { classes[Definition.className] = Definition; } if (Definition.tagName != null) { if (Array.isArray(Definition.tagName)) { Definition.tagName = Definition.tagName.map(function(tagName: string) { return tagName.toUpperCase(); }); } else { Definition.tagName = Definition.tagName.toUpperCase(); } let tagNames = Array.isArray(Definition.tagName) ? Definition.tagName : [Definition.tagName]; tagNames.forEach(function(tag: string) { if (tags[tag] == null || Definition.className == null) { tags[tag] = Definition; } }); } } return Definition; }