@meowdown/react 0.38.0 → 0.40.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/dist/index.d.ts +34 -2
- package/dist/index.js +174 -54
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactElement, ReactNode, Ref } from "react";
|
|
2
|
-
import { AcceptPendingReplacementOptions, ExitBoundaryHandler, FileClickHandler, FileLinkResolver, FilePasteOptions, FileViewOptions, ImageClickHandler, ImageOptions, LinkClickHandler, LinkCopyHandler, MarkMode, PendingReplacement, PendingReplacementOutcome, PlaceholderOptions, StartPendingReplacementOptions, TagClickHandler, TypedEditor, WikilinkClickHandler } from "@meowdown/core";
|
|
2
|
+
import { AcceptPendingReplacementOptions, ExitBoundaryHandler, FileClickHandler, FileLinkResolver, FilePasteOptions, FileViewOptions, ImageClickHandler, ImageOptions, LinkClickHandler, LinkCopyHandler, ListMarker, MarkMode, PendingReplacement, PendingReplacementOutcome, PlaceholderOptions, StartPendingReplacementOptions, TagClickHandler, TypedEditor, WikilinkClickHandler } from "@meowdown/core";
|
|
3
3
|
import { SelectionJSON, SelectionJSON as SelectionJSON$1 } from "@prosekit/core";
|
|
4
4
|
import { useEditor, useExtension, useKeymap } from "@prosekit/react";
|
|
5
5
|
|
|
@@ -404,6 +404,35 @@ declare function MeowdownEditor({
|
|
|
404
404
|
}: EditorProps): import("react").JSX.Element;
|
|
405
405
|
//#endregion
|
|
406
406
|
//#region src/components/markdown-view.d.ts
|
|
407
|
+
/** Payload for {@link TaskClickHandler}. */
|
|
408
|
+
interface TaskClickPayload {
|
|
409
|
+
/**
|
|
410
|
+
* Zero-based position of the clicked checkbox among all the checkboxes this
|
|
411
|
+
* view renders, in document order. Stable for a given `markdown`, so a host
|
|
412
|
+
* can map it back to the corresponding task item in its own parse of the
|
|
413
|
+
* same source.
|
|
414
|
+
*/
|
|
415
|
+
index: number;
|
|
416
|
+
/** The checkbox's rendered state. The view never flips it — see the handler doc. */
|
|
417
|
+
checked: boolean;
|
|
418
|
+
/** The item's list marker as written (`+` renders a circle checkbox, `-`/`*` a square). */
|
|
419
|
+
marker: ListMarker;
|
|
420
|
+
/**
|
|
421
|
+
* First line of the item's own inline content, exactly as it appears in the
|
|
422
|
+
* source after the `[ ]`/`[x]` marker and one space. A host locating the task
|
|
423
|
+
* by {@link index} can cross-check this against its own parse and refuse a
|
|
424
|
+
* mismatch instead of toggling the wrong item.
|
|
425
|
+
*/
|
|
426
|
+
text: string;
|
|
427
|
+
/** The originating click. Read modifier keys or position a popover from it. */
|
|
428
|
+
event: globalThis.MouseEvent;
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Called when a rendered task checkbox is clicked. The view is a pure render
|
|
432
|
+
* of `markdown` and never flips the box itself: apply the toggle to the
|
|
433
|
+
* source and re-render, exactly like the other click handlers.
|
|
434
|
+
*/
|
|
435
|
+
type TaskClickHandler = (payload: TaskClickPayload) => void;
|
|
407
436
|
interface MarkdownViewProps {
|
|
408
437
|
/** The Markdown to render. Live: changing it re-renders the content. */
|
|
409
438
|
markdown: string;
|
|
@@ -419,6 +448,8 @@ interface MarkdownViewProps {
|
|
|
419
448
|
onLinkClick?: LinkClickHandler;
|
|
420
449
|
/** Called when a rendered image is clicked. Pass a stable function. */
|
|
421
450
|
onImageClick?: ImageClickHandler;
|
|
451
|
+
/** Called when a rendered task checkbox is clicked. Pass a stable function. */
|
|
452
|
+
onTaskClick?: TaskClickHandler;
|
|
422
453
|
/** Extra class on the content root (alongside `ProseMirror meowdown-content`). */
|
|
423
454
|
className?: string;
|
|
424
455
|
}
|
|
@@ -441,7 +472,8 @@ declare function MarkdownView({
|
|
|
441
472
|
onWikilinkClick,
|
|
442
473
|
onLinkClick,
|
|
443
474
|
onImageClick,
|
|
475
|
+
onTaskClick,
|
|
444
476
|
className
|
|
445
477
|
}: MarkdownViewProps): ReactElement;
|
|
446
478
|
//#endregion
|
|
447
|
-
export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, MarkdownView, type MarkdownViewProps, MeowdownEditor, type PendingReplacementResolveHandler, type SelectionHint, type SelectionJSON, type SelectionMenuContext, type SelectionMenuItem, type SelectionMenuSearchHandler, type SlashMenuItem, type SlashMenuSearchHandler, type TagItem, type TagSearchHandler, type TimeFormat, type WikilinkItem, type WikilinkSearchHandler, useEditor, useExtension, useKeymap };
|
|
479
|
+
export { type EditorHandle, type EditorMode, type EditorProps, type EditorStateSnapshot, MarkdownView, type MarkdownViewProps, MeowdownEditor, 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 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, createElement, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
|
-
import { codeBlockLanguages, defaultResolveImageUrl, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineHTMLPaste, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, defineMarkdownCopy, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSpellCheckPlugin, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkTrigger, docToMarkdown, getCodeTokens, getMarkBuilders, getPendingReplacement, getSelectedText, getTableColumnAlign, getVirtualElementFromRange, inlineTextToMarkChunks, isCodeBlockPreviewHiddenDecoration, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, renderMathInto } from "@meowdown/core";
|
|
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, defineHTMLPaste, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, defineMarkdownCopy, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSpellCheckPlugin, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkTrigger, docToMarkdown, getCodeTokens, getMarkBuilders, getPendingReplacement, getSelectedText, getTableColumnAlign, getVirtualElementFromRange, inlineTextToMarkChunks, isCodeBlockPreviewHiddenDecoration, 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";
|
|
@@ -15,6 +15,7 @@ import { AutocompleteEmpty, AutocompleteItem, AutocompletePopup, AutocompletePos
|
|
|
15
15
|
import { MenuItem, MenuPopup, MenuPositioner } from "@prosekit/react/menu";
|
|
16
16
|
import { TableHandleColumnMenuRoot, TableHandleColumnMenuTrigger, TableHandleColumnPopup, TableHandleColumnPositioner, TableHandleDragPreview, TableHandleDropIndicator, TableHandleRoot, TableHandleRowMenuRoot, TableHandleRowMenuTrigger, TableHandleRowPopup, TableHandleRowPositioner } from "@prosekit/react/table-handle";
|
|
17
17
|
import { Mark } from "@prosekit/pm/model";
|
|
18
|
+
import { BOOLEAN, OVERLOADED_BOOLEAN, getPropertyInfo, possibleStandardNames } from "react-property";
|
|
18
19
|
|
|
19
20
|
//#region src/hooks/use-katex.ts
|
|
20
21
|
/**
|
|
@@ -998,6 +999,9 @@ var autocomplete_menu_module_default = {
|
|
|
998
999
|
//#endregion
|
|
999
1000
|
//#region src/components/slash-menu.tsx
|
|
1000
1001
|
const regex$2 = canUseRegexLookbehind() ? /(?<!\S)\/(\S.*)?$/u : /\/(\S.*)?$/u;
|
|
1002
|
+
const defaultOnFileSaveError = (error) => {
|
|
1003
|
+
console.error("[meowdown] failed to save attached file:", error);
|
|
1004
|
+
};
|
|
1001
1005
|
function SlashMenuItem({ label, keywords, detail, kbd, onSelect }) {
|
|
1002
1006
|
return /* @__PURE__ */ jsxs(AutocompleteItem, {
|
|
1003
1007
|
value: [label, ...keywords ?? []].join(" "),
|
|
@@ -1019,8 +1023,9 @@ function SlashMenuItem({ label, keywords, detail, kbd, onSelect }) {
|
|
|
1019
1023
|
function selectionInTableCell(editor) {
|
|
1020
1024
|
return isSelectionInTableCell(editor.state);
|
|
1021
1025
|
}
|
|
1022
|
-
function SlashMenu({ timeFormat = "12", onSlashMenuSearch }) {
|
|
1026
|
+
function SlashMenu({ timeFormat = "12", onSlashMenuSearch, onFilePaste, onFileSaveError }) {
|
|
1023
1027
|
const editor = useEditor$1();
|
|
1028
|
+
const fileInputRef = useRef(null);
|
|
1024
1029
|
const inTableCell = useEditorDerivedValue(selectionInTableCell);
|
|
1025
1030
|
const [open, setOpen] = useState(false);
|
|
1026
1031
|
const [query, setQuery] = useState("");
|
|
@@ -1045,11 +1050,42 @@ function SlashMenu({ timeFormat = "12", onSlashMenuSearch }) {
|
|
|
1045
1050
|
query,
|
|
1046
1051
|
fetchHostItems
|
|
1047
1052
|
]);
|
|
1048
|
-
|
|
1053
|
+
const openFilePicker = useCallback(() => {
|
|
1054
|
+
fileInputRef.current?.click();
|
|
1055
|
+
}, []);
|
|
1056
|
+
const handleFileInputChange = useCallback(async (event) => {
|
|
1057
|
+
const input = event.currentTarget;
|
|
1058
|
+
const files = Array.from(input.files ?? []);
|
|
1059
|
+
input.value = "";
|
|
1060
|
+
if (!onFilePaste || files.length === 0) return;
|
|
1061
|
+
const onSaveError = onFileSaveError ?? defaultOnFileSaveError;
|
|
1062
|
+
const markdown = [];
|
|
1063
|
+
for (const file of files) try {
|
|
1064
|
+
const destination = await onFilePaste(file);
|
|
1065
|
+
if (destination) markdown.push(buildFileMarkdown(file, destination));
|
|
1066
|
+
} catch (error) {
|
|
1067
|
+
onSaveError(error, file);
|
|
1068
|
+
}
|
|
1069
|
+
if (markdown.length === 0) return;
|
|
1070
|
+
editor.focus();
|
|
1071
|
+
editor.commands.insertText({ text: markdown.join("\n") });
|
|
1072
|
+
}, [
|
|
1073
|
+
editor,
|
|
1074
|
+
onFilePaste,
|
|
1075
|
+
onFileSaveError
|
|
1076
|
+
]);
|
|
1077
|
+
return /* @__PURE__ */ jsxs(AutocompleteRoot, {
|
|
1049
1078
|
regex: regex$2,
|
|
1050
1079
|
onOpenChange: (event) => setOpen(event.detail),
|
|
1051
1080
|
onQueryChange: (event) => setQuery(event.detail),
|
|
1052
|
-
children: /* @__PURE__ */ jsx(
|
|
1081
|
+
children: [onFilePaste ? /* @__PURE__ */ jsx("input", {
|
|
1082
|
+
ref: fileInputRef,
|
|
1083
|
+
"data-testid": "slash-menu-file-input",
|
|
1084
|
+
type: "file",
|
|
1085
|
+
multiple: true,
|
|
1086
|
+
hidden: true,
|
|
1087
|
+
onChange: handleFileInputChange
|
|
1088
|
+
}) : null, /* @__PURE__ */ jsx(AutocompletePositioner, {
|
|
1053
1089
|
className: autocomplete_menu_module_default.Positioner,
|
|
1054
1090
|
children: /* @__PURE__ */ jsxs(AutocompletePopup, {
|
|
1055
1091
|
className: autocomplete_menu_module_default.Popup,
|
|
@@ -1125,6 +1161,15 @@ function SlashMenu({ timeFormat = "12", onSlashMenuSearch }) {
|
|
|
1125
1161
|
label: "Now",
|
|
1126
1162
|
onSelect: () => editor.commands.insertText({ text: formatNowTime(timeFormat) })
|
|
1127
1163
|
}),
|
|
1164
|
+
onFilePaste ? /* @__PURE__ */ jsx(SlashMenuItem, {
|
|
1165
|
+
label: "Attach file",
|
|
1166
|
+
keywords: [
|
|
1167
|
+
"attachment",
|
|
1168
|
+
"file",
|
|
1169
|
+
"upload"
|
|
1170
|
+
],
|
|
1171
|
+
onSelect: openFilePicker
|
|
1172
|
+
}) : null,
|
|
1128
1173
|
hostItems.map((item) => /* @__PURE__ */ jsx(SlashMenuItem, {
|
|
1129
1174
|
label: item.label,
|
|
1130
1175
|
keywords: item.keywords,
|
|
@@ -1137,7 +1182,7 @@ function SlashMenu({ timeFormat = "12", onSlashMenuSearch }) {
|
|
|
1137
1182
|
})
|
|
1138
1183
|
]
|
|
1139
1184
|
})
|
|
1140
|
-
})
|
|
1185
|
+
})]
|
|
1141
1186
|
});
|
|
1142
1187
|
}
|
|
1143
1188
|
|
|
@@ -1643,7 +1688,9 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
|
|
|
1643
1688
|
blockHandle && !readOnly && /* @__PURE__ */ jsx(DropIndicator$1, {}),
|
|
1644
1689
|
/* @__PURE__ */ jsx(SlashMenu, {
|
|
1645
1690
|
timeFormat,
|
|
1646
|
-
onSlashMenuSearch
|
|
1691
|
+
onSlashMenuSearch,
|
|
1692
|
+
onFilePaste,
|
|
1693
|
+
onFileSaveError
|
|
1647
1694
|
}),
|
|
1648
1695
|
!readOnly && /* @__PURE__ */ jsx(LinkMenu, {
|
|
1649
1696
|
onLinkClick,
|
|
@@ -1788,51 +1835,102 @@ function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashM
|
|
|
1788
1835
|
}
|
|
1789
1836
|
|
|
1790
1837
|
//#endregion
|
|
1791
|
-
//#region src/components/
|
|
1792
|
-
const
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1838
|
+
//#region src/components/attributes-to-props.ts
|
|
1839
|
+
const UNCONTROLLED_COMPONENT_ATTRIBUTES = ["checked", "value"];
|
|
1840
|
+
const UNCONTROLLED_COMPONENT_NAMES = [
|
|
1841
|
+
"input",
|
|
1842
|
+
"select",
|
|
1843
|
+
"textarea"
|
|
1844
|
+
];
|
|
1845
|
+
/**
|
|
1846
|
+
* Converts HTML/SVG DOM attributes to React props.
|
|
1847
|
+
*
|
|
1848
|
+
* @param attributes - HTML/SVG DOM attributes.
|
|
1849
|
+
* @param nodeName - DOM node name.
|
|
1850
|
+
* @returns - React props.
|
|
1851
|
+
*/
|
|
1852
|
+
function attributesToProps(attributes = {}, nodeName) {
|
|
1853
|
+
const props = {};
|
|
1854
|
+
const isInputValueOnly = nodeName === "input" || !!attributes["reset"] || !!attributes["submit"];
|
|
1855
|
+
for (const [attributeName, attributeValue] of Object.entries(attributes)) {
|
|
1856
|
+
if (attributeValue === void 0) continue;
|
|
1857
|
+
const attributeNameLowerCased = attributeName.toLowerCase();
|
|
1858
|
+
if (attributeNameLowerCased === "style") continue;
|
|
1859
|
+
if (attributeNameLowerCased === "contenteditable") continue;
|
|
1860
|
+
if (attributeNameLowerCased.startsWith("aria-") || attributeNameLowerCased.startsWith("data-")) {
|
|
1861
|
+
props[attributeName] = attributeValue;
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
let propName = getPropName(attributeNameLowerCased);
|
|
1865
|
+
if (propName) {
|
|
1866
|
+
const propertyInfo = getPropertyInfo(propName);
|
|
1867
|
+
if (UNCONTROLLED_COMPONENT_ATTRIBUTES.includes(propName) && UNCONTROLLED_COMPONENT_NAMES.includes(nodeName) && !isInputValueOnly) propName = getPropName("default" + attributeNameLowerCased);
|
|
1868
|
+
props[propName] = attributeValue;
|
|
1869
|
+
switch (propertyInfo?.type) {
|
|
1870
|
+
case BOOLEAN:
|
|
1871
|
+
props[propName] = true;
|
|
1872
|
+
break;
|
|
1873
|
+
case OVERLOADED_BOOLEAN:
|
|
1874
|
+
if (attributeValue === "") props[propName] = true;
|
|
1875
|
+
break;
|
|
1876
|
+
}
|
|
1877
|
+
continue;
|
|
1878
|
+
}
|
|
1879
|
+
props[attributeName] = attributeValue;
|
|
1807
1880
|
}
|
|
1808
1881
|
return props;
|
|
1809
1882
|
}
|
|
1810
1883
|
/**
|
|
1811
|
-
*
|
|
1812
|
-
*
|
|
1813
|
-
*
|
|
1814
|
-
*
|
|
1884
|
+
* Gets prop name from lowercased attribute name.
|
|
1885
|
+
*
|
|
1886
|
+
* @param attributeName - Lowercased attribute name.
|
|
1887
|
+
* @returns - Prop name.
|
|
1815
1888
|
*/
|
|
1816
|
-
function
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1889
|
+
function getPropName(attributeName) {
|
|
1890
|
+
return possibleStandardNames[attributeName];
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
//#endregion
|
|
1894
|
+
//#region src/components/dom-output-spec.tsx
|
|
1895
|
+
function normalizeDOMOutputSpec(domSpec) {
|
|
1896
|
+
const spec = domSpec;
|
|
1897
|
+
if (!spec || !Array.isArray(spec)) return;
|
|
1898
|
+
const tag = spec[0];
|
|
1821
1899
|
let childStart = 1;
|
|
1822
1900
|
let attrs;
|
|
1823
|
-
const second =
|
|
1901
|
+
const second = spec[1];
|
|
1824
1902
|
if (second != null && second !== 0 && typeof second === "object" && !Array.isArray(second)) {
|
|
1825
1903
|
attrs = second;
|
|
1826
1904
|
childStart = 2;
|
|
1827
1905
|
}
|
|
1828
|
-
const
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1906
|
+
const rest = spec.slice(childStart);
|
|
1907
|
+
return [
|
|
1908
|
+
tag,
|
|
1909
|
+
attrs,
|
|
1910
|
+
rest
|
|
1911
|
+
];
|
|
1832
1912
|
}
|
|
1833
1913
|
|
|
1834
1914
|
//#endregion
|
|
1835
1915
|
//#region src/components/markdown-view.tsx
|
|
1916
|
+
/**
|
|
1917
|
+
* Convert a ProseMirror `DOMOutputSpec` into a React node, substituting `content`
|
|
1918
|
+
* for the spec's content hole (`0`). Reused for every node/mark spec the static
|
|
1919
|
+
* walker does not special-case, so blocks and plain marks render off their real
|
|
1920
|
+
* `toDOM`, exactly as the editor serializes them.
|
|
1921
|
+
*/
|
|
1922
|
+
function outputSpecToReact(spec, content, context) {
|
|
1923
|
+
const key = context.keyCounter.value++;
|
|
1924
|
+
if (typeof spec === "string") return spec;
|
|
1925
|
+
if (spec === 0) return /* @__PURE__ */ jsx(Fragment, { children: content }, key);
|
|
1926
|
+
const normalized = normalizeDOMOutputSpec(spec);
|
|
1927
|
+
if (!normalized) return null;
|
|
1928
|
+
const [tag, attrs, rest] = normalized;
|
|
1929
|
+
const reactProps = { ...attributesToProps(attrs, tag) };
|
|
1930
|
+
reactProps.key = `${key} ${JSON.stringify(attrs)}`;
|
|
1931
|
+
if (tag === "input" && attrs?.["type"] === "checkbox") reactProps.readOnly = true;
|
|
1932
|
+
return createElement(tag, reactProps, ...rest.map((child) => outputSpecToReact(child, content, context)));
|
|
1933
|
+
}
|
|
1836
1934
|
function WikilinkChip(props) {
|
|
1837
1935
|
const { target, display, onWikilinkClick, children } = props;
|
|
1838
1936
|
return /* @__PURE__ */ jsxs("span", {
|
|
@@ -2044,7 +2142,7 @@ function wrapMark(mark, children, context) {
|
|
|
2044
2142
|
default: {
|
|
2045
2143
|
const toDOM = mark.type.spec.toDOM;
|
|
2046
2144
|
if (!toDOM) return children;
|
|
2047
|
-
return outputSpecToReact(toDOM(mark, true), children);
|
|
2145
|
+
return outputSpecToReact(toDOM(mark, true), children, context);
|
|
2048
2146
|
}
|
|
2049
2147
|
}
|
|
2050
2148
|
}
|
|
@@ -2082,8 +2180,31 @@ function renderInline(node, context) {
|
|
|
2082
2180
|
marks: Mark.setFrom(marks)
|
|
2083
2181
|
})), 0, context);
|
|
2084
2182
|
}
|
|
2085
|
-
function renderBlock(node,
|
|
2086
|
-
|
|
2183
|
+
function renderBlock(node, context) {
|
|
2184
|
+
const key = context.keyCounter.value++;
|
|
2185
|
+
const typeName = node.type.name;
|
|
2186
|
+
let handleTaskClick;
|
|
2187
|
+
if (typeName === "list") {
|
|
2188
|
+
const attrs = node.attrs;
|
|
2189
|
+
const { onTaskClick } = context;
|
|
2190
|
+
if (attrs.kind === "task" && onTaskClick) {
|
|
2191
|
+
const index = context.taskCounter.value++;
|
|
2192
|
+
const checked = attrs.checked === true;
|
|
2193
|
+
const marker = attrs.marker ?? null;
|
|
2194
|
+
const text = node.firstChild?.isTextblock ? node.firstChild.textContent.split("\n", 1)[0] ?? "" : "";
|
|
2195
|
+
handleTaskClick = (event) => {
|
|
2196
|
+
event.preventDefault();
|
|
2197
|
+
onTaskClick({
|
|
2198
|
+
index,
|
|
2199
|
+
checked,
|
|
2200
|
+
marker,
|
|
2201
|
+
text,
|
|
2202
|
+
event: event.nativeEvent
|
|
2203
|
+
});
|
|
2204
|
+
};
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
if (typeName === "codeBlock") {
|
|
2087
2208
|
const attrs = node.attrs;
|
|
2088
2209
|
const language = typeof attrs.language === "string" ? attrs.language : "";
|
|
2089
2210
|
if (language === "math") return /* @__PURE__ */ jsx(MathCodeBlock, { code: node.textContent }, key);
|
|
@@ -2095,13 +2216,12 @@ function renderBlock(node, key, context) {
|
|
|
2095
2216
|
const toDOM = node.type.spec.toDOM;
|
|
2096
2217
|
if (node.isTextblock) {
|
|
2097
2218
|
const inline = renderInline(node, context);
|
|
2098
|
-
return toDOM ? outputSpecToReact(toDOM(node), inline,
|
|
2219
|
+
return toDOM ? outputSpecToReact(toDOM(node), inline, context) : /* @__PURE__ */ jsx(Fragment, { children: inline }, key);
|
|
2099
2220
|
}
|
|
2100
|
-
const children =
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
return toDOM ? outputSpecToReact(toDOM(node), children, key) : /* @__PURE__ */ jsx(Fragment, { children }, key);
|
|
2221
|
+
const children = node.content.content.map((child) => renderBlock(child, context));
|
|
2222
|
+
const reactNode = toDOM ? outputSpecToReact(toDOM(node), children, context) : /* @__PURE__ */ jsx(Fragment, { children }, key);
|
|
2223
|
+
if (typeName === "list" && handleTaskClick && typeof reactNode !== "string" && reactNode != null) return cloneElement(reactNode, { onClick: handleTaskClick });
|
|
2224
|
+
return reactNode;
|
|
2105
2225
|
}
|
|
2106
2226
|
/**
|
|
2107
2227
|
* Render Markdown to a read-only React tree that looks exactly like the editor
|
|
@@ -2114,27 +2234,27 @@ function renderBlock(node, key, context) {
|
|
|
2114
2234
|
* Callbacks (`onWikilinkClick`, etc.) should be stable; pass them via
|
|
2115
2235
|
* `useCallback` to avoid re-rendering the whole tree.
|
|
2116
2236
|
*/
|
|
2117
|
-
function MarkdownView({ markdown, markMode = "hide", frontmatter = false, resolveImageUrl, onWikilinkClick, onLinkClick, onImageClick, className }) {
|
|
2237
|
+
function MarkdownView({ markdown, markMode = "hide", frontmatter = false, resolveImageUrl, onWikilinkClick, onLinkClick, onImageClick, onTaskClick, className }) {
|
|
2118
2238
|
const content = useMemo(() => {
|
|
2119
2239
|
const doc = markdownToDoc(markdown, { frontmatter });
|
|
2120
2240
|
const context = {
|
|
2121
2241
|
resolveImageUrl,
|
|
2122
2242
|
onWikilinkClick,
|
|
2123
2243
|
onLinkClick,
|
|
2124
|
-
onImageClick
|
|
2244
|
+
onImageClick,
|
|
2245
|
+
onTaskClick,
|
|
2246
|
+
taskCounter: { value: 0 },
|
|
2247
|
+
keyCounter: { value: 0 }
|
|
2125
2248
|
};
|
|
2126
|
-
|
|
2127
|
-
doc.forEach((node, _offset, index) => {
|
|
2128
|
-
blocks.push(renderBlock(node, index, context));
|
|
2129
|
-
});
|
|
2130
|
-
return blocks;
|
|
2249
|
+
return doc.content.content.map((node) => renderBlock(node, context));
|
|
2131
2250
|
}, [
|
|
2132
2251
|
markdown,
|
|
2133
2252
|
frontmatter,
|
|
2134
2253
|
resolveImageUrl,
|
|
2135
2254
|
onWikilinkClick,
|
|
2136
2255
|
onLinkClick,
|
|
2137
|
-
onImageClick
|
|
2256
|
+
onImageClick,
|
|
2257
|
+
onTaskClick
|
|
2138
2258
|
]);
|
|
2139
2259
|
return /* @__PURE__ */ jsx("div", {
|
|
2140
2260
|
className: clsx("ProseMirror", "meowdown-content", className),
|
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.40.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"@prosekit/react": "^0.8.0-beta.19",
|
|
26
26
|
"clsx": "^2.1.1",
|
|
27
27
|
"lucide-react": "^1.21.0",
|
|
28
|
-
"
|
|
28
|
+
"react-property": "^2.0.2",
|
|
29
|
+
"@meowdown/core": "0.40.0"
|
|
29
30
|
},
|
|
30
31
|
"peerDependencies": {
|
|
31
32
|
"react": "^19.0.0",
|