@llui/markdown-editor 0.1.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/LICENSE +21 -0
- package/dist/__llui_deps.json +252 -0
- package/dist/editor.d.ts +42 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +157 -0
- package/dist/editor.js.map +1 -0
- package/dist/effects.d.ts +17 -0
- package/dist/effects.d.ts.map +1 -0
- package/dist/effects.js +33 -0
- package/dist/effects.js.map +1 -0
- package/dist/format.d.ts +6 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +51 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/callout.d.ts +15 -0
- package/dist/plugins/callout.d.ts.map +1 -0
- package/dist/plugins/callout.js +151 -0
- package/dist/plugins/callout.js.map +1 -0
- package/dist/plugins/context-menu.d.ts +3 -0
- package/dist/plugins/context-menu.d.ts.map +1 -0
- package/dist/plugins/context-menu.js +93 -0
- package/dist/plugins/context-menu.js.map +1 -0
- package/dist/plugins/core.d.ts +7 -0
- package/dist/plugins/core.d.ts.map +1 -0
- package/dist/plugins/core.js +189 -0
- package/dist/plugins/core.js.map +1 -0
- package/dist/plugins/emoji.d.ts +9 -0
- package/dist/plugins/emoji.d.ts.map +1 -0
- package/dist/plugins/emoji.js +50 -0
- package/dist/plugins/emoji.js.map +1 -0
- package/dist/plugins/floating-toolbar.d.ts +3 -0
- package/dist/plugins/floating-toolbar.d.ts.map +1 -0
- package/dist/plugins/floating-toolbar.js +137 -0
- package/dist/plugins/floating-toolbar.js.map +1 -0
- package/dist/plugins/hr.d.ts +5 -0
- package/dist/plugins/hr.d.ts.map +1 -0
- package/dist/plugins/hr.js +46 -0
- package/dist/plugins/hr.js.map +1 -0
- package/dist/plugins/image.d.ts +8 -0
- package/dist/plugins/image.d.ts.map +1 -0
- package/dist/plugins/image.js +173 -0
- package/dist/plugins/image.js.map +1 -0
- package/dist/plugins/link.d.ts +7 -0
- package/dist/plugins/link.d.ts.map +1 -0
- package/dist/plugins/link.js +100 -0
- package/dist/plugins/link.js.map +1 -0
- package/dist/plugins/math.d.ts +8 -0
- package/dist/plugins/math.d.ts.map +1 -0
- package/dist/plugins/math.js +81 -0
- package/dist/plugins/math.js.map +1 -0
- package/dist/plugins/mention.d.ts +11 -0
- package/dist/plugins/mention.d.ts.map +1 -0
- package/dist/plugins/mention.js +163 -0
- package/dist/plugins/mention.js.map +1 -0
- package/dist/plugins/mermaid.d.ts +8 -0
- package/dist/plugins/mermaid.d.ts.map +1 -0
- package/dist/plugins/mermaid.js +92 -0
- package/dist/plugins/mermaid.js.map +1 -0
- package/dist/plugins/overlay.d.ts +46 -0
- package/dist/plugins/overlay.d.ts.map +1 -0
- package/dist/plugins/overlay.js +83 -0
- package/dist/plugins/overlay.js.map +1 -0
- package/dist/plugins/slash.d.ts +3 -0
- package/dist/plugins/slash.d.ts.map +1 -0
- package/dist/plugins/slash.js +167 -0
- package/dist/plugins/slash.js.map +1 -0
- package/dist/plugins/table.d.ts +3 -0
- package/dist/plugins/table.d.ts.map +1 -0
- package/dist/plugins/table.js +227 -0
- package/dist/plugins/table.js.map +1 -0
- package/dist/plugins/types.d.ts +44 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +4 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/plugins/ui.d.ts +44 -0
- package/dist/plugins/ui.d.ts.map +1 -0
- package/dist/plugins/ui.js +34 -0
- package/dist/plugins/ui.js.map +1 -0
- package/dist/state.d.ts +105 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +100 -0
- package/dist/state.js.map +1 -0
- package/dist/styles/editor.css +517 -0
- package/dist/surfaces/link-dialog.d.ts +19 -0
- package/dist/surfaces/link-dialog.d.ts.map +1 -0
- package/dist/surfaces/link-dialog.js +45 -0
- package/dist/surfaces/link-dialog.js.map +1 -0
- package/dist/surfaces/toolbar.d.ts +48 -0
- package/dist/surfaces/toolbar.d.ts.map +1 -0
- package/dist/surfaces/toolbar.js +134 -0
- package/dist/surfaces/toolbar.js.map +1 -0
- package/dist/transformers/gfm.d.ts +7 -0
- package/dist/transformers/gfm.d.ts.map +1 -0
- package/dist/transformers/gfm.js +41 -0
- package/dist/transformers/gfm.js.map +1 -0
- package/dist/transformers/registry.d.ts +9 -0
- package/dist/transformers/registry.d.ts.map +1 -0
- package/dist/transformers/registry.js +43 -0
- package/dist/transformers/registry.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Plugin UI/state extensions — the seam that makes stateful, UI-bearing features
|
|
2
|
+
// (link editor, slash menu, @mentions, …) into plugins instead of core built-ins.
|
|
3
|
+
//
|
|
4
|
+
// A plugin may contribute a small TEA module: a namespaced state slice (stored
|
|
5
|
+
// under `state.plugins[name]`), a reducer, a view (overlays/panels rendered by
|
|
6
|
+
// the host), and effects (handled with live-editor access). Types are erased at
|
|
7
|
+
// the registry boundary via {@link definePluginUI}, which keeps each plugin's
|
|
8
|
+
// `State`/`Msg`/`Effect` fully typed at the definition site.
|
|
9
|
+
/**
|
|
10
|
+
* Author a plugin UI module with full `State`/`Msg`/`Effect` types, erased for
|
|
11
|
+
* storage. The casts are confined to this boundary (the host only knows
|
|
12
|
+
* `unknown`), exactly like the decorator bridge.
|
|
13
|
+
*/
|
|
14
|
+
export function definePluginUI(spec) {
|
|
15
|
+
return {
|
|
16
|
+
init: spec.init,
|
|
17
|
+
update: spec.update ? (state, msg) => spec.update(state, msg) : undefined,
|
|
18
|
+
view: spec.view
|
|
19
|
+
? (args) => spec.view({
|
|
20
|
+
state: args.state,
|
|
21
|
+
send: args.send,
|
|
22
|
+
editor: args.editor,
|
|
23
|
+
})
|
|
24
|
+
: undefined,
|
|
25
|
+
onEffect: spec.onEffect
|
|
26
|
+
? (effect, ctx) => spec.onEffect(effect, {
|
|
27
|
+
editor: ctx.editor,
|
|
28
|
+
send: ctx.send,
|
|
29
|
+
emit: ctx.emit,
|
|
30
|
+
})
|
|
31
|
+
: undefined,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=ui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../../src/plugins/ui.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,kFAAkF;AAClF,EAAE;AACF,+EAA+E;AAC/E,+EAA+E;AAC/E,gFAAgF;AAChF,8EAA8E;AAC9E,6DAA6D;AA6C7D;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAkB,IAA2B;IACzE,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,MAAO,CAAC,KAAU,EAAE,GAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;QACpF,IAAI,EAAE,IAAI,CAAC,IAAI;YACb,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CACP,IAAI,CAAC,IAAK,CAAC;gBACT,KAAK,EAAE,IAAI,CAAC,KAAkB;gBAC9B,IAAI,EAAE,IAAI,CAAC,IAAwB;gBACnC,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB,CAAC;YACN,CAAC,CAAC,SAAS;QACb,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACrB,CAAC,CAAC,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CACd,IAAI,CAAC,QAAS,CAAC,MAAW,EAAE;gBAC1B,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,IAAI,EAAE,GAAG,CAAC,IAAwB;gBAClC,IAAI,EAAE,GAAG,CAAC,IAAI;aACf,CAAC;YACN,CAAC,CAAC,SAAS;KACd,CAAA;AACH,CAAC","sourcesContent":["// Plugin UI/state extensions — the seam that makes stateful, UI-bearing features\n// (link editor, slash menu, @mentions, …) into plugins instead of core built-ins.\n//\n// A plugin may contribute a small TEA module: a namespaced state slice (stored\n// under `state.plugins[name]`), a reducer, a view (overlays/panels rendered by\n// the host), and effects (handled with live-editor access). Types are erased at\n// the registry boundary via {@link definePluginUI}, which keeps each plugin's\n// `State`/`Msg`/`Effect` fully typed at the definition site.\n\nimport type { LexicalEditor } from 'lexical'\nimport type { Renderable, Signal } from '@llui/dom'\n\n/** Context for a plugin's `onEffect` — reach the live editor and dispatch back. */\nexport interface PluginEffectContext<M> {\n /** The live Lexical editor (null before mount). */\n editor: () => LexicalEditor | null\n /** Dispatch a message back into this plugin. */\n send: (msg: M) => void\n /** Dispatch a host editor message (e.g. `{type:'runCommand', id}`). */\n emit: (msg: unknown) => void\n}\n\n/** Args for a plugin's `view` — its reactive state slice + a scoped dispatcher. */\nexport interface PluginViewArgs<S, M> {\n state: Signal<S>\n send: (msg: M) => void\n editor: () => LexicalEditor | null\n}\n\n/** A typed plugin UI module (authored via {@link definePluginUI}). */\nexport interface PluginUISpec<S, M, E = never> {\n /** Initial slice state (JSON-serializable). */\n init: () => S\n /** Pure reducer over the slice; may return effects. */\n update?: (state: S, msg: M) => S | [S, E[]]\n /** View contribution (overlays/panels), rendered by the host. */\n view?: (args: PluginViewArgs<S, M>) => Renderable\n /** Effect handler with live-editor access + host dispatch. */\n onEffect?: (effect: E, ctx: PluginEffectContext<M>) => void\n}\n\n/** The host message type a plugin effect may emit (the editor's full Msg). */\nexport type HostEmit = (msg: unknown) => void\n\n/** The type-erased form stored on a plugin and consumed by the host. */\nexport interface PluginUI {\n init: () => unknown\n update?: (state: unknown, msg: unknown) => unknown | [unknown, unknown[]]\n view?: (args: PluginViewArgs<unknown, unknown>) => Renderable\n onEffect?: (effect: unknown, ctx: PluginEffectContext<unknown>) => void\n}\n\n/**\n * Author a plugin UI module with full `State`/`Msg`/`Effect` types, erased for\n * storage. The casts are confined to this boundary (the host only knows\n * `unknown`), exactly like the decorator bridge.\n */\nexport function definePluginUI<S, M, E = never>(spec: PluginUISpec<S, M, E>): PluginUI {\n return {\n init: spec.init,\n update: spec.update ? (state, msg) => spec.update!(state as S, msg as M) : undefined,\n view: spec.view\n ? (args) =>\n spec.view!({\n state: args.state as Signal<S>,\n send: args.send as (msg: M) => void,\n editor: args.editor,\n })\n : undefined,\n onEffect: spec.onEffect\n ? (effect, ctx) =>\n spec.onEffect!(effect as E, {\n editor: ctx.editor,\n send: ctx.send as (msg: M) => void,\n emit: ctx.emit,\n })\n : undefined,\n }\n}\n"]}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import type { Alignment } from '@llui/lexical';
|
|
2
|
+
/** The block kind at the selection — base rich-text kinds plus list/code,
|
|
3
|
+
* resolved by the markdown layer. */
|
|
4
|
+
export type BlockType = 'paragraph' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'quote' | 'code' | 'bullet' | 'number' | 'check' | 'other';
|
|
5
|
+
/** The toolbar-facing format surface at the current selection (all primitives). */
|
|
6
|
+
export interface FormatState {
|
|
7
|
+
bold: boolean;
|
|
8
|
+
italic: boolean;
|
|
9
|
+
strikethrough: boolean;
|
|
10
|
+
code: boolean;
|
|
11
|
+
link: boolean;
|
|
12
|
+
blockType: BlockType;
|
|
13
|
+
alignment: Alignment;
|
|
14
|
+
canUndo: boolean;
|
|
15
|
+
canRedo: boolean;
|
|
16
|
+
}
|
|
17
|
+
export declare const EMPTY_FORMAT: FormatState;
|
|
18
|
+
/** Which floating surface is currently open. */
|
|
19
|
+
export type OverlayKind = 'none' | 'floating' | 'slash' | 'context' | 'link';
|
|
20
|
+
export interface EditorState {
|
|
21
|
+
/** Last serialized markdown (mirror of the live document). */
|
|
22
|
+
value: string;
|
|
23
|
+
format: FormatState;
|
|
24
|
+
wordCount: number;
|
|
25
|
+
charCount: number;
|
|
26
|
+
ui: {
|
|
27
|
+
activeOverlay: OverlayKind;
|
|
28
|
+
slashQuery: string;
|
|
29
|
+
menu: {
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/** Per-plugin UI state slices, keyed by plugin name (see {@link PluginUI}). */
|
|
35
|
+
plugins: Record<string, unknown>;
|
|
36
|
+
dirty: boolean;
|
|
37
|
+
readOnly: boolean;
|
|
38
|
+
}
|
|
39
|
+
export type EditorMsg = {
|
|
40
|
+
type: 'markdownChanged';
|
|
41
|
+
value: string;
|
|
42
|
+
} | {
|
|
43
|
+
type: 'formatChanged';
|
|
44
|
+
format: FormatState;
|
|
45
|
+
wordCount: number;
|
|
46
|
+
charCount: number;
|
|
47
|
+
} | {
|
|
48
|
+
type: 'runCommand';
|
|
49
|
+
id: string;
|
|
50
|
+
} | {
|
|
51
|
+
type: 'setValue';
|
|
52
|
+
value: string;
|
|
53
|
+
} | {
|
|
54
|
+
type: 'openOverlay';
|
|
55
|
+
overlay: OverlayKind;
|
|
56
|
+
x?: number;
|
|
57
|
+
y?: number;
|
|
58
|
+
} | {
|
|
59
|
+
type: 'closeOverlay';
|
|
60
|
+
} | {
|
|
61
|
+
type: 'slashQuery';
|
|
62
|
+
query: string;
|
|
63
|
+
} | {
|
|
64
|
+
type: 'setReadOnly';
|
|
65
|
+
readOnly: boolean;
|
|
66
|
+
}
|
|
67
|
+
/** Route a message to a plugin's UI reducer (see {@link PluginUI}). */
|
|
68
|
+
| {
|
|
69
|
+
type: 'plugin';
|
|
70
|
+
name: string;
|
|
71
|
+
msg: unknown;
|
|
72
|
+
};
|
|
73
|
+
/** The subset of messages a plugin may emit through its `PluginContext` (e.g. a
|
|
74
|
+
* `register` listener routing an editor event into its own plugin UI). */
|
|
75
|
+
export type EditorOutMsg = Extract<EditorMsg, {
|
|
76
|
+
type: 'openOverlay' | 'closeOverlay' | 'slashQuery' | 'plugin';
|
|
77
|
+
}>;
|
|
78
|
+
export type EditorEffect = {
|
|
79
|
+
type: 'execCommand';
|
|
80
|
+
id: string;
|
|
81
|
+
} | {
|
|
82
|
+
type: 'applyValue';
|
|
83
|
+
value: string;
|
|
84
|
+
} | {
|
|
85
|
+
type: 'emitChange';
|
|
86
|
+
value: string;
|
|
87
|
+
} | {
|
|
88
|
+
type: 'emitFormat';
|
|
89
|
+
format: FormatState;
|
|
90
|
+
}
|
|
91
|
+
/** An effect produced by a plugin's UI reducer (see {@link PluginUI}). */
|
|
92
|
+
| {
|
|
93
|
+
type: 'pluginEffect';
|
|
94
|
+
name: string;
|
|
95
|
+
effect: unknown;
|
|
96
|
+
};
|
|
97
|
+
export interface InitOptions {
|
|
98
|
+
value: string;
|
|
99
|
+
readOnly: boolean;
|
|
100
|
+
}
|
|
101
|
+
export declare function init(opts: InitOptions): [EditorState, EditorEffect[]];
|
|
102
|
+
export declare function update(state: EditorState, msg: EditorMsg): [EditorState, EditorEffect[]];
|
|
103
|
+
/** Count whitespace-delimited words (shared by init and the format handler). */
|
|
104
|
+
export declare function countWords(text: string): number;
|
|
105
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"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"}
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// The editor's TEA state, messages, effects, and pure reducer. Lexical owns the
|
|
2
|
+
// live document; this state holds JSON-serializable mirrors/derivations only, so
|
|
3
|
+
// `update` stays pure and DOM-free (fully unit-testable).
|
|
4
|
+
export const EMPTY_FORMAT = {
|
|
5
|
+
bold: false,
|
|
6
|
+
italic: false,
|
|
7
|
+
strikethrough: false,
|
|
8
|
+
code: false,
|
|
9
|
+
link: false,
|
|
10
|
+
blockType: 'paragraph',
|
|
11
|
+
alignment: null,
|
|
12
|
+
canUndo: false,
|
|
13
|
+
canRedo: false,
|
|
14
|
+
};
|
|
15
|
+
export function init(opts) {
|
|
16
|
+
const wordCount = countWords(opts.value);
|
|
17
|
+
return [
|
|
18
|
+
{
|
|
19
|
+
value: opts.value,
|
|
20
|
+
format: EMPTY_FORMAT,
|
|
21
|
+
wordCount,
|
|
22
|
+
charCount: opts.value.length,
|
|
23
|
+
ui: { activeOverlay: 'none', slashQuery: '', menu: { x: 0, y: 0 } },
|
|
24
|
+
plugins: {},
|
|
25
|
+
dirty: false,
|
|
26
|
+
readOnly: opts.readOnly,
|
|
27
|
+
},
|
|
28
|
+
[],
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
export function update(state, msg) {
|
|
32
|
+
switch (msg.type) {
|
|
33
|
+
case 'markdownChanged': {
|
|
34
|
+
// Idempotent: re-emitting the current value is a no-op (echo safety).
|
|
35
|
+
if (msg.value === state.value)
|
|
36
|
+
return [state, []];
|
|
37
|
+
return [
|
|
38
|
+
{ ...state, value: msg.value, dirty: true },
|
|
39
|
+
[{ type: 'emitChange', value: msg.value }],
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
case 'formatChanged': {
|
|
43
|
+
return [
|
|
44
|
+
{ ...state, format: msg.format, wordCount: msg.wordCount, charCount: msg.charCount },
|
|
45
|
+
[{ type: 'emitFormat', format: msg.format }],
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
case 'runCommand': {
|
|
49
|
+
return [state, [{ type: 'execCommand', id: msg.id }]];
|
|
50
|
+
}
|
|
51
|
+
case 'setValue': {
|
|
52
|
+
// External markdown push (via the component handle). Idempotent; does not
|
|
53
|
+
// re-emit onChange (the consumer already owns this value).
|
|
54
|
+
if (msg.value === state.value)
|
|
55
|
+
return [state, []];
|
|
56
|
+
return [
|
|
57
|
+
{ ...state, value: msg.value, dirty: true },
|
|
58
|
+
[{ type: 'applyValue', value: msg.value }],
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
case 'openOverlay': {
|
|
62
|
+
return [
|
|
63
|
+
{
|
|
64
|
+
...state,
|
|
65
|
+
ui: {
|
|
66
|
+
...state.ui,
|
|
67
|
+
activeOverlay: msg.overlay,
|
|
68
|
+
slashQuery: msg.overlay === 'slash' ? '' : state.ui.slashQuery,
|
|
69
|
+
menu: { x: msg.x ?? state.ui.menu.x, y: msg.y ?? state.ui.menu.y },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
[],
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
case 'closeOverlay': {
|
|
76
|
+
if (state.ui.activeOverlay === 'none')
|
|
77
|
+
return [state, []];
|
|
78
|
+
return [{ ...state, ui: { ...state.ui, activeOverlay: 'none', slashQuery: '' } }, []];
|
|
79
|
+
}
|
|
80
|
+
case 'slashQuery': {
|
|
81
|
+
return [{ ...state, ui: { ...state.ui, slashQuery: msg.query } }, []];
|
|
82
|
+
}
|
|
83
|
+
case 'setReadOnly': {
|
|
84
|
+
if (state.readOnly === msg.readOnly)
|
|
85
|
+
return [state, []];
|
|
86
|
+
return [{ ...state, readOnly: msg.readOnly }, []];
|
|
87
|
+
}
|
|
88
|
+
case 'plugin': {
|
|
89
|
+
// Plugin messages are routed by the host's composed reducer (it holds the
|
|
90
|
+
// plugin registry); the pure core reducer treats them as a no-op.
|
|
91
|
+
return [state, []];
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** Count whitespace-delimited words (shared by init and the format handler). */
|
|
96
|
+
export function countWords(text) {
|
|
97
|
+
const trimmed = text.trim();
|
|
98
|
+
return trimmed === '' ? 0 : trimmed.split(/\s+/).length;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +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 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"]}
|