UNPKG

@syncfusion/ej2-richtexteditor

Version:
399 lines (398 loc) 20.9 kB
import * as events from '../base/constant'; import { isNullOrUndefined as isNOU } from '@syncfusion/ej2-base'; import { CodeBlockPlugin } from '../../editor-manager/plugin/code-block'; import * as EVENTS from '../../common/constant'; /** * Code Block module provides functionality for working with code blocks in the Rich Text Editor * Handles code block creation, editing, and keypress interactions * * @constructor CodeBlock */ var CodeBlock = /** @class */ (function () { /** * Creates an instance of CodeBlock module * * @param {IRichTextEditor} parent - The parent Rich Text Editor instance * @returns {void} */ function CodeBlock(parent) { this.parent = parent; this.isDestroyed = false; this.isItemsDisabled = false; this.addEventListener(); } /* Registers event handlers for code block operations */ CodeBlock.prototype.addEventListener = function () { this.parent.on(events.onCodeBlock, this.onCodeBlock, this); this.parent.on(events.codeBlockPaste, this.onPaste, this); this.parent.on(events.codeBlockEnter, this.codeBlockEnter, this); this.parent.on(events.keyDown, this.onKeyDown, this); this.parent.on(events.keyUp, this.onKeyUp, this); this.parent.on(events.editAreaClick, this.editAreaClickHandler, this); this.parent.on(events.destroy, this.destroy, this); this.parent.on(events.bindOnEnd, this.bindOnEnd, this); }; /* Handles code block button or dropdown click events */ CodeBlock.prototype.onCodeBlock = function (args) { if (args.member === 'codeBlock' && args.args && args.args.item && args.args.originalEvent.target && args.args.originalEvent.target.parentElement.classList.contains('e-dropdown-btn')) { return; } else if (args.args.originalEvent.target.parentElement.classList.contains('e-split-btn') || args.args.originalEvent.target.classList.contains('e-split-btn')) { var codeBlock_1 = this.parent.codeBlockSettings; var defaultItem = void 0; if (codeBlock_1.languages && codeBlock_1.languages.length > 0) { var filteredItems = !isNOU(codeBlock_1.defaultLanguage) ? codeBlock_1.languages.filter(function (item) { return item.language === codeBlock_1.defaultLanguage; }) : []; defaultItem = filteredItems.length > 0 ? filteredItems[0] : codeBlock_1.languages[0]; this.parent.formatter.process(this.parent, args.args, args.args.originalEvent, { language: defaultItem.language, label: defaultItem.label, action: 'createCodeBlock', enterAction: this.parent.enterKey }); } } }; /* Handles paste events within code blocks while pasting remvoe the all the format and past into the code block */ CodeBlock.prototype.onPaste = function (e) { var range = this.parent.formatter.editorManager.nodeSelection.getRange(this.parent.inputElement.ownerDocument); if (this.codeBlockObj.isValidCodeBlockStructure(range.startContainer) || this.codeBlockObj.isValidCodeBlockStructure(range.endContainer)) { var codeBlockTarget_1 = !isNOU(this.codeBlockObj.isValidCodeBlockStructure(range.startContainer)) ? this.codeBlockObj.isValidCodeBlockStructure(range.startContainer) : this.codeBlockObj.isValidCodeBlockStructure(range.endContainer); var currentFormat = this.parent.codeBlockSettings.languages .slice() .filter(function (item) { return item.label === codeBlockTarget_1.getAttribute('data-language'); }); var item = e.item; if (isNOU(item)) { item = { command: 'CodeBlock', subCommand: 'CodeBlock' }; } var actionBeginArgs = { originalEvent: e.args, item: item }; this.parent.formatter.process(this.parent, actionBeginArgs, e.args, { action: 'codeBlockPaste', currentFormat: { label: currentFormat[0].label, language: currentFormat[0].language } }); } }; /* * Handles Enter key press events within code blocks * Maintains proper behavior when users press Enter while editing inside code blocks */ CodeBlock.prototype.codeBlockEnter = function (args) { if ((args.args) && args.args.which === 13) { var range = this.parent.formatter.editorManager.nodeSelection.getRange(this.parent.inputElement.ownerDocument); if (this.codeBlockObj.isValidCodeBlockStructure(range.startContainer) || this.codeBlockObj.isValidCodeBlockStructure(range.endContainer)) { var item = args.item; if (isNOU(item)) { item = { command: 'CodeBlock', subCommand: 'CodeBlock' }; } var actionBeginArgs = { originalEvent: args.args, item: item }; this.parent.formatter.process(this.parent, actionBeginArgs, args.args, { action: 'codeBlockEnter' }); } } }; CodeBlock.prototype.codeBlockTab = function (args) { var tabAction = (args.args) && args.args.which === 9 && !args.args.shiftKey; var shitTabAction = (args.args) && args.args.which === 9 && args.args.shiftKey; if ((tabAction) || (shitTabAction)) { var range = this.parent.formatter.editorManager.nodeSelection.getRange(this.parent.inputElement.ownerDocument); var isCodeBlock = this.codeBlockObj. isSelectionWithinCodeBlock(range, range.startContainer, range.endContainer); if (isCodeBlock) { var item = this.createToolbarItem(args); var actionBeginArgs = { originalEvent: args.args, item: item }; var currentAction = tabAction ? 'codeBlockTabAction' : 'codeBlockShiftTabAction'; this.parent.formatter.process(this.parent, actionBeginArgs, args.args, { action: currentAction }); args.args.preventDefault(); } } }; /* * Handles special key events within code blocks * Processes delete/backspace keys and code block shortcuts to maintain proper code block structure */ CodeBlock.prototype.onKeyDown = function (e) { var keyboardEvent = e.args; if (keyboardEvent.which === 9) { this.codeBlockTab(e); return; } var isDeleteKey = keyboardEvent.which === 8 || keyboardEvent.which === 46; var isCodeBlockShortcut = keyboardEvent.action === 'code-block'; // Only process delete/backspace keys or code block shortcut if (!isDeleteKey && !isCodeBlockShortcut) { return; } var range = this.parent.formatter.editorManager.nodeSelection.getRange(this.parent.inputElement.ownerDocument); // Skip if selection contains all content if (this.isSelectionAllContent(range)) { return; } var editorManager = this.parent.formatter.editorManager; var codeBlockPosition = editorManager.codeBlockObj.getCodeBlockPosition(range); // Check if cursor is at the end of a code block (for delete key handling) var isCursorAtCodeBlockEnd = !isNOU(codeBlockPosition.nextSiblingCodeBlockElement) && codeBlockPosition.cursorAtLastPosition; // Checks if cursor is at the beginning of the current element // Used to handle merging with preceding elements when Backspace is pressed var firstContentNode = editorManager.nodeSelection.findFirstContentNode(codeBlockPosition.blockNode); var isCursorAtBlockStart = firstContentNode && firstContentNode.node === range.startContainer && range.startOffset === 0; // Find any previous code block that might be affected by backspace var previousSiblingCodeBlock = editorManager.codeBlockObj.findParentOrPreviousSiblingCodeBlock(range); /* Determines if the current node should not be a list (UL/OL) to prevent backspace action. */ var currentNodeShouldNotBeList = !isNOU(previousSiblingCodeBlock) && previousSiblingCodeBlock.currentNode && previousSiblingCodeBlock.currentNode.nodeName !== 'UL' && previousSiblingCodeBlock.currentNode.nodeName !== 'OL'; var hasPreviousCodeBlock = currentNodeShouldNotBeList && isCursorAtBlockStart; // Check if selection is within a code block var isStartPointInCodeBlock = !isNOU(this.codeBlockObj.isValidCodeBlockStructure(range.startContainer)); var isEndPointInCodeBlock = !isNOU(this.codeBlockObj.isValidCodeBlockStructure(range.endContainer)); var isSelectionInCodeBlock = isStartPointInCodeBlock || isEndPointInCodeBlock; var shouldProcessCodeBlock = isSelectionInCodeBlock || isCursorAtCodeBlockEnd || hasPreviousCodeBlock || isCodeBlockShortcut; if (shouldProcessCodeBlock) { var item = this.createToolbarItem(e); var actionBeginArgs = { originalEvent: e.args, item: item }; // Handle backspace/delete operations if (!isCodeBlockShortcut) { this.processCodeBlockDeletion(actionBeginArgs, e, isCursorAtCodeBlockEnd, hasPreviousCodeBlock, isSelectionInCodeBlock, range); } // Handle code block creation shortcut else { this.createCodeBlockWithDefaultLanguage(actionBeginArgs, e); } } }; /* Creates a toolbar item model for code block operations * If no item exists in the event, creates a default code block item */ CodeBlock.prototype.createToolbarItem = function (e) { var item = e.item; if (isNOU(item)) { item = { command: 'CodeBlock', subCommand: 'CodeBlock' }; } return item; }; /* * Handles deletion and backspace operations within code blocks * Evaluates the cursor position and surrounding elements to properly manage code block structure during deletion and backspace */ CodeBlock.prototype.processCodeBlockDeletion = function (actionBeginArgs, e, isAtLastPosition, hasPrevCodeBlock, isInCodeBlock, range) { if (!isAtLastPosition && !hasPrevCodeBlock && isInCodeBlock) { var codeBlockTarget = !isNOU(this.codeBlockObj.isValidCodeBlockStructure(range.startContainer)) ? this.codeBlockObj.isValidCodeBlockStructure(range.startContainer) : this.codeBlockObj.isValidCodeBlockStructure(range.endContainer); var languageLabel_1 = codeBlockTarget.getAttribute('data-language'); var languages = this.parent.codeBlockSettings.languages; var currentFormat = languages .slice() .filter(function (item) { return item.label === languageLabel_1; }); if (currentFormat.length > 0) { this.parent.formatter.process(this.parent, actionBeginArgs, e.args, { action: 'codeBlockBackSpace', currentFormat: { label: currentFormat[0].label, language: currentFormat[0].language } }); } } else { this.parent.formatter.process(this.parent, actionBeginArgs, e.args, { action: 'codeBlockBackSpace' }); } }; /* Creates a code block using the default language configuration * Retrieves the default language from settings and applies it to the selected content */ CodeBlock.prototype.createCodeBlockWithDefaultLanguage = function (actionBeginArgs, e) { var codeBlock = this.parent.codeBlockSettings; if (!codeBlock.languages || codeBlock.languages.length === 0) { return; } var defaultItem; var hasDefaultLanguage = !isNOU(codeBlock.defaultLanguage); if (hasDefaultLanguage) { var filteredItems = codeBlock.languages.filter(function (item) { return item.language === codeBlock.defaultLanguage; }); defaultItem = filteredItems.length > 0 ? filteredItems[0] : codeBlock.languages[0]; } else { defaultItem = codeBlock.languages[0]; } this.parent.formatter.process(this.parent, actionBeginArgs, e.args, { language: defaultItem.language, label: defaultItem.label, action: 'createCodeBlock', enterAction: this.parent.enterKey }); }; /* Determines if the current selection contains all content in the editor * Used to prevent certain operations when the entire document is selected */ CodeBlock.prototype.isSelectionAllContent = function (range) { var div = document.createElement('div'); div.appendChild(range.cloneContents()); var selectedHTML = div.innerHTML; if (selectedHTML === this.parent.inputElement.innerHTML) { return true; } else { return false; } }; /* Handles key up events for delete and backspace keys */ CodeBlock.prototype.onKeyUp = function (e) { if (e.args.which === 8 || e.args.which === 46) { var range = this.parent.formatter.editorManager.nodeSelection.getRange(this.parent.inputElement.ownerDocument); var startContainer = range.startContainer.nodeName === '#text' ? range.startContainer.parentElement : range.startContainer; var endContainer = range.endContainer.nodeName === '#text' ? range.endContainer.parentElement : range.endContainer; if (startContainer.closest('pre[data-language]') && endContainer.closest('pre[data-language]')) { var item = e.item; if (isNOU(item)) { item = { command: 'CodeBlock', subCommand: 'CodeBlock' }; } var actionBeginArgs = { originalEvent: e.args, item: item }; this.parent.formatter.process(this.parent, actionBeginArgs, e.args, { action: 'codeBlockBackSpace' }); } } this.disableToolbarItems(); }; /* Handles edit area click events * Updates toolbar state based on cursor position */ CodeBlock.prototype.editAreaClickHandler = function () { this.disableToolbarItems(); }; /* Manages toolbar item availability based on code block context * When inside a code block, disables most formatting options except those relevant to code blocks * Restores all toolbar items when outside of code blocks */ CodeBlock.prototype.disableToolbarItems = function () { var range = this.parent.formatter.editorManager.nodeSelection.getRange(this.parent.inputElement.ownerDocument); var startContainer = range.startContainer.nodeName === '#text' ? range.startContainer.parentElement : range.startContainer; var endContainer = range.endContainer.nodeName === '#text' ? range.endContainer.parentElement : range.endContainer; var excludeItems = ['undo', 'redo']; var disableItemsList = ['codeblock', 'indent', 'outdent', 'sourcecode', 'emojipicker', 'importword', 'exportword', 'exportpdf', 'lowercase', 'uppercase', 'blockquote', 'numberformatlist', 'bulletformatlist', 'unorderedlist', 'orderedlist', 'fullscreen']; if (this.codeBlockObj.isValidCodeBlockStructure(startContainer) || this.codeBlockObj.isValidCodeBlockStructure(endContainer)) { this.parent.disableToolbarItem(this.parent.toolbarSettings.items .filter(function (item) { return typeof item === 'string' && excludeItems.indexOf(item.toLocaleLowerCase()) === -1; })); this.parent.enableToolbarItem(this.parent.toolbarSettings.items .filter(function (item) { return typeof item === 'string' && disableItemsList.indexOf(item.toLocaleLowerCase()) !== -1; })); // Disable the toolbar items when the range is inside a code block this.disableTextQuickToolbarItems(true); this.isItemsDisabled = true; } else if (this.isItemsDisabled) { var allToolbarItems = this.parent.toolbarSettings.items; var enableItems = allToolbarItems.slice(); this.parent.enableToolbarItem((enableItems).filter(function (item) { return typeof item === 'string' && excludeItems.indexOf(item.toLocaleLowerCase()) === -1; })); // Enable the toolbar items when the range is outside of the code block this.disableTextQuickToolbarItems(false); this.isItemsDisabled = false; } }; /* * Enables or disables specific items in the text quick toolbar based on whether they're supported in the current context. * Only allows specific items like undo, redo, codeblock, etc., while disabling others based on the parameter value. */ CodeBlock.prototype.disableTextQuickToolbarItems = function (disable) { var allowTextQuickItems = ['undo', 'redo', 'codeblock', 'indent', 'outdent', 'sourcecode', 'emojipicker', 'importword', 'exportword', 'exportpdf', 'lowercase', 'uppercase', 'blockquote', 'numberformatlist', 'bulletformatlist', 'unorderedlist', 'orderedlist', 'fullscreen' ]; var textQuickToolbar = (this.parent.quickToolbarModule && this.parent.quickToolbarModule.textQTBar && this.parent.quickToolbarModule.textQTBar.quickTBarObj && this.parent.quickToolbarModule.textQTBar.quickTBarObj.toolbarObj) ? this.parent.quickToolbarModule.textQTBar.quickTBarObj.toolbarObj : null; var toolbarItems = !isNOU(this.parent.quickToolbarSettings.text) ? this.parent.quickToolbarSettings.text : []; for (var index = 0; index < toolbarItems.length; index++) { var item = toolbarItems[index]; var isAllowed = allowTextQuickItems.indexOf(item.toLocaleLowerCase()); if (isAllowed === -1 && textQuickToolbar) { textQuickToolbar.enableItems(index, !disable); } } }; /* Removes all event listeners attached by this module * Unregisters handlers to prevent memory leaks */ CodeBlock.prototype.removeEventListener = function () { this.parent.off(events.onCodeBlock, this.onCodeBlock); this.parent.off(events.codeBlockPaste, this.onPaste); this.parent.off(events.codeBlockEnter, this.codeBlockEnter); this.parent.off(events.keyDown, this.onKeyDown); this.parent.off(events.keyUp, this.onKeyUp); this.parent.off(events.editAreaClick, this.editAreaClickHandler); this.parent.off(events.destroy, this.destroy); this.parent.off(events.bindOnEnd, this.bindOnEnd); this.parent.formatter.editorManager.observer.off(EVENTS.CODEBLOCK_DISABLETOOLBAR, this.editAreaClickHandler); }; /** * Cleans up resources and detaches event handlers when the component is destroyed * * @returns {void} */ CodeBlock.prototype.destroy = function () { if (this.isDestroyed) { return; } this.isDestroyed = true; this.removeEventListener(); }; /** * For internal use only - Get the module name. * * @returns {string} - returns the string value */ CodeBlock.prototype.getModuleName = function () { return 'codeBlock'; }; /** * Initializes the CodeBlockPlugin object in the editor manager after editor initialization is complete. * This method binds the code block module to the editor's formatter for handling code block related operations. * * @returns {void} - This method does not return a value * @private */ CodeBlock.prototype.bindOnEnd = function () { if (!this.parent.formatter.editorManager.codeBlockObj) { this.parent.formatter.editorManager.codeBlockObj = new CodeBlockPlugin(this.parent.formatter.editorManager); this.codeBlockObj = this.parent.formatter.editorManager.codeBlockObj; this.parent.formatter.editorManager.observer.on(EVENTS.CODEBLOCK_DISABLETOOLBAR, this.editAreaClickHandler, this); } }; return CodeBlock; }()); export { CodeBlock };