@meowdown/react 0.58.0 → 0.58.2

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 CHANGED
@@ -13,6 +13,11 @@ type SelectionHint = SelectionJSON$1 | 'start' | 'end';
13
13
  * editor's coordinate space and are not portable across editors.
14
14
  */
15
15
  type EditorStateSnapshot = [markdown: string, selection: SelectionJSON$1];
16
+ /**
17
+ * The imperative API of a mounted editor: read and write the document,
18
+ * move the selection, and drive the selection menu and pending replacements
19
+ * from outside React.
20
+ */
16
21
  interface EditorHandle {
17
22
  /**
18
23
  * Reconciles pending native input, then serializes the current document to
@@ -398,7 +403,13 @@ interface EditorProps {
398
403
  /** Nodes rendered inside the editor's ProseKit context. */
399
404
  children?: ReactNode;
400
405
  }
401
- declare function MeowdownEditor({ mode, initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, frontmatter, blockHandle, caretGlide, placeholder, readOnly, spellCheck, timeFormat, editorClassName, wrapperClassName, handleRef, children }: EditorProps): import("react").JSX.Element;
406
+ /**
407
+ * A hybrid live-preview Markdown editor: the document stays Markdown text,
408
+ * rendered in place as rich content.
409
+ *
410
+ * Callbacks and resolvers should be stable; pass them via `useCallback`.
411
+ */
412
+ declare function MeowdownEditor({ mode, initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, frontmatter, blockHandle, caretGlide, placeholder, readOnly, spellCheck, timeFormat, editorClassName, wrapperClassName, handleRef, children }: EditorProps): ReactElement;
402
413
  //#endregion
403
414
  //#region src/components/markdown-view.d.ts
