@charcuterie/ui 3.13.0 → 3.14.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.
Files changed (31) hide show
  1. package/dist/markdownEditorCodeMirror/MarkdownView.d.ts +140 -0
  2. package/dist/markdownEditorCodeMirror/MarkdownView.d.ts.map +1 -0
  3. package/dist/markdownEditorCodeMirror/MarkdownView.js +268 -0
  4. package/dist/markdownEditorCodeMirror/MarkdownView.js.map +1 -0
  5. package/dist/markdownEditorCodeMirror/index.d.ts +14 -3
  6. package/dist/markdownEditorCodeMirror/index.d.ts.map +1 -1
  7. package/dist/markdownEditorCodeMirror/index.js +12 -3
  8. package/dist/markdownEditorCodeMirror/index.js.map +1 -1
  9. package/dist/markdownEditorCodeMirror/livePreview.d.ts +42 -2
  10. package/dist/markdownEditorCodeMirror/livePreview.d.ts.map +1 -1
  11. package/dist/markdownEditorCodeMirror/livePreview.js +243 -26
  12. package/dist/markdownEditorCodeMirror/livePreview.js.map +1 -1
  13. package/dist/markdownEditorCodeMirror/livePreviewRanges.d.ts.map +1 -1
  14. package/dist/markdownEditorCodeMirror/livePreviewRanges.js +49 -7
  15. package/dist/markdownEditorCodeMirror/livePreviewRanges.js.map +1 -1
  16. package/dist/markdownEditorCodeMirror/safeUrls.d.ts +46 -0
  17. package/dist/markdownEditorCodeMirror/safeUrls.d.ts.map +1 -0
  18. package/dist/markdownEditorCodeMirror/safeUrls.js +123 -0
  19. package/dist/markdownEditorCodeMirror/safeUrls.js.map +1 -0
  20. package/package.json +1 -1
  21. package/src/MarkdownEditor/MarkdownEditor.mdx +5 -0
  22. package/src/markdownEditorCodeMirror/MarkdownEditorCodeMirror.mdx +11 -0
  23. package/src/markdownEditorCodeMirror/MarkdownView.mdx +157 -0
  24. package/src/markdownEditorCodeMirror/MarkdownView.stories.tsx +314 -0
  25. package/src/markdownEditorCodeMirror/MarkdownView.test.tsx +288 -0
  26. package/src/markdownEditorCodeMirror/MarkdownView.tsx +373 -0
  27. package/src/markdownEditorCodeMirror/index.ts +17 -2
  28. package/src/markdownEditorCodeMirror/livePreview.ts +329 -33
  29. package/src/markdownEditorCodeMirror/livePreviewRanges.ts +64 -7
  30. package/src/markdownEditorCodeMirror/safeUrls.test.ts +0 -0
  31. package/src/markdownEditorCodeMirror/safeUrls.ts +142 -0
