@meowdown/react 0.36.2 → 0.37.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 +2 -0
- package/dist/index.js +200 -73
- package/dist/style.css +14 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -60,6 +60,8 @@ Slash menu host items can include `keywords` to match hidden terms without chang
|
|
|
60
60
|
|
|
61
61
|
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).
|
|
62
62
|
|
|
63
|
+
Math (`$E=mc^2$` inline, `$$` blocks, and ` ```math ` fences) is compiled by KaTeX to native MathML.
|
|
64
|
+
|
|
63
65
|
## License
|
|
64
66
|
|
|
65
67
|
MIT
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { clsx } from "clsx/lite";
|
|
2
|
-
import { Fragment, createElement, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
|
|
3
|
-
import { codeBlockLanguages, defaultResolveImageUrl, defineBulletAfterHeading, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineHTMLPaste, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineMarkdownCopy, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSpellCheckPlugin, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkTrigger, docToMarkdown, getCodeTokens, getMarkBuilders, getPendingReplacement, getSelectedText, getTableColumnAlign, getVirtualElementFromRange, inlineTextToMarkChunks, isSelectionInTableCell, listenForTweetHeight, markdownToDoc, matchEmbed } from "@meowdown/core";
|
|
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, 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";
|
|
@@ -16,6 +16,27 @@ 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
18
|
|
|
19
|
+
//#region src/hooks/use-katex.ts
|
|
20
|
+
/**
|
|
21
|
+
* The lazily loaded KaTeX render function, or `undefined` while it loads (or when
|
|
22
|
+
* `enabled` is false, so a document without math never loads it).
|
|
23
|
+
*/
|
|
24
|
+
function useKaTeX(enabled) {
|
|
25
|
+
const [katex, setKaTeX] = useState(void 0);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!enabled || katex) return;
|
|
28
|
+
let cancelled = false;
|
|
29
|
+
loadKaTeX().then((render) => {
|
|
30
|
+
if (!cancelled) setKaTeX(() => render);
|
|
31
|
+
});
|
|
32
|
+
return () => {
|
|
33
|
+
cancelled = true;
|
|
34
|
+
};
|
|
35
|
+
}, [enabled, katex]);
|
|
36
|
+
return katex;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
19
40
|
//#region src/components/code-block-view.module.css
|
|
20
41
|
var code_block_view_module_default = {
|
|
21
42
|
"CopyButton": "meow_CopyButton_D3j_9q",
|
|
@@ -26,6 +47,7 @@ var code_block_view_module_default = {
|
|
|
26
47
|
"List": "meow_List_D3j_9q",
|
|
27
48
|
"Popup": "meow_Popup_D3j_9q",
|
|
28
49
|
"Positioner": "meow_Positioner_D3j_9q",
|
|
50
|
+
"Preview": "meow_Preview_D3j_9q",
|
|
29
51
|
"Root": "meow_Root_D3j_9q",
|
|
30
52
|
"Search": "meow_Search_D3j_9q",
|
|
31
53
|
"SearchRow": "meow_SearchRow_D3j_9q",
|
|
@@ -68,10 +90,53 @@ function CopyButton({ getText, label, onCopy, className, ...rest }) {
|
|
|
68
90
|
});
|
|
69
91
|
}
|
|
70
92
|
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/components/math-render.tsx
|
|
95
|
+
/**
|
|
96
|
+
* KaTeX output rendered into a real element, the same way the editor's
|
|
97
|
+
* `MathMarkView` does. A span host matches KaTeX's own output shape; display
|
|
98
|
+
* mode emits a block-level `math[display="block"]` element.
|
|
99
|
+
*/
|
|
100
|
+
function MathRender(props) {
|
|
101
|
+
const { katex, formula, displayMode, className, onMouseDown } = props;
|
|
102
|
+
const ref = useRef(null);
|
|
103
|
+
useLayoutEffect(() => {
|
|
104
|
+
const element = ref.current;
|
|
105
|
+
if (!element || !katex) return;
|
|
106
|
+
renderMathInto(katex, element, formula, displayMode);
|
|
107
|
+
}, [
|
|
108
|
+
katex,
|
|
109
|
+
formula,
|
|
110
|
+
displayMode
|
|
111
|
+
]);
|
|
112
|
+
return /* @__PURE__ */ jsx("span", {
|
|
113
|
+
ref,
|
|
114
|
+
className,
|
|
115
|
+
contentEditable: false,
|
|
116
|
+
"data-testid": props["data-testid"],
|
|
117
|
+
onMouseDown
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
|
|
71
121
|
//#endregion
|
|
72
122
|
//#region src/components/code-block-view.tsx
|
|
73
123
|
function CodeBlockView(props) {
|
|
74
124
|
const language = props.node.attrs.language || "";
|
|
125
|
+
const isMath = language === "math";
|
|
126
|
+
const code = props.node.textContent;
|
|
127
|
+
const caretInside = props.decorations.some(isCodeBlockPreviewHiddenDecoration);
|
|
128
|
+
const katex = useKaTeX(isMath);
|
|
129
|
+
const showMathPreview = isMath && katex != null;
|
|
130
|
+
const previewOnly = showMathPreview && !caretInside && code.trim() !== "";
|
|
131
|
+
const focusSource = (event) => {
|
|
132
|
+
event.preventDefault();
|
|
133
|
+
const pos = props.getPos();
|
|
134
|
+
if (pos == null) return;
|
|
135
|
+
const { view } = props;
|
|
136
|
+
const selection = TextSelection.near(view.state.doc.resolve(pos + 1), 1);
|
|
137
|
+
view.dispatch(view.state.tr.setSelection(selection));
|
|
138
|
+
view.focus();
|
|
139
|
+
};
|
|
75
140
|
const selected = useMemo(() => {
|
|
76
141
|
return codeBlockLanguages.find((item) => item.value === language) ?? {
|
|
77
142
|
value: language,
|
|
@@ -94,86 +159,98 @@ function CodeBlockView(props) {
|
|
|
94
159
|
};
|
|
95
160
|
return /* @__PURE__ */ jsxs("div", {
|
|
96
161
|
className: code_block_view_module_default.Root,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
children: /* @__PURE__ */ jsx(
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
children:
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
className: code_block_view_module_default.
|
|
131
|
-
|
|
132
|
-
|
|
162
|
+
"data-preview": previewOnly || void 0,
|
|
163
|
+
children: [
|
|
164
|
+
/* @__PURE__ */ jsxs("div", {
|
|
165
|
+
className: code_block_view_module_default.Toolbar,
|
|
166
|
+
contentEditable: false,
|
|
167
|
+
"data-open": comboboxOpen || void 0,
|
|
168
|
+
children: [/* @__PURE__ */ jsxs(Combobox.Root, {
|
|
169
|
+
items: itemsForView,
|
|
170
|
+
value: selected,
|
|
171
|
+
onValueChange: setLanguage,
|
|
172
|
+
inputValue: query,
|
|
173
|
+
onInputValueChange: setQuery,
|
|
174
|
+
onOpenChange: (open) => {
|
|
175
|
+
if (open) setComboboxOpen(true);
|
|
176
|
+
else setQuery("");
|
|
177
|
+
},
|
|
178
|
+
onOpenChangeComplete: (open) => {
|
|
179
|
+
if (!open) setComboboxOpen(false);
|
|
180
|
+
},
|
|
181
|
+
children: [/* @__PURE__ */ jsxs(Combobox.Trigger, {
|
|
182
|
+
className: code_block_view_module_default.Trigger,
|
|
183
|
+
"data-testid": "code-block-language",
|
|
184
|
+
children: [/* @__PURE__ */ jsx(Combobox.Value, { placeholder: "Plain Text" }), /* @__PURE__ */ jsx(Combobox.Icon, {
|
|
185
|
+
className: code_block_view_module_default.TriggerIcon,
|
|
186
|
+
children: /* @__PURE__ */ jsx(ChevronsUpDownIcon, {})
|
|
187
|
+
})]
|
|
188
|
+
}), /* @__PURE__ */ jsx(Combobox.Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, {
|
|
189
|
+
className: code_block_view_module_default.Positioner,
|
|
190
|
+
sideOffset: 4,
|
|
191
|
+
children: /* @__PURE__ */ jsxs(Combobox.Popup, {
|
|
192
|
+
className: code_block_view_module_default.Popup,
|
|
193
|
+
children: [
|
|
194
|
+
/* @__PURE__ */ jsx("div", {
|
|
195
|
+
className: code_block_view_module_default.SearchRow,
|
|
196
|
+
children: /* @__PURE__ */ jsx(Combobox.Input, {
|
|
197
|
+
className: code_block_view_module_default.Search,
|
|
198
|
+
placeholder: "Search or type a language",
|
|
199
|
+
"data-testid": "code-block-language-search"
|
|
200
|
+
})
|
|
201
|
+
}),
|
|
202
|
+
/* @__PURE__ */ jsx(Combobox.Empty, {
|
|
203
|
+
className: code_block_view_module_default.Empty,
|
|
204
|
+
children: "No languages found."
|
|
205
|
+
}),
|
|
206
|
+
/* @__PURE__ */ jsx(Combobox.List, {
|
|
207
|
+
className: code_block_view_module_default.List,
|
|
208
|
+
children: (item) => /* @__PURE__ */ jsxs(Combobox.Item, {
|
|
209
|
+
value: item,
|
|
210
|
+
className: code_block_view_module_default.Item,
|
|
211
|
+
children: [/* @__PURE__ */ jsx(Combobox.ItemIndicator, {
|
|
212
|
+
className: code_block_view_module_default.ItemIndicator,
|
|
213
|
+
children: /* @__PURE__ */ jsx(CheckIcon, {})
|
|
214
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
215
|
+
className: code_block_view_module_default.ItemText,
|
|
216
|
+
children: item.label
|
|
217
|
+
})]
|
|
218
|
+
}, item.label)
|
|
133
219
|
})
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
getText: () => props.node.textContent,
|
|
158
|
-
label: "Copy code",
|
|
159
|
-
className: code_block_view_module_default.CopyButton,
|
|
160
|
-
"data-testid": "code-block-copy"
|
|
161
|
-
})]
|
|
162
|
-
}), /* @__PURE__ */ jsx("pre", {
|
|
163
|
-
ref: props.contentRef,
|
|
164
|
-
"data-language": language
|
|
165
|
-
})]
|
|
220
|
+
]
|
|
221
|
+
})
|
|
222
|
+
}) })]
|
|
223
|
+
}), /* @__PURE__ */ jsx(CopyButton, {
|
|
224
|
+
getText: () => props.node.textContent,
|
|
225
|
+
label: "Copy code",
|
|
226
|
+
className: code_block_view_module_default.CopyButton,
|
|
227
|
+
"data-testid": "code-block-copy"
|
|
228
|
+
})]
|
|
229
|
+
}),
|
|
230
|
+
/* @__PURE__ */ jsx("pre", {
|
|
231
|
+
ref: props.contentRef,
|
|
232
|
+
"data-language": language
|
|
233
|
+
}),
|
|
234
|
+
showMathPreview && /* @__PURE__ */ jsx(MathRender, {
|
|
235
|
+
katex,
|
|
236
|
+
formula: code,
|
|
237
|
+
displayMode: true,
|
|
238
|
+
className: code_block_view_module_default.Preview,
|
|
239
|
+
"data-testid": "code-block-math-preview",
|
|
240
|
+
onMouseDown: focusSource
|
|
241
|
+
})
|
|
242
|
+
]
|
|
166
243
|
});
|
|
167
244
|
}
|
|
168
245
|
|
|
169
246
|
//#endregion
|
|
170
247
|
//#region src/extensions/code-block-view.ts
|
|
171
248
|
function defineCodeBlockView() {
|
|
172
|
-
return defineReactNodeView({
|
|
249
|
+
return union(defineReactNodeView({
|
|
173
250
|
name: "codeBlock",
|
|
174
251
|
contentAs: "code",
|
|
175
252
|
component: CodeBlockView
|
|
176
|
-
});
|
|
253
|
+
}), defineCodeBlockPreviewPlugin());
|
|
177
254
|
}
|
|
178
255
|
|
|
179
256
|
//#endregion
|
|
@@ -588,7 +665,7 @@ const SCROLL_TOP_MARGIN = 16;
|
|
|
588
665
|
function closestScrollable(element) {
|
|
589
666
|
for (let node = element.parentElement; node; node = node.parentElement) {
|
|
590
667
|
const { overflowY } = getComputedStyle(node);
|
|
591
|
-
if (
|
|
668
|
+
if ((overflowY === "auto" || overflowY === "scroll" || overflowY === "overlay") && node.scrollHeight > node.clientHeight) return node;
|
|
592
669
|
}
|
|
593
670
|
return document.scrollingElement;
|
|
594
671
|
}
|
|
@@ -1026,6 +1103,12 @@ function SlashMenu({ timeFormat = "12", onSlashMenuSearch }) {
|
|
|
1026
1103
|
kbd: "```",
|
|
1027
1104
|
onSelect: () => editor.commands.setCodeBlock()
|
|
1028
1105
|
}),
|
|
1106
|
+
/* @__PURE__ */ jsx(SlashMenuItem, {
|
|
1107
|
+
label: "Math",
|
|
1108
|
+
keywords: ["latex"],
|
|
1109
|
+
kbd: "```math",
|
|
1110
|
+
onSelect: () => editor.commands.insertMarkdown("```math\n```")
|
|
1111
|
+
}),
|
|
1029
1112
|
/* @__PURE__ */ jsx(SlashMenuItem, {
|
|
1030
1113
|
label: "Table",
|
|
1031
1114
|
onSelect: () => editor.commands.insertTable({
|
|
@@ -1875,6 +1958,42 @@ function CodeBlock({ code, language }) {
|
|
|
1875
1958
|
children: /* @__PURE__ */ jsx("code", { children: tokens.length > 0 ? renderTokens(code, tokens) : code })
|
|
1876
1959
|
});
|
|
1877
1960
|
}
|
|
1961
|
+
/**
|
|
1962
|
+
* Mirrors the editor's `MathMarkView` DOM: a KaTeX preview next to the source
|
|
1963
|
+
* text, flipped by the same mode CSS (the read-only view has no caret, so the
|
|
1964
|
+
* preview always shows in hide/focus modes).
|
|
1965
|
+
*/
|
|
1966
|
+
function MathView(props) {
|
|
1967
|
+
const { formula, children } = props;
|
|
1968
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
1969
|
+
className: "md-math-view",
|
|
1970
|
+
children: [/* @__PURE__ */ jsx(MathRender, {
|
|
1971
|
+
katex: useKaTeX(true),
|
|
1972
|
+
formula,
|
|
1973
|
+
displayMode: false,
|
|
1974
|
+
className: "md-math-view-preview",
|
|
1975
|
+
"data-testid": "math-preview"
|
|
1976
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
1977
|
+
className: "md-math-view-content",
|
|
1978
|
+
children
|
|
1979
|
+
})]
|
|
1980
|
+
});
|
|
1981
|
+
}
|
|
1982
|
+
/** A `math` code block: the rendered formula alone, the source while KaTeX loads. */
|
|
1983
|
+
function MathCodeBlock({ code }) {
|
|
1984
|
+
const katex = useKaTeX(true);
|
|
1985
|
+
if (!katex) return /* @__PURE__ */ jsx(CodeBlock, {
|
|
1986
|
+
code,
|
|
1987
|
+
language: "math"
|
|
1988
|
+
});
|
|
1989
|
+
return /* @__PURE__ */ jsx(MathRender, {
|
|
1990
|
+
katex,
|
|
1991
|
+
formula: code,
|
|
1992
|
+
displayMode: true,
|
|
1993
|
+
className: code_block_view_module_default.Preview,
|
|
1994
|
+
"data-testid": "code-block-math-preview"
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1878
1997
|
/** Wrap inline `children` in one mark, special-casing the view/link marks. */
|
|
1879
1998
|
function wrapMark(mark, children, context) {
|
|
1880
1999
|
switch (mark.type.name) {
|
|
@@ -1897,6 +2016,13 @@ function wrapMark(mark, children, context) {
|
|
|
1897
2016
|
children
|
|
1898
2017
|
});
|
|
1899
2018
|
}
|
|
2019
|
+
case "mdMath": {
|
|
2020
|
+
const attrs = mark.attrs;
|
|
2021
|
+
return /* @__PURE__ */ jsx(MathView, {
|
|
2022
|
+
formula: attrs.formula,
|
|
2023
|
+
children
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
1900
2026
|
case "mdLinkText": {
|
|
1901
2027
|
const attrs = mark.attrs;
|
|
1902
2028
|
const handleClick = context.onLinkClick ? (event) => context.onLinkClick?.({
|
|
@@ -1955,6 +2081,7 @@ function renderBlock(node, key, context) {
|
|
|
1955
2081
|
if (node.type.name === "codeBlock") {
|
|
1956
2082
|
const attrs = node.attrs;
|
|
1957
2083
|
const language = typeof attrs.language === "string" ? attrs.language : "";
|
|
2084
|
+
if (language === "math") return /* @__PURE__ */ jsx(MathCodeBlock, { code: node.textContent }, key);
|
|
1958
2085
|
return /* @__PURE__ */ jsx(CodeBlock, {
|
|
1959
2086
|
code: node.textContent,
|
|
1960
2087
|
language
|
package/dist/style.css
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
position: relative;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
.meow_Root_D3j_9q[data-preview] pre, .meow_Root_D3j_9q[data-preview] .meow_Toolbar_D3j_9q {
|
|
6
|
+
display: none;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.meow_Preview_D3j_9q {
|
|
10
|
+
text-align: center;
|
|
11
|
+
cursor: text;
|
|
12
|
+
-webkit-user-select: none;
|
|
13
|
+
user-select: none;
|
|
14
|
+
padding: .25rem 0;
|
|
15
|
+
display: block;
|
|
16
|
+
overflow-x: auto;
|
|
17
|
+
}
|
|
18
|
+
|
|
5
19
|
.meow_Toolbar_D3j_9q {
|
|
6
20
|
z-index: 1;
|
|
7
21
|
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.37.0",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"@prosekit/react": "^0.8.0-beta.17",
|
|
26
26
|
"clsx": "^2.1.1",
|
|
27
27
|
"lucide-react": "^1.21.0",
|
|
28
|
-
"@meowdown/core": "0.
|
|
28
|
+
"@meowdown/core": "0.37.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"react": "^19.0.0",
|