# Reetesh Kumar — full content export > Machine-readable export from https://iamreetesh.com. Lextrix version 2.0.5. Canonical monorepo docs: https://github.com/rishureetesh/lextrix/tree/main/docs. --- # Lextrix documentation ## Getting Started Source: https://iamreetesh.com/docs/getting-started **Try it:** [Live playground](https://iamreetesh.com/lextrix) — themes, import/export, and all modules in the browser. New here? Follow the [Evaluation guide](/docs/evaluation) for a linear install → export walkthrough. ## Install ```bash npm install lextrix ``` ```javascript import Lextrix from 'lextrix'; import 'lextrix/snow.css'; ``` | Import | Purpose | |--------|---------| | `lextrix` | Full editor — ESM (`lextrix.esm.js`) for bundlers, UMD (`lextrix.js`) for script tags | | `lextrix/core` | Core-only bundle (no formats/themes) | | `lextrix/lextrix.css` | Base editor styles | | `lextrix/snow.css` | Snow theme | | `lextrix/bubble.css` | Bubble theme | | `lextrix/slate.css` | Dark slate theme | | `lextrix/dawn.css` | Warm dawn theme | Named imports (`ChangeSet`, `registerSerializer`, `lxrPath`, …) work from `lextrix` in bundlers (2.0.1+). Script-tag UMD exposes `window.Lextrix` only. Optional peer dependencies: [highlight.js](https://highlightjs.org/) for syntax, [KaTeX](https://katex.org/) for formulas. **React / Next.js:** [`@lextrix/react`](/docs/react) · [Frameworks](/docs/frameworks) · [Evaluation](/docs/evaluation) · [Cookbook](/docs/cookbook) Script-tag setup (npm paths or CDN): [Framework integration — Script tag](/docs/frameworks#script-tag-no-bundler). ## Snow theme ```html
``` ```javascript const editor = new Lextrix('#editor', { theme: 'snow', placeholder: 'Start writing…', modules: { toolbar: [ [{ header: [1, 2, false] }], ['bold', 'italic', 'underline', 'strike'], [{ list: 'ordered' }, { list: 'bullet' }], ['link', 'image'], ['clean'], ], imageResize: true, }, }); editor.setContents([ { insert: 'Hello Lextrix\n', attributes: { header: 1 } }, { insert: 'Select an image and drag the corner handle to resize.\n' }, ]); ``` ## Bubble theme ```javascript const editor = new Lextrix('#editor', { theme: 'bubble', modules: { toolbar: [ ['bold', 'italic', 'link'], [{ header: 1 }, { header: 2 }], ], }, }); ``` ## Read-only ```javascript const editor = new Lextrix('#editor', { theme: 'snow', readOnly: true, }); ``` ## Getting content ```javascript const json = editor.getContents(); // ChangeSet const html = editor.exportContent('html'); const text = editor.getText(); ``` ## Listening to changes ```javascript editor.on('text-change', (delta, oldDelta, source) => { if (source === 'user') { save(editor.getContents()); } }); ``` See [Configuration](/docs/configuration) for modules, upload handlers, and format whitelists. --- ## Evaluation Guide Source: https://iamreetesh.com/docs/evaluation A linear path for trying Lextrix for the first time. No setup beyond a bundler or static HTML page. **Using React or Next.js?** Prefer [`@lextrix/react`](/docs/react). See [Frameworks](/docs/frameworks) after step 5. --- ## 1. Try the playground Open the [live playground](https://iamreetesh.com/lextrix) in your browser. No install required — edit content, switch themes, and try import/export panels. --- ## 2. Install Lextrix ```bash npm install lextrix ``` Install only the **`lextrix`** package. Everything you need for a full editor ships in that bundle. --- ## 3. Import CSS Lextrix does not inject styles. Import a theme in your app entry: ```javascript import 'lextrix/snow.css'; ``` Other themes: `lextrix/bubble.css`, `lextrix/slate.css`, `lextrix/dawn.css`. --- ## 4. Create an editor ```html
``` ```javascript import Lextrix from 'lextrix'; import 'lextrix/snow.css'; const editor = new Lextrix('#editor', { theme: 'snow', placeholder: 'Start writing…', modules: { toolbar: [ ['bold', 'italic', 'underline'], [{ header: [1, 2, false] }], [{ list: 'ordered' }, { list: 'bullet' }], ['link', 'clean'], ], }, }); ``` You should see a toolbar and an editable area immediately. --- ## 5. Load content **From ChangeSet JSON** (same shape as `getContents()`): ```javascript editor.setContents([ { insert: 'Hello Lextrix\n', attributes: { header: 1 } }, { insert: 'Edit rich text in the browser.\n' }, ]); ``` **From Markdown or HTML:** ```javascript editor.importContent('# Title\n\n**Bold** paragraph.', 'markdown'); editor.importContent('

Hello world

', 'html'); ``` --- ## 6. Export content ```javascript const markdown = editor.exportContent('markdown'); const html = editor.exportContent('html'); const json = editor.getContents(); // ChangeSet ``` **Before Markdown or MDX export**, check for unsupported or lossy content: ```javascript const warnings = editor.getExportWarnings('markdown'); for (const w of warnings) { console.warn(w.message); } const md = editor.exportContent('markdown'); ``` Native editor tables **cannot** export to Markdown/MDX — export throws `SerializationError`. Use `exportContent('html')` instead. See [Serialization](/docs/serialization) for the full limitations list. --- ## 7. Image resize Enable the opt-in **`imageResize`** module and include **`image`** in the toolbar (or insert an image programmatically). Select a single image — a corner handle appears; drag to resize. Aspect ratio is preserved. ```javascript const editor = new Lextrix('#editor', { theme: 'snow', modules: { toolbar: [ ['bold', 'italic'], ['link', 'image'], ['clean'], ], imageResize: { minWidth: 48, maxWidth: null, // null = editor width }, }, }); // Insert via toolbar, drag-and-drop, or API: editor.insertEmbed(0, 'image', 'https://example.com/photo.jpg'); ``` **Programmatic resize** (sets the same `width` / `height` attributes as the handle): ```javascript editor.formatText(imageIndex, 1, { width: '400', height: '300' }, 'user'); ``` Does not apply to video or other embeds. Details: [Configuration — Image resize](/docs/configuration#image-resize) · [API reference — imageResize](/docs/commands#image-resize-imageresize) --- ## 8. Optional modules These features are opt-in or configured via `modules`. Full API: [API reference — Modules](/docs/commands#modules). ### Undo / redo Always available via `editor.history`. Keyboard: `Ctrl+Z` / `Ctrl+Shift+Z`. ```javascript editor.history.undo(); editor.history.redo(); ``` ### Image upload Override the default base64 handler to upload to your server: ```javascript modules: { toolbar: [['bold', 'italic'], ['link', 'image'], ['clean']], uploader: { mimetypes: ['image/png', 'image/jpeg', 'image/webp'], async handler(range, files) { for (const file of files) { const body = new FormData(); body.append('file', file); const res = await fetch('/api/upload', { method: 'POST', body }); const { url } = await res.json(); this.lextrix.insertEmbed(range.index, 'image', url, 'user'); range.index += 1; } }, }, } ``` Toolbar **image** and drag-and-drop both use this handler. ### Syntax highlighting Load [highlight.js](https://highlightjs.org/) first, then enable the module: ```javascript modules: { toolbar: [['bold', 'italic'], ['code-block'], ['clean']], syntax: { hljs: window.hljs, languages: [{ key: 'javascript', label: 'JavaScript' }], }, } ``` ### Tables ```javascript modules: { toolbar: [['bold', 'italic'], ['table'], ['clean']], table: true, } editor.getModule('table')?.insertTable(3, 4); ``` Remember: native tables cannot export to Markdown/MDX — use HTML export. --- ## 9. React / Next.js next steps Prefer the official wrapper: ```bash npm install lextrix @lextrix/react ``` - [React guide](/docs/react) — `LextrixEditor` (recommended) - [Frameworks](/docs/frameworks) — Next.js, Vue, script tag, manual mount - [DOM mounting](/docs/dom-mounting) — if you mount Lextrix yourself - Examples: [vite-vanilla](https://github.com/rishureetesh/lextrix/tree/main/examples/vite-vanilla) · [vite-react](https://github.com/rishureetesh/lextrix/tree/main/examples/vite-react) --- ## 10. Troubleshooting | Symptom | Fix | |---------|-----| | Unstyled editor | Import `lextrix/snow.css` (or another theme) | | `document is not defined` | Client-only — see [Frameworks](/docs/frameworks) | | Duplicate toolbars | Call `editor.destroy()` on unmount — [DOM mounting](/docs/dom-mounting) | | Markdown export throws | Native table in document — use HTML export or check [Serialization](/docs/serialization) | | No image resize handle | Enable `modules.imageResize` and select a **single** image — [Image resize](/docs/configuration#image-resize) | | Code blocks not highlighted | Load highlight.js and enable `modules.syntax` — [API reference](/docs/commands#syntax-highlighting-syntax) | | Upload does nothing | Check `uploader.mimetypes` matches file type — [API reference](/docs/commands#uploader-uploader) | More recipes: [Cookbook](/docs/cookbook) · Full API: [API reference](/docs/commands) --- ## What to read next | Goal | Guide | |------|-------| | Copy-paste recipes | [Cookbook](/docs/cookbook) | | Save on change, uploads, tables | [Cookbook](/docs/cookbook) | | Image resize | [Configuration — Image resize](/docs/configuration#image-resize) · [API reference](/docs/commands#image-resize-imageresize) | | Upload, syntax, tables | [Optional modules](#8-optional-modules) · [API reference — Modules](/docs/commands#modules) | | Custom serializers | [API reference — registerSerializer](/docs/commands#registerserializerserializer) | | All import/export formats | [Serialization](/docs/serialization) | | Module and toolbar options | [Configuration](/docs/configuration) | --- ## Cookbook Source: https://iamreetesh.com/docs/cookbook # Cookbook Copy-paste recipes for common tasks. Written for application developers integrating the **npm** package. **Start here if toolbars duplicate or themes break:** [DOM mounting](/docs/dom-mounting). --- ## 1. Minimal editor ```html
``` ```javascript import Lextrix from 'lextrix'; import 'lextrix/snow.css'; const wrapper = document.getElementById('wrapper'); const mount = document.createElement('div'); wrapper.appendChild(mount); const editor = new Lextrix(mount, { theme: 'snow', placeholder: 'Write here…', }); editor.setContents([{ insert: 'Hello\n' }]); ``` --- ## 2. Toolbar ```javascript new Lextrix(mount, { theme: 'snow', modules: { toolbar: [ ['bold', 'italic', 'underline', 'strike'], [{ header: [1, 2, 3, false] }], [{ list: 'ordered' }, { list: 'bullet' }], ['link', 'image'], ['clean'], ], }, }); ``` Use your own toolbar DOM: ```javascript modules: { toolbar: '#my-toolbar' } ``` Button names: see [Configuration — Toolbar](/docs/configuration#toolbar-buttons). --- ## 3. Save on change ```javascript editor.on('text-change', (delta, oldDelta, source) => { if (source === 'user') { const json = editor.getContents(); localStorage.setItem('draft', JSON.stringify(json)); } }); ``` `source` is `'user'`, `'api'`, or `'silent'`. Ignore `'api'` if you do not want programmatic updates to trigger saves. --- ## 4. Load saved content ```javascript const saved = localStorage.getItem('draft'); if (saved) { editor.setContents(JSON.parse(saved)); } ``` Or import from Markdown/HTML — [Serialization](/docs/serialization). --- ## 5. Read-only display ```javascript new Lextrix(mount, { theme: 'snow', readOnly: true, }); editor.setContents(documentJson); ``` --- ## 6. Serialization (Markdown, HTML, MDX, JSON) ```javascript // Export const md = editor.exportContent('markdown'); const html = editor.exportContent('html'); // Import (replaces document) editor.importContent('# Title\n\n**bold**', 'markdown'); editor.importContent('

Hello

', 'html'); // Slice const slice = editor.exportContent({ format: 'html', index: 0, length: 50 }); ``` Tables: native editor tables cannot export to Markdown/MDX — use HTML. Details: [Serialization](/docs/serialization). --- ## 7. Undo / redo Keyboard: `Ctrl+Z` / `Ctrl+Shift+Z` (focus must be in the editor). ```javascript editor.history.undo(); editor.history.redo(); editor.history.clear(); // new document / discard stack ``` Programmatic changes use `source: 'api'` by default and are undoable unless `history.userOnly` is true. --- ## 8. Image upload to your server Default: base64 in document. Override uploader: ```javascript modules: { uploader: { mimetypes: ['image/png', 'image/jpeg', 'image/webp'], async handler(range, files) { for (const file of files) { const body = new FormData(); body.append('file', file); const res = await fetch('/api/upload', { method: 'POST', body }); const { url } = await res.json(); this.lextrix.insertEmbed(range.index, 'image', url, 'user'); range.index += 1; } }, }, } ``` Toolbar **image** button and drag-and-drop both use this handler. --- ## 9. Image resize ```javascript modules: { imageResize: { minWidth: 48 } } ``` Select an image in the editor — drag the corner handle. --- ## 10. Syntax highlighting Load [highlight.js](https://highlightjs.org/) on the page first. ```javascript modules: { syntax: { hljs: window.hljs, languages: [ { key: 'javascript', label: 'JavaScript' }, { key: 'python', label: 'Python' }, ], }, } ``` --- ## 11. Tables ```javascript modules: { table: true } editor.getModule('table').insertTable(3, 4); // rows, columns ``` --- ## 12. Switch theme at runtime See [Themes](/docs/themes). Summary: 1. `getContents()` 2. Swap CSS 3. `wrapper.replaceChildren()` 4. New mount + `new Lextrix` 5. `setContents()` --- ## 13. React / Next.js Full guide: [Framework integration](/docs/frameworks). Rules: - `'use client'` + `dynamic(..., { ssr: false })` in Next.js - **Wrapper ref** + inner mount div - `import Lextrix from 'lextrix'` (default import) - Never `` in JSX --- ## 14. Custom formats (npm) ```javascript import Lextrix, { lxrPath } from 'lextrix'; class CalloutBlot { // blot implementation … } Lextrix.register({ [lxrPath.format('callout')]: CalloutBlot }); ``` `defineInlineTagFormat` and similar helpers need the monorepo. See [Formats](/docs/plugins). --- ## 15. Custom serializers ```javascript import { registerSerializer } from 'lextrix'; import { ChangeSet } from 'lextrix'; registerSerializer({ format: 'plain', import: (text) => new ChangeSet().insert(text).insert('\n'), export: (delta) => delta.getText(), }); ``` Call **before** `new Lextrix()`. --- ## 16. Check export warnings (Markdown / MDX) ```javascript const warnings = editor.getExportWarnings('markdown'); for (const w of warnings) { console.warn(w.message); // tables blocked, color/align lossy, etc. } const md = editor.exportContent('markdown'); // throws if native table present ``` --- ## Troubleshooting | Symptom | Likely cause | Fix | |---------|--------------|-----| | Multiple toolbars | Old 2.0.0 sibling toolbar, or no `destroy()` on remount | Upgrade to 2.0.1+; call `editor.destroy()` | | `Element type is invalid… undefined` | React: wrong import or `` | [Frameworks](/docs/frameworks) | | `document is not defined` | SSR / Server Component | Client-only + `ssr: false` | | Unstyled editor | Theme CSS not imported | `import 'lextrix/snow.css'` | | Undo does nothing | Editor not focused | `wrapper.click()` or `editor.focus()` | | `{ ChangeSet }` undefined (2.0.0) | Old UMD-only build | Upgrade to **2.0.1+** | --- ## API index | Task | Methods | |------|---------| | Content | `getContents`, `setContents`, `getText`, `updateContents` | | Format | `format`, `formatText`, `removeFormat` | | Selection | `getSelection`, `setSelection`, `focus` | | Events | `on('text-change')`, `on('selection-change')` | | Serialize | `importContent`, `exportContent` | Full list: [API reference](/docs/commands). --- ## React Integration Source: https://iamreetesh.com/docs/react # React integration Use **`@lextrix/react`** for a typed component with correct mount/teardown. Install **both** packages: ```bash npm install lextrix @lextrix/react react react-dom ``` Import a theme stylesheet (required): ```ts import 'lextrix/snow.css'; ``` ## `LextrixEditor` ```tsx 'use client'; // Next.js App Router only import { useState } from 'react'; import { LextrixEditor } from '@lextrix/react'; import 'lextrix/snow.css'; export function BlogEditor() { const [body, setBody] = useState('# Draft\n'); return ( ); } ``` ### Props | Prop | Description | |------|-------------| | `theme` | `snow`, `bubble`, `slate`, `dawn` — changing remounts the editor | | `options` | `LextrixOptions` (minus `theme`); applied on **mount only** | | `value` / `defaultValue` | Document string in `format` | | `format` | `html` \| `markdown` \| `mdx` \| `json` (default `html`) | | `onChange` | `(content, source) => void` after user edits | | `onSelectionChange` | Lextrix selection event | | `onReady` | `(editor) => void` once after create | | `className` / `style` | Wrapper element | ### Ref handle ```tsx import { useRef } from 'react'; import type { LextrixEditorHandle } from '@lextrix/react'; const ref = useRef(null); e.focus()} />; ref.current?.exportContent('markdown'); ref.current?.getEditor()?.getModule('history')?.undo(); ``` ## Remounting when options change `options` are not deep-watched. Remount with `key` when modules or placeholder change: ```tsx ``` ## Next.js - Component ships with `'use client'` - Do not import `lextrix` or `@lextrix/react` in Server Components - Dynamic import optional if you need to defer CSS: ```tsx import dynamic from 'next/dynamic'; const LextrixEditor = dynamic( () => import('@lextrix/react').then((m) => m.LextrixEditor), { ssr: false }, ); ``` ## Manual integration (no wrapper) See [Framework integration](/docs/frameworks#react-vite-cra-etc) for the wrapper-ref pattern behind this component. ## Example app ```bash npm run build npm run example:react ``` Source: [`examples/vite-react`](https://github.com/rishureetesh/lextrix/tree/main/examples/vite-react). --- ## Framework Integration Source: https://iamreetesh.com/docs/frameworks Lextrix is a **DOM-based editor**. Vanilla apps call `new Lextrix(container, options)`. React apps should use **[`@lextrix/react`](/docs/react)** (`LextrixEditor`). ## npm package layout | Package / import | When to use | |------------------|-------------| | `lextrix` | Core editor (required for everyone) | | `@lextrix/react` | Official React component — install **with** `lextrix` | | `import Lextrix from 'lextrix'` | Bundlers resolve the **ESM** build | | `import { ChangeSet, registerSerializer, lxrPath } from 'lextrix'` | Named APIs | | `import 'lextrix/snow.css'` | Theme stylesheet (required) | | Script tag `lextrix.js` | UMD global `window.Lextrix` | Always import a theme CSS file. The editor does not inject styles. ### npm vs monorepo | Need | npm | Monorepo (clone repo) | |------|-----|------------------------| | Editor + themes + serialization | `lextrix` | Same | | React component | `lextrix` + `@lextrix/react` | `packages/react` | | `ChangeSet`, `registerSerializer`, `lxrPath` | `import { … } from 'lextrix'` | Same | | Custom blots | `Lextrix.register()` | Or import from `lextrix-formats` | | Headless serialize only | Editor APIs, or clone for `lextrix-serialize` | `lextrix-serialize` | Lextrix requires a browser (`document`). Do not import it in Server Components without a client-only boundary. **Toolbar / remount bugs?** Read [DOM mounting](/docs/dom-mounting) — call `editor.destroy()` (or use `@lextrix/react`, which does this for you). --- ## React **Recommended:** [`@lextrix/react`](/docs/react) ```bash npm install lextrix @lextrix/react ``` ```tsx import { LextrixEditor } from '@lextrix/react'; import 'lextrix/snow.css'; ; ``` ### Manual mount (advanced) Only if you cannot use `@lextrix/react`. Use a **wrapper ref**, a **fresh inner div**, and `destroy()` on teardown. ```tsx 'use client'; // Next.js App Router only import { useEffect, useRef } from 'react'; import Lextrix from 'lextrix'; import 'lextrix/snow.css'; export default function ManualEditor() { const wrapperRef = useRef(null); useEffect(() => { const wrapper = wrapperRef.current; if (!wrapper) return; const mount = document.createElement('div'); wrapper.appendChild(mount); const editor = new Lextrix(mount, { theme: 'snow', placeholder: 'Start writing…', modules: { toolbar: [ ['bold', 'italic', 'underline'], [{ header: [1, 2, false] }], [{ list: 'ordered' }, { list: 'bullet' }], ['link', 'clean'], ], }, }); editor.setContents([{ insert: 'Hello Lextrix\n' }]); return () => { editor.destroy(); wrapper.replaceChildren(); }; }, []); return
; } ``` **Do not** render `` — `Lextrix` is a class, not a React component. For theme switching and controlled Markdown, prefer [`LextrixEditor`](/docs/react) with a remount `key` instead of hand-rolling effects. See [Themes](/docs/themes) · [React guide](/docs/react). --- ## Next.js App Router Use `@lextrix/react` from a **client** component (it includes `'use client'`): ```tsx 'use client'; import { LextrixEditor } from '@lextrix/react'; import 'lextrix/snow.css'; export default function PageEditor() { return ; } ``` Optional lazy load: ```tsx import dynamic from 'next/dynamic'; const LextrixEditor = dynamic( () => import('@lextrix/react').then((m) => m.LextrixEditor), { ssr: false }, ); ``` Do not import `lextrix` or `@lextrix/react` in Server Components. --- ## Vue 3 ```vue ``` --- ## Script tag (no bundler) Use the published package from **npm** or a **CDN**. After `npm install lextrix`, files are at the package root (e.g. `node_modules/lextrix/lextrix.snow.css`). **From npm (local static server):** ```html
``` **From jsDelivr CDN** (pin the version you use): ```html
``` UMD exposes `window.Lextrix` only. Named exports (`ChangeSet`, `registerSerializer`, …) require a bundler and the ESM entry (`import Lextrix from 'lextrix'`). --- ## Migrating from Quill | Quill | Lextrix | |-------|---------| | `new Quill(el, opts)` | `new Lextrix(el, opts)` | | `quill.getContents()` (Delta) | `editor.getContents()` (ChangeSet) | | `quill.root.innerHTML` | `editor.exportContent('html')` | | `import 'quill/dist/quill.snow.css'` | `import 'lextrix/snow.css'` | | Delta | **ChangeSet** (same JSON shape, different name) | See [change-set.md](https://github.com/rishureetesh/lextrix/blob/main/docs/guides/change-set.md) and [serialization.md](/docs/serialization). --- ## TypeScript Source: https://iamreetesh.com/docs/typescript # TypeScript The published `lextrix` npm package includes hand-written declarations (`lextrix.d.ts`). ## Setup ```bash npm install lextrix ``` ```typescript import Lextrix, { ChangeSet, lxrPath } from 'lextrix'; import type { LextrixOptions, LextrixModulesConfig, ImageResizeOptions, TableModule, ContentSerializer, SafetyIssue, Range, } from 'lextrix'; import 'lextrix/snow.css'; const modules: LextrixModulesConfig = { toolbar: [['bold', 'italic'], ['link', 'image']], table: true, imageResize: { minWidth: 48 } satisfies ImageResizeOptions, syntax: { hljs: globalThis.hljs, languages: [{ key: 'javascript', label: 'JS' }] }, }; const options: LextrixOptions = { theme: 'snow', modules }; const editor = new Lextrix('#editor', options); const table = editor.getModule('table') as TableModule | undefined; table?.insertTable(3, 3); ``` No `@types/lextrix` package is required. ## Covered APIs - `Lextrix` class and `LextrixOptions` - Module config types: `LextrixModulesConfig`, `ImageResizeOptions`, `ToolbarModuleOptions`, `SyntaxModuleOptions`, `UploaderModuleOptions`, `HistoryModuleOptions`, `ClipboardModuleOptions` - `TableModule` and `getModule('table')` - `ImageResizeModule` and `getModule('imageResize')` - `setText`, `getLeaf`, `getLine`, `getLines`, `getIndex`, `scrollSelectionIntoView` - `importContent`, `exportContent`, `listExportFormats`, `getExportWarnings` - `text-change` and `selection-change` handler signatures - `ChangeSet`, `Range`, `SafetyIssue`, `ContentSerializer`, serialization types - Named exports: `ChangeSet`, `lxrPath`, `registerSerializer`, `getMarkdownExportWarnings`, … ## Gaps Declarations are maintained manually and may lag new APIs. Report missing types on [GitHub Issues](https://github.com/rishureetesh/lextrix/issues). Advanced blot types (`Block`, `LeafBlot`, …) are not exported from the npm bundle types — use `unknown` or clone the monorepo for full internal typings. ## Example See [examples/vite-react](https://github.com/rishureetesh/lextrix/tree/main/examples/vite-react) for a typed React + Vite project. --- ## DOM Mounting Source: https://iamreetesh.com/docs/dom-mounting # DOM mounting (read this first) Lextrix is not a single DOM node. Understanding the layout prevents duplicate toolbars and leaked listeners. ## What Lextrix creates When you call `new Lextrix(mountElement, options)`: 1. **`mountElement`** becomes `editor.container` (class `lxr-container`). 2. If `modules.toolbar` is an **array**, Lextrix adds **`div.lxr-toolbar`** as the **first child** of `mountElement` (since 2.0.1). 3. Inside `mountElement`, **`div.lxr-editor`** holds the editable surface. ``` div (mountElement — passed to new Lextrix) ├── div.lxr-toolbar ← when toolbar is a config array └── div.lxr-editor ← editable area ``` Older 2.0.0 inserted the toolbar as a **sibling before** the mount node. Upgrade to **2.0.1+** if you still see stacked toolbars outside the mount. ## Cleanup ### Preferred: `editor.destroy()` ```javascript const editor = new Lextrix(mount, { theme: 'snow', modules: { toolbar: [...] } }); // theme switch, route leave, React unmount editor.destroy(); ``` `destroy()` removes the auto-created toolbar, clears theme `document.body` listeners, drops emitter handlers, and runs `mountElement.replaceChildren()`. ### Manual: clear the mount element ```javascript mount.replaceChildren(); // removes toolbar + editor inside mount (2.0.1+) ``` ### React wrapper pattern Still use an outer **wrapper** you control — especially when swapping themes or remounting: ```tsx const wrapperRef = useRef(null); const editorRef = useRef(null); useEffect(() => { const wrapper = wrapperRef.current; if (!wrapper) return; const mount = document.createElement('div'); wrapper.appendChild(mount); const editor = new Lextrix(mount, { theme, modules: { toolbar: [...] } }); editorRef.current = editor; return () => { editor.destroy(); editorRef.current = null; wrapper.replaceChildren(); }; }, [theme]); ``` ## Custom toolbar element | `modules.toolbar` | Toolbar location | |-------------------|------------------| | Array `[['bold'], …]` | Auto `div.lxr-toolbar` inside mount | | `'#my-toolbar'` | Your existing element | | `{ container: HTMLElement }` | Your element | You manage cleanup for custom toolbar DOM. ## Theme CSS Load one theme CSS file (`import 'lextrix/snow.css'`). When switching themes at runtime, swap CSS then `destroy()` + recreate. See [Themes](/docs/themes). ## Peer dependencies | Feature | Requires | If missing (2.0.1+) | |---------|----------|---------------------| | Formula button | [KaTeX](https://katex.org/) on `window.katex` | Button hidden; paste/import shows LaTeX source | | Syntax highlighting | [highlight.js](https://highlightjs.org/) | Module no-ops; code blocks work unhighlighted | ## Markdown export warnings Before `exportContent('markdown'|'mdx')`: ```javascript const warnings = editor.getExportWarnings('markdown'); // e.g. native tables (blocked), color/align (lossy) ``` Native editor tables still **throw** on export — use HTML or check warnings first. --- ## Serialization Source: https://iamreetesh.com/docs/serialization Import and export editor content as HTML, Markdown, MDX, or JSON. All formats go through **ChangeSet** — the same model as `getContents()`, clipboard, and OT. There is no direct HTML ↔ Markdown ↔ MDX conversion. To change formats, import into the editor (or `SerializerHost`) and export the target format. ## Quick start ```typescript import Lextrix from 'lextrix'; const editor = new Lextrix('#editor'); const html = editor.exportContent('html'); const md = editor.exportContent('markdown'); editor.importContent('

