UNPKG

@syncfusion/ej2-richtexteditor

Version:
324 lines (317 loc) 13 kB
import { debounce, isNullOrUndefined, detach, removeClass } from '@syncfusion/ej2-base'; import { NodeSelection } from './../../selection/selection'; import * as EVENTS from './../../common/constant'; import { isIDevice, scrollToCursor, setEditFrameFocus } from '../../common/util'; import { CLS_AUD_FOCUS, CLS_VID_FOCUS } from './../../common/constant'; /** * `Undo` module is used to handle undo actions. */ var UndoRedoManager = /** @class */ (function () { function UndoRedoManager(parent, options) { this.undoRedoStack = []; this.parent = parent; this.undoRedoSteps = !isNullOrUndefined(options) ? options.undoRedoSteps : 30; this.undoRedoTimer = !isNullOrUndefined(options) ? options.undoRedoTimer : 300; this.addEventListener(); } UndoRedoManager.prototype.addEventListener = function () { this.debounceListener = debounce(this.keyUp, this.undoRedoTimer); this.parent.observer.on(EVENTS.KEY_UP_HANDLER, this.debounceListener, this); this.parent.observer.on(EVENTS.KEY_DOWN_HANDLER, this.keyDown, this); this.parent.observer.on(EVENTS.ACTION, this.onAction, this); this.parent.observer.on(EVENTS.MODEL_CHANGED_PLUGIN, this.onPropertyChanged, this); this.parent.observer.on(EVENTS.INTERNAL_DESTROY, this.destroy, this); }; UndoRedoManager.prototype.onPropertyChanged = function (props) { for (var _i = 0, _a = Object.keys(props.newProp); _i < _a.length; _i++) { var prop = _a[_i]; switch (prop) { case 'undoRedoSteps': this.undoRedoSteps = props.newProp.undoRedoSteps; break; case 'undoRedoTimer': this.undoRedoTimer = props.newProp.undoRedoTimer; break; } } }; UndoRedoManager.prototype.removeEventListener = function () { this.parent.observer.off(EVENTS.KEY_UP_HANDLER, this.keyUp); this.parent.observer.off(EVENTS.KEY_DOWN_HANDLER, this.keyDown); this.parent.observer.off(EVENTS.ACTION, this.onAction); this.parent.observer.off(EVENTS.MODEL_CHANGED_PLUGIN, this.onPropertyChanged); this.parent.observer.off(EVENTS.INTERNAL_DESTROY, this.destroy); this.debounceListener = null; }; /** * onAction method * * @param {IHtmlSubCommands} e - specifies the sub command * @returns {void} * @hidden */ UndoRedoManager.prototype.onAction = function (e) { if (e.subCommand === 'Undo') { this.undo(e); } else { this.redo(e); } }; /** * Destroys the ToolBar. * * @function destroy * @returns {void} * @hidden */ UndoRedoManager.prototype.destroy = function () { this.removeEventListener(); this.element = null; this.steps = null; this.undoRedoStack = []; this.undoRedoSteps = null; this.undoRedoTimer = null; }; UndoRedoManager.prototype.keyDown = function (e) { var event = e.event; // eslint-disable-next-line var proxy = this; switch (event.action) { case 'undo': event.preventDefault(); proxy.undo(e); break; case 'redo': event.preventDefault(); proxy.redo(e); break; } }; UndoRedoManager.prototype.keyUp = function (e) { if (e.event.keyCode !== 17 && !e.event.ctrlKey) { this.saveData(e); } }; UndoRedoManager.prototype.getTextContentFromFragment = function (fragment) { var textContent = ''; for (var i = 0; i < fragment.childNodes.length; i++) { var childNode = fragment.childNodes[i]; if (childNode.nodeType === Node.TEXT_NODE) { textContent += childNode.textContent; } else if (childNode.nodeType === Node.ELEMENT_NODE) { textContent += this.getTextContentFromFragment(childNode); } } return textContent; }; UndoRedoManager.prototype.isElementStructureEqual = function (previousFragment, currentFragment) { if (previousFragment.childNodes.length !== currentFragment.childNodes.length) { return false; } for (var i = 0; i < previousFragment.childNodes.length; i++) { var previousFragmentNode = previousFragment.childNodes[i]; var currentFragmentNode = currentFragment.childNodes[i]; if (!previousFragmentNode || !currentFragmentNode) { return false; } if (previousFragmentNode.nodeType !== currentFragmentNode.nodeType) { return false; } if (previousFragmentNode.outerHTML !== currentFragmentNode.outerHTML) { return false; } } return true; }; /** * RTE collection stored html format. * * @function saveData * @param {KeyboardEvent} e - specifies the keyboard event * @returns {void} * @hidden */ UndoRedoManager.prototype.saveData = function (e) { if (!this.parent.currentDocument) { return; } var range = new NodeSelection(this.parent.editableElement).getRange(this.parent.currentDocument); var currentContainer = this.parent.editableElement === range.startContainer.parentElement ? range.startContainer.parentElement : range.startContainer; for (var i = currentContainer.childNodes.length - 1; i >= 0; i--) { if (!isNullOrUndefined(currentContainer.childNodes[i]) && currentContainer.childNodes[i].nodeName === '#text' && currentContainer.childNodes[i].textContent.length === 0 && currentContainer.childNodes[i].nodeName !== 'IMG' && currentContainer.childNodes[i].nodeName !== 'BR' && currentContainer.childNodes[i].nodeName && 'HR') { detach(currentContainer.childNodes[i]); } } range = new NodeSelection(this.parent.editableElement).getRange(this.parent.currentDocument); var save = new NodeSelection(this.parent.editableElement).save(range, this.parent.currentDocument); var clonedElement = this.removeResizeElement(this.parent.editableElement.cloneNode(true)); var fragment = document.createDocumentFragment(); while (clonedElement.firstChild) { fragment.appendChild(clonedElement.firstChild); } var changEle = { text: fragment, range: save }; if (this.undoRedoStack.length >= this.steps) { this.undoRedoStack = this.undoRedoStack.slice(0, this.steps + 1); } if (this.undoRedoStack.length > 1 && (this.undoRedoStack[this.undoRedoStack.length - 1].range.range.collapsed === range.collapsed) && (this.undoRedoStack[this.undoRedoStack.length - 1].range.startOffset === save.range.startOffset) && (this.undoRedoStack[this.undoRedoStack.length - 1].range.endOffset === save.range.endOffset) && (this.undoRedoStack[this.undoRedoStack.length - 1].range.range.startContainer === save.range.startContainer) && (this.getTextContentFromFragment(this.undoRedoStack[this.undoRedoStack.length - 1].text).trim() === this.getTextContentFromFragment(changEle.text).trim()) && this.isElementStructureEqual(this.undoRedoStack[this.undoRedoStack.length - 1].text, changEle.text)) { return; } this.undoRedoStack.push(changEle); this.steps = this.undoRedoStack.length - 1; if (this.steps > this.undoRedoSteps) { this.undoRedoStack.shift(); this.steps--; } if (e && e.callBack) { e.callBack(); } }; /* * Cleans up an HTML element by removing all resize handles and visual focus indicators * related to video, image, and audio editing UI. * * - Removes the DOM elements used for resizing videos and images. * - Clears visual focus classes and outline styles added for video, image, and audio elements. */ UndoRedoManager.prototype.removeResizeElement = function (element) { // Remove video resize element var videoResize = element.querySelector('.e-vid-resize'); if (videoResize) { detach(videoResize); } // Remove video focus class var videoFocus = element.querySelector("." + CLS_VID_FOCUS); if (videoFocus) { removeClass([videoFocus], [CLS_VID_FOCUS, 'e-resize']); } // Remove image resize element var imageResize = element.querySelector('.e-img-resize'); if (imageResize) { detach(imageResize); } // Remove focus/resize classes from all images var images = element.querySelectorAll('img'); for (var i = 0; i < images.length; i++) { var img = images[i]; removeClass([img], ['e-img-focus', 'e-resize']); } // Remove audio focus class var audioFocus = element.querySelector("." + CLS_AUD_FOCUS); if (audioFocus) { removeClass([audioFocus], [CLS_AUD_FOCUS]); } // Remove outline from images, audio, and video elements var outlineElements = element.querySelectorAll('img, audio, video'); for (var i = 0; i < outlineElements.length; i++) { var outlineElem = outlineElements[i]; outlineElem.style.outline = ''; } return element; }; /** * Undo the editable text. * * @function undo * @param {IHtmlSubCommands} e - specifies the sub commands * @returns {void} * @hidden */ UndoRedoManager.prototype.undo = function (e) { if (this.steps > 0) { var range = this.undoRedoStack[this.steps - 1].range; var removedContent = this.undoRedoStack[this.steps - 1].text; this.parent.editableElement.innerHTML = ''; this.parent.editableElement.appendChild(removedContent.cloneNode(true)); this.parent.editableElement.focus(); scrollToCursor(this.parent.currentDocument, this.parent.editableElement); if (isIDevice()) { setEditFrameFocus(this.parent.editableElement, e.selector); } range.restore(); this.steps--; if (e.callBack) { e.callBack({ requestType: 'Undo', editorMode: 'HTML', range: range, elements: this.parent.nodeSelection.getSelectedNodes(this.parent.currentDocument), event: e.event }); } } }; /** * Redo the editable text. * * @param {IHtmlSubCommands} e - specifies the sub commands * @function redo * @returns {void} * @hidden */ UndoRedoManager.prototype.redo = function (e) { if (this.undoRedoStack[this.steps + 1] != null) { var range = this.undoRedoStack[this.steps + 1].range; var addedContent = this.undoRedoStack[this.steps + 1].text; this.parent.editableElement.innerHTML = ''; this.parent.editableElement.appendChild(addedContent.cloneNode(true)); this.parent.editableElement.focus(); scrollToCursor(this.parent.currentDocument, this.parent.editableElement); if (isIDevice()) { setEditFrameFocus(this.parent.editableElement, e.selector); } range.restore(); this.steps++; if (e.callBack) { e.callBack({ requestType: 'Redo', editorMode: 'HTML', range: range, elements: this.parent.nodeSelection.getSelectedNodes(this.parent.currentDocument), event: e.event }); } } }; /** * getUndoStatus method * * @returns {boolean} - returns the boolean value * @hidden */ UndoRedoManager.prototype.getUndoStatus = function () { var status = { undo: false, redo: false }; if (this.steps > 0) { status.undo = true; } if (this.undoRedoStack[this.steps + 1] != null) { status.redo = true; } return status; }; UndoRedoManager.prototype.getCurrentStackIndex = function () { return this.steps; }; /** * Clears the undo and redo stacks and reset the steps to null.. * * @returns {void} * @public */ UndoRedoManager.prototype.clear = function () { this.undoRedoStack = []; this.steps = null; }; return UndoRedoManager; }()); export { UndoRedoManager };