403Webshell
Server IP : 80.87.202.40  /  Your IP : 216.73.216.169
Web Server : Apache
System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64
User : bitrix ( 600)
PHP Version : 8.2.27
Disable Function : NONE
MySQL : OFF |  cURL : ON |  WGET : ON |  Perl : ON |  Python : OFF |  Sudo : ON |  Pkexec : ON
Directory :  /home/bitrix/ext_www/rospirotorg.ru/bitrix/js/ui/text-editor/src/toolbar/

Upload File :
current_dir [ Writeable] document_root [ Writeable]

 

Command :


[ Back ]     

Current File : /home/bitrix/ext_www/rospirotorg.ru/bitrix/js/ui/text-editor/src/toolbar/toolbar.js
/* eslint-disable no-underscore-dangle */
import { Dom, Tag, Type, Event } from 'main.core';
import { MemoryCache, type BaseCache } from 'main.core.cache';

import 'ui.icon-set.editor';
import { UNFORMATTED } from '../constants';

import {
	SELECTION_CHANGE_COMMAND,
	COMMAND_PRIORITY_CRITICAL,
	BLUR_COMMAND,
	FOCUS_COMMAND,
	$getSelection,
	$isRangeSelection,
	$getRoot,
	type RangeSelection,
	type LexicalNode,
	type ElementNode,
} from 'ui.lexical.core';

import { $findMatchingParent, $getNearestNodeOfType, mergeRegister } from 'ui.lexical.utils';
import { $isListNode, ListNode } from 'ui.lexical.list';
import { $isAutoLinkNode, $isLinkNode } from 'ui.lexical.link';
import { $isCodeTokenNode } from '../plugins/code';

import { type TextEditor } from '../text-editor';
import { TextEditorLexicalNode } from '../types/text-editor-lexical-node';
import type { ToolbarOptions, ToolbarItem } from '../types/toolbar-options';
import Separator from './separator';
import Button from './button';

import './toolbar.css';

export default class Toolbar
{
	#textEditor: TextEditor = null;
	#items: ToolbarItem[] = [];
	#rendered: boolean = false;
	#moreBtn: Button = null;
	#refs: BaseCache<HTMLElement> = new MemoryCache();
	#resizeObserver: ResizeObserver = null;
	#timeoutId: number = null;
	#removeListeners: Function = null;

	constructor(textEditor: TextEditor, options: ToolbarOptions)
	{
		this.#textEditor = textEditor;

		const toolbarOptions: ToolbarOptions = Type.isArray(options) ? options : [];
		this.#fillFromOptions(toolbarOptions);

		if (this.#items.length > 0)
		{
			this.#removeListeners = this.#registerListeners();
			this.#resizeObserver = new ResizeObserver(this.#handleResize.bind(this));
		}
	}

	renderTo(container: HTMLElement): void
	{
		if (this.isRendered())
		{
			return;
		}

		if (Type.isElementNode(container))
		{
			this.#items.forEach((item: ToolbarItem) => {
				Dom.append(item.render(), this.getItemsContainer());
			});

			Dom.append(this.getContainer(), container);

			if (this.#resizeObserver !== null)
			{
				this.#resizeObserver.observe(this.getContainer());
			}

			this.#rendered = true;
		}
	}

	isEmpty(): boolean
	{
		return this.#items.length === 0;
	}

