UNPKG

typewriter-editor

Version:

A rich text editor using the Delta format with decorations and rendered with a tiny virtual dom

118 lines (117 loc) 3.91 kB
import { AttributeMap, Line } from '@typewriter/document'; import { Editor } from '../Editor'; const EMPTY_ARR = []; const lineTypes = {}; const formatTypes = {}; const embedTypes = {}; const shouldCombine = (prev, next) => true; export class Typeset { lines; formats; embeds; static line = line; static format = format; static embed = embed; constructor(types) { const lines = types.lines?.map(entry => (typeof entry === 'string' ? lineTypes[entry] : entry)).filter(Boolean); const formats = types.formats ?.map(entry => (typeof entry === 'string' ? formatTypes[entry] : entry)) .filter(Boolean); const embeds = types.embeds?.map(entry => (typeof entry === 'string' ? embedTypes[entry] : entry)).filter(Boolean); this.lines = new Types(lines || EMPTY_ARR); this.formats = new Types(formats || EMPTY_ARR); this.embeds = new Types(embeds || EMPTY_ARR); } } export function line(type) { if (type.renderMultiple && !type.shouldCombine) type.shouldCombine = shouldCombine; return (lineTypes[type.name] = type); } export function format(type) { return (formatTypes[type.name] = type); } export function embed(type) { return (embedTypes[type.name] = type); } /** * A type store to hold types and make it easy to manage them. */ export class Types { // An array of the types list; // A selector which will match all nodes of this type (e.g. all lines) selector; // A map of all types by name types; // A reverse lookup of priority by type name priorities; constructor(types) { this.list = types; this.init(); } get default() { return this.list[0]; } init() { this.selector = this.list .map(type => type.selector || '') .filter(Boolean) .join(', '); this.types = this.list.reduce((types, type) => { types[type.name] = type; return types; }, {}); this.priorities = this.list.reduce((priorities, type, i) => { priorities[type.name] = i; return priorities; }, {}); } add(type) { this.list.push(type); this.init(); } remove(type) { const name = typeof type === 'string' ? type : type.name; this.list = this.list.filter(type => type.name !== name); this.init(); } get(name) { return this.types[name]; } priority(name) { // Attribute keys that do not have types assigned to them need a default sorting value. // A default value of -1 means that "loose" attribute keys do not corrupt priority sorting // and are sorted to the back of the list in rendering.ts::renderInline() const priority = this.priorities[name]; return priority !== undefined ? priority : -1; } // Whether or not the provided element is one of our types matches(node) { if (!node) return false; if (!node.nodeType) throw new Error('Cannot match against ' + node); if (node.nodeType === Node.ELEMENT_NODE) { return this.selector ? node.matches(this.selector) : false; } } findByNode(node, fallbackToDefault = false) { if (node.nodeType !== Node.ELEMENT_NODE) return; let i = this.list.length; while (i--) { let type = this.list[i]; if (node.matches(type.selector)) return type; } if (fallbackToDefault) return this.default; } findByAttributes(attributes, fallbackToDefault = false) { const keys = attributes && Object.keys(attributes); let type; keys && keys.every(name => !(type = this.get(name))); return type || (fallbackToDefault ? this.default : undefined); } }