Your IP : 3.145.65.133


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

import { Type, type JsonObject, type JsonValue } from 'main.core';
import { BBCodeNode, type BBCodeNodeOptions, type BBCodeContentNode, type SerializedBBCodeNode, privateMap } from './node';
import { typeof BBCodeFragmentNode } from './fragment-node';
import { type BBCodeNodeStringifier } from '../scheme/node-schemes/node-scheme';
import { typeof BBCodeTagScheme } from '../scheme/node-schemes/tag-scheme';
import { typeof BBCodeScheme } from '../scheme/bbcode-scheme';

export type BBCodeElementNodeValue = string | number | boolean;

export type BBCodeElementNodeOptions = BBCodeNodeOptions & {
	attributes?: JsonObject,
	value?: BBCodeElementNodeValue,
};

export type SerializedBBCodeElementNode = SerializedBBCodeNode & {
	attributes: JsonObject,
	value: BBCodeElementNodeValue,
};

export type FilteredChildren = {
	resolved: Array<BBCodeContentNode>,
	unresolved: Array<BBCodeContentNode>,
};

const voidSymbol: Symbol = Symbol('void');

export class BBCodeElementNode extends BBCodeNode
{
	attributes: JsonObject = {};
	value: JsonValue = '';
	[voidSymbol]: boolean = false;

	constructor(options: BBCodeElementNodeOptions = {})
	{
		super(options);
		privateMap.get(this).type = BBCodeNode.ELEMENT_NODE;

		const tagScheme: BBCodeTagScheme = this.getTagScheme();

		this[voidSymbol] = tagScheme.isVoid();

		this.setValue(options.value);
		this.setAttributes(options.attributes);
	}

	setScheme(scheme: BBCodeScheme, onUnknown: (node: BBCodeContentNode) => any)
	{
		this.getChildren().forEach((node: BBCodeContentNode) => {
			node.setScheme(scheme, onUnknown);
		});

		if (scheme.isAllowedTag(this.getName()))
		{
			super.setScheme(scheme);

			const tagScheme: BBCodeTagScheme = this.getTagScheme();
			this[voidSymbol] = tagScheme.isVoid();
		}
		else
		{
			super.setScheme(scheme);
			onUnknown(this, scheme);
		}
	}

	filterChildren(children: Array<BBCodeContentNode>): FilteredChildren
	{
		const filteredChildren: FilteredChildren = {
			resolved: [],
			unresolved: [],
		};
		const scheme: BBCodeScheme = this.getScheme();

		children.forEach((child: BBCodeContentNode) => {
			if (scheme.isChildAllowed(this, child))
			{
				filteredChildren.resolved.push(child);
			}
			else
			{
				filteredChildren.unresolved.push(child);
			}
		});

		return filteredChildren;
	}

	convertChildren(children: Array<BBCodeContentNode>): Array<BBCodeContentNode>
	{
		const tagScheme: BBCodeTagScheme = this.getTagScheme();
		const childConverter = tagScheme.getChildConverter();
		if (childConverter)
		{
			const scheme: BBCodeScheme = this.getScheme();

			return children.map((child: BBCodeNode) => {
				return childConverter(child, scheme);
			});
		}

		return children;
	}

	setValue(value: BBCodeElementNodeValue)
	{
		if (Type.isString(value) || Type.isNumber(value) || Type.isBoolean(value))
		{
			this.value = value;
		}
	}

	getValue(): BBCodeElementNodeValue
	{
		return this.value;
	}

	isVoid(): boolean
	{
		return this[voidSymbol];
	}

	canBeEmpty(): boolean
	{
		return this.getTagScheme().canBeEmpty();
	}

	hasGroup(groupName: string): boolean
	{
		return this.getTagScheme().hasGroup(groupName);
	}

	setAttributes(attributes: JsonObject)
	{
		if (Type.isPlainObject(attributes))
		{
			const entries = Object.entries(attributes).map(([key, value]) => {
				return [key.toLowerCase(), value];
			});

			this.attributes = Object.fromEntries(entries);
		}
	}

	setAttribute(name: string, value: any)
	{
		if (Type.isStringFilled(name))
		{
			const preparedName: string = name.toLowerCase();
			if (Type.isNil(value))
			{
				delete this.attributes[preparedName];
			}
			else
			{
				this.attributes[preparedName] = value;
			}
		}
	}

	getAttribute(name: string): string | number | null
	{
		if (Type.isString(name))
		{
			return this.attributes[name.toLowerCase()];
		}

		return null;
	}

	getAttributes(): JsonObject
	{
		return { ...this.attributes };
	}

	appendChild(...children: Array<BBCodeContentNode | BBCodeFragmentNode>)
	{
		const flattenedChildren: Array<BBCodeContentNode> = BBCodeNode.flattenChildren(children);
		const filteredChildren: FilteredChildren = this.filterChildren(flattenedChildren);
		const convertedChildren: Array<BBCodeContentNode> = this.convertChildren(filteredChildren.resolved);

		convertedChildren.forEach((node: BBCodeContentNode) => {
			node.remove();
			node.setParent(this);
			this.children.push(node);
		});

		if (Type.isArrayFilled(filteredChildren.unresolved))
		{
			if (this.getScheme().isAllowedUnresolvedNodesHoisting())
			{
				this.propagateChild(...filteredChildren.unresolved);
			}
			else
			{
				filteredChildren.unresolved.forEach((node: BBCodeContentNode) => {
					node.remove();
				});
			}
		}
	}

