@syncfusion/ej2-richtexteditor
Version:
Essential JS 2 RichTextEditor component
399 lines (398 loc) • 20.9 kB
JavaScript
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 };