@meowdown/react 0.58.1 → 0.58.3

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,6 +1,6 @@
1
1
  import { clsx } from "clsx/lite";
2
- import { Fragment, cloneElement, createElement, useCallback, 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, 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";
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, defineSpellCheckPlugin, defineSubstitution, defineTagClickHandler, defineViewAttributes, 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";
6
6
  import { Selection, TextSelection } from "@prosekit/pm/state";
@@ -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
@@ -382,7 +391,7 @@ function DropIndicator$1() {
382
391
 
383
392
  //#endregion
384
393
  //#region src/components/editor-extensions.tsx
385
- function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick, onTagClick, onExitBoundary, resolveImageUrl, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, placeholder, readOnly, wikilinkEnabled, spellCheck }) {
394
+ function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick, onTagClick, onExitBoundary, resolveImageUrl, resolveFileInfo, onFileClick, onFilePaste, onFileSaveError, onImageClick, embedPaste, linkPaste, bulletAfterHeading, substitution, placeholder, readOnly, wikilinkEnabled, spellCheck, editorClassName }) {
386
395
  const editor = useEditor$1();
387
396
  useEffect(() => {
388
397
  editor.commands.setMarkMode(markMode);
@@ -460,6 +469,9 @@ function EditorExtensions({ markMode, onDocChange, onWikilinkClick, onLinkClick,
460
469
  useExtension$1(useMemo(() => {
461
470
  return spellCheck == null ? null : defineSpellCheckPlugin(spellCheck);
462
471
  }, [spellCheck]));
472
+ useExtension$1(useMemo(() => {
473
+ return editorClassName ? defineViewAttributes({ class: editorClassName }) : null;
474
+ }, [editorClassName]));
463
475
  return null;
464
476
  }
465
477
 
@@ -1815,10 +1827,7 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
1815
1827
  return /* @__PURE__ */ jsxs(ProseKit, {
1816
1828
  editor,
1817
1829
  children: [
1818
- /* @__PURE__ */ jsx("div", {
1819
- ref: editor.mount,
1820
- className: clsx("meowdown-content", editorClassName)
1821
- }),
1830
+ /* @__PURE__ */ jsx("div", { ref: editor.mount }),
1822
1831
  /* @__PURE__ */ jsx(EditorExtensions, {
1823
1832
  markMode,
1824
1833
  onDocChange: handleDocChange,
@@ -1839,7 +1848,8 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
1839
1848
  placeholder,
1840
1849
  readOnly,
1841
1850
  wikilinkEnabled: !!onWikilinkSearch,
1842
- spellCheck
1851
+ spellCheck,
1852
+ editorClassName
1843
1853
  }),
1844
1854
  blockHandle && !readOnly && /* @__PURE__ */ jsx(BlockHandle, {}),
1845
1855
  !readOnly && /* @__PURE__ */ jsx(TableHandle, {}),
@@ -1875,6 +1885,12 @@ function ProseKitEditor({ markMode = "focus", initialMarkdown, onDocChange, onSl
1875
1885
  //#endregion
1876
1886
  //#region src/components/editor.tsx
1877
1887
  const CARET_GLIDE_OFF = { "--meowdown-caret-glide": "0ms" };
1888
+ /**
1889
+ * A hybrid live-preview Markdown editor: the document stays Markdown text,
1890
+ * rendered in place as rich content.
1891
+ *
1892
+ * Callbacks and resolvers should be stable; pass them via `useCallback`.
1893
+ */
1878
1894
  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
1895
  const childRef = useRef(null);
1880
1896
  useImperativeHandle(handleRef, () => {
@@ -2313,10 +2329,12 @@ function CodeBlock({ code, language }) {
2313
2329
  */
2314
2330
  function MathView(props) {
2315
2331
  const { formula, children } = props;
2332
+ const katex = useKaTeX(true);
2333
+ if (!katex) return /* @__PURE__ */ jsx("span", { children: formula });
2316
2334
  return /* @__PURE__ */ jsxs("span", {
2317
2335
  className: "md-math-view",
2318
2336
  children: [/* @__PURE__ */ jsx(MathRender, {
2319
- katex: useKaTeX(true),
2337
+ katex,
2320
2338
  formula,
2321
2339
  displayMode: false,
2322
2340
  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.1",
4
+ "version": "0.58.3",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -31,7 +31,7 @@
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.1"
34
+ "@meowdown/core": "0.58.3"
35
35
  },
36
36
  "peerDependencies": {
37
37
  "react": "^19.0.0",