Your IP : 18.118.140.96


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

import { Type } from 'main.core';
import type { BBCodeEncoder } from 'ui.bbcode.encoder';
import { getByIndex } from '../../../shared';
import { typeof BBCodeElementNode } from './element-node';
import { typeof BBCodeTextNode } from './text-node';
import { typeof BBCodeNewLineNode } from './new-line-node';
import { typeof BBCodeTabNode } from './tab-node';
import { typeof BBCodeRootNode } from './root-node';
import { typeof BBCodeFragmentNode } from './fragment-node';
import { typeof BBCodeScheme } from '../scheme/bbcode-scheme';
import { type BBCodeNodeScheme } from '../scheme/node-schemes/node-scheme';

export type BBCodeSpecialCharNode = BBCodeNewLineNode | BBCodeTabNode;
export type BBCodeContentNode = BBCodeElementNode | BBCodeTextNode | BBCodeSpecialCharNode;
export type BBCodeParentNode = BBCodeRootNode | BBCodeElementNode | BBCodeFragmentNode;

export type BBCodeNodeOptions = {
	name?: string,
	parent?: BBCodeParentNode | null,
	children?: Array<BBCodeContentNode | BBCodeFragmentNode>,
	scheme: BBCodeScheme,
};

export type SerializedBBCodeNode = {
	name: string,
	children: Array<SerializedBBCodeNode>,
};

type PrivateMapKey = BBCodeNode | BBCodeContentNode | BBCodeRootNode | BBCodeFragmentNode;
type PrivateStorage = {[key: string]: any};

export const privateMap: WeakMap<PrivateMapKey, PrivateStorage> = new WeakMap();
export const nameSymbol: Symbol = Symbol('name');

export class BBCodeNode
{
	[nameSymbol]: string = '#unknown';
	children: Array<BBCodeContentNode | BBCodeFragmentNode> = [];

	constructor(options: BBCodeNodeOptions = {})
	{
		privateMap.set(this, {
			delayedChildren: [],
		});

		this.setName(options.name);
		privateMap.get(this).scheme = options.scheme;
		this.setParent(options.parent);
		this.setChildren(options.children);
	}

	static get ELEMENT_NODE(): number
	{
		return 1;
	}

	static get TEXT_NODE(): number
	{
		return 2;
	}

	static get ROOT_NODE(): number
	{
		return 3;
	}

	static get FRAGMENT_NODE(): number
	{
		return 4;
	}

	static freezeProperty(node: BBCodeNode, property: string, value: any, enumerable: boolean = true)
	{
		Object.defineProperty(node, property, {
			value,
			writable: false,
			configurable: false,
			enumerable,
		});
	}

	static makeNonEnumerableProperty(node: BBCodeNode, property: string)
	{
		Object.defineProperty(node, property, {
			writable: false,
			enumerable: false,
			configurable: false,
		});
	}

	static flattenChildren(children: Array<BBCodeContentNode | BBCodeFragmentNode>): Array<BBCodeContentNode>
	{
		if (Type.isArrayFilled(children))
		{
			return children.flatMap((node: BBCodeContentNode | BBCodeFragmentNode) => {
				if (node.getType() === BBCodeNode.FRAGMENT_NODE)
				{
					return node.getChildren();
				}

				return node;
			});
		}

		return [];
	}

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

	getScheme(): BBCodeScheme
	{
		return privateMap.get(this).scheme;
	}

	getTagScheme(): BBCodeNodeScheme
	{
		return this.getScheme().getTagScheme(this.getName());
	}

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

	prepareCase(value: string): string
	{
		const scheme: BBCodeScheme = this.getScheme();
		const currentCase = scheme.getOutputTagCase();
		if (currentCase === 'upper')
		{
			return value.toUpperCase();
		}

		return value.toLowerCase();
	}

	setName(name: string)
	{
		if (Type.isString(name))
		{
			this[nameSymbol] = name.toLowerCase();
		}
	}

