UNPKG

jodit

Version:

Jodit is awesome and usefully wysiwyg editor with filebrowser

238 lines (205 loc) 5.37 kB
/*! * Jodit Editor (https://xdsoft.net/jodit/) * Licensed under GNU General Public License version 2 or later or a commercial license or MIT; * For GPL see LICENSE-GPL.txt in the project root for license information. * For MIT see LICENSE-MIT.txt in the project root for license information. * For commercial licenses see https://xdsoft.net/jodit/commercial/ * Copyright (c) 2013-2019 Valeriy Chupurnov. All rights reserved. https://xdsoft.net */ import { IJodit, SnapshotType } from '../types'; import { Component } from './Component'; import { Dom } from './Dom'; /** * Module for creating snapshot of editor which includes html content and the current selection */ export class Snapshot extends Component<IJodit> { /** * Compare two snapshotes, if and htmls and selections match, then return true * * @param {SnapshotType} first - the first snapshote * @param {SnapshotType} second - second shot * @return {boolean} */ public static equal(first: SnapshotType, second: SnapshotType): boolean { return ( first.html === second.html && JSON.stringify(first.range) === JSON.stringify(second.range) ); } /** * Calc count element before some node in parentNode. All text nodes are joined * * @param {Node | null} elm * @return {number} */ private static countNodesBeforeInParent(elm: Node): number { if (!elm.parentNode) { return 0; } const elms: NodeList = elm.parentNode.childNodes; let count: number = 0, last: Node | null = null, j: number; for (j = 0; j < elms.length; j += 1) { if ( last && (!( elms[j].nodeType === Node.TEXT_NODE && elms[j].textContent === '' ) && !( last.nodeType === Node.TEXT_NODE && elms[j].nodeType === Node.TEXT_NODE )) ) { count += 1; } if (elms[j] === elm) { return count; } last = elms[j]; } return 0; } /** * Calc normal offset in joined text nodes * * @param {Node | null} elm * @param {number} offset * @return {number} */ private static strokeOffset(elm: Node | null, offset: number): number { while (elm && elm.nodeType === Node.TEXT_NODE) { elm = elm.previousSibling; if ( elm && elm.nodeType === Node.TEXT_NODE && elm.textContent !== null ) { offset += elm.textContent.length; } } return offset; } /** * Calc whole hierarchy path before some element in editor's tree * * @param {Node | null} elm * @return {number[]} * @private */ private calcHierarchyLadder(elm: Node | null): number[] { const counts: number[] = []; if ( !elm || !elm.parentNode || !Dom.isOrContains(this.jodit.editor, elm) ) { return []; } while (elm && elm !== this.jodit.editor) { if (elm) { counts.push(Snapshot.countNodesBeforeInParent(elm)); } elm = elm.parentNode; } return counts.reverse(); } private getElementByLadder(ladder: number[]): Node { let n: Node = this.jodit.editor as Node, i: number; for (i = 0; n && i < ladder.length; i += 1) { n = n.childNodes[ladder[i]]; } return n; } isBlocked: boolean = false; /** * Creates object a snapshot of editor: html and the current selection. Current selection calculate by * offset by start document * * @return {object} * {html: string, range: {startContainer: int, startOffset: int, endContainer: int, endOffset: int}} or * {html: string} without selection */ make(): SnapshotType { const snapshot: SnapshotType = { html: '', range: { startContainer: [], startOffset: 0, endContainer: [], endOffset: 0 } }; snapshot.html = this.jodit.getNativeEditorValue(); const sel = this.jodit.selection.sel; if (sel && sel.rangeCount) { const range = sel.getRangeAt(0), startContainer = this.calcHierarchyLadder(range.startContainer), endContainer = this.calcHierarchyLadder(range.endContainer); let startOffset = Snapshot.strokeOffset( range.startContainer, range.startOffset ), endOffset = Snapshot.strokeOffset( range.endContainer, range.endOffset ); if ( !startContainer.length && range.startContainer !== this.jodit.editor ) { startOffset = 0; } if ( !endContainer.length && range.endContainer !== this.jodit.editor ) { endOffset = 0; } snapshot.range = { startContainer, startOffset, endContainer, endOffset }; } return snapshot; } /** * Restores the state of the editor of the snapshot. Rebounding is not only html but selected text * * @param {object} snapshot - snapshot of editor resulting from the `{@link Snapshot~make|make}` * @see make */ restore(snapshot: SnapshotType) { this.isBlocked = true; this.jodit.setEditorValue(snapshot.html); try { if (snapshot.range) { const range: Range = this.jodit.editorDocument.createRange(); range.setStart( this.getElementByLadder(snapshot.range.startContainer), snapshot.range.startOffset ); range.setEnd( this.getElementByLadder(snapshot.range.endContainer), snapshot.range.endOffset ); this.jodit.selection.selectRange(range); } } catch (__ignore) { if (process.env.NODE_ENV !== 'production') { throw __ignore; } } this.isBlocked = false; } destruct(): any { this.isBlocked = false; super.destruct(); } }