404
415
  /** Payload for {@link TaskClickHandler}. */
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { clsx } from "clsx/lite";
2
- import { Fragment, cloneElement, createElement, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
2
+ import { Fragment, cloneElement, createElement, useCallback, useDeferredValue, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
3
3
  import { buildFileMarkdown, codeBlockLanguages, collectReferenceDefinitions, defaultResolveImageUrl, defineBulletAfterHeading, defineCodeBlockPreviewPlugin, defineEditorExtension, defineEmbedPaste, defineExitBoundaryHandler, defineFileClickHandler, defineFilePaste, defineFileView, defineFollowLinkHandler, defineImage, defineImageClickHandler, defineLinkClickHandler, defineLinkEditKeymap, defineLinkHoverHandler, defineLinkPaste, definePendingReplacementHandler, definePlaceholder, defineReadonly, defineSpellCheckPlugin, defineSubstitution, defineTagClickHandler, defineWikilinkClickHandler, defineWikilinkHoverHandler, defineWikilinkTrigger, docToMarkdown, formatFileSize, getCodeTokens, getFileKind, getMarkBuilders, getPendingReplacement, getSelectedText, getTableColumnAlign, getTextblockDisplayText, getVirtualElementFromRange, inlineTextToMarkChunksWithContext, isCodeBlockPreviewHiddenDecoration, isNodeOfType, isSelectionInTableCell, listenForTweetHeight, loadKaTeX, markdownToDoc, matchEmbed, renderMathInto } from "@meowdown/core";
4
4
  import { clamp } from "@ocavue/utils";
5
5
  import { canUseRegexLookbehind, createEditor, defineDocChangeHandler, defineUpdateHandler, isTextSelection, union } from "@prosekit/core";
@@ -35,7 +35,7 @@ function useBeautifulMermaid(enabled) {
35
35
  let cancelled = false;
36
36
  loadBeautifulMermaid().then((loadedRenderer) => {
37
37
  if (!cancelled) setRenderer(() => loadedRenderer);
38
- }).catch(() => {});
38
+ });
39
39
  return () => {
40
40
  cancelled = true;
41
41
  };
@@ -131,7 +131,7 @@ function MathRender(props) {
131
131
  const ref = useRef(null);
132
132
  useLayoutEffect(() => {
133
133
  const element = ref.current;
134
- if (!element || !katex) return;
134
+ if (!element) return;
135
135
  renderMathInto(katex, element, formula, displayMode);
136
136
  }, [
137
137
  katex,
@@ -200,122 +200,46 @@ function MermaidRender(props) {
200
200
  //#endregion
201
201
  //#region src/components/code-block-view.tsx
202
202
  function CodeBlockView(props) {
203
- const language = props.node.attrs.language || "";
203
+ const { node, view, getPos, decorations, selected, setAttrs, contentRef } = props;
204
+ const language = node.attrs.language || "";
204
205
  const isMath = language === "math";
205
206
  const isMermaid = language === "mermaid";
206
- const code = props.node.textContent;
207
- const caretInside = props.decorations.some(isCodeBlockPreviewHiddenDecoration);
207
+ const code = node.textContent;
208
+ const caretInside = decorations.some(isCodeBlockPreviewHiddenDecoration);
208
209
  const katex = useKaTeX(isMath);
209
210
  const mermaid = useBeautifulMermaid(isMermaid);
210
211
  const showMathPreview = isMath && katex != null;
211
212
  const showMermaidPreview = isMermaid && mermaid != null;
212
- const previewOnly = (showMathPreview || showMermaidPreview) && !caretInside && code.trim() !== "";
213
- const focusSource = (event) => {
213
+ const showPreview = showMathPreview || showMermaidPreview;
214
+ const previewCode = useDeferredValue(showPreview ? code : "");
215
+ const previewOnly = showPreview && !caretInside && code.trim() !== "";
216
+ const focusSource = useCallback((event) => {
214
217
  event.preventDefault();
215
- const pos = props.getPos();
218
+ const pos = getPos();
216
219
  if (pos == null) return;
217
- const { view } = props;
218
220
  const selection = TextSelection.near(view.state.doc.resolve(pos + 1), 1);
219
221
  view.dispatch(view.state.tr.setSelection(selection));
220
222
  view.focus();
221
- };
222
- const selected = useMemo(() => {
223
- return codeBlockLanguages.find((item) => item.value === language) ?? {
224
- value: language,
225
- label: language
226
- };
227
- }, [language]);
228
- const [query, setQuery] = useState("");
229
- const [comboboxOpen, setComboboxOpen] = useState(false);
230
- const itemsForView = useMemo(() => {
231
- const value = query.trim();
232
- if (!value) return codeBlockLanguages;
233
- const lowercased = value.toLowerCase();
234
- return codeBlockLanguages.some((item) => item.value.toLowerCase() === lowercased || item.label.toLowerCase() === lowercased) ? codeBlockLanguages : [...codeBlockLanguages, {
235
- value,
236
- label: `Use "${value}"`
237
- }];
238
- }, [query]);
239
- const setLanguage = (item) => {
240
- props.setAttrs({ language: item?.value ?? "" });
241
- };
223
+ }, [view, getPos]);
224
+ const setLanguage = useCallback((language) => {
225
+ setAttrs({ language });
226
+ }, [setAttrs]);
242
227
  return /* @__PURE__ */ jsxs("div", {
243
228
  className: code_block_view_module_default.Root,
244
229
  "data-preview": previewOnly || void 0,
245
230
  children: [
246
- /* @__PURE__ */ jsxs("div", {
247
- className: code_block_view_module_default.Toolbar,
248
- contentEditable: false,
249
- "data-open": comboboxOpen || void 0,
250
- children: [/* @__PURE__ */ jsxs(Combobox.Root, {
251
- items: itemsForView,
252
- value: selected,
253
- onValueChange: setLanguage,
254
- inputValue: query,
255
- onInputValueChange: setQuery,
256
- onOpenChange: (open) => {
257
- if (open) setComboboxOpen(true);
258
- else setQuery("");
259
- },
260
- onOpenChangeComplete: (open) => {
261
- if (!open) setComboboxOpen(false);
262
- },
263
- children: [/* @__PURE__ */ jsxs(Combobox.Trigger, {
264
- className: code_block_view_module_default.Trigger,
265
- "data-testid": "code-block-language",
266
- children: [/* @__PURE__ */ jsx(Combobox.Value, { placeholder: "Plain Text" }), /* @__PURE__ */ jsx(Combobox.Icon, {
267
- className: code_block_view_module_default.TriggerIcon,
268
- children: /* @__PURE__ */ jsx(ChevronsUpDownIcon, {})
269
- })]
270
- }), /* @__PURE__ */ jsx(Combobox.Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, {
271
- className: code_block_view_module_default.Positioner,
272
- sideOffset: 4,
273
- children: /* @__PURE__ */ jsxs(Combobox.Popup, {
274
- className: code_block_view_module_default.Popup,
275
- children: [
276
- /* @__PURE__ */ jsx("div", {
277
- className: code_block_view_module_default.SearchRow,
278
- children: /* @__PURE__ */ jsx(Combobox.Input, {
279
- className: code_block_view_module_default.Search,
280
- placeholder: "Search or type a language",
281
- "data-testid": "code-block-language-search"
282
- })
283
- }),
284
- /* @__PURE__ */ jsx(Combobox.Empty, {
285
- className: code_block_view_module_default.Empty,
286
- children: "No languages found."
287
- }),
288
- /* @__PURE__ */ jsx(Combobox.List, {
289
- className: code_block_view_module_default.List,
290
- children: (item) => /* @__PURE__ */ jsxs(Combobox.Item, {
291
- value: item,
292
- className: code_block_view_module_default.Item,
293
- children: [/* @__PURE__ */ jsx(Combobox.ItemIndicator, {
294
- className: code_block_view_module_default.ItemIndicator,
295
- children: /* @__PURE__ */ jsx(CheckIcon, {})
296
- }), /* @__PURE__ */ jsx("span", {
297
- className: code_block_view_module_default.ItemText,
298
- children: item.label
299
- })]
300
- }, item.label)
301
- })
302
- ]
303
- })
304
- }) })]
305
- }), /* @__PURE__ */ jsx(CopyButton, {
306
- getText: () => props.node.textContent,
307
- label: "Copy code",
308
- className: code_block_view_module_default.CopyButton,
309
- "data-testid": "code-block-copy"
310
- })]
311
- }),
312
231
  /* @__PURE__ */ jsx("pre", {
313
- ref: props.contentRef,
232
+ ref: contentRef,
314
233
  "data-language": language
315
234
  }),
235
+ selected ? null : /* @__PURE__ */ jsx(CodeBlockToolbar, {
236
+ code,
237
+ language,
238
+ setLanguage
239
+ }),
316
240
  showMathPreview && /* @__PURE__ */ jsx(MathRender, {
317
241
  katex,
318
- formula: code,
242
+ formula: previewCode,
319
243
  displayMode: true,
320
244
  className: code_block_view_module_default.Preview,
321
245
  "data-testid": "code-block-math-preview",
@@ -323,7 +247,7 @@ function CodeBlockView(props) {
323
247
  }),
324
248
  showMermaidPreview && /* @__PURE__ */ jsx(MermaidRender, {
325
249
  renderer: mermaid,
326
- source: code,
250
+ source: previewCode,
327
251
  className: `${code_block_view_module_default.Preview} ${code_block_view_module_default.MermaidPreview}`,
328
252
  "data-testid": "code-block-mermaid-preview",
329
253
  onMouseDown: focusSource
@@ -331,6 +255,91 @@ function CodeBlockView(props) {
331
255
  ]
332
256
  });
333
257
  }
258
+ function CodeBlockToolbar({ code, language, setLanguage }) {
259
+ const selected = useMemo(() => {
260
+ return codeBlockLanguages.find((item) => item.value === language) ?? {
261
+ value: language,
262
+ label: language
263
+ };
264
+ }, [language]);
265
+ const [query, setQuery] = useState("");
266
+ const [comboboxOpen, setComboboxOpen] = useState(false);
267
+ const itemsForView = useMemo(() => {
268
+ const value = query.trim();
269
+ if (!value) return codeBlockLanguages;
270
+ const lowercased = value.toLowerCase();
271
+ return codeBlockLanguages.some((item) => item.value.toLowerCase() === lowercased || item.label.toLowerCase() === lowercased) ? codeBlockLanguages : [...codeBlockLanguages, {
272
+ value,
273
+ label: `Use "${value}"`
274
+ }];
275
+ }, [query]);
276
+ return /* @__PURE__ */ jsxs("div", {
277
+ className: code_block_view_module_default.Toolbar,
278
+ contentEditable: false,
279
+ "data-open": comboboxOpen || void 0,
280
+ children: [/* @__PURE__ */ jsxs(Combobox.Root, {
281
+ items: itemsForView,
282
+ value: selected,
283
+ onValueChange: (item) => setLanguage(item?.value ?? ""),
284
+ inputValue: query,
285
+ onInputValueChange: setQuery,
286
+ onOpenChange: (open) => {
287
+ if (open) setComboboxOpen(true);
288
+ else setQuery("");
289
+ },
290
+ onOpenChangeComplete: (open) => {
291
+ if (!open) setComboboxOpen(false);
292
+ },
293
+ children: [/* @__PURE__ */ jsxs(Combobox.Trigger, {
294
+ className: code_block_view_module_default.Trigger,
295
+ "data-testid": "code-block-language",
296
+ children: [/* @__PURE__ */ jsx(Combobox.Value, { placeholder: "Plain Text" }), /* @__PURE__ */ jsx(Combobox.Icon, {
297
+ className: code_block_view_module_default.TriggerIcon,
298
+ children: /* @__PURE__ */ jsx(ChevronsUpDownIcon, {})
299
+ })]
300
+ }), /* @__PURE__ */ jsx(Combobox.Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, {
301
+ className: code_block_view_module_default.Positioner,
302
+ sideOffset: 4,
303
+ children: /* @__PURE__ */ jsxs(Combobox.Popup, {
304
+ className: code_block_view_module_default.Popup,
305
+ children: [
306
+ /* @__PURE__ */ jsx("div", {
307
+ className: code_block_view_module_default.SearchRow,
308
+ children: /* @__PURE__ */ jsx(Combobox.Input, {
309
+ className: code_block_view_module_default.Search,
310
+ placeholder: "Search or type a language",
311
+ "data-testid": "code-block-language-search"
312
+ })
313
+ }),
314
+ /* @__PURE__ */ jsx(Combobox.Empty, {
315
+ className: code_block_view_module_default.Empty,
316
+ children: "No languages found."
317
+ }),
318
+ /* @__PURE__ */ jsx(Combobox.List, {
319
+ className: code_block_view_module_default.List,
320
+ children: (item) => /* @__PURE__ */ jsxs(Combobox.Item, {
321
+ value: item,
322
+ className: code_block_view_module_default.Item,
323
+ children: [/* @__PURE__ */ jsx(Combobox.ItemIndicator, {
324
+ className: code_block_view_module_default.ItemIndicator,
325
+ children: /* @__PURE__ */ jsx(CheckIcon, {})
326
+ }), /* @__PURE__ */ jsx("span", {
327
+ className: code_block_view_module_default.ItemText,
328
+ children: item.label
329
+ })]
330
+ }, item.label)
331
+ })
332
+ ]
333
+ })
334
+ }) })]
335
+ }), /* @__PURE__ */ jsx(CopyButton, {
336
+ getText: () => code,
337
+ label: "Copy code",
338
+ className: code_block_view_module_default.CopyButton,
339
+ "data-testid": "code-block-copy"
340
+ })]
341
+ });
342
+ }
334
343
 
335
344
  //#endregion
336
345
  //#region src/extensions/code-block-view.ts
@@ -1875,6 +1884,12 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
1875
1884
  //#endregion
1876
1885
  //#region src/components/editor.tsx
1877
1886
  const CARET_GLIDE_OFF = { "--meowdown-caret-glide": "0ms" };
1887
+ /**
1888
+ * A hybrid live-preview Markdown editor: the document stays Markdown text,
1889
+ * rendered in place as rich content.
1890
+ *
1891
+ * Callbacks and resolvers should be stable; pass them via `useCallback`.
1892
+ */
1878
1893
  function MeowdownEditor({ mode = "focus", initialMarkdown, onDocChange, onSlashMenuSearch, onTagSearch, onWikilinkSearch, onSelectionMenuSearch, selectionMenuAffordance = true, pendingReplacementActions, onPendingReplacementResolve, onWikilinkClick, onLinkClick, onLinkCopy, onTagClick, onExitBoundary, resolveImageUrl, resolveFileLink, resolveWikiEmbed, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste = true, linkPaste = true, bulletAfterHeading = false, substitution = true, frontmatter = false, blockHandle = true, caretGlide = true, placeholder, readOnly, spellCheck, timeFormat, editorClassName, wrapperClassName, handleRef, children }) {
1879
1894
  const childRef = useRef(null);
1880
1895
  useImperativeHandle(handleRef, () => {
@@ -2313,10 +2328,12 @@ function CodeBlock({ code, language }) {
2313
2328
  */
2314
2329
  function MathView(props) {
2315
2330
  const { formula, children } = props;
2331
+ const katex = useKaTeX(true);
2332
+ if (!katex) return /* @__PURE__ */ jsx("span", { children: formula });
2316
2333
  return /* @__PURE__ */ jsxs("span", {
2317
2334
  className: "md-math-view",
2318
2335
  children: [/* @__PURE__ */ jsx(MathRender, {
2319
- katex: useKaTeX(true),
2336
+ katex,
2320
2337
  formula,
2321
2338
  displayMode: false,
2322
2339
  className: "md-math-view-preview",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meowdown/react",
3
3
  "type": "module",
4
- "version": "0.58.0",
4
+ "version": "0.58.2",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -23,15 +23,15 @@
23
23
  "dependencies": {
24
24
  "@base-ui/react": "^1.6.0",
25
25
  "@ocavue/utils": "^1.7.0",
26
- "@prosekit/core": "^0.13.0-beta.6",
27
- "@prosekit/pm": "^0.1.19-beta.3",
28
- "@prosekit/react": "^0.8.0-beta.22",
26
+ "@prosekit/core": "^0.13.0-beta.7",
27
+ "@prosekit/pm": "^0.1.19-beta.4",
28
+ "@prosekit/react": "^0.8.0-beta.23",
29
29
  "beautiful-mermaid": "^1.1.3",
30
30
  "clsx": "^2.1.1",
31
31
  "github-slugger": "^2.0.0",
32
32
  "lucide-react": "^1.24.0",
33
33
  "react-property": "^2.0.2",
34
- "@meowdown/core": "0.58.0"
34
+ "@meowdown/core": "0.58.2"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "react": "^19.0.0",
@@ -53,8 +53,8 @@
53
53
  "@types/react-dom": "^19.2.3",
54
54
  "@vitest/browser-playwright": "^4.1.10",
55
55
  "dedent": "^1.7.2",
56
- "react": "^19.2.7",
57
- "react-dom": "^19.2.7",
56
+ "react": "^19.2.8",
57
+ "react-dom": "^19.2.8",
58
58
  "tsdown": "^0.22.8",
59
59
  "vitest": "^4.1.10",
60
60
  "vitest-browser-commands": "^0.2.1",
@@ -62,8 +62,6 @@
62
62
  "@meowdown/vitest": "0.0.0"
63
63
  },
64
64
  "publishConfig": {
65
- "scripts": {},
66
- "devDependencies": {},
67
65
  "access": "public"
68
66
  },
69
67
  "scripts": {