@meowdown/react 0.68.2 → 0.69.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 +8 -3
- package/dist/index.js +105 -48
- package/dist/style.css +69 -8
- package/package.json +7 -7
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
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";
|
|
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
|
|
@@ -509,6 +509,11 @@ interface EditorProps {
|
|
|
509
509
|
* Class on the outer `.meowdown` wrapper div.
|
|
510
510
|
*/
|
|
511
511
|
wrapperClassName?: string;
|
|
512
|
+
/**
|
|
513
|
+
* React component that renders code blocks in place of the built-in one.
|
|
514
|
+
* `false` disables the React code block view.
|
|
515
|
+
*/
|
|
516
|
+
CodeBlockView?: ReactNodeViewComponent$1 | false | undefined;
|
|
512
517
|
/**
|
|
513
518
|
* Imperative handle for the editor.
|
|
514
519
|
*/
|
|
@@ -524,7 +529,7 @@ interface EditorProps {
|
|
|
524
529
|
*
|
|
525
530
|
* Callbacks and resolvers should be stable; pass them via `useCallback`.
|
|
526
531
|
*/
|
|
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;
|
|
532
|
+
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, CodeBlockView, handleRef, children }: EditorProps): ReactElement;
|
|
528
533
|
//#endregion
|
|
529
534
|
//#region src/components/markdown-view.d.ts
|
|
530
535
|
/**
|
|
@@ -675,4 +680,4 @@ interface WikilinkHoverCardProps {
|
|
|
675
680
|
*/
|
|
676
681
|
export declare function WikilinkHoverCard({ children, className }: WikilinkHoverCardProps): ReactNode;
|
|
677
682
|
//#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 };
|
|
683
|
+
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, 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, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, normalizeHref, renderMathInto } from "@meowdown/core";
|
|
2
|
+
import { Fragment, cloneElement, createElement, memo, 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, isReferenceDefinitionNode, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, normalizeHref, 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";
|
|
@@ -353,11 +353,11 @@ function CodeBlockToolbar({ code, language, setLanguage }) {
|
|
|
353
353
|
|
|
354
354
|
//#endregion
|
|
355
355
|
//#region src/extensions/code-block-view.ts
|
|
356
|
-
function defineCodeBlockView() {
|
|
356
|
+
function defineCodeBlockView(component = CodeBlockView) {
|
|
357
357
|
return union(defineReactNodeView({
|
|
358
358
|
name: "codeBlock",
|
|
359
359
|
contentAs: "code",
|
|
360
|
-
component
|
|
360
|
+
component
|
|
361
361
|
}), defineCodeBlockPreviewPlugin());
|
|
362
362
|
}
|
|
363
363
|
|
|
@@ -741,32 +741,33 @@ function LinkEditContent({ edit, resolveLinkPreview, onSubmit, onRemove }) {
|
|
|
741
741
|
const [text, setText] = useState(edit.text);
|
|
742
742
|
const [href, setHref] = useState(edit.link?.href ?? "");
|
|
743
743
|
const [debouncedHref, setDebouncedHref] = useState(href.trim());
|
|
744
|
-
const
|
|
744
|
+
const hrefInputRef = useRef(null);
|
|
745
745
|
const previewHref = normalizeHref(debouncedHref);
|
|
746
746
|
const previewState = useLinkPreview(previewHref || void 0, resolveLinkPreview);
|
|
747
747
|
const canUseTitle = !!edit.link && debouncedHref === href.trim() && isLinkTextForHref(text, previewHref);
|
|
748
|
+
const canSave = text.trim() !== "" && href.trim() !== "";
|
|
748
749
|
useEffect(() => {
|
|
749
750
|
const timer = window.setTimeout(() => setDebouncedHref(href.trim()), 400);
|
|
750
751
|
return () => window.clearTimeout(timer);
|
|
751
752
|
}, [href]);
|
|
752
753
|
useEffect(() => {
|
|
753
|
-
|
|
754
|
+
hrefInputRef.current?.focus();
|
|
755
|
+
hrefInputRef.current?.select();
|
|
754
756
|
}, []);
|
|
755
757
|
return /* @__PURE__ */ jsxs("form", {
|
|
756
758
|
className: link_menu_module_default.Form,
|
|
757
759
|
"data-testid": "link-popover-edit",
|
|
760
|
+
noValidate: true,
|
|
758
761
|
onSubmit: (event) => {
|
|
759
762
|
event.preventDefault();
|
|
760
|
-
if (
|
|
763
|
+
if (canSave) onSubmit(text, href);
|
|
761
764
|
},
|
|
762
765
|
children: [
|
|
763
766
|
/* @__PURE__ */ jsxs("label", {
|
|
764
767
|
className: link_menu_module_default.Field,
|
|
765
768
|
children: [/* @__PURE__ */ jsx("span", { children: "Text" }), /* @__PURE__ */ jsx("input", {
|
|
766
|
-
ref: textInputRef,
|
|
767
769
|
className: link_menu_module_default.Input,
|
|
768
770
|
value: text,
|
|
769
|
-
required: true,
|
|
770
771
|
"data-testid": "link-popover-text-input",
|
|
771
772
|
onChange: (event) => setText(event.target.value)
|
|
772
773
|
})]
|
|
@@ -774,10 +775,10 @@ function LinkEditContent({ edit, resolveLinkPreview, onSubmit, onRemove }) {
|
|
|
774
775
|
/* @__PURE__ */ jsxs("label", {
|
|
775
776
|
className: link_menu_module_default.Field,
|
|
776
777
|
children: [/* @__PURE__ */ jsx("span", { children: "Link" }), /* @__PURE__ */ jsx("input", {
|
|
778
|
+
ref: hrefInputRef,
|
|
777
779
|
className: link_menu_module_default.Input,
|
|
778
780
|
value: href,
|
|
779
|
-
|
|
780
|
-
placeholder: "Paste link...",
|
|
781
|
+
placeholder: "Paste link…",
|
|
781
782
|
"data-testid": "link-popover-input",
|
|
782
783
|
onChange: (event) => setHref(event.target.value)
|
|
783
784
|
})]
|
|
@@ -800,6 +801,7 @@ function LinkEditContent({ edit, resolveLinkPreview, onSubmit, onRemove }) {
|
|
|
800
801
|
/* @__PURE__ */ jsx("button", {
|
|
801
802
|
type: "submit",
|
|
802
803
|
className: link_menu_module_default.SaveButton,
|
|
804
|
+
disabled: !canSave,
|
|
803
805
|
"data-testid": "link-popover-submit",
|
|
804
806
|
children: "Save"
|
|
805
807
|
})
|
|
@@ -926,7 +928,7 @@ function LinkMenu({ onLinkClick, onLinkCopy, resolveLinkPreview, readOnly = fals
|
|
|
926
928
|
children: editing ? /* @__PURE__ */ jsx(LinkEditContent, {
|
|
927
929
|
edit,
|
|
928
930
|
resolveLinkPreview,
|
|
929
|
-
onRemove: handleEditRemove,
|
|
931
|
+
onRemove: edit.link ? handleEditRemove : void 0,
|
|
930
932
|
onSubmit: handleEditSubmit
|
|
931
933
|
}) : link ? /* @__PURE__ */ jsx(LinkInfoContent, {
|
|
932
934
|
href: link.href,
|
|
@@ -1923,7 +1925,7 @@ function findHeadingPosition(doc, fragment) {
|
|
|
1923
1925
|
});
|
|
1924
1926
|
return match;
|
|
1925
1927
|
}
|
|
1926
|
-
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 }) {
|
|
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, CodeBlockView, ref, children }) {
|
|
1927
1929
|
const [editor] = useState(() => {
|
|
1928
1930
|
const baseExtension = defineEditorExtension({
|
|
1929
1931
|
resolveFileLink,
|
|
@@ -1931,7 +1933,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
|
|
|
1931
1933
|
resolveWikilink,
|
|
1932
1934
|
markMode
|
|
1933
1935
|
});
|
|
1934
|
-
const extension = union(baseExtension, defineCodeBlockView());
|
|
1936
|
+
const extension = CodeBlockView === false ? baseExtension : union(baseExtension, defineCodeBlockView(CodeBlockView));
|
|
1935
1937
|
const editor = createEditor({ extension });
|
|
1936
1938
|
if (initialMarkdown) editor.setContent(markdownToDoc(initialMarkdown, {
|
|
1937
1939
|
nodes: editor.nodes,
|
|
@@ -2148,7 +2150,7 @@ const CARET_GLIDE_OFF = { "--meowdown-caret-glide": "0ms" };
|
|
|
2148
2150
|
*
|
|
2149
2151
|
* Callbacks and resolvers should be stable; pass them via `useCallback`.
|
|
2150
2152
|
*/
|
|
2151
|
-
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 }) {
|
|
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, CodeBlockView, handleRef, children }) {
|
|
2152
2154
|
const childRef = useRef(null);
|
|
2153
2155
|
useImperativeHandle(handleRef, () => {
|
|
2154
2156
|
function getMarkdown() {
|
|
@@ -2284,6 +2286,7 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
|
|
|
2284
2286
|
onSearchChange,
|
|
2285
2287
|
timeFormat,
|
|
2286
2288
|
editorClassName,
|
|
2289
|
+
CodeBlockView,
|
|
2287
2290
|
children
|
|
2288
2291
|
})
|
|
2289
2292
|
});
|
|
@@ -2764,10 +2767,7 @@ function renderInline(node, context) {
|
|
|
2764
2767
|
resolveFileLink: context.resolveFileLink,
|
|
2765
2768
|
resolveWikiEmbed: context.resolveWikiEmbed,
|
|
2766
2769
|
resolveWikilink: context.resolveWikilink
|
|
2767
|
-
}, {
|
|
2768
|
-
referenceDefinitions: context.referenceDefinitions,
|
|
2769
|
-
isReferenceDefinition: context.referenceDefinitionNodes.has(node)
|
|
2770
|
-
}).map(([from, to, marks]) => ({
|
|
2770
|
+
}, { referenceDefinitions: context.referenceDefinitions }).map(([from, to, marks]) => ({
|
|
2771
2771
|
text: text.slice(from, to),
|
|
2772
2772
|
marks: Mark.setFrom(marks)
|
|
2773
2773
|
})), 0, context);
|
|
@@ -2812,8 +2812,8 @@ function renderCodeBlock(node, key) {
|
|
|
2812
2812
|
language
|
|
2813
2813
|
}, key);
|
|
2814
2814
|
}
|
|
2815
|
-
function renderBlock(node, context) {
|
|
2816
|
-
if (
|
|
2815
|
+
function renderBlock(node, context, parent, index) {
|
|
2816
|
+
if (isReferenceDefinitionNode(node, parent, index)) return null;
|
|
2817
2817
|
const key = context.keyCounter.value++;
|
|
2818
2818
|
const typeName = node.type.name;
|
|
2819
2819
|
let handleTaskClick;
|
|
@@ -2827,11 +2827,64 @@ function renderBlock(node, context) {
|
|
|
2827
2827
|
const inline = renderInline(node, context);
|
|
2828
2828
|
return toDOM ? outputSpecToReact(toDOM(node), inline, context) : /* @__PURE__ */ jsx(Fragment, { children: inline }, key);
|
|
2829
2829
|
}
|
|
2830
|
-
const children = node.content.content.map((child) =>
|
|
2830
|
+
const children = node.content.content.map((child, childIndex) => {
|
|
2831
|
+
return renderBlock(child, context, node, childIndex);
|
|
2832
|
+
});
|
|
2831
2833
|
const reactNode = toDOM ? outputSpecToReact(toDOM(node), children, context) : /* @__PURE__ */ jsx(Fragment, { children }, key);
|
|
2832
2834
|
if (typeName === "list" && handleTaskClick && typeof reactNode !== "string" && reactNode != null) return cloneElement(reactNode, { onClick: handleTaskClick });
|
|
2833
2835
|
return reactNode;
|
|
2834
2836
|
}
|
|
2837
|
+
function isTaskList(node) {
|
|
2838
|
+
return isNodeOfType(node, "list") && node.attrs.kind === "task";
|
|
2839
|
+
}
|
|
2840
|
+
/**
|
|
2841
|
+
* Checkboxes a block renders, in the same pre-order `renderBlock` walks, so a
|
|
2842
|
+
* block's first checkbox index is the sum over the blocks before it.
|
|
2843
|
+
*/
|
|
2844
|
+
function countTaskItems(node) {
|
|
2845
|
+
let count = isTaskList(node) ? 1 : 0;
|
|
2846
|
+
node.descendants((child) => {
|
|
2847
|
+
if (isTaskList(child)) count++;
|
|
2848
|
+
return true;
|
|
2849
|
+
});
|
|
2850
|
+
return count;
|
|
2851
|
+
}
|
|
2852
|
+
/**
|
|
2853
|
+
* The effective definitions as one string, so blocks can compare them by
|
|
2854
|
+
* content: `collectReferenceDefinitions` builds a new map on every parse.
|
|
2855
|
+
*/
|
|
2856
|
+
function definitionsSignature(definitions) {
|
|
2857
|
+
let signature = "";
|
|
2858
|
+
for (const { key, href, title } of definitions.values()) signature += `${key}\0${href}\0${title}\n`;
|
|
2859
|
+
return signature;
|
|
2860
|
+
}
|
|
2861
|
+
function splitBlocks(doc) {
|
|
2862
|
+
const blocks = [];
|
|
2863
|
+
let taskBase = 0;
|
|
2864
|
+
for (const node of doc.content.content) {
|
|
2865
|
+
blocks.push({
|
|
2866
|
+
node,
|
|
2867
|
+
taskBase
|
|
2868
|
+
});
|
|
2869
|
+
taskBase += countTaskItems(node);
|
|
2870
|
+
}
|
|
2871
|
+
return blocks;
|
|
2872
|
+
}
|
|
2873
|
+
/**
|
|
2874
|
+
* One top-level block. Memoized on the block's own content (`Node.eq`), its
|
|
2875
|
+
* first checkbox index, the shared props object, and the definitions'
|
|
2876
|
+
* content, so a growing document re-renders only the block that changed.
|
|
2877
|
+
*/
|
|
2878
|
+
const MarkdownBlock = memo(function MarkdownBlock({ node, taskBase, context, referenceDefinitions }) {
|
|
2879
|
+
return renderBlock(node, {
|
|
2880
|
+
...context,
|
|
2881
|
+
referenceDefinitions,
|
|
2882
|
+
taskCounter: { value: taskBase },
|
|
2883
|
+
keyCounter: { value: 0 }
|
|
2884
|
+
}, null, 0);
|
|
2885
|
+
}, (previous, next) => {
|
|
2886
|
+
return previous.taskBase === next.taskBase && previous.context === next.context && previous.definitionsKey === next.definitionsKey && (previous.node === next.node || previous.node.eq(next.node));
|
|
2887
|
+
});
|
|
2835
2888
|
/**
|
|
2836
2889
|
* Render Markdown to a read-only React tree that looks exactly like the editor
|
|
2837
2890
|
* in `hide` mark mode: inline marks, wikilink chips, images, tweet/YouTube
|
|
@@ -2844,31 +2897,20 @@ function renderBlock(node, context) {
|
|
|
2844
2897
|
* `useCallback` to avoid re-rendering the whole tree.
|
|
2845
2898
|
*/
|
|
2846
2899
|
function MarkdownView({ markdown, markMode = "hide", frontmatter = false, interactive = true, expandCollapsed = false, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveWikilink, resolveFileInfo, onWikilinkClick, onLinkClick, onImageClick, onFileClick, onTaskClick, className }) {
|
|
2847
|
-
const
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
onFileClick: interactive ? onFileClick : void 0,
|
|
2862
|
-
onTaskClick: interactive ? onTaskClick : void 0,
|
|
2863
|
-
referenceDefinitions: referenceIndex.definitions,
|
|
2864
|
-
referenceDefinitionNodes: referenceIndex.nodes,
|
|
2865
|
-
taskCounter: { value: 0 },
|
|
2866
|
-
keyCounter: { value: 0 }
|
|
2867
|
-
};
|
|
2868
|
-
return doc.content.content.map((node) => renderBlock(node, context));
|
|
2869
|
-
}, [
|
|
2870
|
-
markdown,
|
|
2871
|
-
frontmatter,
|
|
2900
|
+
const context = useMemo(() => ({
|
|
2901
|
+
interactive,
|
|
2902
|
+
expandCollapsed,
|
|
2903
|
+
resolveImageUrl,
|
|
2904
|
+
resolveFileLink,
|
|
2905
|
+
resolveWikiEmbed,
|
|
2906
|
+
resolveWikilink,
|
|
2907
|
+
resolveFileInfo,
|
|
2908
|
+
onWikilinkClick: interactive ? onWikilinkClick : void 0,
|
|
2909
|
+
onLinkClick: interactive ? onLinkClick : void 0,
|
|
2910
|
+
onImageClick: interactive ? onImageClick : void 0,
|
|
2911
|
+
onFileClick: interactive ? onFileClick : void 0,
|
|
2912
|
+
onTaskClick: interactive ? onTaskClick : void 0
|
|
2913
|
+
}), [
|
|
2872
2914
|
interactive,
|
|
2873
2915
|
expandCollapsed,
|
|
2874
2916
|
resolveImageUrl,
|
|
@@ -2882,10 +2924,25 @@ function MarkdownView({ markdown, markMode = "hide", frontmatter = false, intera
|
|
|
2882
2924
|
onFileClick,
|
|
2883
2925
|
onTaskClick
|
|
2884
2926
|
]);
|
|
2927
|
+
const { blocks, referenceDefinitions, definitionsKey } = useMemo(() => {
|
|
2928
|
+
const doc = markdownToDoc(markdown, { frontmatter });
|
|
2929
|
+
const referenceDefinitions = collectReferenceDefinitions(doc).definitions;
|
|
2930
|
+
return {
|
|
2931
|
+
blocks: splitBlocks(doc),
|
|
2932
|
+
referenceDefinitions,
|
|
2933
|
+
definitionsKey: definitionsSignature(referenceDefinitions)
|
|
2934
|
+
};
|
|
2935
|
+
}, [markdown, frontmatter]);
|
|
2885
2936
|
return /* @__PURE__ */ jsx("div", {
|
|
2886
2937
|
className: clsx("ProseMirror", "meowdown-content", className),
|
|
2887
2938
|
"data-mark-mode": markMode,
|
|
2888
|
-
children:
|
|
2939
|
+
children: blocks.map(({ node, taskBase }, index) => /* @__PURE__ */ jsx(MarkdownBlock, {
|
|
2940
|
+
node,
|
|
2941
|
+
taskBase,
|
|
2942
|
+
context,
|
|
2943
|
+
referenceDefinitions,
|
|
2944
|
+
definitionsKey
|
|
2945
|
+
}, index))
|
|
2889
2946
|
});
|
|
2890
2947
|
}
|
|
2891
2948
|
|
package/dist/style.css
CHANGED
|
@@ -533,28 +533,44 @@
|
|
|
533
533
|
|
|
534
534
|
& > span {
|
|
535
535
|
color: var(--meowdown-muted);
|
|
536
|
-
font-size: .
|
|
537
|
-
font-weight:
|
|
536
|
+
font-size: .8125rem;
|
|
537
|
+
font-weight: 500;
|
|
538
538
|
}
|
|
539
539
|
}
|
|
540
540
|
|
|
541
541
|
.meow_Input_IjcqKG {
|
|
542
542
|
box-sizing: border-box;
|
|
543
543
|
width: 100%;
|
|
544
|
+
height: 2rem;
|
|
544
545
|
font: inherit;
|
|
545
546
|
color: var(--meowdown-text);
|
|
546
|
-
border: 1px solid var(--meowdown-border);
|
|
547
|
+
border: 1px solid var(--meowdown-input-border);
|
|
547
548
|
background: none;
|
|
548
549
|
border-radius: .5rem;
|
|
549
|
-
padding:
|
|
550
|
+
padding: 0 .625rem;
|
|
551
|
+
transition: border-color .1s, box-shadow .1s;
|
|
552
|
+
|
|
553
|
+
&::placeholder {
|
|
554
|
+
color: var(--meowdown-placeholder);
|
|
555
|
+
}
|
|
550
556
|
|
|
551
557
|
&:focus {
|
|
552
|
-
|
|
553
|
-
|
|
558
|
+
border-color: var(--meowdown-focus-ring);
|
|
559
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--meowdown-focus-ring) 50%, transparent);
|
|
560
|
+
outline: none;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
@media (pointer: coarse) {
|
|
564
|
+
height: 2.75rem;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
@media (prefers-reduced-motion: reduce) {
|
|
568
|
+
transition: none;
|
|
554
569
|
}
|
|
555
570
|
}
|
|
556
571
|
|
|
557
572
|
.meow_FormActions_IjcqKG {
|
|
573
|
+
justify-content: flex-end;
|
|
558
574
|
align-items: center;
|
|
559
575
|
gap: .375rem;
|
|
560
576
|
padding-top: .125rem;
|
|
@@ -569,19 +585,64 @@
|
|
|
569
585
|
border: 0;
|
|
570
586
|
border-radius: .5rem;
|
|
571
587
|
padding: .375rem .625rem;
|
|
588
|
+
font-weight: 500;
|
|
589
|
+
transition: background-color .1s;
|
|
590
|
+
|
|
591
|
+
&:focus-visible {
|
|
592
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--meowdown-focus-ring) 50%, transparent);
|
|
593
|
+
outline: none;
|
|
594
|
+
}
|
|
572
595
|
|
|
573
596
|
@media (pointer: coarse) {
|
|
574
597
|
min-height: 2.75rem;
|
|
575
598
|
}
|
|
599
|
+
|
|
600
|
+
@media (prefers-reduced-motion: reduce) {
|
|
601
|
+
transition: none;
|
|
602
|
+
}
|
|
576
603
|
}
|
|
577
604
|
|
|
578
605
|
.meow_RemoveButton_IjcqKG {
|
|
579
|
-
color: var(--meowdown-danger
|
|
606
|
+
color: var(--meowdown-danger);
|
|
607
|
+
margin-right: auto;
|
|
608
|
+
|
|
609
|
+
&:hover {
|
|
610
|
+
background: color-mix(in oklab, var(--meowdown-danger) 10%, transparent);
|
|
611
|
+
}
|
|
580
612
|
}
|
|
581
613
|
|
|
582
614
|
.meow_UseTitleButton_IjcqKG {
|
|
583
615
|
color: var(--meowdown-accent);
|
|
584
|
-
|
|
616
|
+
|
|
617
|
+
&:hover {
|
|
618
|
+
background: var(--meowdown-popover-hover-bg);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
.meow_SaveButton_IjcqKG {
|
|
623
|
+
transition: background-color .1s, transform .1s ease-out;
|
|
624
|
+
|
|
625
|
+
&:hover:not(:disabled) {
|
|
626
|
+
background: color-mix(in oklab, var(--meowdown-accent) 80%, transparent);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
&:active:not(:disabled) {
|
|
630
|
+
transform: translateY(1px);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
&:focus-visible {
|
|
634
|
+
box-shadow: 0 0 0 3px color-mix(in oklab, var(--meowdown-focus-ring) 50%, transparent);
|
|
635
|
+
outline: none;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
&:disabled {
|
|
639
|
+
opacity: .5;
|
|
640
|
+
cursor: default;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
@media (prefers-reduced-motion: reduce) {
|
|
644
|
+
transition: none;
|
|
645
|
+
}
|
|
585
646
|
}
|
|
586
647
|
.meow_Positioner_DPJ5ia {
|
|
587
648
|
z-index: 50;
|
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.69.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@base-ui/react": "^1.7.0",
|
|
25
|
-
"@ocavue/utils": "^1.
|
|
25
|
+
"@ocavue/utils": "^1.8.0",
|
|
26
26
|
"@prosekit/core": "^0.13.2",
|
|
27
27
|
"@prosekit/pm": "^0.1.19",
|
|
28
28
|
"@prosekit/react": "^0.8.2",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"github-slugger": "^2.0.0",
|
|
32
32
|
"lucide-react": "^1.38.0",
|
|
33
33
|
"react-property": "^2.0.2",
|
|
34
|
-
"@meowdown/core": "0.
|
|
34
|
+
"@meowdown/core": "0.69.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": "^19.0.0",
|
|
@@ -49,12 +49,12 @@
|
|
|
49
49
|
"@css-modules-kit/codegen": "^1.4.0",
|
|
50
50
|
"@ocavue/tsconfig": "^0.7.1",
|
|
51
51
|
"@tsdown/css": "^0.23.0-rc.1",
|
|
52
|
-
"@types/react": "^19.
|
|
53
|
-
"@types/react-dom": "^19.
|
|
52
|
+
"@types/react": "^19.3.0",
|
|
53
|
+
"@types/react-dom": "^19.3.0",
|
|
54
54
|
"@vitest/browser-playwright": "^5.0.0",
|
|
55
55
|
"dedent": "^1.7.2",
|
|
56
|
-
"react": "^19.
|
|
57
|
-
"react-dom": "^19.
|
|
56
|
+
"react": "^19.3.0",
|
|
57
|
+
"react-dom": "^19.3.0",
|
|
58
58
|
"tsdown": "^0.23.0-rc.1",
|
|
59
59
|
"vitest": "^5.0.0",
|
|
60
60
|
"vitest-browser-commands": "^0.4.1",
|