Your IP : 3.147.126.199


Current Path : /var/www/www-root/data/www/monolith-realty.ru/bitrix/js/ui/bbcode/model/src/scheme/
Upload File :
Current File : /var/www/www-root/data/www/monolith-realty.ru/bitrix/js/ui/bbcode/model/src/scheme/bbcode-scheme.js

import { Type } from 'main.core';
import { BBCodeEncoder } from 'ui.bbcode.encoder';
import { BBCodeTagScheme } from './node-schemes/tag-scheme';
import { BBCodeNode, type BBCodeNodeOptions } from '../nodes/node';
import { BBCodeRootNode, type RootNodeOptions } from '../nodes/root-node';
import { BBCodeFragmentNode, type FragmentNodeOptions } from '../nodes/fragment-node';
import { BBCodeElementNode, type BBCodeElementNodeOptions } from '../nodes/element-node';
import { BBCodeTextNode, type BBCodeTextNodeOptions } from '../nodes/text-node';
import { BBCodeNewLineNode } from '../nodes/new-line-node';
import { BBCodeTabNode } from '../nodes/tab-node';
import { BBCodeNodeScheme } from './node-schemes/node-scheme';
import type { BBCodeGroupName, BBCodeNodeName } from './node-schemes/node-scheme';

export type OutputTagCases = $Values<BBCodeScheme.Case>;

export type ParentChildMap = Map<
	BBCodeNodeName | BBCodeGroupName,
	{
		allowedChildren: Array<BBCodeNodeName | BBCodeGroupName>,
		allowedIn: Array<BBCodeNodeName | BBCodeGroupName>,
		aliases: Array<BBCodeNodeName | BBCodeGroupName>,
	},
>;

export type BBCodeSchemeOptions = {
	tagSchemes: Array<BBCodeTagScheme>,
	outputTagCase?: OutputTagCases,
	unresolvedNodesHoisting?: boolean,
	encoder?: BBCodeEncoder,
};

export class BBCodeScheme
{
	static Case: {[key: string]: string} = {
		LOWER: 'lower',
		UPPER: 'upper',
	};

	tagSchemes: Array<BBCodeTagScheme> = [];
	outputTagCase: OutputTagCases = BBCodeScheme.Case.LOWER;
	unresolvedNodesHoisting: boolean = true;
	encoder: BBCodeEncoder = new BBCodeEncoder();

	static isNodeScheme(value: any): boolean
	{
		return value instanceof BBCodeNodeScheme;
	}

	static getTagName(node: string | BBCodeNode): string | null
	{
		if (Type.isString(node))
		{
			return node;
		}

		if (Type.isObject(node) && node instanceof BBCodeNode)
		{
			return node.getName();
		}

		return null;
	}

	constructor(options: BBCodeSchemeOptions = {})
	{
		if (!Type.isPlainObject(options))
		{
			throw new TypeError('options is not a object');
		}

		this.setTagSchemes(options.tagSchemes);
		this.setOutputTagCase(options.outputTagCase);
		this.setUnresolvedNodesHoisting(options.unresolvedNodesHoisting);
		this.setEncoder(options.encoder);
	}

	setTagSchemes(tagSchemes: Array<BBCodeTagScheme>)
	{
		if (Type.isArray(tagSchemes))
		{
			const invalidSchemeIndex: number = tagSchemes.findIndex((scheme: BBCodeTagScheme): boolean => {
				return !BBCodeScheme.isNodeScheme(scheme);
			});

			if (invalidSchemeIndex > -1)
			{
				throw new TypeError(`tagScheme #${invalidSchemeIndex} is not TagScheme instance`);
			}

			this.tagSchemes = [...tagSchemes];
		}
	}