Hello world

', 'html'); editor.importContent('# Title\n\nParagraph.', 'markdown'); ``` Use `importContent` / `exportContent` instead of `import` / `export`. `Lextrix.import()` is the module loader and is unrelated. ## Limitations | Topic | Behavior | |-------|----------| | Native editor tables → Markdown/MDX | Throws `SerializationError` (`TABLE_EXPORT_UNSUPPORTED`). Use `exportContent('html')`. | | GFM tables (Markdown import) | Simple tables round-trip. No colspan, rowspan, or merged cells. | | HTML round-trip | Partial. Export uses `getSemanticHTML()`; re-import normalizes DOM structure. | | Markdown dialect | Subset only — not full CommonMark/GFM. | | MDX components | Experimental. JSX preserved as text unless you register handlers. | | Ordered lists | Numbering is sequential (`1.`, `2.`, …); nested lists restart at `1.` per indent level | | Combined bold+italic | May export as bold only | | Align, color, font | HTML/JSON only — not exported to Markdown/MDX | | Video embed | Markdown/MDX export as \`[video](url)\` | | Horizontal rule | Literal `---` paragraph exports as `\-\-\-` so it does not become a thematic break | | Inline escaping | Only `\*`, `` \` ``, `\_`, `\[`, `\]` — not `.` or `!` at end of sentences | ### MDX components (experimental) `registerMdxComponent` is not stable API. - Unknown JSX is stored as raw text with an `mdx-component` block attribute - Frontmatter and `import`/`export` lines are stripped on import - Parsing uses regex, not an AST — nested or complex JSX will miss edge cases - No component embed blot, in-editor preview, or JSX runtime - Use MDX export for CMS storage; register `toChangeSet` / `fromChangeSet` only for components you control and test ## API ### Editor ```typescript editor.exportContent(format: string): string editor.exportContent({ format, index?, length? }): string editor.importContent(content: string, format: string, source?: EmitterSource): void editor.listExportFormats(): string[] ``` `export()` and `import()` are aliases. Prefer `exportContent` / `importContent` in application code. ### Global registration ```typescript import { registerSerializer, registerMdxComponent } from 'lextrix'; import ChangeSet from 'lextrix-change'; registerSerializer({ format: 'plain', import: (text) => new ChangeSet([{ insert: text }, { insert: '\n' }]), export: (delta) => delta.getText(), }); ``` Call `registerSerializer` before creating editors. Serializers registered globally are merged into each editor unless `serializers: false`. ### Headless (`lextrix-serialize`) Monorepo / contributors only — the npm package bundles serialization into `lextrix`; use `editor.importContent` / `exportContent` in apps. ```typescript import { SerializerHost, createSerializerRegistry, markdownSerializer, } from 'lextrix-serialize'; const host = new SerializerHost(createSerializerRegistry([markdownSerializer()])); const delta = host.parse('# Hello', 'markdown'); const md = host.stringify(delta, 'markdown'); ``` `host.export()` needs a bound editor adapter. For headless export, use `host.stringify()`. ### `LextrixOptions.serializers` | Value | Effect | |-------|--------| | omitted or `true` | Built-in formats (json, html, markdown, mdx) plus globals | | `ContentSerializer[]` | Listed serializers plus globals | | `false` | No serializers, including globals | ## Examples ### Partial export ```typescript const slice = editor.exportContent({ format: 'html', index: 0, length: 100 }); ``` ### React / Next.js Prefer **[`@lextrix/react`](/docs/react)** so mount/teardown and `format` are handled for you: ```tsx import { LextrixEditor } from '@lextrix/react'; import 'lextrix/snow.css'; save(mdx)} /> ``` Manual mount pattern: [Frameworks](/docs/frameworks) · [DOM mounting](/docs/dom-mounting). ### Migration from older patterns | Before | After | |--------|-------| | `editor.getSemanticHTML()` | `editor.exportContent('html')` | | `clipboard.convert({ html })` + `setContents` | `editor.importContent(html, 'html')` | | `JSON.stringify(editor.getContents().ops)` | `editor.exportContent('json')` | ## How it works ``` HTML / Markdown / MDX / JSON ↓ import ChangeSet ↓ export HTML / Markdown / MDX / JSON ``` | Package | Role | |---------|------| | `lextrix-change` | ChangeSet ops | | `lextrix-modules/html-import` | HTML string → ChangeSet (shared with clipboard paste) | | `lextrix-core` | Editor API, HTML export via blot tree | | `lextrix-serialize` | Markdown, MDX, JSON ↔ ChangeSet; registry; host | `lextrix-serialize` does not depend on `lextrix-core`. HTML export is DOM-backed, not derived purely from ChangeSet. ## Testing ```bash npm run test -w lextrix-serialize npm run test -w lextrix-core npm run test:unit -w lextrix ``` Tests: `packages/serialize/tests/`, `packages/lextrix/test/unit/core/serialization.spec.ts`, `packages/lextrix/test/e2e/serialization.spec.ts`. --- ## API Reference Source: https://iamreetesh.com/docs/commands # API reference Lextrix exposes a familiar rich-text API centered on **ChangeSet** (operational transforms over document content). ## Construction ```javascript const editor = new Lextrix(container, options); ``` Static properties: `Lextrix.version`, `Lextrix.events`, `Lextrix.sources`, `Lextrix.import()`, `Lextrix.register()`. ### Lifecycle | Method | Description | |--------|-------------| | `destroy()` | Remove toolbar, theme listeners, and editor DOM inside the mount container | | `getExportWarnings(input)` | Lossy/unsupported issues before Markdown/MDX export (does not throw) | See [DOM mounting](/docs/dom-mounting). ### Editor options | Option | Type | Default | Description | |--------|------|---------|-------------| | `theme` | `string` | `'snow'` | `snow` \| `bubble` \| `slate` \| `dawn` | | `placeholder` | `string` | `''` | Shown when the document is empty | | `readOnly` | `boolean` | `false` | Disable editing (`editor.enable()` / `disable()`) | | `bounds` | `HTMLElement` \| `string` \| `null` | `null` | Root for tooltip positioning | | `formats` | `string[]` \| `null` | `null` | Whitelist of format names; `null` = all registered | | `modules` | `object` | `{}` | Module configuration — see [Modules](#modules) | | `debug` | `boolean` \| `string` | `false` | Log level for development | ```javascript new Lextrix('#editor', { theme: 'snow', placeholder: 'Write here…', readOnly: false, modules: { toolbar: [['bold', 'italic']], table: true }, }); ``` ## Serialization Import and export whole documents or slices. Prefer **`importContent`** / **`exportContent`** in application code — not `Lextrix.import()`, which loads internal modules. Full limitations (tables, lossy formats): [Serialization guide](/docs/serialization). ### `importContent(content, format, source?)` | | | |---|---| | **Parameters** | `content` — string to parse · `format` — `'html'` \| `'markdown'` \| `'mdx'` \| `'json'` · `source` — optional `'user'` \| `'api'` \| `'silent'` (default `'api'`) | | **Returns** | `ChangeSet` applied to the editor (document replaced) | ```javascript editor.importContent('# Hello\n\n**bold**', 'markdown'); editor.importContent('

