edytor 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -4
- package/dist/block/block.svelte.js +2 -4
- package/dist/clipboard/htmlFlow.d.ts +20 -7
- package/dist/clipboard/htmlFlow.js +23 -15
- package/dist/clipboard/serializeClipboardFragment.d.ts +37 -4
- package/dist/clipboard/serializeClipboardFragment.js +6 -4
- package/dist/clipboard/textBlocks.d.ts +8 -0
- package/dist/clipboard/textBlocks.js +6 -2
- package/dist/cloudflare/room/admission.js +3 -3
- package/dist/cloudflare/room/context.d.ts +1 -1
- package/dist/cloudflare/routeDocumentSocket.d.ts +17 -0
- package/dist/cloudflare/routeDocumentSocket.js +53 -0
- package/dist/collaboration/comments/client.d.ts +1 -1
- package/dist/collaboration/comments/client.js +1 -1
- package/dist/collaboration/remoteSelection.js +3 -3
- package/dist/components/Edytor.svelte +2 -10
- package/dist/components/Edytor.svelte.d.ts +1 -3
- package/dist/crdt/admission.d.ts +5 -10
- package/dist/crdt/admission.js +5 -11
- package/dist/crdt/doc/funnel.d.ts +7 -6
- package/dist/crdt/doc/funnel.js +10 -9
- package/dist/crdt/doc/gate.d.ts +0 -17
- package/dist/crdt/doc/gate.js +2 -41
- package/dist/crdt/doc/history.js +2 -2
- package/dist/crdt/document.d.ts +5 -14
- package/dist/crdt/document.js +5 -19
- package/dist/crdt/edytor-doc.d.ts +4 -4
- package/dist/crdt/edytor-doc.js +3 -7
- package/dist/crdt/index.d.ts +3 -3
- package/dist/crdt/index.js +2 -2
- package/dist/crdt/protocol.d.ts +2 -2
- package/dist/crdt/protocol.js +2 -2
- package/dist/crdt/providers/index.d.ts +1 -1
- package/dist/crdt/providers/room.d.ts +10 -9
- package/dist/crdt/providers/room.js +13 -10
- package/dist/crdt/providers/websocket.d.ts +1 -1
- package/dist/crdt/providers/websocket.js +28 -29
- package/dist/crdt/semantics.js +3 -1
- package/dist/dnd/autoScroll.d.ts +15 -7
- package/dist/dnd/autoScroll.js +10 -15
- package/dist/dnd/dropIndicator.js +2 -5
- package/dist/dnd/external.d.ts +6 -0
- package/dist/dnd/external.js +17 -0
- package/dist/dnd/geometry.d.ts +2 -0
- package/dist/dnd/geometry.js +3 -4
- package/dist/dnd/placement.d.ts +1 -1
- package/dist/dnd/pragmatic.d.ts +3 -1
- package/dist/dnd/pragmatic.js +12 -2
- package/dist/dnd/vendor/pragmatic/UPSTREAM.md +51 -18
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/drop-target-for-external.d.ts +4 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/drop-target-for-external.js +6 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/element-adapter.d.ts +0 -2
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/element-adapter.js +4 -22
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter-types.d.ts +26 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter.d.ts +30 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/external-adapter.js +203 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/get-available-items.js +13 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/get-available-types.js +9 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/is-an-available-type.js +19 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/monitor-for-external.d.ts +4 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/adapter/monitor-for-external.js +6 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/honey-pot-fix/make-honey-pot-fix.js +4 -16
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/internal-types.d.ts +1 -8
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/make-adapter/make-drop-target.js +5 -6
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/make-adapter/make-monitor.js +2 -3
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/element/custom-native-drag-preview/set-custom-native-drag-preview.d.ts +1 -1
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/element/custom-native-drag-preview/set-custom-native-drag-preview.js +6 -24
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/contains-files.d.ts +2 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/contains-files.js +5 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/get-files.d.ts +3 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/get-files.js +12 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/public-utils/external/native-types.d.ts +12 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/is-entering-window-in-safari.js +10 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/changing-window/is-entering-window.js +55 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/entry-point/external.d.ts +4 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/entry-point/external.js +8 -0
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/internal-types.d.ts +0 -4
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/get-scroll-by.js +16 -72
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/over-element/try-scroll.js +1 -9
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/configuration.js +4 -29
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/get-scroll-change.js +2 -3
- package/dist/edytor.svelte.d.ts +2 -5
- package/dist/edytor.svelte.js +17 -31
- package/dist/events/onBeforeInput.js +1 -1
- package/dist/events/onDrop.d.ts +0 -6
- package/dist/events/onDrop.js +10 -23
- package/dist/events/onInput.d.ts +0 -12
- package/dist/events/onInput.js +1 -67
- package/dist/export/forms.d.ts +627 -0
- package/dist/export/forms.js +749 -0
- package/dist/export/index.d.ts +170 -0
- package/dist/export/index.js +161 -0
- package/dist/export/markdown.d.ts +41 -0
- package/dist/export/markdown.js +585 -0
- package/dist/export/markdownWriter.d.ts +9 -0
- package/dist/export/markdownWriter.js +133 -0
- package/dist/export/mdx.d.ts +15 -0
- package/dist/export/mdx.js +33 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +1 -1
- package/dist/kinds.js +1 -1
- package/dist/plugins/anchored.svelte.d.ts +21 -25
- package/dist/plugins/anchored.svelte.js +23 -39
- package/dist/plugins/blockHandles/BlockHandleController.svelte.js +24 -32
- package/dist/plugins/blockHandles/dragPreview.d.ts +26 -7
- package/dist/plugins/blockHandles/dragPreview.js +33 -0
- package/dist/plugins/blockMenu/BlockMenuController.svelte.d.ts +5 -0
- package/dist/plugins/blockMenu/BlockMenuController.svelte.js +7 -2
- package/dist/plugins/blockMenu/blockMenuPlugin.js +5 -50
- package/dist/plugins/chrome.d.ts +27 -5
- package/dist/plugins/chrome.js +24 -9
- package/dist/plugins/code/CodePlugin.svelte +12 -9
- package/dist/plugins/code/header.svelte.js +7 -11
- package/dist/plugins/code/languageMenu.svelte.d.ts +5 -2
- package/dist/plugins/code/languageMenu.svelte.js +13 -20
- package/dist/plugins/columns/ColumnsPlugin.svelte +7 -20
- package/dist/plugins/columns/resize.svelte.d.ts +2 -3
- package/dist/plugins/columns/resize.svelte.js +2 -5
- package/dist/plugins/comments/CommentsController.svelte.d.ts +6 -0
- package/dist/plugins/comments/CommentsController.svelte.js +20 -0
- package/dist/plugins/equation/EquationPlugin.svelte +12 -22
- package/dist/plugins/equation/equation.svelte.d.ts +7 -15
- package/dist/plugins/equation/equation.svelte.js +13 -50
- package/dist/plugins/equation/tex.d.ts +17 -0
- package/dist/plugins/equation/tex.js +42 -0
- package/dist/plugins/fileDrop.d.ts +38 -23
- package/dist/plugins/fileDrop.js +82 -76
- package/dist/plugins/find/FindController.svelte.d.ts +5 -0
- package/dist/plugins/find/FindController.svelte.js +14 -14
- package/dist/plugins/find/findPlugin.js +1 -15
- package/dist/plugins/image/ImageChrome.svelte +8 -2
- package/dist/plugins/image/ImagePlugin.svelte +15 -52
- package/dist/plugins/image/controls.svelte.js +16 -2
- package/dist/plugins/image/toolbar.svelte.d.ts +5 -4
- package/dist/plugins/image/toolbar.svelte.js +21 -31
- package/dist/plugins/index.d.ts +1 -1
- package/dist/plugins/list.svelte.d.ts +9 -0
- package/dist/plugins/list.svelte.js +18 -0
- package/dist/plugins/marquee/marqueePlugin.js +0 -4
- package/dist/plugins/media/AudioPlugin.svelte +1 -1
- package/dist/plugins/media/BookmarkPlugin.svelte +6 -97
- package/dist/plugins/media/EmbedPlugin.svelte +5 -90
- package/dist/plugins/media/EmptySlot.svelte +36 -20
- package/dist/plugins/media/EmptySlot.svelte.d.ts +6 -15
- package/dist/plugins/media/FilePlugin.svelte +1 -19
- package/dist/plugins/media/MediaEmpty.svelte +2 -55
- package/dist/plugins/media/MediaEmpty.svelte.d.ts +18 -14
- package/dist/plugins/media/SourceBlock.svelte +142 -52
- package/dist/plugins/media/SourceBlock.svelte.d.ts +10 -4
- package/dist/plugins/media/VideoPlugin.svelte +1 -1
- package/dist/plugins/media/empty.svelte.js +2 -7
- package/dist/plugins/media/media.d.ts +2 -63
- package/dist/plugins/media/media.js +1 -194
- package/dist/plugins/media/sources.d.ts +60 -0
- package/dist/plugins/media/sources.js +165 -0
- package/dist/plugins/media/urlPaste.svelte.d.ts +2 -1
- package/dist/plugins/media/urlPaste.svelte.js +12 -21
- package/dist/plugins/mention/MentionPlugin.svelte +8 -35
- package/dist/plugins/page/PagePlugin.svelte +2 -9
- package/dist/plugins/page/page.js +1 -1
- package/dist/plugins/pageLink/PageLinkPlugin.svelte +6 -13
- package/dist/plugins/richtext/RichTextPlugin.svelte +85 -197
- package/dist/plugins/richtext/body.js +1 -1
- package/dist/plugins/richtext/calloutIcons.svelte.d.ts +2 -2
- package/dist/plugins/richtext/calloutIcons.svelte.js +5 -7
- package/dist/plugins/richtext/richTextOperations.d.ts +1 -19
- package/dist/plugins/richtext/richTextOperations.js +3 -49
- package/dist/plugins/slashMenu/SlashMenuController.svelte.d.ts +2 -0
- package/dist/plugins/slashMenu/SlashMenuController.svelte.js +7 -0
- package/dist/plugins/slashMenu/slashMenuPlugin.js +5 -14
- package/dist/plugins/table/TableChrome.svelte +2 -3
- package/dist/plugins/table/TablePlugin.svelte +20 -54
- package/dist/plugins/table/chrome.svelte.d.ts +5 -2
- package/dist/plugins/table/chrome.svelte.js +30 -54
- package/dist/plugins/table/table.js +3 -3
- package/dist/plugins/toc/TocPlugin.svelte +2 -4
- package/dist/plugins/toolbar/ToolbarController.svelte.d.ts +7 -0
- package/dist/plugins/toolbar/ToolbarController.svelte.js +20 -13
- package/dist/plugins/toolbar/toolbarPlugin.js +19 -53
- package/dist/plugins/triggers/TriggerController.svelte.d.ts +7 -2
- package/dist/plugins/triggers/TriggerController.svelte.js +11 -4
- package/dist/plugins/triggers/place.js +1 -2
- package/dist/plugins/triggers/triggers.d.ts +6 -1
- package/dist/plugins/triggers/triggers.js +12 -13
- package/dist/plugins/uploads.svelte.d.ts +5 -2
- package/dist/plugins/uploads.svelte.js +25 -15
- package/dist/plugins.d.ts +26 -0
- package/dist/plugins.js +0 -24
- package/dist/selection/replaceSelection.js +2 -2
- package/dist/selection/selection.svelte.d.ts +4 -10
- package/dist/selection/selection.svelte.js +7 -11
- package/dist/session/bindings.js +1 -1
- package/dist/session/commands.d.ts +7 -11
- package/dist/session/commands.js +13 -19
- package/dist/session/inputRules.js +1 -1
- package/dist/session/navigation.js +1 -1
- package/dist/session/suggestions.svelte.d.ts +0 -11
- package/dist/session/suggestions.svelte.js +15 -3
- package/dist/session/uploads.d.ts +8 -8
- package/dist/surface/lines.js +1 -3
- package/dist/surface/observer.svelte.d.ts +17 -1
- package/dist/surface/observer.svelte.js +72 -17
- package/dist/surface/overlay.d.ts +0 -14
- package/dist/surface/overlay.js +0 -25
- package/dist/surface/projector.svelte.js +1 -1
- package/dist/text/text.svelte.js +3 -13
- package/dist/utils/sanitize.d.ts +38 -0
- package/dist/utils/sanitize.js +82 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +5 -0
- package/package.json +18 -1
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/max-z-index.js +0 -3
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop/util/supports-popover.js +0 -16
- package/dist/dnd/vendor/pragmatic/pragmatic-drag-and-drop-auto-scroll/shared/is-axis-allowed.js +0 -3
- package/dist/plugins/code/LanguageMenu.svelte +0 -25
- package/dist/plugins/code/LanguageMenu.svelte.d.ts +0 -9
- package/dist/plugins/equation/EquationEditor.svelte +0 -27
- package/dist/plugins/equation/EquationEditor.svelte.d.ts +0 -9
- package/dist/plugins/image/ImageToolbarHost.svelte +0 -37
- package/dist/plugins/image/ImageToolbarHost.svelte.d.ts +0 -9
- package/dist/plugins/richtext/CalloutIconPicker.svelte +0 -26
- package/dist/plugins/richtext/CalloutIconPicker.svelte.d.ts +0 -9
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A plain or markdown string as blocks — AI output, a suggestion's string
|
|
3
|
+
* content (`edytor.suggestions`), a markdown file. Pure: no view, no
|
|
4
|
+
* document, no DOM; Worker-safe.
|
|
5
|
+
*
|
|
6
|
+
* Plain (`markdown: false`): blank lines split paragraphs; a single newline
|
|
7
|
+
* stays in the text. Markdown (the default) is parsed by
|
|
8
|
+
* `@tanstack/markdown` (`parseMarkdown`, its serializable AST) and the AST
|
|
9
|
+
* mapped to blocks: headings (deeper than `###` read as `###`), bulleted,
|
|
10
|
+
* numbered and to-do items nested as the list nests, quotes (their first
|
|
11
|
+
* paragraph their text), fenced code (its language in `data.language`),
|
|
12
|
+
* GFM tables (a table whose first row is a header), dividers, a paragraph
|
|
13
|
+
* holding only an image an image, and paragraphs (a soft break a space, a
|
|
14
|
+
* hard break a newline). Inline: bold, italic, strike, code, links
|
|
15
|
+
* (`[text](url)`, `<https://…>`; an href through the link sanitizer: a
|
|
16
|
+
* `javascript:` URL is its text), nested. Raw HTML is never trusted: it
|
|
17
|
+
* stays text.
|
|
18
|
+
*
|
|
19
|
+
* Edytor's own syntax is a TanStack extension each, not a second parser:
|
|
20
|
+
* a GitHub alert (`> [!NOTE]`) is a callout (a leading emoji its icon),
|
|
21
|
+
* `<details>`/`<summary>` a toggle, `$$ … $$` an equation, an autolink a
|
|
22
|
+
* link, and, given `components`, an MDX-lite tag (`<Endpoint id="x" />`)
|
|
23
|
+
* a block of the kind it names.
|
|
24
|
+
*
|
|
25
|
+
* `streaming: true` reads a text that is still arriving (a model's answer
|
|
26
|
+
* so far): the last line, while it has no newline yet, is held back while
|
|
27
|
+
* it is only a block marker (`#`, `-`, `1.`, a table row, a tag being
|
|
28
|
+
* typed), and from an inline marker it has not closed yet (`**bol`); a
|
|
29
|
+
* table's header row waits for its separator row; then TanStack's streaming
|
|
30
|
+
* profile drops a trailing empty heading or list item. Each call then
|
|
31
|
+
* shows only what will not change kind or marks when more arrives.
|
|
32
|
+
*/
|
|
33
|
+
import { parseMarkdown } from '@tanstack/markdown/parser';
|
|
34
|
+
import { streamingMarkdownExtension } from '@tanstack/markdown/extensions/streaming';
|
|
35
|
+
import { sanitizeLinkHref } from '../utils/sanitize.js';
|
|
36
|
+
import { safeImageSrc } from '../plugins/image/image.js';
|
|
37
|
+
import { tableBlock } from '../crdt/tables.js';
|
|
38
|
+
import { headingLevel, languageOf } from './forms.js';
|
|
39
|
+
/** The data a component node carries, typed (a node's `attributes` are strings). */
|
|
40
|
+
const dataOf = new WeakMap();
|
|
41
|
+
/** The components of Edytor's own syntax: their names cannot be a tag's (`:`). */
|
|
42
|
+
const CALLOUT = 'edytor:callout';
|
|
43
|
+
const TOGGLE = 'edytor:toggle';
|
|
44
|
+
const EQUATION = 'edytor:equation';
|
|
45
|
+
const component = (name, data, children) => {
|
|
46
|
+
const node = {
|
|
47
|
+
type: 'component',
|
|
48
|
+
name,
|
|
49
|
+
attributes: Object.fromEntries(Object.entries(data).map(([k, v]) => [k, String(v)])),
|
|
50
|
+
children
|
|
51
|
+
};
|
|
52
|
+
dataOf.set(node, data);
|
|
53
|
+
return node;
|
|
54
|
+
};
|
|
55
|
+
/** The lines from `from` up to the closing line `close` matches (nesting by `open`), and where it is. */
|
|
56
|
+
const blockUntil = (lines, from, close, open) => {
|
|
57
|
+
let depth = 0;
|
|
58
|
+
for (let i = from; i < lines.length; i++) {
|
|
59
|
+
const line = lines[i];
|
|
60
|
+
if (close.test(line)) {
|
|
61
|
+
if (depth === 0)
|
|
62
|
+
return { body: lines.slice(from, i), end: i };
|
|
63
|
+
depth--;
|
|
64
|
+
}
|
|
65
|
+
else if (open?.test(line))
|
|
66
|
+
depth++;
|
|
67
|
+
}
|
|
68
|
+
return { body: lines.slice(from), end: -1 };
|
|
69
|
+
};
|
|
70
|
+
/** `<https://…>` and `<mailto:…>` autolinks (the parser keeps them as text). */
|
|
71
|
+
const autolinks = {
|
|
72
|
+
name: 'edytor-autolinks',
|
|
73
|
+
inlineParser: {
|
|
74
|
+
markers: '<',
|
|
75
|
+
parse(context) {
|
|
76
|
+
if (context.inLink)
|
|
77
|
+
return undefined;
|
|
78
|
+
const auto = /^<((?:https?|mailto):[^\s<>]+)>/i.exec(context.source.slice(context.index));
|
|
79
|
+
if (!auto)
|
|
80
|
+
return undefined;
|
|
81
|
+
return {
|
|
82
|
+
node: { type: 'link', href: auto[1], children: [{ type: 'text', value: auto[1] }] },
|
|
83
|
+
length: auto[0].length
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const ALERT = /^ {0,3}>\s*\[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\s*$/i;
|
|
89
|
+
const ICON = /^(\p{Extended_Pictographic}(?:️|\p{Extended_Pictographic}|\p{Emoji_Modifier})*)\s+/u;
|
|
90
|
+
/** A GitHub alert as a callout, a leading emoji on its first line its icon. */
|
|
91
|
+
const callouts = {
|
|
92
|
+
name: 'edytor-callouts',
|
|
93
|
+
parseBlock(context) {
|
|
94
|
+
if (!ALERT.test(context.lines[context.index] ?? ''))
|
|
95
|
+
return undefined;
|
|
96
|
+
const body = [];
|
|
97
|
+
let i = context.index + 1;
|
|
98
|
+
for (; i < context.lines.length; i++) {
|
|
99
|
+
const quoted = /^ {0,3}>\s?(.*)$/.exec(context.lines[i]);
|
|
100
|
+
if (!quoted)
|
|
101
|
+
break;
|
|
102
|
+
body.push(quoted[1]);
|
|
103
|
+
}
|
|
104
|
+
context.consume(i - context.index);
|
|
105
|
+
const icon = ICON.exec(body[0] ?? '');
|
|
106
|
+
if (icon)
|
|
107
|
+
body[0] = body[0].slice(icon[0].length);
|
|
108
|
+
return component(CALLOUT, icon ? { icon: icon[1] } : {}, context.parseBlocks(body.join('\n')));
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
/** A to-do box without its bullet (`[ ] task`) as a to-do item, as the editor's paste reads it. */
|
|
112
|
+
const bareTodos = {
|
|
113
|
+
name: 'edytor-bare-todos',
|
|
114
|
+
parseBlock(context) {
|
|
115
|
+
const todo = /^\[([ xX])\]\s+(.*)$/.exec(context.lines[context.index] ?? '');
|
|
116
|
+
if (!todo)
|
|
117
|
+
return undefined;
|
|
118
|
+
context.consume(1);
|
|
119
|
+
const children = [{ type: 'paragraph', children: context.parseInline(todo[2]) }];
|
|
120
|
+
return {
|
|
121
|
+
type: 'list',
|
|
122
|
+
ordered: false,
|
|
123
|
+
items: [{ type: 'listItem', checked: todo[1] !== ' ', children }]
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const DETAILS = /^\s*<details>\s*$/i;
|
|
128
|
+
const DETAILS_END = /^\s*<\/details>\s*$/i;
|
|
129
|
+
const SUMMARY = /^\s*<summary>(?:<(h[1-6])>)?(.*?)(?:<\/h[1-6]>)?<\/summary>\s*$/i;
|
|
130
|
+
/** `<details>` and its `<summary>` as a toggle (a `<summary><h2>` a toggle heading). */
|
|
131
|
+
const details = {
|
|
132
|
+
name: 'edytor-details',
|
|
133
|
+
parseBlock(context) {
|
|
134
|
+
if (!DETAILS.test(context.lines[context.index] ?? ''))
|
|
135
|
+
return undefined;
|
|
136
|
+
const { body, end } = blockUntil(context.lines, context.index + 1, DETAILS_END, DETAILS);
|
|
137
|
+
context.consume((end === -1 ? context.lines.length : end + 1) - context.index);
|
|
138
|
+
const first = body.findIndex((line) => line.trim());
|
|
139
|
+
const summary = first === -1 ? null : SUMMARY.exec(body[first]);
|
|
140
|
+
const level = summary?.[1]?.toLowerCase();
|
|
141
|
+
const data = level ? { level: headingLevel(level) } : {};
|
|
142
|
+
const head = summary
|
|
143
|
+
? [{ type: 'paragraph', children: context.parseInline(summary[2]) }]
|
|
144
|
+
: [];
|
|
145
|
+
if (summary)
|
|
146
|
+
body.splice(first, 1);
|
|
147
|
+
if (summary)
|
|
148
|
+
data.summary = true;
|
|
149
|
+
return component(TOGGLE, data, [...head, ...context.parseBlocks(body.join('\n'))]);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
/** `$$` … `$$` lines as an equation (an unclosed one runs to the end: a stream cut mid-block). */
|
|
153
|
+
const equations = {
|
|
154
|
+
name: 'edytor-equations',
|
|
155
|
+
parseBlock(context) {
|
|
156
|
+
if (!/^\s*\$\$\s*$/.test(context.lines[context.index] ?? ''))
|
|
157
|
+
return undefined;
|
|
158
|
+
const { body, end } = blockUntil(context.lines, context.index + 1, /^\s*\$\$\s*$/);
|
|
159
|
+
context.consume((end === -1 ? context.lines.length : end + 1) - context.index);
|
|
160
|
+
return component(EQUATION, { expression: body.join('\n').trim() }, []);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
/** The tag opening `line` (`<Name a="x" b={1} c>`), its literal attributes as data. */
|
|
164
|
+
const openingTag = (line) => {
|
|
165
|
+
const head = /^\s*<([A-Z][\w.]*)/.exec(line);
|
|
166
|
+
if (!head)
|
|
167
|
+
return null;
|
|
168
|
+
const name = head[1];
|
|
169
|
+
const data = {};
|
|
170
|
+
let i = head[0].length;
|
|
171
|
+
for (;;) {
|
|
172
|
+
const space = /^\s*/.exec(line.slice(i))[0];
|
|
173
|
+
i += space.length;
|
|
174
|
+
if (line.startsWith('/>', i))
|
|
175
|
+
return { name, data, selfClosing: true, rest: line.slice(i + 2).trim() };
|
|
176
|
+
if (line[i] === '>')
|
|
177
|
+
return { name, data, selfClosing: false, rest: line.slice(i + 1) };
|
|
178
|
+
const attr = space || i === head[0].length ? /^[A-Za-z_][\w:-]*/.exec(line.slice(i)) : null;
|
|
179
|
+
if (!attr)
|
|
180
|
+
return { name, issue: 'expression' };
|
|
181
|
+
i += attr[0].length;
|
|
182
|
+
if (line[i] !== '=') {
|
|
183
|
+
data[attr[0]] = true;
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
i++;
|
|
187
|
+
const quote = line[i];
|
|
188
|
+
if (quote === '"' || quote === "'") {
|
|
189
|
+
const close = line.indexOf(quote, i + 1);
|
|
190
|
+
if (close === -1)
|
|
191
|
+
return { name, issue: 'expression' };
|
|
192
|
+
data[attr[0]] = line.slice(i + 1, close);
|
|
193
|
+
i = close + 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const literal = /^\{\s*(true|false|-?\d+(?:\.\d+)?)\s*\}/.exec(line.slice(i));
|
|
197
|
+
if (!literal)
|
|
198
|
+
return { name, issue: 'expression' };
|
|
199
|
+
data[attr[0]] =
|
|
200
|
+
literal[1] === 'true' ? true : literal[1] === 'false' ? false : Number(literal[1]);
|
|
201
|
+
i += literal[0].length;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
205
|
+
/**
|
|
206
|
+
* MDX-lite: a tag `components` names as a component node; any other tag,
|
|
207
|
+
* an expression, an import or an export declines (the parser keeps the
|
|
208
|
+
* line as text) and is reported.
|
|
209
|
+
*/
|
|
210
|
+
const mdx = (components, report, streaming) => ({
|
|
211
|
+
name: 'edytor-mdx',
|
|
212
|
+
parseBlock(context) {
|
|
213
|
+
const line = context.lines[context.index] ?? '';
|
|
214
|
+
const statement = /^(import|export)\s/.exec(line);
|
|
215
|
+
if (statement) {
|
|
216
|
+
report({ reason: statement[1], source: line });
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
const tag = openingTag(line);
|
|
220
|
+
if (!tag)
|
|
221
|
+
return undefined;
|
|
222
|
+
if (!Object.hasOwn(components, tag.name)) {
|
|
223
|
+
report({ reason: 'component', name: tag.name, source: line });
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
if ('issue' in tag) {
|
|
227
|
+
report({ reason: tag.issue, name: tag.name, source: line });
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
const closing = `</${tag.name}>`;
|
|
231
|
+
if (tag.selfClosing || tag.rest.trimEnd().endsWith(closing)) {
|
|
232
|
+
if (tag.selfClosing && tag.rest)
|
|
233
|
+
return undefined;
|
|
234
|
+
context.consume(1);
|
|
235
|
+
const text = tag.selfClosing ? '' : tag.rest.trimEnd().slice(0, -closing.length).trim();
|
|
236
|
+
const children = text
|
|
237
|
+
? [{ type: 'paragraph', children: context.parseInline(text) }]
|
|
238
|
+
: [];
|
|
239
|
+
return component(tag.name, tag.data, children);
|
|
240
|
+
}
|
|
241
|
+
if (tag.rest.trim()) {
|
|
242
|
+
report({ reason: 'unclosed', name: tag.name, source: line });
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
const name = escapeRegExp(tag.name);
|
|
246
|
+
const { body, end } = blockUntil(context.lines, context.index + 1, new RegExp(`^\\s*</${name}>\\s*$`), new RegExp(`^\\s*<${name}(?:\\s[^>]*)?>\\s*$`));
|
|
247
|
+
if (end === -1 && !streaming) {
|
|
248
|
+
report({ reason: 'unclosed', name: tag.name, source: line });
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
context.consume((end === -1 ? context.lines.length : end + 1) - context.index);
|
|
252
|
+
return component(tag.name, tag.data, context.parseBlocks(body.join('\n')));
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
/** Inline nodes as text runs, `marks` on every run (nested marks add up), equal runs merged. */
|
|
256
|
+
const inline = (nodes, cell = false) => {
|
|
257
|
+
const out = [];
|
|
258
|
+
const push = (value, marks) => {
|
|
259
|
+
if (!value)
|
|
260
|
+
return;
|
|
261
|
+
const last = out.at(-1);
|
|
262
|
+
if (last && JSON.stringify(last.marks ?? {}) === JSON.stringify(marks))
|
|
263
|
+
last.text += value;
|
|
264
|
+
else
|
|
265
|
+
out.push(Object.keys(marks).length ? { text: value, marks } : { text: value });
|
|
266
|
+
};
|
|
267
|
+
const walk = (nodes, marks) => {
|
|
268
|
+
for (const node of nodes)
|
|
269
|
+
switch (node.type) {
|
|
270
|
+
case 'text': {
|
|
271
|
+
const text = node.value.replace(/\n/g, ' ');
|
|
272
|
+
push(cell ? text.replace(/<br\s*\/?>/gi, '\n') : text, marks);
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
case 'inlineCode':
|
|
276
|
+
push(node.value, { ...marks, code: true });
|
|
277
|
+
break;
|
|
278
|
+
case 'strong':
|
|
279
|
+
walk(node.children, { ...marks, bold: true });
|
|
280
|
+
break;
|
|
281
|
+
case 'emphasis':
|
|
282
|
+
walk(node.children, { ...marks, italic: true });
|
|
283
|
+
break;
|
|
284
|
+
case 'strike':
|
|
285
|
+
walk(node.children, { ...marks, strike: true });
|
|
286
|
+
break;
|
|
287
|
+
case 'link': {
|
|
288
|
+
const href = sanitizeLinkHref(node.href);
|
|
289
|
+
walk(node.children, href ? { ...marks, link: { href } } : marks);
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
case 'image':
|
|
293
|
+
push(node.alt, marks);
|
|
294
|
+
break;
|
|
295
|
+
case 'break':
|
|
296
|
+
push('\n', marks);
|
|
297
|
+
break;
|
|
298
|
+
case 'inlineHtml':
|
|
299
|
+
push(node.value, marks);
|
|
300
|
+
break;
|
|
301
|
+
case 'footnoteReference':
|
|
302
|
+
push(`[^${node.id}]`, marks);
|
|
303
|
+
break;
|
|
304
|
+
case 'inlineComponent':
|
|
305
|
+
walk(node.children, marks);
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
walk(nodes, {});
|
|
310
|
+
const last = out.at(-1);
|
|
311
|
+
if (last && !last.marks)
|
|
312
|
+
last.text = last.text.replace(/ +$/, '');
|
|
313
|
+
if (last && !last.text)
|
|
314
|
+
out.pop();
|
|
315
|
+
if (!out.length)
|
|
316
|
+
out.push({ text: '' });
|
|
317
|
+
return out;
|
|
318
|
+
};
|
|
319
|
+
/** A container's blocks as its own text (a leading paragraph's) and its children. */
|
|
320
|
+
const split = (blocks, reading) => {
|
|
321
|
+
const first = blocks[0]?.type === reading.type ? blocks.shift() : undefined;
|
|
322
|
+
return { content: first?.content ?? [], children: blocks };
|
|
323
|
+
};
|
|
324
|
+
const withChildren = (block, children) => children.length ? { ...block, children } : block;
|
|
325
|
+
/** AST blocks as Edytor blocks. */
|
|
326
|
+
const blocks = (nodes, reading) => nodes.flatMap((node) => block(node, reading));
|
|
327
|
+
const block = (node, reading) => {
|
|
328
|
+
switch (node.type) {
|
|
329
|
+
case 'heading':
|
|
330
|
+
return [
|
|
331
|
+
{
|
|
332
|
+
type: 'heading',
|
|
333
|
+
data: { level: `h${Math.min(3, node.depth)}` },
|
|
334
|
+
content: inline(node.children)
|
|
335
|
+
}
|
|
336
|
+
];
|
|
337
|
+
case 'paragraph': {
|
|
338
|
+
const only = node.children.filter((child) => child.type !== 'text' || child.value.trim());
|
|
339
|
+
const image = only.length === 1 && only[0].type === 'image' ? only[0] : null;
|
|
340
|
+
// A safe source, or a relative one (a markdown file's own `./cover.png`).
|
|
341
|
+
const src = image && (safeImageSrc(image.src) ?? (/^[^:]*$/.test(image.src) && image.src));
|
|
342
|
+
if (image && src)
|
|
343
|
+
return [
|
|
344
|
+
{
|
|
345
|
+
type: 'image',
|
|
346
|
+
data: { src, ...(image.alt && { alt: image.alt }) },
|
|
347
|
+
content: image.title ? [{ text: image.title }] : []
|
|
348
|
+
}
|
|
349
|
+
];
|
|
350
|
+
return [{ type: reading.type, content: inline(node.children) }];
|
|
351
|
+
}
|
|
352
|
+
case 'code': {
|
|
353
|
+
const language = languageOf(node.lang);
|
|
354
|
+
return [
|
|
355
|
+
{
|
|
356
|
+
type: 'code',
|
|
357
|
+
...(language && { data: { language } }),
|
|
358
|
+
content: [],
|
|
359
|
+
children: node.value
|
|
360
|
+
? node.value.split('\n').map((text) => ({ type: 'codeLine', content: [{ text }] }))
|
|
361
|
+
: []
|
|
362
|
+
}
|
|
363
|
+
];
|
|
364
|
+
}
|
|
365
|
+
case 'list':
|
|
366
|
+
return node.items.map((item) => {
|
|
367
|
+
const { content, children } = split(blocks(item.children, reading), reading);
|
|
368
|
+
const own = item.checked !== undefined
|
|
369
|
+
? { type: 'todo-item', data: { checked: item.checked }, content }
|
|
370
|
+
: { type: node.ordered ? 'numbered-list-item' : 'bulleted-list-item', content };
|
|
371
|
+
return withChildren(own, children);
|
|
372
|
+
});
|
|
373
|
+
case 'blockquote': {
|
|
374
|
+
const { content, children } = split(blocks(node.children, reading), reading);
|
|
375
|
+
return [withChildren({ type: 'quote', content }, children)];
|
|
376
|
+
}
|
|
377
|
+
case 'table': {
|
|
378
|
+
const cells = [node.header, ...node.rows].map((row) => row.map((cell) => {
|
|
379
|
+
const runs = inline(cell.children, true);
|
|
380
|
+
return runs.length === 1 && !runs[0].text ? '' : runs;
|
|
381
|
+
}));
|
|
382
|
+
return [tableBlock({ cells, headerRow: true })];
|
|
383
|
+
}
|
|
384
|
+
case 'thematicBreak':
|
|
385
|
+
return [{ type: 'divider', content: [] }];
|
|
386
|
+
case 'html':
|
|
387
|
+
return [{ type: reading.type, content: [{ text: node.value }] }];
|
|
388
|
+
case 'footnotes':
|
|
389
|
+
return node.items.flatMap((item) => {
|
|
390
|
+
const inner = blocks(item.children, reading);
|
|
391
|
+
const head = inner[0]?.type === reading.type ? inner.shift() : null;
|
|
392
|
+
const label = { text: `[^${item.id}]: ` };
|
|
393
|
+
return [{ type: reading.type, content: [label, ...(head?.content ?? [])] }, ...inner];
|
|
394
|
+
});
|
|
395
|
+
case 'callout':
|
|
396
|
+
return [withChildren({ type: 'callout', content: [] }, blocks(node.children, reading))];
|
|
397
|
+
case 'component': {
|
|
398
|
+
const data = dataOf.get(node) ?? node.attributes;
|
|
399
|
+
const inner = blocks(node.children, reading);
|
|
400
|
+
if (node.name === EQUATION)
|
|
401
|
+
return [{ type: 'equation', data, content: [] }];
|
|
402
|
+
if (node.name === TOGGLE) {
|
|
403
|
+
const { summary, ...rest } = data;
|
|
404
|
+
const head = summary ? inner.shift() : undefined;
|
|
405
|
+
const level = Object.keys(rest).length ? { data: rest } : {};
|
|
406
|
+
return [
|
|
407
|
+
withChildren({
|
|
408
|
+
type: rest.level ? 'toggle-heading' : 'toggle',
|
|
409
|
+
...level,
|
|
410
|
+
content: head?.content ?? []
|
|
411
|
+
}, inner)
|
|
412
|
+
];
|
|
413
|
+
}
|
|
414
|
+
const type = node.name === CALLOUT ? 'callout' : reading.components[node.name];
|
|
415
|
+
const { content, children } = split(inner, reading);
|
|
416
|
+
const own = Object.keys(data).length ? { type, data, content } : { type, content };
|
|
417
|
+
return [withChildren(own, children)];
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
// ——— Streaming: what of a text still arriving shows now ———
|
|
422
|
+
const isSpace = (c) => c === undefined || /\s/.test(c);
|
|
423
|
+
const isWord = (c) => c !== undefined && /[\p{L}\p{N}]/u.test(c);
|
|
424
|
+
/** The end of the code span opened by the backtick run at `at`, or -1. */
|
|
425
|
+
const codeEnd = (text, at, length) => {
|
|
426
|
+
for (let i = at + length; i < text.length;) {
|
|
427
|
+
if (text[i] !== '`') {
|
|
428
|
+
i++;
|
|
429
|
+
continue;
|
|
430
|
+
}
|
|
431
|
+
let j = i;
|
|
432
|
+
while (text[j] === '`')
|
|
433
|
+
j++;
|
|
434
|
+
if (j - i === length)
|
|
435
|
+
return i;
|
|
436
|
+
i = j;
|
|
437
|
+
}
|
|
438
|
+
return -1;
|
|
439
|
+
};
|
|
440
|
+
/**
|
|
441
|
+
* The `close` closing the `open` that `text` opens at `at` (a link label's
|
|
442
|
+
* `]`, its destination's `)`), skipping escapes and nested pairs; or -1.
|
|
443
|
+
*/
|
|
444
|
+
const closing = (text, at, open, close) => {
|
|
445
|
+
let depth = 0;
|
|
446
|
+
for (let i = at + 1; i < text.length; i++) {
|
|
447
|
+
const c = text[i];
|
|
448
|
+
if (c === '\\')
|
|
449
|
+
i++;
|
|
450
|
+
else if (c === open)
|
|
451
|
+
depth++;
|
|
452
|
+
else if (c === close && depth-- === 0)
|
|
453
|
+
return i;
|
|
454
|
+
}
|
|
455
|
+
return -1;
|
|
456
|
+
};
|
|
457
|
+
/** An inline marker the last line opened and has not closed: where it starts, else -1. */
|
|
458
|
+
const unclosedAt = (line) => {
|
|
459
|
+
const open = [];
|
|
460
|
+
for (let i = 0; i < line.length;) {
|
|
461
|
+
const c = line[i];
|
|
462
|
+
if (c === '\\') {
|
|
463
|
+
i += 2;
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
if (c === '`') {
|
|
467
|
+
let n = 0;
|
|
468
|
+
while (line[i + n] === '`')
|
|
469
|
+
n++;
|
|
470
|
+
const end = codeEnd(line, i, n);
|
|
471
|
+
if (end === -1)
|
|
472
|
+
return open[0]?.at ?? i;
|
|
473
|
+
i = end + n;
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
if (c === '[' || (c === '!' && line[i + 1] === '[')) {
|
|
477
|
+
const at = c === '!' ? i + 1 : i;
|
|
478
|
+
const end = closing(line, at, '[', ']');
|
|
479
|
+
if (end === -1)
|
|
480
|
+
return open[0]?.at ?? i;
|
|
481
|
+
if (line[end + 1] === '(') {
|
|
482
|
+
const to = closing(line, end + 1, '(', ')');
|
|
483
|
+
if (to === -1)
|
|
484
|
+
return open[0]?.at ?? i;
|
|
485
|
+
i = to + 1;
|
|
486
|
+
}
|
|
487
|
+
else
|
|
488
|
+
i = end + 1;
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (c === '*' || c === '_' || c === '~') {
|
|
492
|
+
let n = 0;
|
|
493
|
+
while (line[i + n] === c)
|
|
494
|
+
n++;
|
|
495
|
+
const top = open.at(-1);
|
|
496
|
+
if (top && top.char === c && !isSpace(line[i - 1])) {
|
|
497
|
+
if (n >= top.length)
|
|
498
|
+
open.pop();
|
|
499
|
+
}
|
|
500
|
+
else if (!isSpace(line[i + n]) &&
|
|
501
|
+
!(c === '_' && isWord(line[i - 1])) &&
|
|
502
|
+
(c !== '~' || n >= 2))
|
|
503
|
+
open.push({ char: c, length: n, at: i });
|
|
504
|
+
i += n;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
i++;
|
|
508
|
+
}
|
|
509
|
+
return open[0]?.at ?? -1;
|
|
510
|
+
};
|
|
511
|
+
const FENCE = /^(\s*)(`{3,}|~{3,})\s*([^`\s]*)[^`]*$/;
|
|
512
|
+
/** Whether `line` opens a fence, or closes the one open (`fenced`). */
|
|
513
|
+
const fences = (line, fenced) => FENCE.test(line) || (fenced && /^\s*(`{3,}|~{3,})\s*$/.test(line));
|
|
514
|
+
const TABLE_SEPARATOR = /^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/;
|
|
515
|
+
/**
|
|
516
|
+
* A line that is only a block marker so far: `#`, `-`, `1.`, `>`, a
|
|
517
|
+
* fence's backticks, a to-do box, a tag being typed (`<Endpoint id="a`).
|
|
518
|
+
*/
|
|
519
|
+
const MARKER_ONLY = /^\s*(?:#{1,6}|[-*+]|\d+[.)]?|>|`{1,2}|~{1,2}|\$\$?|(?:[-*+]\s+)?\[[ xX]?\]?|<\/?[A-Za-z]?[^>]*)\s*$/;
|
|
520
|
+
/** What of a text still arriving shows now (see `streaming`). */
|
|
521
|
+
const settled = (text) => {
|
|
522
|
+
const lines = text.split(/\r?\n/);
|
|
523
|
+
let partial = lines.pop() ?? '';
|
|
524
|
+
// Inside an open fence, everything is code: only a fence being typed waits.
|
|
525
|
+
let fenced = false;
|
|
526
|
+
for (const line of lines)
|
|
527
|
+
if (fences(line, fenced))
|
|
528
|
+
fenced = !fenced;
|
|
529
|
+
if (fenced)
|
|
530
|
+
return [...lines, /^\s*[`~]{1,}$/.test(partial) ? '' : partial].join('\n');
|
|
531
|
+
if (MARKER_ONLY.test(partial) || /^\s*\|/.test(partial))
|
|
532
|
+
partial = '';
|
|
533
|
+
else {
|
|
534
|
+
const at = unclosedAt(partial);
|
|
535
|
+
if (at !== -1)
|
|
536
|
+
partial = partial.slice(0, at);
|
|
537
|
+
}
|
|
538
|
+
// A table's header row waits for its separator row.
|
|
539
|
+
let end = lines.length;
|
|
540
|
+
while (end > 0 && lines[end - 1].includes('|') && lines[end - 1].trim())
|
|
541
|
+
end--;
|
|
542
|
+
const rows = lines.slice(end);
|
|
543
|
+
if (rows.length && !rows.some((line) => TABLE_SEPARATOR.test(line) && line.includes('-'))) {
|
|
544
|
+
lines.length = end;
|
|
545
|
+
partial = '';
|
|
546
|
+
}
|
|
547
|
+
return [...lines, partial].join('\n');
|
|
548
|
+
};
|
|
549
|
+
/**
|
|
550
|
+
* A line ending in two spaces as one ending in `\\` (both a hard break in
|
|
551
|
+
* CommonMark; the parser reads only the backslash), outside fenced code.
|
|
552
|
+
*/
|
|
553
|
+
const hardBreaks = (text) => {
|
|
554
|
+
let fenced = false;
|
|
555
|
+
return text
|
|
556
|
+
.split(/\r?\n/)
|
|
557
|
+
.map((line, i, lines) => {
|
|
558
|
+
if (fences(line, fenced))
|
|
559
|
+
fenced = !fenced;
|
|
560
|
+
return !fenced && /\S {2,}$/.test(line) && lines[i + 1]?.trim()
|
|
561
|
+
? `${line.trimEnd()}\\`
|
|
562
|
+
: line;
|
|
563
|
+
})
|
|
564
|
+
.join('\n');
|
|
565
|
+
};
|
|
566
|
+
const STREAMING = streamingMarkdownExtension();
|
|
567
|
+
const OWN = [autolinks, callouts, details, equations, bareTodos];
|
|
568
|
+
/** `text` as blocks (see the module comment). */
|
|
569
|
+
export const textToBlocks = (text, { markdown = true, type = 'paragraph', streaming = false, components, onUnknown } = {}) => {
|
|
570
|
+
if (!markdown)
|
|
571
|
+
return text
|
|
572
|
+
.split(/\r?\n[ \t]*(?:\r?\n[ \t]*)+/)
|
|
573
|
+
.filter((part) => part.trim())
|
|
574
|
+
.map((part) => ({ type, content: [{ text: part }] }));
|
|
575
|
+
const extensions = [...OWN];
|
|
576
|
+
if (components || onUnknown)
|
|
577
|
+
extensions.unshift(mdx(components ?? {}, onUnknown ?? (() => { }), streaming));
|
|
578
|
+
if (streaming)
|
|
579
|
+
extensions.push(STREAMING);
|
|
580
|
+
const document = parseMarkdown(hardBreaks(streaming ? settled(text) : text), {
|
|
581
|
+
extensions,
|
|
582
|
+
headingIds: false
|
|
583
|
+
});
|
|
584
|
+
return blocks(document.children, { type, components: components ?? {} });
|
|
585
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blocks as Markdown (`toMarkdown`): each block by its kind's `markdown`
|
|
3
|
+
* form (else its `plain` form, else its text), text by its marks' and
|
|
4
|
+
* atoms' forms. The export's alone: a view ships none of it.
|
|
5
|
+
*/
|
|
6
|
+
import type { JSONBlock } from '../utils/json.js';
|
|
7
|
+
import { type ExportKinds } from '../clipboard/serializeClipboardFragment.js';
|
|
8
|
+
/** Blocks as markdown (`toMarkdown`), by each kind's, mark's and atom's `markdown` form. */
|
|
9
|
+
export declare const blocksToMarkdown: (blocks: JSONBlock[], kinds: ExportKinds) => string;
|