	setTagScheme(...tagSchemes: Array<BBCodeTagScheme>)
	{
		const invalidSchemeIndex: number = tagSchemes.findIndex((scheme: BBCodeTagScheme): boolean => {
			return !BBCodeScheme.isNodeScheme(scheme);
		});

		if (invalidSchemeIndex > -1)
		{
			throw new TypeError(`tagScheme #${invalidSchemeIndex} is not TagScheme instance`);
		}

		const newTagSchemesNames: Array<string> = tagSchemes.flatMap((scheme: BBCodeTagScheme) => {
			return scheme.getName();
		});

		const currentTagSchemes: Array<BBCodeTagScheme> = this.getTagSchemes();
		currentTagSchemes.forEach((scheme: BBCodeTagScheme) => {
			scheme.removeName(...newTagSchemesNames);
		});

		const filteredCurrentTagSchemes: Array<BBCodeTagScheme> = currentTagSchemes.filter((scheme: BBCodeTagScheme) => {
			return Type.isArrayFilled(scheme.getName());
		});

		this.setTagSchemes([
			...filteredCurrentTagSchemes,
			...tagSchemes,
		]);
	}

	getTagSchemes(): Array<BBCodeTagScheme>
	{
		return [...this.tagSchemes];
	}

	getTagScheme(node: string | BBCodeNode): ?BBCodeTagScheme
	{
		const tagName: ?string = BBCodeScheme.getTagName(node);
		if (Type.isString(tagName))
		{
			return this.getTagSchemes().find((scheme: BBCodeTagScheme): boolean => {
				return scheme.getName().includes(tagName.toLowerCase());
			});
		}

		return null;
	}

	setOutputTagCase(tagCase: $Values<BBCodeScheme.Case>)
	{
		if (!Type.isNil(tagCase))
		{
			const allowedCases = Object.values(BBCodeScheme.Case);
			if (allowedCases.includes(tagCase))
			{
				this.outputTagCase = tagCase;
			}
			else
			{
				throw new TypeError(`'${tagCase}' is not allowed`);
			}
		}
	}

	getOutputTagCase(): $Values<BBCodeScheme.Case>
	{
		return this.outputTagCase;
	}

	setUnresolvedNodesHoisting(value: boolean)
	{
		if (!Type.isNil(value))
		{
			if (Type.isBoolean(value))
			{
				this.unresolvedNodesHoisting = value;
			}
			else
			{
				throw new TypeError(`'${value}' is not allowed value`);
			}
		}
	}

	isAllowedUnresolvedNodesHoisting(): boolean
	{
		return this.unresolvedNodesHoisting;
	}

	setEncoder(encoder: BBCodeEncoder)
	{
		if (encoder instanceof BBCodeEncoder)
		{
			this.encoder = encoder;
		}
	}

	getEncoder(): BBCodeEncoder
	{
		return this.encoder;
	}

	getAllowedTags(): Array<string>
	{
		return this.getTagSchemes().flatMap((tagScheme: BBCodeTagScheme) => {
			return tagScheme.getName();
		});
	}

	isAllowedTag(node: string | BBCodeNode): boolean
	{
		const allowedTags: Array<string> = this.getAllowedTags();
		const tagName: ?string = BBCodeScheme.getTagName(node);

		return allowedTags.includes(String(tagName).toLowerCase());
	}

	isVoid(node: string | BBCodeNode): boolean
	{
		const tagScheme: ?BBCodeTagScheme = this.getTagScheme(node);
		if (tagScheme)
		{
			return tagScheme.isVoid();
		}

		return false;
	}

	isElement(node: BBCodeNode): boolean
	{
		return node && node.getType() === BBCodeNode.ELEMENT_NODE;
	}

	isRoot(node: BBCodeNode): boolean
	{
		return node && node.getName() === '#root';
	}

	isFragment(node: BBCodeNode): boolean
	{
		return node && node.getName() === '#fragment';
	}

	isAnyText(node: BBCodeNode): boolean
	{
		return node && node.getType() === BBCodeNode.TEXT_NODE;
	}

	isText(node: BBCodeNode): boolean
	{
		return node && node.getName() === '#text';
	}

	isNewLine(node: BBCodeNode): boolean
	{
		return node && node.getName() === '#linebreak';
	}

	isTab(node: BBCodeNode): boolean
	{
		return node && node.getName() === '#tab';
	}

