@hachej/boring-ui-kit 0.1.110 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +255 -239
- package/THIRD_PARTY_NOTICES.md +85 -0
- package/dist/anchor-conformance.d.ts +78 -0
- package/dist/anchor-conformance.js +288 -0
- package/dist/canvas-document.d.ts +25 -0
- package/dist/canvas-document.js +141 -0
- package/dist/canvas-editor-native.d.ts +22 -0
- package/dist/canvas-editor-native.js +112 -0
- package/dist/canvas-editor.d.ts +6 -0
- package/dist/canvas-editor.js +13 -0
- package/dist/canvas-mounted.d.ts +41 -0
- package/dist/canvas-mounted.js +180 -0
- package/dist/canvas-proposals.d.ts +11 -0
- package/dist/canvas-proposals.js +45 -0
- package/dist/canvas-records.d.ts +3 -0
- package/dist/canvas-records.js +39 -0
- package/dist/canvas.d.ts +92 -0
- package/dist/canvas.js +264 -0
- package/dist/contracts.d.ts +125 -0
- package/dist/contracts.js +1 -0
- package/dist/experience-catalog.d.ts +142 -0
- package/dist/experience-catalog.js +25 -0
- package/dist/experience-compose.d.ts +83 -0
- package/dist/experience-compose.js +218 -0
- package/dist/experience-composition-schema.d.ts +29 -0
- package/dist/experience-composition-schema.js +22 -0
- package/dist/experience-document-viewer.d.ts +13 -0
- package/dist/experience-document-viewer.js +54 -0
- package/dist/experience-document.d.ts +64 -0
- package/dist/experience-document.js +234 -0
- package/dist/experience-region-text.d.ts +3 -0
- package/dist/experience-region-text.js +84 -0
- package/dist/experience-region-tree.d.ts +22 -0
- package/dist/experience-region-tree.js +50 -0
- package/dist/experience-regions.d.ts +9 -0
- package/dist/experience-regions.js +122 -0
- package/dist/experience-renderer.d.ts +16 -0
- package/dist/experience-renderer.js +37 -0
- package/dist/experience.d.ts +12 -0
- package/dist/experience.js +23 -0
- package/dist/file-tree-shadcn.d.ts +8 -0
- package/dist/file-tree-shadcn.js +14 -0
- package/dist/file-tree-view.d.ts +64 -0
- package/dist/file-tree-view.js +256 -0
- package/dist/file-tree.d.ts +57 -0
- package/dist/file-tree.js +204 -0
- package/dist/html-preview.d.ts +2 -0
- package/dist/html-preview.js +47 -0
- package/dist/html-viewer.d.ts +13 -0
- package/dist/html-viewer.js +69 -0
- package/dist/html.d.ts +21 -0
- package/dist/html.js +21 -0
- package/dist/index.d.ts +2 -472
- package/dist/index.js +1 -2671
- package/dist/markdown-editor.d.ts +28 -0
- package/dist/markdown-editor.js +230 -0
- package/dist/markdown-extensions.d.ts +16 -0
- package/dist/markdown-extensions.js +96 -0
- package/dist/markdown-icons.d.ts +244 -0
- package/dist/markdown-icons.js +32 -0
- package/dist/markdown-mounted.d.ts +63 -0
- package/dist/markdown-mounted.js +263 -0
- package/dist/markdown-parser.d.ts +4 -0
- package/dist/markdown-parser.js +33 -0
- package/dist/markdown-proposal-diff.d.ts +5 -0
- package/dist/markdown-proposal-diff.js +122 -0
- package/dist/markdown-safety.d.ts +21 -0
- package/dist/markdown-safety.js +135 -0
- package/dist/markdown.d.ts +86 -0
- package/dist/markdown.js +121 -0
- package/dist/native-chat.d.ts +150 -0
- package/dist/native-chat.js +376 -0
- package/dist/pi.d.ts +12 -0
- package/dist/pi.js +1 -0
- package/dist/public-text-buffer.d.ts +5 -0
- package/dist/public-text-buffer.js +1 -0
- package/dist/remote-chat.d.ts +76 -0
- package/dist/remote-chat.js +324 -0
- package/dist/resources.d.ts +34 -0
- package/dist/resources.js +1 -0
- package/dist/text-buffer-state.d.ts +24 -0
- package/dist/text-buffer-state.js +1 -0
- package/dist/text-buffer.d.ts +52 -0
- package/dist/text-buffer.js +315 -0
- package/dist/text-draft-controls.d.ts +9 -0
- package/dist/text-draft-controls.js +33 -0
- package/dist/text-draft-types.d.ts +111 -0
- package/dist/text-draft-types.js +1 -0
- package/dist/text-drafts.d.ts +29 -0
- package/dist/text-drafts.js +378 -0
- package/package.json +226 -58
- package/styles/file-tree.css +46 -0
- package/dist/styles.css +0 -2308
- package/dist/tokens.css +0 -155
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { MarkdownController } from './markdown.js';
|
|
2
|
+
import { type MarkdownImageResolver } from './markdown-extensions.js';
|
|
3
|
+
import { type MarkdownRichSafety } from './markdown-safety.js';
|
|
4
|
+
import { type MarkdownMountedTools } from './markdown-mounted.js';
|
|
5
|
+
export { checkMarkdownRichSafety } from './markdown-safety.js';
|
|
6
|
+
export type { MarkdownRichSafety } from './markdown-safety.js';
|
|
7
|
+
export type { MarkdownImageResolver } from './markdown-extensions.js';
|
|
8
|
+
export type { MarkdownMountedTools, MarkdownMountedSubject, MarkdownMountedSelection, MarkdownMountedInspection } from './markdown-mounted.js';
|
|
9
|
+
export interface MarkdownEditorProps {
|
|
10
|
+
readonly controller: MarkdownController;
|
|
11
|
+
readonly title?: string;
|
|
12
|
+
readonly initialMode?: 'rich' | 'source';
|
|
13
|
+
readonly placeholder?: string;
|
|
14
|
+
readonly className?: string;
|
|
15
|
+
readonly onMountedTools?: (tools: MarkdownMountedTools | null) => void;
|
|
16
|
+
/** `false` omits the built-in header (title, status, source toggle, Save) so a host bar such as the viewer frame supplies them. */
|
|
17
|
+
readonly header?: boolean;
|
|
18
|
+
/** Controlled mode. The editor still falls back to `source` for a document that rich editing cannot keep exactly. */
|
|
19
|
+
readonly mode?: 'rich' | 'source';
|
|
20
|
+
readonly onModeChange?: (mode: 'rich' | 'source') => void;
|
|
21
|
+
/** Reports whether the loaded document can be edited in rich mode without losing anything. */
|
|
22
|
+
readonly onRichSafety?: (safety: MarkdownRichSafety) => void;
|
|
23
|
+
/** Images never load unless this maps their address to a URL (inline `data:` images load without it). */
|
|
24
|
+
readonly resolveImage?: MarkdownImageResolver;
|
|
25
|
+
}
|
|
26
|
+
/** Borrows the controller. The host owns document selection, access and disposal. */
|
|
27
|
+
export declare function MarkdownEditor(props: MarkdownEditorProps): import("react").JSX.Element;
|
|
28
|
+
//# sourceMappingURL=markdown-editor.d.ts.map
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { TextDraftControls } from './text-draft-controls.js';
|
|
4
|
+
import { useDeferredValue, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
|
|
5
|
+
import { EditorContent, useEditor, useEditorState } from '@tiptap/react';
|
|
6
|
+
import { createMarkdownExtensions } from './markdown-extensions.js';
|
|
7
|
+
import { checkMarkdownRichSafety, describeUnsafe, finishRichMarkdown } from './markdown-safety.js';
|
|
8
|
+
import { MarkdownIcon } from './markdown-icons.js';
|
|
9
|
+
import { MarkdownProposalDiff } from './markdown-proposal-diff.js';
|
|
10
|
+
import { createMountedMarkdownTools } from './markdown-mounted.js';
|
|
11
|
+
export { checkMarkdownRichSafety } from './markdown-safety.js';
|
|
12
|
+
/** Borrows the controller. The host owns document selection, access and disposal. */
|
|
13
|
+
export function MarkdownEditor(props) {
|
|
14
|
+
const active = useRef(props.controller);
|
|
15
|
+
active.current = props.controller;
|
|
16
|
+
const [mount, setMount] = useState({ controller: props.controller, sequence: 0 });
|
|
17
|
+
if (mount.controller !== props.controller)
|
|
18
|
+
setMount({ controller: props.controller, sequence: mount.sequence + 1 });
|
|
19
|
+
return _jsx(MarkdownEditorSession, { ...props, activeController: active }, mount.sequence);
|
|
20
|
+
}
|
|
21
|
+
function MarkdownEditorSession({ controller, title = 'Document', initialMode = 'rich', placeholder = 'Start writing', className, onMountedTools, header = true, mode: controlledMode, onModeChange, onRichSafety, resolveImage, activeController: active }) {
|
|
22
|
+
const state = useSyncExternalStore(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
|
|
23
|
+
const projected = useRef(null);
|
|
24
|
+
const emitted = useRef(null);
|
|
25
|
+
const [ownMode, setOwnMode] = useState(initialMode);
|
|
26
|
+
const requestedMode = controlledMode ?? ownMode;
|
|
27
|
+
// Rich editing is only offered for a document the editor can keep exactly; anything else is edited as source.
|
|
28
|
+
const checked = useDeferredValue(state.text);
|
|
29
|
+
// The editor's own output is safe by construction; only text that arrived from elsewhere (or from the source box) is checked.
|
|
30
|
+
const ownText = state.text === emitted.current;
|
|
31
|
+
const safety = useMemo(() => ownText ? { safe: true } : checkMarkdownRichSafety(checked), [checked, ownText]);
|
|
32
|
+
const richOffered = safety.safe;
|
|
33
|
+
const mode = richOffered ? requestedMode : 'source';
|
|
34
|
+
const activeMode = useRef(mode);
|
|
35
|
+
activeMode.current = mode;
|
|
36
|
+
const images = useRef(resolveImage);
|
|
37
|
+
images.current = resolveImage;
|
|
38
|
+
useEffect(() => { onRichSafety?.(safety); }, [safety, onRichSafety]);
|
|
39
|
+
const [error, setError] = useState(null);
|
|
40
|
+
const interaction = useRef(false);
|
|
41
|
+
const sourceInput = useRef(null);
|
|
42
|
+
const richBookmark = useRef(null);
|
|
43
|
+
const sourceBookmark = useRef(null);
|
|
44
|
+
const focusAfterSwitch = useRef(false);
|
|
45
|
+
const markInteraction = () => {
|
|
46
|
+
interaction.current = true;
|
|
47
|
+
setTimeout(() => { interaction.current = false; }, 0);
|
|
48
|
+
};
|
|
49
|
+
const editor = useEditor({
|
|
50
|
+
immediatelyRender: false,
|
|
51
|
+
extensions: createMarkdownExtensions({ placeholder, images: () => images.current }),
|
|
52
|
+
content: '',
|
|
53
|
+
editable: !state.readOnly && state.lifecycle === 'active',
|
|
54
|
+
editorProps: { attributes: { role: 'textbox', 'aria-label': `${title} rich text`, 'aria-multiline': 'true', tabindex: '0', class: 'boring-markdown-content' } },
|
|
55
|
+
onUpdate: ({ editor: current, transaction }) => {
|
|
56
|
+
if (active.current !== controller || !interaction.current || !transaction.docChanged || controller.getSnapshot().lifecycle !== 'active' || controller.getSnapshot().readOnly)
|
|
57
|
+
return;
|
|
58
|
+
interaction.current = false;
|
|
59
|
+
const markdown = finishRichMarkdown(current.getMarkdown(), controller.getSnapshot().text);
|
|
60
|
+
projected.current = markdown;
|
|
61
|
+
emitted.current = markdown;
|
|
62
|
+
controller.actions.edit(markdown);
|
|
63
|
+
},
|
|
64
|
+
}, [controller]);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
projected.current = null;
|
|
67
|
+
emitted.current = null;
|
|
68
|
+
interaction.current = false;
|
|
69
|
+
setError(null);
|
|
70
|
+
}, [controller]);
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (!editor || editor.isDestroyed || state.text === projected.current)
|
|
73
|
+
return;
|
|
74
|
+
// Text from outside (a load, a refresh, a discard, the source box) starts with the selection at the beginning, where the browser leaves
|
|
75
|
+
// its caret when the text is replaced. Replacing the whole document would otherwise leave the editor's selection at the end while the
|
|
76
|
+
// caret shows the start: a click there changes nothing the editor sees, so formatting applied to the end, and ProseMirror's own sync
|
|
77
|
+
// after a focus or click put the caret back at the end.
|
|
78
|
+
editor.chain().setContent(state.text, { contentType: 'markdown', emitUpdate: false }).setTextSelection(0).run();
|
|
79
|
+
projected.current = state.text;
|
|
80
|
+
}, [editor, state.text]);
|
|
81
|
+
useEffect(() => { editor?.setEditable(!state.readOnly && state.lifecycle === 'active'); }, [editor, state.readOnly, state.lifecycle]);
|
|
82
|
+
const mounted = useMemo(() => {
|
|
83
|
+
if (mode === 'rich' && !editor)
|
|
84
|
+
return null;
|
|
85
|
+
const current = () => active.current === controller && activeMode.current === mode;
|
|
86
|
+
return mode === 'rich' && editor
|
|
87
|
+
? createMountedMarkdownTools({ controller, current, view: { kind: 'rich', editor, projectedText: () => projected.current } })
|
|
88
|
+
: createMountedMarkdownTools({ controller, current, view: { kind: 'source', element: () => sourceInput.current } });
|
|
89
|
+
}, [active, controller, editor, mode]);
|
|
90
|
+
useLayoutEffect(() => { mounted?.activate(); return () => mounted?.dispose(); }, [mounted]);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (!mounted || !onMountedTools)
|
|
93
|
+
return;
|
|
94
|
+
onMountedTools(mounted.tools);
|
|
95
|
+
return () => onMountedTools(null);
|
|
96
|
+
}, [mounted, onMountedTools]);
|
|
97
|
+
const marks = useEditorState({ editor, selector: ({ editor: current }) => current ? {
|
|
98
|
+
bold: current.isActive('bold'), italic: current.isActive('italic'), strike: current.isActive('strike'), code: current.isActive('code'), highlight: current.isActive('highlight'),
|
|
99
|
+
h1: current.isActive('heading', { level: 1 }), h2: current.isActive('heading', { level: 2 }), h3: current.isActive('heading', { level: 3 }),
|
|
100
|
+
bullet: current.isActive('bulletList'), ordered: current.isActive('orderedList'), task: current.isActive('taskList'),
|
|
101
|
+
quote: current.isActive('blockquote'), codeBlock: current.isActive('codeBlock'), link: current.isActive('link'), table: current.isActive('table'),
|
|
102
|
+
href: current.isActive('link') ? String(current.getAttributes('link')['href'] ?? '') : '',
|
|
103
|
+
} : null });
|
|
104
|
+
const outline = useEditorState({ editor, selector: ({ editor: current }) => {
|
|
105
|
+
if (!current)
|
|
106
|
+
return null;
|
|
107
|
+
const headings = [];
|
|
108
|
+
current.state.doc.descendants((node, position) => {
|
|
109
|
+
if (node.type.name === 'heading')
|
|
110
|
+
headings.push({ position, level: Number(node.attrs['level']), text: node.textContent });
|
|
111
|
+
});
|
|
112
|
+
return { document: current.state.doc, headings };
|
|
113
|
+
}, equalityFn: (left, right) => left?.document === right?.document });
|
|
114
|
+
const switchMode = () => {
|
|
115
|
+
const current = controller.getSnapshot();
|
|
116
|
+
if (mode === 'rich') {
|
|
117
|
+
richBookmark.current = editor && !editor.isDestroyed && projected.current === current.text
|
|
118
|
+
? { version: current.bufferVersion, selection: editor.state.selection.getBookmark() } : null;
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
const input = sourceInput.current;
|
|
122
|
+
sourceBookmark.current = input?.value === current.text
|
|
123
|
+
? { version: current.bufferVersion, start: input.selectionStart, end: input.selectionEnd, direction: input.selectionDirection } : null;
|
|
124
|
+
}
|
|
125
|
+
interaction.current = false;
|
|
126
|
+
focusAfterSwitch.current = true;
|
|
127
|
+
const next = mode === 'source' ? 'rich' : 'source';
|
|
128
|
+
if (controlledMode === undefined)
|
|
129
|
+
setOwnMode(next);
|
|
130
|
+
onModeChange?.(next);
|
|
131
|
+
};
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (!focusAfterSwitch.current || active.current !== controller || state.lifecycle !== 'active')
|
|
134
|
+
return;
|
|
135
|
+
if (mode === 'source') {
|
|
136
|
+
const input = sourceInput.current;
|
|
137
|
+
if (!input)
|
|
138
|
+
return;
|
|
139
|
+
const bookmark = sourceBookmark.current;
|
|
140
|
+
input.focus();
|
|
141
|
+
if (bookmark?.version === state.bufferVersion)
|
|
142
|
+
input.setSelectionRange(bookmark.start, bookmark.end, bookmark.direction);
|
|
143
|
+
else
|
|
144
|
+
input.setSelectionRange(0, 0);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
if (!editor || editor.isDestroyed)
|
|
148
|
+
return;
|
|
149
|
+
const bookmark = richBookmark.current;
|
|
150
|
+
if (bookmark?.version === state.bufferVersion)
|
|
151
|
+
editor.view.dispatch(editor.state.tr.setSelection(bookmark.selection.resolve(editor.state.doc)));
|
|
152
|
+
else
|
|
153
|
+
editor.commands.setTextSelection(0);
|
|
154
|
+
editor.commands.focus(undefined, { scrollIntoView: false });
|
|
155
|
+
}
|
|
156
|
+
focusAfterSwitch.current = false;
|
|
157
|
+
}, [active, controller, editor, mode, state.bufferVersion, state.lifecycle]);
|
|
158
|
+
const unavailable = state.readOnly || state.lifecycle !== 'active';
|
|
159
|
+
const pending = state.save.kind === 'pending';
|
|
160
|
+
const outcome = state.save.kind === 'settled' ? state.save.result : null;
|
|
161
|
+
const uncertain = outcome?.kind === 'unknown';
|
|
162
|
+
const conflict = outcome?.kind === 'conflict' || state.remote !== null;
|
|
163
|
+
const save = () => {
|
|
164
|
+
const selected = controller.actions.selection();
|
|
165
|
+
setError(null);
|
|
166
|
+
void controller.flush(selected).then(report, () => { if (active.current === controller)
|
|
167
|
+
setError('The save could not be completed. Your text is still available.'); });
|
|
168
|
+
};
|
|
169
|
+
/** Shows a refused save or accept; the buffer keeps the text. */
|
|
170
|
+
const report = (result) => {
|
|
171
|
+
if (active.current !== controller || !result || typeof result !== 'object')
|
|
172
|
+
return;
|
|
173
|
+
const { kind, reason } = result;
|
|
174
|
+
if (kind === 'denied' || kind === 'conflict' || kind === 'unavailable')
|
|
175
|
+
setError(typeof reason === 'string' && reason ? reason : kind === 'conflict' ? 'The document changed elsewhere. Your text is still available.' : 'The document could not be saved. Your text is still available.');
|
|
176
|
+
};
|
|
177
|
+
const run = (operation) => {
|
|
178
|
+
setError(null);
|
|
179
|
+
void operation().then(report, () => { if (active.current === controller)
|
|
180
|
+
setError('The operation could not be completed. Your text is still available.'); });
|
|
181
|
+
};
|
|
182
|
+
const richOnly = unavailable || mode === 'source' || !editor;
|
|
183
|
+
const [linkDraft, setLinkDraft] = useState(null);
|
|
184
|
+
const command = (operation) => { interaction.current = true; try {
|
|
185
|
+
operation();
|
|
186
|
+
}
|
|
187
|
+
finally {
|
|
188
|
+
interaction.current = false;
|
|
189
|
+
} };
|
|
190
|
+
const tool = (label, icon, pressed, operation, extra = {}) => (_jsx("button", { type: "button", className: "boring-markdown-tool", "aria-label": label, title: label, "aria-pressed": pressed === true, "data-active": pressed === true || undefined, disabled: richOnly || extra.disabled, onClick: () => command(operation), children: _jsx(MarkdownIcon, { name: icon }) }));
|
|
191
|
+
const separator = _jsx("span", { role: "separator", "aria-orientation": "vertical", className: "boring-markdown-separator" });
|
|
192
|
+
const chain = () => editor.chain().focus();
|
|
193
|
+
const openLink = () => setLinkDraft(marks?.href ?? '');
|
|
194
|
+
const applyLink = () => {
|
|
195
|
+
const href = (linkDraft ?? '').trim();
|
|
196
|
+
if (!editor || /^(?:javascript|data|vbscript):/i.test(href))
|
|
197
|
+
return;
|
|
198
|
+
command(() => href === '' ? chain().extendMarkRange('link').unsetLink().run() : chain().extendMarkRange('link').setLink({ href }).run());
|
|
199
|
+
setLinkDraft(null);
|
|
200
|
+
};
|
|
201
|
+
return _jsxs("section", { className: className, "data-boring": "markdown-editor", "data-mode": mode, "data-dirty": state.dirty || undefined, onKeyDownCapture: event => {
|
|
202
|
+
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 's') {
|
|
203
|
+
event.preventDefault();
|
|
204
|
+
if (!unavailable && !pending && !uncertain)
|
|
205
|
+
save();
|
|
206
|
+
}
|
|
207
|
+
}, children: [header && _jsxs("header", { children: [_jsx("strong", { children: title }), state.readOnly && _jsx("span", { children: "Read only" }), _jsx("span", { role: "status", children: state.lifecycle === 'disposed' ? 'Closed' : pending ? 'Saving' : uncertain ? 'Save unconfirmed' : conflict ? 'Changed elsewhere' : state.dirty ? 'Unsaved changes' : 'Saved' }), _jsx("button", { type: "button", "aria-pressed": mode === 'source', disabled: !richOffered, onClick: switchMode, children: "Markdown source" }), _jsx("button", { type: "button", disabled: unavailable || !state.dirty || pending || uncertain, onClick: save, children: "Save" })] }), _jsx(TextDraftControls, { recovery: state.recovery, actions: controller.actions, readOnly: state.readOnly, blocked: state.lifecycle !== 'active' || state.save.kind === 'pending' || (state.save.kind === 'settled' && state.save.result.kind === 'unknown') }), mode === 'rich' && _jsxs("div", { role: "toolbar", "aria-label": "Text formatting", className: "boring-markdown-toolbar", children: [tool('Bold', 'Bold', marks?.bold, () => chain().toggleBold().run()), tool('Italic', 'Italic', marks?.italic, () => chain().toggleItalic().run()), tool('Strikethrough', 'Strike', marks?.strike, () => chain().toggleStrike().run()), tool('Highlight', 'Highlight', marks?.highlight, () => chain().toggleHighlight().run()), tool('Code', 'Code', marks?.code, () => chain().toggleCode().run()), separator, tool('Heading 1', 'Heading1', marks?.h1, () => chain().toggleHeading({ level: 1 }).run()), tool('Heading 2', 'Heading2', marks?.h2, () => chain().toggleHeading({ level: 2 }).run()), tool('Heading 3', 'Heading3', marks?.h3, () => chain().toggleHeading({ level: 3 }).run()), separator, tool('Bullet list', 'BulletList', marks?.bullet, () => chain().toggleBulletList().run()), tool('Numbered list', 'OrderedList', marks?.ordered, () => chain().toggleOrderedList().run()), tool('Task list', 'TaskList', marks?.task, () => chain().toggleTaskList().run()), separator, tool('Quote', 'Quote', marks?.quote, () => chain().toggleBlockquote().run()), tool('Code block', 'CodeBlock', marks?.codeBlock, () => chain().toggleCodeBlock().run()), tool('Link', 'Link', marks?.link || linkDraft !== null, openLink), tool('Table', 'Table', marks?.table, () => marks?.table ? chain().deleteTable().run() : chain().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run()), tool('Horizontal rule', 'Rule', false, () => chain().setHorizontalRule().run()), marks?.table && _jsxs(_Fragment, { children: [separator, tool('Add table row', 'Rows', false, () => chain().addRowAfter().run()), tool('Add table column', 'Columns', false, () => chain().addColumnAfter().run()), tool('Delete table', 'Trash', false, () => chain().deleteTable().run())] })] }), mode === 'rich' && linkDraft !== null && _jsxs("form", { className: "boring-markdown-link", "aria-label": "Link address", onSubmit: event => { event.preventDefault(); applyLink(); }, children: [_jsx("input", { type: "text", inputMode: "url", autoFocus: true, "aria-label": "Link address", placeholder: "https://", value: linkDraft, onChange: event => setLinkDraft(event.currentTarget.value), onKeyDown: event => { if (event.key === 'Escape') {
|
|
208
|
+
event.preventDefault();
|
|
209
|
+
setLinkDraft(null);
|
|
210
|
+
editor?.commands.focus();
|
|
211
|
+
} } }), _jsx("button", { type: "submit", children: linkDraft.trim() === '' && marks?.link ? 'Remove link' : 'Apply link' }), _jsx("button", { type: "button", onClick: () => { setLinkDraft(null); editor?.commands.focus(); }, children: "Cancel" })] }), !richOffered && _jsx("p", { role: "note", className: "boring-markdown-notice", "data-testid": "markdown-source-only", children: describeUnsafe(safety) }), mode === 'rich' && outline && outline.headings.length > 0 && _jsx("nav", { "aria-label": "Document outline", children: _jsx("ol", { children: outline.headings.map((heading, index) => _jsx("li", { "data-heading-level": heading.level, children: _jsx("button", { type: "button", "aria-label": `Go to heading: ${heading.text || 'Untitled heading'}`, disabled: state.lifecycle !== 'active', onClick: () => {
|
|
212
|
+
if (!editor || editor.isDestroyed || active.current !== controller || controller.getSnapshot().lifecycle !== 'active'
|
|
213
|
+
|| controller.getSnapshot().bufferVersion !== state.bufferVersion || editor.state.doc !== outline.document)
|
|
214
|
+
return;
|
|
215
|
+
const node = editor.state.doc.nodeAt(heading.position);
|
|
216
|
+
if (node?.type.name !== 'heading' || node.textContent !== heading.text || node.attrs['level'] !== heading.level)
|
|
217
|
+
return;
|
|
218
|
+
const target = mounted?.tools.getTarget();
|
|
219
|
+
if (target)
|
|
220
|
+
void mounted?.tools.revealHeading.invoke(target, { index, expiresAt: Date.now() + 5000 }).then(result => {
|
|
221
|
+
if (active.current === controller && result.kind !== 'applied' && result.kind !== 'proposed')
|
|
222
|
+
setError(result.reason);
|
|
223
|
+
}).catch(() => { if (active.current === controller)
|
|
224
|
+
setError('The heading could not be selected'); });
|
|
225
|
+
}, children: heading.text || 'Untitled heading' }) }, heading.position)) }) }), uncertain && _jsxs("div", { role: "alert", className: "boring-markdown-alert", children: ["Save acknowledgement was lost. Keep editing while its outcome is checked.", _jsx("button", { type: "button", onClick: () => run(controller.actions.reconcile), children: "Check save outcome" }), _jsx("button", { type: "button", onClick: () => run(controller.actions.abandon), children: "Stop checking and refresh, keeping my draft" })] }), conflict && _jsxs("div", { role: "alert", className: "boring-markdown-alert", children: ["The saved document changed. Your local text has been kept.", _jsx("button", { type: "button", disabled: pending || uncertain, onClick: () => run(controller.actions.refresh), children: "Check saved version" }), _jsx("button", { type: "button", disabled: pending || uncertain, onClick: () => run(controller.actions.discardToRemote), children: "Discard local changes and reload" })] }), (outcome?.kind === 'denied' || outcome?.kind === 'unavailable') && _jsx("p", { role: "alert", className: "boring-markdown-alert", children: outcome.reason }), error && _jsx("p", { role: "alert", className: "boring-markdown-alert", children: error }), state.proposals.map(proposal => _jsxs("article", { "data-boring": "proposal", className: "boring-markdown-proposal", children: [_jsx("h3", { children: proposal.summary || 'Proposed edit' }), _jsxs("details", { children: [_jsx("summary", { children: "Review text changes" }), _jsx(MarkdownProposalDiff, { proposal: proposal }), _jsxs("details", { children: [_jsx("summary", { children: "Exact before and after" }), _jsx("h4", { children: "Before" }), _jsx("pre", { children: proposal.before }), _jsx("h4", { children: "After" }), _jsx("pre", { children: proposal.after })] })] }), _jsx("button", { type: "button", disabled: unavailable || pending || uncertain || proposal.adopted, onClick: () => run(() => controller.actions.accept(proposal.id)), children: proposal.adopted ? 'Adopted' : 'Accept and save' }), _jsx("button", { type: "button", onClick: () => controller.actions.reject(proposal.id), children: "Dismiss proposal" })] }, proposal.id)), mode === 'source'
|
|
226
|
+
? _jsx("textarea", { ref: sourceInput, className: "boring-markdown-source", "aria-label": `${title} Markdown source`, value: state.text, readOnly: unavailable, spellCheck: false, onChange: event => controller.actions.edit(event.currentTarget.value) })
|
|
227
|
+
: _jsx("div", { className: "boring-markdown-surface", onKeyDownCapture: markInteraction, onBeforeInputCapture: markInteraction, onInputCapture: markInteraction, onPasteCapture: markInteraction, onDropCapture: markInteraction, onChangeCapture: markInteraction, onClickCapture: event => { markInteraction(); if (event.target instanceof Element && event.target.closest('a'))
|
|
228
|
+
event.preventDefault(); }, onAuxClickCapture: event => { if (event.target instanceof Element && event.target.closest('a'))
|
|
229
|
+
event.preventDefault(); }, children: _jsx(EditorContent, { editor: editor }) })] });
|
|
230
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Extensions } from '@tiptap/core';
|
|
2
|
+
/** Maps an image address in the document to a URL the browser may load, or `undefined` to keep the image inert. */
|
|
3
|
+
export type MarkdownImageResolver = (source: string) => string | undefined;
|
|
4
|
+
/** Only an image carried inside the document itself loads by default. Anything else needs the host's resolver. */
|
|
5
|
+
export declare const inlineImagesOnly: MarkdownImageResolver;
|
|
6
|
+
export interface MarkdownExtensionOptions {
|
|
7
|
+
readonly placeholder?: string;
|
|
8
|
+
readonly images?: () => MarkdownImageResolver | undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The Markdown constructs the rich editor can keep exactly: StarterKit (headings, lists, quotes, code, rules, bold, italic,
|
|
12
|
+
* strike, links), task lists, GFM tables, images and highlight. Underline is off because Markdown has no underline.
|
|
13
|
+
* Images are inert: without a host resolver they render as a labelled placeholder and never make a request.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createMarkdownExtensions({ placeholder, images }?: MarkdownExtensionOptions): Extensions;
|
|
16
|
+
//# sourceMappingURL=markdown-extensions.d.ts.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
2
|
+
import { Markdown } from '@tiptap/markdown';
|
|
3
|
+
import { Placeholder } from '@tiptap/extensions';
|
|
4
|
+
import { TaskItem, TaskList } from '@tiptap/extension-list';
|
|
5
|
+
import { Table, TableCell, TableHeader, TableRow } from '@tiptap/extension-table';
|
|
6
|
+
import Image from '@tiptap/extension-image';
|
|
7
|
+
import Highlight from '@tiptap/extension-highlight';
|
|
8
|
+
import { createMarkdownParser } from './markdown-parser.js';
|
|
9
|
+
/**
|
|
10
|
+
* The serialiser HTML-encodes text (`&` becomes `&`), which rewrites ordinary prose such as "Notes & Highlights". The parser
|
|
11
|
+
* here reads text back as plain characters, so only the backslash escapes for Markdown syntax are kept.
|
|
12
|
+
*/
|
|
13
|
+
const PlainTextMarkdown = Markdown.extend({
|
|
14
|
+
onBeforeCreate() {
|
|
15
|
+
this.parent?.();
|
|
16
|
+
const manager = this.editor.markdown;
|
|
17
|
+
if (!manager)
|
|
18
|
+
return;
|
|
19
|
+
const encode = manager.encodeTextForMarkdown.bind(manager);
|
|
20
|
+
manager.encodeTextForMarkdown = (text, node, parent) => {
|
|
21
|
+
const encoded = encode(text, node, parent);
|
|
22
|
+
// Code is returned untouched by the original; for other text, replace its entity encoding by the plain characters.
|
|
23
|
+
return encoded === text ? text : manager.escapeMarkdownSyntax(text);
|
|
24
|
+
};
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* A GFM table keeps the exact text it was written in (unpadded `|a|b|`, `:---:` alignment rows, `_emphasis_`, escaped pipes, empty
|
|
29
|
+
* cells) while its content is unchanged: parsing records the source and the canonical JSON of the rows it produced, and rendering
|
|
30
|
+
* writes the source back when the rows are still identical. An edited table (a cell, a row, a column, an alignment) is rendered by
|
|
31
|
+
* the native serialiser, which writes padded GFM with the column alignments. The source is never rendered into the HTML, so a
|
|
32
|
+
* copied or pasted table is serialised normally.
|
|
33
|
+
*/
|
|
34
|
+
function createSourceTable(schema) {
|
|
35
|
+
return Table.extend({
|
|
36
|
+
addAttributes() {
|
|
37
|
+
return {
|
|
38
|
+
...this.parent?.(),
|
|
39
|
+
markdownSource: { default: null, rendered: false, parseHTML: () => null },
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
parseMarkdown(token, helpers) {
|
|
43
|
+
const parsed = this.parent(token, helpers);
|
|
44
|
+
const current = schema();
|
|
45
|
+
const raw = typeof token.raw === 'string' ? token.raw.replace(/\n+$/, '') : '';
|
|
46
|
+
if (!current || !raw || Array.isArray(parsed))
|
|
47
|
+
return parsed;
|
|
48
|
+
try {
|
|
49
|
+
const rows = JSON.stringify(current.nodeFromJSON({ type: 'table', content: parsed.content ?? [] }).toJSON().content ?? []);
|
|
50
|
+
return { ...parsed, attrs: { ...parsed.attrs, markdownSource: { raw, rows } } };
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
renderMarkdown(node, helpers, context) {
|
|
57
|
+
const source = node.attrs?.['markdownSource'];
|
|
58
|
+
if (source && typeof source.raw === 'string' && source.rows === JSON.stringify(node.content ?? []))
|
|
59
|
+
return `\n${source.raw}\n`;
|
|
60
|
+
return this.parent(node, helpers, context);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/** Only an image carried inside the document itself loads by default. Anything else needs the host's resolver. */
|
|
65
|
+
export const inlineImagesOnly = source => /^data:image\/(?:png|jpe?g|gif|webp);base64,[a-z0-9+/=]+$/i.test(source) ? source : undefined;
|
|
66
|
+
/**
|
|
67
|
+
* The Markdown constructs the rich editor can keep exactly: StarterKit (headings, lists, quotes, code, rules, bold, italic,
|
|
68
|
+
* strike, links), task lists, GFM tables, images and highlight. Underline is off because Markdown has no underline.
|
|
69
|
+
* Images are inert: without a host resolver they render as a labelled placeholder and never make a request.
|
|
70
|
+
*/
|
|
71
|
+
export function createMarkdownExtensions({ placeholder, images } = {}) {
|
|
72
|
+
const InertImage = Image.extend({
|
|
73
|
+
renderHTML({ node }) {
|
|
74
|
+
const source = String(node.attrs['src'] ?? '');
|
|
75
|
+
const alt = String(node.attrs['alt'] ?? '');
|
|
76
|
+
const title = node.attrs['title'] ? String(node.attrs['title']) : undefined;
|
|
77
|
+
const resolved = (images?.() ?? inlineImagesOnly)(source);
|
|
78
|
+
return resolved
|
|
79
|
+
? ['img', { src: resolved, alt, ...(title ? { title } : {}), 'data-boring-image': 'loaded' }]
|
|
80
|
+
: ['span', { class: 'boring-markdown-image', 'data-boring-image': 'inert', role: 'img', 'aria-label': alt || 'Image', ...(title ? { title } : {}) }, alt || 'Image'];
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
let schema;
|
|
84
|
+
const SourceTable = createSourceTable(() => schema).extend({
|
|
85
|
+
onBeforeCreate() { this.parent?.(); schema = this.editor.schema; },
|
|
86
|
+
});
|
|
87
|
+
return [
|
|
88
|
+
StarterKit.configure({ link: { openOnClick: false, autolink: false, linkOnPaste: false }, underline: false }),
|
|
89
|
+
TaskList, TaskItem.configure({ nested: true }),
|
|
90
|
+
SourceTable.configure({ resizable: false }), TableRow, TableHeader, TableCell,
|
|
91
|
+
InertImage.configure({ inline: false, allowBase64: true }),
|
|
92
|
+
Highlight,
|
|
93
|
+
Placeholder.configure({ placeholder: placeholder ?? 'Start writing' }),
|
|
94
|
+
PlainTextMarkdown.configure({ marked: createMarkdownParser(), markedOptions: { gfm: true, breaks: false } }),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
declare const ICONS: {
|
|
3
|
+
readonly Bold: readonly [readonly ["path", {
|
|
4
|
+
readonly d: "M6 12h9a4 4 0 0 1 0 8H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h7a4 4 0 0 1 0 8";
|
|
5
|
+
}]];
|
|
6
|
+
readonly Italic: readonly [readonly ["line", {
|
|
7
|
+
readonly x1: "19";
|
|
8
|
+
readonly x2: "10";
|
|
9
|
+
readonly y1: "4";
|
|
10
|
+
readonly y2: "4";
|
|
11
|
+
}], readonly ["line", {
|
|
12
|
+
readonly x1: "14";
|
|
13
|
+
readonly x2: "5";
|
|
14
|
+
readonly y1: "20";
|
|
15
|
+
readonly y2: "20";
|
|
16
|
+
}], readonly ["line", {
|
|
17
|
+
readonly x1: "15";
|
|
18
|
+
readonly x2: "9";
|
|
19
|
+
readonly y1: "4";
|
|
20
|
+
readonly y2: "20";
|
|
21
|
+
}]];
|
|
22
|
+
readonly Strike: readonly [readonly ["path", {
|
|
23
|
+
readonly d: "M16 4H9a3 3 0 0 0-2.83 4";
|
|
24
|
+
}], readonly ["path", {
|
|
25
|
+
readonly d: "M14 12a4 4 0 0 1 0 8H6";
|
|
26
|
+
}], readonly ["line", {
|
|
27
|
+
readonly x1: "4";
|
|
28
|
+
readonly x2: "20";
|
|
29
|
+
readonly y1: "12";
|
|
30
|
+
readonly y2: "12";
|
|
31
|
+
}]];
|
|
32
|
+
readonly Heading1: readonly [readonly ["path", {
|
|
33
|
+
readonly d: "M4 12h8";
|
|
34
|
+
}], readonly ["path", {
|
|
35
|
+
readonly d: "M4 18V6";
|
|
36
|
+
}], readonly ["path", {
|
|
37
|
+
readonly d: "M12 18V6";
|
|
38
|
+
}], readonly ["path", {
|
|
39
|
+
readonly d: "m17 12 3-2v8";
|
|
40
|
+
}]];
|
|
41
|
+
readonly Heading2: readonly [readonly ["path", {
|
|
42
|
+
readonly d: "M4 12h8";
|
|
43
|
+
}], readonly ["path", {
|
|
44
|
+
readonly d: "M4 18V6";
|
|
45
|
+
}], readonly ["path", {
|
|
46
|
+
readonly d: "M12 18V6";
|
|
47
|
+
}], readonly ["path", {
|
|
48
|
+
readonly d: "M21 18h-4c0-4 4-3 4-6 0-1.5-2-2.5-4-1";
|
|
49
|
+
}]];
|
|
50
|
+
readonly Heading3: readonly [readonly ["path", {
|
|
51
|
+
readonly d: "M4 12h8";
|
|
52
|
+
}], readonly ["path", {
|
|
53
|
+
readonly d: "M4 18V6";
|
|
54
|
+
}], readonly ["path", {
|
|
55
|
+
readonly d: "M12 18V6";
|
|
56
|
+
}], readonly ["path", {
|
|
57
|
+
readonly d: "M17.5 10.5c1.7-1 3.5 0 3.5 1.5a2 2 0 0 1-2 2";
|
|
58
|
+
}], readonly ["path", {
|
|
59
|
+
readonly d: "M17 17.5c2 1.5 4 .3 4-1.5a2 2 0 0 0-2-2";
|
|
60
|
+
}]];
|
|
61
|
+
readonly BulletList: readonly [readonly ["path", {
|
|
62
|
+
readonly d: "M3 5h.01";
|
|
63
|
+
}], readonly ["path", {
|
|
64
|
+
readonly d: "M3 12h.01";
|
|
65
|
+
}], readonly ["path", {
|
|
66
|
+
readonly d: "M3 19h.01";
|
|
67
|
+
}], readonly ["path", {
|
|
68
|
+
readonly d: "M8 5h13";
|
|
69
|
+
}], readonly ["path", {
|
|
70
|
+
readonly d: "M8 12h13";
|
|
71
|
+
}], readonly ["path", {
|
|
72
|
+
readonly d: "M8 19h13";
|
|
73
|
+
}]];
|
|
74
|
+
readonly OrderedList: readonly [readonly ["path", {
|
|
75
|
+
readonly d: "M11 5h10";
|
|
76
|
+
}], readonly ["path", {
|
|
77
|
+
readonly d: "M11 12h10";
|
|
78
|
+
}], readonly ["path", {
|
|
79
|
+
readonly d: "M11 19h10";
|
|
80
|
+
}], readonly ["path", {
|
|
81
|
+
readonly d: "M4 4h1v5";
|
|
82
|
+
}], readonly ["path", {
|
|
83
|
+
readonly d: "M4 9h2";
|
|
84
|
+
}], readonly ["path", {
|
|
85
|
+
readonly d: "M6.5 20H3.4c0-1 2.6-1.925 2.6-3.5a1.5 1.5 0 0 0-2.6-1.02";
|
|
86
|
+
}]];
|
|
87
|
+
readonly TaskList: readonly [readonly ["path", {
|
|
88
|
+
readonly d: "M13 5h8";
|
|
89
|
+
}], readonly ["path", {
|
|
90
|
+
readonly d: "M13 12h8";
|
|
91
|
+
}], readonly ["path", {
|
|
92
|
+
readonly d: "M13 19h8";
|
|
93
|
+
}], readonly ["path", {
|
|
94
|
+
readonly d: "m3 17 2 2 4-4";
|
|
95
|
+
}], readonly ["rect", {
|
|
96
|
+
readonly x: "3";
|
|
97
|
+
readonly y: "4";
|
|
98
|
+
readonly width: "6";
|
|
99
|
+
readonly height: "6";
|
|
100
|
+
readonly rx: "1";
|
|
101
|
+
}]];
|
|
102
|
+
readonly Quote: readonly [readonly ["path", {
|
|
103
|
+
readonly d: "M16 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z";
|
|
104
|
+
}], readonly ["path", {
|
|
105
|
+
readonly d: "M5 3a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2 1 1 0 0 1 1 1v1a2 2 0 0 1-2 2 1 1 0 0 0-1 1v2a1 1 0 0 0 1 1 6 6 0 0 0 6-6V5a2 2 0 0 0-2-2z";
|
|
106
|
+
}]];
|
|
107
|
+
readonly Code: readonly [readonly ["path", {
|
|
108
|
+
readonly d: "m16 18 6-6-6-6";
|
|
109
|
+
}], readonly ["path", {
|
|
110
|
+
readonly d: "m8 6-6 6 6 6";
|
|
111
|
+
}]];
|
|
112
|
+
readonly CodeBlock: readonly [readonly ["path", {
|
|
113
|
+
readonly d: "m10 9-3 3 3 3";
|
|
114
|
+
}], readonly ["path", {
|
|
115
|
+
readonly d: "m14 15 3-3-3-3";
|
|
116
|
+
}], readonly ["rect", {
|
|
117
|
+
readonly x: "3";
|
|
118
|
+
readonly y: "3";
|
|
119
|
+
readonly width: "18";
|
|
120
|
+
readonly height: "18";
|
|
121
|
+
readonly rx: "2";
|
|
122
|
+
}]];
|
|
123
|
+
readonly Link: readonly [readonly ["path", {
|
|
124
|
+
readonly d: "M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71";
|
|
125
|
+
}], readonly ["path", {
|
|
126
|
+
readonly d: "M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71";
|
|
127
|
+
}]];
|
|
128
|
+
readonly Unlink: readonly [readonly ["path", {
|
|
129
|
+
readonly d: "m18.84 12.25 1.72-1.71h-.02a5.004 5.004 0 0 0-.12-7.07 5.006 5.006 0 0 0-6.95 0l-1.72 1.71";
|
|
130
|
+
}], readonly ["path", {
|
|
131
|
+
readonly d: "m5.17 11.75-1.71 1.71a5.004 5.004 0 0 0 .12 7.07 5.006 5.006 0 0 0 6.95 0l1.71-1.71";
|
|
132
|
+
}], readonly ["line", {
|
|
133
|
+
readonly x1: "8";
|
|
134
|
+
readonly x2: "8";
|
|
135
|
+
readonly y1: "2";
|
|
136
|
+
readonly y2: "5";
|
|
137
|
+
}], readonly ["line", {
|
|
138
|
+
readonly x1: "2";
|
|
139
|
+
readonly x2: "5";
|
|
140
|
+
readonly y1: "8";
|
|
141
|
+
readonly y2: "8";
|
|
142
|
+
}], readonly ["line", {
|
|
143
|
+
readonly x1: "16";
|
|
144
|
+
readonly x2: "16";
|
|
145
|
+
readonly y1: "19";
|
|
146
|
+
readonly y2: "22";
|
|
147
|
+
}], readonly ["line", {
|
|
148
|
+
readonly x1: "19";
|
|
149
|
+
readonly x2: "22";
|
|
150
|
+
readonly y1: "16";
|
|
151
|
+
readonly y2: "16";
|
|
152
|
+
}]];
|
|
153
|
+
readonly Highlight: readonly [readonly ["path", {
|
|
154
|
+
readonly d: "m9 11-6 6v3h9l3-3";
|
|
155
|
+
}], readonly ["path", {
|
|
156
|
+
readonly d: "m22 12-4.6 4.6a2 2 0 0 1-2.8 0l-5.2-5.2a2 2 0 0 1 0-2.8L14 4";
|
|
157
|
+
}]];
|
|
158
|
+
readonly Rule: readonly [readonly ["path", {
|
|
159
|
+
readonly d: "M5 12h14";
|
|
160
|
+
}]];
|
|
161
|
+
readonly Table: readonly [readonly ["path", {
|
|
162
|
+
readonly d: "M3 9h18";
|
|
163
|
+
}], readonly ["path", {
|
|
164
|
+
readonly d: "M9 3v18";
|
|
165
|
+
}], readonly ["rect", {
|
|
166
|
+
readonly x: "3";
|
|
167
|
+
readonly y: "3";
|
|
168
|
+
readonly width: "18";
|
|
169
|
+
readonly height: "18";
|
|
170
|
+
readonly rx: "2";
|
|
171
|
+
}]];
|
|
172
|
+
readonly Rows: readonly [readonly ["path", {
|
|
173
|
+
readonly d: "M14 10h2";
|
|
174
|
+
}], readonly ["path", {
|
|
175
|
+
readonly d: "M15 22v-8";
|
|
176
|
+
}], readonly ["path", {
|
|
177
|
+
readonly d: "M15 2v4";
|
|
178
|
+
}], readonly ["path", {
|
|
179
|
+
readonly d: "M2 10h2";
|
|
180
|
+
}], readonly ["path", {
|
|
181
|
+
readonly d: "M20 10h2";
|
|
182
|
+
}], readonly ["path", {
|
|
183
|
+
readonly d: "M3 19h18";
|
|
184
|
+
}], readonly ["path", {
|
|
185
|
+
readonly d: "M3 22v-6a2 2 135 0 1 2-2h14a2 2 45 0 1 2 2v6";
|
|
186
|
+
}], readonly ["path", {
|
|
187
|
+
readonly d: "M3 2v2a2 2 45 0 0 2 2h14a2 2 135 0 0 2-2V2";
|
|
188
|
+
}], readonly ["path", {
|
|
189
|
+
readonly d: "M8 10h2";
|
|
190
|
+
}], readonly ["path", {
|
|
191
|
+
readonly d: "M9 22v-8";
|
|
192
|
+
}], readonly ["path", {
|
|
193
|
+
readonly d: "M9 2v4";
|
|
194
|
+
}]];
|
|
195
|
+
readonly Columns: readonly [readonly ["path", {
|
|
196
|
+
readonly d: "M14 14v2";
|
|
197
|
+
}], readonly ["path", {
|
|
198
|
+
readonly d: "M14 20v2";
|
|
199
|
+
}], readonly ["path", {
|
|
200
|
+
readonly d: "M14 2v2";
|
|
201
|
+
}], readonly ["path", {
|
|
202
|
+
readonly d: "M14 8v2";
|
|
203
|
+
}], readonly ["path", {
|
|
204
|
+
readonly d: "M2 15h8";
|
|
205
|
+
}], readonly ["path", {
|
|
206
|
+
readonly d: "M2 3h6a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H2";
|
|
207
|
+
}], readonly ["path", {
|
|
208
|
+
readonly d: "M2 9h8";
|
|
209
|
+
}], readonly ["path", {
|
|
210
|
+
readonly d: "M22 15h-4";
|
|
211
|
+
}], readonly ["path", {
|
|
212
|
+
readonly d: "M22 3h-2a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h2";
|
|
213
|
+
}], readonly ["path", {
|
|
214
|
+
readonly d: "M22 9h-4";
|
|
215
|
+
}], readonly ["path", {
|
|
216
|
+
readonly d: "M5 3v18";
|
|
217
|
+
}]];
|
|
218
|
+
readonly Trash: readonly [readonly ["path", {
|
|
219
|
+
readonly d: "M10 11v6";
|
|
220
|
+
}], readonly ["path", {
|
|
221
|
+
readonly d: "M14 11v6";
|
|
222
|
+
}], readonly ["path", {
|
|
223
|
+
readonly d: "M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6";
|
|
224
|
+
}], readonly ["path", {
|
|
225
|
+
readonly d: "M3 6h18";
|
|
226
|
+
}], readonly ["path", {
|
|
227
|
+
readonly d: "M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2";
|
|
228
|
+
}]];
|
|
229
|
+
readonly Check: readonly [readonly ["path", {
|
|
230
|
+
readonly d: "M20 6 9 17l-5-5";
|
|
231
|
+
}]];
|
|
232
|
+
readonly X: readonly [readonly ["path", {
|
|
233
|
+
readonly d: "M18 6 6 18";
|
|
234
|
+
}], readonly ["path", {
|
|
235
|
+
readonly d: "m6 6 12 12";
|
|
236
|
+
}]];
|
|
237
|
+
};
|
|
238
|
+
export type MarkdownIconName = keyof typeof ICONS;
|
|
239
|
+
/** Decorative: the button that holds it carries the accessible name. */
|
|
240
|
+
export declare function MarkdownIcon({ name }: {
|
|
241
|
+
readonly name: MarkdownIconName;
|
|
242
|
+
}): ReactElement;
|
|
243
|
+
export {};
|
|
244
|
+
//# sourceMappingURL=markdown-icons.d.ts.map
|