@@ -0,0 +1,140 @@
1
+ import type { ReactNode } from "react";
2
+ import type { SlotProps } from "../slotProps.ts";
3
+ export type MarkdownViewProps = SlotProps & {
4
+ className?: string;
5
+ /**
6
+ * The accessible name of the region, as `aria-label`.
7
+ *
8
+ * Worth passing on anything that is not the only thing on the
9
+ * page. A read view is announced as an article, and "article" with
10
+ * no name tells a reader stepping through the page nothing about
11
+ * which article it is.
12
+ */
13
+ label?: string;
14
+ /**
15
+ * Called when the reader ticks a `- [ ]` box, with the **whole**
16
+ * next markdown document — the same shape the editors' `onChange`
17
+ * hands back, so the same autosave wires to both.
18
+ *
19
+ * **Passing it is what makes the checkboxes operable at all.**
20
+ * Leave it out and they render disabled: the state is still on
21
+ * screen, and nothing pretends the reader can change it. That is
22
+ * deliberate rather than defensive. Two of the three surfaces this
23
+ * component was built for — a rendered comment, and a repo file
24
+ * fetched at a commit hash — have nowhere to put a tick, and a
25
+ * checkbox that visibly toggles and then loses the change on
26
+ * reload is worse than one that says up front it cannot.
27
+ *
28
+ * The view still applies the tick to its own document
29
+ * immediately, so the box responds under the pointer. If the
30
+ * consumer rejects the change, the next `value` it passes puts it
31
+ * back.
32
+ */
33
+ onToggleTask?: (nextValue: string) => void;
34
+ /**
35
+ * The markdown to render, and **controlled** — unlike either
36
+ * editor.
37
+ *
38
+ * The editors take `defaultValue` because reassigning their
39
+ * document from outside would throw away the undo history that
40
+ * belonged to it, which is a real cost and is why they ask for a
41
+ * `key` instead. A view has no undo history and no caret to
42
+ * protect, so it has nothing to lose by simply rendering what it
43
+ * is given: fetch a file at a different commit hash, pass the new
44
+ * string, and the page changes. No remount, no `key`, no stale
45
+ * document above a new title.
46
+ */
47
+ value: string;
48
+ };
49
+ /**
50
+ * Rendered markdown, with no toolbar and nothing to type into.
51
+ *
52
+ * ## Why this is a component and not `isReadOnly` on the editors
53
+ *
54
+ * Both editors already have `isReadOnly`, and it means *an editor
55
+ * you cannot type in*: the nine formatting buttons are still in the
56
+ * DOM, still queryable, still in the accessibility tree, drawn in
57
+ * their disabled colours above a document nobody is editing. That is
58
+ * the right shape for a form field that is temporarily locked. It is
59
+ * the wrong shape for reading a file, and a `hasToolbar={false}`
60
+ * prop would not fix it — the type would still carry
61
+ * `onUploadImage`, `icons`, `placeholder` and `rawModeLabel`, none
62
+ * of which mean anything to a reader, and "no toolbar" would be a
63
+ * branch that could be got wrong rather than a thing that does not
64
+ * exist.
65
+ *
66
+ * So this is its own component, the way `Listbox`, `Combobox` and
67
+ * `Picker` are their own components rather than three modes of one.
68
+ * The props are the four a reader needs, and there is no code path
69
+ * in this file that could render a toolbar.
70
+ *
71
+ * ## Why it is in the CodeMirror subpath
72
+ *
73
+ * Because the requirement is that it renders **the same** as the
74
+ * editor a consumer edits the same document in — and the only way
75
+ * to guarantee that is to be the same code, not to be a second
76
+ * implementation that agrees today.
77
+ *
78
+ * A `react-markdown` reader in the main barrel was the obvious
79
+ * alternative and is the one that goes wrong slowly: two parsers,
80
+ * two table implementations, two answers to what a bare URL does,
81
+ * and a bug report six months later that says "the table changed
82
+ * when I saved it". This shares `livePreviewRanges.ts` — the 1,200
83
+ * lines that decide what a document *looks* like — with
84
+ * `MarkdownEditorCodeMirror`, and the theme with it too. There is no
85
+ * second renderer to keep in step.
86
+ *
87
+ * The cost is that it lives behind the same optional peers, so a
88
+ * consumer who wants only the reader still installs CodeMirror. That
89
+ * is the honest trade and it is the right way round: an app that
90
+ * renders markdown in three places and edits it in one should pay
91
+ * once.
92
+ *
93
+ * ## What differs from the editor, and why each one differs
94
+ *
95
+ * | | editor | this |
96
+ * | --- | --- | --- |
97
+ * | Toolbar | nine buttons | **none in the DOM** |
98
+ * | Value | `defaultValue`, uncontrolled | `value`, **controlled** |
99
+ * | Content role | `textbox` | **`article`** |
100
+ * | Headings | scaled text | scaled **and `role="heading"`** |
101
+ * | Links | painted span, opened by a handler | **real `<a href>`** |
102
+ * | Markers | revealed by the caret | never revealed |
103
+ * | Task boxes | always operable | **inert unless `onToggleTask`** |
104
+ *
105
+ * Every one of those follows from there being no caret. The editor
106
+ * paints a span instead of an anchor because an `<a href>` inside a
107
+ * `contenteditable` competes with the caret for the line; a document
108
+ * has no caret, so the link is simply a link — tabbable, activated
109
+ * by Enter, and copyable from the context menu. The editor reveals
110
+ * markup when the selection touches it because you cannot edit a URL
111
+ * you cannot see; there is nothing here to edit.
112
+ *
113
+ * ## Hostile markdown
114
+ *
115
+ * This renders files fetched from a git host, so the document is
116
+ * untrusted by default, and the answer is mostly structural rather
117
+ * than a sanitiser:
118
+ *
119
+ * - **Raw HTML in the source is never parsed as HTML.** There is no
120
+ * `innerHTML` in this subpath, no `dangerouslySetInnerHTML`, and
121
+ * no sanitiser to have a bug in. CodeMirror's state holds the
122
+ * source as *text*, and every rendered node is `createElement`
123
+ * plus `textContent`. A `<script>` in a fetched README is drawn
124
+ * as the characters `<script>`. `MarkdownView.test.tsx` asserts
125
+ * it against the tag most likely to be tried.
126
+ * - **A URL is checked before it is used as one**, because that was
127
+ * the one place the document reached the platform:
128
+ * `[click](javascript:…)` used to paint as a link and run. Both
129
+ * components now go through `safeUrls.ts`, which allowlists
130
+ * schemes the way a browser reads them.
131
+ * - **The reader is not a beacon.** Remote images carry
132
+ * `referrerpolicy="no-referrer"`, so a fetched image cannot learn
133
+ * which page loaded it.
134
+ *
135
+ * What is *not* claimed: this does not stop a document from linking
136
+ * somewhere unpleasant, and it does not fetch anything on the
137
+ * reader's behalf beyond the images the markdown names.
138
+ */
139
+ export declare const MarkdownView: ({ className, label, onToggleTask, value, ...receivedSlotProps }: MarkdownViewProps) => ReactNode;
140
+ //# sourceMappingURL=MarkdownView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownView.d.ts","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/MarkdownView.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAOhD,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG;IAC1C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;;;;;;;;;;;;;;;;OAkBG;IACH,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1C;;;;;;;;;;;;OAYG;IACH,KAAK,EAAE,MAAM,CAAA;CACd,CAAA;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyFG;AACH,eAAO,MAAM,YAAY,GAAI,iEAM1B,iBAAiB,KAAG,SAsMtB,CAAA"}
@@ -0,0 +1,268 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useUniqueId } from "@charcuterie/logic";
3
+ import { markdown, markdownLanguage, } from "@codemirror/lang-markdown";
4
+ import { Annotation, Compartment, EditorState, } from "@codemirror/state";
5
+ import { EditorView } from "@codemirror/view";
6
+ import { useEffect, useRef } from "react";
7
+ import { toClassName } from "../toClassName.js";
8
+ import { livePreview, livePreviewOptions, } from "./livePreview.js";
9
+ /**
10
+ * Tells the update listener that a document change came from a new
11
+ * `value`, not from a reader.
12
+ *
13
+ * Without it, syncing the prop into the document fires
14
+ * `onToggleTask` with the string the consumer just passed in — a
15
+ * loop that looks like an autosave storm and is really an echo.
16
+ */
17
+ const isValueSync = Annotation.define();
18
+ /**
19
+ * Rendered markdown, with no toolbar and nothing to type into.
20
+ *
21
+ * ## Why this is a component and not `isReadOnly` on the editors
22
+ *
23
+ * Both editors already have `isReadOnly`, and it means *an editor
24
+ * you cannot type in*: the nine formatting buttons are still in the
25
+ * DOM, still queryable, still in the accessibility tree, drawn in
26
+ * their disabled colours above a document nobody is editing. That is
27
+ * the right shape for a form field that is temporarily locked. It is
28
+ * the wrong shape for reading a file, and a `hasToolbar={false}`
29
+ * prop would not fix it — the type would still carry
30
+ * `onUploadImage`, `icons`, `placeholder` and `rawModeLabel`, none
31
+ * of which mean anything to a reader, and "no toolbar" would be a
32
+ * branch that could be got wrong rather than a thing that does not
33
+ * exist.
34
+ *
35
+ * So this is its own component, the way `Listbox`, `Combobox` and
36
+ * `Picker` are their own components rather than three modes of one.
37
+ * The props are the four a reader needs, and there is no code path
38
+ * in this file that could render a toolbar.
39
+ *
40
+ * ## Why it is in the CodeMirror subpath
41
+ *
42
+ * Because the requirement is that it renders **the same** as the
43
+ * editor a consumer edits the same document in — and the only way
44
+ * to guarantee that is to be the same code, not to be a second
45
+ * implementation that agrees today.
46
+ *
47
+ * A `react-markdown` reader in the main barrel was the obvious
48
+ * alternative and is the one that goes wrong slowly: two parsers,
49
+ * two table implementations, two answers to what a bare URL does,
50
+ * and a bug report six months later that says "the table changed
51
+ * when I saved it". This shares `livePreviewRanges.ts` — the 1,200
52
+ * lines that decide what a document *looks* like — with
53
+ * `MarkdownEditorCodeMirror`, and the theme with it too. There is no
54
+ * second renderer to keep in step.
55
+ *
56
+ * The cost is that it lives behind the same optional peers, so a
57
+ * consumer who wants only the reader still installs CodeMirror. That
58
+ * is the honest trade and it is the right way round: an app that
59
+ * renders markdown in three places and edits it in one should pay
60
+ * once.
61
+ *
62
+ * ## What differs from the editor, and why each one differs
63
+ *
64
+ * | | editor | this |
65
+ * | --- | --- | --- |
66
+ * | Toolbar | nine buttons | **none in the DOM** |
67
+ * | Value | `defaultValue`, uncontrolled | `value`, **controlled** |
68
+ * | Content role | `textbox` | **`article`** |
69
+ * | Headings | scaled text | scaled **and `role="heading"`** |
70
+ * | Links | painted span, opened by a handler | **real `<a href>`** |
71
+ * | Markers | revealed by the caret | never revealed |
72
+ * | Task boxes | always operable | **inert unless `onToggleTask`** |
73
+ *
74
+ * Every one of those follows from there being no caret. The editor
75
+ * paints a span instead of an anchor because an `<a href>` inside a
76
+ * `contenteditable` competes with the caret for the line; a document
77
+ * has no caret, so the link is simply a link — tabbable, activated
78
+ * by Enter, and copyable from the context menu. The editor reveals
79
+ * markup when the selection touches it because you cannot edit a URL
80
+ * you cannot see; there is nothing here to edit.
81
+ *
82
+ * ## Hostile markdown
83
+ *
84
+ * This renders files fetched from a git host, so the document is
85
+ * untrusted by default, and the answer is mostly structural rather
86
+ * than a sanitiser:
87
+ *
88
+ * - **Raw HTML in the source is never parsed as HTML.** There is no
89
+ * `innerHTML` in this subpath, no `dangerouslySetInnerHTML`, and
90
+ * no sanitiser to have a bug in. CodeMirror's state holds the
91
+ * source as *text*, and every rendered node is `createElement`
92
+ * plus `textContent`. A `<script>` in a fetched README is drawn
93
+ * as the characters `<script>`. `MarkdownView.test.tsx` asserts
94
+ * it against the tag most likely to be tried.
95
+ * - **A URL is checked before it is used as one**, because that was
96
+ * the one place the document reached the platform:
97
+ * `[click](javascript:…)` used to paint as a link and run. Both
98
+ * components now go through `safeUrls.ts`, which allowlists
99
+ * schemes the way a browser reads them.
100
+ * - **The reader is not a beacon.** Remote images carry
101
+ * `referrerpolicy="no-referrer"`, so a fetched image cannot learn
102
+ * which page loaded it.
103
+ *
104
+ * What is *not* claimed: this does not stop a document from linking
105
+ * somewhere unpleasant, and it does not fetch anything on the
106
+ * reader's behalf beyond the images the markdown names.
107
+ */
108
+ export const MarkdownView = ({ className, label, onToggleTask, value, ...receivedSlotProps }) => {
109
+ const baseId = useUniqueId();
110
+ const hostRef = useRef(null);
111
+ const viewRef = useRef(null);
112
+ const optionsRef = useRef(new Compartment());
113
+ /**
114
+ * The handler reaches the view through a ref, never a closure.
115
+ *
116
+ * The `EditorView` is built once — rebuilding it on a new callback
117
+ * identity would drop the scroll position mid-read — so its
118
+ * extensions capture *this* object and the render loop keeps the
119
+ * object current.
120
+ */
121
+ const handlerRef = useRef(onToggleTask);
122
+ handlerRef.current = onToggleTask;
123
+ /**
124
+ * Frozen at mount, and named so rather than read from the render
125
+ * closure, because the build-once effect below is honestly
126
+ * dependency-free rather than lint-suppressed.
127
+ */
128
+ const mountRef = useRef({
129
+ ariaDescribedBy: receivedSlotProps["aria-describedby"],
130
+ id: receivedSlotProps.id ?? baseId,
131
+ label,
132
+ value,
133
+ });
134
+ useEffect(() => {
135
+ const host = hostRef.current;
136
+ if (!host) {
137
+ return;
138
+ }
139
+ const view = new EditorView({
140
+ parent: host,
141
+ state: EditorState.create({
142
+ doc: mountRef.current.value,
143
+ extensions: [
144
+ // GFM, not strict CommonMark — the same argument as the
145
+ // editor's: it is what autolinks a bare URL, renders a
146
+ // task list, and parses a table. A reader that disagreed
147
+ // with the editor about any of those would be the bug this
148
+ // component exists to avoid.
149
+ markdown({ base: markdownLanguage }),
150
+ EditorView.lineWrapping,
151
+ livePreview({ hasDocumentSemantics: true }),
152
+ /**
153
+ * The one answer that can change after mount, and only
154
+ * that answer.
155
+ *
156
+ * A `Compartment` holding the whole `livePreview()` would
157
+ * reconfigure the `StateField`s inside it — raw mode,
158
+ * focus, the table pass — which means destroying and
159
+ * recreating them to change a boolean about checkboxes.
160
+ * Compartmenting the facet alone reconfigures one value.
161
+ */
162
+ optionsRef.current.of(livePreviewOptions.of({
163
+ isTaskListInteractive: Boolean(handlerRef.current),
164
+ })),
165
+ /**
166
+ * `editable`, and deliberately **not**
167
+ * `EditorState.readOnly`.
168
+ *
169
+ * `editable` is the DOM half: it writes
170
+ * `contenteditable="false"`, which is what stops typing,
171
+ * pasting, dropping and IME, and what keeps the content
172
+ * out of the tab order. It is the whole of what this
173
+ * surface needs, because no keymap and no input handler is
174
+ * installed for `readOnly` to guard against.
175
+ *
176
+ * `readOnly` also makes CodeMirror write
177
+ * `aria-readonly="true"` onto the content element, and
178
+ * there is no way to unset an attribute it decides to
179
+ * write. On `role="article"` that attribute is one ARIA
180
+ * does not define, which axe reports and which is a fair
181
+ * report: "read-only" is a statement about a *field*, and
182
+ * this is not one. Nothing is lost by leaving it off, so it
183
+ * is left off rather than papered over.
184
+ */
185
+ EditorView.editable.of(false),
186
+ EditorView.contentAttributes.of({
187
+ /**
188
+ * `article`, not the `textbox` CodeMirror writes.
189
+ *
190
+ * This is the difference between reading a document and
191
+ * being trapped in a text field. A screen reader entering
192
+ * `role="textbox"` switches to forms mode and reads the
193
+ * contents as one flat string — no heading navigation, no
194
+ * table navigation, no list structure — which for an
195
+ * editor is exactly right and for a reader deletes every
196
+ * affordance the markup was rendered for.
197
+ */
198
+ role: "article",
199
+ /**
200
+ * CodeMirror writes `aria-multiline="true"` into the
201
+ * content element's attributes, and there is no way to
202
+ * unset one: its `updateAttrs` calls `setAttribute` with
203
+ * whatever it is handed, so `undefined` would arrive as
204
+ * the string "undefined".
205
+ *
206
+ * `false` is both the true answer — this is not a
207
+ * multiline textbox, because it is not a textbox — and
208
+ * the value axe explicitly ignores on an element carrying
209
+ * `contenteditable`. An attribute ARIA does not define
210
+ * for `article` is inert either way; this way the gate
211
+ * agrees.
212
+ */
213
+ "aria-multiline": "false",
214
+ ...(mountRef.current.label
215
+ ? { "aria-label": mountRef.current.label }
216
+ : {}),
217
+ ...(mountRef.current.ariaDescribedBy
218
+ ? {
219
+ "aria-describedby": mountRef.current.ariaDescribedBy,
220
+ }
221
+ : {}),
222
+ id: mountRef.current.id,
223
+ }),
224
+ EditorView.updateListener.of((update) => {
225
+ if (!update.docChanged ||
226
+ update.transactions.some((transaction) => transaction.annotation(isValueSync))) {
227
+ return;
228
+ }
229
+ // The only change a reader can make is a checkbox, so
230
+ // this is that checkbox and nothing else.
231
+ handlerRef.current?.(update.state.doc.toString());
232
+ }),
233
+ ],
234
+ }),
235
+ });
236
+ viewRef.current = view;
237
+ return () => {
238
+ view.destroy();
239
+ viewRef.current = null;
240
+ };
241
+ }, []);
242
+ useEffect(() => {
243
+ const view = viewRef.current;
244
+ if (!view || view.state.doc.toString() === value) {
245
+ return;
246
+ }
247
+ view.dispatch({
248
+ annotations: isValueSync.of(true),
249
+ changes: {
250
+ from: 0,
251
+ insert: value,
252
+ to: view.state.doc.length,
253
+ },
254
+ });
255
+ }, [value]);
256
+ // Through a compartment rather than a rebuild, so a consumer that
257
+ // turns ticking on and off — a task that becomes editable — keeps
258
+ // its scroll position.
259
+ useEffect(() => {
260
+ viewRef.current?.dispatch({
261
+ effects: optionsRef.current.reconfigure(livePreviewOptions.of({
262
+ isTaskListInteractive: Boolean(onToggleTask),
263
+ })),
264
+ });
265
+ }, [onToggleTask]);
266
+ return (_jsx("div", { className: toClassName("text-content-primary text-md", className), ref: hostRef }));
267
+ };
268
+ //# sourceMappingURL=MarkdownView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownView.js","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/MarkdownView.tsx"],"names":[],"mappings":"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"}
@@ -1,6 +1,13 @@
1
1
  /**
2
- * `@charcuterie/ui/markdown-editor-codemirror` — the opt-in live
3
- * preview surface.
2
+ * `@charcuterie/ui/markdown-editor-codemirror` — the opt-in
3
+ * CodeMirror markdown surface: the live-preview **editor**, and the
4
+ * read-only **view** that renders the same document the same way.
5
+ *
6
+ * Both are here rather than in two subpaths because there is one
7
+ * dependency, not two. A consumer who wants only `MarkdownView`
8
+ * still installs CodeMirror; splitting the entry point would suggest
9
+ * otherwise and would give the shared `livePreview` module two ways
10
+ * into the bundle.
4
11
  *
5
12
  * Its own entry point, and every CodeMirror package it needs is an
6
13
  * **optional** peer, so a consumer that never imports this subpath
@@ -8,9 +15,13 @@
8
15
  * `@charcuterie/ui/react-router`, and the same reason: the capacity
9
16
  * is worth having and is not worth charging everybody for.
10
17
  */