	getParentChildMap(): ParentChildMap
	{
		const tagSchemes: Array<BBCodeTagScheme> = this.getTagSchemes();
		const map = new Map();

		tagSchemes.forEach((tagScheme: BBCodeTagScheme) => {
			const groups: Array<BBCodeGroupName> = tagScheme.getGroup();
			const schemeNames: Array<string> = [
				...tagScheme.getName(),
				...groups,
				...(tagScheme.isVoid() ? ['#void'] : []),
			];

			const allowedChildren = tagScheme.getAllowedChildren();
			const allowedIn = tagScheme.getAllowedIn();

			schemeNames.forEach((name) => {
				if (!map.has(name))
				{
					map.set(
						name,
						{
							allowedChildren: new Set(),
							allowedIn: new Set(),
							aliases: new Set(),
						},
					);
				}

				const entry: {
					allowedChildren: Set,
					allowedIn: Set,
					aliases: Set,
				} = map.get(name);

				const newEntry = {
					allowedChildren: new Set([...entry.allowedChildren, ...allowedChildren]),
					allowedIn: new Set([...entry.allowedIn, ...allowedIn]),
					aliases: new Set([name, ...groups, ...(tagScheme.isVoid() ? ['#void'] : [])]),
				};

				map.set(name, newEntry);
			});
		});

		return map;
	}

	isChildAllowed(parent: string | BBCodeNode, child: string | BBCodeNode): boolean
	{
		const parentName: ?string = BBCodeScheme.getTagName(parent);
		const childName: ?string = BBCodeScheme.getTagName(child);

		if (
			Type.isStringFilled(parentName)
			&& Type.isStringFilled(childName)
		)
		{
			if (parentName === '#fragment')
			{
				return true;
			}

			const parentChildMap = this.getParentChildMap();
			const parentMap = parentChildMap.get(parentName);
			const childMap = parentChildMap.get(childName);

			if (
				Type.isPlainObject(parentMap)
				&& Type.isPlainObject(childMap)
			)
			{
				return (
					(
						parentMap.allowedChildren.size === 0
						|| [...childMap.aliases].some((name) => {
							return parentMap.allowedChildren.has(name);
						})
					)
					&& (
						childMap.allowedIn.size === 0
						|| [...parentMap.aliases].some((name) => {
							return childMap.allowedIn.has(name);
						})
					)
				);
			}
		}

		return false;
	}

	createRoot(options: RootNodeOptions = {}): BBCodeRootNode
	{
		return new BBCodeRootNode({
			...options,
			scheme: this,
		});
	}

	createNode(options: BBCodeNodeOptions): BBCodeNode
	{
		if (!Type.isPlainObject(options))
		{
			throw new TypeError('options is not a object');
		}

		if (!Type.isStringFilled(options.name))
		{
			throw new TypeError('options.name is required');
		}

		if (!this.isAllowedTag(options.name))
		{
			throw new TypeError(`Scheme for "${options.name}" tag is not specified.`);
		}

		return new BBCodeNode({
			...options,
			scheme: this,
		});
	}

	createElement(options: BBCodeElementNodeOptions = {}): BBCodeElementNode
	{
		if (!Type.isPlainObject(options))
		{
			throw new TypeError('options is not a object');
		}

		if (!Type.isStringFilled(options.name))
		{
			throw new TypeError('options.name is required');
		}

		if (!this.isAllowedTag(options.name))
		{
			throw new TypeError(`Scheme for "${options.name}" tag is not specified.`);
		}

		return new BBCodeElementNode({
			...options,
			scheme: this,
		});
	}

	createText(options: BBCodeTextNodeOptions = {}): BBCodeTextNode
	{
		const preparedOptions = Type.isPlainObject(options) ? options : { content: options };

		return new BBCodeTextNode({
			...preparedOptions,
			scheme: this,
		});
	}

	createNewLine(options: BBCodeTextNodeOptions = {}): BBCodeNewLineNode
	{
		const preparedOptions = Type.isPlainObject(options) ? options : { content: options };

		return new BBCodeNewLineNode({
			...preparedOptions,
			scheme: this,
		});
	}

	createTab(options: BBCodeTextNodeOptions = {}): BBCodeTabNode
	{
		const preparedOptions = Type.isPlainObject(options) ? options : { content: options };

		return new BBCodeTabNode({
			...preparedOptions,
			scheme: this,
		});
	}

	createFragment(options: FragmentNodeOptions = {}): BBCodeFragmentNode
	{
		return new BBCodeFragmentNode({
			...options,
			scheme: this,
		});
	}
}