@lyfie/luthor 2.2.0 → 2.3.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/README.md +228 -475
- package/dist/ExtensiveEditor-Cn84lsVT.d.ts +448 -0
- package/dist/index-BtdZVw3X.d.ts +17 -0
- package/dist/index-DFnca6tP.d.ts +30 -0
- package/dist/index.css +1 -1
- package/dist/index.d.ts +203 -53
- package/dist/index.js +3 -16
- package/dist/presets/chat-window/index.css +1 -0
- package/dist/presets/chat-window/index.d.ts +22 -0
- package/dist/presets/chat-window/index.js +3 -0
- package/dist/presets/email-compose/index.css +1 -0
- package/dist/presets/email-compose/index.d.ts +17 -0
- package/dist/presets/email-compose/index.js +3 -0
- package/dist/presets/extensive/index.css +1 -0
- package/dist/presets/extensive/index.d.ts +5 -0
- package/dist/presets/extensive/index.js +3 -0
- package/dist/presets/headless-editor/index.css +1 -0
- package/dist/presets/headless-editor/index.d.ts +13 -0
- package/dist/presets/headless-editor/index.js +1 -0
- package/dist/presets/md-text/index.css +1 -0
- package/dist/presets/md-text/index.d.ts +15 -0
- package/dist/presets/md-text/index.js +3 -0
- package/dist/presets/notes/index.css +1 -0
- package/dist/presets/notes/index.d.ts +21 -0
- package/dist/presets/notes/index.js +3 -0
- package/dist/presets/notion-like/index.css +1 -0
- package/dist/presets/notion-like/index.d.ts +17 -0
- package/dist/presets/notion-like/index.js +3 -0
- package/dist/presets/rich-text-box/index.css +1 -0
- package/dist/presets/rich-text-box/index.d.ts +17 -0
- package/dist/presets/rich-text-box/index.js +3 -0
- package/dist/presets/simple-text/index.css +1 -0
- package/dist/presets/simple-text/index.d.ts +15 -0
- package/dist/presets/simple-text/index.js +3 -0
- package/package.json +76 -20
package/README.md
CHANGED
|
@@ -1,567 +1,320 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @lyfie/luthor
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Plug-and-play React editor preset package built on top of `@lyfie/luthor-headless`.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Package responsibility
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Why Use This Package?
|
|
13
|
-
|
|
14
|
-
### ✅ Zero Configuration Hassle
|
|
15
|
-
- All Lexical packages bundled as dependencies
|
|
16
|
-
- No peer dependency warnings
|
|
17
|
-
- No version conflicts to resolve
|
|
18
|
-
|
|
19
|
-
### 🎮 Ready-to-Use Editors
|
|
20
|
-
- Multiple presets for different use cases
|
|
21
|
-
- Pre-built toolbar components
|
|
22
|
-
- Styled and themed out of the box
|
|
23
|
-
|
|
24
|
-
### 🔧 Still Fully Customizable
|
|
25
|
-
- Extend or modify any preset
|
|
26
|
-
- Access all luthor-headless features
|
|
27
|
-
- Build on top of presets with your own extensions
|
|
28
|
-
|
|
29
|
-
---
|
|
7
|
+
- Owns preset UX/composition and plug-and-play defaults.
|
|
8
|
+
- Reuses headless behavior APIs instead of re-implementing Lexical semantics.
|
|
9
|
+
- Exposes headless namespace for advanced users who need lower-level control.
|
|
30
10
|
|
|
31
11
|
## Installation
|
|
32
12
|
|
|
33
|
-
Install both the headless package and this preset package:
|
|
34
|
-
|
|
35
13
|
```bash
|
|
36
|
-
|
|
37
|
-
npm install @lyfie/luthor-headless @lyfie/luthor
|
|
38
|
-
|
|
39
|
-
# pnpm
|
|
40
|
-
pnpm add @lyfie/luthor-headless @lyfie/luthor
|
|
14
|
+
pnpm add @lyfie/luthor react react-dom
|
|
41
15
|
```
|
|
42
16
|
|
|
43
|
-
**That's it!** All required Lexical packages are automatically installed as dependencies of `@lyfie/luthor`.
|
|
44
|
-
|
|
45
|
-
### What Gets Installed
|
|
46
|
-
|
|
47
|
-
When you install `@lyfie/luthor`, your package manager (npm/pnpm) automatically installs:
|
|
48
|
-
- `@lyfie/luthor-headless` (the core editor)
|
|
49
|
-
- `lexical` (the Lexical framework)
|
|
50
|
-
- All `@lexical/*` packages (code, html, link, list, markdown, react, rich-text, selection, table, utils)
|
|
51
|
-
|
|
52
|
-
These satisfy the peer dependencies of luthor-headless, so you don't need to install anything else.
|
|
53
|
-
|
|
54
|
-
### Peer Dependencies
|
|
55
|
-
|
|
56
|
-
Only React remains as a peer dependency (which you already have in your project):
|
|
57
|
-
- `react` (^18.0.0 or ^19.0.0)
|
|
58
|
-
- `react-dom` (^18.0.0 or ^19.0.0)
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
17
|
## Quick Start
|
|
63
18
|
|
|
64
|
-
### Using the Extensive Editor (Recommended)
|
|
65
|
-
|
|
66
|
-
The fastest way to get a full-featured editor:
|
|
67
|
-
|
|
68
19
|
```tsx
|
|
69
20
|
import { ExtensiveEditor } from "@lyfie/luthor";
|
|
70
|
-
import
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
const editorRef = useRef<ExtensiveEditorRef>(null);
|
|
75
|
-
|
|
76
|
-
const handleSave = () => {
|
|
77
|
-
const html = editorRef.current?.getHTML();
|
|
78
|
-
const markdown = editorRef.current?.getMarkdown();
|
|
79
|
-
console.log({ html, markdown });
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
return (
|
|
83
|
-
<div>
|
|
84
|
-
<ExtensiveEditor
|
|
85
|
-
ref={editorRef}
|
|
86
|
-
placeholder="Start writing..."
|
|
87
|
-
onReady={(methods) => {
|
|
88
|
-
console.log("Editor ready!", methods);
|
|
89
|
-
}}
|
|
90
|
-
/>
|
|
91
|
-
<button onClick={handleSave}>Save Content</button>
|
|
92
|
-
</div>
|
|
93
|
-
);
|
|
21
|
+
import "@lyfie/luthor/styles.css";
|
|
22
|
+
|
|
23
|
+
export function App() {
|
|
24
|
+
return <ExtensiveEditor placeholder="Start writing..." />;
|
|
94
25
|
}
|
|
95
26
|
```
|
|
96
27
|
|
|
97
|
-
This
|
|
98
|
-
- ✅ Full-featured toolbar
|
|
99
|
-
- ✅ All formatting options (bold, italic, underline, etc.)
|
|
100
|
-
- ✅ Lists, tables, images, links
|
|
101
|
-
- ✅ Code blocks with syntax highlighting
|
|
102
|
-
- ✅ HTML/Markdown export
|
|
103
|
-
- ✅ Dark mode support
|
|
104
|
-
- ✅ Command palette (Cmd+K / Ctrl+K)
|
|
105
|
-
|
|
106
|
-
### Using Preset Definitions
|
|
28
|
+
## What This Package Exposes
|
|
107
29
|
|
|
108
|
-
|
|
30
|
+
- Preset editor component: `ExtensiveEditor`
|
|
31
|
+
- Additional preset editors: `SimpleTextEditor`, `RichTextBoxEditor`, `ChatWindowEditor`, `EmailComposeEditor`, `MDTextEditor`, `NotionLikeEditor`, `HeadlessEditorPreset`, `NotesEditor`
|
|
32
|
+
- Preset builder helpers: `extensivePreset`, `createExtensivePreset`, `presetRegistry`
|
|
33
|
+
- Extension composition helpers: `createExtensiveExtensions`, `extensiveExtensions`
|
|
34
|
+
- Feature flag helpers: `resolveFeatureFlags`, `isFeatureEnabled`, `DEFAULT_FEATURE_FLAGS`
|
|
35
|
+
- Core UI + command utilities: `Toolbar`, `FloatingToolbar`, `generateCommands`, `registerKeyboardShortcuts`, and more
|
|
36
|
+
- Full headless passthrough namespace: `headless`
|
|
109
37
|
|
|
110
38
|
```tsx
|
|
111
|
-
import {
|
|
112
|
-
import { extensiveExtensions } from "@lyfie/luthor";
|
|
113
|
-
|
|
114
|
-
const { Provider, useEditor } = createEditorSystem<typeof extensiveExtensions>();
|
|
115
|
-
|
|
116
|
-
function MyToolbar() {
|
|
117
|
-
const { commands, activeStates } = useEditor();
|
|
118
|
-
|
|
119
|
-
return (
|
|
120
|
-
<div className="my-custom-toolbar">
|
|
121
|
-
<button onClick={() => commands.toggleBold()}>
|
|
122
|
-
Bold
|
|
123
|
-
</button>
|
|
124
|
-
{/* Add your custom UI */}
|
|
125
|
-
</div>
|
|
126
|
-
);
|
|
127
|
-
}
|
|
39
|
+
import { headless } from "@lyfie/luthor";
|
|
128
40
|
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<Provider extensions={extensiveExtensions}>
|
|
132
|
-
<MyToolbar />
|
|
133
|
-
<RichText placeholder="Start writing..." />
|
|
134
|
-
</Provider>
|
|
135
|
-
);
|
|
136
|
-
}
|
|
41
|
+
const { createEditorSystem, richTextExtension, boldExtension } = headless;
|
|
137
42
|
```
|
|
138
43
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
-
|
|
181
|
-
-
|
|
44
|
+
## Preset Catalog
|
|
45
|
+
|
|
46
|
+
- `ExtensiveEditor`: full-feature visual/jsonb preset.
|
|
47
|
+
- `SimpleTextEditor`: plain-text locked preset.
|
|
48
|
+
- `RichTextBoxEditor`: inline formatting + list preset with optional compact toolbar.
|
|
49
|
+
- `ChatWindowEditor`: chat composer with send action row.
|
|
50
|
+
- `EmailComposeEditor`: compose shell (To/CC/BCC/Subject) + rich body.
|
|
51
|
+
- `MDTextEditor`: visual/markdown mode switching.
|
|
52
|
+
- `NotionLikeEditor`: slash-first + draggable defaults.
|
|
53
|
+
- `HeadlessEditorPreset`: minimal plain-control reference.
|
|
54
|
+
- `NotesEditor`: note-taking shell with title/actions callbacks.
|
|
55
|
+
|
|
56
|
+
## ExtensiveEditor API
|
|
57
|
+
|
|
58
|
+
### Props
|
|
59
|
+
|
|
60
|
+
| Prop | Type | Default | Notes |
|
|
61
|
+
|---|---|---|---|
|
|
62
|
+
| `className` | `string` | `undefined` | Appended to wrapper root. |
|
|
63
|
+
| `variantClassName` | `string` | `undefined` | Extra preset variant class on wrapper. |
|
|
64
|
+
| `onReady` | `(methods: ExtensiveEditorRef) => void` | `undefined` | Called when editor methods are ready. |
|
|
65
|
+
| `initialTheme` | `"light" \| "dark"` | `"light"` | Initial visual theme mode. |
|
|
66
|
+
| `theme` | `Partial<LuthorTheme>` | `undefined` | Theme class/style overrides merged with default theme. |
|
|
67
|
+
| `defaultContent` | `string` | `undefined` | If provided, injected as initial content (JSON parsed or plain text converted to JSONB). |
|
|
68
|
+
| `showDefaultContent` | `boolean` | `true` | If `true` and `defaultContent` is not provided, loads built-in welcome content. |
|
|
69
|
+
| `placeholder` | `string \| { visual?: string; jsonb?: string }` | `"Write anything..."` | String applies to visual mode; object lets you set visual and JSONB placeholders separately. |
|
|
70
|
+
| `initialMode` | `"visual" \| "jsonb"` | `"visual"` | Initial open mode; clamped to `availableModes`. |
|
|
71
|
+
| `availableModes` | `readonly ("visual" \| "jsonb")[]` | `["visual", "jsonb"]` | Visible mode tabs and allowed switching targets. |
|
|
72
|
+
| `toolbarLayout` | `ToolbarLayout` | `TRADITIONAL_TOOLBAR_LAYOUT` | Custom toolbar sections/items order. |
|
|
73
|
+
| `toolbarVisibility` | `Partial<Record<ToolbarItemType, boolean>>` | `undefined` | Per-item hide/show map. Unsupported items are auto-hidden. |
|
|
74
|
+
| `toolbarPosition` | `"top" \| "bottom"` | `"top"` | Renders toolbar above or below visual editor. |
|
|
75
|
+
| `toolbarAlignment` | `"left" \| "center" \| "right"` | `"left"` | Horizontal toolbar alignment class. |
|
|
76
|
+
| `toolbarClassName` | `string` | `undefined` | Extra class for toolbar root (`.luthor-toolbar`). |
|
|
77
|
+
| `toolbarStyleVars` | `ToolbarStyleVars` | `undefined` | Inline `--luthor-toolbar-*` CSS variable overrides. |
|
|
78
|
+
| `isToolbarEnabled` | `boolean` | `true` | Hides toolbar UI only; keyboard/commands still exist unless feature-flagged off. |
|
|
79
|
+
| `quoteClassName` | `string` | `undefined` | Appended to quote node class. |
|
|
80
|
+
| `quoteStyleVars` | `QuoteStyleVars` | `undefined` | Inline `--luthor-quote-*` CSS variable overrides. |
|
|
81
|
+
| `defaultSettings` | `DefaultSettings` | `undefined` | High-level style token API for common color settings. |
|
|
82
|
+
| `editorThemeOverrides` | `LuthorEditorThemeOverrides` | `undefined` | Inline editor-wide `--luthor-*` token overrides. |
|
|
83
|
+
| `fontFamilyOptions` | `readonly FontFamilyOption[]` | preset defaults | Sanitized: duplicates removed, invalid tokens removed, `default` auto-added if missing. |
|
|
84
|
+
| `fontSizeOptions` | `readonly FontSizeOption[]` | preset defaults | Sanitized similarly to font family options. |
|
|
85
|
+
| `minimumDefaultLineHeight` | `string \| number` | `1.5` | Sets the editor default line height and the minimum selectable/accepted line-height floor for the line-height feature; minimum accepted value is `1.0`. |
|
|
86
|
+
| `lineHeightOptions` | `readonly LineHeightOption[]` | preset defaults | Uses unitless ratios (`"1.5"`) for non-default values; minimum allowed is `1.0`. |
|
|
87
|
+
| `scaleByRatio` | `boolean` | `false` | Image resize behavior (`true` = keep ratio by default, Shift unlocks). |
|
|
88
|
+
| `headingOptions` | `readonly ("h1"\|"h2"\|"h3"\|"h4"\|"h5"\|"h6")[]` | all headings | Invalid/duplicate entries are removed. |
|
|
89
|
+
| `paragraphLabel` | `string` | `"Paragraph"` behavior | Label for paragraph entry in block format menu/commands. |
|
|
90
|
+
| `syncHeadingOptionsWithCommands` | `boolean` | `true` | Syncs heading command generation with `headingOptions`. |
|
|
91
|
+
| `slashCommandVisibility` | `SlashCommandVisibility` | `undefined` | Slash command filter using allowlist/denylist or enabled-ID selection array form. |
|
|
92
|
+
| `shortcutConfig` | `ShortcutConfig` | `undefined` | Per-instance command shortcut config (disable/remap, collision and native conflict prevention). |
|
|
93
|
+
| `commandPaletteShortcutOnly` | `boolean` | `false` | When `true`, command palette only shows commands that have a visible keyboard shortcut. |
|
|
94
|
+
| `isDraggableBoxEnabled` | `boolean` | `undefined` | Shortcut for enabling/disabling draggable block UI (maps into `featureFlags.draggableBlock`). |
|
|
95
|
+
| `featureFlags` | `Partial<Record<FeatureFlag, boolean>>` | all `true` | Central capability switchboard; affects extensions, toolbar, commands, shortcuts. |
|
|
96
|
+
| `syntaxHighlighting` | `"auto" \| "disabled"` | extension default (`"auto"`) | Code block syntax highlighting strategy. |
|
|
97
|
+
| `codeHighlightProvider` | `CodeHighlightProvider \| null` | `undefined` | Injected highlight provider instance. |
|
|
98
|
+
| `loadCodeHighlightProvider` | `() => Promise<CodeHighlightProvider \| null>` | `undefined` | Lazy async provider loader. |
|
|
99
|
+
| `maxAutoDetectCodeLength` | `number` | `12000` in code intelligence extension | Guard for auto language detection input size. |
|
|
100
|
+
| `isCopyAllowed` | `boolean` | `true` | Enables/disables code block copy button and command path. |
|
|
101
|
+
| `languageOptions` | `readonly string[] \| { mode?: "append"\|"replace"; values: readonly string[] }` | default language catalog | Controls visible code language options. |
|
|
102
|
+
|
|
103
|
+
### Ref Methods
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
type ExtensiveEditorRef = {
|
|
107
|
+
injectJSONB: (content: string) => void;
|
|
108
|
+
getJSONB: () => string;
|
|
109
|
+
};
|
|
110
|
+
```
|
|
182
111
|
|
|
183
|
-
|
|
184
|
-
Markdown-first editing.
|
|
185
|
-
- Markdown shortcuts
|
|
186
|
-
- Preview mode
|
|
187
|
-
- Clean export
|
|
112
|
+
## Valid Argument Sets
|
|
188
113
|
|
|
189
|
-
###
|
|
190
|
-
Code snippet editor.
|
|
191
|
-
- Syntax highlighting
|
|
192
|
-
- Multiple language support
|
|
193
|
-
- Line numbers
|
|
114
|
+
### `ToolbarItemType`
|
|
194
115
|
|
|
195
|
-
|
|
196
|
-
Balanced general-purpose editor.
|
|
197
|
-
- Good starting point for customization
|
|
116
|
+
`"fontFamily"`, `"fontSize"`, `"lineHeight"`, `"textColor"`, `"textHighlight"`, `"bold"`, `"italic"`, `"underline"`, `"strikethrough"`, `"subscript"`, `"superscript"`, `"code"`, `"link"`, `"blockFormat"`, `"quote"`, `"alignLeft"`, `"alignCenter"`, `"alignRight"`, `"alignJustify"`, `"codeBlock"`, `"unorderedList"`, `"orderedList"`, `"checkList"`, `"indentList"`, `"outdentList"`, `"horizontalRule"`, `"table"`, `"image"`, `"emoji"`, `"embed"`, `"undo"`, `"redo"`, `"commandPalette"`, `"themeToggle"`.
|
|
198
117
|
|
|
199
|
-
###
|
|
200
|
-
Full-featured editor with everything.
|
|
201
|
-
- All extensions included
|
|
202
|
-
- Complete toolbar
|
|
203
|
-
- Pre-built component
|
|
118
|
+
### `FeatureFlag`
|
|
204
119
|
|
|
205
|
-
|
|
120
|
+
`"bold"`, `"italic"`, `"underline"`, `"strikethrough"`, `"fontFamily"`, `"fontSize"`, `"lineHeight"`, `"textColor"`, `"textHighlight"`, `"subscript"`, `"superscript"`, `"link"`, `"horizontalRule"`, `"table"`, `"list"`, `"history"`, `"image"`, `"blockFormat"`, `"code"`, `"codeIntelligence"`, `"codeFormat"`, `"tabIndent"`, `"enterKeyBehavior"`, `"iframeEmbed"`, `"youTubeEmbed"`, `"floatingToolbar"`, `"contextMenu"`, `"commandPalette"`, `"slashCommand"`, `"emoji"`, `"draggableBlock"`, `"customNode"`, `"themeToggle"`.
|
|
206
121
|
|
|
207
|
-
## Usage
|
|
122
|
+
## Usage Recipes
|
|
208
123
|
|
|
209
|
-
###
|
|
124
|
+
### 1) Minimal with defaults
|
|
210
125
|
|
|
211
126
|
```tsx
|
|
212
|
-
import {
|
|
213
|
-
|
|
214
|
-
// Get a preset by ID
|
|
215
|
-
const blogPreset = presetRegistry.blog;
|
|
216
|
-
const minimalPreset = presetRegistry.minimal;
|
|
127
|
+
import { ExtensiveEditor } from "@lyfie/luthor";
|
|
128
|
+
import "@lyfie/luthor/styles.css";
|
|
217
129
|
|
|
218
|
-
|
|
219
|
-
|
|
130
|
+
export function App() {
|
|
131
|
+
return <ExtensiveEditor />;
|
|
132
|
+
}
|
|
220
133
|
```
|
|
221
134
|
|
|
222
|
-
###
|
|
135
|
+
### 2) Placeholder per mode + restricted modes
|
|
223
136
|
|
|
224
137
|
```tsx
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
toolbar: ["bold", "italic", "link"], // Override toolbar
|
|
234
|
-
config: {
|
|
235
|
-
...defaultPreset.config,
|
|
236
|
-
placeholder: "Write your story...",
|
|
237
|
-
},
|
|
238
|
-
};
|
|
138
|
+
<ExtensiveEditor
|
|
139
|
+
initialMode="visual"
|
|
140
|
+
availableModes={["visual", "jsonb"]}
|
|
141
|
+
placeholder={{
|
|
142
|
+
visual: "Write release notes...",
|
|
143
|
+
jsonb: "Paste JSONB...",
|
|
144
|
+
}}
|
|
145
|
+
/>
|
|
239
146
|
```
|
|
240
147
|
|
|
241
|
-
###
|
|
148
|
+
### 3) Feature-gated editor (product tiers)
|
|
242
149
|
|
|
243
150
|
```tsx
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
<RichText />
|
|
259
|
-
</div>
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function App() {
|
|
264
|
-
return (
|
|
265
|
-
<Provider extensions={extensiveExtensions}>
|
|
266
|
-
<Editor />
|
|
267
|
-
</Provider>
|
|
268
|
-
);
|
|
269
|
-
}
|
|
151
|
+
<ExtensiveEditor
|
|
152
|
+
featureFlags={{
|
|
153
|
+
image: false,
|
|
154
|
+
iframeEmbed: false,
|
|
155
|
+
youTubeEmbed: false,
|
|
156
|
+
emoji: false,
|
|
157
|
+
commandPalette: false,
|
|
158
|
+
}}
|
|
159
|
+
toolbarVisibility={{
|
|
160
|
+
embed: false,
|
|
161
|
+
image: false,
|
|
162
|
+
emoji: false,
|
|
163
|
+
}}
|
|
164
|
+
/>
|
|
270
165
|
```
|
|
271
166
|
|
|
272
|
-
###
|
|
167
|
+
### 4) Slash command allowlist
|
|
273
168
|
|
|
274
169
|
```tsx
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
setSavedContent(html || "");
|
|
287
|
-
console.log({ html, markdown });
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
const handleImport = () => {
|
|
291
|
-
editorRef.current?.injectHTML(savedContent);
|
|
292
|
-
// or
|
|
293
|
-
editorRef.current?.injectMarkdown("# Hello\n\nMarkdown content");
|
|
294
|
-
};
|
|
295
|
-
|
|
296
|
-
return (
|
|
297
|
-
<div>
|
|
298
|
-
<ExtensiveEditor ref={editorRef} />
|
|
299
|
-
<button onClick={handleExport}>Export</button>
|
|
300
|
-
<button onClick={handleImport}>Import</button>
|
|
301
|
-
</div>
|
|
302
|
-
);
|
|
303
|
-
}
|
|
170
|
+
<ExtensiveEditor
|
|
171
|
+
slashCommandVisibility={{
|
|
172
|
+
allowlist: [
|
|
173
|
+
"block.paragraph",
|
|
174
|
+
"block.heading1",
|
|
175
|
+
"block.heading2",
|
|
176
|
+
"insert.table",
|
|
177
|
+
],
|
|
178
|
+
denylist: ["insert.image"],
|
|
179
|
+
}}
|
|
180
|
+
/>
|
|
304
181
|
```
|
|
305
182
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
## API Reference
|
|
309
|
-
|
|
310
|
-
### ExtensiveEditor Component
|
|
183
|
+
### 5) Code language list replacement
|
|
311
184
|
|
|
312
185
|
```tsx
|
|
313
|
-
|
|
314
|
-
|
|
186
|
+
<ExtensiveEditor
|
|
187
|
+
languageOptions={{
|
|
188
|
+
mode: "replace",
|
|
189
|
+
values: ["plaintext", "typescript", "javascript", "markdown", "sql"],
|
|
190
|
+
}}
|
|
191
|
+
syntaxHighlighting="auto"
|
|
192
|
+
maxAutoDetectCodeLength={10000}
|
|
193
|
+
/>
|
|
315
194
|
```
|
|
316
195
|
|
|
317
|
-
|
|
318
|
-
```typescript
|
|
319
|
-
interface ExtensiveEditorProps {
|
|
320
|
-
placeholder?: string; // Placeholder text
|
|
321
|
-
className?: string; // CSS class for container
|
|
322
|
-
onReady?: (ref: ExtensiveEditorRef) => void; // Called when editor is ready
|
|
323
|
-
}
|
|
324
|
-
```
|
|
196
|
+
Optional: use highlight.js stylesheet for richer code colors
|
|
325
197
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
interface ExtensiveEditorRef {
|
|
329
|
-
injectMarkdown: (content: string) => void; // Import markdown
|
|
330
|
-
injectHTML: (content: string) => void; // Import HTML
|
|
331
|
-
getMarkdown: () => string; // Export as markdown
|
|
332
|
-
getHTML: () => string; // Export as HTML
|
|
333
|
-
}
|
|
198
|
+
```bash
|
|
199
|
+
pnpm add highlight.js
|
|
334
200
|
```
|
|
335
201
|
|
|
336
|
-
### Preset Registry
|
|
337
|
-
|
|
338
202
|
```tsx
|
|
339
|
-
import {
|
|
340
|
-
import
|
|
341
|
-
|
|
203
|
+
import { ExtensiveEditor } from "@lyfie/luthor";
|
|
204
|
+
import "@lyfie/luthor/styles.css";
|
|
205
|
+
import "highlight.js/styles/github.css"; // any highlight.js theme
|
|
342
206
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
interface EditorPreset {
|
|
346
|
-
id: string; // Unique preset ID
|
|
347
|
-
label: string; // Display name
|
|
348
|
-
description?: string; // Preset description
|
|
349
|
-
extensions?: Extension[]; // Included extensions
|
|
350
|
-
config?: EditorConfig; // Editor configuration
|
|
351
|
-
theme?: LuthorTheme; // Custom theme
|
|
352
|
-
toolbar?: string[]; // Toolbar items
|
|
353
|
-
components?: Record<string, unknown>; // Custom components
|
|
354
|
-
css?: string; // CSS file path
|
|
207
|
+
export function App() {
|
|
208
|
+
return <ExtensiveEditor syntaxHighlighting="auto" />;
|
|
355
209
|
}
|
|
356
210
|
```
|
|
357
211
|
|
|
358
|
-
|
|
359
|
-
- `presetRegistry.minimal`
|
|
360
|
-
- `presetRegistry.classic`
|
|
361
|
-
- `presetRegistry.blog`
|
|
362
|
-
- `presetRegistry.cms`
|
|
363
|
-
- `presetRegistry.docs`
|
|
364
|
-
- `presetRegistry.chat`
|
|
365
|
-
- `presetRegistry.email`
|
|
366
|
-
- `presetRegistry.markdown`
|
|
367
|
-
- `presetRegistry.code`
|
|
368
|
-
- `presetRegistry.default`
|
|
369
|
-
- `presetRegistry.extensive`
|
|
370
|
-
|
|
371
|
-
### Extension Sets
|
|
372
|
-
|
|
373
|
-
```tsx
|
|
374
|
-
import { extensiveExtensions } from "@lyfie/luthor";
|
|
375
|
-
```
|
|
376
|
-
|
|
377
|
-
Pre-configured extension arrays that can be used with luthor-headless:
|
|
212
|
+
What happens:
|
|
378
213
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
## Comparison: Headless vs Luthor
|
|
387
|
-
|
|
388
|
-
| Feature | @lyfie/luthor-headless | @lyfie/luthor |
|
|
389
|
-
|---------|----------------------|---------------|
|
|
390
|
-
| **Installation** | Manual Lexical deps | Zero additional deps |
|
|
391
|
-
| **Bundle Size** | Minimal | Includes all Lexical |
|
|
392
|
-
| **Setup Time** | More configuration | Instant |
|
|
393
|
-
| **Flexibility** | Maximum control | Pre-configured |
|
|
394
|
-
| **Use Case** | Custom editors | Quick implementation |
|
|
395
|
-
| **UI Components** | Build your own | ExtensiveEditor included |
|
|
396
|
-
| **Presets** | None | 11 ready-to-use |
|
|
397
|
-
| **Dependencies** | Peer deps | Bundled deps |
|
|
398
|
-
|
|
399
|
-
**Choose luthor-headless when:**
|
|
400
|
-
- Building completely custom UI
|
|
401
|
-
- Need minimal bundle size
|
|
402
|
-
- Want control over Lexical versions
|
|
403
|
-
- Have specific dependency requirements
|
|
404
|
-
|
|
405
|
-
**Choose @lyfie/luthor when:**
|
|
406
|
-
- Want to start quickly
|
|
407
|
-
- Need a working editor ASAP
|
|
408
|
-
- Don't want to manage dependencies
|
|
409
|
-
- Want ready-to-use components
|
|
214
|
+
- Luthor does not auto-detect code language anymore. Language comes from the selected code language option.
|
|
215
|
+
- Language options are normalized through Lexical/Prism aliases (`md` -> `markdown`, `js` -> `javascript` family, `ts` -> `typescript`).
|
|
216
|
+
- Only Prism-supported loaded languages are accepted. Unsupported values (for example `yaml` without Prism YAML grammar loaded) fall back to plain text.
|
|
217
|
+
- `plaintext` uses plain fallback syntax styling.
|
|
218
|
+
- Non-plaintext languages emit `hljs-*` token classes.
|
|
219
|
+
- If a highlight.js stylesheet is loaded, those `hljs-*` tokens use highlight.js colors.
|
|
220
|
+
- Without highlight.js stylesheet, code uses the built-in muted/plain fallback.
|
|
410
221
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
---
|
|
414
|
-
|
|
415
|
-
## Advanced Usage
|
|
416
|
-
|
|
417
|
-
### Creating Custom Presets
|
|
222
|
+
### 6) Per-instance shortcut remap/disable
|
|
418
223
|
|
|
419
224
|
```tsx
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
extensions: [boldExtension, italicExtension, linkExtension],
|
|
432
|
-
config: {
|
|
433
|
-
placeholder: "Start typing...",
|
|
434
|
-
editable: true,
|
|
435
|
-
},
|
|
436
|
-
toolbar: ["bold", "italic", "link"],
|
|
437
|
-
};
|
|
225
|
+
<ExtensiveEditor
|
|
226
|
+
shortcutConfig={{
|
|
227
|
+
disabledCommandIds: ["format.italic"],
|
|
228
|
+
bindings: {
|
|
229
|
+
"format.bold": { key: "m", ctrlKey: true },
|
|
230
|
+
"palette.show": [
|
|
231
|
+
{ key: "k", ctrlKey: true, shiftKey: true },
|
|
232
|
+
],
|
|
233
|
+
},
|
|
234
|
+
}}
|
|
235
|
+
/>
|
|
438
236
|
```
|
|
439
237
|
|
|
440
|
-
###
|
|
238
|
+
### 7) Command palette shortcut-only mode
|
|
441
239
|
|
|
442
240
|
```tsx
|
|
443
|
-
|
|
444
|
-
import { myCustomExtension } from "./my-extension";
|
|
445
|
-
|
|
446
|
-
const enhancedPreset: EditorPreset = {
|
|
447
|
-
...defaultPreset,
|
|
448
|
-
id: "enhanced-default",
|
|
449
|
-
extensions: [
|
|
450
|
-
...(defaultPreset.extensions || []),
|
|
451
|
-
myCustomExtension,
|
|
452
|
-
],
|
|
453
|
-
toolbar: [
|
|
454
|
-
...(defaultPreset.toolbar || []),
|
|
455
|
-
"myCustomCommand",
|
|
456
|
-
],
|
|
457
|
-
};
|
|
241
|
+
<ExtensiveEditor commandPaletteShortcutOnly />
|
|
458
242
|
```
|
|
459
243
|
|
|
460
|
-
###
|
|
461
|
-
|
|
462
|
-
Since `@lyfie/luthor` depends on `@lyfie/luthor-headless`, you have access to all headless features:
|
|
244
|
+
### 8) Style token overrides
|
|
463
245
|
|
|
464
246
|
```tsx
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
247
|
+
<ExtensiveEditor
|
|
248
|
+
editorThemeOverrides={{
|
|
249
|
+
"--luthor-bg": "#fffaf2",
|
|
250
|
+
"--luthor-fg": "#431407",
|
|
251
|
+
"--luthor-accent": "#c2410c",
|
|
252
|
+
}}
|
|
253
|
+
toolbarStyleVars={{
|
|
254
|
+
"--luthor-toolbar-button-active-bg": "#ea580c",
|
|
255
|
+
"--luthor-toolbar-button-active-fg": "#ffffff",
|
|
256
|
+
}}
|
|
257
|
+
quoteStyleVars={{
|
|
258
|
+
"--luthor-quote-bg": "#fff7ed",
|
|
259
|
+
"--luthor-quote-fg": "#7c2d12",
|
|
260
|
+
"--luthor-quote-border": "#ea580c",
|
|
261
|
+
}}
|
|
262
|
+
/>
|
|
481
263
|
```
|
|
482
264
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
## TypeScript Support
|
|
265
|
+
### 9) Emoji library auto-detection (works in `apps/demo`)
|
|
486
266
|
|
|
487
|
-
|
|
267
|
+
Install emoji-mart data in the app:
|
|
488
268
|
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
EditorPreset,
|
|
492
|
-
ExtensiveEditorRef,
|
|
493
|
-
ExtensiveEditorProps,
|
|
494
|
-
ExtensiveEditorMode,
|
|
495
|
-
} from "@lyfie/luthor";
|
|
269
|
+
```bash
|
|
270
|
+
pnpm add -F demo @emoji-mart/data
|
|
496
271
|
```
|
|
497
272
|
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
## Styling
|
|
501
|
-
|
|
502
|
-
The `ExtensiveEditor` component includes default styles. To customize:
|
|
273
|
+
Then use `ExtensiveEditor` normally:
|
|
503
274
|
|
|
504
275
|
```tsx
|
|
505
276
|
import { ExtensiveEditor } from "@lyfie/luthor";
|
|
277
|
+
import "@lyfie/luthor/styles.css";
|
|
506
278
|
|
|
507
|
-
|
|
508
|
-
<ExtensiveEditor
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
```css
|
|
512
|
-
/* Override default styles */
|
|
513
|
-
.my-editor {
|
|
514
|
-
--luthor-bg: #ffffff;
|
|
515
|
-
--luthor-text: #000000;
|
|
516
|
-
--luthor-border: #e5e5e5;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/* Dark mode */
|
|
520
|
-
.my-editor.dark {
|
|
521
|
-
--luthor-bg: #1a1a1a;
|
|
522
|
-
--luthor-text: #ffffff;
|
|
523
|
-
--luthor-border: #333333;
|
|
279
|
+
export function App() {
|
|
280
|
+
return <ExtensiveEditor />;
|
|
524
281
|
}
|
|
525
282
|
```
|
|
526
283
|
|
|
527
|
-
|
|
284
|
+
What happens:
|
|
528
285
|
|
|
529
|
-
|
|
286
|
+
- `:shortcode` suggestions and resolution use the detected emoji-mart dataset.
|
|
287
|
+
- Emoji toolbar dropdown uses the detected emoji-mart catalog.
|
|
288
|
+
- If the library is not installed/available, it automatically falls back to the built-in emoji list.
|
|
530
289
|
|
|
531
|
-
|
|
290
|
+
## Notes and Nuances
|
|
532
291
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
292
|
+
- `featureFlags` are authoritative. If a feature is disabled, related toolbar items and commands are removed/no-op even if you attempt to show them.
|
|
293
|
+
- `slashCommandVisibility` keeps original command ordering; it only filters visibility.
|
|
294
|
+
- `shortcutConfig.disabledCommandIds` removes commands from keyboard handling and command UIs for that editor instance.
|
|
295
|
+
- `shortcutConfig` drops duplicate bindings by default and blocks native editable conflicts by default.
|
|
296
|
+
- `commandPaletteShortcutOnly` is optional; by default command palette can include command items without shortcuts.
|
|
297
|
+
- `languageOptions` normalizes aliases (for example `js` becomes `javascript`) and rejects duplicates after normalization.
|
|
298
|
+
- Emoji suggestions/tooling auto-detect external emoji-mart data when available, and otherwise use the built-in default emoji catalog.
|
|
299
|
+
- `defaultSettings` is style-only; behavior is controlled by explicit props (for example `featureFlags`, `availableModes`).
|
|
300
|
+
- `isToolbarEnabled={false}` hides toolbar UI, but editor shortcuts/features still work unless disabled via `featureFlags`.
|
|
538
301
|
|
|
539
|
-
|
|
540
|
-
pnpm add @lyfie/luthor-headless
|
|
541
|
-
pnpm add lexical @lexical/react @lexical/html # ... many packages
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
**After:**
|
|
545
|
-
```bash
|
|
546
|
-
# npm
|
|
547
|
-
npm install @lyfie/luthor-headless @lyfie/luthor
|
|
548
|
-
|
|
549
|
-
# pnpm
|
|
550
|
-
pnpm add @lyfie/luthor-headless @lyfie/luthor
|
|
551
|
-
|
|
552
|
-
# Remove individual @lexical/* packages if desired
|
|
553
|
-
```
|
|
554
|
-
|
|
555
|
-
Your code doesn't need to change! All luthor-headless APIs work the same way.
|
|
556
|
-
|
|
557
|
-
---
|
|
302
|
+
## Which Package Should I Use?
|
|
558
303
|
|
|
559
|
-
|
|
304
|
+
- Use `@lyfie/luthor` for fast onboarding and polished defaults.
|
|
305
|
+
- Use `@lyfie/luthor-headless` for full extension/UI control.
|
|
560
306
|
|
|
561
|
-
|
|
307
|
+
## Documentation
|
|
562
308
|
|
|
563
|
-
|
|
309
|
+
- Monorepo docs index: [../../documentation/index.md](../../documentation/index.md)
|
|
310
|
+
- Headless package README: [../headless/README.md](../headless/README.md)
|
|
311
|
+
- User docs: [../../documentation/user/luthor/getting-started.md](../../documentation/user/luthor/getting-started.md)
|
|
312
|
+
- Developer docs: [../../documentation/developer/luthor/architecture.md](../../documentation/developer/luthor/architecture.md)
|
|
564
313
|
|
|
565
|
-
|
|
314
|
+
## Workspace Development
|
|
566
315
|
|
|
567
|
-
|
|
316
|
+
```bash
|
|
317
|
+
pnpm --filter @lyfie/luthor dev
|
|
318
|
+
pnpm --filter @lyfie/luthor build
|
|
319
|
+
pnpm --filter @lyfie/luthor lint
|
|
320
|
+
```
|