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/rich-text-area/src/

Upload File :
current_dir [ Writeable] document_root [ Writeable]

 

Command :


[ Back ]     

Current File : /home/bitrix/ext_www/rospirotorg.ru/bitrix/js/ui/rich-text-area/src/rich-text-area.js
import { Type, Runtime } from 'main.core';
import { EventEmitter, type BaseEvent } from 'main.core.events';
import { COMMAND_PRIORITY_LOW, COMMAND_PRIORITY_NORMAL, PASTE_COMMAND } from 'ui.lexical.core';
import { Plugins, TextEditor, Commands, type TextEditorOptions } from 'ui.text-editor';
import { VueUploaderAdapter } from 'ui.uploader.vue';

import {
	FileEvent,
	getFilesFromDataTransfer,
	isFilePasted,
	Uploader,
	UploaderFile,
	type UploaderOptions,
	type UploaderFileInfo,
} from 'ui.uploader.core';

const { DRAG_END_COMMAND, DRAG_START_COMMAND } = Commands;

import type { TileWidgetItem } from 'ui.uploader.tile-widget';
import type { RichTextAreaOptions } from './rich-text-area-options';

export class RichTextArea extends EventEmitter
{
	#textEditor: TextEditor = null;
	#uploaderAdapter: VueUploaderAdapter = null;
	#uploader: Uploader = null;
	#allowDropFiles: boolean = true;
	#syncHighlightsDebounced = Runtime.debounce(this.#syncHighlights, 500);
	#lastInserted: Set<string | number> = new Set();

