diff --git a/package.json b/package.json index 4a032232..87c282d2 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,10 @@ "types": "./es/headless.d.ts", "import": "./es/headless.js" }, + "./litexml-commands": { + "types": "./es/plugins/litexml/command/symbols.d.ts", + "import": "./es/plugins/litexml/command/symbols.js" + }, "./react": { "types": "./es/react.d.ts", "import": "./es/react.js" diff --git a/src/plugins/litexml/command/diffCommand.ts b/src/plugins/litexml/command/diffCommand.ts index 1dc57614..3cc08094 100644 --- a/src/plugins/litexml/command/diffCommand.ts +++ b/src/plugins/litexml/command/diffCommand.ts @@ -1,26 +1,8 @@ import { mergeRegister } from '@lexical/utils'; -import { - $getNodeByKey, - $isElementNode, - COMMAND_PRIORITY_EDITOR, - LexicalEditor, - createCommand, -} from 'lexical'; +import { $getNodeByKey, $isElementNode, COMMAND_PRIORITY_EDITOR, LexicalEditor } from 'lexical'; import { DiffNode } from '../node/DiffNode'; - -export enum DiffAction { - Reject, - Accept, -} - -export const LITEXML_DIFFNODE_COMMAND = createCommand<{ action: DiffAction; nodeKey: string }>( - 'LITEXML_DIFFNODE_COMMAND', -); - -export const LITEXML_DIFFNODE_ALL_COMMAND = createCommand<{ action: DiffAction }>( - 'LITEXML_DIFFNODE_ALL_COMMAND', -); +import { DiffAction, LITEXML_DIFFNODE_ALL_COMMAND, LITEXML_DIFFNODE_COMMAND } from './symbols'; function doAction(node: DiffNode, action: DiffAction) { if (node.diffType === 'modify') { @@ -146,3 +128,7 @@ export function registerLiteXMLDiffCommand(editor: LexicalEditor) { ), ); } + +// Command identities and the `DiffAction` enum live in the side-effect-free +// `./symbols` module so they stay single-instance across the package bundles. +export { DiffAction, LITEXML_DIFFNODE_ALL_COMMAND, LITEXML_DIFFNODE_COMMAND } from './symbols'; diff --git a/src/plugins/litexml/command/index.ts b/src/plugins/litexml/command/index.ts index 4d559b24..d9d68a7d 100644 --- a/src/plugins/litexml/command/index.ts +++ b/src/plugins/litexml/command/index.ts @@ -10,7 +10,6 @@ import { COMMAND_PRIORITY_EDITOR, LexicalEditor, LexicalNode, - createCommand, } from 'lexical'; import { $closest } from '@/editor-kernel'; @@ -19,6 +18,12 @@ import { createDebugLogger } from '@/utils/debug'; import type LitexmlDataSource from '../data-source/litexml-data-source'; import { $createDiffNode, DiffNode } from '../node/DiffNode'; import { $cloneNode, $parseSerializedNodeImpl, charToId } from '../utils'; +import { + LITEXML_APPLY_COMMAND, + LITEXML_INSERT_COMMAND, + LITEXML_MODIFY_COMMAND, + LITEXML_REMOVE_COMMAND, +} from './symbols'; const logger = createDebugLogger('plugin', 'litexml'); @@ -138,48 +143,6 @@ function wrapBlockModify(oldBlock: LexicalNode, editor: LexicalEditor, changeFn: newBlock.replace(diffNode, false); } -export const LITEXML_MODIFY_COMMAND = createCommand< - Array< - | { - action: 'insert'; - beforeId: string; - litexml: string; - } - | { - action: 'insert'; - afterId: string; - litexml: string; - } - | { - action: 'remove'; - id: string; - } - | { - action: 'modify'; - litexml: string | string[]; - } - > ->('LITEXML_MODIFY_COMMAND'); - -export const LITEXML_APPLY_COMMAND = createCommand<{ delay?: boolean; litexml: string | string[] }>( - 'LITEXML_APPLY_COMMAND', -); -export const LITEXML_REMOVE_COMMAND = createCommand<{ delay?: boolean; id: string }>( - 'LITEXML_REMOVE_COMMAND', -); -export const LITEXML_INSERT_COMMAND = createCommand< - | { - beforeId: string; - delay?: boolean; - litexml: string; - } - | { - afterId: string; - delay?: boolean; - litexml: string; - } ->('LITEXML_INSERT_COMMAND'); - export function registerLiteXMLCommand(editor: LexicalEditor, dataSource: LitexmlDataSource) { return mergeRegister( editor.registerCommand( @@ -570,3 +533,12 @@ function handleInsert( } }); } + +// Command identities live in the side-effect-free `./symbols` module so they +// keep a single runtime identity across the package's browser/node bundles. +export { + LITEXML_APPLY_COMMAND, + LITEXML_INSERT_COMMAND, + LITEXML_MODIFY_COMMAND, + LITEXML_REMOVE_COMMAND, +} from './symbols'; diff --git a/src/plugins/litexml/command/symbols.ts b/src/plugins/litexml/command/symbols.ts new file mode 100644 index 00000000..cf722c61 --- /dev/null +++ b/src/plugins/litexml/command/symbols.ts @@ -0,0 +1,78 @@ +import { createCommand } from 'lexical'; + +/** + * LiteXML command identities. + * + * These symbols are intentionally isolated in a side-effect-free module so they + * keep a SINGLE runtime identity across every entry of this package. + * + * Lexical's `dispatchCommand` matches command listeners by object reference, not + * by the string label. The package ships two independently-bundled entries — the + * browser build (`index` / `react` / `renderer`) and the node build (`headless`) + * — and if each entry inlined its own `createCommand(...)` call, dispatching a + * command obtained from one entry onto an editor registered by the other would + * silently no-op (different object identities, same label). + * + * By keeping every command in this one module — exposed verbatim through + * `@lobehub/editor/litexml-commands` and emitted as a shared chunk by both + * builds — a single object backs the command in any runtime, and the module is + * pure enough to be imported on the server without pulling in the DOM-dependent + * editor bundle. + */ + +export enum DiffAction { + Reject, + Accept, +} + +export const LITEXML_MODIFY_COMMAND = createCommand< + Array< + | { + action: 'insert'; + beforeId: string; + litexml: string; + } + | { + action: 'insert'; + afterId: string; + litexml: string; + } + | { + action: 'remove'; + id: string; + } + | { + action: 'modify'; + litexml: string | string[]; + } + > +>('LITEXML_MODIFY_COMMAND'); + +export const LITEXML_APPLY_COMMAND = createCommand<{ delay?: boolean; litexml: string | string[] }>( + 'LITEXML_APPLY_COMMAND', +); + +export const LITEXML_REMOVE_COMMAND = createCommand<{ delay?: boolean; id: string }>( + 'LITEXML_REMOVE_COMMAND', +); + +export const LITEXML_INSERT_COMMAND = createCommand< + | { + beforeId: string; + delay?: boolean; + litexml: string; + } + | { + afterId: string; + delay?: boolean; + litexml: string; + } +>('LITEXML_INSERT_COMMAND'); + +export const LITEXML_DIFFNODE_COMMAND = createCommand<{ action: DiffAction; nodeKey: string }>( + 'LITEXML_DIFFNODE_COMMAND', +); + +export const LITEXML_DIFFNODE_ALL_COMMAND = createCommand<{ action: DiffAction }>( + 'LITEXML_DIFFNODE_ALL_COMMAND', +); diff --git a/tsdown.config.ts b/tsdown.config.ts index b8405f2a..a31e584a 100644 --- a/tsdown.config.ts +++ b/tsdown.config.ts @@ -14,7 +14,13 @@ export default defineConfig([ ...commonConfig, clean: true, entry: { - headless: 'src/headless/index.ts', + 'headless': 'src/headless/index.ts', + // Emit the LiteXML command identities as their own chunk so the bundled + // node build references them instead of inlining a second copy. Both this + // entry and the unbundled browser build resolve to the same emitted + // `es/plugins/litexml/command/symbols.js`, giving the commands a single + // runtime identity (and a DOM-free import via `./litexml-commands`). + 'plugins/litexml/command/symbols': 'src/plugins/litexml/command/symbols.ts', }, outExtensions: () => ({ dts: '.d.ts', js: '.js' }), platform: 'node',