Current Path : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/js/ui/bbcode/formatter/src/ |
Current File : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/js/ui/bbcode/formatter/src/formatter.js |
import { Type } from 'main.core'; import { BBCodeFragmentNode, BBCodeNode, BBCodeScheme } from 'ui.bbcode.model'; import { BBCodeParser } from 'ui.bbcode.parser'; import { NodeFormatter, type BeforeConvertCallbackOptions } from './node-formatter'; export * from './node-formatter'; export type FormatterData = { [key: string]: any, }; export type UnknownNodeCallbackOptions = { node: BBCodeNode, formatter: Formatter, data: FormatterData, }; export type FormatterOptions = { formatters: Array<NodeFormatter>, onUnknown: (UnknownNodeCallbackOptions) => BBCodeNode | null, }; export type FormatterFormatOptions = { source: BBCodeNode | string, data?: FormatterData, }; const formattersSymbol: Symbol = Symbol('formatters'); const onUnknownSymbol: Symbol = Symbol('onUnknown'); /** * @memberOf BX.UI.BBCode */ export class Formatter { [formattersSymbol]: Map<any, any> = new Map(); [onUnknownSymbol]: (UnknownNodeCallbackOptions) => NodeFormatter | null = null; constructor(options: FormatterOptions = {}) { this.setNodeFormatters(options.formatters); if (Type.isNil(options.onUnknown)) { this.setOnUnknown(this.getDefaultUnknownNodeCallback()); } else { this.setOnUnknown(options.onUnknown); } } static prepareSourceNode(source: BBCodeNode | string): BBCodeNode | null { if (source instanceof BBCodeNode) { return source; } if (Type.isString(source)) { return (new BBCodeParser()).parse(source); } return null; } setNodeFormatters(formatters: Array<NodeFormatter>) { if (Type.isArrayFilled(formatters)) { formatters.forEach((formatter: NodeFormatter) => { this.setNodeFormatter(formatter); }); } } setNodeFormatter(formatter: NodeFormatter) { if (formatter instanceof NodeFormatter) { this[formattersSymbol].set(formatter.getName(), formatter); } else { throw new TypeError('formatter is not a NodeFormatter instance.'); } } getDefaultUnknownNodeCallback(): (UnknownNodeCallbackOptions) => NodeFormatter | null { return () => { return new NodeFormatter({ name: 'unknown', before({ node }: BeforeConvertCallbackOptions): BBCodeFragmentNode { const scheme: BBCodeScheme = node.getScheme(); if (node.isVoid()) { return scheme.createFragment({ children: [ scheme.createText(node.getOpeningTag()), ], }); } return scheme.createFragment({ children: [ scheme.createText(node.getOpeningTag()), ...node.getChildren(), scheme.createText(node.getClosingTag()), ], }); }, convert(): DocumentFragment { return document.createDocumentFragment(); }, }); }; } setOnUnknown(callback: (UnknownNodeCallbackOptions) => NodeFormatter | null) { if (Type.isFunction(callback)) { this[onUnknownSymbol] = callback; } else { throw new TypeError('OnUnknown callback is not a function.'); } } runOnUnknown(options: UnknownNodeCallbackOptions): NodeFormatter | null { const result: NodeFormatter | null = this[onUnknownSymbol](options); if (result instanceof NodeFormatter || Type.isNull(result)) { return result; } throw new TypeError('OnUnknown callback returned not NodeFormatter instance or null.'); } getNodeFormatter(node: BBCodeNode): NodeFormatter | null { const formatter: ?NodeFormatter = this[formattersSymbol].get(node.getName()); if (formatter instanceof NodeFormatter) { return formatter; } return this.runOnUnknown({ node, formatter: this }); } format(options: FormatterFormatOptions): DocumentFragment | HTMLElement | Text | null { if (!Type.isPlainObject(options)) { throw new TypeError('options is not a object'); } const { source, data = {} } = options; if (!Type.isUndefined(data) && !Type.isPlainObject(data)) { throw new TypeError('options.data is not a object'); } const sourceNode: ?BBCodeNode = Formatter.prepareSourceNode(source); if (Type.isNull(sourceNode)) { throw new TypeError('options.source is not a BBCodeNode or string'); } const nodeFormatter: NodeFormatter = this.getNodeFormatter(sourceNode); const isValidNode: boolean = nodeFormatter.validate({ node: sourceNode, formatter: this, data, }); if (!isValidNode) { return null; } const preparedNode: ?BBCodeNode = nodeFormatter.runBefore({ node: sourceNode, formatter: this, data, }); if (Type.isNull(preparedNode)) { return null; } const convertedElement: ?HTMLElement = nodeFormatter.runConvert({ node: preparedNode, formatter: this, data, }); if (Type.isNull(convertedElement)) { return null; } preparedNode.getChildren().forEach((childNode: BBCodeNode) => { const childElement: ?HTMLElement = this.format({ source: childNode, data }); if (Type.isDomNode(childElement)) { const convertedChildElement: ?HTMLElement = nodeFormatter.runForChild({ node: childNode, element: childElement, formatter: this, data, }); if (Type.isDomNode(convertedChildElement)) { convertedElement.appendChild(convertedChildElement); } } }); return nodeFormatter.runAfter({ node: preparedNode, element: convertedElement, formatter: this, data, }); } }