@powerduck/md-editor 0.10.9 → 0.11.1

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.
Files changed (4) hide show
  1. package/README.md +316 -387
  2. package/dist/index.cjs +28 -30
  3. package/dist/index.mjs +2132 -4691
  4. package/package.json +1 -1
package/README.md CHANGED
@@ -1,466 +1,395 @@
1
1
  # @powerduck/md-editor
2
2
 
3
- High-performance embeddable Markdown editor built on the markdown-it ecosystem. Supports KaTeX math formulas (self-contained, no texmath), Markmap mindmaps, highlight.js code blocks with traffic-light UI and copy button, github-markdown-css preview styling, admonition/tip blocks, rich toolbar with image/video/YouTube/table popup dialogs, keyboard shortcuts with help dialog, configurable toolbar, image upload hook, standalone `renderMarkdown()` API, native React integration, block-level incremental rendering with debounced scheduling. Ships in two modes: **simple** (no toolbar, pure edit + preview) and **complex** (toolbar + status bar). Styled via CSS custom properties with light/dark themes.
3
+ High-performance embeddable Markdown editor with syntax highlighting, math formulas, mindmaps, @mentions, document link cards, and incremental rendering. Built on CodeMirror 6 with a React wrapper.
4
4
 
5
- ## Features
5
+ [![npm version](https://img.shields.io/npm/v/@powerduck/md-editor)](https://www.npmjs.com/package/@powerduck/md-editor)
6
+ [![license](https://img.shields.io/npm/l/@powerduck/md-editor)](https://github.com/PowerDuckie/md-editor/blob/main/LICENSE)
6
7
 
7
- - **Markdown editing** powered by CodeMirror 6 with markdown syntax highlighting
8
- - **Math formulas** via self-contained KaTeX plugin (`$E=mc^2$` inline, `$$...$$` block) -- no texmath dependency, no double-rendering
9
- - **Mindmaps** via Markmap (```` ```mindmap ```` code fences)
10
- - **Code highlighting** via highlight.js -- 20+ languages, macOS-style traffic-light header, one-click copy button
11
- - **Admonition blocks** -- `:::notice`, `:::warning`, `:::tip`, `:::danger`, `:::info`, `:::success` with icons and colored borders
12
- - **github-markdown-css** preview -- proper heading, list, table, and typography styling
13
- - **Rich toolbar** -- heading, bold, italic, link, image, video, YouTube, quote, lists, code, table, math, mindmap, horizontal rule, tip block, preview toggle, help, theme toggle
14
- - **Popup dialogs** -- image URL, video URL, YouTube URL/ID, table rows/columns
15
- - **Keyboard shortcuts** -- Ctrl+B bold, Ctrl+I italic, Ctrl+K link, etc. with a Help dialog listing all shortcuts
16
- - **Configurable toolbar** -- show/hide items, custom labels and icons via `toolbar` option
17
- - **Image upload hook** -- paste/drop images, call `onImageUpload(file)` to get a URL
18
- - **Video rendering** -- `.mp4`, `.webm`, `.ogg` links render as `<video>` elements
19
- - **Standalone renderer** -- `renderMarkdown(source, options)` for consistent styling outside the editor
20
- - **Block-level incremental rendering** -- only changed sections re-render
21
- - **Adaptive debounce** -- preview render delay scales with document length
22
- - **Simple / Complex modes** -- toggle toolbar and status bar at runtime
23
- - **Light / Dark themes** -- CSS custom properties, inherits host design tokens
24
- - **React component** -- `forwardRef` with imperative handle
25
-
26
- ## Installation
8
+ ## Links
27
9
 
28
- ```bash
29
- npm install @powerduck/md-editor
30
- ```
10
+ - [Official Website](https://www.powerduck.com/opensource/md-editor.html)
11
+ - [Documentation](https://www.powerduck.com/docs/md-editor/introduction)
12
+ - [Live Demo](https://www.powerduck.com/demo/md-editor.html)
13
+ - [GitHub](https://github.com/PowerDuckie/md-editor)
14
+ - [npm](https://www.npmjs.com/package/@powerduck/md-editor)
31
15
 
32
- ## Usage
16
+ ---
33
17
 
34
- ### Vanilla JS
18
+ ## Quick Start
35
19
 
36
- ```ts
37
- import { MarkdownEditor } from '@powerduck/md-editor';
38
- import '@powerduck/md-editor/dist/style.css';
39
-
40
- const editor = new MarkdownEditor('#editor', {
41
- value: '# Hello\n\nStart typing...',
42
- mode: 'complex', // 'simple' | 'complex'
43
- theme: 'light', // 'light' | 'dark'
44
- math: true,
45
- mindmap: true,
46
- onChange: (value) => console.log(value)
47
- });
20
+ ### Install
21
+
22
+ ```bash
23
+ npm install @powerduck/md-editor
48
24
  ```
49
25
 
50
- ### React
26
+ ### React Component
51
27
 
52
28
  ```tsx
53
- import { MarkdownEditorReact } from '@powerduck/md-editor/react';
54
- import '@powerduck/md-editor/dist/style.css';
29
+ import { useRef, useState } from "react";
30
+ import { MarkdownEditorReact, type MarkdownEditorHandle } from "@powerduck/md-editor/react";
31
+ import "@powerduck/md-editor/dist/style.css";
55
32
 
56
33
  function App() {
34
+ const [value, setValue] = useState("# Hello\n\nStart typing...");
35
+ const editorRef = useRef<MarkdownEditorHandle>(null);
36
+
57
37
  return (
58
- <MarkdownEditorReact
59
- defaultValue="# Hello"
60
- mode="complex"
61
- onChange={(v) => console.log(v)}
62
- />
38
+ <div style={{ height: "600px" }}>
39
+ <MarkdownEditorReact
40
+ ref={editorRef}
41
+ value={value}
42
+ onChange={setValue}
43
+ mode="complex"
44
+ theme="light"
45
+ />
46
+ <button onClick={() => console.log(editorRef.current?.getHtml())}>
47
+ Get HTML
48
+ </button>
49
+ </div>
63
50
  );
64
51
  }
65
52
  ```
66
53
 
67
- ## API
68
-
69
- ### MarkdownEditorOptions
70
-
71
- | Option | Type | Default | Description |
72
- |--------|------|---------|-------------|
73
- | `value` | `string` | `''` | Initial content |
74
- | `mode` | `'simple' \| 'complex'` | `'simple'` | Toolbar + status bar in complex mode |
75
- | `theme` | `'light' \| 'dark'` | `'light'` | Color theme |
76
- | `math` | `boolean` | `true` | Enable KaTeX math |
77
- | `mindmap` | `boolean` | `true` | Enable Markmap mindmaps |
78
- | `codeHighlight` | `boolean` | `true` | Enable highlight.js code blocks with copy button |
79
- | `tips` | `boolean` | `true` | Enable admonition/tip blocks (:::notice, :::warning, etc.) |
80
- | `preview` | `boolean` | `true` | Show preview pane |
81
- | `toolbar` | `ToolbarConfig` | all items | Configure which toolbar items show, their labels and icons |
82
- | `onImageUpload` | `(file: File) => Promise<string>` | - | Image upload hook for paste/drop |
83
- | `autoPreview` | `boolean` | `true` | Auto-render preview on edit |
84
- | `renderDebounce` | `number \| false` | adaptive | Preview debounce in ms |
85
- | `onChange` | `(value: string) => void` | - | Change callback |
86
-
87
- ### Methods
88
-
89
- - `getValue(): string`
90
- - `setValue(value: string): void`
91
- - `getHtml(): string`
92
- - `renderNow(): void` -- manual preview render (use with `autoPreview: false`)
93
- - `setMode(mode: EditorMode): void`
94
- - `setTheme(theme: EditorTheme): void`
95
- - `setAutoPreview(auto: boolean): void`
96
- - `focus(): void`
97
- - `destroy(): void`
54
+ ### Vanilla JS
98
55
 
99
- ## Mindmap Syntax
56
+ ```typescript
57
+ import { MarkdownEditor } from "@powerduck/md-editor";
58
+ import "@powerduck/md-editor/dist/style.css";
100
59
 
101
- ````markdown
102
- ```mindmap
103
- # Root topic
104
- ## Branch one
105
- - Child A
106
- - Child B
107
- ## Branch two
108
- - Child C
60
+ const editor = new MarkdownEditor("#editor", {
61
+ value: "# Hello\n\nStart typing...",
62
+ mode: "complex",
63
+ theme: "light",
64
+ onChange: (value) => console.log(value.length, "chars"),
65
+ });
66
+
67
+ // Read and write content
68
+ const markdown = editor.getValue();
69
+ editor.setValue("# Updated content");
70
+
71
+ // Render to HTML
72
+ const html = editor.getHtml();
109
73
  ```
110
- ````
111
74
 
112
- ## Code Blocks
75
+ ### @mention Integration
113
76
 
114
- Code fences are automatically highlighted with highlight.js (20+ languages including JavaScript, TypeScript, Python, JSON, Bash, CSS, HTML, SQL, YAML, Go, Rust, Java, C/C++). Each code block gets a macOS-style header with traffic-light dots, a language label, and a copy button.
77
+ ```typescript
78
+ import { MarkdownEditor, type MentionItem } from "@powerduck/md-editor";
79
+
80
+ const USERS: MentionItem[] = [
81
+ { id: "1", label: "Alice", avatar: "https://example.com/a.png", description: "alice@example.com" },
82
+ { id: "2", label: "Bob", description: "bob@example.com" },
83
+ ];
84
+
85
+ new MarkdownEditor("#editor", {
86
+ mode: "complex",
87
+ mention: {
88
+ onMentionSearch: async (query) =>
89
+ USERS.filter((u) => u.label.toLowerCase().includes(query.toLowerCase())),
90
+ onMentionSelect: (item) => `[@${item.label}](mention:${item.id})`,
91
+ minChars: 0,
92
+ maxItems: 8,
93
+ },
94
+ });
95
+ ```
96
+
97
+ ### Document Link Card
98
+
99
+ ```typescript
100
+ import { MarkdownEditor, type DocItem } from "@powerduck/md-editor";
101
+
102
+ const DOCS: DocItem[] = [
103
+ { id: "d1", title: "Getting Started", url: "https://docs.example.com/start", description: "Quick start guide", thumbnail: "https://picsum.photos/seed/start/96/72" },
104
+ { id: "d2", title: "API Reference", url: "https://docs.example.com/api", description: "Full API documentation" },
105
+ ];
106
+
107
+ new MarkdownEditor("#editor", {
108
+ mode: "complex",
109
+ docLink: {
110
+ onDocSearch: async (query) =>
111
+ DOCS.filter((d) => d.title.toLowerCase().includes(query.toLowerCase())),
112
+ onFetchDocMeta: async (url) => {
113
+ const res = await fetch(`/api/og?url=${encodeURIComponent(url)}`);
114
+ return res.json();
115
+ },
116
+ insertStyle: "auto", // "auto" | "card" | "link"
117
+ minChars: 0,
118
+ maxItems: 8,
119
+ },
120
+ });
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Features
126
+
127
+ - **Syntax highlighting** — CodeMirror 6 with Markdown grammar and token colors
128
+ - **Math formulas** — KaTeX rendering for inline `$...$` and block `$$...$$`
129
+ - **Mindmaps** — Markmap interactive SVG diagrams from ```` ```mindmap ```` fenced blocks
130
+ - **Code blocks** — highlight.js with traffic-light header and copy button
131
+ - **@mentions** — Searchable dropdown with custom search/select hooks, inserts `[@label](mention:id)` for renderer distinction
132
+ - **Document link cards** — Insert plain links or preview cards with auto-fetched title/thumbnail/description, with custom fetch hook
133
+ - **Image upload** — Paste/drag/select image files with custom upload hook
134
+ - **Task lists** — `- [ ]` / `- [x]` with clickable toggles in preview
135
+ - **Callouts** — `:::notice`, `:::warning`, `:::danger`, etc.
136
+ - **Tables** — Quick-insert popup with configurable rows/columns
137
+ - **Incremental rendering** — Block-level DOM reuse for large documents, adaptive debounce
138
+ - **Themes** — Light and dark themes with CSS custom properties
139
+ - **Two modes** — `simple` (pure edit + preview) and `complex` (toolbar + status bar + line numbers)
140
+ - **React wrapper** — `forwardRef` component with controlled/uncontrolled value and imperative handle
141
+ - **Dual ESM/CJS builds** — Works with `import` and `require`
142
+
143
+ ---
144
+
145
+ ## MarkdownEditorOptions
115
146
 
116
- ````markdown
117
147
  ```typescript
118
- interface User {
119
- name: string;
120
- age: number;
148
+ interface MarkdownEditorOptions {
149
+ value?: string;
150
+ mode?: "simple" | "complex"; // default: "simple"
151
+ theme?: "light" | "dark"; // default: "light"
152
+ math?: boolean; // default: true
153
+ mindmap?: boolean; // default: true
154
+ codeHighlight?: boolean; // default: true
155
+ tips?: boolean; // default: true
156
+ preview?: boolean; // default: true
157
+ onChange?: (value: string) => void;
158
+ toolbar?: ToolbarConfig;
159
+ onImageUpload?: (file: File) => Promise<string>;
160
+ mention?: MentionOptions;
161
+ docLink?: DocLinkOptions;
162
+ renderDebounce?: number | false; // adaptive by default
163
+ autoPreview?: boolean; // default: true
121
164
  }
122
165
  ```
123
- ````
124
166
 
125
- Unknown languages are auto-detected; if detection fails, the content is HTML-escaped and displayed as plain text. Disable code highlighting with `codeHighlight: false`.
167
+ ### MentionOptions
126
168
 
127
- ## Media Insertion
169
+ ```typescript
170
+ interface MentionOptions {
171
+ onMentionSearch?: (query: string) => MentionItem[] | Promise<MentionItem[]>;
172
+ onMentionSelect?: (item: MentionItem) => string; // default: `[@label](mention:id)`
173
+ minChars?: number; // default: 0
174
+ maxItems?: number; // default: 8
175
+ }
128
176
 
129
- The complex-mode toolbar includes popup dialogs for inserting media:
177
+ interface MentionItem {
178
+ id: string;
179
+ label: string;
180
+ avatar?: string;
181
+ description?: string;
182
+ [key: string]: unknown;
183
+ }
184
+ ```
130
185
 
131
- - **Image** (`image` button): URL + alt text -> `![alt](url)`
132
- - **Video** (`video` button): URL (.mp4, .webm, .ogg) -> renders as `<video controls>` in preview
133
- - **YouTube** (`youtube` button): accepts any YouTube URL format (watch?v=, youtu.be, embed, shorts) or raw 11-char ID -> inserts a linked thumbnail image that opens the video on click
186
+ ### DocLinkOptions
134
187
 
135
- YouTube insertion extracts the video ID automatically and produces:
136
- ```markdown
137
- [![YouTube video](https://img.youtube.com/vi/VIDEO_ID/hqdefault.jpg)](https://www.youtube.com/watch?v=VIDEO_ID)
188
+ ```typescript
189
+ interface DocLinkOptions {
190
+ onDocSearch?: (query: string) => DocItem[] | Promise<DocItem[]>;
191
+ onFetchDocMeta?: (url: string) => Promise<DocMeta>;
192
+ insertStyle?: "card" | "link" | "auto"; // default: "auto"
193
+ triggerChar?: string; // default: "/"
194
+ minChars?: number; // default: 0
195
+ maxItems?: number; // default: 8
196
+ }
197
+
198
+ interface DocItem {
199
+ id: string;
200
+ title: string;
201
+ url: string;
202
+ thumbnail?: string;
203
+ description?: string;
204
+ [key: string]: unknown;
205
+ }
138
206
  ```
139
207
 
140
- ## Standalone Renderer
208
+ ---
141
209
 
142
- Use `renderMarkdown()` to render markdown with the same styling and plugins as the editor preview, without mounting an editor:
210
+ ## React Component Props
143
211
 
144
- ```ts
145
- import { renderMarkdown } from '@powerduck/md-editor';
146
- import '@powerduck/md-editor/dist/style.css';
212
+ ```typescript
213
+ interface MarkdownEditorProps extends Omit<MarkdownEditorOptions, "value" | "onChange"> {
214
+ value?: string; // controlled value
215
+ defaultValue?: string; // uncontrolled initial value (mount only)
216
+ onChange?: (value: string) => void;
217
+ className?: string;
218
+ style?: CSSProperties;
219
+ }
147
220
 
148
- const html = renderMarkdown('# Hello\n\n$E=mc^2$\n\n:::tip\nWorks!\n:::');
149
- container.innerHTML = html;
221
+ interface MarkdownEditorHandle {
222
+ getValue: () => string;
223
+ setValue: (value: string) => void;
224
+ getHtml: () => string;
225
+ focus: () => void;
226
+ setMode: (mode: "simple" | "complex") => void;
227
+ setTheme: (theme: "light" | "dark") => void;
228
+ renderNow: () => void;
229
+ }
150
230
  ```
151
231
 
152
- Options: `math`, `mindmap`, `codeHighlight`, `tips`, `html`, `breaks` -- all default to `true` except `html` and `breaks`.
232
+ > **Performance note:** Structural options (`mode`, `math`, `mindmap`, `preview`, `autoPreview`, `renderDebounce`) recreate the underlying instance when they change. `value`, `onChange`, and `theme` use lightweight sync paths and never recreate the instance, so controlled usage stays responsive on large documents.
153
233
 
154
- ## Keyboard Shortcuts
234
+ ---
155
235
 
156
- | Shortcut | Action |
157
- |----------|--------|
158
- | `Ctrl+B` / `Cmd+B` | Bold |
159
- | `Ctrl+I` / `Cmd+I` | Italic |
160
- | `Ctrl+Alt+1` | Heading |
161
- | `Ctrl+K` / `Cmd+K` | Insert link |
162
- | `Ctrl+Alt+C` | Code block |
163
- | `Ctrl+Shift+.` | Blockquote |
164
- | `Ctrl+Shift+8` | Unordered list |
165
- | `Ctrl+Shift+7` | Ordered list |
166
- | `Ctrl+Alt+M` | Math formula |
167
- | `Ctrl+Alt+P` | Toggle preview |
168
- | `Ctrl+Shift+L` | Toggle theme |
169
-
170
- Click the **Help** (?) button in the toolbar to see all shortcuts at runtime.
171
-
172
- ## Configurable Toolbar
173
-
174
- Control which buttons appear and customize their labels/icons:
175
-
176
- ```ts
177
- // Only show specific buttons
178
- new MarkdownEditor('#editor', {
179
- mode: 'complex',
180
- toolbar: ['bold', 'italic', 'link', 'code']
236
+ ## Toolbar Actions
237
+
238
+ 22 built-in toolbar actions in default order:
239
+
240
+ `heading`, `bold`, `italic`, `link`, `image`, `video`, `youtube`, `quote`, `ul`, `ol`, `tasklist`, `mention`, `doclink`, `code`, `table`, `math`, `mindmap`, `hr`, `tips`, `preview`, `help`, `theme`
241
+
242
+ ### Configure Toolbar
243
+
244
+ ```typescript
245
+ // Array form: include-list (only these actions show, in this order)
246
+ new MarkdownEditor("#editor", {
247
+ mode: "complex",
248
+ toolbar: ["bold", "italic", "link", "code", "math"],
181
249
  });
182
250
 
183
- // Fine-grained control
184
- new MarkdownEditor('#editor', {
185
- mode: 'complex',
251
+ // Record form: per-item overrides (everything not listed stays visible)
252
+ new MarkdownEditor("#editor", {
253
+ mode: "complex",
186
254
  toolbar: {
187
- bold: { label: 'Make bold', icon: '<b>B</b>' },
255
+ bold: { label: "Make bold" },
188
256
  image: { show: false },
189
- youtube: { show: false }
190
- }
257
+ youtube: { show: false },
258
+ },
191
259
  });
192
260
  ```
193
261
 
194
- ## Image Upload
262
+ ---
195
263
 
196
- Provide an upload hook to handle pasted/dropped images:
264
+ ## Standalone Rendering
197
265
 
198
- ```ts
199
- new MarkdownEditor('#editor', {
200
- mode: 'complex',
201
- onImageUpload: async (file) => {
202
- const formData = new FormData();
203
- formData.append('file', file);
204
- const res = await fetch('/api/upload', { method: 'POST', body: formData });
205
- const { url } = await res.json();
206
- return url; // inserted as ![filename](url)
207
- }
208
- });
266
+ ```typescript
267
+ import { renderMarkdown } from "@powerduck/md-editor";
268
+ import "@powerduck/md-editor/dist/style.css";
269
+
270
+ const html = renderMarkdown("# Hello\n\n$E=mc^2$", { math: true });
271
+ document.getElementById("output")!.innerHTML = html;
272
+ ```
273
+
274
+ > `renderMarkdown` returns a string and does not mount an editor. Mindmap placeholders are emitted as inert `<div>`s; call `Renderer.hydrate` yourself if you need live diagrams outside the editor.
275
+
276
+ ---
277
+
278
+ ## Public API (Vanilla JS)
279
+
280
+ ```typescript
281
+ class MarkdownEditor {
282
+ constructor(container: HTMLElement | string, options?: MarkdownEditorOptions);
283
+
284
+ getValue(): string;
285
+ setValue(value: string): void;
286
+ getHtml(): string;
287
+ renderNow(): void;
288
+ setAutoPreview(auto: boolean): void;
289
+ setMode(mode: "simple" | "complex"): void;
290
+ setTheme(theme: "light" | "dark"): void;
291
+ focus(): void;
292
+ destroy(): void;
293
+ }
294
+ ```
295
+
296
+ ---
297
+
298
+ ## Keyboard Shortcuts
299
+
300
+ | Shortcut | Action |
301
+ |---|---|
302
+ | `Ctrl/Cmd + B` | Bold |
303
+ | `Ctrl/Cmd + I` | Italic |
304
+ | `Ctrl/Cmd + K` | Link |
305
+ | `Ctrl/Cmd + S` | (custom via onChange) |
306
+
307
+ ---
308
+
309
+ ## Mindmap Syntax
310
+
311
+ ````markdown
312
+ ```mindmap
313
+ # Root topic
314
+ ## Branch one
315
+ - Child A
316
+ - Child B
317
+ ## Branch two
318
+ - Child C
319
+ ```
320
+ ````
321
+
322
+ Renders as an interactive markmap SVG diagram in the preview pane.
323
+
324
+ ---
325
+
326
+ ## Math Syntax
327
+
328
+ ```markdown
329
+ Inline: $E = mc^2$
330
+
331
+ Block:
332
+ $$
333
+ \int_{a}^{b} f(x)\,dx
334
+ $$
209
335
  ```
210
336
 
211
- ## Admonition Blocks
337
+ Powered by KaTeX.
338
+
339
+ ---
340
+
341
+ ## Callout Syntax
212
342
 
213
343
  ```markdown
214
- :::tip Pro tip
215
- Use admonition blocks for callouts.
344
+ :::notice Title
345
+ Notice content.
216
346
  :::
217
347
 
218
- :::warning
219
- This action cannot be undone.
348
+ :::warning Warning
349
+ Warning content.
220
350
  :::
221
351
 
222
- :::danger
223
- Destructive operation.
352
+ :::danger Danger
353
+ Danger content.
224
354
  :::
225
- ```
226
355
 
227
- Supported types: `notice`, `info`, `tip`, `success`, `warning`, `danger`.
356
+ :::tip Tip
357
+ Tip content.
358
+ :::
359
+ ```
228
360
 
229
- ## Performance
361
+ Available types: `notice`, `info`, `tip`, `success`, `warning`, `danger`.
230
362
 
231
- - **Block splitting**: documents are split at ATX headings (`#` through `######`). Content inside code fences is never split.
232
- - **Incremental rendering**: unchanged blocks reuse their DOM nodes directly, including already-hydrated mindmap SVGs.
233
- - **Content-hash cache**: render results are cached by content hash, so undo/redo and copy-paste hit the cache.
234
- - **Adaptive debounce**: `adaptiveDebounceMs(docLength)` scales from 120ms to 600ms based on document size.
235
- - **`content-visibility: auto`**: off-screen blocks skip layout and paint entirely.
363
+ ---
236
364
 
237
365
  ## Development
238
366
 
239
367
  ```bash
368
+ # Install dependencies
240
369
  npm install
241
- npm run typecheck # src only
242
- npm run typecheck:test # src + tests
243
- npm test # run all tests (vitest + jsdom)
244
- npm run build # vite build + d.ts generation
245
- ```
246
-
247
- ## React Demo
248
- ```jsx
249
- import { useRef, useCallback } from "react";
250
- import { type MentionItem, type DocItem } from "@powerduck/md-editor";
251
- import {
252
- MarkdownEditorReact,
253
- type MarkdownEditorHandle,
254
- } from "@powerduck/md-editor/react";
255
- import "@powerduck/md-editor/dist/style.css";
256
370
 
257
- const USERS: MentionItem[] = [
258
- {
259
- id: "1",
260
- label: "Alice",
261
- avatar: "https://i.pravatar.cc/40?u=alice",
262
- description: "alice@example.com",
263
- },
264
- {
265
- id: "2",
266
- label: "Bob",
267
- avatar: "https://i.pravatar.cc/40?u=bob",
268
- description: "bob@example.com",
269
- },
270
- { id: "3", label: "Charlie", description: "charlie@example.com" },
271
- ];
371
+ # Type check
372
+ npm run typecheck
272
373
 
273
- const DOCS: DocItem[] = [
274
- {
275
- id: "d1",
276
- title: "Getting Started",
277
- url: "https://docs.example.com/start",
278
- description: "Quick start guide",
279
- thumbnail: "https://picsum.photos/seed/start/96/72",
280
- },
281
- {
282
- id: "d2",
283
- title: "API Reference",
284
- url: "https://docs.example.com/api",
285
- description: "Full API documentation",
286
- },
287
- {
288
- id: "d3",
289
- title: "Changelog",
290
- url: "https://docs.example.com/changelog",
291
- description: "Release notes",
292
- },
293
- ];
374
+ # Build (ESM + CJS + type declarations + CSS)
375
+ npm run build
294
376
 
295
- function App() {
296
- const editorRef = useRef<MarkdownEditorHandle>(null);
377
+ # Run tests
378
+ npm test
297
379
 
298
- const handleSave = useCallback(() => {
299
- const markdown = editorRef.current?.getValue() ?? "";
300
- console.log("Saving:", markdown);
301
- }, []);
302
-
303
- // Stable callbacks so the editor does not recreate on every render
304
- const searchUsers = useCallback(async (query: string) => {
305
- return USERS.filter((u) =>
306
- u.label.toLowerCase().includes(query.toLowerCase()),
307
- );
308
- }, []);
309
-
310
- const searchDocs = useCallback(async (query: string) => {
311
- return DOCS.filter((d) =>
312
- d.title.toLowerCase().includes(query.toLowerCase()),
313
- );
314
- }, []);
315
-
316
- const fetchDocMeta = useCallback(async (url: string) => {
317
- // In production, route through your backend to avoid CORS:
318
- // const res = await fetch(`/api/og?url=${encodeURIComponent(url)}`);
319
- // return res.json();
320
- // For demo: return whatever metadata the item already has
321
- const doc = DOCS.find((d) => d.url === url);
322
- return {
323
- title: doc?.title ?? new URL(url).hostname,
324
- description: doc?.description,
325
- thumbnail: doc?.thumbnail,
326
- url,
327
- };
328
- }, []);
380
+ # Dev server
381
+ npm run dev
382
+ ```
329
383
 
330
- return (
331
- <div style={{ padding: 24, maxWidth: 1100, margin: "0 auto" }}>
332
- <div style={{ marginBottom: 12, display: "flex", gap: 8 }}>
333
- <button onClick={handleSave}>Save</button>
334
- <button onClick={() => editorRef.current?.setTheme("dark")}>
335
- Dark
336
- </button>
337
- <button onClick={() => editorRef.current?.setTheme("light")}>
338
- Light
339
- </button>
340
- </div>
384
+ ---
341
385
 
342
- <MarkdownEditorReact
343
- ref={editorRef}
344
- defaultValue={[
345
- "# Welcome",
346
- "",
347
- "Type **@** to mention a user, or **/** to insert a document link.",
348
- "",
349
- "## Task list",
350
- "- [ ] This is checked (green)",
351
- "- [x] This is unchecked",
352
- "- [ x] Spaces inside brackets also work",
353
- "",
354
- "## Table",
355
- "| Name | Role |",
356
- "|------|------|",
357
- "| Alice | Admin |",
358
- "| Bob | Editor |",
359
- ].join("\n")}
360
- mode="complex"
361
- theme="light"
362
- onChange={(v) => console.log("auto-save:", v.length)}
363
- mention={{
364
- onMentionSearch: searchUsers,
365
- minChars: 0,
366
- maxItems: 8,
367
- }}
368
- docLink={{
369
- onDocSearch: searchDocs,
370
- onFetchDocMeta: fetchDocMeta,
371
- insertStyle: "auto",
372
- minChars: 0,
373
- maxItems: 8,
374
- }}
375
- />
376
- </div>
377
- );
378
- }
386
+ ## Related Packages
379
387
 
380
- export default App;
381
- ```
388
+ - [`@powerduck/openapi-parser`](https://www.npmjs.com/package/@powerduck/openapi-parser) — OpenAPI 3.2 parser, validator, and upgrader
389
+ - [`@powerduck/conf-patch`](https://www.npmjs.com/package/@powerduck/conf-patch) — Configuration file editor with atomic writes and locking
390
+ - [`@powerduck/openapi-codegen`](https://www.npmjs.com/package/@powerduck/openapi-codegen) — Generate runnable request examples in 21 languages
382
391
 
383
- ## Changelog
384
-
385
- ### v0.8.4
386
-
387
- - **Fixed**: React component `MarkdownEditorReact` now passes through all extra props (`mention`, `docLink`, `toolbar`, `onImageUpload`, `includeCommonHeaders`, `includeCookies`, `placeholder`, etc.) to the underlying `MarkdownEditor` instance. Previously only a subset of props were forwarded, so `mention` and `docLink` configs were silently ignored.
388
- - **Test**: 311 tests, all passing, zero unhandled errors.
389
-
390
- ### v0.8.3
391
-
392
- - **Changed**: Task list checkbox semantics inverted per project convention. `- [ ]` now renders as **checked** (green checkmark); `- [x]` / `- [X]` renders as **unchecked** (empty box). `toggleTaskList()` and all tests updated accordingly.
393
- - **Improved**: Clicking the @mention or document-link toolbar button without configuring the search hook now logs a clear `console.warn` explaining that `mention.onMentionSearch` / `docLink.onDocSearch` must be provided.
394
- - **Test**: 310 tests, all passing, zero unhandled errors.
395
-
396
- ### v0.8.2
397
-
398
- - **Fixed**: @mention and `/` doc-link dropdowns did not appear. Root cause: v0.8.1 mounted dropdowns inside `.md-editor-root` where ancestor `overflow:hidden` / stacking contexts could clip or misplace `position:fixed` elements. Reverted to `document.body` mounting with `position:fixed`, and added explicit light/dark colors via `data-theme` attribute (no dependency on editor-scoped CSS variables).
399
- - **Fixed**: Clicking toolbar button icons (SVG) did not open popups. Root cause: `mousedown` event bubbled to `document` and triggered the newly-opened Popup's outside-click handler (because `e.target` was the SVG, not the button). Fixed in two ways: Popup now uses `anchor.contains(target)` instead of `target !== anchor`; toolbar `mousedown` handler calls `e.stopPropagation()`.
400
- - **Fixed**: Task list checkboxes now render with a clear green checkmark when checked (`- [x]` or `- [X]`). Previously used native `accent-color` (orange) which was hard to distinguish in disabled state. Custom checkbox: gray border when unchecked, solid green with white checkmark when checked.
401
- - **Added**: Help dialog now includes a "Syntax" section with quick-reference guidance for task lists, @mentions, document links, math, mindmaps, and callouts.
402
- - **Fixed**: Help dialog and all popup-internal styles no longer use editor-scoped CSS variables (undefined when popup is on `document.body`), ensuring consistent light/dark rendering.
403
- - **Test**: 310 tests, all passing, zero unhandled errors.
404
-
405
- ### v0.8.1
406
-
407
- - **Fixed**: Toolbar buttons now use `mousedown` + `preventDefault` instead of `click`, preventing the editor from losing focus and collapsing the selection before wrap operations. This fixes the long-standing issue where selecting text and clicking quote/list/other toolbar buttons replaced the selection with placeholder text ("quoted text", "list item").
408
- - **Fixed**: `toggleTaskList()` mixed-state logic. Previously, a mix of checked and unchecked items (or all-unchecked) removed all task markers. Now: mixed or all-unchecked → check all; all-checked → uncheck all. Also supports uppercase `[X]` markers.
409
- - **Fixed**: Code block copy button now falls back to `document.execCommand('copy')` when `navigator.clipboard` is unavailable (non-HTTPS contexts), instead of silently failing.
410
- - **Fixed**: @mention and doc-link dropdowns now close when the editor loses focus (blur), with a 150ms delay so clicks on the dropdown itself register first.
411
- - **Fixed**: @mention and doc-link dropdowns had transparent backgrounds because they used editor-scoped CSS variables (`--_surface`) while being appended to `document.body`. Now appended inside `.md-editor-root` with `position: fixed` (not clipped by `overflow: hidden`), so theme variables resolve correctly.
412
- - **Fixed**: Popup dialogs (image, video, YouTube, table, help, callout) now respect the editor theme. Previously always light-themed; now dark-themed when the editor is in dark mode.
413
- - **Test**: 310 tests, all passing, zero unhandled errors.
414
-
415
- ### v0.8.0
416
-
417
- - **Added**: Document/article link inserter. Type `/` to open a searchable dropdown of documents. `onDocSearch(query)` returns matching documents; on selection, inserts either a plain markdown link or a `:::doc-link` preview card.
418
- - **Added**: `:::doc-link` preview card rendering — displays title, thumbnail, description, and domain as a clickable card. Auto-fetches Open Graph / meta tags via `onFetchDocMeta(url)` hook; falls back to plain link when metadata is unavailable or fetch fails.
419
- - **Added**: `insertStyle` option: `'auto'` (default, card when meta available), `'card'` (always card, fallback to link), `'link'` (always plain link).
420
- - **Added**: `docLink` option to `MarkdownEditorOptions` and `CodeEditorOptions`, with `triggerChar`, `minChars`, `maxItems` configuration.
421
- - **Added**: Toolbar button for document link insertion.
422
- - **Test**: 307 tests, all passing, zero unhandled errors.
423
-
424
- ### v0.7.0
425
-
426
- - **Fixed**: Mindmap nested code fences no longer lose content. Custom block rule tracks fence nesting depth (lines with info string open, bare backticks close), so ` ```js ` inside a ` ```mindmap ` block is preserved.
427
- - **Added**: Task list (checkbox) toolbar button and `toggleTaskList()` API. Toggles `- [ ]` / `- [x]` on current line or selected lines. Renders as checkboxes in preview. Keyboard shortcut: `Ctrl+Shift+9`.
428
- - **Added**: @mention dropdown with configurable hooks. `onMentionSearch(query)` returns matching items, `onMentionSelect(item)` returns replacement text. Supports keyboard navigation (ArrowUp/Down, Enter, Escape), avatars, descriptions, `minChars`, and `maxItems` options.
429
- - **Added**: `mention` option to `MarkdownEditorOptions` and `CodeEditorOptions`.
430
- - **Test**: 281 tests, all passing, zero unhandled errors.
431
-
432
- ### v0.6.0
433
-
434
- - **Fixed**: Mindmap `transform: translate(NaN,NaN) scale(NaN)` console errors (hundreds per mindmap). Root cause: `autoFit: true` computed transform on zero-size SVG before layout, and d3-zoom scheduled hundreds of animation frames each throwing "Expected number". Fix: create with `autoFit: false`, set safe default transform immediately, defer `fit()` to next animation frame with dimension guard.
435
- - **Fixed**: Code block colors now sync with editor theme (light/dark). Previously hardcoded to atom-one-dark in all themes. Now uses CSS variables (`--code-bg`, `--code-fg`, `--code-keyword`, etc.) with GitHub-style light and dark palettes.
436
- - **Fixed**: UL/OL list styling in preview now has consistent spacing, bullet alignment, and line height.
437
- - **Improved**: Removed `highlight.js/styles/atom-one-dark.css` import; syntax highlighting colors are now fully theme-aware via CSS variables.
438
- - **Improved**: Removed stale `markdown-it-texmath` from vite external deps (removed in v0.4.0).
439
- - **Improved**: Vitest config now excludes `__MACOSX` and AppleDouble (`._*`) files to prevent false test failures from macOS zip artifacts.
440
- - **Test**: 251 tests, all passing, zero unhandled errors.
441
-
442
- ### v0.5.0
443
-
444
- - **Fixed**: Admonition `:::` closing marker no longer leaks into rendered body content.
445
- - **Fixed**: Quote, unordered list, and ordered list toolbar actions now wrap selected text (or the current line) instead of replacing it.
446
- - **Improved**: Tip/admonition toolbar button now opens a type-picker popup with all six styles (notice, info, tip, success, warning, danger).
447
- - **Improved**: Help dialog now displays keyboard shortcuts with styled key caps and modifier-key symbols (⌘, ⌥, ⇧).
448
- - **Improved**: All toolbar icons redesigned in a clean Lucide/Feather line style for a modern Western aesthetic.
449
- - **Fixed**: Mindmap `transform: translate(NaN,NaN)` error guarded when SVG has zero layout size.
450
- - **Added**: `formatShortcutHtml()` export for rendering styled key caps.
451
- - **Test**: 251 tests, all passing.
452
-
453
- ### v0.4.0
454
-
455
- - Self-contained KaTeX math plugin (removed `markdown-it-texmath`); currency `$5` no longer misparsed.
456
- - Image upload hook `onImageUpload(file)` with paste/drag support.
457
- - 11 keyboard shortcuts with macOS Cmd auto-mapping.
458
- - Help dialog listing all shortcuts.
459
- - Table rows/columns popup dialog.
460
- - Theme toggle fixed for dark mode with github-markdown-css.
461
- - Admonition/tip blocks (`:::notice`, `:::warning`, etc.).
462
- - Configurable toolbar (include-list or per-item show/label/icon).
463
- - Standalone `renderMarkdown(source, options)` API export.
392
+ ---
464
393
 
465
394
  ## License
466
395