@mikedown/core 0.1.0 → 0.2.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/CHANGELOG.md +12 -0
- package/README.md +63 -2
- package/dist/index.d.ts +67 -2
- package/dist/index.js +410 -39
- package/dist/index.js.map +1 -1
- package/dist/styles.css +96 -5
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- `bindMikeDownValue` (`@mikedown/core/binding`): the framework-neutral value binding the React, Vue, Angular and web component wrappers are built on (pristine writes, debounced change, flush on blur, read-only, disabled and touched state).
|
|
6
|
+
- Editor options for `placeholder`, `id` and ARIA naming, and a landmark role on the editor.
|
|
7
|
+
- The bound theme is re-applied only when it changes, which ends a theme update loop.
|
|
8
|
+
- List markers are set explicitly, so page CSS resets can no longer strip bullets and numbers.
|
|
9
|
+
- Completed task text uses the muted text color, keeping at least 4.5:1 contrast in light and dark themes.
|
|
10
|
+
- Task checkboxes are named after their item for screen readers and have a 24 by 24px click and touch target.
|
|
11
|
+
- Toolbar buttons that show text have screen reader names that start with that text; toolbar icons are hidden from assistive technology.
|
|
12
|
+
- The floating table toolbar shows only while a focused cursor is in a table.
|
|
13
|
+
- Toolbar buttons that don't fit move into a More (⋯) menu at the end of the toolbar instead of being cut off.
|
|
14
|
+
|
|
3
15
|
## 0.1.0
|
|
4
16
|
|
|
5
17
|
First public release. The MikeDown WYSIWYG markdown editor as a framework-agnostic package, the same editor that ships in the MikeDown VS Code extension and MikeDown Desktop.
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ The MikeDown WYSIWYG markdown editor as a framework-agnostic package: TipTap (Pr
|
|
|
8
8
|
npm install @mikedown/core
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
npm 7+ and pnpm 8+ install the peer dependencies for you (see [Peer dependencies](#peer-dependencies)); Yarn does not, so use the `yarn add` line under [Framework wrappers](#framework-wrappers). The package is ESM only.
|
|
12
12
|
|
|
13
13
|
## Minimal example
|
|
14
14
|
|
|
@@ -33,6 +33,36 @@ handle.destroy();
|
|
|
33
33
|
|
|
34
34
|
The editor fills its container (`height: 100%`), so give the container a height. Without one the editor falls back to a `240px` minimum height; set `--mikedown-min-height` to change that floor.
|
|
35
35
|
|
|
36
|
+
## Binding a value
|
|
37
|
+
|
|
38
|
+
`bindMikeDownValue` is the glue a form field or framework wrapper needs, in one call. Import it from the package root or from `@mikedown/core/binding`.
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { bindMikeDownValue } from '@mikedown/core/binding';
|
|
42
|
+
|
|
43
|
+
const field = bindMikeDownValue(container, {
|
|
44
|
+
initialValue: '# Hello',
|
|
45
|
+
debounceMs: 150,
|
|
46
|
+
onValue: (markdown) => save(markdown),
|
|
47
|
+
theme: 'auto',
|
|
48
|
+
darkSelector: 'html[data-mode=dark]',
|
|
49
|
+
id: 'notes',
|
|
50
|
+
ariaLabel: 'Meeting notes',
|
|
51
|
+
placeholder: 'Start writing…',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
field.setValue(fromServer); // loads pristine; skipped when equal to the current value
|
|
55
|
+
field.flush(); // report a pending change now
|
|
56
|
+
field.setTheme('dark');
|
|
57
|
+
field.focus();
|
|
58
|
+
field.handle; // the MikeDownEditorHandle
|
|
59
|
+
field.destroy(); // flushes, then removes observers and listeners
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
- **Change reporting.** `onValue` fires after `debounceMs` of quiet and at once on blur. `outputTransform(markdown)` maps the text on its way out; `setValue` compares against its output.
|
|
63
|
+
- **Theme.** `auto` is dark when the container or an ancestor matches `darkSelector` (it uses `closest()`, so both `html[data-mode=dark]` and a `.dark-theme` wrapper work), re-checked when attributes change. Without a selector it follows `prefers-color-scheme`.
|
|
64
|
+
- **Accessibility.** `id`, `ariaLabel` and `ariaLabelledBy` go on the editable surface in both views (rich text and source). With only an `id`, the binding looks up `label[for=id]`, uses it as the name and focuses the editor when it is clicked. `ariaLabelledBy` is the sturdier choice. The accessible name follows ARIA's order: `aria-labelledby`, then `aria-label`, then `<label for>` elements. A bound editor drops `role="main"` (`landmark: false`), since a field is not a page landmark; the same `landmark`, `placeholder`, `id`, `ariaLabel` and `ariaLabelledBy` options exist on `createMikeDownEditor`.
|
|
65
|
+
|
|
36
66
|
## Host interface
|
|
37
67
|
|
|
38
68
|
`options.host` is a `MikeDownHost`. Only `onChange` is required. Every other method is optional, and leaving one out turns its feature off and hides its UI. `NoopHost` is a ready-made host with just `onChange`.
|
|
@@ -121,7 +151,7 @@ The app must have exactly one copy of ProseMirror and CodeMirror, so these are p
|
|
|
121
151
|
|
|
122
152
|
`tiptap-markdown`, `markdown-it-emoji`, `markdown-it-footnote`, `markdown-it-mark` and `nspell` are regular dependencies and install with the package.
|
|
123
153
|
|
|
124
|
-
`katex` is a required peer because `styles.css` imports its stylesheet. `mermaid` is optional. Both are loaded with dynamic `import()` the first time a diagram or formula appears, so they stay out of your initial bundle. Without `mermaid` the diagram block shows an error or the raw source.
|
|
154
|
+
`katex` is a required peer because `styles.css` imports its stylesheet. `mermaid` is optional. Both are loaded with dynamic `import()` the first time a diagram or formula appears, so they stay out of your initial bundle. Without `mermaid` the diagram block shows an error or the raw source. Install it anyway for Angular and Next.js (Turbopack) builds, which resolve the `import('mermaid')` at build time and fail without it; Vite builds pass.
|
|
125
155
|
|
|
126
156
|
## Bundler notes
|
|
127
157
|
|
|
@@ -134,6 +164,23 @@ The app must have exactly one copy of ProseMirror and CodeMirror, so these are p
|
|
|
134
164
|
|
|
135
165
|
Mermaid renders SVG with `securityLevel: 'strict'` and loads its diagram types by dynamic `import()`. Your CSP needs `script-src 'self'` (or wherever your chunks are served from) and must allow inline styles for the SVG output (`style-src 'unsafe-inline'`, or a nonce on the style elements your bundler injects). A page that cannot allow this should set `features: { mermaid: false }`. KaTeX needs no script or eval permissions, only its stylesheet and fonts.
|
|
136
166
|
|
|
167
|
+
## Framework wrappers
|
|
168
|
+
|
|
169
|
+
- [`@mikedown/web-component`](https://www.npmjs.com/package/@mikedown/web-component): `<mikedown-editor>`, a form-associated custom element for plain HTML, CDN pages and server-rendered templates.
|
|
170
|
+
- [`@mikedown/react`](https://www.npmjs.com/package/@mikedown/react): `MikeDownEditor` for React 18 and 19.
|
|
171
|
+
- [`@mikedown/vue`](https://www.npmjs.com/package/@mikedown/vue): `MikeDownEditor` with `v-model` for Vue 3.3+.
|
|
172
|
+
- [`@mikedown/angular`](https://www.npmjs.com/package/@mikedown/angular): a form control for Angular 17+.
|
|
173
|
+
|
|
174
|
+
Each wrapper depends only on core and its framework. Yarn does not install peers, so list core's:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
yarn add @mikedown/core @codemirror/commands @codemirror/lang-markdown @codemirror/language @codemirror/state @codemirror/theme-one-dark @codemirror/view @tiptap/core @tiptap/extension-code-block-lowlight @tiptap/extension-hard-break @tiptap/extension-highlight @tiptap/extension-image @tiptap/extension-link @tiptap/extension-placeholder @tiptap/extension-table @tiptap/extension-table-cell @tiptap/extension-table-header @tiptap/extension-table-row @tiptap/extension-task-item @tiptap/extension-task-list @tiptap/extension-text @tiptap/pm @tiptap/starter-kit katex lowlight mermaid
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Testing
|
|
181
|
+
|
|
182
|
+
Use vitest with jsdom, stubbing `Range.prototype.getBoundingClientRect` and `getClientRects`. Jest: add `transformIgnorePatterns: ['/node_modules/(?!(@mikedown|lowlight|devlop)/)']` so the ESM-only packages in core's import graph get transformed (TipTap, CodeMirror and `tiptap-markdown` ship `require` builds). vitest is the recommended runner.
|
|
183
|
+
|
|
137
184
|
## Demo
|
|
138
185
|
|
|
139
186
|
From a clone of the repository:
|
|
@@ -144,6 +191,20 @@ npm run demo:core
|
|
|
144
191
|
|
|
145
192
|
This builds the package and serves `packages/core/demo/` with two editors side by side, a theme toggle, a "Log markdown" button and an `uploadImage` stub that returns an object URL.
|
|
146
193
|
|
|
194
|
+
## Browser support
|
|
195
|
+
|
|
196
|
+
Current Chrome, Edge, Firefox and Safari (ES2020 output, ESM only).
|
|
197
|
+
|
|
198
|
+
## Privacy
|
|
199
|
+
|
|
200
|
+
No telemetry. The editor makes no network calls except the ones you configure: spell check dictionaries (when `spellcheckDictionaryUrl` is set), your `host` callbacks, and the lazy `import()` of KaTeX and Mermaid from your own bundle.
|
|
201
|
+
|
|
202
|
+
## Links
|
|
203
|
+
|
|
204
|
+
- [Developers hub](https://www.iadev.net/mikedown/developers/)
|
|
205
|
+
- [npm](https://www.npmjs.com/package/@mikedown/core)
|
|
206
|
+
- [GitHub](https://github.com/mikejoseph23/mikedown/tree/main/packages/core)
|
|
207
|
+
|
|
147
208
|
## Build
|
|
148
209
|
|
|
149
210
|
```bash
|
package/dist/index.d.ts
CHANGED
|
@@ -353,6 +353,19 @@ interface MikeDownOptions {
|
|
|
353
353
|
* the rich editor and source mode.
|
|
354
354
|
*/
|
|
355
355
|
hostOwnsShortcuts?: boolean;
|
|
356
|
+
/** Empty-document placeholder. Default: "Type / for commands…" or "Start writing…". */
|
|
357
|
+
placeholder?: string;
|
|
358
|
+
/** `id` of the editable surface (the contenteditable, and the source view's content). */
|
|
359
|
+
id?: string;
|
|
360
|
+
/** Accessible name of the editable surface. Default "Document text". */
|
|
361
|
+
ariaLabel?: string;
|
|
362
|
+
/** Id of the element that names the editable surface. Wins over `ariaLabel`'s default. */
|
|
363
|
+
ariaLabelledBy?: string;
|
|
364
|
+
/**
|
|
365
|
+
* Put `role="main"` on the editor container. Default true (VS Code, Desktop).
|
|
366
|
+
* Embedded form fields pass false, since a field is not a page landmark.
|
|
367
|
+
*/
|
|
368
|
+
landmark?: boolean;
|
|
356
369
|
}
|
|
357
370
|
type MikeDownCommand = 'toggleBold' | 'toggleItalic' | 'toggleStrike' | 'toggleHighlight' | 'toggleCode' | 'toggleBulletList' | 'toggleOrderedList' | 'toggleTaskList' | 'pasteWithoutFormatting' | 'openEmojiPicker' | 'undo' | 'redo' | 'toggleSource' | 'toggleSidebar' | 'openSettings' | 'copyAsRichText' | 'heading1' | 'heading2' | 'heading3' | 'heading4' | 'heading5' | 'heading6' | 'paragraph' | 'openFind' | 'openFindReplace' | 'insertTable' | 'insertLink' | 'insertImage' | 'zoomIn' | 'zoomOut' | 'zoomReset';
|
|
358
371
|
type MikeDownEvent = 'change' | 'focus' | 'blur' | 'ready';
|
|
@@ -455,5 +468,57 @@ interface MikeDownEditorHandle {
|
|
|
455
468
|
|
|
456
469
|
declare function createMikeDownEditor(container: HTMLElement, options: MikeDownOptions): MikeDownEditorHandle;
|
|
457
470
|
|
|
458
|
-
|
|
459
|
-
|
|
471
|
+
/**
|
|
472
|
+
* Value binding for framework wrappers. Every wrapper (React, Vue, Angular,
|
|
473
|
+
* the web component) needs the same glue around the editor: a debounced change
|
|
474
|
+
* callback that reports at once on blur, external writes that load pristine and
|
|
475
|
+
* skip when nothing changed, a theme that can follow the page, and an accessible
|
|
476
|
+
* name for the editable surface. `bindMikeDownValue` holds it once.
|
|
477
|
+
*/
|
|
478
|
+
|
|
479
|
+
type BindingTheme = 'light' | 'dark' | 'auto';
|
|
480
|
+
interface MikeDownBindingOptions extends Omit<MikeDownOptions, 'host' | 'initialMarkdown' | 'theme'> {
|
|
481
|
+
/** Loaded as the pristine baseline. Default ''. */
|
|
482
|
+
initialValue?: string;
|
|
483
|
+
/** Quiet time after the last keystroke before `onValue` fires. Default 150. 0 reports at once. */
|
|
484
|
+
debounceMs?: number;
|
|
485
|
+
/** Called with the markdown after the debounce, and at once on blur. Not called when the text equals the last value. */
|
|
486
|
+
onValue?: (markdown: string) => void;
|
|
487
|
+
/**
|
|
488
|
+
* Called when focus leaves the whole control (editor and toolbar), like a native input's blur. Moving into the
|
|
489
|
+
* core's menus and dialogs (`.mikedown-portal`) does not count. A pending change is flushed first.
|
|
490
|
+
*/
|
|
491
|
+
onTouched?: () => void;
|
|
492
|
+
/** Start disabled. A disabled binding is read-only too. */
|
|
493
|
+
disabled?: boolean;
|
|
494
|
+
/** Default 'auto'. */
|
|
495
|
+
theme?: BindingTheme;
|
|
496
|
+
/**
|
|
497
|
+
* CSS selector for 'auto': dark when the container, or an ancestor, matches
|
|
498
|
+
* (`html[data-mode=dark]`, `.dark-theme`). Without it 'auto' follows
|
|
499
|
+
* `prefers-color-scheme`.
|
|
500
|
+
*/
|
|
501
|
+
darkSelector?: string;
|
|
502
|
+
/** Maps the markdown on its way out (e.g. to preserve brackets). `setValue` compares against its output. */
|
|
503
|
+
outputTransform?: (markdown: string) => string;
|
|
504
|
+
/** Optional host methods (image upload, wikilinks, ...). `onChange` is run before the binding's own. */
|
|
505
|
+
host?: Partial<MikeDownHost>;
|
|
506
|
+
}
|
|
507
|
+
interface MikeDownBinding {
|
|
508
|
+
/** The underlying editor handle. */
|
|
509
|
+
readonly handle: MikeDownEditorHandle;
|
|
510
|
+
/** Load an external value as the pristine baseline. Skipped when it equals the current value. */
|
|
511
|
+
setValue(markdown: string): void;
|
|
512
|
+
/** Report a pending change now. */
|
|
513
|
+
flush(): void;
|
|
514
|
+
setTheme(theme: BindingTheme): void;
|
|
515
|
+
/** Read-only is `readOnly || disabled`. */
|
|
516
|
+
setReadOnly(readOnly: boolean): void;
|
|
517
|
+
setDisabled(disabled: boolean): void;
|
|
518
|
+
focus(): void;
|
|
519
|
+
destroy(): void;
|
|
520
|
+
}
|
|
521
|
+
declare function bindMikeDownValue(container: HTMLElement, options?: MikeDownBindingOptions): MikeDownBinding;
|
|
522
|
+
|
|
523
|
+
export { DEFAULT_FEATURES, NoopHost, bindMikeDownValue, createMikeDownEditor };
|
|
524
|
+
export type { BacklinkItem, BindingTheme, DiffBaseline, ImageInsertResult, ImagePathMapping, ImageResizeRequest, ImageResizeResult, ImageUploadRequest, LinkCheckRequest, LinkOpenBehavior, LinkSuggestion, MikeDownBinding, MikeDownBindingOptions, MikeDownCommand, MikeDownEditorHandle, MikeDownEvent, MikeDownFeatures, MikeDownHost, MikeDownOptions, MikeDownSettings, MikeDownSettingsPatch, MikeDownSettingsSection, MikeDownSettingsSectionBuild, MikeDownSettingsSectionContext, MikeDownSidebarPrefs, MikeDownStats, MikeDownViewState, NormalizationStyle, SidebarFooterLink, SidebarStateUpdate, TagCount, WikilinkResolution };
|