	getName(): string
	{
		return this[nameSymbol];
	}

	getDisplayedName(): string
	{
		return this.prepareCase(this.getName());
	}

	setParent(parent: BBCodeParentNode | null = null)
	{
		const mounted = !this.hasParent() && parent;
		privateMap.get(this).parent = parent;

		if (mounted)
		{
			this.onNodeDidMount();
		}
	}

	getParent(): BBCodeParentNode | null
	{
		return privateMap.get(this).parent;
	}

	getType(): number
	{
		return privateMap.get(this).type;
	}

	hasParent(): boolean
	{
		return Boolean(privateMap.get(this).parent);
	}

	remove()
	{
		if (this.hasParent())
		{
			this.getParent().removeChild(this);
		}
	}

	setChildren(children: Array<BBCodeContentNode | BBCodeFragmentNode>)
	{
		if (Type.isArray(children))
		{
			this.children = [];
			this.appendChild(...children);
		}
	}

	getChildren(): Array<BBCodeContentNode>
	{
		return [...this.children];
	}

	getLastChild(): ?BBCodeContentNode
	{
		return getByIndex(this.getChildren(), -1);
	}

	getLastChildOfType(type: number): ?BBCodeContentNode
	{
		return this.getChildren().reverse().find((node: BBCodeContentNode) => {
			return node.getType() === type;
		});
	}

	getLastChildOfName(name: string): ?BBCodeContentNode
	{
		return this.getChildren().reverse().find((node: BBCodeContentNode) => {
			return node.getType() === BBCodeNode.ELEMENT_NODE && node.getName() === name;
		});
	}

	getFirstChild(): ?BBCodeContentNode
	{
		return getByIndex(this.getChildren(), 0);
	}

	getFirstChildOfType(type: number): ?BBCodeContentNode
	{
		return this.getChildren().find((node: BBCodeContentNode) => {
			return node.getType() === type;
		});
	}

	getFirstChildOfName(name: string): ?BBCodeContentNode
	{
		return this.getChildren().find((node: BBCodeContentNode) => {
			return node.getType() === BBCodeNode.ELEMENT_NODE && node.getName() === name;
		});
	}

	getPreviewsSibling(): ?BBCodeContentNode
	{
		if (this.hasParent())
		{
			const parentChildren: Array<BBCodeContentNode> = this.getParent().getChildren();
			const currentIndex: number = parentChildren.indexOf(this);
			if (currentIndex > 0)
			{
				return getByIndex(parentChildren, currentIndex - 1);
			}
		}

		return null;
	}

	getPreviewsSiblings(): ?Array<BBCodeContentNode>
	{
		if (this.hasParent())
		{
			const parentChildren: Array<BBCodeContentNode> = this.getParent().getChildren();
			const currentIndex: number = parentChildren.indexOf(this);

			return parentChildren.filter((child: BBCodeContentNode, index) => {
				return index < currentIndex;
			});
		}

		return null;
	}

	getNextSibling(): ?BBCodeContentNode
	{
		if (this.hasParent())
		{
			const parentChildren: Array<BBCodeContentNode> = this.getParent().getChildren();
			const currentIndex: number = parentChildren.indexOf(this);
			if (currentIndex !== -1 && currentIndex !== parentChildren.length)
			{
				return getByIndex(parentChildren, currentIndex + 1);
			}
		}

		return null;
	}

	getNextSiblings(): ?Array<BBCodeContentNode>
	{
		if (this.hasParent())
		{
			const parentChildren: Array<BBCodeContentNode> = this.getParent().getChildren();
			const currentIndex: number = parentChildren.indexOf(this);

			return parentChildren.filter((child: BBCodeContentNode, index) => {
				return index > currentIndex;
			});
		}

		return null;
	}

	getChildrenCount(): number
	{
		return this.children.length;
	}

	hasChildren(): boolean
	{
		return this.getChildrenCount() > 0;
	}

