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 : |
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; } }