@openmarkdown-dev/editor 0.0.0-stage → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Xueyan Zhang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,391 @@
1
- # Temporary Holding Version
1
+ ![The OpenMarkdown editor: typing in a table, rendering math and a diagram, and saving with no change to the file](https://raw.githubusercontent.com/OpenMarkdown-dev/OpenMarkdown-editor/main/media/hero.gif)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ # @openmarkdown-dev/editor
4
+
5
+ A live-preview Markdown editor on CodeMirror 6 that edits the Markdown source itself, so the file you save is the file you wrote.
6
+
7
+ ```sh
8
+ npm install @openmarkdown-dev/editor
9
+ ```
10
+
11
+ ```ts
12
+ import { createEditorState, mountEditor, openMarkdownFile, saveMarkdownFile } from "@openmarkdown-dev/editor";
13
+
14
+ const file = openMarkdownFile(new Uint8Array(await (await fetch("/notes.md")).arrayBuffer()));
15
+ const editor = mountEditor(document.getElementById("editor")!, createEditorState({ doc: file.text }));
16
+ const bytes = saveMarkdownFile(editor.getDoc(), file); // unedited: the file's own bytes
17
+ ```
18
+
19
+ - **Live preview:** headings, lists, tables, math, Mermaid diagrams, callouts and YAML properties render in place; the Markdown shows under the caret.
20
+ - **Your file stays yours:** open and save without editing, and the file is byte-for-byte what it was.
21
+ - **Tables in place:** edit a cell where it is drawn.
22
+ - **Images:** paste or drop them in, drag a corner to resize.
23
+ - **Framework-free:** plain TypeScript and DOM. Works with React, Vue, Svelte or nothing. Not build-free: with no bundler, [self-host it](https://github.com/OpenMarkdown-dev/OpenMarkdown-editor/blob/main/docs/guides/self-hosting.md).
24
+ - **No CSS to import:** every color and font is a `--ome-*` custom property you can override.
25
+ - **Safe with untrusted documents:** rendering a document never runs its script.
26
+ - **Diagrams optional:** `@openmarkdown-dev/editor/no-diagrams` is the same editor without Mermaid, for a smaller build or a strict style policy.
27
+ - **SSR-safe import:** importing in Node does not touch `window`. Mounting needs a DOM.
28
+
29
+ [Website](https://openmarkdown.dev/editor/) · [Demo](https://openmarkdown.dev/editor/demo/) · [Examples](https://openmarkdown.dev/editor/examples/) · [Docs](https://openmarkdown.dev/editor/docs/)
30
+
31
+ Extracted from the [OpenMarkdown](https://openmarkdown.dev) app, where it is the editor
32
+ people and AI agents write in every day. MIT licensed. The [docs](https://github.com/OpenMarkdown-dev/OpenMarkdown-editor/tree/main/docs/) folder holds the
33
+ guides in plain Markdown.
34
+
35
+ ## Fidelity
36
+
37
+ Lossless round-trip: open a Markdown file and save it without editing, and the file is byte-for-byte what it was.
38
+
39
+ That names one sentence of the claim below. For edits, the claim says exactly what changes. This is the whole claim. Every sentence is backed by a test:
40
+
41
+ > OpenMarkdown edits your Markdown source text directly; it never rebuilds the file from a document tree. Open and save without editing, and the file is byte-for-byte what it was: line endings (LF / CRLF), BOM, final newline, whitespace, tabs, escapes and Unicode spelling untouched. When you edit, only what you touched changes, with a few exceptions, each pinned by a test: a table you edit (or click into a cell of) is re-aligned as a whole when you leave it — alignment, cell text and escapes kept; table commands rewrite that table the same way; indenting a list item or pressing Enter in a numbered list renumbers the affected items; Enter in a list item drops trailing spaces before the cursor; a file with mixed line endings gets uniform ones once you edit it.
42
+
43
+ The claim holds when your host reads and writes the file with the package's
44
+ `openMarkdownFile` and `saveMarkdownFile`. The editor keeps the document as a string in `\n`
45
+ with no BOM; these two keep the rest of the file:
46
+
47
+ ```ts
48
+ import { createEditorState, mountEditor, openMarkdownFile, saveMarkdownFile } from "@openmarkdown-dev/editor";
49
+
50
+ let file = openMarkdownFile(bytes); // strict UTF-8; notes the BOM and line endings
51
+ const editor = mountEditor(document.getElementById("editor")!, createEditorState({ doc: file.text }));
52
+
53
+ const out = saveMarkdownFile(editor.getDoc(), file); // unedited: the original bytes
54
+ ```
55
+
56
+ `openMarkdownFile` throws `InvalidUtf8Error` for bytes that are not UTF-8, so a damaged file is
57
+ refused instead of silently rewritten. Unedited, `saveMarkdownFile` returns the original bytes,
58
+ even for a file that mixes line endings. Edited, it writes the BOM back and uses the file's
59
+ dominant line ending (the most frequent one; on a tie, the first) for every line.
60
+
61
+ After a save, make the bytes you wrote the new baseline: `file = openMarkdownFile(out)`. Then
62
+ `editor.getDoc() !== file.text` tells you whether there are unsaved edits.
63
+
64
+ ### How it compares
65
+
66
+ We opened the same 56-line Markdown file in six embeddable editors (their npm packages, in
67
+ headless Chromium), saved it without editing, then changed one word and saved again:
68
+
69
+ | Editor | Open → save, no edit | Change one word → save |
70
+ | --- | --- | --- |
71
+ | **@openmarkdown-dev/editor** (CodeMirror 6) | byte-identical | only that word changed |
72
+ | **Atomic Editor** (CodeMirror 6) | byte-identical | only that word changed |
73
+ | Milkdown Crepe (ProseMirror) | 13 of 22 tracked features changed | the same 30 changed lines, plus the word |
74
+ | Toast UI Editor (ProseMirror) | 15 of 22 changed | the same changes, plus the word |
75
+ | Tiptap + `@tiptap/markdown` (ProseMirror) | 17 of 22 changed | the same changes, plus the word |
76
+ | MDXEditor (Lexical) | refused the file (reference links); 13 of 19 changed without them | the edit was lost on the full file |
77
+
78
+ Atomic Editor ties with us here, because it is built the same way: the source is the
79
+ document. The difference is with editors that rebuild Markdown from a tree. When we
80
+ edited one table cell, both CodeMirror editors re-aligned that table. Ours kept the
81
+ column alignment. Atomic's rewrote the delimiter row to `| --- |` and lost it.
82
+
83
+ The method, an example diff, the run date and the caveats: [How we compared editors](https://github.com/OpenMarkdown-dev/OpenMarkdown-editor/blob/main/docs/fidelity/method.md).
84
+
85
+ ## Install in detail
86
+
87
+ ```sh
88
+ npm install @openmarkdown-dev/editor @codemirror/state @codemirror/view @codemirror/commands \
89
+ @codemirror/language @codemirror/language-data @codemirror/lang-markdown \
90
+ @codemirror/search @codemirror/autocomplete @lezer/highlight @lezer/markdown
91
+ ```
92
+
93
+ CodeMirror and Lezer are **peer dependencies**: your page must hold exactly one copy of
94
+ `@codemirror/state`. If you already use CodeMirror, the editor shares your copy. npm 7 and
95
+ later install peers for you. `temml` (math) and `mermaid` (diagrams) are regular
96
+ dependencies. They are loaded with `import()` only when a document contains math or a
97
+ diagram, and your bundler splits them into separate chunks.
98
+
99
+ ### Without diagrams
100
+
101
+ `@openmarkdown-dev/editor/no-diagrams` is a second entry with the same API, built from the same
102
+ code, minus diagram support. Nothing it imports reaches Mermaid, so your bundler emits no
103
+ Mermaid chunks at all; a ```` ```mermaid ```` block stays a code block and the `diagrams` option
104
+ is ignored. Use it when no page needs diagrams, or under a style policy Mermaid cannot meet
105
+ (see [Security](#security)). npm still installs `mermaid`, because the main entry depends on
106
+ it.
107
+
108
+ ```ts
109
+ import { createEditorState, mountEditor } from "@openmarkdown-dev/editor/no-diagrams";
110
+ ```
111
+
112
+ ### Without a bundler
113
+
114
+ Framework-free is not build-free. A page with no bundler of its own can load the editor from a
115
+ CDN once the package is published there, or build it once and serve the files itself:
116
+ [self-hosting](https://github.com/OpenMarkdown-dev/OpenMarkdown-editor/blob/main/docs/guides/self-hosting.md) gives the build script, the output to deploy and the
117
+ Content Security Policy.
118
+
119
+ ## Usage
120
+
121
+ ```ts
122
+ import { configureEditorEnvironment, createEditorState, mountEditor } from "@openmarkdown-dev/editor";
123
+
124
+ // Optional, once per page: language, color scheme, clipboard, links, spelling.
125
+ configureEditorEnvironment({ language: "en" });
126
+
127
+ const state = createEditorState({
128
+ doc: "# Hello\n\nSome *Markdown*, a table and $E = mc^2$.\n",
129
+ host: {
130
+ onChange: (doc, isUserEdit) => {
131
+ if (isUserEdit) save(doc);
132
+ },
133
+ },
134
+ });
135
+ const editor = mountEditor(document.getElementById("editor")!, state);
136
+
137
+ editor.getDoc(); // the current Markdown, as a string
138
+ editor.setMode("source"); // "live" | "source" | "reading"
139
+ editor.view.focus(); // setMode does not move focus; give it back after a button click
140
+ editor.destroy(); // when the editor leaves the page
141
+ ```
142
+
143
+ Give the parent element a height. The editor fills it and scrolls inside it.
144
+
145
+ ### React
146
+
147
+ There is no React wrapper to install. Mount the editor in an effect:
148
+
149
+ ```tsx
150
+ import { useEffect, useRef } from "react";
151
+ import { createEditorState, mountEditor, type MarkdownEditor } from "@openmarkdown-dev/editor";
152
+
153
+ export function MarkdownField({ value, onChange }: { value: string; onChange: (doc: string) => void }) {
154
+ const parent = useRef<HTMLDivElement>(null);
155
+ const editor = useRef<MarkdownEditor | null>(null);
156
+ const latest = useRef(onChange);
157
+ latest.current = onChange;
158
+
159
+ useEffect(() => {
160
+ const state = createEditorState({
161
+ doc: value,
162
+ host: { onChange: (doc, isUserEdit) => isUserEdit && latest.current(doc) },
163
+ });
164
+ editor.current = mountEditor(parent.current!, state);
165
+ return () => editor.current?.destroy();
166
+ // Mount once; later `value` changes are pushed in below.
167
+ // eslint-disable-next-line react-hooks/exhaustive-deps
168
+ }, []);
169
+
170
+ useEffect(() => {
171
+ if (editor.current && editor.current.getDoc() !== value) editor.current.setDoc(value);
172
+ }, [value]);
173
+
174
+ return <div ref={parent} style={{ height: 480 }} />;
175
+ }
176
+ ```
177
+
178
+ `host` is read through the object on every call. You can mutate it, or read the latest
179
+ props through a ref as above, without rebuilding the state.
180
+
181
+ ## API
182
+
183
+ Three functions to make an editor, two to read and write its file, and their types. The complete, commented surface is
184
+ [`api/editor.api.d.ts`](https://github.com/OpenMarkdown-dev/OpenMarkdown-editor/blob/main/api/editor.api.d.ts), and the build checks that it matches. The
185
+ [API reference](https://openmarkdown.dev/editor/docs/api/) is generated from it.
186
+
187
+ ### `createEditorState(options)` → `EditorState`
188
+
189
+ Builds a CodeMirror `EditorState` with the whole editor installed. Every option is optional:
190
+
191
+ | Option | Type | Default | What it does |
192
+ | --- | --- | --- | --- |
193
+ | `doc` | `string` | `""` | The Markdown source. |
194
+ | `mode` | `"live" \| "source" \| "reading"` | `"live"` | Live preview, plain colored source, or rendered read-only. |
195
+ | `docPath` | `string \| null` | `null` | The document's absolute path. Relative links and pasted images resolve against it. |
196
+ | `selection` | `{ anchor, head? }` | start of document | Where the caret starts. |
197
+ | `host` | `EditorHost` | none | Per-document callbacks (below). |
198
+ | `checkSpelling` | `boolean` | `true` | Draw spelling squiggles when the environment's `spell` service is on. |
199
+ | `diagrams` | `boolean` | `true` | Render ```` ```mermaid ```` fences as diagrams. With `false` they stay code blocks and Mermaid is never loaded. |
200
+ | `extensions` | CodeMirror `Extension` | none | Your own CodeMirror extensions, installed beside the editor's in every mode. |
201
+
202
+ One state is one document, with its undo history, folds and mode. A host with several
203
+ documents can keep one state each and swap them into one view with
204
+ `editor.view.setState(state)`, then call `editor.syncAmbient()`.
205
+
206
+ ### `mountEditor(parent, state)` → `MarkdownEditor`
207
+
208
+ Appends an editor view to `parent` and returns a handle:
209
+
210
+ | Member | What it does |
211
+ | --- | --- |
212
+ | `view` | The CodeMirror `EditorView`, for anything not covered here. |
213
+ | `getDoc()` | The current Markdown source. |
214
+ | `setDoc(doc)` | Make `doc` the document, for example after the file changed on disk. Only the part that differs is replaced, so the reader's scroll position and selection stay. Not a user edit, not an undo step. |
215
+ | `refreshImages()` | Ask `resolveImage` again for every image, after its answer changed (the reader allowed remote images, say). |
216
+ | `mode()` / `setMode(mode)` | Read or switch the mode. The document, selection and undo history are kept. Focus does not move: call `view.focus()` to type right away. |
217
+ | `docPath()` / `setDocPath(path)` | Read or change the document's path, for example after a rename. |
218
+ | `syncAmbient()` | After swapping a state in, re-apply the current light or dark scheme. |
219
+ | `destroy()` | Remove the view and stop following the color scheme. |
220
+
221
+ ### `EditorHost` (per document, passed as `host`)
222
+
223
+ Every callback is optional. When you leave one out, that feature is switched off and
224
+ nothing errors.
225
+
226
+ | Callback | Called when | Without it |
227
+ | --- | --- | --- |
228
+ | `onChange(doc, isUserEdit, docPath)` | The document changed. `isUserEdit` is false for `setDoc` and other programmatic changes. | Nothing happens. |
229
+ | `resolveImage(src)` | An image is shown. Return a URL the page may load, or `null` to show the source. Script-capable URLs are dropped whatever you return. | Only absolute `https:` images load, from any host. Pass `() => null` to load none. |
230
+ | `openLink(absPath, fragment)` | A link to a local file was followed (Cmd/Ctrl+click; a plain click in reading mode). `fragment` is its `#fragment` without the `#`, or `null`. | Local links do nothing. |
231
+ | `getFolder()` | Link completion needs the root of the user's files. | Completion uses the document's own directory. |
232
+ | `listFiles(root)` | Link completion lists linkable files under `root` (root-relative paths). | Completion offers headings only. |
233
+ | `saveAttachment(docPath, bytes, hint)` | An image is pasted or dropped. Store it and resolve the relative path to link, or `null`. | Pasting or dropping an image inserts nothing. |
234
+ | `onUnsupportedPaste(reason, count)` | Images were pasted or dropped but could not be stored: `reason` is `"no-save-attachment"` or `"no-doc-path"`. Use it to tell the user. | The images are dropped silently. |
235
+ | `onMoved()` | The selection or scroll position moved (debounced). Use it to remember the position. | Nothing happens. |
236
+ | `onOutline(items)` | The heading outline changed (`OutlineItem[]`, or `null` when the view goes away). | Nothing happens. |
237
+
238
+ ### `openMarkdownFile(bytes)` → `MarkdownFile` and `saveMarkdownFile(doc, file)` → `Uint8Array`
239
+
240
+ The host's file codec, pure and DOM-free (it runs in Node too). `openMarkdownFile` decodes a
241
+ file's bytes as strict UTF-8 and returns:
242
+
243
+ | Field | What it is |
244
+ | --- | --- |
245
+ | `text` | The text to edit: no BOM, every line ending as `\n`. Unicode is not normalized. |
246
+ | `bytes` | A copy of the original bytes. |
247
+ | `bom` | Whether the file started with a UTF-8 BOM. |
248
+ | `ending` | The line ending an edited save writes: `"\n"`, `"\r\n"` or `"\r"`. |
249
+ | `mixed` | Whether the file mixes line endings. |
250
+
251
+ Invalid UTF-8 throws `InvalidUtf8Error` (`code: "INVALID_UTF8"`). `saveMarkdownFile` returns
252
+ `file.bytes` while `doc` equals `file.text`, and otherwise encodes `doc` with the BOM and
253
+ `ending`. See [Fidelity](#fidelity).
254
+
255
+ ### `configureEditorEnvironment(env)` (once per page)
256
+
257
+ Window-wide capabilities. Call it before the first editor mounts. A later call replaces
258
+ the whole environment. Every member is optional:
259
+
260
+ | Member | What it does | Without it |
261
+ | --- | --- | --- |
262
+ | `language` | The editor's own UI words: `"en"`, `"zh"`, `"ja"` or `"ko"`. | English. |
263
+ | `colorScheme` | `{ current(), subscribe(cb) }`: the page's `"light"` / `"dark"` scheme and its changes. | Always light. |
264
+ | `copyText(text)` | Put text on the clipboard (the code-block copy button). | No copy button action. |
265
+ | `openExternal(url)` | A web link was followed (Cmd/Ctrl+click; a plain click in reading mode). Your policy decides how it opens. | Web links do nothing. |
266
+ | `refusedLink(url, scheme)` | The editor refused a script-capable link (`javascript:`, `data:` and similar). Use it to tell the user. Never open the URL. | The refusal is silent. |
267
+ | `spell` | A `SpellService` that draws spelling squiggles and offers suggestions (`isOn`, `onChange`, `newDocument`, `check`, `suggestions`, `closeDocument`). | No spell checking. |
268
+ | `handleKey(view, event)` | Sees each key before the editor's defaults. Return `true` to consume it. | The editor's keymap only. |
269
+
270
+ A color scheme that follows the operating system:
271
+
272
+ ```ts
273
+ const media = matchMedia("(prefers-color-scheme: dark)");
274
+ configureEditorEnvironment({
275
+ colorScheme: {
276
+ current: () => (media.matches ? "dark" : "light"),
277
+ subscribe: (cb) => {
278
+ const on = () => cb(media.matches ? "dark" : "light");
279
+ media.addEventListener("change", on);
280
+ return () => media.removeEventListener("change", on);
281
+ },
282
+ },
283
+ copyText: (text) => navigator.clipboard.writeText(text),
284
+ openExternal: (url) => window.open(url, "_blank", "noopener"),
285
+ });
286
+ ```
287
+
288
+ ## Theming
289
+
290
+ The editor paints only with `--ome-*` custom properties. Each one has a light and a dark
291
+ default. To restyle the editor, set them **on `.cm-editor`**. Setting them on `:root` has
292
+ no effect, because the editor's own defaults win over inherited values.
293
+
294
+ ```css
295
+ .cm-editor {
296
+ --ome-bg: #fffdf7;
297
+ --ome-accent: #0b6bcb;
298
+ --ome-font: "Inter", system-ui, sans-serif;
299
+ }
300
+ ```
301
+
302
+ | Token | Used for |
303
+ | --- | --- |
304
+ | `--ome-bg` | The document surface. |
305
+ | `--ome-fg` | Body text and the caret. |
306
+ | `--ome-muted` | Quiet surfaces. |
307
+ | `--ome-muted-fg` | Secondary text: Markdown marks, placeholders, resting controls. |
308
+ | `--ome-border` | Hairlines: table rules, quote bars, frames. |
309
+ | `--ome-accent` | Links, selection, find hits, focus, list marks. |
310
+ | `--ome-danger` | Errors, such as a search with no match. |
311
+ | `--ome-agent` | The `@agent` mention. |
312
+ | `--ome-spelling` | The spelling squiggle. |
313
+ | `--ome-code-bg` | Inline code, code blocks, table headers (translucent). |
314
+ | `--ome-callout-note`, `--ome-callout-tip`, `--ome-callout-important`, `--ome-callout-warning`, `--ome-callout-caution` | The five callout kinds. |
315
+ | `--ome-state-hover`, `--ome-state-selected` | Hover and selected layers on controls. |
316
+ | `--ome-radius` | The corner radius of controls. |
317
+ | `--ome-font`, `--ome-font-mono` | The prose and code font stacks. No font files are shipped. |
318
+ | `--ome-syntax-keyword`, `--ome-syntax-string`, `--ome-syntax-number`, `--ome-syntax-function`, `--ome-syntax-type`, `--ome-syntax-property` | Code highlighting. One value is used for both schemes. |
319
+
320
+ ## Languages
321
+
322
+ The editor's own words are the find panel, copy and fold buttons, the properties block,
323
+ and image and link chips. They ship in English, Chinese, Japanese and Korean. Choose one
324
+ with `configureEditorEnvironment({ language })`. The editor does not guess from the
325
+ browser. Your document text can be in any language.
326
+
327
+ ## Security
328
+
329
+ The editor is built to render documents written by someone else, such as a teammate or an
330
+ AI agent:
331
+
332
+ - **Raw HTML is shown as source**, never rendered.
333
+ - **Math** (Temml, `trust: false`) and the editor's icons reach the DOM only through one
334
+ sanitiser with an allowlist of elements and attributes. Unknown elements make the
335
+ formula fall back to its source. Unknown attributes are dropped.
336
+ - **Mermaid** runs with `securityLevel: "strict"` and no HTML labels. Its SVG goes through
337
+ the same allowlist.
338
+ - **Links** are classified the way a browser reads them, after decoding entities and
339
+ removing control characters. `javascript:`, `vbscript:`, `livescript:` and `data:` links
340
+ are never passed to `openLink` or `openExternal`, however they are obfuscated. Your
341
+ `refusedLink` is told instead.
342
+ - **Images**: without a `resolveImage`, only absolute `https:` images load. With one,
343
+ script-capable URLs are still dropped.
344
+
345
+ ### Known advisories
346
+
347
+ `npm audit` reports one advisory in a fresh install; npm counts it as `3 low severity
348
+ vulnerabilities`, one for each package on the path (`katex`, `mermaid` and this package). We
349
+ assessed it, and it does not apply to the editor:
350
+
351
+ - **GHSA-238p-pmpm-9mq7** (low; `katex`, installed through `mermaid` 11.17.2): "KaTeX: Existing
352
+ prototype pollution can bypass trust restrictions". Exploiting it needs a prototype pollution
353
+ already present in the page *and* KaTeX output inserted without sanitising. The editor renders
354
+ math with Temml, not KaTeX. Mermaid runs with `htmlLabels: false`, so its labels are plain SVG
355
+ text. Every piece of markup the editor inserts goes through its one sanitiser, which refuses
356
+ anything outside its allow-list. The fix exists only in Mermaid 12, which adds a dependency
357
+ under a license this package cannot take (EPL-2.0 OR GPL-3.0). With the `no-diagrams` entry,
358
+ neither Mermaid nor KaTeX reaches your bundle. Next review: 2027-02-04.
359
+
360
+ The release check fails on any advisory that is not assessed here.
361
+
362
+ Content Security Policy is your page's job. CodeMirror injects `<style>` elements, so a
363
+ strict `style-src` needs a nonce through CodeMirror's `EditorView.cspNonce` (pass it with
364
+ `extensions`). That nonce is all the editor and math need. Mermaid sets inline styles while it
365
+ draws, so diagrams also need `style-src-attr` and `style-src-elem` to allow `'unsafe-inline'`,
366
+ or turn them off with `diagrams: false` or the `no-diagrams` entry
367
+ ([self-hosting](https://github.com/OpenMarkdown-dev/OpenMarkdown-editor/blob/main/docs/guides/self-hosting.md#diagrams-and-the-policy)). Report vulnerabilities
368
+ privately through [GitHub security advisories](https://github.com/OpenMarkdown-dev/OpenMarkdown-editor/security/advisories/new).
369
+
370
+ ## Browser support
371
+
372
+ Every build is tested by mounting, typing, rendering math and a diagram, and switching mode
373
+ and scheme in headless Chromium, WebKit (Safari's engine) and Firefox. The package is
374
+ ES2022 JavaScript. Mounting needs a DOM. Importing does not.
375
+
376
+ ## Size
377
+
378
+ Measured from the 0.1.0 build:
379
+
380
+ - The package's own code: about 78 KB gzipped (shipped unminified; your bundler minifies it).
381
+ - A Vite app with the editor, first load including CodeMirror: about 245 KB gzipped.
382
+ - Loaded only when a document needs them: Temml about 58 KB and Mermaid about 160 KB
383
+ for its core, gzipped. Mermaid's diagram types load as separate chunks.
384
+ - With the `no-diagrams` entry, first load is the same, and the whole build (every chunk
385
+ a page might ever load) drops from about 1.6 MB to about 680 KB gzipped: no Mermaid chunks.
386
+
387
+ ## License and trademark
388
+
389
+ The code is [MIT](https://github.com/OpenMarkdown-dev/OpenMarkdown-editor/blob/main/LICENSE). The MIT license covers the code, not the name: "OpenMarkdown"
390
+ and its logo are trademarks, and [TRADEMARK.md](https://github.com/OpenMarkdown-dev/OpenMarkdown-editor/blob/main/TRADEMARK.md) says what you may do with
391
+ them. Contributions require a [CLA](https://github.com/OpenMarkdown-dev/OpenMarkdown-editor/blob/main/CLA.md). See [CONTRIBUTING.md](https://github.com/OpenMarkdown-dev/OpenMarkdown-editor/blob/main/CONTRIBUTING.md).