11
- export { livePreview, livePreviewRawModeField, setLivePreviewRawMode, } from "./livePreview.ts";
18
+ export type { LivePreviewOptions } from "./livePreview.ts";
19
+ export { livePreview, livePreviewOptions, livePreviewRawModeField, setLivePreviewRawMode, } from "./livePreview.ts";
12
20
  export type { LivePreviewLineKind, LivePreviewMarkKind, LivePreviewRange, LivePreviewSelection, ToLivePreviewRangesOptions, } from "./livePreviewRanges.ts";
13
21
  export { toLivePreviewRanges } from "./livePreviewRanges.ts";
14
22
  export type { MarkdownEditorCodeMirrorProps } from "./MarkdownEditorCodeMirror.tsx";
15
23
  export { MarkdownEditorCodeMirror } from "./MarkdownEditorCodeMirror.tsx";
24
+ export type { MarkdownViewProps } from "./MarkdownView.tsx";
25
+ export { MarkdownView } from "./MarkdownView.tsx";
26
+ export { toSafeImageUrl, toSafeLinkUrl, } from "./safeUrls.ts";
16
27
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,WAAW,EACX,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,YAAY,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAA;AACnF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,YAAY,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAC1D,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,kBAAkB,CAAA;AACzB,YAAY,EACV,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,0BAA0B,GAC3B,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,YAAY,EAAE,6BAA6B,EAAE,MAAM,gCAAgC,CAAA;AACnF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAA;AACzE,YAAY,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EACL,cAAc,EACd,aAAa,GACd,MAAM,eAAe,CAAA"}
@@ -1,6 +1,13 @@
1
1
  /**
2
- * `@charcuterie/ui/markdown-editor-codemirror` — the opt-in live
3
- * preview surface.
2
+ * `@charcuterie/ui/markdown-editor-codemirror` — the opt-in
3
+ * CodeMirror markdown surface: the live-preview **editor**, and the
4
+ * read-only **view** that renders the same document the same way.
5
+ *
6
+ * Both are here rather than in two subpaths because there is one
7
+ * dependency, not two. A consumer who wants only `MarkdownView`
8
+ * still installs CodeMirror; splitting the entry point would suggest
9
+ * otherwise and would give the shared `livePreview` module two ways
10
+ * into the bundle.
4
11
  *
5
12
  * Its own entry point, and every CodeMirror package it needs is an
6
13
  * **optional** peer, so a consumer that never imports this subpath
@@ -8,7 +15,9 @@
8
15
  * `@charcuterie/ui/react-router`, and the same reason: the capacity
9
16
  * is worth having and is not worth charging everybody for.
10
17
  */
