@meowdown/react 0.68.3 → 0.70.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 +1 -0
- package/dist/index.d.ts +34 -5
- package/dist/index.js +155 -79
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -66,6 +66,7 @@ Common `MeowdownEditor` props:
|
|
|
66
66
|
| `onFilePaste` | Persist pasted or dropped files |
|
|
67
67
|
| `onSlashMenuSearch` / `onTagSearch` / `onWikilinkSearch` / `onSelectionMenuSearch` | Search menus for `/`, `#`, `[[`, and selection commands |
|
|
68
68
|
| `readOnly` / `placeholder` / `blockHandle` / `caretGlide` / `embedPaste` / `linkPaste` / `bulletAfterHeading` | Behavior toggles |
|
|
69
|
+
| `CodeBlockView` | Custom React node view component for code blocks |
|
|
69
70
|
|
|
70
71
|
Every prop, callback, and `EditorHandle` method is documented in the [API reference](https://npmx.dev/package-docs/@meowdown%2Freact/).
|
|
71
72
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ReactElement, ReactNode, Ref } from "react";
|
|
2
|
-
import { AcceptPendingReplacementOptions, ExitBoundaryHandler, FileClickHandler, FileInfoResolver, FileLinkResolver, FilePasteOptions, FileViewOptions, ImageClickHandler, ImageOptions, LinkClickHandler, LinkCopyHandler, LinkPreview, LinkPreviewResolver, LinkPreviewResolver as LinkPreviewResolver$1, ListMarker, MarkMode, PendingReplacement, PendingReplacementOutcome, PlaceholderOptions, SearchStatusHandler, StartPendingReplacementOptions, TagClickHandler, TypedEditor, WikiEmbedResolver, WikilinkClickHandler, WikilinkHoverHit, WikilinkResolver } from "@meowdown/core";
|
|
2
|
+
import { AcceptPendingReplacementOptions, ExitBoundaryHandler, FileClickHandler, FileInfoResolver, FileLinkResolver, FilePasteOptions, FileViewOptions, ImageClickHandler, ImageOptions, LinkClickHandler, LinkCopyHandler, LinkPreview, LinkPreviewResolver, LinkPreviewResolver as LinkPreviewResolver$1, ListMarker, MarkMode, PendingReplacement, PendingReplacementOutcome, PlaceholderOptions, SearchStatusHandler, StartPendingReplacementOptions, TagClickHandler, TypedEditor, WikiEmbedResolver, WikilinkClickHandler, WikilinkHoverHit, WikilinkResolver, XPostResolver, XPostResolver as XPostResolver$1, YouTubeVideoResolver, YouTubeVideoResolver as YouTubeVideoResolver$1 } from "@meowdown/core";
|
|
3
3
|
import { SelectionJSON, SelectionJSON as SelectionJSON$1 } from "@prosekit/core";
|
|
4
|
-
import { useEditor, useExtension, useKeymap } from "@prosekit/react";
|
|
4
|
+
import { ReactNodeViewComponent, ReactNodeViewComponent as ReactNodeViewComponent$1, useEditor, useExtension, useKeymap } from "@prosekit/react";
|
|
5
5
|
//#region src/utils/date-format.d.ts
|
|
6
6
|
type TimeFormat = '12' | '24';
|
|
7
7
|
//#endregion
|
|
@@ -408,6 +408,20 @@ interface EditorProps {
|
|
|
408
408
|
* (e.g. from `useCallback`).
|
|
409
409
|
*/
|
|
410
410
|
resolveFileInfo?: FileViewOptions['resolveFileInfo'];
|
|
411
|
+
/**
|
|
412
|
+
* Resolves the data behind an X post URL, directly or as a promise; the post
|
|
413
|
+
* renders as a `post-embed-x-post` card. Defaults to `defaultResolveXPost`,
|
|
414
|
+
* which fetches through react-tweet's hosted proxy. Pass a stable function
|
|
415
|
+
* (e.g. from `useCallback`).
|
|
416
|
+
*/
|
|
417
|
+
resolveXPost?: XPostResolver$1;
|
|
418
|
+
/**
|
|
419
|
+
* Resolves the data behind a YouTube video URL, directly or as a promise;
|
|
420
|
+
* the video renders as a `post-embed-youtube-video` card. Defaults to
|
|
421
|
+
* `defaultResolveYouTubeVideo`, which reads YouTube's oEmbed endpoint. Pass
|
|
422
|
+
* a stable function (e.g. from `useCallback`).
|
|
423
|
+
*/
|
|
424
|
+
resolveYouTubeVideo?: YouTubeVideoResolver$1;
|
|
411
425
|
/**
|
|
412
426
|
* Called when the user clicks a rendered file pill (or presses `Mod-Enter`
|
|
413
427
|
* with the caret on one), with its `href`, `name`, and the originating
|
|
@@ -509,6 +523,11 @@ interface EditorProps {
|
|
|
509
523
|
* Class on the outer `.meowdown` wrapper div.
|
|
510
524
|
*/
|
|
511
525
|
wrapperClassName?: string;
|
|
526
|
+
/**
|
|
527
|
+
* React component that renders code blocks in place of the built-in one.
|
|
528
|
+
* `false` disables the React code block view.
|
|
529
|
+
*/
|
|
530
|
+
CodeBlockView?: ReactNodeViewComponent$1 | false | undefined;
|
|
512
531
|
/**
|
|
513
532
|
* Imperative handle for the editor.
|
|
514
533
|
*/
|
|
@@ -524,7 +543,7 @@ interface EditorProps {
|
|
|
524
543
|
*
|
|
525
544
|
* Callbacks and resolvers should be stable; pass them via `useCallback`.
|
|
526
545
|
*/
|
|
527
|
-
export declare function MeowdownEditor({ mode, initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, frontmatter, blockHandle, caretGlide, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, handleRef, children }: EditorProps): ReactElement;
|
|
546
|
+
export declare function MeowdownEditor({ mode, initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, resolveXPost, resolveYouTubeVideo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, frontmatter, blockHandle, caretGlide, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, CodeBlockView, handleRef, children }: EditorProps): ReactElement;
|
|
528
547
|
//#endregion
|
|
529
548
|
//#region src/components/markdown-view.d.ts
|
|
530
549
|
/**
|
|
@@ -613,6 +632,16 @@ interface MarkdownViewProps {
|
|
|
613
632
|
* Resolve metadata shown on a file pill.
|
|
614
633
|
*/
|
|
615
634
|
resolveFileInfo?: FileInfoResolver;
|
|
635
|
+
/**
|
|
636
|
+
* Resolve the data behind an X post URL, rendered as a `post-embed-x-post`
|
|
637
|
+
* card. Defaults to `defaultResolveXPost`.
|
|
638
|
+
*/
|
|
639
|
+
resolveXPost?: XPostResolver$1;
|
|
640
|
+
/**
|
|
641
|
+
* Resolve the data behind a YouTube video URL, rendered as a
|
|
642
|
+
* `post-embed-youtube-video` card. Defaults to `defaultResolveYouTubeVideo`.
|
|
643
|
+
*/
|
|
644
|
+
resolveYouTubeVideo?: YouTubeVideoResolver$1;
|
|
616
645
|
/**
|
|
617
646
|
* Called when a rendered wikilink is clicked. Pass a stable function.
|
|
618
647
|
*/
|
|
@@ -649,7 +678,7 @@ interface MarkdownViewProps {
|
|
|
649
678
|
* Callbacks (`onWikilinkClick`, etc.) and resolvers should be stable; pass them via
|
|
650
679
|
* `useCallback` to avoid re-rendering the whole tree.
|
|
651
680
|
*/
|
|
652
|
-
export declare function MarkdownView({ markdown, markMode, frontmatter, interactive, expandCollapsed, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, onWikilinkClick, onLinkClick, onImageClick, onFileClick, onTaskClick, className }: MarkdownViewProps): ReactElement;
|
|
681
|
+
export declare function MarkdownView({ markdown, markMode, frontmatter, interactive, expandCollapsed, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, resolveXPost, resolveYouTubeVideo, onWikilinkClick, onLinkClick, onImageClick, onFileClick, onTaskClick, className }: MarkdownViewProps): ReactElement;
|
|
653
682
|
//#endregion
|
|
654
683
|
//#region src/components/wikilink-hover-card.d.ts
|
|
655
684
|
/**
|
|
@@ -675,4 +704,4 @@ interface WikilinkHoverCardProps {
|
|
|
675
704
|
*/
|
|
676
705
|
export declare function WikilinkHoverCard({ children, className }: WikilinkHoverCardProps): ReactNode;
|
|
677
706
|
//#endregion
|
|
678
|
-
export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, type LinkPreview, type LinkPreviewResolver, type MarkdownViewProps, type PendingReplacementResolveHandler, type SelectionHint, type SelectionJSON, type SelectionMenuContext, type SelectionMenuItem, type SelectionMenuSearchHandler, type SlashMenuItem, type SlashMenuSearchHandler, type TagItem, type TagSearchHandler, type TaskClickHandler, type TaskClickPayload, type TimeFormat, type WikilinkHoverCardProps, type WikilinkItem, type WikilinkSearchHandler, useEditor, useExtension, useKeymap };
|
|
707
|
+
export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, type LinkPreview, type LinkPreviewResolver, type MarkdownViewProps, type PendingReplacementResolveHandler, type ReactNodeViewComponent, type SelectionHint, type SelectionJSON, type SelectionMenuContext, type SelectionMenuItem, type SelectionMenuSearchHandler, type SlashMenuItem, type SlashMenuSearchHandler, type TagItem, type TagSearchHandler, type TaskClickHandler, type TaskClickPayload, type TimeFormat, type WikilinkHoverCardProps, type WikilinkItem, type WikilinkSearchHandler, type XPostResolver, type YouTubeVideoResolver, useEditor, useExtension, useKeymap };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { clsx } from "clsx/lite";
|
|
2
|
-
import { Fragment, cloneElement, createElement, useCallback, useDeferredValue, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
-
import { buildFileMarkdown, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSearchStatusHandler, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, getCodeTokens, getFileKind, getLinkText, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunksWithContext, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isModEvent, isNodeOfType,
|
|
2
|
+
import { Fragment, cloneElement, createElement, memo, useCallback, useDeferredValue, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
+
import { buildFileMarkdown, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defaultResolveXPost, defaultResolveYouTubeVideo, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSearchStatusHandler, defineSubstitution, defineTagClickHandler, defineViewAttributes, defineVirtualCaret, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, getCodeTokens, getFileKind, getLinkText, getMarkBuilders, getPendingReplacement, getSearchStatus, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunksWithContext, isCodeBlockPreviewHiddenDecoration, isLinkTextForHref, isModEvent, isNodeOfType, isReferenceDefinitionNode, isSelectionInTableCell, loadKaTeX, markdownToDoc, matchPostEmbed, normalizeHref, parsePostEmbedSnapshot, 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";
|
|
@@ -15,6 +15,8 @@ import { Popover } from "@base-ui/react/popover";
|
|
|
15
15
|
import { AutocompleteEmpty, AutocompleteItem, AutocompletePopup, AutocompletePositioner, AutocompleteRoot } from "@prosekit/react/autocomplete";
|
|
16
16
|
import { MenuItem, MenuPopup, MenuPositioner } from "@prosekit/react/menu";
|
|
17
17
|
import { TableHandleColumnMenuRoot, TableHandleColumnMenuTrigger, TableHandleColumnPopup, TableHandleColumnPositioner, TableHandleDragPreview, TableHandleDropIndicator, TableHandleRoot, TableHandleRowMenuRoot, TableHandleRowMenuTrigger, TableHandleRowPopup, TableHandleRowPositioner } from "@prosekit/react/table-handle";
|
|
18
|
+
import { registerXPost } from "@post-embed/elements/x";
|
|
19
|
+
import { registerYouTubeVideo } from "@post-embed/elements/youtube";
|
|
18
20
|
import { Mark } from "@prosekit/pm/model";
|
|
19
21
|
import { BOOLEAN, OVERLOADED_BOOLEAN, getPropertyInfo, possibleStandardNames } from "react-property";
|
|
20
22
|
import { PreviewCard } from "@base-ui/react/preview-card";
|
|
@@ -353,11 +355,11 @@ function CodeBlockToolbar({ code, language, setLanguage }) {
|
|
|
353
355
|
|
|
354
356
|
//#endregion
|
|
355
357
|
//#region src/extensions/code-block-view.ts
|
|
356
|
-
function defineCodeBlockView() {
|
|
358
|
+
function defineCodeBlockView(component = CodeBlockView) {
|
|
357
359
|
return union(defineReactNodeView({
|
|
358
360
|
name: "codeBlock",
|
|
359
361
|
contentAs: "code",
|
|
360
|
-
component
|
|
362
|
+
component
|
|
361
363
|
}), defineCodeBlockPreviewPlugin());
|
|
362
364
|
}
|
|
363
365
|
|
|
@@ -401,7 +403,7 @@ function DropIndicator$1() {
|
|
|
401
403
|
|
|
402
404
|
//#endregion
|
|
403
405
|
//#region src/components/editor-extensions.tsx
|
|
404
|
-
function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick, onTagClick, onExitBoundary, resolveImageUrl, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, placeholder, readOnly, wikilinkEnabled, spellCheck, searchQuery, onSearchChange, editorClassName }) {
|
|
406
|
+
function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick, onTagClick, onExitBoundary, resolveImageUrl, resolveFileInfo, resolveXPost, resolveYouTubeVideo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, placeholder, readOnly, wikilinkEnabled, spellCheck, searchQuery, onSearchChange, editorClassName }) {
|
|
405
407
|
const editor = useEditor$1();
|
|
406
408
|
useLayoutEffect(() => {
|
|
407
409
|
editor.commands.setMarkMode(markMode);
|
|
@@ -458,8 +460,16 @@ function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick,
|
|
|
458
460
|
return onExitBoundary ? defineExitBoundaryHandler(onExitBoundary) : null;
|
|
459
461
|
}, [onExitBoundary]));
|
|
460
462
|
useExtension$1(useMemo(() => {
|
|
461
|
-
return defineImage({
|
|
462
|
-
|
|
463
|
+
return defineImage({
|
|
464
|
+
resolveImageUrl,
|
|
465
|
+
resolveXPost,
|
|
466
|
+
resolveYouTubeVideo
|
|
467
|
+
});
|
|
468
|
+
}, [
|
|
469
|
+
resolveImageUrl,
|
|
470
|
+
resolveXPost,
|
|
471
|
+
resolveYouTubeVideo
|
|
472
|
+
]));
|
|
463
473
|
useExtension$1(useMemo(() => {
|
|
464
474
|
return defineFileView({ resolveFileInfo });
|
|
465
475
|
}, [resolveFileInfo]));
|
|
@@ -1925,7 +1935,7 @@ function findHeadingPosition(doc, fragment) {
|
|
|
1925
1935
|
});
|
|
1926
1936
|
return match;
|
|
1927
1937
|
}
|
|
1928
|
-
function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution = true, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, searchQuery = "", onSearchChange, timeFormat, editorClassName, ref, children }) {
|
|
1938
|
+
function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, resolveXPost, resolveYouTubeVideo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution = true, frontmatter = false, blockHandle = true, placeholder, readOnly, spellCheck, searchQuery = "", onSearchChange, timeFormat, editorClassName, CodeBlockView, ref, children }) {
|
|
1929
1939
|
const [editor] = useState(() => {
|
|
1930
1940
|
const baseExtension = defineEditorExtension({
|
|
1931
1941
|
resolveFileLink,
|
|
@@ -1933,7 +1943,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
|
|
|
1933
1943
|
resolveWikilink,
|
|
1934
1944
|
markMode
|
|
1935
1945
|
});
|
|
1936
|
-
const extension = union(baseExtension, defineCodeBlockView());
|
|
1946
|
+
const extension = CodeBlockView === false ? baseExtension : union(baseExtension, defineCodeBlockView(CodeBlockView));
|
|
1937
1947
|
const editor = createEditor({ extension });
|
|
1938
1948
|
if (initialMarkdown) editor.setContent(markdownToDoc(initialMarkdown, {
|
|
1939
1949
|
nodes: editor.nodes,
|
|
@@ -2092,6 +2102,8 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
|
|
|
2092
2102
|
onExitBoundary,
|
|
2093
2103
|
resolveImageUrl,
|
|
2094
2104
|
resolveFileInfo,
|
|
2105
|
+
resolveXPost,
|
|
2106
|
+
resolveYouTubeVideo,
|
|
2095
2107
|
onFileClick,
|
|
2096
2108
|
onFilePaste,
|
|
2097
2109
|
onFileSaveError,
|
|
@@ -2150,7 +2162,7 @@ const CARET_GLIDE_OFF = { "--meowdown-caret-glide": "0ms" };
|
|
|
2150
2162
|
*
|
|
2151
2163
|
* Callbacks and resolvers should be stable; pass them via `useCallback`.
|
|
2152
2164
|
*/
|
|
2153
|
-
function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste = true, linkPaste = true, bulletAfterHeading = false, substitution = true, frontmatter = false, blockHandle = true, caretGlide = true, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, handleRef, children }) {
|
|
2165
|
+
function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, resolveLinkPreview, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, resolveXPost, resolveYouTubeVideo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste = true, linkPaste = true, bulletAfterHeading = false, substitution = true, frontmatter = false, blockHandle = true, caretGlide = true, placeholder, readOnly, spellCheck, searchQuery, onSearchChange, timeFormat, editorClassName, wrapperClassName, CodeBlockView, handleRef, children }) {
|
|
2154
2166
|
const childRef = useRef(null);
|
|
2155
2167
|
useImperativeHandle(handleRef, () => {
|
|
2156
2168
|
function getMarkdown() {
|
|
@@ -2269,6 +2281,8 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
|
|
|
2269
2281
|
resolveWikiEmbed,
|
|
2270
2282
|
resolveWikilink,
|
|
2271
2283
|
resolveFileInfo,
|
|
2284
|
+
resolveXPost,
|
|
2285
|
+
resolveYouTubeVideo,
|
|
2272
2286
|
onFileClick,
|
|
2273
2287
|
onFilePaste,
|
|
2274
2288
|
onFileSaveError,
|
|
@@ -2286,6 +2300,7 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
|
|
|
2286
2300
|
onSearchChange,
|
|
2287
2301
|
timeFormat,
|
|
2288
2302
|
editorClassName,
|
|
2303
|
+
CodeBlockView,
|
|
2289
2304
|
children
|
|
2290
2305
|
})
|
|
2291
2306
|
});
|
|
@@ -2419,44 +2434,45 @@ function WikilinkChip(props) {
|
|
|
2419
2434
|
})]
|
|
2420
2435
|
});
|
|
2421
2436
|
}
|
|
2422
|
-
function
|
|
2423
|
-
const {
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
const iframe = iframeRef.current;
|
|
2428
|
-
if (!iframe) return;
|
|
2429
|
-
return listenForTweetHeight(iframe);
|
|
2430
|
-
}, [embed.kind, embed.key]);
|
|
2431
|
-
const youtubeWidth = embed.kind === "youtube" ? width : null;
|
|
2432
|
-
const tweetHeight = embed.kind === "tweet" ? height : null;
|
|
2437
|
+
function PostEmbed(props) {
|
|
2438
|
+
const { kind, src, width, snapshot, resolveXPost, resolveYouTubeVideo } = props;
|
|
2439
|
+
registerXPost();
|
|
2440
|
+
registerYouTubeVideo();
|
|
2441
|
+
const saved = snapshot == null ? void 0 : parsePostEmbedSnapshot(snapshot);
|
|
2433
2442
|
return /* @__PURE__ */ jsx("span", {
|
|
2434
2443
|
className: "md-image-view-preview md-atom-view-preview",
|
|
2435
2444
|
contentEditable: false,
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2445
|
+
"data-testid": `${kind}-embed`,
|
|
2446
|
+
children: kind === "x-post" ? createElement("post-embed-x-post", {
|
|
2447
|
+
data: saved?.kind === "x-post" ? saved.data : null,
|
|
2448
|
+
url: src,
|
|
2449
|
+
resolver: resolveXPost
|
|
2450
|
+
}) : createElement("post-embed-youtube-video", {
|
|
2451
|
+
data: saved?.kind === "youtube-video" ? saved.data : null,
|
|
2452
|
+
url: src,
|
|
2453
|
+
resolver: resolveYouTubeVideo,
|
|
2454
|
+
playback: "inline",
|
|
2455
|
+
style: width == null ? void 0 : {
|
|
2456
|
+
width,
|
|
2457
|
+
maxWidth: "none"
|
|
2458
|
+
}
|
|
2459
|
+
})
|
|
2450
2460
|
});
|
|
2451
2461
|
}
|
|
2452
2462
|
function ImagePreview(props) {
|
|
2453
|
-
const { src, alt, width,
|
|
2454
|
-
const
|
|
2455
|
-
if (
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2463
|
+
const { src, alt, width, snapshot, resolveImageUrl, resolveXPost, resolveYouTubeVideo, onImageClick, interactive } = props;
|
|
2464
|
+
const kind = matchPostEmbed(src);
|
|
2465
|
+
if (kind) {
|
|
2466
|
+
if (!interactive) return null;
|
|
2467
|
+
return /* @__PURE__ */ jsx(PostEmbed, {
|
|
2468
|
+
kind,
|
|
2469
|
+
src,
|
|
2470
|
+
width,
|
|
2471
|
+
snapshot,
|
|
2472
|
+
resolveXPost: resolveXPost ?? defaultResolveXPost,
|
|
2473
|
+
resolveYouTubeVideo: resolveYouTubeVideo ?? defaultResolveYouTubeVideo
|
|
2474
|
+
}, src);
|
|
2475
|
+
}
|
|
2460
2476
|
const url = (resolveImageUrl ?? defaultResolveImageUrl)(src);
|
|
2461
2477
|
if (!url) return null;
|
|
2462
2478
|
return /* @__PURE__ */ jsx("span", {
|
|
@@ -2480,15 +2496,17 @@ function ImagePreview(props) {
|
|
|
2480
2496
|
});
|
|
2481
2497
|
}
|
|
2482
2498
|
function ImageView(props) {
|
|
2483
|
-
const { src, alt, width,
|
|
2499
|
+
const { src, alt, width, snapshot, context, children } = props;
|
|
2484
2500
|
return /* @__PURE__ */ jsxs("span", {
|
|
2485
2501
|
className: "md-image-view md-atom-view",
|
|
2486
2502
|
children: [/* @__PURE__ */ jsx(ImagePreview, {
|
|
2487
2503
|
src,
|
|
2488
2504
|
alt,
|
|
2489
2505
|
width,
|
|
2490
|
-
|
|
2506
|
+
snapshot,
|
|
2491
2507
|
resolveImageUrl: context.resolveImageUrl,
|
|
2508
|
+
resolveXPost: context.resolveXPost,
|
|
2509
|
+
resolveYouTubeVideo: context.resolveYouTubeVideo,
|
|
2492
2510
|
onImageClick: context.onImageClick,
|
|
2493
2511
|
interactive: context.interactive
|
|
2494
2512
|
}), /* @__PURE__ */ jsx("span", {
|
|
@@ -2684,7 +2702,7 @@ function wrapMark(mark, children, context) {
|
|
|
2684
2702
|
src: attrs.src,
|
|
2685
2703
|
alt: attrs.alt,
|
|
2686
2704
|
width: attrs.width,
|
|
2687
|
-
|
|
2705
|
+
snapshot: attrs.snapshot,
|
|
2688
2706
|
context,
|
|
2689
2707
|
children
|
|
2690
2708
|
});
|
|
@@ -2766,10 +2784,7 @@ function renderInline(node, context) {
|
|
|
2766
2784
|
resolveFileLink: context.resolveFileLink,
|
|
2767
2785
|
resolveWikiEmbed: context.resolveWikiEmbed,
|
|
2768
2786
|
resolveWikilink: context.resolveWikilink
|
|
2769
|
-
}, {
|
|
2770
|
-
referenceDefinitions: context.referenceDefinitions,
|
|
2771
|
-
isReferenceDefinition: context.referenceDefinitionNodes.has(node)
|
|
2772
|
-
}).map(([from, to, marks]) => ({
|
|
2787
|
+
}, { referenceDefinitions: context.referenceDefinitions }).map(([from, to, marks]) => ({
|
|
2773
2788
|
text: text.slice(from, to),
|
|
2774
2789
|
marks: Mark.setFrom(marks)
|
|
2775
2790
|
})), 0, context);
|
|
@@ -2814,8 +2829,8 @@ function renderCodeBlock(node, key) {
|
|
|
2814
2829
|
language
|
|
2815
2830
|
}, key);
|
|
2816
2831
|
}
|
|
2817
|
-
function renderBlock(node, context) {
|
|
2818
|
-
if (
|
|
2832
|
+
function renderBlock(node, context, parent, index) {
|
|
2833
|
+
if (isReferenceDefinitionNode(node, parent, index)) return null;
|
|
2819
2834
|
const key = context.keyCounter.value++;
|
|
2820
2835
|
const typeName = node.type.name;
|
|
2821
2836
|
let handleTaskClick;
|
|
@@ -2829,11 +2844,64 @@ function renderBlock(node, context) {
|
|
|
2829
2844
|
const inline = renderInline(node, context);
|
|
2830
2845
|
return toDOM ? outputSpecToReact(toDOM(node), inline, context) : /* @__PURE__ */ jsx(Fragment, { children: inline }, key);
|
|
2831
2846
|
}
|
|
2832
|
-
const children = node.content.content.map((child) =>
|
|
2847
|
+
const children = node.content.content.map((child, childIndex) => {
|
|
2848
|
+
return renderBlock(child, context, node, childIndex);
|
|
2849
|
+
});
|
|
2833
2850
|
const reactNode = toDOM ? outputSpecToReact(toDOM(node), children, context) : /* @__PURE__ */ jsx(Fragment, { children }, key);
|
|
2834
2851
|
if (typeName === "list" && handleTaskClick && typeof reactNode !== "string" && reactNode != null) return cloneElement(reactNode, { onClick: handleTaskClick });
|
|
2835
2852
|
return reactNode;
|
|
2836
2853
|
}
|
|
2854
|
+
function isTaskList(node) {
|
|
2855
|
+
return isNodeOfType(node, "list") && node.attrs.kind === "task";
|
|
2856
|
+
}
|
|
2857
|
+
/**
|
|
2858
|
+
* Checkboxes a block renders, in the same pre-order `renderBlock` walks, so a
|
|
2859
|
+
* block's first checkbox index is the sum over the blocks before it.
|
|
2860
|
+
*/
|
|
2861
|
+
function countTaskItems(node) {
|
|
2862
|
+
let count = isTaskList(node) ? 1 : 0;
|
|
2863
|
+
node.descendants((child) => {
|
|
2864
|
+
if (isTaskList(child)) count++;
|
|
2865
|
+
return true;
|
|
2866
|
+
});
|
|
2867
|
+
return count;
|
|
2868
|
+
}
|
|
2869
|
+
/**
|
|
2870
|
+
* The effective definitions as one string, so blocks can compare them by
|
|
2871
|
+
* content: `collectReferenceDefinitions` builds a new map on every parse.
|
|
2872
|
+
*/
|
|
2873
|
+
function definitionsSignature(definitions) {
|
|
2874
|
+
let signature = "";
|
|
2875
|
+
for (const { key, href, title } of definitions.values()) signature += `${key}\0${href}\0${title}\n`;
|
|
2876
|
+
return signature;
|
|
2877
|
+
}
|
|
2878
|
+
function splitBlocks(doc) {
|
|
2879
|
+
const blocks = [];
|
|
2880
|
+
let taskBase = 0;
|
|
2881
|
+
for (const node of doc.content.content) {
|
|
2882
|
+
blocks.push({
|
|
2883
|
+
node,
|
|
2884
|
+
taskBase
|
|
2885
|
+
});
|
|
2886
|
+
taskBase += countTaskItems(node);
|
|
2887
|
+
}
|
|
2888
|
+
return blocks;
|
|
2889
|
+
}
|
|
2890
|
+
/**
|
|
2891
|
+
* One top-level block. Memoized on the block's own content (`Node.eq`), its
|
|
2892
|
+
* first checkbox index, the shared props object, and the definitions'
|
|
2893
|
+
* content, so a growing document re-renders only the block that changed.
|
|
2894
|
+
*/
|
|
2895
|
+
const MarkdownBlock = memo(function MarkdownBlock({ node, taskBase, context, referenceDefinitions }) {
|
|
2896
|
+
return renderBlock(node, {
|
|
2897
|
+
...context,
|
|
2898
|
+
referenceDefinitions,
|
|
2899
|
+
taskCounter: { value: taskBase },
|
|
2900
|
+
keyCounter: { value: 0 }
|
|
2901
|
+
}, null, 0);
|
|
2902
|
+
}, (previous, next) => {
|
|
2903
|
+
return previous.taskBase === next.taskBase && previous.context === next.context && previous.definitionsKey === next.definitionsKey && (previous.node === next.node || previous.node.eq(next.node));
|
|
2904
|
+
});
|
|
2837
2905
|
/**
|
|
2838
2906
|
* Render Markdown to a read-only React tree that looks exactly like the editor
|
|
2839
2907
|
* in `hide` mark mode: inline marks, wikilink chips, images, tweet/YouTube
|
|
@@ -2845,32 +2913,8 @@ function renderBlock(node, context) {
|
|
|
2845
2913
|
* Callbacks (`onWikilinkClick`, etc.) and resolvers should be stable; pass them via
|
|
2846
2914
|
* `useCallback` to avoid re-rendering the whole tree.
|
|
2847
2915
|
*/
|
|
2848
|
-
function MarkdownView({ markdown, markMode = "hide", frontmatter = false, interactive = true, expandCollapsed = false, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, onWikilinkClick, onLinkClick, onImageClick, onFileClick, onTaskClick, className }) {
|
|
2849
|
-
const
|
|
2850
|
-
const doc = markdownToDoc(markdown, { frontmatter });
|
|
2851
|
-
const referenceIndex = collectReferenceDefinitions(doc);
|
|
2852
|
-
const context = {
|
|
2853
|
-
interactive,
|
|
2854
|
-
expandCollapsed,
|
|
2855
|
-
resolveImageUrl,
|
|
2856
|
-
resolveFileLink,
|
|
2857
|
-
resolveWikiEmbed,
|
|
2858
|
-
resolveWikilink,
|
|
2859
|
-
resolveFileInfo,
|
|
2860
|
-
onWikilinkClick: interactive ? onWikilinkClick : void 0,
|
|
2861
|
-
onLinkClick: interactive ? onLinkClick : void 0,
|
|
2862
|
-
onImageClick: interactive ? onImageClick : void 0,
|
|
2863
|
-
onFileClick: interactive ? onFileClick : void 0,
|
|
2864
|
-
onTaskClick: interactive ? onTaskClick : void 0,
|
|
2865
|
-
referenceDefinitions: referenceIndex.definitions,
|
|
2866
|
-
referenceDefinitionNodes: referenceIndex.nodes,
|
|
2867
|
-
taskCounter: { value: 0 },
|
|
2868
|
-
keyCounter: { value: 0 }
|
|
2869
|
-
};
|
|
2870
|
-
return doc.content.content.map((node) => renderBlock(node, context));
|
|
2871
|
-
}, [
|
|
2872
|
-
markdown,
|
|
2873
|
-
frontmatter,
|
|
2916
|
+
function MarkdownView({ markdown, markMode = "hide", frontmatter = false, interactive = true, expandCollapsed = false, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, resolveXPost, resolveYouTubeVideo, onWikilinkClick, onLinkClick, onImageClick, onFileClick, onTaskClick, className }) {
|
|
2917
|
+
const context = useMemo(() => ({
|
|
2874
2918
|
interactive,
|
|
2875
2919
|
expandCollapsed,
|
|
2876
2920
|
resolveImageUrl,
|
|
@@ -2878,16 +2922,48 @@ function MarkdownView({ markdown, markMode = "hide", frontmatter = false, intera
|
|
|
2878
2922
|
resolveWikiEmbed,
|
|
2879
2923
|
resolveWikilink,
|
|
2880
2924
|
resolveFileInfo,
|
|
2925
|
+
resolveXPost,
|
|
2926
|
+
resolveYouTubeVideo,
|
|
2927
|
+
onWikilinkClick: interactive ? onWikilinkClick : void 0,
|
|
2928
|
+
onLinkClick: interactive ? onLinkClick : void 0,
|
|
2929
|
+
onImageClick: interactive ? onImageClick : void 0,
|
|
2930
|
+
onFileClick: interactive ? onFileClick : void 0,
|
|
2931
|
+
onTaskClick: interactive ? onTaskClick : void 0
|
|
2932
|
+
}), [
|
|
2933
|
+
interactive,
|
|
2934
|
+
expandCollapsed,
|
|
2935
|
+
resolveImageUrl,
|
|
2936
|
+
resolveFileLink,
|
|
2937
|
+
resolveWikiEmbed,
|
|
2938
|
+
resolveWikilink,
|
|
2939
|
+
resolveFileInfo,
|
|
2940
|
+
resolveXPost,
|
|
2941
|
+
resolveYouTubeVideo,
|
|
2881
2942
|
onWikilinkClick,
|
|
2882
2943
|
onLinkClick,
|
|
2883
2944
|
onImageClick,
|
|
2884
2945
|
onFileClick,
|
|
2885
2946
|
onTaskClick
|
|
2886
2947
|
]);
|
|
2948
|
+
const { blocks, referenceDefinitions, definitionsKey } = useMemo(() => {
|
|
2949
|
+
const doc = markdownToDoc(markdown, { frontmatter });
|
|
2950
|
+
const referenceDefinitions = collectReferenceDefinitions(doc).definitions;
|
|
2951
|
+
return {
|
|
2952
|
+
blocks: splitBlocks(doc),
|
|
2953
|
+
referenceDefinitions,
|
|
2954
|
+
definitionsKey: definitionsSignature(referenceDefinitions)
|
|
2955
|
+
};
|
|
2956
|
+
}, [markdown, frontmatter]);
|
|
2887
2957
|
return /* @__PURE__ */ jsx("div", {
|
|
2888
2958
|
className: clsx("ProseMirror", "meowdown-content", className),
|
|
2889
2959
|
"data-mark-mode": markMode,
|
|
2890
|
-
children:
|
|
2960
|
+
children: blocks.map(({ node, taskBase }, index) => /* @__PURE__ */ jsx(MarkdownBlock, {
|
|
2961
|
+
node,
|
|
2962
|
+
taskBase,
|
|
2963
|
+
context,
|
|
2964
|
+
referenceDefinitions,
|
|
2965
|
+
definitionsKey
|
|
2966
|
+
}, index))
|
|
2891
2967
|
});
|
|
2892
2968
|
}
|
|
2893
2969
|
|
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.70.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@base-ui/react": "^1.7.0",
|
|
25
25
|
"@ocavue/utils": "^1.8.0",
|
|
26
|
+
"@post-embed/elements": "^0.2.0",
|
|
27
|
+
"@post-embed/types": "^0.2.0",
|
|
26
28
|
"@prosekit/core": "^0.13.2",
|
|
27
29
|
"@prosekit/pm": "^0.1.19",
|
|
28
30
|
"@prosekit/react": "^0.8.2",
|
|
@@ -31,7 +33,7 @@
|
|
|
31
33
|
"github-slugger": "^2.0.0",
|
|
32
34
|
"lucide-react": "^1.38.0",
|
|
33
35
|
"react-property": "^2.0.2",
|
|
34
|
-
"@meowdown/core": "0.
|
|
36
|
+
"@meowdown/core": "0.70.0"
|
|
35
37
|
},
|
|
36
38
|
"peerDependencies": {
|
|
37
39
|
"react": "^19.0.0",
|
|
@@ -49,12 +51,12 @@
|
|
|
49
51
|
"@css-modules-kit/codegen": "^1.4.0",
|
|
50
52
|
"@ocavue/tsconfig": "^0.7.1",
|
|
51
53
|
"@tsdown/css": "^0.23.0-rc.1",
|
|
52
|
-
"@types/react": "^19.
|
|
53
|
-
"@types/react-dom": "^19.
|
|
54
|
+
"@types/react": "^19.3.0",
|
|
55
|
+
"@types/react-dom": "^19.3.0",
|
|
54
56
|
"@vitest/browser-playwright": "^5.0.0",
|
|
55
57
|
"dedent": "^1.7.2",
|
|
56
|
-
"react": "^19.
|
|
57
|
-
"react-dom": "^19.
|
|
58
|
+
"react": "^19.3.0",
|
|
59
|
+
"react-dom": "^19.3.0",
|
|
58
60
|
"tsdown": "^0.23.0-rc.1",
|
|
59
61
|
"vitest": "^5.0.0",
|
|
60
62
|
"vitest-browser-commands": "^0.4.1",
|