import { BlockAPI as BlockAPIInterface, BlockTool as IBlockTool, BlockToolData, BlockTune as IBlockTune, SanitizerConfig, ToolConfig, ToolboxConfigEntry, PopoverItem } from '../../../types'; import { SavedData } from '../../../types/data-formats'; import $ from '../dom'; import * as _ from '../utils'; import ApiModules from '../modules/api'; import BlockAPI from './api'; import SelectionUtils from '../selection'; import BlockTool from '../tools/block'; import BlockTune from '../tools/tune'; import { BlockTuneData } from '../../../types/block-tunes/block-tune-data'; import ToolsCollection from '../tools/collection'; import EventsDispatcher from '../utils/events'; import { TunesMenuConfigItem } from '../../../types/tools'; import { isMutationBelongsToElement } from '../utils/mutations'; import { EditorEventMap, FakeCursorAboutToBeToggled, FakeCursorHaveBeenSet, RedactorDomChanged } from '../events'; import { RedactorDomChangedPayload } from '../events/RedactorDomChanged'; import { convertBlockDataToString } from '../utils/blocks'; /** * Interface describes Block class constructor argument */ interface BlockConstructorOptions { /** * Block's id. Should be passed for existed block, and omitted for a new one. */ id?: string; /** * Initial Block data */ data: BlockToolData; /** * Tool object */ tool: BlockTool; /** * Editor's API methods */ api: ApiModules; /** * This flag indicates that the Block should be constructed in the read-only mode. */ readOnly: boolean; /** * Tunes data for current Block */ tunesData: { [name: string]: BlockTuneData }; } /** * @class Block * @classdesc This class describes editor`s block, including block`s HTMLElement, data and tool * @property {BlockTool} tool — current block tool (Paragraph, for example) * @property {object} CSS — block`s css classes */ /** * Available Block Tool API methods */ export enum BlockToolAPI { /** * @todo remove method in 3.0.0 * @deprecated — use 'rendered' hook instead */ // eslint-disable-next-line @typescript-eslint/naming-convention APPEND_CALLBACK = 'appendCallback', RENDERED = 'rendered', MOVED = 'moved', UPDATED = 'updated', REMOVED = 'removed', // eslint-disable-next-line @typescript-eslint/naming-convention ON_PASTE = 'onPaste', } /** * Names of events used in Block */ interface BlockEvents { 'didMutated': Block, } /** * @classdesc Abstract Block class that contains Block information, Tool name and Tool class instance * @property {BlockTool} tool - Tool instance * @property {HTMLElement} holder - Div element that wraps block content with Tool's content. Has `ce-block` CSS class * @property {HTMLElement} pluginsContent - HTML content that returns by Tool's render function */ export default class Block extends EventsDispatcher { /** * CSS classes for the Block * * @returns {{wrapper: string, content: string}} */ public static get CSS(): { [name: string]: string } { return { wrapper: 'ce-block', wrapperStretched: 'ce-block--stretched', content: 'ce-block__content', focused: 'ce-block--focused', selected: 'ce-block--selected', dropTarget: 'ce-block--drop-target', }; } /** * Block unique identifier */ public id: string; /** * Block Tool`s name */ public readonly name: string; /** * Instance of the Tool Block represents */ public readonly tool: BlockTool; /** * User Tool configuration */ public readonly settings: ToolConfig; /** * Wrapper for Block`s content */ public readonly holder: HTMLDivElement; /** * Tunes used by Tool */ public readonly tunes: ToolsCollection; /** * Tool's user configuration */ public readonly config: ToolConfig; /** * Cached inputs * * @type {HTMLElement[]} */ private cachedInputs: HTMLElement[] = []; /** * We'll store a reference to the tool's rendered element to access it later */ private toolRenderedElement: HTMLElement | null = null; /** * Tool class instance */ private readonly toolInstance: IBlockTool; /** * User provided Block Tunes instances */ private readonly tunesInstances: Map = new Map(); /** * Editor provided Block Tunes instances */ private readonly defaultTunesInstances: Map = new Map(); /** * If there is saved data for Tune which is not available at the moment, * we will store it here and provide back on save so data is not lost */ private unavailableTunesData: { [name: string]: BlockTuneData } = {}; /** * Editor`s API module */ private readonly api: ApiModules; /** * Focused input index * * @type {number} */ private inputIndex = 0; /** * Common editor event bus */ private readonly editorEventBus: EventsDispatcher | null = null; /** * Link to editor dom change callback. Used to remove listener on remove */ private redactorDomChangedCallback: (payload: RedactorDomChangedPayload) => void; /** * Current block API interface */ private readonly blockAPI: BlockAPIInterface; /** * @param options - block constructor options * @param [options.id] - block's id. Will be generated if omitted. * @param options.data - Tool's initial data * @param options.tool — block's tool * @param options.api - Editor API module for pass it to the Block Tunes * @param options.readOnly - Read-Only flag * @param [eventBus] - Editor common event bus. Allows to subscribe on some Editor events. Could be omitted when "virtual" Block is created. See BlocksAPI@composeBlockData. */ constructor({ id = _.generateBlockId(), data, tool, api, readOnly, tunesData, }: BlockConstructorOptions, eventBus?: EventsDispatcher) { super(); this.name = tool.name; this.id = id; this.settings = tool.settings; this.config = tool.settings.config || {}; this.api = api; this.editorEventBus = eventBus || null; this.blockAPI = new BlockAPI(this); this.tool = tool; this.toolInstance = tool.create(data, this.blockAPI, readOnly); /** * @type {BlockTune[]} */ this.tunes = tool.tunes; this.composeTunes(tunesData); this.holder = this.compose(); /** * Start watching block mutations */ this.watchBlockMutations(); /** * Mutation observer doesn't track changes in "" and "