11
- export { livePreview, livePreviewRawModeField, setLivePreviewRawMode, } from "./livePreview.js";
18
+ export { livePreview, livePreviewOptions, livePreviewRawModeField, setLivePreviewRawMode, } from "./livePreview.js";
12
19
  export { toLivePreviewRanges } from "./livePreviewRanges.js";
13
20
  export { MarkdownEditorCodeMirror } from "./MarkdownEditorCodeMirror.js";
21
+ export { MarkdownView } from "./MarkdownView.js";
22
+ export { toSafeImageUrl, toSafeLinkUrl, } from "./safeUrls.js";
14
23
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,WAAW,EACX,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,kBAAkB,CAAA;AAQzB,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAE5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAAgC,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAGH,OAAO,EACL,WAAW,EACX,kBAAkB,EAClB,uBAAuB,EACvB,qBAAqB,GACtB,MAAM,kBAAkB,CAAA;AAQzB,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAE5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAAgC,CAAA;AAEzE,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAoB,CAAA;AACjD,OAAO,EACL,cAAc,EACd,aAAa,GACd,MAAM,eAAe,CAAA"}
@@ -25,7 +25,47 @@
25
25
  * that resolves to nothing paints nothing, silently.
26
26
  */
27
27
  import type { Extension } from "@codemirror/state";
