@llui/markdown-editor 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__llui_deps.json +3 -3
- package/dist/editor.d.ts +1 -1
- package/dist/editor.js +2 -2
- package/dist/editor.js.map +1 -1
- package/dist/state.d.ts +3 -3
- package/dist/state.js +3 -3
- package/dist/state.js.map +1 -1
- package/package.json +7 -7
package/dist/__llui_deps.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"placeholder",
|
|
18
18
|
"plugins",
|
|
19
19
|
"plugins.length",
|
|
20
|
-
"
|
|
20
|
+
"readonly",
|
|
21
21
|
"theme",
|
|
22
22
|
"toolbar",
|
|
23
23
|
"value"
|
|
@@ -177,7 +177,7 @@
|
|
|
177
177
|
{
|
|
178
178
|
"index": 0,
|
|
179
179
|
"reads": [
|
|
180
|
-
"
|
|
180
|
+
"readonly",
|
|
181
181
|
"value",
|
|
182
182
|
"value.length"
|
|
183
183
|
],
|
|
@@ -201,7 +201,7 @@
|
|
|
201
201
|
"id",
|
|
202
202
|
"overlay",
|
|
203
203
|
"query",
|
|
204
|
-
"
|
|
204
|
+
"readonly",
|
|
205
205
|
"type",
|
|
206
206
|
"value",
|
|
207
207
|
"wordCount",
|
package/dist/editor.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface EditorConfig {
|
|
|
13
13
|
/** Debounced markdown-emission window (ms). Default 300. */
|
|
14
14
|
changeDebounceMs?: number;
|
|
15
15
|
placeholder?: string;
|
|
16
|
-
|
|
16
|
+
readonly?: boolean;
|
|
17
17
|
/** Lexical theme class map. */
|
|
18
18
|
theme?: EditorThemeClasses;
|
|
19
19
|
/** Editor namespace (instance isolation). */
|
package/dist/editor.js
CHANGED
|
@@ -51,7 +51,7 @@ export function markdownEditor(config = {}) {
|
|
|
51
51
|
const seedValue = config.value ? config.value.peek() : (config.defaultValue ?? '');
|
|
52
52
|
// ── Composed TEA: core + plugin UI slices ──────────────────────────────────
|
|
53
53
|
const composedInit = () => {
|
|
54
|
-
const [core, effects] = init({ value: seedValue,
|
|
54
|
+
const [core, effects] = init({ value: seedValue, readonly: config.readonly ?? false });
|
|
55
55
|
const slices = {};
|
|
56
56
|
for (const { name, ui } of pluginUIs)
|
|
57
57
|
slices[name] = ui.init();
|
|
@@ -95,7 +95,7 @@ export function markdownEditor(config = {}) {
|
|
|
95
95
|
},
|
|
96
96
|
defaultValue: config.value ? undefined : (config.defaultValue ?? ''),
|
|
97
97
|
...(config.value ? { value: config.value } : {}),
|
|
98
|
-
|
|
98
|
+
readonly: state.at('readonly'),
|
|
99
99
|
...(config.changeDebounceMs !== undefined
|
|
100
100
|
? { changeDebounceMs: config.changeDebounceMs }
|
|
101
101
|
: {}),
|
package/dist/editor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editor.js","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"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","sourcesContent":["// `markdownEditor(config)` — the high-level component. Lexical owns the live\n// document; this wires the foreign seam to the markdown transformer converters,\n// surfaces the format state for the chrome, routes command intents back to the\n// live editor through effects, and COMPOSES plugin UI extensions (each plugin's\n// state slice + reducer + view + effects) into the single component.\n\nimport { $getRoot, $setSelection, type EditorThemeClasses, type LexicalEditor } from 'lexical'\nimport {\n $convertFromMarkdownString,\n $convertToMarkdownString,\n registerMarkdownShortcuts,\n} from '@lexical/markdown'\nimport { component, div, type Renderable, type Signal, type SignalComponentDef } from '@llui/dom'\nimport {\n lexicalForeign,\n registerDecoratorBridges,\n PROGRAMMATIC_TAG,\n type DecoratorBridge,\n} from '@llui/lexical'\nimport { corePlugin } from './plugins/core.js'\nimport { linkPlugin } from './plugins/link.js'\nimport { toolbar as renderToolbar } from './surfaces/toolbar.js'\nimport type { CommandItem, MarkdownPlugin } from './plugins/types.js'\nimport type { PluginUI } from './plugins/ui.js'\nimport { buildTransformers } from './transformers/registry.js'\nimport { computeFormatState } from './format.js'\nimport { makeOnEffect } from './effects.js'\nimport {\n countWords,\n init,\n update,\n type EditorEffect,\n type EditorMsg,\n type EditorOutMsg,\n type EditorState,\n type FormatState,\n} from './state.js'\n\nexport interface EditorConfig {\n /** Plugins composing the feature set; order defines transformer precedence.\n * Defaults to `[corePlugin(), linkPlugin()]` so the minimal editor has GFM + links. */\n plugins?: readonly MarkdownPlugin[]\n /** Initial markdown (uncontrolled seed). */\n defaultValue?: string\n /** Controlled: the consumer owns this signal; the editor follows it. */\n value?: Signal<string>\n /** Debounced markdown-emission window (ms). Default 300. */\n changeDebounceMs?: number\n placeholder?: string\n readOnly?: boolean\n /** Lexical theme class map. */\n theme?: EditorThemeClasses\n /** Editor namespace (instance isolation). */\n namespace?: string\n /** Outbound markdown (after debounce). */\n onChange?: (markdown: string) => void\n /** Outbound format surface (for chrome built outside this package). */\n onFormatChange?: (format: FormatState) => void\n /** Receives the live Lexical editor at mount (imperative access, collab hooks). */\n onReady?: (editor: LexicalEditor) => void\n /** Render the built-in toolbar above the editor. Default false (minimal). */\n toolbar?: boolean\n}\n\n/** Hooks the chrome layer (toolbar/menus) uses to compose around the editor. */\nexport interface EditorParts {\n /** The merged, surface-filtered command items. */\n items: readonly CommandItem[]\n /** Reactive format signal for `connect`-style toolbars. */\n format: Signal<FormatState>\n}\n\n/** Default plugin set when the consumer supplies none. */\nfunction defaultPlugins(): MarkdownPlugin[] {\n return [corePlugin(), linkPlugin()]\n}\n\n/**\n * Build the markdown editor component. Embed it with `mountApp(el, markdownEditor(...))`\n * or compose it inside a larger component.\n */\nexport function markdownEditor(\n config: EditorConfig = {},\n): SignalComponentDef<EditorState, EditorMsg, EditorEffect> {\n const plugins = config.plugins && config.plugins.length > 0 ? config.plugins : defaultPlugins()\n const transformers = buildTransformers(plugins)\n\n const items: CommandItem[] = plugins.flatMap((p) => p.items ?? [])\n const itemsById = new Map(items.map((i) => [i.id, i]))\n // Share the merged item list with plugins that want it (e.g. the slash menu).\n for (const plugin of plugins) plugin.onItems?.(items)\n const decorators: DecoratorBridge[] = plugins.flatMap((p) => p.decorators ?? [])\n const pluginUIs: Array<{ name: string; ui: PluginUI }> = plugins\n .filter((p): p is MarkdownPlugin & { ui: PluginUI } => p.ui !== undefined)\n .map((p) => ({ name: p.name, ui: p.ui }))\n const pluginUIByName = new Map(pluginUIs.map((p) => [p.name, p.ui]))\n\n // The live editor, captured at mount; effects dispatch through it.\n let editorRef: LexicalEditor | null = null\n const getEditor = (): LexicalEditor | null => editorRef\n\n const baseOnEffect = makeOnEffect(getEditor, itemsById, {\n onChange: config.onChange,\n onFormatChange: config.onFormatChange,\n applyValue: (editor, value) =>\n editor.update(\n () => {\n $convertFromMarkdownString(value, transformers)\n // Clear selection so the reconciler doesn't pull DOM focus into the\n // editor on an external push (e.g. typing in a bound source textarea).\n $setSelection(null)\n },\n { tag: PROGRAMMATIC_TAG },\n ),\n })\n\n const seedValue = config.value ? config.value.peek() : (config.defaultValue ?? '')\n\n // ── Composed TEA: core + plugin UI slices ──────────────────────────────────\n const composedInit = (): [EditorState, EditorEffect[]] => {\n const [core, effects] = init({ value: seedValue, readOnly: config.readOnly ?? false })\n const slices: Record<string, unknown> = {}\n for (const { name, ui } of pluginUIs) slices[name] = ui.init()\n return [{ ...core, plugins: slices }, effects]\n }\n\n const composedUpdate = (state: EditorState, msg: EditorMsg): [EditorState, EditorEffect[]] => {\n if (msg.type === 'plugin') {\n const ui = pluginUIByName.get(msg.name)\n if (!ui?.update) return [state, []]\n const result = ui.update(state.plugins[msg.name], msg.msg)\n const [slice, effects] = (Array.isArray(result) ? result : [result, []]) as [\n unknown,\n unknown[],\n ]\n return [\n { ...state, plugins: { ...state.plugins, [msg.name]: slice } },\n effects.map((effect) => ({ type: 'pluginEffect' as const, name: msg.name, effect })),\n ]\n }\n return update(state, msg)\n }\n\n const composedOnEffect = (\n effect: EditorEffect,\n api: { send: (msg: EditorMsg) => void; state: Signal<EditorState> },\n ): void => {\n if (effect.type === 'pluginEffect') {\n const ui = pluginUIByName.get(effect.name)\n ui?.onEffect?.(effect.effect, {\n editor: getEditor,\n send: (msg) => api.send({ type: 'plugin', name: effect.name, msg }),\n emit: (msg) => api.send(msg as EditorMsg),\n })\n return\n }\n baseOnEffect(effect, api)\n }\n\n const view = ({\n state,\n send,\n }: {\n state: Signal<EditorState>\n send: (msg: EditorMsg) => void\n }): Renderable => {\n const host = lexicalForeign<EditorOutMsg>({\n namespace: config.namespace ?? 'llui-markdown',\n theme: config.theme,\n plugins,\n serialize: (editor) =>\n editor.getEditorState().read(() => $convertToMarkdownString(transformers)),\n deserialize: (_editor, value) => {\n $convertFromMarkdownString(value, transformers)\n $setSelection(null)\n },\n defaultValue: config.value ? undefined : (config.defaultValue ?? ''),\n ...(config.value ? { value: config.value } : {}),\n readOnly: state.at('readOnly'),\n ...(config.changeDebounceMs !== undefined\n ? { changeDebounceMs: config.changeDebounceMs }\n : {}),\n register: (editor) => {\n const disposers = [registerMarkdownShortcuts(editor, transformers)]\n if (decorators.length > 0) disposers.push(registerDecoratorBridges(editor, decorators))\n return () => {\n for (const dispose of disposers) dispose()\n }\n },\n onReady: (editor) => {\n editorRef = editor\n if (config.placeholder) {\n editor.getRootElement()?.setAttribute('data-placeholder', config.placeholder)\n }\n config.onReady?.(editor)\n },\n onChange: (value) => send({ type: 'markdownChanged', value }),\n onSelectionChange: (ctx) => {\n const format = computeFormatState(ctx.editor, ctx)\n const text = ctx.editor.getEditorState().read(() => $getRoot().getTextContent())\n // Toggle an empty marker so CSS can show the placeholder.\n ctx.editor.getRootElement()?.setAttribute('data-empty', text === '' ? 'true' : 'false')\n send({ type: 'formatChanged', format, wordCount: countWords(text), charCount: text.length })\n },\n emit: (msg) => send(msg),\n })\n\n // Plugin view contributions (overlays/panels) — each gets its own slice + send.\n const pluginViews: Renderable = pluginUIs.flatMap(({ name, ui }) => {\n if (!ui.view) return []\n const rendered = ui.view({\n state: state.at(`plugins.${name}`),\n send: (msg) => send({ type: 'plugin', name, msg }),\n editor: getEditor,\n })\n return Array.isArray(rendered) ? rendered : [rendered]\n })\n\n if (!config.toolbar) return [host, ...pluginViews]\n return [\n div({ 'data-scope': 'md-editor', 'data-part': 'root' }, [\n renderToolbar({ format: state.at('format'), send, items }),\n div({ 'data-scope': 'md-editor', 'data-part': 'surface' }, [host]),\n ]),\n ...pluginViews,\n ]\n }\n\n return component<EditorState, EditorMsg, EditorEffect>({\n name: 'MarkdownEditor',\n init: composedInit,\n update: composedUpdate,\n view,\n onEffect: composedOnEffect,\n })\n}\n"]}
|
|
1
|
+
{"version":3,"file":"editor.js","sourceRoot":"","sources":["../src/editor.ts"],"names":[],"mappings":"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","sourcesContent":["// `markdownEditor(config)` — the high-level component. Lexical owns the live\n// document; this wires the foreign seam to the markdown transformer converters,\n// surfaces the format state for the chrome, routes command intents back to the\n// live editor through effects, and COMPOSES plugin UI extensions (each plugin's\n// state slice + reducer + view + effects) into the single component.\n\nimport { $getRoot, $setSelection, type EditorThemeClasses, type LexicalEditor } from 'lexical'\nimport {\n $convertFromMarkdownString,\n $convertToMarkdownString,\n registerMarkdownShortcuts,\n} from '@lexical/markdown'\nimport { component, div, type Renderable, type Signal, type SignalComponentDef } from '@llui/dom'\nimport {\n lexicalForeign,\n registerDecoratorBridges,\n PROGRAMMATIC_TAG,\n type DecoratorBridge,\n} from '@llui/lexical'\nimport { corePlugin } from './plugins/core.js'\nimport { linkPlugin } from './plugins/link.js'\nimport { toolbar as renderToolbar } from './surfaces/toolbar.js'\nimport type { CommandItem, MarkdownPlugin } from './plugins/types.js'\nimport type { PluginUI } from './plugins/ui.js'\nimport { buildTransformers } from './transformers/registry.js'\nimport { computeFormatState } from './format.js'\nimport { makeOnEffect } from './effects.js'\nimport {\n countWords,\n init,\n update,\n type EditorEffect,\n type EditorMsg,\n type EditorOutMsg,\n type EditorState,\n type FormatState,\n} from './state.js'\n\nexport interface EditorConfig {\n /** Plugins composing the feature set; order defines transformer precedence.\n * Defaults to `[corePlugin(), linkPlugin()]` so the minimal editor has GFM + links. */\n plugins?: readonly MarkdownPlugin[]\n /** Initial markdown (uncontrolled seed). */\n defaultValue?: string\n /** Controlled: the consumer owns this signal; the editor follows it. */\n value?: Signal<string>\n /** Debounced markdown-emission window (ms). Default 300. */\n changeDebounceMs?: number\n placeholder?: string\n readonly?: boolean\n /** Lexical theme class map. */\n theme?: EditorThemeClasses\n /** Editor namespace (instance isolation). */\n namespace?: string\n /** Outbound markdown (after debounce). */\n onChange?: (markdown: string) => void\n /** Outbound format surface (for chrome built outside this package). */\n onFormatChange?: (format: FormatState) => void\n /** Receives the live Lexical editor at mount (imperative access, collab hooks). */\n onReady?: (editor: LexicalEditor) => void\n /** Render the built-in toolbar above the editor. Default false (minimal). */\n toolbar?: boolean\n}\n\n/** Hooks the chrome layer (toolbar/menus) uses to compose around the editor. */\nexport interface EditorParts {\n /** The merged, surface-filtered command items. */\n items: readonly CommandItem[]\n /** Reactive format signal for `connect`-style toolbars. */\n format: Signal<FormatState>\n}\n\n/** Default plugin set when the consumer supplies none. */\nfunction defaultPlugins(): MarkdownPlugin[] {\n return [corePlugin(), linkPlugin()]\n}\n\n/**\n * Build the markdown editor component. Embed it with `mountApp(el, markdownEditor(...))`\n * or compose it inside a larger component.\n */\nexport function markdownEditor(\n config: EditorConfig = {},\n): SignalComponentDef<EditorState, EditorMsg, EditorEffect> {\n const plugins = config.plugins && config.plugins.length > 0 ? config.plugins : defaultPlugins()\n const transformers = buildTransformers(plugins)\n\n const items: CommandItem[] = plugins.flatMap((p) => p.items ?? [])\n const itemsById = new Map(items.map((i) => [i.id, i]))\n // Share the merged item list with plugins that want it (e.g. the slash menu).\n for (const plugin of plugins) plugin.onItems?.(items)\n const decorators: DecoratorBridge[] = plugins.flatMap((p) => p.decorators ?? [])\n const pluginUIs: Array<{ name: string; ui: PluginUI }> = plugins\n .filter((p): p is MarkdownPlugin & { ui: PluginUI } => p.ui !== undefined)\n .map((p) => ({ name: p.name, ui: p.ui }))\n const pluginUIByName = new Map(pluginUIs.map((p) => [p.name, p.ui]))\n\n // The live editor, captured at mount; effects dispatch through it.\n let editorRef: LexicalEditor | null = null\n const getEditor = (): LexicalEditor | null => editorRef\n\n const baseOnEffect = makeOnEffect(getEditor, itemsById, {\n onChange: config.onChange,\n onFormatChange: config.onFormatChange,\n applyValue: (editor, value) =>\n editor.update(\n () => {\n $convertFromMarkdownString(value, transformers)\n // Clear selection so the reconciler doesn't pull DOM focus into the\n // editor on an external push (e.g. typing in a bound source textarea).\n $setSelection(null)\n },\n { tag: PROGRAMMATIC_TAG },\n ),\n })\n\n const seedValue = config.value ? config.value.peek() : (config.defaultValue ?? '')\n\n // ── Composed TEA: core + plugin UI slices ──────────────────────────────────\n const composedInit = (): [EditorState, EditorEffect[]] => {\n const [core, effects] = init({ value: seedValue, readonly: config.readonly ?? false })\n const slices: Record<string, unknown> = {}\n for (const { name, ui } of pluginUIs) slices[name] = ui.init()\n return [{ ...core, plugins: slices }, effects]\n }\n\n const composedUpdate = (state: EditorState, msg: EditorMsg): [EditorState, EditorEffect[]] => {\n if (msg.type === 'plugin') {\n const ui = pluginUIByName.get(msg.name)\n if (!ui?.update) return [state, []]\n const result = ui.update(state.plugins[msg.name], msg.msg)\n const [slice, effects] = (Array.isArray(result) ? result : [result, []]) as [\n unknown,\n unknown[],\n ]\n return [\n { ...state, plugins: { ...state.plugins, [msg.name]: slice } },\n effects.map((effect) => ({ type: 'pluginEffect' as const, name: msg.name, effect })),\n ]\n }\n return update(state, msg)\n }\n\n const composedOnEffect = (\n effect: EditorEffect,\n api: { send: (msg: EditorMsg) => void; state: Signal<EditorState> },\n ): void => {\n if (effect.type === 'pluginEffect') {\n const ui = pluginUIByName.get(effect.name)\n ui?.onEffect?.(effect.effect, {\n editor: getEditor,\n send: (msg) => api.send({ type: 'plugin', name: effect.name, msg }),\n emit: (msg) => api.send(msg as EditorMsg),\n })\n return\n }\n baseOnEffect(effect, api)\n }\n\n const view = ({\n state,\n send,\n }: {\n state: Signal<EditorState>\n send: (msg: EditorMsg) => void\n }): Renderable => {\n const host = lexicalForeign<EditorOutMsg>({\n namespace: config.namespace ?? 'llui-markdown',\n theme: config.theme,\n plugins,\n serialize: (editor) =>\n editor.getEditorState().read(() => $convertToMarkdownString(transformers)),\n deserialize: (_editor, value) => {\n $convertFromMarkdownString(value, transformers)\n $setSelection(null)\n },\n defaultValue: config.value ? undefined : (config.defaultValue ?? ''),\n ...(config.value ? { value: config.value } : {}),\n readonly: state.at('readonly'),\n ...(config.changeDebounceMs !== undefined\n ? { changeDebounceMs: config.changeDebounceMs }\n : {}),\n register: (editor) => {\n const disposers = [registerMarkdownShortcuts(editor, transformers)]\n if (decorators.length > 0) disposers.push(registerDecoratorBridges(editor, decorators))\n return () => {\n for (const dispose of disposers) dispose()\n }\n },\n onReady: (editor) => {\n editorRef = editor\n if (config.placeholder) {\n editor.getRootElement()?.setAttribute('data-placeholder', config.placeholder)\n }\n config.onReady?.(editor)\n },\n onChange: (value) => send({ type: 'markdownChanged', value }),\n onSelectionChange: (ctx) => {\n const format = computeFormatState(ctx.editor, ctx)\n const text = ctx.editor.getEditorState().read(() => $getRoot().getTextContent())\n // Toggle an empty marker so CSS can show the placeholder.\n ctx.editor.getRootElement()?.setAttribute('data-empty', text === '' ? 'true' : 'false')\n send({ type: 'formatChanged', format, wordCount: countWords(text), charCount: text.length })\n },\n emit: (msg) => send(msg),\n })\n\n // Plugin view contributions (overlays/panels) — each gets its own slice + send.\n const pluginViews: Renderable = pluginUIs.flatMap(({ name, ui }) => {\n if (!ui.view) return []\n const rendered = ui.view({\n state: state.at(`plugins.${name}`),\n send: (msg) => send({ type: 'plugin', name, msg }),\n editor: getEditor,\n })\n return Array.isArray(rendered) ? rendered : [rendered]\n })\n\n if (!config.toolbar) return [host, ...pluginViews]\n return [\n div({ 'data-scope': 'md-editor', 'data-part': 'root' }, [\n renderToolbar({ format: state.at('format'), send, items }),\n div({ 'data-scope': 'md-editor', 'data-part': 'surface' }, [host]),\n ]),\n ...pluginViews,\n ]\n }\n\n return component<EditorState, EditorMsg, EditorEffect>({\n name: 'MarkdownEditor',\n init: composedInit,\n update: composedUpdate,\n view,\n onEffect: composedOnEffect,\n })\n}\n"]}
|
package/dist/state.d.ts
CHANGED
|
@@ -34,7 +34,7 @@ export interface EditorState {
|
|
|
34
34
|
/** Per-plugin UI state slices, keyed by plugin name (see {@link PluginUI}). */
|
|
35
35
|
plugins: Record<string, unknown>;
|
|
36
36
|
dirty: boolean;
|
|
37
|
-
|
|
37
|
+
readonly: boolean;
|
|
38
38
|
}
|
|
39
39
|
export type EditorMsg = {
|
|
40
40
|
type: 'markdownChanged';
|
|
@@ -62,7 +62,7 @@ export type EditorMsg = {
|
|
|
62
62
|
query: string;
|
|
63
63
|
} | {
|
|
64
64
|
type: 'setReadOnly';
|
|
65
|
-
|
|
65
|
+
readonly: boolean;
|
|
66
66
|
}
|
|
67
67
|
/** Route a message to a plugin's UI reducer (see {@link PluginUI}). */
|
|
68
68
|
| {
|
|
@@ -96,7 +96,7 @@ export type EditorEffect = {
|
|
|
96
96
|
};
|
|
97
97
|
export interface InitOptions {
|
|
98
98
|
value: string;
|
|
99
|
-
|
|
99
|
+
readonly: boolean;
|
|
100
100
|
}
|
|
101
101
|
export declare function init(opts: InitOptions): [EditorState, EditorEffect[]];
|
|
102
102
|
export declare function update(state: EditorState, msg: EditorMsg): [EditorState, EditorEffect[]];
|
package/dist/state.js
CHANGED
|
@@ -23,7 +23,7 @@ export function init(opts) {
|
|
|
23
23
|
ui: { activeOverlay: 'none', slashQuery: '', menu: { x: 0, y: 0 } },
|
|
24
24
|
plugins: {},
|
|
25
25
|
dirty: false,
|
|
26
|
-
|
|
26
|
+
readonly: opts.readonly,
|
|
27
27
|
},
|
|
28
28
|
[],
|
|
29
29
|
];
|
|
@@ -81,9 +81,9 @@ export function update(state, msg) {
|
|
|
81
81
|
return [{ ...state, ui: { ...state.ui, slashQuery: msg.query } }, []];
|
|
82
82
|
}
|
|
83
83
|
case 'setReadOnly': {
|
|
84
|
-
if (state.
|
|
84
|
+
if (state.readonly === msg.readonly)
|
|
85
85
|
return [state, []];
|
|
86
|
-
return [{ ...state,
|
|
86
|
+
return [{ ...state, readonly: msg.readonly }, []];
|
|
87
87
|
}
|
|
88
88
|
case 'plugin': {
|
|
89
89
|
// Plugin messages are routed by the host's composed reducer (it holds the
|
package/dist/state.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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","sourcesContent":["// The editor's TEA state, messages, effects, and pure reducer. Lexical owns the\n// live document; this state holds JSON-serializable mirrors/derivations only, so\n// `update` stays pure and DOM-free (fully unit-testable).\n\nimport type { Alignment } from '@llui/lexical'\n\n/** The block kind at the selection — base rich-text kinds plus list/code,\n * resolved by the markdown layer. */\nexport type BlockType =\n | 'paragraph'\n | 'h1'\n | 'h2'\n | 'h3'\n | 'h4'\n | 'h5'\n | 'h6'\n | 'quote'\n | 'code'\n | 'bullet'\n | 'number'\n | 'check'\n | 'other'\n\n/** The toolbar-facing format surface at the current selection (all primitives). */\nexport interface FormatState {\n bold: boolean\n italic: boolean\n strikethrough: boolean\n code: boolean\n link: boolean\n blockType: BlockType\n alignment: Alignment\n canUndo: boolean\n canRedo: boolean\n}\n\nexport const EMPTY_FORMAT: FormatState = {\n bold: false,\n italic: false,\n strikethrough: false,\n code: false,\n link: false,\n blockType: 'paragraph',\n alignment: null,\n canUndo: false,\n canRedo: false,\n}\n\n/** Which floating surface is currently open. */\nexport type OverlayKind = 'none' | 'floating' | 'slash' | 'context' | 'link'\n\nexport interface EditorState {\n /** Last serialized markdown (mirror of the live document). */\n value: string\n format: FormatState\n wordCount: number\n charCount: number\n ui: {\n activeOverlay: OverlayKind\n slashQuery: string\n menu: { x: number; y: number }\n }\n /** Per-plugin UI state slices, keyed by plugin name (see {@link PluginUI}). */\n plugins: Record<string, unknown>\n dirty: boolean\n
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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","sourcesContent":["// The editor's TEA state, messages, effects, and pure reducer. Lexical owns the\n// live document; this state holds JSON-serializable mirrors/derivations only, so\n// `update` stays pure and DOM-free (fully unit-testable).\n\nimport type { Alignment } from '@llui/lexical'\n\n/** The block kind at the selection — base rich-text kinds plus list/code,\n * resolved by the markdown layer. */\nexport type BlockType =\n | 'paragraph'\n | 'h1'\n | 'h2'\n | 'h3'\n | 'h4'\n | 'h5'\n | 'h6'\n | 'quote'\n | 'code'\n | 'bullet'\n | 'number'\n | 'check'\n | 'other'\n\n/** The toolbar-facing format surface at the current selection (all primitives). */\nexport interface FormatState {\n bold: boolean\n italic: boolean\n strikethrough: boolean\n code: boolean\n link: boolean\n blockType: BlockType\n alignment: Alignment\n canUndo: boolean\n canRedo: boolean\n}\n\nexport const EMPTY_FORMAT: FormatState = {\n bold: false,\n italic: false,\n strikethrough: false,\n code: false,\n link: false,\n blockType: 'paragraph',\n alignment: null,\n canUndo: false,\n canRedo: false,\n}\n\n/** Which floating surface is currently open. */\nexport type OverlayKind = 'none' | 'floating' | 'slash' | 'context' | 'link'\n\nexport interface EditorState {\n /** Last serialized markdown (mirror of the live document). */\n value: string\n format: FormatState\n wordCount: number\n charCount: number\n ui: {\n activeOverlay: OverlayKind\n slashQuery: string\n menu: { x: number; y: number }\n }\n /** Per-plugin UI state slices, keyed by plugin name (see {@link PluginUI}). */\n plugins: Record<string, unknown>\n dirty: boolean\n readonly: boolean\n}\n\nexport type EditorMsg =\n | { type: 'markdownChanged'; value: string }\n | { type: 'formatChanged'; format: FormatState; wordCount: number; charCount: number }\n | { type: 'runCommand'; id: string }\n | { type: 'setValue'; value: string }\n | { type: 'openOverlay'; overlay: OverlayKind; x?: number; y?: number }\n | { type: 'closeOverlay' }\n | { type: 'slashQuery'; query: string }\n | { type: 'setReadOnly'; readonly: boolean }\n /** Route a message to a plugin's UI reducer (see {@link PluginUI}). */\n | { type: 'plugin'; name: string; msg: unknown }\n\n/** The subset of messages a plugin may emit through its `PluginContext` (e.g. a\n * `register` listener routing an editor event into its own plugin UI). */\nexport type EditorOutMsg = Extract<\n EditorMsg,\n { type: 'openOverlay' | 'closeOverlay' | 'slashQuery' | 'plugin' }\n>\n\nexport type EditorEffect =\n | { type: 'execCommand'; id: string }\n | { type: 'applyValue'; value: string }\n | { type: 'emitChange'; value: string }\n | { type: 'emitFormat'; format: FormatState }\n /** An effect produced by a plugin's UI reducer (see {@link PluginUI}). */\n | { type: 'pluginEffect'; name: string; effect: unknown }\n\nexport interface InitOptions {\n value: string\n readonly: boolean\n}\n\nexport function init(opts: InitOptions): [EditorState, EditorEffect[]] {\n const wordCount = countWords(opts.value)\n return [\n {\n value: opts.value,\n format: EMPTY_FORMAT,\n wordCount,\n charCount: opts.value.length,\n ui: { activeOverlay: 'none', slashQuery: '', menu: { x: 0, y: 0 } },\n plugins: {},\n dirty: false,\n readonly: opts.readonly,\n },\n [],\n ]\n}\n\nexport function update(state: EditorState, msg: EditorMsg): [EditorState, EditorEffect[]] {\n switch (msg.type) {\n case 'markdownChanged': {\n // Idempotent: re-emitting the current value is a no-op (echo safety).\n if (msg.value === state.value) return [state, []]\n return [\n { ...state, value: msg.value, dirty: true },\n [{ type: 'emitChange', value: msg.value }],\n ]\n }\n case 'formatChanged': {\n return [\n { ...state, format: msg.format, wordCount: msg.wordCount, charCount: msg.charCount },\n [{ type: 'emitFormat', format: msg.format }],\n ]\n }\n case 'runCommand': {\n return [state, [{ type: 'execCommand', id: msg.id }]]\n }\n case 'setValue': {\n // External markdown push (via the component handle). Idempotent; does not\n // re-emit onChange (the consumer already owns this value).\n if (msg.value === state.value) return [state, []]\n return [\n { ...state, value: msg.value, dirty: true },\n [{ type: 'applyValue', value: msg.value }],\n ]\n }\n case 'openOverlay': {\n return [\n {\n ...state,\n ui: {\n ...state.ui,\n activeOverlay: msg.overlay,\n slashQuery: msg.overlay === 'slash' ? '' : state.ui.slashQuery,\n menu: { x: msg.x ?? state.ui.menu.x, y: msg.y ?? state.ui.menu.y },\n },\n },\n [],\n ]\n }\n case 'closeOverlay': {\n if (state.ui.activeOverlay === 'none') return [state, []]\n return [{ ...state, ui: { ...state.ui, activeOverlay: 'none', slashQuery: '' } }, []]\n }\n case 'slashQuery': {\n return [{ ...state, ui: { ...state.ui, slashQuery: msg.query } }, []]\n }\n case 'setReadOnly': {\n if (state.readonly === msg.readonly) return [state, []]\n return [{ ...state, readonly: msg.readonly }, []]\n }\n case 'plugin': {\n // Plugin messages are routed by the host's composed reducer (it holds the\n // plugin registry); the pure core reducer treats them as a no-op.\n return [state, []]\n }\n }\n}\n\n/** Count whitespace-delimited words (shared by init and the format handler). */\nexport function countWords(text: string): number {\n const trimmed = text.trim()\n return trimmed === '' ? 0 : trimmed.split(/\\s+/).length\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llui/markdown-editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"./styles/editor.css": "./dist/styles/editor.css"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"@llui/dom": "^0.
|
|
28
|
-
"@llui/lexical": "^0.
|
|
29
|
-
"@llui/components": "^0.
|
|
27
|
+
"@llui/dom": "^0.10.0",
|
|
28
|
+
"@llui/lexical": "^0.2.0",
|
|
29
|
+
"@llui/components": "^0.11.0",
|
|
30
30
|
"lexical": "^0.45.0",
|
|
31
31
|
"@lexical/markdown": "^0.45.0",
|
|
32
32
|
"@lexical/rich-text": "^0.45.0",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"typescript": "^6.0.0",
|
|
51
51
|
"vitest": "^4.1.2",
|
|
52
52
|
"@lexical/code-core": "^0.45.0",
|
|
53
|
-
"@llui/dom": "0.
|
|
54
|
-
"@llui/lexical": "0.
|
|
55
|
-
"@llui/components": "0.
|
|
53
|
+
"@llui/dom": "0.10.0",
|
|
54
|
+
"@llui/lexical": "0.2.0",
|
|
55
|
+
"@llui/components": "0.11.0"
|
|
56
56
|
},
|
|
57
57
|
"sideEffects": [
|
|
58
58
|
"./dist/styles/editor.css"
|