@meowdown/react 0.51.0 → 0.52.1
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 +14 -0
- package/dist/beautiful-mermaid-chunk-Cc6FHgAa.js +3 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +101 -4
- package/dist/style.css +17 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -56,6 +56,8 @@ See the full API reference [here](https://npmx.dev/package-docs/@meowdown%2Freac
|
|
|
56
56
|
|
|
57
57
|
Slash menu host items can include `keywords` to match hidden terms without changing the displayed label.
|
|
58
58
|
|
|
59
|
+
The caret glides between positions by default; pass `caretGlide={false}` to move it instantly (hosts can also tune the duration via the `--meowdown-caret-glide` CSS variable).
|
|
60
|
+
|
|
59
61
|
### Wiki embeds
|
|
60
62
|
|
|
61
63
|
Obsidian-style wiki embeds (`![[path]]`, with optional `|width` or
|
|
@@ -119,6 +121,18 @@ resolver that accepts only trusted local image sources.
|
|
|
119
121
|
<MarkdownView markdown={markdown} interactive={false} resolveImageUrl={resolveLocalImageUrl} />
|
|
120
122
|
```
|
|
121
123
|
|
|
124
|
+
### Mermaid code blocks
|
|
125
|
+
|
|
126
|
+
Fenced `mermaid` code blocks render as diagrams in both `MeowdownEditor` and
|
|
127
|
+
`MarkdownView`. The editor shows only the preview while the caret is outside
|
|
128
|
+
the block, then shows source and a live preview while editing it.
|
|
129
|
+
|
|
130
|
+
Rendering uses `beautiful-mermaid`, which currently supports Flowchart, State,
|
|
131
|
+
Sequence, Class, ER, and XY Chart diagrams. Unsupported syntax renders an error
|
|
132
|
+
instead of an empty preview. Override `--meowdown-mermaid-bg`,
|
|
133
|
+
`--meowdown-mermaid-fg`, or `--meowdown-mermaid-error` to customize the
|
|
134
|
+
diagram surface.
|
|
135
|
+
|
|
122
136
|
## Styling
|
|
123
137
|
|
|
124
138
|
Import both stylesheets: `@meowdown/core/style.css` (the editor theme and variables) and `@meowdown/react/style.css` (the component layout). The core theme is documented in [`@meowdown/core`](https://www.npmjs.com/package/@meowdown/core).
|
package/dist/index.d.ts
CHANGED
|
@@ -366,6 +366,12 @@ interface EditorProps {
|
|
|
366
366
|
* Ignored when `readOnly` is set.
|
|
367
367
|
*/
|
|
368
368
|
blockHandle?: boolean;
|
|
369
|
+
/**
|
|
370
|
+
* Glides the caret between positions instead of jumping instantly. On by
|
|
371
|
+
* default. Set to `false` to disable the movement animation; equivalent to
|
|
372
|
+
* setting the `--meowdown-caret-glide` CSS variable to `0ms`.
|
|
373
|
+
*/
|
|
374
|
+
caretGlide?: boolean;
|
|
369
375
|
/**
|
|
370
376
|
* Placeholder text shown when the whole document is empty. A function
|
|
371
377
|
* receives the editor state. Pass a stable function.
|
|
@@ -392,7 +398,7 @@ interface EditorProps {
|
|
|
392
398
|
/** Nodes rendered inside the editor's ProseKit context. */
|
|
393
399
|
children?: ReactNode;
|
|
394
400
|
}
|
|
395
|
-
declare function MeowdownEditor({ mode, initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, frontmatter, blockHandle, placeholder, readOnly, spellCheck, timeFormat, editorClassName, wrapperClassName, handleRef, children }: EditorProps): import("react").JSX.Element;
|
|
401
|
+
declare function MeowdownEditor({ mode, initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, frontmatter, blockHandle, caretGlide, placeholder, readOnly, spellCheck, timeFormat, editorClassName, wrapperClassName, handleRef, children }: EditorProps): import("react").JSX.Element;
|
|
396
402
|
//#endregion
|
|
397
403
|
//#region src/components/markdown-view.d.ts
|
|
398
404
|
/** Payload for {@link TaskClickHandler}. */
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { clsx } from "clsx/lite";
|
|
2
2
|
import { Fragment, cloneElement, createElement, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
-
import { buildFileMarkdown, codeBlockLanguages, defaultResolveImageUrl, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSpellCheckPlugin, defineSubstitution, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, getCodeTokens, getFileKind, getMarkBuilders, getPendingReplacement, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunks, isCodeBlockPreviewHiddenDecoration, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, renderMathInto } from "@meowdown/core";
|
|
3
|
+
import { buildFileMarkdown, codeBlockLanguages, defaultResolveImageUrl, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSpellCheckPlugin, defineSubstitution, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, getCodeTokens, getFileKind, getMarkBuilders, getPendingReplacement, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunks, isCodeBlockPreviewHiddenDecoration, isNodeOfType, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, renderMathInto } from "@meowdown/core";
|
|
4
4
|
import { clamp } from "@ocavue/utils";
|
|
5
5
|
import { canUseRegexLookbehind, createEditor, defineDocChangeHandler, defineUpdateHandler, isTextSelection, union } from "@prosekit/core";
|
|
6
6
|
import { Selection, TextSelection } from "@prosekit/pm/state";
|
|
@@ -19,6 +19,31 @@ import { Mark } from "@prosekit/pm/model";
|
|
|
19
19
|
import { BOOLEAN, OVERLOADED_BOOLEAN, getPropertyInfo, possibleStandardNames } from "react-property";
|
|
20
20
|
import { PreviewCard } from "@base-ui/react/preview-card";
|
|
21
21
|
|
|
22
|
+
//#region src/hooks/use-beautiful-mermaid.ts
|
|
23
|
+
let beautifulMermaidPromise;
|
|
24
|
+
function loadBeautifulMermaid() {
|
|
25
|
+
beautifulMermaidPromise ??= import("./beautiful-mermaid-chunk-Cc6FHgAa.js").then((module) => module.renderMermaidSVG).catch((error) => {
|
|
26
|
+
console.error("[meowdown] Failed to load beautiful-mermaid.", error);
|
|
27
|
+
throw error;
|
|
28
|
+
});
|
|
29
|
+
return beautifulMermaidPromise;
|
|
30
|
+
}
|
|
31
|
+
function useBeautifulMermaid(enabled) {
|
|
32
|
+
const [renderer, setRenderer] = useState(void 0);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!enabled || renderer) return;
|
|
35
|
+
let cancelled = false;
|
|
36
|
+
loadBeautifulMermaid().then((loadedRenderer) => {
|
|
37
|
+
if (!cancelled) setRenderer(() => loadedRenderer);
|
|
38
|
+
}).catch(() => {});
|
|
39
|
+
return () => {
|
|
40
|
+
cancelled = true;
|
|
41
|
+
};
|
|
42
|
+
}, [enabled, renderer]);
|
|
43
|
+
return renderer;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
//#endregion
|
|
22
47
|
//#region src/hooks/use-katex.ts
|
|
23
48
|
/**
|
|
24
49
|
* The lazily loaded KaTeX render function, or `undefined` while it loads (or when
|
|
@@ -48,6 +73,7 @@ var code_block_view_module_default = {
|
|
|
48
73
|
"ItemIndicator": "meow_ItemIndicator_D3j_9q",
|
|
49
74
|
"ItemText": "meow_ItemText_D3j_9q",
|
|
50
75
|
"List": "meow_List_D3j_9q",
|
|
76
|
+
"MermaidPreview": "meow_MermaidPreview_D3j_9q",
|
|
51
77
|
"Popup": "meow_Popup_D3j_9q",
|
|
52
78
|
"Positioner": "meow_Positioner_D3j_9q",
|
|
53
79
|
"Preview": "meow_Preview_D3j_9q",
|
|
@@ -121,16 +147,64 @@ function MathRender(props) {
|
|
|
121
147
|
});
|
|
122
148
|
}
|
|
123
149
|
|
|
150
|
+
//#endregion
|
|
151
|
+
//#region src/components/mermaid-render.tsx
|
|
152
|
+
const MERMAID_OPTIONS = {
|
|
153
|
+
bg: "var(--meowdown-mermaid-bg)",
|
|
154
|
+
fg: "var(--meowdown-mermaid-fg)",
|
|
155
|
+
transparent: true,
|
|
156
|
+
interactive: false
|
|
157
|
+
};
|
|
158
|
+
function renderMermaid(renderer, source) {
|
|
159
|
+
try {
|
|
160
|
+
const svg = renderer(source, MERMAID_OPTIONS);
|
|
161
|
+
const document = new DOMParser().parseFromString(svg, "image/svg+xml");
|
|
162
|
+
const element = document.documentElement;
|
|
163
|
+
if (document.querySelector("parsererror") || element.localName !== "svg" || element.namespaceURI !== "http://www.w3.org/2000/svg") return { error: "Invalid SVG output." };
|
|
164
|
+
return { element };
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return { error: error instanceof Error ? error.message : String(error) };
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function MermaidRender(props) {
|
|
170
|
+
const { renderer, source, className, onMouseDown } = props;
|
|
171
|
+
const output = useMemo(() => renderMermaid(renderer, source), [renderer, source]);
|
|
172
|
+
const ref = useRef(null);
|
|
173
|
+
useLayoutEffect(() => {
|
|
174
|
+
const host = ref.current;
|
|
175
|
+
if (!host || !output.element) return;
|
|
176
|
+
host.replaceChildren(window.document.importNode(output.element, true));
|
|
177
|
+
}, [output]);
|
|
178
|
+
if (output.error) return /* @__PURE__ */ jsx("span", {
|
|
179
|
+
className,
|
|
180
|
+
contentEditable: false,
|
|
181
|
+
"data-error": true,
|
|
182
|
+
"data-testid": props["data-testid"],
|
|
183
|
+
onMouseDown,
|
|
184
|
+
children: output.error
|
|
185
|
+
}, "error");
|
|
186
|
+
return /* @__PURE__ */ jsx("span", {
|
|
187
|
+
ref,
|
|
188
|
+
className,
|
|
189
|
+
contentEditable: false,
|
|
190
|
+
"data-testid": props["data-testid"],
|
|
191
|
+
onMouseDown
|
|
192
|
+
}, "svg");
|
|
193
|
+
}
|
|
194
|
+
|
|
124
195
|
//#endregion
|
|
125
196
|
//#region src/components/code-block-view.tsx
|
|
126
197
|
function CodeBlockView(props) {
|
|
127
198
|
const language = props.node.attrs.language || "";
|
|
128
199
|
const isMath = language === "math";
|
|
200
|
+
const isMermaid = language === "mermaid";
|
|
129
201
|
const code = props.node.textContent;
|
|
130
202
|
const caretInside = props.decorations.some(isCodeBlockPreviewHiddenDecoration);
|
|
131
203
|
const katex = useKaTeX(isMath);
|
|
204
|
+
const mermaid = useBeautifulMermaid(isMermaid);
|
|
132
205
|
const showMathPreview = isMath && katex != null;
|
|
133
|
-
const
|
|
206
|
+
const showMermaidPreview = isMermaid && mermaid != null;
|
|
207
|
+
const previewOnly = (showMathPreview || showMermaidPreview) && !caretInside && code.trim() !== "";
|
|
134
208
|
const focusSource = (event) => {
|
|
135
209
|
event.preventDefault();
|
|
136
210
|
const pos = props.getPos();
|
|
@@ -241,6 +315,13 @@ function CodeBlockView(props) {
|
|
|
241
315
|
className: code_block_view_module_default.Preview,
|
|
242
316
|
"data-testid": "code-block-math-preview",
|
|
243
317
|
onMouseDown: focusSource
|
|
318
|
+
}),
|
|
319
|
+
showMermaidPreview && /* @__PURE__ */ jsx(MermaidRender, {
|
|
320
|
+
renderer: mermaid,
|
|
321
|
+
source: code,
|
|
322
|
+
className: `${code_block_view_module_default.Preview} ${code_block_view_module_default.MermaidPreview}`,
|
|
323
|
+
"data-testid": "code-block-mermaid-preview",
|
|
324
|
+
onMouseDown: focusSource
|
|
244
325
|
})
|
|
245
326
|
]
|
|
246
327
|
});
|
|
@@ -1571,7 +1652,7 @@ function findHeadingPosition(doc, fragment) {
|
|
|
1571
1652
|
let match;
|
|
1572
1653
|
doc.descendants((node, pos) => {
|
|
1573
1654
|
if (match != null) return false;
|
|
1574
|
-
if (node
|
|
1655
|
+
if (!isNodeOfType(node, "heading")) return true;
|
|
1575
1656
|
const displayText = getTextblockDisplayText(node);
|
|
1576
1657
|
const slug = slugger.slug(displayText);
|
|
1577
1658
|
if (headingLookupKey(node.textContent) === target || headingLookupKey(displayText) === target || slug === slugTarget) {
|
|
@@ -1784,7 +1865,8 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
|
|
|
1784
1865
|
|
|
1785
1866
|
//#endregion
|
|
1786
1867
|
//#region src/components/editor.tsx
|
|
1787
|
-
|
|
1868
|
+
const CARET_GLIDE_OFF = { "--meowdown-caret-glide": "0ms" };
|
|
1869
|
+
function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste = true, linkPaste = true, bulletAfterHeading = false, substitution = true, frontmatter = false, blockHandle = true, caretGlide = true, placeholder, readOnly, spellCheck, timeFormat, editorClassName, wrapperClassName, handleRef, children }) {
|
|
1788
1870
|
const childRef = useRef(null);
|
|
1789
1871
|
useImperativeHandle(handleRef, () => {
|
|
1790
1872
|
function getMarkdown() {
|
|
@@ -1871,6 +1953,7 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
|
|
|
1871
1953
|
}, []);
|
|
1872
1954
|
return /* @__PURE__ */ jsx("div", {
|
|
1873
1955
|
className: clsx("meowdown", wrapperClassName),
|
|
1956
|
+
style: caretGlide ? void 0 : CARET_GLIDE_OFF,
|
|
1874
1957
|
children: /* @__PURE__ */ jsx(ProseKitEditor, {
|
|
1875
1958
|
ref: childRef,
|
|
1876
1959
|
markMode: mode,
|
|
@@ -2250,6 +2333,19 @@ function MathCodeBlock({ code }) {
|
|
|
2250
2333
|
"data-testid": "code-block-math-preview"
|
|
2251
2334
|
});
|
|
2252
2335
|
}
|
|
2336
|
+
function MermaidCodeBlock({ code }) {
|
|
2337
|
+
const renderer = useBeautifulMermaid(true);
|
|
2338
|
+
if (!renderer || code.trim() === "") return /* @__PURE__ */ jsx(CodeBlock, {
|
|
2339
|
+
code,
|
|
2340
|
+
language: "mermaid"
|
|
2341
|
+
});
|
|
2342
|
+
return /* @__PURE__ */ jsx(MermaidRender, {
|
|
2343
|
+
renderer,
|
|
2344
|
+
source: code,
|
|
2345
|
+
className: `${code_block_view_module_default.Preview} ${code_block_view_module_default.MermaidPreview}`,
|
|
2346
|
+
"data-testid": "code-block-mermaid-preview"
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2253
2349
|
/** Wrap inline `children` in one mark, special-casing the view/link marks. */
|
|
2254
2350
|
function wrapMark(mark, children, context) {
|
|
2255
2351
|
switch (mark.type.name) {
|
|
@@ -2377,6 +2473,7 @@ function renderBlock(node, context) {
|
|
|
2377
2473
|
const attrs = node.attrs;
|
|
2378
2474
|
const language = typeof attrs.language === "string" ? attrs.language : "";
|
|
2379
2475
|
if (language === "math") return /* @__PURE__ */ jsx(MathCodeBlock, { code: node.textContent }, key);
|
|
2476
|
+
if (language === "mermaid") return /* @__PURE__ */ jsx(MermaidCodeBlock, { code: node.textContent }, key);
|
|
2380
2477
|
return /* @__PURE__ */ jsx(CodeBlock, {
|
|
2381
2478
|
code: node.textContent,
|
|
2382
2479
|
language
|
package/dist/style.css
CHANGED
|
@@ -16,6 +16,23 @@
|
|
|
16
16
|
overflow-x: auto;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
.meow_MermaidPreview_D3j_9q {
|
|
20
|
+
text-align: initial;
|
|
21
|
+
|
|
22
|
+
&[data-error] {
|
|
23
|
+
color: var(--meowdown-mermaid-error);
|
|
24
|
+
font-family: var(--meowdown-font-mono);
|
|
25
|
+
white-space: pre-wrap;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
& svg {
|
|
29
|
+
max-width: 100%;
|
|
30
|
+
height: auto;
|
|
31
|
+
margin-inline: auto;
|
|
32
|
+
display: block;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
19
36
|
.meow_Toolbar_D3j_9q {
|
|
20
37
|
z-index: 1;
|
|
21
38
|
user-select: none;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meowdown/react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.52.1",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -23,11 +23,12 @@
|
|
|
23
23
|
"@prosekit/core": "^0.13.0-beta.6",
|
|
24
24
|
"@prosekit/pm": "^0.1.19-beta.3",
|
|
25
25
|
"@prosekit/react": "^0.8.0-beta.21",
|
|
26
|
+
"beautiful-mermaid": "^1.1.3",
|
|
26
27
|
"clsx": "^2.1.1",
|
|
27
28
|
"github-slugger": "^2.0.0",
|
|
28
29
|
"lucide-react": "^1.24.0",
|
|
29
30
|
"react-property": "^2.0.2",
|
|
30
|
-
"@meowdown/core": "0.
|
|
31
|
+
"@meowdown/core": "0.52.1"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|
|
33
34
|
"react": "^19.0.0",
|