	prependChild(...children: Array<BBCodeContentNode | BBCodeFragmentNode>)
	{
		const flattenedChildren: Array<BBCodeContentNode> = BBCodeNode.flattenChildren(children);
		const filteredChildren: FilteredChildren = this.filterChildren(flattenedChildren);
		const convertedChildren: Array<BBCodeContentNode> = this.convertChildren(filteredChildren.resolved);

		convertedChildren.forEach((node: BBCodeContentNode) => {
			node.remove();
			node.setParent(this);
			this.children.unshift(node);
		});

		if (Type.isArrayFilled(filteredChildren.unresolved))
		{
			if (this.getScheme().isAllowedUnresolvedNodesHoisting())
			{
				this.propagateChild(...filteredChildren.unresolved);
			}
			else
			{
				filteredChildren.unresolved.forEach((node: BBCodeContentNode) => {
					node.remove();
				});
			}
		}
	}

	replaceChild(targetNode: BBCodeContentNode, ...children: Array<BBCodeContentNode | BBCodeFragmentNode>)
	{
		this.children = this.children.flatMap((node: BBCodeContentNode) => {
			if (node === targetNode)
			{
				node.setParent(null);

				const flattenedChildren: Array<BBCodeContentNode> = BBCodeNode.flattenChildren(children);
				const filteredChildren: FilteredChildren = this.filterChildren(flattenedChildren);
				const convertedChildren: Array<BBCodeContentNode> = this.convertChildren(filteredChildren.resolved);

				return convertedChildren.map((child: BBCodeContentNode) => {
					child.remove();
					child.setParent(this);

					return child;
				});
			}

			return node;
		});
	}

	toStringValue(): string
	{
		const value: BBCodeElementNodeValue = this.getValue();
		const encodedValue: string = this.getEncoder().encodeAttribute(value);

		return value ? `=${encodedValue}` : '';
	}

	toStringAttributes(): string
	{
		return Object
			.entries(this.getAttributes())
			.map(([key: string, attrValue: string]) => {
				const preparedKey: string = this.prepareCase(key);
				const encodedValue: string = this.getEncoder().encodeAttribute(attrValue);

				return attrValue ? `${preparedKey}=${encodedValue}` : preparedKey;
			})
			.join(' ');
	}

	getContent(): string
	{
		return this.getChildren()
			.map((child: BBCodeContentNode) => {
				return child.toString();
			})
			.join('');
	}

	getOpeningTag(): string
	{
		const displayedName: string = this.getDisplayedName();
		const tagValue: BBCodeElementNodeValue = this.toStringValue();
		const attributes: JsonObject = this.toStringAttributes();
		const formattedAttributes: string = Type.isStringFilled(attributes) ? ` ${attributes}` : '';

		return `[${displayedName}${tagValue}${formattedAttributes}]`;
	}

	getClosingTag(): string
	{
		return `[/${this.getDisplayedName()}]`;
	}

	clone(options: { deep: boolean } = {}): BBCodeElementNode
	{
		const children = (() => {
			if (options.deep)
			{
				return this.getChildren().map((child) => {
					return child.clone(options);
				});
			}

			return [];
		})();

		return this.getScheme().createElement({
			name: this.getName(),
			void: this.isVoid(),
			value: this.getValue(),
			attributes: { ...this.getAttributes() },
			children,
		});
	}

	splitByChildIndex(index: number): Array<BBCodeElementNode | null>
	{
		if (!Type.isNumber(index))
		{
			throw new TypeError('index is not a number');
		}

		const childrenCount = this.getChildrenCount();
		if (index < 0 || index > childrenCount)
		{
			throw new TypeError(`index '${index}' is out of range ${0}-${childrenCount}`);
		}

		const leftNode = (() => {
			if (index === childrenCount)
			{
				return this;
			}

			if (index === 0)
			{
				return null;
			}

			const leftChildren = this.getChildren().filter((child, childIndex) => {
				return childIndex < index;
			});

			const node = this.clone();
			node.setChildren(leftChildren);

			return node;
		})();

		const rightNode = (() => {
			if (index === 0)
			{
				return this;
			}

			if (index === childrenCount)
			{
				return null;
			}

			const rightChildren = this.getChildren();
			const node = this.clone();
			node.setChildren(rightChildren);

			return node;
		})();

		if (leftNode && rightNode)
		{
			this.replace(leftNode, rightNode);
		}

		return [leftNode, rightNode];
	}

	getTagScheme(): BBCodeTagScheme
	{
		return super.getTagScheme();
	}

	toString(): string
	{
		const tagScheme: BBCodeTagScheme = this.getTagScheme();
		const stringifier: BBCodeNodeStringifier = tagScheme.getStringifier();
		if (Type.isFunction(stringifier))
		{
			const scheme: BBCodeScheme = this.getScheme();

			return stringifier(this, scheme);
		}

		const openingTag: string = this.getOpeningTag();
		const content: string = this.getContent();

		if (this.isVoid())
		{
			return `${openingTag}${content}`;
		}

		const closingTag: string = this.getClosingTag();

		return `${openingTag}${content}${closingTag}`;
	}

	toJSON(): SerializedBBCodeElementNode
	{
		return {
			...super.toJSON(),
			value: this.getValue(),
			attributes: this.getAttributes(),
			void: this.isVoid(),
		};
	}
}