	isEmpty(): boolean
	{
		return this.getChildrenCount() === 0;
	}

	adjustChildren()
	{
		this.setChildren(this.getChildren());
	}

	setDelayedChildren(children: Array<BBCodeContentNode>)
	{
		if (Type.isArray(children))
		{
			privateMap.get(this).delayedChildren = children;
		}
	}

	addDelayedChildren(children: Array<BBCodeContentNode>)
	{
		if (Type.isArrayFilled(children))
		{
			this.setDelayedChildren([
				...this.getDelayedChildren(),
				...children,
			]);
		}
	}

	hasDelayedChildren(): boolean
	{
		return privateMap.get(this).delayedChildren.length > 0;
	}

	getDelayedChildren(): Array<BBCodeContentNode>
	{
		return [...privateMap.get(this).delayedChildren];
	}

	appendChild(...children: Array<BBCodeContentNode | BBCodeFragmentNode>)
	{
		const flattenedChildren: Array<BBCodeContentNode> = BBCodeNode.flattenChildren(children);

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

	prependChild(...children: Array<BBCodeContentNode | BBCodeFragmentNode>)
	{
		const flattenedChildren: Array<BBCodeContentNode> = BBCodeNode.flattenChildren(children);

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

	insertBefore(...nodes: Array<BBCodeContentNode>)
	{
		if (this.hasParent() && Type.isArrayFilled(nodes))
		{
			const parent: BBCodeContentNode = this.getParent();
			const parentChildren: Array<BBCodeContentNode> = parent.getChildren();
			const currentNodeIndex: number = parentChildren.indexOf(this);

			const deleteCount: number = 0;
			parentChildren.splice(currentNodeIndex, deleteCount, ...nodes);

			parent.setChildren(parentChildren);
		}
	}

	insertAfter(...nodes: Array<BBCodeContentNode>)
	{
		if (this.hasParent() && Type.isArrayFilled(nodes))
		{
			const parent: BBCodeContentNode = this.getParent();
			const parentChildren: Array<BBCodeContentNode> = parent.getChildren();
			const currentNodeIndex: number = parentChildren.indexOf(this);

			const startIndex: number = currentNodeIndex + 1;
			const deleteCount: number = 0;
			parentChildren.splice(startIndex, deleteCount, ...nodes);

			parent.setChildren(parentChildren);
		}
	}

	propagateChild(...children: Array<BBCodeContentNode>)
	{
		if (this.hasParent())
		{
			this.insertBefore(
				...children.filter((child: BBCodeContentNode) => {
					return !['#linebreak', '#tab'].includes(child.getName());
				}),
			);
		}
		else
		{
			this.addDelayedChildren(children);
		}
	}

	onNodeDidMount()
	{
		const delayedChildren = this.getDelayedChildren();
		if (Type.isArrayFilled(delayedChildren))
		{
			this.propagateChild(...delayedChildren);
			this.setDelayedChildren([]);
		}
	}

	removeChild(...children: Array<BBCodeContentNode>)
	{
		this.children = this.children.reduce((acc: Array<BBCodeContentNode>, node: BBCodeContentNode) => {
			if (children.includes(node))
			{
				node.setParent(null);

				return acc;
			}

			return [...acc, node];
		}, []);
	}

	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);

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

					return child;
				});
			}

			return node;
		});
	}

	replace(...children: Array<BBCodeContentNode | BBCodeFragmentNode>)
	{
		if (this.hasParent())
		{
			const parent = this.getParent();
			parent.replaceChild(this, ...children);
		}
	}

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

			return [];
		})();

		return this.getScheme().createNode({
			name: this.getName(),
			parent: this.getParent(),
			children,
		});
	}

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

	getTextContent(): string
	{
		return this.toPlainText();
	}

	getPlainTextLength(): number
	{
		return this.toPlainText().length;
	}

	removePreviewsSiblings()
	{
		const removePreviewsSiblings = (node: BBCodeContentNode) => {
			const previewsSiblings = node.getPreviewsSiblings();
			if (Type.isArray(previewsSiblings))
			{
				previewsSiblings.forEach((sibling: BBCodeContentNode) => {
					sibling.remove();
				});
			}

			const parent = node.getParent();
			if (parent)
			{
				removePreviewsSiblings(parent);
			}
		};

		removePreviewsSiblings(this);
	}

	removeNextSiblings()
	{
		const removeNextSiblings = (node: BBCodeContentNode) => {
			const nextSiblings = node.getNextSiblings();
			if (Type.isArray(nextSiblings))
			{
				nextSiblings.forEach((sibling: BBCodeContentNode) => {
					sibling.remove();
				});
			}

			const parent = node.getParent();
			if (parent)
			{
				removeNextSiblings(parent);
			}
		};

		removeNextSiblings(this);
	}

	findByTextIndex(index: number): ?{ node: BBCodeTextNode, startIndex: number, endIndex: number }
	{
		let currentIndex = 0;
		let startIndex = 0;
		let endIndex = 0;

		const node = BBCodeNode.flattenAst(this).find((child: BBCodeContentNode) => {
			if (
				child.getName() === '#text'
				|| child.getName() === '#linebreak'
				|| child.getName() === '#tab'
			)
			{
				startIndex = currentIndex;
				endIndex = startIndex + child.getLength();
				currentIndex = endIndex;

				return index >= startIndex && endIndex >= index;
			}

			return false;
		});

		if (node)
		{
			return { node, startIndex, endIndex };
		}

		return null;
	}

	split(options: { offset: number, byWord?: boolean}): Array<BBCodeContentNode>
	{
		const { offset, byWord = false } = options;
		const plainTextLength = this.getPlainTextLength();

		const leftTree = (() => {
			if (plainTextLength === offset)
			{
				return this.clone({ deep: true });
			}

			if (offset <= 0 || offset > plainTextLength)
			{
				return null;
			}

			const tree = this.clone({ deep: true });
			const { node, startIndex } = tree.findByTextIndex(offset);
			const [leftNode, rightNode] = node.split({ offset: offset - startIndex, byWord });
			if (leftNode)
			{
				node.replace(leftNode);
				leftNode.removeNextSiblings();
			}
			else if (rightNode)
			{
				rightNode.removeNextSiblings();
				rightNode.remove();
			}

			return tree;
		})();

		const rightTree = (() => {
			if (plainTextLength === offset)
			{
				return null;
			}

			if (offset === 0)
			{
				return this.clone({ deep: true });
			}

			const tree = this.clone({ deep: true });
			const { node, startIndex } = tree.findByTextIndex(offset);
			const [leftNode, rightNode] = node.split({ offset: offset - startIndex, byWord });
			if (rightNode)
			{
				node.replace(rightNode);
				rightNode.removePreviewsSiblings();
			}
			else if (leftNode)
			{
				leftNode.removePreviewsSiblings();
				if (leftNode.hasParent())
				{
					const parent = leftNode.getParent();
					leftNode.remove();
					if (parent.getChildrenCount() === 0)
					{
						parent.remove();
					}
				}
			}

			return tree;
		})();

		return [leftTree, rightTree];
	}

	static flattenAst(ast): Array<any>
	{
		const flat = [];

		const traverse = (node: BBCodeContentNode) => {
			flat.push(node);
			if (node.hasChildren())
			{
				node.getChildren().forEach((child: BBCodeContentNode) => {
					traverse(child);
				});
			}
		};

		if (ast.hasChildren())
		{
			ast.getChildren().forEach((child: BBCodeContentNode) => {
				traverse(child);
			});
		}

		return flat;
	}

	toJSON(): SerializedBBCodeNode
	{
		return {
			name: this.getName(),
			children: this.getChildren().map((child: BBCodeContentNode) => {
				return child.toJSON();
			}),
		};
	}
}