28
- import { StateField } from "@codemirror/state";
28
+ import { Facet, StateField } from "@codemirror/state";
29
+ /**
30
+ * What kind of surface this is, and what its checkboxes may do.
31
+ *
32
+ * A facet rather than closure state, for the same reason raw mode is
33
+ * a `StateField`: the decoration builders below are module-level
34
+ * singletons — one `ViewPlugin`, one `StateField` — and rebuilding
35
+ * them per instance would mean a second copy of the whole live
36
+ * preview for every surface on the page. They read the answer out of
37
+ * the state they were already handed.
38
+ *
39
+ * It is also what lets `MarkdownView` put the task flag in a
40
+ * `Compartment` and flip it without discarding the view, exactly as
41
+ * the editors already flip editability.
42
+ */
43
+ export type LivePreviewOptions = {
44
+ /**
45
+ * Draw a **document** rather than an editing surface: real
46
+ * anchors, heading roles, and a table that is a table instead of a
47
+ * way back into the markdown behind it.
48
+ *
49
+ * Off by default, because the editors are the surface this module
50
+ * was written for and every one of these would be wrong inside a
51
+ * `contenteditable`.
52
+ */
53
+ hasDocumentSemantics?: boolean;
54
+ /** Whether a `- [ ]` checkbox can be ticked. Defaults to `true`. */
55
+ isTaskListInteractive?: boolean;
56
+ };
57
+ /**
58
+ * The facet itself is exported so a consumer can put **only this**
59
+ * in a `Compartment` and flip one answer without reconfiguring the
60
+ * whole extension — which would tear down and recreate the
61
+ * `StateField`s beside it.
62
+ *
63
+ * A later input wins per key, and a key nobody stated keeps the
64
+ * default. That is a merge rather than "the last one wins outright",
65
+ * so an override that only cares about checkboxes cannot silently
66
+ * turn document semantics back off.
67
+ */
68
+ export declare const livePreviewOptions: Facet<LivePreviewOptions, Required<LivePreviewOptions>>;
29
69
  /** Toggle the "edit Markdown" mode. */