Hello world

', 'html'); ``` ### `exportContent(input)` | | | |---|---| | **Parameters** | `input` — format string (`'html'`, `'markdown'`, `'mdx'`, `'json'`) **or** `{ format, index?, length? }` for a slice | | **Returns** | `string` — serialized content | | **Throws** | `SerializationError` when export is unsupported (e.g. native editor table → Markdown/MDX) | ```javascript const md = editor.exportContent('markdown'); const html = editor.exportContent('html'); const slice = editor.exportContent({ format: 'html', index: 0, length: 50 }); ``` Aliases: `editor.import()` / `editor.export()` — same behavior. ### `listExportFormats()` | | | |---|---| | **Returns** | `string[]` — formats registered for this editor (default includes `html`, `markdown`, `mdx`, `json`) | ```javascript editor.listExportFormats(); // ['html', 'markdown', 'mdx', 'json', …] ``` ### `getExportWarnings(input)` | | | |---|---| | **Parameters** | Same shape as `exportContent` — format string or `{ format, index?, length? }`. Only **`markdown`** and **`mdx`** produce warnings. | | **Returns** | `SafetyIssue[]` — `{ feature, safety: 'lossy' \| 'unsupported', message }`. Does **not** throw. | ```javascript const warnings = editor.getExportWarnings('markdown'); if (warnings.some((w) => w.safety === 'unsupported')) { // e.g. native editor table — use exportContent('html') instead } const md = editor.exportContent('markdown'); // throws if unsupported content remains ``` ## Content | Method | Description | |--------|-------------| | `getContents(index?, length?)` | ChangeSet JSON | | `setContents(delta, source?)` | Replace document | | `getText(index?, length?)` | Plain text | | `getLength()` | Document length | | `getSemanticHTML(index?, length?)` | HTML string | | `insertText(index, text, formats?, source?)` | Insert text | | `insertEmbed(index, name, value, source?)` | Insert image, video, formula | | `deleteText(index, length, source?)` | Delete range | | `updateContents(delta, source?)` | Apply ChangeSet | ## Formatting | Method | Description | |--------|-------------| | `format(name, value, source?)` | Format at cursor | | `formatText(index, length, name, value, source?)` | Format range | | `formatLine(index, length, name, value, source?)` | Block format | | `getFormat(index?, length?)` | Active formats | | `removeFormat(index, length, source?)` | Clear inline formats | ## Selection | Method | Description | |--------|-------------| | `getSelection(focus?)` | `{ index, length }` or null | | `setSelection(index, length?, source?)` | Set selection | | `getBounds(index, length?)` | Pixel rect for caret/range | | `focus()` / `blur()` / `hasFocus()` | Focus management | ## History Access via `editor.history` (always loaded). Configure with `modules.history`. | Method | Description | |--------|-------------| | `history.undo()` | Undo | | `history.redo()` | Redo | | `history.clear()` | Clear stacks | | `history.cutoff()` | Start a new undo branch | Keyboard: `Ctrl+Z` / `Ctrl+Shift+Z` when the editor is focused. **Module options** (`modules.history`): | Option | Default | Description | |--------|---------|-------------| | `delay` | `1000` | Milliseconds before merging consecutive undo steps | | `maxStack` | `100` | Maximum undo depth | | `userOnly` | `false` | When `true`, only `'user'`-sourced changes are undoable | ## Modules Core modules (`clipboard`, `keyboard`, `history`, `uploader`) load automatically. Opt-in modules require `modules.` in constructor options. ```javascript editor.getModule('toolbar'); editor.getModule('table'); editor.getModule('syntax'); editor.getModule('imageResize'); editor.clipboard; // shorthand editor.keyboard; editor.uploader; editor.history; ``` ### Toolbar (`toolbar`) | | | |---|---| | **Enable** | `modules: { toolbar: […] }` — button groups array, `'#selector'`, or `{ container, handlers }` | | **Access** | `editor.getModule('toolbar')` | Pass an array to auto-create `div.lxr-toolbar` as the **first child** of the mount element: ```javascript modules: { toolbar: [ ['bold', 'italic', 'underline'], [{ header: [1, 2, false] }], ['link', 'image', 'video', 'formula', 'table'], ['clean'], ], } ``` Custom handlers override theme defaults: ```javascript modules: { toolbar: { container: [['image'], ['table']], handlers: { table() { this.lextrix.getModule('table')?.insertTable(3, 3); }, }, }, } ``` More: [Configuration — Toolbar buttons](/docs/configuration#toolbar-buttons) ### Table (`table`) | | | |---|---| | **Enable** | `modules: { table: true }` | | **Access** | `editor.getModule('table')` | ```javascript editor.getModule('table')?.insertTable(3, 4); // rows, columns ``` Also: `insertRowAbove()`, `insertRowBelow()`, `insertColumnLeft()`, `insertColumnRight()`, `deleteRow()`, `deleteColumn()`. Native editor tables **cannot** export to Markdown/MDX — use `exportContent('html')`. See [Serialization](/docs/serialization). ### Syntax highlighting (`syntax`) | | | |---|---| | **Enable** | `modules: { syntax: true }` or options object | | **Requires** | [highlight.js](https://highlightjs.org/) on the page (`window.hljs`) | ```javascript modules: { syntax: { hljs: window.hljs, languages: [ { key: 'javascript', label: 'JavaScript' }, { key: 'python', label: 'Python' }, ], }, } ``` Without highlight.js the module no-ops (code blocks still work, without token colors). ### Uploader (`uploader`) Always loaded. Handles drag-and-drop and the toolbar **image** button. | Option | Default | Description | |--------|---------|-------------| | `mimetypes` | `['image/png', 'image/jpeg']` | Allowed file types | | `handler` | base64 embed | `(range, files) => void` — `this.lextrix` is the editor | ```javascript modules: { uploader: { mimetypes: ['image/png', 'image/jpeg', 'image/webp'], async handler(range, files) { for (const file of files) { const body = new FormData(); body.append('file', file); const res = await fetch('/api/upload', { method: 'POST', body }); const { url } = await res.json(); this.lextrix.insertEmbed(range.index, 'image', url, 'user'); range.index += 1; } }, }, } ``` ### Clipboard (`clipboard`) Always loaded. Normalizes paste from HTML, Word, Google Docs. | Option | Description | |--------|-------------| | `matchers` | `[selector, matcherFn][]` — custom HTML → ChangeSet rules | ```javascript modules: { clipboard: { matchers: [ ['B', (node, delta) => delta.insert(node.textContent ?? '', { bold: true })], ], }, } ``` ### Image resize (`imageResize`) Opt-in module. When exactly **one image embed** is selected, shows a corner drag handle and preserves aspect ratio while resizing. | | | |---|---| | **Enable** | `modules: { imageResize: true }` or an options object (see below) | | **Access** | `editor.getModule('imageResize')` | | **Applies to** | Image embeds only — not video, formula, or other embeds | **Options** (pass as `modules.imageResize`): | Option | Type | Default | Description | |--------|------|---------|-------------| | `minWidth` | `number` | `48` | Minimum width in pixels | | `maxWidth` | `number` \| `null` | `null` | Maximum width; `null` = editor root width | ```javascript modules: { toolbar: [['bold', 'italic'], ['link', 'image'], ['clean']], imageResize: { minWidth: 48, maxWidth: null }, }, ``` Select an image in the editor, then drag the handle. Resize commits `width` and `height` attributes on the image blot. **Programmatic resize** (same attributes the handle sets): ```javascript editor.formatText(index, 1, { width: '400', height: '300' }, 'user'); ``` Requires theme CSS (included in `lextrix/snow.css`, etc.) for `.lxr-image-resize` overlay styles. The overlay mounts on `.lxr-container` and repositions after layout settles (selection, scroll, and resize). Always call `editor.destroy()` on unmount. More: [Configuration — Image resize](/docs/configuration#image-resize) · [Modules](/docs/plugins#image-resize) ## Registration helpers Call **before** `new Lextrix()` unless noted. ### `registerSerializer(serializer)` Register a custom import/export format globally. | | | |---|---| | **Parameter** | `{ format, import(content, context?), export(changeSet, context?) }` | | **See also** | [Serialization — Custom serializers](/docs/serialization) | ```javascript import { registerSerializer } from 'lextrix'; registerSerializer({ format: 'plain', import: (text) => [{ insert: text }], export: (delta) => delta.getText(), }); ``` ### `unregisterSerializer(format)` Remove a registered format. Returns `boolean`. ### `registerMdxComponent(name, handler)` Register a JSX component handler for MDX import/export. Experimental — see [Serialization](/docs/serialization). ### `getMarkdownExportWarnings(delta)` Headless helper — same warnings as `editor.getExportWarnings('markdown')` without an editor instance. ```javascript import { getMarkdownExportWarnings, ChangeSet } from 'lextrix'; getMarkdownExportWarnings(new ChangeSet().insert('Hello\n')); ``` ## Events ```javascript editor.on('text-change', (delta, oldDelta, source) => {}); editor.on('selection-change', (range, oldRange, source) => {}); editor.on('editor-change', (eventName, ...args) => {}); ``` Sources: `'user'`, `'api'`, `'silent'`. ## ChangeSet ```javascript import { ChangeSet } from 'lextrix'; new ChangeSet() .retain(5) .insert('Hello', { bold: true }) .delete(2); ``` Legacy name `Delta` is not supported — use `ChangeSet`. See the [ChangeSet guide](https://github.com/rishureetesh/lextrix/blob/main/docs/guides/change-set.md). ## Registry paths Register custom formats or modules: ```javascript import Lextrix, { lxrPath } from 'lextrix'; Lextrix.register({ [lxrPath.format('callout')]: CalloutBlot }); Lextrix.register({ [lxrPath.module('mentions')]: MentionsModule }); ``` Bare paths like `formats/bold` or `parchment` throw — use `lxr/*` paths only. See the [Formats guide](/docs/plugins#registration). Advanced blot helpers (`defineInlineTagFormat`, …) require the monorepo — see [Framework integration](/docs/frameworks). --- ## Themes Source: https://iamreetesh.com/docs/themes Four built-in themes: **snow**, **bubble**, **slate**, **dawn**. ## Install CSS Themes are separate CSS files. Import exactly one (or swap at runtime): ```javascript import 'lextrix/snow.css'; // import 'lextrix/bubble.css'; // import 'lextrix/slate.css'; // import 'lextrix/dawn.css'; ``` | Theme | Toolbar style | Best for | |-------|---------------|----------| | `snow` | Fixed bar above editor | Docs, CMS, classic UI | | `bubble` | Floating on selection | Comments, inline editing | | `slate` | Dark snow-like bar | Dark apps | | `dawn` | Warm palette | Marketing / editorial | ## Basic usage ```javascript import Lextrix from 'lextrix'; import 'lextrix/snow.css'; const mount = document.createElement('div'); document.getElementById('wrapper').appendChild(mount); new Lextrix(mount, { theme: 'snow', modules: { toolbar: [ ['bold', 'italic', 'underline'], [{ header: [1, 2, false] }], ['link', 'clean'], ], }, }); ``` Bubble uses a smaller default toolbar set — see [Configuration](/docs/configuration#toolbar-buttons). ## Switch theme at runtime 1. Save document: `const contents = editor.getContents()` 2. Load new CSS (replace `` href or dynamic `import()`) 3. **Clear wrapper** (not just the mount div) — see [DOM mounting](/docs/dom-mounting) 4. Create fresh inner mount + `new Lextrix(mount, { theme: newTheme, … })` 5. `editor.setContents(contents)` ```javascript async function switchTheme(wrapper, editor, newTheme) { const contents = editor.getContents(); await loadThemeCss(newTheme); // your helper — swap link href wrapper.replaceChildren(); const mount = document.createElement('div'); wrapper.appendChild(mount); return new Lextrix(mount, { theme: newTheme, modules: { toolbar: toolbarFor(newTheme) }, }).setContents(contents); } ``` ### Race conditions If the user clicks themes quickly: - **Debounce** or disable buttons while switching - **Await** CSS load before creating the editor - Always **`wrapper.replaceChildren()`** before each `new Lextrix` — never stack instances Duplicate toolbars = cleanup skipped on parent wrapper. Not a Lextrix race bug; a DOM lifecycle bug. ## React example Prefer [`@lextrix/react`](/docs/react) — remount with `key` when the theme changes: ```tsx import { LextrixEditor } from '@lextrix/react'; import 'lextrix/snow.css'; // or dynamically swap theme CSS in your app export function ThemedEditor({ theme }: { theme: 'snow' | 'bubble' | 'slate' | 'dawn' }) { return ( ); } ``` Load the matching `lextrix/{theme}.css` when switching themes (global `` or bundler import). Manual mount pattern (without `@lextrix/react`): [Frameworks](/docs/frameworks) · [DOM mounting](/docs/dom-mounting). --- ## Configuration Source: https://iamreetesh.com/docs/configuration # Configuration > **Integrating in React or switching themes?** Call `editor.destroy()` on teardown — see [DOM mounting](/docs/dom-mounting). ## Editor options ```typescript new Lextrix(container, { theme: 'snow', // snow | bubble | slate | dawn placeholder: '', readOnly: false, bounds: null, // tooltip positioning root debug: false, formats: null, // null = all registered formats; or string[] modules: { /* … */ }, }); ``` ## Modules ### Always enabled These load automatically — configure via `modules.`: | Module | Options | Purpose | |--------|---------|---------| | `clipboard` | `matchers` | Paste HTML → ChangeSet | | `keyboard` | bindings | Shortcuts | | `history` | `delay`, `maxStack`, `userOnly` | Undo / redo | | `uploader` | `mimetypes`, `handler` | Drag-drop & toolbar image upload | ### Opt-in modules | Module | Enable | Notes | |--------|--------|-------| | `toolbar` | `toolbar: […]` or `'#selector'` | Button names — see below | | `syntax` | `syntax: true` | Needs highlight.js | | `table` | `table: true` | Table blots + cell keyboard nav | | `imageResize` | `imageResize: true` | Drag handle on selected images | ### Toolbar buttons Pass an array of groups: ```javascript modules: { toolbar: [ ['bold', 'italic', 'underline'], [{ header: [1, 2, 3, 4, 5, false] }], [{ list: 'ordered' }, { list: 'bullet' }, { list: 'check' }], [{ indent: '-1' }, { indent: '+1' }], [{ align: [] }, { direction: 'rtl' }], ['blockquote', 'code-block'], ['link', 'image', 'video', 'formula'], ['clean'], ], } ``` Or point to existing DOM: ```javascript modules: { toolbar: '#my-toolbar' } ``` When `toolbar` is an **array**, Lextrix creates `div.lxr-toolbar` as the **first child** of your mount element. Call `editor.destroy()` when tearing down — see [DOM mounting](/docs/dom-mounting). Custom handlers override theme defaults: ```javascript modules: { toolbar: { container: ['image'], handlers: { image() { /* custom */ }, }, }, } ``` ## Image upload to your API Default uploader embeds images as base64. Override the handler: ```javascript modules: { uploader: { mimetypes: ['image/png', 'image/jpeg', 'image/webp'], async handler(range, files) { for (const file of files) { const body = new FormData(); body.append('file', file); const res = await fetch('/api/upload', { method: 'POST', body }); const { url } = await res.json(); this.lextrix.insertEmbed(range.index, 'image', url, 'user'); range.index += 1; } }, }, } ``` The theme **image** button and drag-and-drop both call `uploader.upload()`. ## Image resize Enable the module and select an image in the editor — a corner handle appears. ```javascript modules: { imageResize: { minWidth: 48, maxWidth: null, // null = editor width }, } ``` Resize sets `width` and `height` attributes on the `` blot. Programmatic resize: ```javascript editor.formatText(index, 1, { width: '400', height: '300' }, 'user'); ``` See also: [API reference — imageResize](/docs/commands#image-resize-imageresize) · [Modules — Image resize](/docs/plugins#image-resize) ## Format whitelist Restrict allowed formats: ```javascript formats: ['bold', 'italic', 'link', 'header', 'list', 'image'], ``` Registered format names: `bold`, `italic`, `underline`, `strike`, `link`, `script`, `header`, `list`, `blockquote`, `code`, `code-block`, `color`, `background`, `font`, `size`, `align`, `direction`, `indent`, `image`, `video`, `formula`. ## Syntax highlighting Load highlight.js, then: ```javascript modules: { syntax: { hljs: window.hljs, languages: [ { key: 'javascript', label: 'JavaScript' }, { key: 'python', label: 'Python' }, ], interval: 1000, }, } ``` ## Tables ```javascript modules: { table: true } // Insert programmatically editor.getModule('table').insertTable(3, 4); // rows, columns ``` ## Modular registration (advanced) Build a custom bundle from workspace packages: ```javascript import Lextrix, { registerBlots } from 'lextrix-core'; import { registerFormats } from 'lextrix-formats'; import { registerCoreModules, registerOptionalModules } from 'lextrix-modules'; import { registerUI } from 'lextrix-ui'; import { registerThemes } from 'lextrix-themes'; registerBlots(Lextrix); registerFormats(Lextrix); registerCoreModules(Lextrix); registerOptionalModules(Lextrix); registerUI(Lextrix); registerThemes(Lextrix); ``` See [Modules](/docs/plugins) for per-module details and authoring. --- ## Plugin System Source: https://iamreetesh.com/docs/plugins # Modules ## Core (always loaded) ### Clipboard Normalizes paste from HTML, Word, Google Docs, etc. Add custom matchers: ```javascript modules: { clipboard: { matchers: [ ['B', (node, delta) => delta.insert(node.textContent, { bold: true })], ], }, } ``` ### Keyboard Default bindings for Enter in lists/headers, tab indent, link shortcuts (snow theme adds Ctrl/Cmd+K). Extend: ```javascript editor.keyboard.addBinding({ key: 'b', shortKey: true }, (range, context) => { editor.format('bold', !context.format.bold); }); ``` ### History | Option | Default | Description | |--------|---------|-------------| | `delay` | `1000` | ms before merging undo steps | | `maxStack` | `100` | Max undo depth | | `userOnly` | `false` | Ignore API changes | ### Uploader Handles drag-and-drop and cooperates with the toolbar image button. See [Configuration](/docs/configuration#image-upload-to-your-api). --- ## Optional ### Toolbar Renders formatting controls. Theme provides default handlers for `image`, `video`, and `formula`. Snow theme auto-builds a default toolbar when `modules.toolbar` is set without a `container`. ### Syntax Requires **highlight.js**. Adds a language `