@synclineapi/mdx-editor 0.1.1 → 1.0.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 +320 -140
- package/dist/core/config/EditorConfig.d.ts +5 -0
- package/dist/core/config/defaults.d.ts +33 -0
- package/dist/core/config/index.d.ts +2 -0
- package/dist/core/dom/editor-pane.d.ts +7 -0
- package/dist/core/dom/editor-root.d.ts +8 -0
- package/dist/core/dom/index.d.ts +11 -0
- package/dist/core/dom/preview-pane.d.ts +5 -0
- package/dist/core/dom/skip-link.d.ts +1 -0
- package/dist/core/dom/status-bar.d.ts +13 -0
- package/dist/core/dom/toolbar-dom.d.ts +5 -0
- package/dist/core/editor.d.ts +99 -6
- package/dist/core/mdx-themes.d.ts +3 -0
- package/dist/core/platform.d.ts +31 -0
- package/dist/core/plugin-manager.d.ts +1 -24
- package/dist/core/plugins/PluginContext.d.ts +24 -0
- package/dist/core/plugins/PluginManager.d.ts +33 -0
- package/dist/core/plugins/index.d.ts +3 -0
- package/dist/core/renderer/CodeRenderer.d.ts +4 -0
- package/dist/core/renderer/ListRenderer.d.ts +5 -0
- package/dist/core/renderer/MarkdownRenderer.d.ts +7 -0
- package/dist/core/renderer/MdxValidator.d.ts +3 -0
- package/dist/core/renderer/Renderer.d.ts +10 -0
- package/dist/core/renderer/TableRenderer.d.ts +4 -0
- package/dist/core/renderer/index.d.ts +7 -0
- package/dist/core/renderer/sanitize.d.ts +1 -0
- package/dist/core/renderer.d.ts +6 -28
- package/dist/core/toolbar.d.ts +1 -0
- package/dist/core/types.d.ts +72 -2
- package/dist/core/ui/AutocompleteController.d.ts +58 -0
- package/dist/core/ui/DropdownController.d.ts +8 -0
- package/dist/core/ui/FindReplaceController.d.ts +60 -0
- package/dist/core/ui/LineNumberController.d.ts +218 -0
- package/dist/core/ui/ModeController.d.ts +9 -0
- package/dist/core/ui/PreviewController.d.ts +13 -0
- package/dist/core/ui/ResponsiveController.d.ts +30 -0
- package/dist/core/ui/SplitterController.d.ts +31 -0
- package/dist/core/ui/StatusBarController.d.ts +16 -0
- package/dist/core/ui/ThemeController.d.ts +8 -0
- package/dist/core/ui/ToolbarController.d.ts +10 -0
- package/dist/core/ui/UIController.d.ts +43 -0
- package/dist/core/ui/index.d.ts +12 -0
- package/dist/index.d.ts +28 -1
- package/dist/plugins/basic-formatting/index.d.ts +1 -0
- package/dist/plugins/callouts/index.d.ts +1 -0
- package/dist/plugins/diagrams/index.d.ts +1 -0
- package/dist/plugins/index.d.ts +1 -11
- package/dist/plugins/layout/index.d.ts +3 -0
- package/dist/plugins/lists/index.d.ts +1 -0
- package/dist/plugins/media/index.d.ts +2 -0
- package/dist/plugins/utilities/index.d.ts +5 -0
- package/dist/style.css +1 -1
- package/dist/syncline-mdx-editor.js +8468 -1812
- package/dist/syncline-mdx-editor.umd.cjs +958 -87
- package/package.json +6 -12
package/README.md
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
# @synclineapi/mdx-editor
|
|
2
2
|
|
|
3
|
-
[](https://github.com/RadhaHariharan/syncline-mdx-editor/actions/workflows/ci.yml)
|
|
4
3
|
[](https://www.npmjs.com/package/@synclineapi/mdx-editor)
|
|
5
|
-
[](https://opensource.org/licenses/MIT)
|
|
6
4
|
[](https://www.typescriptlang.org/)
|
|
7
5
|
[](https://www.w3.org/TR/WCAG21/)
|
|
6
|
+
[](https://markdown.synclineapi.com/)
|
|
8
7
|
|
|
9
|
-
A **framework-agnostic**, plugin-based MDX/Markdown editor with
|
|
8
|
+
A **framework-agnostic**, plugin-based MDX/Markdown editor with a high-performance virtual-rendering code pane, live HTML preview, a configurable toolbar, and 33+ built-in content plugins. Works with React, Vue, Angular, Next.js, Svelte, or plain JavaScript.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
**[Live Demo →](https://markdown.synclineapi.com/)**
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
12
15
|
|
|
13
16
|
```bash
|
|
14
17
|
npm install @synclineapi/mdx-editor
|
|
15
18
|
```
|
|
16
19
|
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
17
24
|
### Vanilla JavaScript
|
|
18
25
|
|
|
19
26
|
```html
|
|
20
|
-
<div id="editor"></div>
|
|
27
|
+
<div id="editor" style="height: 600px;"></div>
|
|
21
28
|
|
|
22
29
|
<script type="module">
|
|
23
30
|
import { createEditor } from '@synclineapi/mdx-editor';
|
|
@@ -25,15 +32,36 @@ npm install @synclineapi/mdx-editor
|
|
|
25
32
|
|
|
26
33
|
const editor = createEditor({
|
|
27
34
|
container: '#editor',
|
|
28
|
-
value: '# Hello World',
|
|
29
|
-
onChange: (markdown) => console.log(
|
|
35
|
+
value: '# Hello World\n\nStart writing *MDX* here.',
|
|
36
|
+
onChange: (markdown) => console.log(markdown),
|
|
30
37
|
});
|
|
31
38
|
</script>
|
|
32
39
|
```
|
|
33
40
|
|
|
41
|
+
### React
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { useEffect, useRef } from 'react';
|
|
45
|
+
import { createEditor, type SynclineMDXEditor } from '@synclineapi/mdx-editor';
|
|
46
|
+
import '@synclineapi/mdx-editor/style.css';
|
|
47
|
+
|
|
48
|
+
export function Editor({ value, onChange }: { value: string; onChange: (v: string) => void }) {
|
|
49
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
50
|
+
const editor = useRef<SynclineMDXEditor | null>(null);
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!ref.current) return;
|
|
54
|
+
editor.current = createEditor({ container: ref.current, value, onChange });
|
|
55
|
+
return () => editor.current?.destroy();
|
|
56
|
+
}, []);
|
|
57
|
+
|
|
58
|
+
return <div ref={ref} style={{ height: 600 }} />;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
34
62
|
### Custom Plugin Selection
|
|
35
63
|
|
|
36
|
-
```
|
|
64
|
+
```ts
|
|
37
65
|
import { SynclineMDXEditor, boldPlugin, italicPlugin, headingPlugin } from '@synclineapi/mdx-editor';
|
|
38
66
|
import '@synclineapi/mdx-editor/style.css';
|
|
39
67
|
|
|
@@ -44,83 +72,266 @@ const editor = new SynclineMDXEditor({
|
|
|
44
72
|
});
|
|
45
73
|
```
|
|
46
74
|
|
|
47
|
-
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Features
|
|
78
|
+
|
|
79
|
+
### 33+ Built-in Plugins
|
|
48
80
|
|
|
49
81
|
| Category | Plugins |
|
|
50
82
|
|----------|---------|
|
|
51
|
-
| **Formatting** | Heading (
|
|
83
|
+
| **Formatting** | Heading (H1–H6), Bold, Italic, Strikethrough, Quote |
|
|
52
84
|
| **Links & Media** | Link, Image, Image Background, Image Frame |
|
|
53
|
-
| **Code** | Inline Code, Code Block
|
|
85
|
+
| **Code** | Inline Code, Fenced Code Block |
|
|
54
86
|
| **Lists** | Unordered List, Ordered List, Task List |
|
|
55
|
-
| **Layout** | Table, Multi-Column (2–5), Tabs, Container |
|
|
87
|
+
| **Layout** | Table, Multi-Column (2–5 cols), Tabs, Container |
|
|
56
88
|
| **Components** | Accordion, Accordion Group, Card, Card Group, Steps |
|
|
57
|
-
| **Callouts** | Admonition (Tip/Warning/Caution/Danger/
|
|
58
|
-
| **Rich Content** | Highlight (8
|
|
89
|
+
| **Callouts** | Admonition (Tip / Warning / Caution / Danger / Info / Note), Tip (Good / Bad / Info) |
|
|
90
|
+
| **Rich Content** | Highlight (8 colours), Emoji, Formula (KaTeX inline & block), Tooltip, Copy Text |
|
|
59
91
|
| **Embeds** | YouTube, Video file, GitHub Gist, Twitter/X, CodePen, CodeSandbox |
|
|
60
92
|
| **Diagrams** | Mermaid (Flowchart, Sequence, Class, State, ER, Journey, Gantt) |
|
|
61
93
|
| **Insert** | API Endpoint, Markdown Import, Code Snippet |
|
|
62
94
|
|
|
63
|
-
|
|
95
|
+
### Editor Engine
|
|
96
|
+
|
|
97
|
+
- **Virtual rendering** — only visible rows are in the DOM; handles documents of 10 000+ lines at 60 fps
|
|
98
|
+
- **Word wrap** — proper pixel-width wrap with full active-line highlight across all visual rows
|
|
99
|
+
- **MDX autocomplete** — context-aware component-tag and attribute suggestions; Tab-trigger snippet expansion
|
|
100
|
+
- **Single-colour prose mode** — all syntax tokens collapse to the text colour for distraction-free writing
|
|
101
|
+
- **Live HTML preview** — side-by-side split / editor-only / preview-only modes with a draggable splitter
|
|
102
|
+
- **Table of Contents** — auto-generated from headings, collapsible panel
|
|
103
|
+
- **Theme system** — light / dark toggle; auto-syncs with `data-theme`, `.smdx-dark`, or `data-color-scheme`
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Configuration
|
|
108
|
+
|
|
109
|
+
### `EditorConfig`
|
|
110
|
+
|
|
111
|
+
| Option | Type | Default | Description |
|
|
112
|
+
|--------|------|---------|-------------|
|
|
113
|
+
| `container` | `HTMLElement \| string` | — | **Required.** DOM element or CSS selector to mount into. |
|
|
114
|
+
| `value` | `string` | `''` | Initial markdown content. |
|
|
115
|
+
| `onChange` | `(value: string) => void` | — | Called on every content change (typing, paste, undo, `setValue()`). |
|
|
116
|
+
| `plugins` | `EditorPlugin[]` | all built-ins | Plugins to register. |
|
|
117
|
+
| `toolbar` | `ToolbarConfig` | default toolbar | Toolbar layout (rows of item IDs, groups, dividers). |
|
|
118
|
+
| `theme` | `Record<string, string>` | — | CSS custom property overrides for the MDX prose shell. |
|
|
119
|
+
| `mode` | `'split' \| 'editor' \| 'preview'` | `'split'` | Initial layout mode. |
|
|
120
|
+
| `placeholder` | `string` | — | Placeholder shown in an empty editor. |
|
|
121
|
+
| `readOnly` | `boolean` | `false` | Prevent all content mutations. |
|
|
122
|
+
| `renderers` | `Record<string, RendererFn>` | — | Custom preview renderer overrides. |
|
|
123
|
+
| `locale` | `Partial<EditorLocale>` | — | i18n label overrides. |
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Theming
|
|
127
|
+
|
|
128
|
+
### CSS custom properties
|
|
129
|
+
|
|
130
|
+
```css
|
|
131
|
+
.smdx-editor {
|
|
132
|
+
--smdx-primary: #6366f1; /* accent colour */
|
|
133
|
+
--smdx-bg: #ffffff; /* prose shell background */
|
|
134
|
+
--smdx-text: #1e293b; /* prose text */
|
|
135
|
+
--smdx-border: #e2e8f0; /* dividers */
|
|
136
|
+
--smdx-font-family: 'Inter', sans-serif;
|
|
137
|
+
--smdx-font-mono: 'JetBrains Mono', monospace;
|
|
138
|
+
--smdx-radius: 12px;
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Via config
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
createEditor({
|
|
146
|
+
container: '#editor',
|
|
147
|
+
theme: {
|
|
148
|
+
'--smdx-primary': '#e11d48',
|
|
149
|
+
'--smdx-bg': '#0f172a',
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Dark mode
|
|
155
|
+
|
|
156
|
+
Add `.smdx-dark` to the editor root, any ancestor element, `<body>`, or `<html>` — the editor observes all of them. Alternatively set `data-theme="dark"` on any ancestor (Next.js / Nuxt / Tailwind pattern):
|
|
64
157
|
|
|
158
|
+
```ts
|
|
159
|
+
// CSS class toggle
|
|
160
|
+
editor.getRoot().classList.add('smdx-dark');
|
|
161
|
+
|
|
162
|
+
// data-theme attribute (works on any ancestor)
|
|
163
|
+
document.documentElement.dataset.theme = 'dark';
|
|
65
164
|
```
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Autocomplete
|
|
169
|
+
|
|
170
|
+
### `registerAutoComplete()`
|
|
171
|
+
|
|
172
|
+
Add custom autocomplete items on top of the built-in MDX completions. Items appear in the popup immediately and persist for the lifetime of the editor.
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import type { CompletionItem } from '@synclineapi/mdx-editor';
|
|
176
|
+
|
|
177
|
+
editor.registerAutoComplete([
|
|
178
|
+
// Plain word completion
|
|
179
|
+
{ label: 'MyCard', kind: 'cls', detail: 'component' },
|
|
180
|
+
|
|
181
|
+
// Snippet — Tab or click inserts the full body; $1 is the cursor position
|
|
182
|
+
{
|
|
183
|
+
label: 'mycard',
|
|
184
|
+
kind: 'snip',
|
|
185
|
+
detail: '<MyCard> component',
|
|
186
|
+
description: 'Inserts a custom card block.',
|
|
187
|
+
body: '<MyCard title="$1">\n $2\n</MyCard>',
|
|
188
|
+
},
|
|
189
|
+
]);
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Call it again at any time to add more items — they accumulate:
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
editor.registerAutoComplete({ label: 'MyBanner', kind: 'cls', detail: 'component' });
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Completion kinds
|
|
199
|
+
|
|
200
|
+
| `kind` | Badge | Use for |
|
|
201
|
+
|--------|-------|---------|
|
|
202
|
+
| `'cls'` | **C** | Component names (`<MyCard>`, `<Tabs>`) |
|
|
203
|
+
| `'fn'` | **f** | Functions / methods |
|
|
204
|
+
| `'kw'` | **K** | Keywords |
|
|
205
|
+
| `'var'` | **·** | Attributes, variables, props |
|
|
206
|
+
| `'typ'` | **T** | Types / interfaces |
|
|
207
|
+
| `'snip'` | **S** | Snippet template — set `body` to expand on accept |
|
|
208
|
+
|
|
209
|
+
### Snippet bodies
|
|
210
|
+
|
|
211
|
+
Use `$1`, `$2`, … as cursor tab stops. The cursor lands on `$1` after expansion:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
{
|
|
215
|
+
label: 'endpoint',
|
|
216
|
+
kind: 'snip',
|
|
217
|
+
detail: 'API endpoint block',
|
|
218
|
+
body: '<Endpoint method="$1GET" path="$2/api/resource">\n $3Description\n</Endpoint>',
|
|
219
|
+
}
|
|
75
220
|
```
|
|
76
221
|
|
|
222
|
+
---
|
|
223
|
+
|
|
77
224
|
## Plugin API
|
|
78
225
|
|
|
79
|
-
|
|
226
|
+
Plugins are the recommended way to bundle a toolbar button, keyboard shortcut, preview renderer, and autocomplete items together as one reusable unit.
|
|
80
227
|
|
|
81
228
|
```ts
|
|
82
229
|
import type { EditorPlugin } from '@synclineapi/mdx-editor';
|
|
83
230
|
|
|
84
231
|
const myPlugin: EditorPlugin = {
|
|
85
232
|
name: 'my-custom-block',
|
|
233
|
+
|
|
234
|
+
// Toolbar button
|
|
86
235
|
toolbarItems: [{
|
|
87
236
|
id: 'myBlock',
|
|
88
237
|
label: 'My Block',
|
|
89
238
|
icon: '<svg>...</svg>',
|
|
90
|
-
tooltip: 'Insert my block',
|
|
239
|
+
tooltip: 'Insert my block (Ctrl+Shift+M)',
|
|
91
240
|
action: ({ editor }) => {
|
|
92
241
|
editor.insertBlock('<MyBlock>\n Content\n</MyBlock>');
|
|
93
242
|
},
|
|
94
243
|
}],
|
|
244
|
+
|
|
245
|
+
// Autocomplete — same content as the toolbar action, now also reachable
|
|
246
|
+
// by typing the trigger word in the editor
|
|
247
|
+
completions: [
|
|
248
|
+
{
|
|
249
|
+
label: 'myblock',
|
|
250
|
+
kind: 'snip',
|
|
251
|
+
detail: '<MyBlock> component',
|
|
252
|
+
body: '<MyBlock>\n $1Content\n</MyBlock>',
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
|
|
256
|
+
// Preview renderer
|
|
95
257
|
renderers: [{
|
|
96
258
|
name: 'myBlock',
|
|
97
259
|
pattern: /<MyBlock>([\s\S]*?)<\/MyBlock>/g,
|
|
98
260
|
render: (match) => {
|
|
99
261
|
const m = match.match(/<MyBlock>([\s\S]*?)<\/MyBlock>/);
|
|
100
|
-
return `<div class="my-block">${m?.[1]
|
|
262
|
+
return `<div class="my-block">${m?.[1] ?? ''}</div>`;
|
|
101
263
|
},
|
|
102
264
|
}],
|
|
265
|
+
|
|
103
266
|
shortcuts: [
|
|
104
|
-
{
|
|
267
|
+
{
|
|
268
|
+
key: 'Ctrl+Shift+m',
|
|
269
|
+
action: ({ editor }) => editor.insertBlock('<MyBlock>\n Content\n</MyBlock>'),
|
|
270
|
+
description: 'Insert custom block',
|
|
271
|
+
},
|
|
105
272
|
],
|
|
106
|
-
|
|
273
|
+
|
|
274
|
+
styles: `.my-block { border: 2px solid #6366f1; padding: 16px; border-radius: 8px; }`,
|
|
107
275
|
};
|
|
108
276
|
```
|
|
109
277
|
|
|
278
|
+
You can also register completions dynamically inside `init()` using `ctx.registerCompletion()`:
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
const myPlugin: EditorPlugin = {
|
|
282
|
+
name: 'dynamic-completions',
|
|
283
|
+
async init(ctx) {
|
|
284
|
+
const components = await fetchMyComponents(); // dynamic list
|
|
285
|
+
for (const comp of components) {
|
|
286
|
+
ctx.registerCompletion({
|
|
287
|
+
label: comp.tag,
|
|
288
|
+
kind: 'cls',
|
|
289
|
+
detail: comp.description,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Completion merge order
|
|
297
|
+
|
|
298
|
+
Every time a plugin is registered or unregistered the autocomplete list is rebuilt:
|
|
299
|
+
|
|
300
|
+
```
|
|
301
|
+
built-in MDX completions (tags, JSX attributes)
|
|
302
|
+
+ built-in MDX snippets (toolbar-matched snippet bodies)
|
|
303
|
+
+ plugin completions (all registered plugins, in order)
|
|
304
|
+
+ user completions (registerAutoComplete() calls)
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
110
309
|
## Toolbar Customization
|
|
111
310
|
|
|
112
311
|
### Flat toolbar
|
|
113
312
|
|
|
114
|
-
```
|
|
313
|
+
```ts
|
|
115
314
|
createEditor({
|
|
116
315
|
container: '#editor',
|
|
117
316
|
toolbar: [['bold', 'italic', '|', 'heading', '|', 'link', 'image']],
|
|
118
317
|
});
|
|
119
318
|
```
|
|
120
319
|
|
|
320
|
+
### Multi-row toolbar
|
|
321
|
+
|
|
322
|
+
```ts
|
|
323
|
+
createEditor({
|
|
324
|
+
container: '#editor',
|
|
325
|
+
toolbar: [
|
|
326
|
+
['heading', '|', 'bold', 'italic', 'strikethrough'],
|
|
327
|
+
['link', 'image', '|', 'table', 'code'],
|
|
328
|
+
],
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
121
332
|
### Nested dropdowns
|
|
122
333
|
|
|
123
|
-
```
|
|
334
|
+
```ts
|
|
124
335
|
createEditor({
|
|
125
336
|
container: '#editor',
|
|
126
337
|
toolbar: [[
|
|
@@ -135,91 +346,78 @@ createEditor({
|
|
|
135
346
|
});
|
|
136
347
|
```
|
|
137
348
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
Override CSS custom properties:
|
|
141
|
-
|
|
142
|
-
```css
|
|
143
|
-
.smdx-editor {
|
|
144
|
-
--smdx-primary: #e11d48;
|
|
145
|
-
--smdx-bg: #0f172a;
|
|
146
|
-
--smdx-text: #e2e8f0;
|
|
147
|
-
--smdx-border: #334155;
|
|
148
|
-
--smdx-font-family: 'Inter', sans-serif;
|
|
149
|
-
--smdx-font-mono: 'JetBrains Mono', monospace;
|
|
150
|
-
--smdx-radius: 12px;
|
|
151
|
-
}
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
Or pass theme via config:
|
|
155
|
-
|
|
156
|
-
```js
|
|
157
|
-
createEditor({
|
|
158
|
-
container: '#editor',
|
|
159
|
-
theme: {
|
|
160
|
-
'--smdx-primary': '#e11d48',
|
|
161
|
-
'--smdx-bg': '#0f172a',
|
|
162
|
-
},
|
|
163
|
-
});
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
Add `.smdx-dark` class for dark mode:
|
|
167
|
-
|
|
168
|
-
```js
|
|
169
|
-
editor.getRoot().classList.add('smdx-dark');
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
## Configuration
|
|
173
|
-
|
|
174
|
-
| Option | Type | Default | Description |
|
|
175
|
-
|--------|------|---------|-------------|
|
|
176
|
-
| `container` | `HTMLElement \| string` | — | **Required.** The DOM element (or CSS selector) to mount the editor into. |
|
|
177
|
-
| `value` | `string` | `''` | Initial markdown content. |
|
|
178
|
-
| `onChange` | `(value: string) => void` | — | Callback invoked every time the content changes (user input, `setValue()`, undo/redo). |
|
|
179
|
-
| `plugins` | `EditorPlugin[]` | all built-in plugins | Plugins to register. |
|
|
180
|
-
| `toolbar` | `ToolbarConfig` | default toolbar | Toolbar layout. |
|
|
181
|
-
| `theme` | `Record<string, string>` | — | CSS custom property overrides. |
|
|
182
|
-
| `mode` | `'split' \| 'editor' \| 'preview'` | `'split'` | Initial editor mode. |
|
|
183
|
-
| `placeholder` | `string` | — | Textarea placeholder text. |
|
|
184
|
-
| `readOnly` | `boolean` | `false` | Make the editor read-only. |
|
|
185
|
-
| `scrollSync` | `boolean` | `true` | Sync scroll position between editor and preview. |
|
|
186
|
-
| `renderers` | `Record<string, RendererFn>` | — | Custom renderer overrides. |
|
|
187
|
-
| `locale` | `Partial<EditorLocale>` | — | i18n overrides. |
|
|
349
|
+
---
|
|
188
350
|
|
|
189
351
|
## API Reference
|
|
190
352
|
|
|
191
353
|
```ts
|
|
192
|
-
interface
|
|
354
|
+
interface SynclineMDXEditor {
|
|
355
|
+
// Content
|
|
193
356
|
getValue(): string;
|
|
194
357
|
setValue(value: string): void;
|
|
358
|
+
|
|
359
|
+
// Editing
|
|
195
360
|
insertText(text: string): void;
|
|
361
|
+
insertBlock(template: string): void;
|
|
196
362
|
wrapSelection(prefix: string, suffix: string): void;
|
|
197
363
|
replaceSelection(text: string): void;
|
|
364
|
+
replaceCurrentLine(text: string): void;
|
|
365
|
+
insertAt(position: number, text: string): void;
|
|
366
|
+
|
|
367
|
+
// Selection & cursor
|
|
198
368
|
getSelection(): SelectionState;
|
|
199
|
-
|
|
369
|
+
setSelection(start: number, end: number): void;
|
|
370
|
+
getCurrentLine(): string;
|
|
371
|
+
getCurrentLineNumber(): number;
|
|
372
|
+
jumpToLine(lineNumber: number): void;
|
|
373
|
+
|
|
374
|
+
// Undo / Redo
|
|
375
|
+
undo(): void;
|
|
376
|
+
redo(): void;
|
|
377
|
+
|
|
378
|
+
// View
|
|
200
379
|
focus(): void;
|
|
201
380
|
renderPreview(): void;
|
|
202
|
-
getMode():
|
|
381
|
+
getMode(): 'split' | 'editor' | 'preview';
|
|
203
382
|
setMode(mode: 'split' | 'editor' | 'preview'): void;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
off(event: string, handler: Function): void;
|
|
208
|
-
undo(): void;
|
|
209
|
-
redo(): void;
|
|
383
|
+
setLineNumbers(enabled: boolean): void;
|
|
384
|
+
|
|
385
|
+
// Metrics
|
|
210
386
|
getWordCount(): number;
|
|
211
387
|
getLineCount(): number;
|
|
388
|
+
|
|
389
|
+
// DOM access
|
|
390
|
+
getRoot(): HTMLElement;
|
|
391
|
+
getTextarea(): HTMLTextAreaElement;
|
|
392
|
+
getPreview(): HTMLElement;
|
|
393
|
+
|
|
394
|
+
// Plugins
|
|
395
|
+
registerPlugin(plugin: EditorPlugin): void;
|
|
396
|
+
unregisterPlugin(name: string): void;
|
|
397
|
+
|
|
398
|
+
// Autocomplete
|
|
399
|
+
registerAutoComplete(items: CompletionItem | CompletionItem[]): void;
|
|
400
|
+
|
|
401
|
+
// Theme sync
|
|
402
|
+
syncCodeEditorTheme(): void;
|
|
403
|
+
|
|
404
|
+
// Events
|
|
405
|
+
on(event: string, handler: (data?: unknown) => void): void;
|
|
406
|
+
off(event: string, handler: (data?: unknown) => void): void;
|
|
407
|
+
emit(event: string, data?: unknown): void;
|
|
408
|
+
|
|
409
|
+
// Lifecycle
|
|
212
410
|
destroy(): void;
|
|
213
411
|
}
|
|
214
412
|
```
|
|
215
413
|
|
|
216
|
-
|
|
414
|
+
---
|
|
217
415
|
|
|
218
|
-
|
|
416
|
+
## Events
|
|
219
417
|
|
|
220
|
-
|
|
418
|
+
### `onChange` callback (recommended)
|
|
221
419
|
|
|
222
|
-
```
|
|
420
|
+
```ts
|
|
223
421
|
const editor = createEditor({
|
|
224
422
|
container: '#editor',
|
|
225
423
|
onChange: (markdown) => {
|
|
@@ -230,81 +428,63 @@ const editor = createEditor({
|
|
|
230
428
|
|
|
231
429
|
### Event emitter
|
|
232
430
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
editor.on('change',
|
|
237
|
-
editor.on('
|
|
238
|
-
editor.on('
|
|
239
|
-
editor.on('
|
|
240
|
-
editor.on('toolbar-action',
|
|
431
|
+
```ts
|
|
432
|
+
editor.on('change', (markdown) => console.log('Changed:', markdown));
|
|
433
|
+
editor.on('selection-change', (sel) => console.log('Selection:', sel));
|
|
434
|
+
editor.on('mode-change', (mode) => console.log('Mode:', mode));
|
|
435
|
+
editor.on('render', (html) => console.log('Preview HTML ready'));
|
|
436
|
+
editor.on('focus', () => console.log('Editor focused'));
|
|
437
|
+
editor.on('blur', () => console.log('Editor blurred'));
|
|
438
|
+
editor.on('toolbar-action', (id) => console.log('Toolbar clicked:', id));
|
|
241
439
|
```
|
|
242
440
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
> **Note:** The GitHub repository for this package is **private** — the source code is not publicly visible. The package is freely installable from npm, but external contributions, forks, and pull requests are not accepted.
|
|
246
|
-
>
|
|
247
|
-
> To report a bug or request a feature, open an issue at the [GitHub Issues page](https://github.com/RadhaHariharan/syncline-mdx-editor/issues).
|
|
248
|
-
>
|
|
249
|
-
> ```bash
|
|
250
|
-
> npm install @synclineapi/mdx-editor
|
|
251
|
-
> ```
|
|
252
|
-
|
|
253
|
-
Internal development commands (for maintainers only):
|
|
254
|
-
|
|
255
|
-
```bash
|
|
256
|
-
cd syncline-mdx-editor
|
|
257
|
-
npm install
|
|
258
|
-
npm run dev # Start Vite dev server
|
|
259
|
-
npm run build # Build library for production
|
|
260
|
-
```
|
|
441
|
+
---
|
|
261
442
|
|
|
262
443
|
## Bundle Size
|
|
263
444
|
|
|
264
|
-
|
|
445
|
+
The production build targets **< 150 kB** (ESM + UMD, gzipped) when `mermaid`, `katex`, and `highlight.js` are treated as external peer dependencies.
|
|
265
446
|
|
|
266
447
|
```bash
|
|
267
448
|
npm run build
|
|
268
449
|
npm run size
|
|
269
450
|
```
|
|
270
451
|
|
|
271
|
-
|
|
452
|
+
---
|
|
272
453
|
|
|
273
454
|
## Security
|
|
274
455
|
|
|
275
|
-
All rendered Markdown/MDX HTML is sanitized
|
|
456
|
+
All rendered Markdown/MDX HTML is sanitized by a built-in XSS sanitizer before DOM injection. Dangerous tags (`script`, `iframe`, `object`), event-handler attributes (`onclick`, `onerror`, etc.), and `javascript:` protocol URLs are stripped automatically. See [SECURITY.md](./SECURITY.md) for the vulnerability disclosure policy.
|
|
457
|
+
|
|
458
|
+
---
|
|
276
459
|
|
|
277
460
|
## Accessibility
|
|
278
461
|
|
|
279
462
|
`@synclineapi/mdx-editor` targets **WCAG 2.1 AA** compliance:
|
|
280
|
-
|
|
463
|
+
|
|
464
|
+
- All toolbar buttons carry `aria-label` and `title` attributes
|
|
281
465
|
- The editor textarea has `aria-label="Markdown editor"` and `aria-multiline="true"`
|
|
282
466
|
- The preview pane has `role="region"`, `aria-label="Preview"`, and `aria-live="polite"`
|
|
283
467
|
- Mode toggle buttons expose `aria-pressed` state
|
|
284
|
-
- Toolbar dropdowns
|
|
285
|
-
- A skip link is provided for keyboard users
|
|
286
|
-
- All
|
|
468
|
+
- Toolbar dropdowns use `role="menu"` / `role="menuitem"`
|
|
469
|
+
- A skip link is provided for keyboard-only users
|
|
470
|
+
- All foreground/background colour pairs meet WCAG AA contrast ratios
|
|
287
471
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
```bash
|
|
291
|
-
npm run test # Run all tests
|
|
292
|
-
npm run test:watch # Watch mode
|
|
293
|
-
npm run test:coverage # Coverage report (target: 80%+)
|
|
294
|
-
npm run test:ui # Visual test UI
|
|
295
|
-
```
|
|
472
|
+
---
|
|
296
473
|
|
|
297
474
|
## Peer Dependencies
|
|
298
475
|
|
|
299
|
-
`mermaid`, `katex`, and `highlight.js` are optional
|
|
476
|
+
`mermaid`, `katex`, and `highlight.js` are optional. Install only the ones you need:
|
|
300
477
|
|
|
301
478
|
```bash
|
|
302
|
-
npm install mermaid #
|
|
303
|
-
npm install katex #
|
|
304
|
-
npm install highlight.js #
|
|
479
|
+
npm install mermaid # Mermaid diagrams
|
|
480
|
+
npm install katex # Math / formula rendering
|
|
481
|
+
npm install highlight.js # Syntax highlighting in code blocks
|
|
305
482
|
```
|
|
306
483
|
|
|
484
|
+
---
|
|
485
|
+
|
|
307
486
|
## License
|
|
308
487
|
|
|
309
|
-
|
|
488
|
+
Copyright © Syncline API. All rights reserved.
|
|
310
489
|
|
|
490
|
+
This software is proprietary and confidential. Unauthorized copying, distribution, modification, or use of this software, in whole or in part, is strictly prohibited without the express written permission of Syncline API.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { EditorConfig, EditorMode } from '../types';
|
|
2
|
+
export declare const DEFAULT_MODE: EditorMode;
|
|
3
|
+
export declare const DEFAULT_PLACEHOLDER = "Start writing markdown...";
|
|
4
|
+
export declare const DEFAULT_PREVIEW_DELAY = 150;
|
|
5
|
+
export declare const DEFAULT_FEATURES: {
|
|
6
|
+
readonly lineNumbers: false;
|
|
7
|
+
readonly wordCount: true;
|
|
8
|
+
readonly lineCount: true;
|
|
9
|
+
readonly modeSwitcher: true;
|
|
10
|
+
readonly statusBar: true;
|
|
11
|
+
readonly autoPreview: true;
|
|
12
|
+
readonly previewDelay: 150;
|
|
13
|
+
readonly spellcheck: false;
|
|
14
|
+
readonly autocomplete: false;
|
|
15
|
+
readonly dragAndDrop: true;
|
|
16
|
+
readonly pasteImages: false;
|
|
17
|
+
readonly keyboardShortcuts: true;
|
|
18
|
+
readonly autoSave: false;
|
|
19
|
+
readonly autoSaveKey: "smdx-autosave";
|
|
20
|
+
readonly autofocus: false;
|
|
21
|
+
readonly vimMode: false;
|
|
22
|
+
readonly history: {
|
|
23
|
+
readonly maxSize: 100;
|
|
24
|
+
readonly debounce: 300;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
export declare const DEFAULT_LAYOUT: {
|
|
28
|
+
readonly splitterPosition: 50;
|
|
29
|
+
readonly minEditorWidth: 200;
|
|
30
|
+
readonly minPreviewWidth: 200;
|
|
31
|
+
readonly resizableSplitter: true;
|
|
32
|
+
};
|
|
33
|
+
export declare function resolveDefaults(config: Partial<EditorConfig>): EditorConfig;
|