NeahNew/node_modules/parchment/src/registry.ts
2025-05-03 14:17:46 +02:00

156 lines
4.9 KiB
TypeScript

import Attributor from './attributor/attributor.js';
import {
type Blot,
type BlotConstructor,
type Root,
} from './blot/abstract/blot.js';
import ParchmentError from './error.js';
import Scope from './scope.js';
export type RegistryDefinition = Attributor | BlotConstructor;
export interface RegistryInterface {
create(scroll: Root, input: Node | string | Scope, value?: any): Blot;
query(query: string | Node | Scope, scope: Scope): RegistryDefinition | null;
register(...definitions: any[]): any;
}
export default class Registry implements RegistryInterface {
public static blots = new WeakMap<Node, Blot>();
public static find(node?: Node | null, bubble = false): Blot | null {
if (node == null) {
return null;
}
if (this.blots.has(node)) {
return this.blots.get(node) || null;
}
if (bubble) {
let parentNode: Node | null = null;
try {
parentNode = node.parentNode;
} catch (err) {
// Probably hit a permission denied error.
// A known case is in Firefox, event targets can be anonymous DIVs
// inside an input element.
// https://bugzilla.mozilla.org/show_bug.cgi?id=208427
return null;
}
return this.find(parentNode, bubble);
}
return null;
}
private attributes: { [key: string]: Attributor } = {};
private classes: { [key: string]: BlotConstructor } = {};
private tags: { [key: string]: BlotConstructor } = {};
private types: { [key: string]: RegistryDefinition } = {};
public create(scroll: Root, input: Node | string | Scope, value?: any): Blot {
const match = this.query(input);
if (match == null) {
throw new ParchmentError(`Unable to create ${input} blot`);
}
const blotClass = match as BlotConstructor;
const node =
// @ts-expect-error Fix me later
input instanceof Node || input.nodeType === Node.TEXT_NODE
? input
: blotClass.create(value);
const blot = new blotClass(scroll, node as Node, value);
Registry.blots.set(blot.domNode, blot);
return blot;
}
public find(node: Node | null, bubble = false): Blot | null {
return Registry.find(node, bubble);
}
public query(
query: string | Node | Scope,
scope: Scope = Scope.ANY,
): RegistryDefinition | null {
let match;
if (typeof query === 'string') {
match = this.types[query] || this.attributes[query];
// @ts-expect-error Fix me later
} else if (query instanceof Text || query.nodeType === Node.TEXT_NODE) {
match = this.types.text;
} else if (typeof query === 'number') {
if (query & Scope.LEVEL & Scope.BLOCK) {
match = this.types.block;
} else if (query & Scope.LEVEL & Scope.INLINE) {
match = this.types.inline;
}
} else if (query instanceof Element) {
const names = (query.getAttribute('class') || '').split(/\s+/);
names.some((name) => {
match = this.classes[name];
if (match) {
return true;
}
return false;
});
match = match || this.tags[query.tagName];
}
if (match == null) {
return null;
}
if (
'scope' in match &&
scope & Scope.LEVEL & match.scope &&
scope & Scope.TYPE & match.scope
) {
return match;
}
return null;
}
public register(...definitions: RegistryDefinition[]): RegistryDefinition[] {
return definitions.map((definition) => {
const isBlot = 'blotName' in definition;
const isAttr = 'attrName' in definition;
if (!isBlot && !isAttr) {
throw new ParchmentError('Invalid definition');
} else if (isBlot && definition.blotName === 'abstract') {
throw new ParchmentError('Cannot register abstract class');
}
const key = isBlot
? definition.blotName
: isAttr
? definition.attrName
: (undefined as never); // already handled by above checks
this.types[key] = definition;
if (isAttr) {
if (typeof definition.keyName === 'string') {
this.attributes[definition.keyName] = definition;
}
} else if (isBlot) {
if (definition.className) {
this.classes[definition.className] = definition;
}
if (definition.tagName) {
if (Array.isArray(definition.tagName)) {
definition.tagName = definition.tagName.map((tagName: string) => {
return tagName.toUpperCase();
});
} else {
definition.tagName = definition.tagName.toUpperCase();
}
const tagNames = Array.isArray(definition.tagName)
? definition.tagName
: [definition.tagName];
tagNames.forEach((tag: string) => {
if (this.tags[tag] == null || definition.className == null) {
this.tags[tag] = definition;
}
});
}
}
return definition;
});
}
}