	getContainer(): HTMLElement
	{
		return this.#refs.remember('container', () => {
			return Tag.render`
				<div class="ui-text-editor-toolbar-container">
					${this.getItemsContainer()}
					${this.getMoreBtnContainer()}
				</div>
			`;
		});
	}

	getItemsContainer(): HTMLElement
	{
		return this.#refs.remember('items-container', () => {
			return Tag.render`
				<div class="ui-text-editor-toolbar-items"></div>
			`;
		});
	}

	getMoreBtnContainer(): HTMLElement
	{
		return this.#refs.remember('more-btn-container', () => {
			return Tag.render`
				<div class="ui-text-editor-toolbar-more-btn">
				${this.getMoreBtn().render()}
				</div>
			`;
		});
	}

	getMoreBtn(): Button
	{
		if (this.#moreBtn === null)
		{
			const resetAnimation = () => {
				Event.unbind(this.getItemsContainer(), 'transitionend', resetAnimation);
				Dom.style(this.getItemsContainer(), { height: null });
				Dom.removeClass(this.getItemsContainer(), '--animating');
			};

			this.#moreBtn = new Button();
			this.#moreBtn.setContent('<span class="ui-text-editor-toolbar-more-btn-icon"></span>');
			this.#moreBtn.subscribe('onClick', (): void => {
				Event.unbind(this.getItemsContainer(), 'transitionend', resetAnimation);

				if (Dom.hasClass(this.getContainer(), '--expanded'))
				{
					Dom.style(this.getItemsContainer(), { height: `${this.getItemsContainer().scrollHeight}px` });
					requestAnimationFrame(() => {
						Dom.removeClass(this.getContainer(), '--expanded');
						Dom.addClass(this.getItemsContainer(), '--animating');
						Dom.style(this.getItemsContainer(), { height: null });
					});
				}
				else
				{
					Dom.addClass(this.getItemsContainer(), '--animating');
					Dom.style(this.getItemsContainer(), { height: `${this.getItemsContainer().scrollHeight}px` });
					Dom.addClass(this.getContainer(), '--expanded');
				}

				Event.bind(this.getItemsContainer(), 'transitionend', resetAnimation);
			});
		}

		return this.#moreBtn;
	}

	getItems(): ToolbarItem[]
	{
		return this.#items;
	}

	isRendered(): boolean
	{
		return this.#rendered;
	}

	destroy(): boolean
	{
		if (this.#removeListeners !== null)
		{
			this.#removeListeners();
		}

		if (this.#resizeObserver !== null)
		{
			this.#resizeObserver.disconnect();
			this.#resizeObserver = null;
		}

		if (this.isRendered())
		{
			Dom.remove(this.getContainer());
		}

		if (this.#timeoutId)
		{
			clearTimeout(this.#timeoutId);
		}

		this.#items = null;
		this.#refs = null;
	}

	#registerListeners(): () => {}
	{
		return mergeRegister(
			this.#textEditor.registerCommand(
				SELECTION_CHANGE_COMMAND,
				() => {
					this.update();

					return false;
				},
				COMMAND_PRIORITY_CRITICAL,
			),
			this.#textEditor.registerCommand(
				FOCUS_COMMAND,
				(): boolean => {
					if (this.#timeoutId)
					{
						clearTimeout(this.#timeoutId);
						this.#timeoutId = null;
					}

					return false;
				},
				COMMAND_PRIORITY_CRITICAL,
			),
			this.#textEditor.registerCommand(
				BLUR_COMMAND,
				(): boolean => {
					if (this.#timeoutId)
					{
						clearTimeout(this.#timeoutId);
					}

					this.#timeoutId = setTimeout((): void => {
						const activeElement = document.activeElement;
						const rootElement = this.#textEditor.getScrollerContainer();
						if (activeElement === null || !rootElement.contains(activeElement))
						{
							this.reset();
						}
					}, 400);

					return false;
				},
				COMMAND_PRIORITY_CRITICAL,
			),
			this.#textEditor.registerUpdateListener(() => {
				this.update();
			}),
			this.#textEditor.registerEditableListener(() => {
				this.update();
			}),
		);
	}

	#fillFromOptions(options: ToolbarOptions)
	{
		options.forEach((item: ToolbarItem) => {
			if (item === '|')
			{
				this.#items.push(new Separator());
			}
			else
			{
				const component = this.#textEditor.getComponentRegistry().create(item);
				if (component === null)
				{
					// eslint-disable-next-line no-console
					console.warn(`TextEditor Toolbar: "${item}" component doesn't exist.`);
				}
				else
				{
					this.#items.push(component);
				}
			}
		});
	}

	#handleResize(entries: ResizeObserverEntry[])
	{
		if (this.getContainer().offsetWidth === 0 || Dom.hasClass(this.getItemsContainer(), '--animating'))
		{
			return;
		}

		const lastItem: ?ToolbarItem = this.#items.at(-1);
		if (!lastItem || lastItem.getContainer().offsetTop >= lastItem.getContainer().offsetHeight)
		{
			Dom.addClass(this.getContainer(), '--overflowed');
		}
		else
		{
			Dom.removeClass(this.getContainer(), ['--overflowed', '--expanded']);
		}
	}

	update(): void
	{
		this.#textEditor.getEditorState().read((): void => {
			let selection: RangeSelection = $getSelection();
			if (!$isRangeSelection(selection))
			{
				selection = null;
			}

			let unformattedNode = null;
			if (selection !== null)
			{
				unformattedNode = $findMatchingParent(
					selection.anchor.getNode(),
					(node: TextEditorLexicalNode): boolean => {
						return (node.__flags & UNFORMATTED) !== 0;
					},
				);
			}

			const blockTypes: Set<string> = selection === null ? new Set() : this.#getSelectionBlockTypes(selection);
			const isReadOnly: boolean = !this.#textEditor.isEditable();

			this.#items.forEach((item: Button) => {
				if (!(item instanceof Button))
				{
					return;
				}

				// First let's figure out a disabled status
				if (item.hasOwnDisableCallback())
				{
					item.setDisabled(item.invokeDisableCallback());
				}
				else if (isReadOnly)
				{
					item.disable();
				}
				else if (unformattedNode !== null && item.shouldDisableInsideUnformatted())
				{
					item.disable();
				}
				else
				{
					item.enable();
				}

				// Now set an active status
				if (item.isDisabled())
				{
					item.setActive(false);
				}
				else if (item.hasFormat())
				{
					const format = item.getFormat();
					item.setActive(selection === null ? false : selection.hasFormat(format));
				}
				else if (item.getBlockType() !== null)
				{
					item.setActive(blockTypes.has(item.getBlockType()));
				}
			});
		});
	}

	reset(): void
	{
		this.#items.forEach((item: Button) => {
			if (item instanceof Button)
			{
				item.setActive(false);
			}
		});
	}

	#getSelectionBlockTypes(selection: RangeSelection): Set<string>
	{
		const anchorNode = selection.anchor.getNode();
		const blockTypes = new Set();
		let currentNode: ElementNode | LexicalNode | null = anchorNode;
		while (currentNode !== $getRoot() && currentNode !== null)
		{
			const blockType = this.#getBlockType(currentNode);
			blockTypes.add(blockType);
			currentNode = currentNode.getParent();
		}

		return blockTypes;
	}

	#getBlockType(node: LexicalNode): string | null
	{
		if ($isListNode(node))
		{
			const listNode: ListNode = node;
			const parentList = $getNearestNodeOfType(listNode, ListNode);

			return parentList ? parentList.getListType() : listNode.getListType();
		}

		if ($isLinkNode(node) || $isAutoLinkNode(node))
		{
			return 'link';
		}

		if ($isCodeTokenNode(node))
		{
			return 'code';
		}

		return node.getType();
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit