Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion apps/web/public/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Concatenated markdown corpus for AI ingestion.
- Rahul Anand is the creator and BDFL of Lyfie.org: [https://www.rahulnsanand.com](https://www.rahulnsanand.com)

Source root: C:\Users\rahul\Documents\GitHub\luthor\apps\web\src\content\docs
Generated at: 2026-09-27T10:34:30.489Z
Generated at: 2026-09-28T18:53:35.014Z

## AI Agent Workflow

Expand Down Expand Up @@ -3186,6 +3186,10 @@ Use `ExtensiveEditor` when you want full formatting, media, code, and command wo

Tab indents and Shift+Tab outdents inside the editor. Keyboard-only users escape the capture with **Escape, then Tab** — the armed Tab performs the browser's native focus move out of the editor (WCAG 2.1.2); any other key restores Tab-as-indent.

## List indentation

Bulleted, numbered and checkbox lists share one marker column, so item text starts at the same place whatever the list type. Set `--luthor-list-gutter` (default `24px`) on the editor wrapper to widen or narrow it; long numbers like `10.` grow into the list margin instead of pushing the text.

## Custom upload hooks

~~~tsx
Expand Down
17 changes: 14 additions & 3 deletions apps/web/src/data/docs-index.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6286,8 +6286,8 @@ export const docsIndex = [
"title": "Extensive Editor",
"navTitle": "Extensive Editor",
"description": "Full preset profile with all core features enabled and broad mode support.",
"content": "\n# Extensive Editor\n\nThis preset is the broadest out-of-box profile.\n\n## When to use this\n\nUse `ExtensiveEditor` when you want full formatting, media, code, and command workflows in one preset.\n\n## Mode profile\n\n- Default modes: `visual-editor`, `visual-only`, `json`, `markdown`, `html`.\n- Default initial mode: `visual-editor`.\n\n## Preset props\n\n- `featureFlags`: Toggle individual preset capabilities. Use `featureFlags.codeIntelligence` to turn code intelligence on/off.\n- `availableModes`: Restrict visible mode tabs and allowed mode switching targets.\n- `maxListIndentation`: Caps nested list depth in visual editing.\n- `imageUploadHandler`: Intercepts local image file uploads from the toolbar.\n- `gifUploadHandler`: Intercepts local GIF uploads. Falls back to `imageUploadHandler` when omitted.\n- `onChange`: Change notification, coalesced to one call per committed change, with `{ markdown, source, isDirty }`. Fires for every mutation path (typing, toolbar, slash commands, undo/redo, paste, drag-drop, markdown source view) as `source: \"user\"`; host adopts (`injectJSON`) fire as `source: \"programmatic\"`; the initial `defaultContent` load never fires. Wire autosave here — a DOM `onInput` handler on a wrapper element does **not** work, because Lexical stops propagation of the contenteditable's `input` event.\n- `onReady`: Fires after the editor is interactive **and** initial content has reconciled, so `getMarkdown()` inside the callback is immediately stable. The editor normalises imported markdown, so baseline dirty checks against its own output, never against your input string.\n- `onDesync`: Opt-in watchdog reporting model/DOM divergence — text painted into the contenteditable behind the reconciler's back (`document.execCommand`, extensions, password managers) that would silently miss from `getMarkdown()`.\n- `presetId`: Preset identity for the editable-surface class names (`luthor-preset-<id>__container` / `__content` / `__placeholder`). Wrapper presets set their own id so host CSS matches the rendered element.\n\n## Keyboard access\n\nTab indents and Shift+Tab outdents inside the editor. Keyboard-only users escape the capture with **Escape, then Tab** — the armed Tab performs the browser's native focus move out of the editor (WCAG 2.1.2); any other key restores Tab-as-indent.\n\n## Custom upload hooks\n\n~~~tsx\n<ExtensiveEditor\n imageUploadHandler={async (file) => uploadToCdn(file)}\n gifUploadHandler={async (file) => uploadGifToMediaStore(file)}\n/>\n~~~\n\nIf `gifUploadHandler` is not provided, GIF file uploads use `imageUploadHandler`.\n\nFor production handlers, return a persistent URL from your storage service. Returning `blob:` URLs from handlers is fine for quick prototypes, but dev StrictMode remount cycles can revoke blob URLs and cause temporary `ERR_FILE_NOT_FOUND` preview errors.\n\n## Code intelligence toggle\n\n~~~tsx\n<ExtensiveEditor\n featureFlags={{ codeIntelligence: false }}\n/>\n~~~\n\n~~~tsx\nimport '@lyfie/luthor/styles.css';\nimport { ExtensiveEditor } from '@lyfie/luthor';\n\nexport function App() {\n return <ExtensiveEditor placeholder=\"Write anything...\" />;\n}\n~~~\n\n\n",
"plainContent": "Extensive Editor This preset is the broadest out-of-box profile. When to use this Use ExtensiveEditor when you want full formatting, media, code, and command workflows in one preset. Mode profile - Default modes: visual-editor , visual-only , json , markdown , html . - Default initial mode: visual-editor . Preset props - featureFlags : Toggle individual preset capabilities. Use featureFlags.codeIntelligence to turn code intelligence on/off. - availableModes : Restrict visible mode tabs and allowed mode switching targets. - maxListIndentation : Caps nested list depth in visual editing. - imageUploadHandler : Intercepts local image file uploads from the toolbar. - gifUploadHandler : Intercepts local GIF uploads. Falls back to imageUploadHandler when omitted. - onChange : Change notification, coalesced to one call per committed change, with { markdown, source, isDirty } . Fires for every mutation path (typing, toolbar, slash commands, undo/redo, paste, drag-drop, markdown source view) as source: \"user\" ; host adopts ( injectJSON ) fire as source: \"programmatic\" ; the initial defaultContent load never fires. Wire autosave here — a DOM onInput handler on a wrapper element does not work, because Lexical stops propagation of the contenteditable's input event. - onReady : Fires after the editor is interactive and initial content has reconciled, so getMarkdown() inside the callback is immediately stable. The editor normalises imported markdown, so baseline dirty checks against its own output, never against your input string. - onDesync : Opt-in watchdog reporting model/DOM divergence — text painted into the contenteditable behind the reconciler's back ( document.execCommand , extensions, password managers) that would silently miss from getMarkdown() . - presetId : Preset identity for the editable-surface class names ( luthor-preset- container / content / placeholder ). Wrapper presets set their own id so host CSS matches the rendered element. Keyboard access Tab indents and Shift+Tab outdents inside the editor. Keyboard-only users escape the capture with Escape, then Tab — the armed Tab performs the browser's native focus move out of the editor (WCAG 2.1.2); any other key restores Tab-as-indent. Custom upload hooks tsx uploadToCdn(file)} gifUploadHandler={async (file) = uploadGifToMediaStore(file)} / If gifUploadHandler is not provided, GIF file uploads use imageUploadHandler . For production handlers, return a persistent URL from your storage service. Returning blob: URLs from handlers is fine for quick prototypes, but dev StrictMode remount cycles can revoke blob URLs and cause temporary ERR FILE NOT FOUND preview errors. Code intelligence toggle tsx tsx import '@lyfie/luthor/styles.css'; import { ExtensiveEditor } from '@lyfie/luthor'; export function App() { return ; }",
"content": "\n# Extensive Editor\n\nThis preset is the broadest out-of-box profile.\n\n## When to use this\n\nUse `ExtensiveEditor` when you want full formatting, media, code, and command workflows in one preset.\n\n## Mode profile\n\n- Default modes: `visual-editor`, `visual-only`, `json`, `markdown`, `html`.\n- Default initial mode: `visual-editor`.\n\n## Preset props\n\n- `featureFlags`: Toggle individual preset capabilities. Use `featureFlags.codeIntelligence` to turn code intelligence on/off.\n- `availableModes`: Restrict visible mode tabs and allowed mode switching targets.\n- `maxListIndentation`: Caps nested list depth in visual editing.\n- `imageUploadHandler`: Intercepts local image file uploads from the toolbar.\n- `gifUploadHandler`: Intercepts local GIF uploads. Falls back to `imageUploadHandler` when omitted.\n- `onChange`: Change notification, coalesced to one call per committed change, with `{ markdown, source, isDirty }`. Fires for every mutation path (typing, toolbar, slash commands, undo/redo, paste, drag-drop, markdown source view) as `source: \"user\"`; host adopts (`injectJSON`) fire as `source: \"programmatic\"`; the initial `defaultContent` load never fires. Wire autosave here — a DOM `onInput` handler on a wrapper element does **not** work, because Lexical stops propagation of the contenteditable's `input` event.\n- `onReady`: Fires after the editor is interactive **and** initial content has reconciled, so `getMarkdown()` inside the callback is immediately stable. The editor normalises imported markdown, so baseline dirty checks against its own output, never against your input string.\n- `onDesync`: Opt-in watchdog reporting model/DOM divergence — text painted into the contenteditable behind the reconciler's back (`document.execCommand`, extensions, password managers) that would silently miss from `getMarkdown()`.\n- `presetId`: Preset identity for the editable-surface class names (`luthor-preset-<id>__container` / `__content` / `__placeholder`). Wrapper presets set their own id so host CSS matches the rendered element.\n\n## Keyboard access\n\nTab indents and Shift+Tab outdents inside the editor. Keyboard-only users escape the capture with **Escape, then Tab** — the armed Tab performs the browser's native focus move out of the editor (WCAG 2.1.2); any other key restores Tab-as-indent.\n\n## List indentation\n\nBulleted, numbered and checkbox lists share one marker column, so item text starts at the same place whatever the list type. Set `--luthor-list-gutter` (default `24px`) on the editor wrapper to widen or narrow it; long numbers like `10.` grow into the list margin instead of pushing the text.\n\n## Custom upload hooks\n\n~~~tsx\n<ExtensiveEditor\n imageUploadHandler={async (file) => uploadToCdn(file)}\n gifUploadHandler={async (file) => uploadGifToMediaStore(file)}\n/>\n~~~\n\nIf `gifUploadHandler` is not provided, GIF file uploads use `imageUploadHandler`.\n\nFor production handlers, return a persistent URL from your storage service. Returning `blob:` URLs from handlers is fine for quick prototypes, but dev StrictMode remount cycles can revoke blob URLs and cause temporary `ERR_FILE_NOT_FOUND` preview errors.\n\n## Code intelligence toggle\n\n~~~tsx\n<ExtensiveEditor\n featureFlags={{ codeIntelligence: false }}\n/>\n~~~\n\n~~~tsx\nimport '@lyfie/luthor/styles.css';\nimport { ExtensiveEditor } from '@lyfie/luthor';\n\nexport function App() {\n return <ExtensiveEditor placeholder=\"Write anything...\" />;\n}\n~~~\n\n\n",
"plainContent": "Extensive Editor This preset is the broadest out-of-box profile. When to use this Use ExtensiveEditor when you want full formatting, media, code, and command workflows in one preset. Mode profile - Default modes: visual-editor , visual-only , json , markdown , html . - Default initial mode: visual-editor . Preset props - featureFlags : Toggle individual preset capabilities. Use featureFlags.codeIntelligence to turn code intelligence on/off. - availableModes : Restrict visible mode tabs and allowed mode switching targets. - maxListIndentation : Caps nested list depth in visual editing. - imageUploadHandler : Intercepts local image file uploads from the toolbar. - gifUploadHandler : Intercepts local GIF uploads. Falls back to imageUploadHandler when omitted. - onChange : Change notification, coalesced to one call per committed change, with { markdown, source, isDirty } . Fires for every mutation path (typing, toolbar, slash commands, undo/redo, paste, drag-drop, markdown source view) as source: \"user\" ; host adopts ( injectJSON ) fire as source: \"programmatic\" ; the initial defaultContent load never fires. Wire autosave here — a DOM onInput handler on a wrapper element does not work, because Lexical stops propagation of the contenteditable's input event. - onReady : Fires after the editor is interactive and initial content has reconciled, so getMarkdown() inside the callback is immediately stable. The editor normalises imported markdown, so baseline dirty checks against its own output, never against your input string. - onDesync : Opt-in watchdog reporting model/DOM divergence — text painted into the contenteditable behind the reconciler's back ( document.execCommand , extensions, password managers) that would silently miss from getMarkdown() . - presetId : Preset identity for the editable-surface class names ( luthor-preset- container / content / placeholder ). Wrapper presets set their own id so host CSS matches the rendered element. Keyboard access Tab indents and Shift+Tab outdents inside the editor. Keyboard-only users escape the capture with Escape, then Tab — the armed Tab performs the browser's native focus move out of the editor (WCAG 2.1.2); any other key restores Tab-as-indent. List indentation Bulleted, numbered and checkbox lists share one marker column, so item text starts at the same place whatever the list type. Set --luthor-list-gutter (default 24px ) on the editor wrapper to widen or narrow it; long numbers like 10. grow into the list margin instead of pushing the text. Custom upload hooks tsx uploadToCdn(file)} gifUploadHandler={async (file) = uploadGifToMediaStore(file)} / If gifUploadHandler is not provided, GIF file uploads use imageUploadHandler . For production handlers, return a persistent URL from your storage service. Returning blob: URLs from handlers is fine for quick prototypes, but dev StrictMode remount cycles can revoke blob URLs and cause temporary ERR FILE NOT FOUND preview errors. Code intelligence toggle tsx tsx import '@lyfie/luthor/styles.css'; import { ExtensiveEditor } from '@lyfie/luthor'; export function App() { return ; }",
"sections": [
{
"heading": "Overview",
Expand Down Expand Up @@ -6319,6 +6319,12 @@ export const docsIndex = [
"level": 2,
"text": "Tab indents and Shift+Tab outdents inside the editor. Keyboard-only users escape the capture with Escape, then Tab — the armed Tab performs the browser's native focus move out of the editor (WCAG 2.1.2); any other key restores Tab-as-indent."
},
{
"heading": "List indentation",
"id": "list-indentation",
"level": 2,
"text": "Bulleted, numbered and checkbox lists share one marker column, so item text starts at the same place whatever the list type. Set --luthor-list-gutter (default 24px ) on the editor wrapper to widen or narrow it; long numbers like 10. grow into the list margin instead of pushing the text."
},
{
"heading": "Custom upload hooks",
"id": "custom-upload-hooks",
Expand Down Expand Up @@ -6353,6 +6359,11 @@ export const docsIndex = [
"text": "Keyboard access",
"id": "keyboard-access"
},
{
"level": 2,
"text": "List indentation",
"id": "list-indentation"
},
{
"level": 2,
"text": "Custom upload hooks",
Expand All @@ -6366,7 +6377,7 @@ export const docsIndex = [
],
"urlPath": "/docs/luthor/presets/extensive-editor/",
"sourcePath": "apps/web/src/content/docs/luthor/presets/extensive-editor.md",
"updatedAt": "2026-08-11T11:14:46.911Z",
"updatedAt": "2026-09-27T16:53:28.553Z",
"package": "luthor",
"docType": "reference",
"surface": "preset",
Expand Down
18 changes: 9 additions & 9 deletions apps/web/src/data/homepage-metrics.generated.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
export const homepageMetrics = {
"totalDownloads": 26021,
"lastMonthDownloads": 3551,
"latestVersion": "2.10.0",
"headlessVersion": "2.10.0",
"luthorPackageSize": 375352,
"headlessPackageSize": 436866,
"combinedPackageSize": 812218,
"releaseCount": 95,
"fetchedAtIso": "2026-09-27T10:34:29.491Z"
"totalDownloads": 28354,
"lastMonthDownloads": 5768,
"latestVersion": "2.10.3",
"headlessVersion": "2.10.3",
"luthorPackageSize": 388672,
"headlessPackageSize": 437010,
"combinedPackageSize": 825682,
"releaseCount": 101,
"fetchedAtIso": "2026-09-28T18:53:34.022Z"
} as const;
13 changes: 12 additions & 1 deletion packages/headless/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"default": "./dist/index.js"
},
"./collab": {
"types": "./dist/collab.d.ts",
"import": "./dist/collab.js",
"default": "./dist/collab.js"
}
},
"scripts": {
Expand Down Expand Up @@ -71,9 +76,15 @@
"@lexical/selection": ">=0.40.0",
"@lexical/table": ">=0.40.0",
"@lexical/utils": ">=0.40.0",
"@lexical/yjs": ">=0.40.0",
"lexical": ">=0.40.0",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
"react-dom": "^18.0.0 || ^19.0.0",
"yjs": ">=13.5.0"
},
"peerDependenciesMeta": {
"@lexical/yjs": { "optional": true },
"yjs": { "optional": true }
},
"optionalDependencies": {
"@emoji-mart/data": "^1.2.1"
Expand Down
24 changes: 24 additions & 0 deletions packages/headless/src/collab.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright (c) Luthor Team and contributors.
* Open source under the MIT License (LICENSE).
* Fork it. Remix it. Ship it.
* Build freely. Credit kindly.
*/

/**
* `@lyfie/luthor-headless/collab` — real-time collaboration (Yjs).
*
* A separate entry so the main bundle never imports `yjs` / `@lexical/yjs`:
* install them (optional peers) only when you use this.
*/
export {
CollaborationExtension,
isCollaborationUpdate,
type CollaborationConfig,
} from "./extensions/core/CollaborationExtension";
export { COLLABORATION_UPDATE_TAG } from "./extensions/core/collaborationTag";
export {
createHeadlessCollabSession,
type HeadlessCollabOptions,
type HeadlessCollabSession,
} from "./core/collaboration-headless";
Loading
Loading