30
70
  export declare const setLivePreviewRawMode: import("@codemirror/state").StateEffectType<boolean>;
31
71
  /**
@@ -36,5 +76,5 @@ export declare const setLivePreviewRawMode: import("@codemirror/state").StateEff
36
76
  * without a round trip through React.
37
77
  */
38
78
  export declare const livePreviewRawModeField: StateField<boolean>;
39
- export declare const livePreview: () => Extension;
79
+ export declare const livePreview: (options?: LivePreviewOptions) => Extension;
40
80
  //# sourceMappingURL=livePreview.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"livePreview.d.ts","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/livePreview.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,KAAK,EAEV,SAAS,EAEV,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAGL,UAAU,EACX,MAAM,mBAAmB,CAAA;AAyC1B,uCAAuC;AACvC,eAAO,MAAM,qBAAqB,sDACH,CAAA;AAE/B;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,qBAchC,CAAA;AAg3BJ,eAAO,MAAM,WAAW,QAAO,SAa9B,CAAA"}
1
+ {"version":3,"file":"livePreview.d.ts","sourceRoot":"","sources":["../../src/markdownEditorCodeMirror/livePreview.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAGH,OAAO,KAAK,EAEV,SAAS,EAEV,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EACL,KAAK,EAGL,UAAU,EACX,MAAM,mBAAmB,CAAA;AAyC1B;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;;;;;;OAQG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,oEAAoE;IACpE,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,yDAmB7B,CAAA;AAOF,uCAAuC;AACvC,eAAO,MAAM,qBAAqB,sDACH,CAAA;AAE/B;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,qBAchC,CAAA;AAmkCJ,eAAO,MAAM,WAAW,GACtB,UAAS,kBAAuB,KAC/B,SA6BF,CAAA"}