	constructor(richTextAreaOptions: RichTextAreaOptions)
	{
		super();
		this.setEventNamespace('BX.UI.RichTextArea');

		const options: RichTextAreaOptions = Type.isPlainObject(richTextAreaOptions) ? richTextAreaOptions : {};

		this.subscribeFromOptions(options.widgetOptions.events);

		this.#createTextEditor(options.editorOptions, options.editorInstance);
		this.#createUploaderAdapter(options.uploaderOptions, options.uploaderInstance, options.files);

		const fileInfos = this.#uploaderAdapter.getUploader().getFiles().map((file: UploaderFile) => {
			return file.toJSON();
		});

		this.getEditor().dispatchCommand(Plugins.File.ADD_FILES_COMMAND, fileInfos);

		this.#registerCommands();
	}

	#createTextEditor(editorOptions: TextEditorOptions, editorInstance: TextEditor): TextEditor
	{
		if (editorInstance)
		{
			this.#textEditor = editorInstance;
		}
		else
		{
			const options: TextEditorOptions = Type.isPlainObject(editorOptions) ? { ...editorOptions } : {};
			this.#textEditor = new TextEditor(options);
		}

		this.#textEditor.subscribeFromOptions({
			onChange: (event: BaseEvent<{ isInitialChange: boolean, tags: Set<string> }>) => {
				const { tags, isInitialChange } = event.getData();
				if (tags.has('historic'))
				{
					// Undo/Redo case uses setEditorState that's why we need a new update circle
					this.getEditor().update(() => {
						this.#syncHighlights();
					});
				}
				else if (isInitialChange)
				{
					this.#syncHighlights(true);
				}
				else
				{
					this.#syncHighlightsDebounced();
				}
			},
		});

		return this.#textEditor;
	}

	#createUploaderAdapter(uploaderOptions: UploaderOptions, uploader: Uploader, files: UploaderFileInfo[]): void
	{
		if (uploader instanceof Uploader)
		{
			this.#uploaderAdapter = new VueUploaderAdapter(uploader);
		}
		else
		{
			const options: UploaderOptions = Type.isPlainObject(uploaderOptions) ? uploaderOptions : {};
			const defaultOptions: UploaderOptions = {
				imagePreviewHeight: 1200, // double size (see DiskUploaderController)
				imagePreviewWidth: 1200,
				imagePreviewQuality: 0.85,
				treatOversizeImageAsFile: true,
				ignoreUnknownImageTypes: true,
				multiple: true,
			};

			this.#uploaderAdapter = new VueUploaderAdapter({
				...defaultOptions,
				...options,
			});
		}

		this.#uploaderAdapter.subscribeFromOptions({
			'Item:onAdd': (event: BaseEvent<{ item: TileWidgetItem }>): void => {
				const item: TileWidgetItem = event.getData().item;
				const fileCount = this.getFileCount();

				this.emit('Item:onAdd', { item, fileCount });
			},
			'Item:onComplete': (event: BaseEvent<{ item: TileWidgetItem }>): void => {
				const item: TileWidgetItem = event.getData().item;
				const fileCount = this.getFileCount();

				this.getEditor().dispatchCommand(Plugins.File.ADD_FILE_COMMAND, item);

				this.emit('Item:onComplete', { item, fileCount });
			},
			'Item:onRemove': (event: BaseEvent<{ item: TileWidgetItem }>): void => {
				const item: TileWidgetItem = event.getData().item;

				this.removeFile(event.getData().item.serverFileId);
				const fileCount = this.getFileCount();

				this.emit('Item:onRemove', { item, fileCount });
			},
		});

		this.#uploaderAdapter.getUploader().addFiles(files);
	}

	getUploaderAdapter(): VueUploaderAdapter
	{
		return this.#uploaderAdapter;
	}

	getUploader(): Uploader
	{
		return this.#uploaderAdapter.getUploader();
	}

	getFileCount(): number
	{
		return this.getUploader().getFiles().length;
	}

	getEditor(): TextEditor
	{
		return this.#textEditor;
	}

	isFilePluginEnabled(): boolean
	{
		const filePlugin: typeof(Plugins.File.FilePlugin) = this.getEditor().getPlugin('File');

		return filePlugin?.isEnabled() === true;
	}

	canDropFiles(): boolean
	{
		return this.#allowDropFiles;
	}

	insertFile(fileInfo: TileWidgetItem | UploaderFileInfo): void
	{
		this.getEditor().dispatchCommand(Plugins.File.INSERT_FILE_COMMAND, {
			serverFileId: fileInfo.serverFileId,
			width: 600, // half size of imagePreviewWidth
			height: 600, // half size of imagePreviewHeight
			info: fileInfo,
		});
	}

	removeFile(serverFileId: string | number): void
	{
		this.getEditor().dispatchCommand(
			Plugins.File.REMOVE_FILE_COMMAND,
			{
				serverFileId,
				skipHistoryStack: true,
			},
		);

		this.#syncHighlights(); // onChange doesn't emit due to history-merge
	}

	#registerCommands(): void
	{
		this.getEditor().registerCommand(
			PASTE_COMMAND,
			(clipboardEvent: ClipboardEvent) => {
				const clipboardData: DataTransfer = clipboardEvent.clipboardData;
				if (!clipboardData || !isFilePasted(clipboardData))
				{
					return false;
				}

				clipboardEvent.preventDefault();

				getFilesFromDataTransfer(clipboardData)
					.then((files: File[]): void => {
						if (files.length > 0)
						{
							this.emit('onBeforeFilePaste');
						}

						files.forEach((file: File): void => {
							this.getUploader().addFile(file, {
								events: {
									[FileEvent.LOAD_ERROR]: () => {},
									[FileEvent.UPLOAD_ERROR]: () => {},
									[FileEvent.UPLOAD_COMPLETE]: (event: BaseEvent): void => {
										const uploaderFile: UploaderFile = event.getTarget();

										this.emit('onFilePaste', { file: uploaderFile });
										this.insertFile(uploaderFile.toJSON());
									},
								},
							});
						});
					})
					.catch((): void => {
						console.error('RichTextArea: clipboard pasting error.');
					})
				;

				return true;
			},
			COMMAND_PRIORITY_NORMAL,
		);

		this.getEditor().registerCommand(
			DRAG_START_COMMAND,
			() => {
				this.#allowDropFiles = false;
			},
			COMMAND_PRIORITY_LOW,
		);

		this.getEditor().registerCommand(
			DRAG_END_COMMAND,
			() => {
				this.#allowDropFiles = true;
			},
			COMMAND_PRIORITY_LOW,
		);
	}

	#syncHighlights(initialSync: boolean = false): void
	{
		this.getEditor().dispatchCommand(Plugins.File.GET_INSERTED_FILES_COMMAND, (nodes) => {
			const inserted: Set<number | string> = new Set();
			for (const node of nodes)
			{
				const { serverFileId } = node.getInfo();
				if (Type.isStringFilled(serverFileId) || Type.isNumber(serverFileId))
				{
					inserted.add(serverFileId);
				}
			}

			const isInsertedChanged: boolean = this.#isInsertedChanged(inserted);
			this.#lastInserted = new Set(inserted);

			let hasInsertedItems = false;
			this.getUploader().getFiles().forEach((file: UploaderFile) => {
				if (inserted.has(file.getServerFileId()))
				{
					hasInsertedItems = true;
					file.setCustomData('tileSelected', true);
					inserted.delete(file.getServerFileId());
				}
				else
				{
					file.setCustomData('tileSelected', false);
				}
			});

			// Redo/Undo history can have files that were removed from uploader
			for (const serverFileId of inserted)
			{
				this.richTextArea.removeFile(serverFileId);
			}

			if (!initialSync && isInsertedChanged)
			{
				this.emit('Item:onInsertChange', { hasInsertedItems });
			}
		});
	}

	#isInsertedChanged(inserted: Set<string | number>): boolean
	{
		if (this.#lastInserted.size !== inserted.size)
		{
			return true;
		}

		for (const serverFileId of this.#lastInserted)
		{
			if (!inserted.has(serverFileId))
			{
				return true;
			}
		}

		return false;
	}

	destroy(): void
	{
		this.#textEditor.destroy();
		this.#uploader.destroy();

		this.#textEditor = null;
		this.#uploader = null;
	}
}

Youez - 2016 - github.com/yon3zu
LinuXploit