UNPKG

suneditor

Version:

Vanilla JavaScript based WYSIWYG web editor

325 lines (283 loc) 11.3 kB
// L1: kernel import Store from './store'; // L2: config import ContextProvider from '../config/contextProvider'; import OptionProvider from '../config/optionProvider'; import InstanceCheck from '../config/instanceCheck'; import EventManager from '../config/eventManager'; // L3: logic/dom import Offset from '../logic/dom/offset'; import Selection_ from '../logic/dom/selection'; import Format from '../logic/dom/format'; import Inline from '../logic/dom/inline'; import ListFormat from '../logic/dom/listFormat'; import HTML from '../logic/dom/html'; import NodeTransform from '../logic/dom/nodeTransform'; import Char from '../logic/dom/char'; // L3: logic/shell import Shortcuts from '../logic/shell/shortcuts'; import Component from '../logic/shell/component'; import PluginManager from '../logic/shell/pluginManager'; import FocusManager from '../logic/shell/focusManager'; import UI from '../logic/shell/ui'; import CommandDispatcher from '../logic/shell/commandDispatcher'; import History from '../logic/shell/history'; // L3: logic/panel import Toolbar from '../logic/panel/toolbar'; import Menu from '../logic/panel/menu'; import Viewer from '../logic/panel/viewer'; import Finder from '../logic/panel/finder'; // L4: event import EventOrchestrator from '../event/eventOrchestrator'; /** * @typedef {import('../section/constructor').ConstructorReturnType} ProductType */ /** * @typedef {Object} Deps * @property {SunEditor.Instance} facade - Editor facade (public API) * @property {SunEditor.Store} store - L1: Central state store * * @property {import('../config/contextProvider').default} contextProvider - L2: Context provider * @property {import('../config/optionProvider').default} optionProvider - L2: Option provider * @property {import('../config/instanceCheck').default} instanceCheck - L2: Instance type checker * @property {import('../config/eventManager').default} eventManager - L2: Event manager (public API) * * @property {Map<string, SunEditor.FrameContext>} frameRoots - Frame root elements map * @property {SunEditor.Context} context - Editor context * @property {SunEditor.Options} options - Editor options * @property {Object} icons - Icon set * @property {Object} lang - Language strings * @property {SunEditor.FrameContext} frameContext - Current frame context * @property {SunEditor.FrameOptions} frameOptions - Current frame options * * @property {import('../logic/dom/offset').default} offset - L3: Offset calculator * @property {import('../logic/dom/selection').default} selection - L3: Selection handler * @property {import('../logic/dom/format').default} format - L3: Block formatting * @property {import('../logic/dom/inline').default} inline - L3: Inline styling * @property {import('../logic/dom/listFormat').default} listFormat - L3: List operations * @property {import('../logic/dom/html').default} html - L3: HTML processing * @property {import('../logic/dom/nodeTransform').default} nodeTransform - L3: Node transformation * @property {import('../logic/dom/char').default} char - L3: Character counting * * @property {import('../logic/shell/component').default} component - L3: Component lifecycle * @property {import('../logic/shell/focusManager').default} focusManager - L3: Focus management * @property {import('../logic/shell/pluginManager').default} pluginManager - L3: Plugin registry * @property {Object<string, Object>} plugins - Plugin instances map * @property {import('../logic/shell/ui').default} ui - L3: UI state management * @property {import('../logic/shell/commandDispatcher').default} commandDispatcher - L3: Command routing * @property {ReturnType<import('../logic/shell/history').default>} history - L3: Undo/Redo stack * @property {import('../logic/shell/shortcuts').default} shortcuts - L3: Shortcut mapping * * @property {import('../logic/panel/toolbar').default} toolbar - L3: Toolbar renderer * @property {import('../logic/panel/toolbar').default} subToolbar - L3: Sub-toolbar renderer * @property {import('../logic/panel/menu').default} menu - L3: Menu renderer * @property {import('../logic/panel/viewer').default} viewer - L3: View mode handler * @property {import('../logic/panel/finder').default} finder - L3: Finder handler */ /** * @description Core dependency container for the editor. * - Stores and retrieves config/logic/plugin instances. * - Orchestrates dependency injection across layers. * - Initialization order: L1 Store -> L2 Config (`$` Phase 1) -> L3 Logic (`$` Phase 2) -> L4 Event. */ class CoreKernel { #config = new Map(); #logic = new Map(); /** @type {Deps} */ $ = null; /** * @param {SunEditor.Instance} facade - Editor instance (Public API) * @param {Object} config - Initial configuration * @param {ProductType} config.product - The initial product object. * @param {SunEditor.InitOptions} config.options - The initial options. */ constructor(facade, config) { const { product, options } = config; // L1: Store this.store = new Store(product); /** @type {Deps} */ this.$ = /** @type {*} */ ({ facade, store: this.store }); // L2: Config this.#registerConfig(product, options); // Deps Phase 1: Config deps added to $ (available to Logic constructors) this.#buildConfigDeps(); // L3: Logic (dom, shell, ui) this.#registerLogic(product); // Deps Phase 2: Logic deps added to $ this.#assignLogicDeps(); //---------------------------------------------- // Initialize Logic modules that need EventManager reference this.#initLogic(); // Event orchestrator this._eventOrchestrator = new EventOrchestrator(this); } /** * @description L2: Register config instances * @param {ProductType} product - The initial product object. * @param {SunEditor.InitOptions} options - The initial options. */ #registerConfig(product, options) { const contextProvider = new ContextProvider(product); const optionProvider = new OptionProvider(this, product, options); this.#config.set('contextProvider', contextProvider); this.#config.set('optionProvider', optionProvider); this.#config.set('instanceCheck', new InstanceCheck(contextProvider.frameContext)); this.#config.set('eventManager', new EventManager(contextProvider, optionProvider, this.$)); } /** * @description Deps Phase 1: Build Deps bag with config entries only. * Logic constructors can access the Deps bag via `kernel.$`. */ #buildConfigDeps() { const contextProvider = this.#config.get('contextProvider'); const optionProvider = this.#config.get('optionProvider'); Object.assign(this.$, { // L2: Config contextProvider, optionProvider, instanceCheck: this.#config.get('instanceCheck'), eventManager: this.#config.get('eventManager'), // L2: Config - Convenience accessors frameRoots: contextProvider.frameRoots, context: contextProvider.context, options: optionProvider.options, icons: contextProvider.icons, lang: contextProvider.lang, frameContext: contextProvider.frameContext, frameOptions: optionProvider.frameOptions, }); } /** * @description L3: Register logic instances (dom, shell, ui). * @param {ProductType} product - The initial product object. */ #registerLogic(product) { // dom this.#logic.set('offset', new Offset(this)); this.#logic.set('selection', new Selection_(this)); this.#logic.set('html', new HTML(this)); this.#logic.set('nodeTransform', new NodeTransform(this)); this.#logic.set('format', new Format(this)); this.#logic.set('inline', new Inline(this)); this.#logic.set('listFormat', new ListFormat(this)); this.#logic.set('char', new Char(this)); // shell this.#logic.set('shortcuts', new Shortcuts(this)); this.#logic.set('component', new Component(this)); this.#logic.set('pluginManager', new PluginManager(this, product)); this.#logic.set('focusManager', new FocusManager(this)); this.#logic.set('ui', new UI(this)); this.#logic.set('commandDispatcher', new CommandDispatcher(this)); // ui this.#logic.set( 'toolbar', new Toolbar(this, { keyName: 'toolbar', balloon: this.store.mode.isBalloon, balloonAlways: this.store.mode.isBalloonAlways, inline: this.store.mode.isInline, res: product.responsiveButtons, }), ); if (this.$.options.has('_subMode')) { this.#logic.set( 'subToolbar', new Toolbar(this, { keyName: 'toolbar_sub', balloon: this.store.mode.isSubBalloon, balloonAlways: this.store.mode.isSubBalloonAlways, inline: false, res: product.responsiveButtons_sub, }), ); } this.#logic.set('menu', new Menu(this)); this.#logic.set('viewer', new Viewer(this)); this.#logic.set('finder', new Finder(this)); // history (last — closure captures all L3 modules above) this.#logic.set('history', History(this)); } /** * @description Initialize Logic modules that need `EventManager` reference. * Called after `EventManager` is created. */ #initLogic() { for (const [, instance] of this.#logic) { if (typeof instance?._init === 'function') { instance._init(); } } } /** * @description Deps Phase 2: Add logic entries to the Deps bag (`$`). * Called after all logic instances are registered. */ #assignLogicDeps() { const pluginManager = this.#logic.get('pluginManager'); Object.assign(this.$, { // L3: Logic (dom) offset: this.#logic.get('offset'), selection: this.#logic.get('selection'), format: this.#logic.get('format'), inline: this.#logic.get('inline'), listFormat: this.#logic.get('listFormat'), html: this.#logic.get('html'), nodeTransform: this.#logic.get('nodeTransform'), char: this.#logic.get('char'), // L3: Logic (shell) component: this.#logic.get('component'), focusManager: this.#logic.get('focusManager'), pluginManager, plugins: pluginManager.plugins, ui: this.#logic.get('ui'), commandDispatcher: this.#logic.get('commandDispatcher'), history: this.#logic.get('history'), shortcuts: this.#logic.get('shortcuts'), // L3: Logic (ui) toolbar: this.#logic.get('toolbar'), subToolbar: this.#logic.get('subToolbar'), menu: this.#logic.get('menu'), viewer: this.#logic.get('viewer'), finder: this.#logic.get('finder'), }); } /** * @description Destroy the kernel and release all resources. * Teardown order (reverse of init): plugins -> logic -> event -> config -> store * Uses error aggregation to ensure all modules are cleaned up even if some fail. */ _destroy() { if (this.$ === null) return; const errors = []; for (const [key, instance] of this.#logic) { try { instance?._destroy?.(); } catch (e) { errors.push(`[Logic:${key}] ${e.message}`); } } this.#logic.clear(); try { this._eventOrchestrator?._removeAllEvents(); } catch (e) { errors.push(`[EventOrchestrator] ${e.message}`); } this._eventOrchestrator = null; for (const [key, instance] of this.#config) { try { instance?._destroy?.(); } catch (e) { errors.push(`[Config:${key}] ${e.message}`); } } this.#config.clear(); if (errors.length > 0) { console.warn('[CoreKernel._destroy] Cleanup completed with errors:', errors); } this.$ = null; this.store._destroy(); this.store = null; this.facade = null; } } export default CoreKernel;