@4399ywkf/editor 0.7.0 → 0.8.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.
@@ -1,19 +1,21 @@
1
1
  import { useTiptapEditor, isExtensionAvailable, isNodeInSchema, Button, cn, getSelectionBoundingRect, isSelectionValid, getSelectedNodesOfType, selectCurrentBlockContent, Popover, PopoverTrigger, PopoverContent, isValidPosition, updateNodesAttr, parseShortcutKeys, isMarkInSchema, isNodeTypeSelected, SR_ONLY, sanitizeUrl, selectionWithinConvertibleTypes, getSelectedBlockNodes, findNodePosition, clamp, focusNextNode, getAvatar, getUrlParam, handleImageUpload, getNodeDisplayName, isTextSelectionValid, AIButton, getElementOverflowPosition } from './chunk-QMYT5WXO.js';
2
+ import { createCodeHighlighter, getCodeBlockTitle, resolveShikiTheme } from './chunk-ZY5KLA7T.js';
2
3
  import * as React from 'react';
3
4
  import { createContext, memo, forwardRef, useState, useCallback, useRef, useEffect, useMemo, useLayoutEffect, useContext, Children, isValidElement, cloneElement } from 'react';
4
5
  import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
5
6
  import { PluginKey, Plugin, TextSelection, Selection, NodeSelection } from '@tiptap/pm/state';
6
7
  import { Node, mergeAttributes, ReactNodeViewRenderer, Extension as Extension$1, NodeViewWrapper, NodeViewContent, useEditorState, EditorContext, EditorContent, useEditor, isNodeSelection } from '@tiptap/react';
7
- import { Extension, Node as Node$1, mergeAttributes as mergeAttributes$1 } from '@tiptap/core';
8
+ import { Extension, mergeAttributes as mergeAttributes$1, Node as Node$1, generateJSON, generateHTML } from '@tiptap/core';
8
9
  import { Fragment, Slice } from '@tiptap/pm/model';
10
+ import { CodeBlock } from '@tiptap/extension-code-block';
11
+ import { MarkdownManager, Markdown } from '@tiptap/markdown';
12
+ import { Marked } from 'marked';
9
13
  import { shift, flip, offset, useMergeRefs, useFloating, autoUpdate, useTransitionStyles, useDismiss, useInteractions, FloatingPortal, size } from '@floating-ui/react';
10
14
  import { Doc } from 'yjs';
11
15
  import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
12
16
  import throttle from 'lodash.throttle';
13
17
  import { createPortal } from 'react-dom';
14
18
  import { StarterKit } from '@tiptap/starter-kit';
15
- import { CodeBlockLowlight } from '@tiptap/extension-code-block-lowlight';
16
- import { createLowlight, common } from 'lowlight';
17
19
  import { Mention } from '@tiptap/extension-mention';
18
20
  import { TaskList, TaskItem } from '@tiptap/extension-list';
19
21
  import { TextStyle, FontSize, FontFamily, Color } from '@tiptap/extension-text-style';
@@ -31,11 +33,12 @@ import { Emoji, gitHubEmojis } from '@tiptap/extension-emoji';
31
33
  import { TableOfContents, getHierarchicalIndexes } from '@tiptap/extension-table-of-contents';
32
34
  import TiptapHorizontalRule from '@tiptap/extension-horizontal-rule';
33
35
  import { Image as Image$1 } from '@tiptap/extension-image';
36
+ import { DecorationSet, Decoration } from '@tiptap/pm/view';
37
+ import { closeHistory } from '@tiptap/pm/history';
34
38
  import { Table } from '@tiptap/extension-table/table';
35
39
  import { TableCell, TableHeader, TableRow } from '@tiptap/extension-table';
36
40
  import { columnResizing, tableEditing, cellAround, TableView, CellSelection, moveTableRow, moveTableColumn, TableMap, findTable, mergeCells, splitCell, selectedRect, deleteCellSelection, selectionCell, isInTable, columnResizingPluginKey, deleteRow, deleteColumn, toggleHeader, addRowBefore, addRowAfter, addColumnBefore, addColumnAfter, rowIsHeader, columnIsHeader } from '@tiptap/pm/tables';
37
41
  import { canJoin, Mapping } from '@tiptap/pm/transform';
38
- import { Decoration, DecorationSet } from '@tiptap/pm/view';
39
42
  import * as Ariakit2 from '@ariakit/react';
40
43
  import { useHotkeys } from 'react-hotkeys-hook';
41
44
  import '@base-ui/react/merge-props';
@@ -1347,6 +1350,1670 @@ var PasteDropMedia = Extension$1.create({
1347
1350
  ];
1348
1351
  }
1349
1352
  });
1353
+
1354
+ // src/components/tiptap-node/collapsible-node/markdown-container.ts
1355
+ var OPENER_RE = /^ {0,3}(:{3,})[ \t]*([\w-]+)[ \t]*(.*?)[ \t]*$/;
1356
+ var CLOSER_RE = /^ {0,3}(:{3,})[ \t]*$/;
1357
+ var FENCE_OPEN_RE = /^( {0,3})(`{3,}|~{3,})(.*)$/;
1358
+ function parseContainerOpener(line) {
1359
+ const m = OPENER_RE.exec(line);
1360
+ if (!m) return null;
1361
+ return { colons: m[1].length, name: m[2], info: m[3] };
1362
+ }
1363
+ function parseContainerCloser(line) {
1364
+ const m = CLOSER_RE.exec(line);
1365
+ return m ? m[1].length : null;
1366
+ }
1367
+ function matchFenceOpen(line) {
1368
+ const m = FENCE_OPEN_RE.exec(line);
1369
+ if (!m) return null;
1370
+ const char = m[2][0];
1371
+ if (char === "`" && m[3].includes("`")) return null;
1372
+ return { char, length: m[2].length, indent: m[1].length, info: m[3] };
1373
+ }
1374
+ function isFenceClose(line, fence) {
1375
+ const re = new RegExp(`^ {0,3}${fence.char === "`" ? "`" : "~"}{${fence.length},}[ \\t]*$`);
1376
+ return re.test(line);
1377
+ }
1378
+ function scanContainer(lines) {
1379
+ const open = parseContainerOpener(lines[0] ?? "");
1380
+ if (!open) return null;
1381
+ const stack = [open.colons];
1382
+ let fence = null;
1383
+ for (let i = 1; i < lines.length; i += 1) {
1384
+ const line = lines[i];
1385
+ if (fence) {
1386
+ if (isFenceClose(line, fence)) fence = null;
1387
+ continue;
1388
+ }
1389
+ const fenceOpen = matchFenceOpen(line);
1390
+ if (fenceOpen) {
1391
+ fence = fenceOpen;
1392
+ continue;
1393
+ }
1394
+ const closer = parseContainerCloser(line);
1395
+ if (closer !== null) {
1396
+ const top = stack[stack.length - 1];
1397
+ if (top <= closer) {
1398
+ stack.pop();
1399
+ if (stack.length === 0) {
1400
+ return {
1401
+ name: open.name,
1402
+ info: open.info,
1403
+ colons: open.colons,
1404
+ bodyLines: lines.slice(1, i),
1405
+ lineCount: i + 1
1406
+ };
1407
+ }
1408
+ }
1409
+ continue;
1410
+ }
1411
+ const nested = parseContainerOpener(line);
1412
+ if (nested) stack.push(nested.colons);
1413
+ }
1414
+ return null;
1415
+ }
1416
+ var ALL_CONTAINER_NAMES = [
1417
+ "details",
1418
+ "info",
1419
+ "tip",
1420
+ "warning",
1421
+ "danger",
1422
+ "param",
1423
+ "code-group"
1424
+ ];
1425
+ function containsCompleteContainer(text, names = ALL_CONTAINER_NAMES) {
1426
+ const lines = text.replace(/\r\n?/g, "\n").split("\n");
1427
+ for (let i = 0; i < lines.length; i += 1) {
1428
+ const open = parseContainerOpener(lines[i]);
1429
+ if (!open || !names.includes(open.name)) continue;
1430
+ if (scanContainer(lines.slice(i))) return true;
1431
+ }
1432
+ return false;
1433
+ }
1434
+ var REQUIRED_WORDS = /* @__PURE__ */ new Set(["必选", "required"]);
1435
+ var DEFAULT_WORDS = /* @__PURE__ */ new Set(["默认", "default"]);
1436
+ function tokenizeInfo(info) {
1437
+ const out = [];
1438
+ const re = /`([^`]*)`|(\S+)/g;
1439
+ let m = re.exec(info);
1440
+ while (m) {
1441
+ if (m[1] !== void 0) out.push({ kind: "code", value: m[1] });
1442
+ else out.push({ kind: "word", value: m[2] });
1443
+ m = re.exec(info);
1444
+ }
1445
+ return out;
1446
+ }
1447
+ function parseParamInfo(info) {
1448
+ const result = { name: "", type: null, required: false, default: null };
1449
+ const tokens = tokenizeInfo(info);
1450
+ let expectDefault = false;
1451
+ for (const token of tokens) {
1452
+ if (expectDefault) {
1453
+ result.default = token.value;
1454
+ expectDefault = false;
1455
+ continue;
1456
+ }
1457
+ if (token.kind === "code") {
1458
+ if (result.type === null) result.type = token.value;
1459
+ continue;
1460
+ }
1461
+ if (REQUIRED_WORDS.has(token.value)) {
1462
+ result.required = true;
1463
+ continue;
1464
+ }
1465
+ if (DEFAULT_WORDS.has(token.value)) {
1466
+ expectDefault = true;
1467
+ continue;
1468
+ }
1469
+ if (!result.name) result.name = token.value;
1470
+ }
1471
+ return result;
1472
+ }
1473
+ function formatParamInfo(attrs) {
1474
+ const parts = [];
1475
+ const name = attrs.name.trim();
1476
+ if (name) parts.push(name.replace(/\s+/g, "-"));
1477
+ if (attrs.type) parts.push(`\`${attrs.type.replace(/`/g, "'")}\``);
1478
+ if (attrs.required) parts.push("必选");
1479
+ if (attrs.default !== null && attrs.default !== void 0)
1480
+ parts.push(`默认 \`${attrs.default.replace(/`/g, "'")}\``);
1481
+ return parts.join(" ");
1482
+ }
1483
+ function containerColons(body) {
1484
+ let longest = 2;
1485
+ for (const line of body.split("\n")) {
1486
+ const m = /^ {0,3}(:{3,})/.exec(line);
1487
+ if (m) longest = Math.max(longest, m[1].length);
1488
+ }
1489
+ return longest + 1;
1490
+ }
1491
+ function renderContainerMarkdown(name, info, body) {
1492
+ const fence = ":".repeat(containerColons(body));
1493
+ const head = info ? `${fence} ${name} ${info}` : `${fence} ${name}`;
1494
+ return body ? `${head}
1495
+ ${body}
1496
+ ${fence}` : `${head}
1497
+ ${fence}`;
1498
+ }
1499
+ function createContainerTokenizer(tokenName, names) {
1500
+ const startRe = new RegExp(`^ {0,3}:{3,}[ \\t]*(?:${names.join("|")})(?![\\w-])`, "m");
1501
+ return {
1502
+ name: tokenName,
1503
+ level: "block",
1504
+ start: (src) => {
1505
+ const m = startRe.exec(src);
1506
+ return m ? m.index : -1;
1507
+ },
1508
+ tokenize: (src, _tokens, lexer) => {
1509
+ const lines = src.split("\n");
1510
+ const open = parseContainerOpener(lines[0] ?? "");
1511
+ if (!open || !names.includes(open.name)) return void 0;
1512
+ const scanned = scanContainer(lines);
1513
+ if (!scanned) return void 0;
1514
+ const body = scanned.bodyLines.join("\n");
1515
+ return {
1516
+ type: tokenName,
1517
+ raw: lines.slice(0, scanned.lineCount).join("\n"),
1518
+ name: scanned.name,
1519
+ info: scanned.info,
1520
+ body,
1521
+ tokens: body.trim() ? lexer.blockTokens(body) : []
1522
+ };
1523
+ }
1524
+ };
1525
+ }
1526
+
1527
+ // src/components/tiptap-node/code-block-node/code-group-markdown.ts
1528
+ var CODE_GROUP_TOKEN = "codeGroup";
1529
+ var OPENER_RE2 = /^ {0,3}:::[ \t]*code-group[ \t]*$/;
1530
+ var BLANK_RE = /^[ \t]*$/;
1531
+ function parseFenceInfo(info) {
1532
+ const trimmed = info.trim();
1533
+ if (!trimmed) return { language: null, label: null };
1534
+ let i = 0;
1535
+ let language = "";
1536
+ while (i < trimmed.length && !/[\s[]/.test(trimmed[i])) {
1537
+ language += trimmed[i];
1538
+ i += 1;
1539
+ }
1540
+ while (i < trimmed.length && /\s/.test(trimmed[i])) i += 1;
1541
+ let label = null;
1542
+ if (trimmed[i] === "[") {
1543
+ i += 1;
1544
+ let text = "";
1545
+ let closed = false;
1546
+ while (i < trimmed.length) {
1547
+ const ch = trimmed[i];
1548
+ if (ch === "\\" && i + 1 < trimmed.length) {
1549
+ text += trimmed[i + 1];
1550
+ i += 2;
1551
+ continue;
1552
+ }
1553
+ if (ch === "]") {
1554
+ closed = true;
1555
+ break;
1556
+ }
1557
+ text += ch;
1558
+ i += 1;
1559
+ }
1560
+ if (closed) label = text.trim() || null;
1561
+ }
1562
+ return { language: language || null, label };
1563
+ }
1564
+ function escapeFenceLabel(label) {
1565
+ return label.replace(/[\\[\]]/g, (ch) => `\\${ch}`).replace(/[\r\n]+/g, " ");
1566
+ }
1567
+ function formatFenceInfo(language, label) {
1568
+ const parts = [];
1569
+ const lang = (language ?? "").trim();
1570
+ if (lang && !/[\s[\]`]/.test(lang)) parts.push(lang);
1571
+ const text = (label ?? "").trim();
1572
+ if (text) parts.push(`[${escapeFenceLabel(text)}]`);
1573
+ return parts.join(" ");
1574
+ }
1575
+ function chooseFence(source) {
1576
+ let longest = 0;
1577
+ for (const run of source.match(/`+/g) ?? []) longest = Math.max(longest, run.length);
1578
+ return "`".repeat(Math.max(3, longest + 1));
1579
+ }
1580
+ function fenceInfoFromRaw(raw) {
1581
+ const firstLine = raw.split("\n", 1)[0] ?? "";
1582
+ const m = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(firstLine);
1583
+ return m ? m[2] : null;
1584
+ }
1585
+ function scanFenceBlocks(lines) {
1586
+ const blocks = [];
1587
+ let i = 0;
1588
+ while (i < lines.length) {
1589
+ const line = lines[i];
1590
+ if (BLANK_RE.test(line)) {
1591
+ i += 1;
1592
+ continue;
1593
+ }
1594
+ const fence = matchFenceOpen(line);
1595
+ if (!fence) return null;
1596
+ const content = [];
1597
+ let j = i + 1;
1598
+ let closed = false;
1599
+ while (j < lines.length) {
1600
+ if (isFenceClose(lines[j], fence)) {
1601
+ closed = true;
1602
+ break;
1603
+ }
1604
+ content.push(stripIndent(lines[j], fence.indent));
1605
+ j += 1;
1606
+ }
1607
+ if (!closed) return null;
1608
+ const { language, label } = parseFenceInfo(fence.info);
1609
+ blocks.push({ language, label, source: content.join("\n") });
1610
+ i = j + 1;
1611
+ }
1612
+ return blocks;
1613
+ }
1614
+ function scanCodeGroupPartial(lines) {
1615
+ const blocks = [];
1616
+ let i = 1;
1617
+ while (i < lines.length) {
1618
+ const line = lines[i];
1619
+ if (BLANK_RE.test(line)) {
1620
+ i += 1;
1621
+ continue;
1622
+ }
1623
+ const fence = matchFenceOpen(line);
1624
+ if (!fence) return { status: "invalid" };
1625
+ const content = [];
1626
+ let j = i + 1;
1627
+ let closed = false;
1628
+ while (j < lines.length) {
1629
+ if (isFenceClose(lines[j], fence)) {
1630
+ closed = true;
1631
+ break;
1632
+ }
1633
+ content.push(stripIndent(lines[j], fence.indent));
1634
+ j += 1;
1635
+ }
1636
+ if (!closed) return { status: "partial", blocks };
1637
+ const { language, label } = parseFenceInfo(fence.info);
1638
+ blocks.push({ language, label, source: content.join("\n") });
1639
+ i = j + 1;
1640
+ }
1641
+ return { status: "partial", blocks };
1642
+ }
1643
+ function scanCodeGroupLines(lines) {
1644
+ if (lines.length === 0 || !OPENER_RE2.test(lines[0])) return { status: "invalid" };
1645
+ const container = scanContainer(lines);
1646
+ if (container && container.name === "code-group") {
1647
+ const blocks = scanFenceBlocks(container.bodyLines);
1648
+ if (!blocks || blocks.length === 0) return { status: "invalid" };
1649
+ return { status: "complete", blocks, lineCount: container.lineCount };
1650
+ }
1651
+ return scanCodeGroupPartial(lines);
1652
+ }
1653
+ function stripIndent(line, indent) {
1654
+ let n = 0;
1655
+ while (n < indent && line[n] === " ") n += 1;
1656
+ return line.slice(n);
1657
+ }
1658
+ function scanCodeGroup(src) {
1659
+ const lines = src.split("\n");
1660
+ const result = scanCodeGroupLines(lines);
1661
+ if (result.status !== "complete") return null;
1662
+ return { raw: lines.slice(0, result.lineCount).join("\n"), blocks: result.blocks };
1663
+ }
1664
+ var OPENER_START_RE = /^ {0,3}:::[ \t]*code-group[ \t]*$/m;
1665
+ var codeGroupTokenizer = {
1666
+ name: CODE_GROUP_TOKEN,
1667
+ level: "block",
1668
+ // 让 `::: code-group` 能打断前面的段落(与 fence 行为一致)
1669
+ start: (src) => {
1670
+ const m = OPENER_START_RE.exec(src);
1671
+ return m ? m.index : -1;
1672
+ },
1673
+ tokenize: (src) => {
1674
+ const scanned = scanCodeGroup(src);
1675
+ if (!scanned) return void 0;
1676
+ return { type: CODE_GROUP_TOKEN, raw: scanned.raw, blocks: scanned.blocks, tokens: [] };
1677
+ }
1678
+ };
1679
+ function codeBlockNode(helpers, block) {
1680
+ return helpers.createNode(
1681
+ "codeBlock",
1682
+ { language: block.language, label: block.label },
1683
+ block.source ? [helpers.createTextNode(block.source)] : []
1684
+ );
1685
+ }
1686
+ function parseCodeGroupToken(token, helpers) {
1687
+ const blocks = token.blocks ?? [];
1688
+ if (blocks.length === 0) return [];
1689
+ return helpers.createNode(
1690
+ CODE_GROUP_TOKEN,
1691
+ {},
1692
+ blocks.map((b) => codeBlockNode(helpers, b))
1693
+ );
1694
+ }
1695
+ function parseCodeBlockToken(token, helpers) {
1696
+ const raw = token.raw ?? "";
1697
+ const info = fenceInfoFromRaw(raw);
1698
+ if (info === null && token.codeBlockStyle !== "indented") return [];
1699
+ const { language, label } = info === null ? { language: null, label: null } : parseFenceInfo(info);
1700
+ const text = typeof token.text === "string" ? token.text : "";
1701
+ return helpers.createNode(
1702
+ "codeBlock",
1703
+ { language, label },
1704
+ text ? [helpers.createTextNode(text)] : []
1705
+ );
1706
+ }
1707
+ function codeBlockSourceFromJSON(node) {
1708
+ return (node.content ?? []).map((n) => n.type === "text" ? n.text ?? "" : "").join("");
1709
+ }
1710
+ function renderCodeBlockMarkdown(node) {
1711
+ const attrs = node.attrs ?? {};
1712
+ const source = codeBlockSourceFromJSON(node);
1713
+ const fence = chooseFence(source);
1714
+ const info = formatFenceInfo(attrs.language, attrs.label);
1715
+ return `${fence}${info}
1716
+ ${source}
1717
+ ${fence}`;
1718
+ }
1719
+ function renderCodeGroupMarkdown(node, _helpers) {
1720
+ const children = (node.content ?? []).filter((c) => c.type === "codeBlock");
1721
+ const body = children.map(renderCodeBlockMarkdown).join("\n\n");
1722
+ return `::: code-group
1723
+ ${body}
1724
+ :::`;
1725
+ }
1726
+
1727
+ // src/components/tiptap-node/code-block-node/code-block-schema.ts
1728
+ var LANGUAGE_CLASS_PREFIX = "language-";
1729
+ function parseLanguageFromElement(element) {
1730
+ const direct = element.getAttribute("data-language");
1731
+ if (direct) return direct;
1732
+ const code = element.tagName === "CODE" ? element : element.querySelector("code");
1733
+ const candidates = [element, code].filter((el) => Boolean(el));
1734
+ for (const el of candidates) {
1735
+ for (const cls of Array.from(el.classList)) {
1736
+ if (cls.startsWith(LANGUAGE_CLASS_PREFIX)) {
1737
+ const lang = cls.slice(LANGUAGE_CLASS_PREFIX.length);
1738
+ if (lang) return lang;
1739
+ }
1740
+ }
1741
+ }
1742
+ return null;
1743
+ }
1744
+ function getCodeBlockSource(node) {
1745
+ return node.textContent;
1746
+ }
1747
+ var CodeBlockSchema = CodeBlock.extend({
1748
+ // Markdown:fence info 解析 `语言 [标签]`,输出时按源码挑足够长的 fence
1749
+ markdownTokenName: "code",
1750
+ parseMarkdown: parseCodeBlockToken,
1751
+ renderMarkdown: (node) => renderCodeBlockMarkdown(node),
1752
+ addAttributes() {
1753
+ return {
1754
+ language: {
1755
+ default: null,
1756
+ parseHTML: (element) => parseLanguageFromElement(element),
1757
+ // pre 上的 data-language 与 code 上的 class 由节点级 renderHTML 统一产出
1758
+ rendered: false
1759
+ },
1760
+ label: {
1761
+ default: null,
1762
+ parseHTML: (element) => element.getAttribute("data-label") || null,
1763
+ rendered: false
1764
+ }
1765
+ };
1766
+ },
1767
+ parseHTML() {
1768
+ return [
1769
+ {
1770
+ tag: "pre",
1771
+ preserveWhitespace: "full",
1772
+ // 高亮后的 <span> 只取文本;pre 里可能被别的编辑器塞进 <code> 之外的包装,
1773
+ // 有 <code> 就以它为内容根,否则整段文本
1774
+ contentElement: (node) => {
1775
+ const el = node;
1776
+ return el.querySelector("code") ?? el;
1777
+ }
1778
+ }
1779
+ ];
1780
+ },
1781
+ renderHTML({ node, HTMLAttributes }) {
1782
+ const { language, label } = node.attrs;
1783
+ return [
1784
+ "pre",
1785
+ mergeAttributes$1(this.options.HTMLAttributes, HTMLAttributes, {
1786
+ "data-language": language || null,
1787
+ "data-label": label || null
1788
+ }),
1789
+ ["code", { class: language ? `${LANGUAGE_CLASS_PREFIX}${language}` : null }, 0]
1790
+ ];
1791
+ }
1792
+ });
1793
+ var COPY_FEEDBACK_MS = 1600;
1794
+ async function copyText(text) {
1795
+ try {
1796
+ if (typeof navigator !== "undefined" && navigator.clipboard?.writeText) {
1797
+ await navigator.clipboard.writeText(text);
1798
+ return true;
1799
+ }
1800
+ } catch {
1801
+ }
1802
+ try {
1803
+ if (typeof document === "undefined") return false;
1804
+ const area = document.createElement("textarea");
1805
+ area.value = text;
1806
+ area.setAttribute("readonly", "");
1807
+ area.style.position = "fixed";
1808
+ area.style.opacity = "0";
1809
+ document.body.appendChild(area);
1810
+ area.select();
1811
+ const ok = document.execCommand("copy");
1812
+ document.body.removeChild(area);
1813
+ return ok;
1814
+ } catch {
1815
+ return false;
1816
+ }
1817
+ }
1818
+ function useCopyFeedback() {
1819
+ const [state, setState] = useState("idle");
1820
+ const timer = useRef(null);
1821
+ useEffect(
1822
+ () => () => {
1823
+ if (timer.current) clearTimeout(timer.current);
1824
+ },
1825
+ []
1826
+ );
1827
+ const copy = (text) => {
1828
+ void copyText(text).then((ok) => {
1829
+ setState(ok ? "copied" : "failed");
1830
+ if (timer.current) clearTimeout(timer.current);
1831
+ timer.current = setTimeout(() => setState("idle"), COPY_FEEDBACK_MS);
1832
+ });
1833
+ };
1834
+ return [state, copy];
1835
+ }
1836
+ var COPY_LABELS = {
1837
+ idle: "复制",
1838
+ copied: "已复制",
1839
+ failed: "复制失败"
1840
+ };
1841
+ function useHighlighter(editor) {
1842
+ const storage = editor.storage;
1843
+ return storage.codeHighlight?.highlighter ?? null;
1844
+ }
1845
+ function CodeBlockNodeView(props) {
1846
+ const { node, editor } = props;
1847
+ const attrs = node.attrs;
1848
+ const highlighter = useHighlighter(editor);
1849
+ const title = getCodeBlockTitle(
1850
+ attrs,
1851
+ (input) => highlighter?.normalizeLanguage(input) ?? (input ?? "").trim().toLowerCase()
1852
+ );
1853
+ const [copyState, copy] = useCopyFeedback();
1854
+ return /* @__PURE__ */ jsxs(
1855
+ NodeViewWrapper,
1856
+ {
1857
+ className: "tiptap-code-block",
1858
+ "data-language": attrs.language ?? void 0,
1859
+ "data-label": attrs.label ?? void 0,
1860
+ children: [
1861
+ /* @__PURE__ */ jsxs("div", { className: "tiptap-code-block__header", contentEditable: false, children: [
1862
+ /* @__PURE__ */ jsx("span", { className: "tiptap-code-block__title", children: title }),
1863
+ /* @__PURE__ */ jsx(
1864
+ "button",
1865
+ {
1866
+ type: "button",
1867
+ className: "tiptap-code-block__copy",
1868
+ "data-state": copyState,
1869
+ "aria-live": "polite",
1870
+ onMouseDown: (event) => event.preventDefault(),
1871
+ onClick: () => copy(getCodeBlockSource(node)),
1872
+ children: COPY_LABELS[copyState]
1873
+ }
1874
+ )
1875
+ ] }),
1876
+ /* @__PURE__ */ jsx(
1877
+ NodeViewContent,
1878
+ {
1879
+ as: "pre",
1880
+ className: "tiptap-code-block__pre",
1881
+ style: { whiteSpace: "pre" }
1882
+ }
1883
+ )
1884
+ ]
1885
+ }
1886
+ );
1887
+ }
1888
+
1889
+ // src/components/tiptap-node/code-block-node/code-block-node-extension.ts
1890
+ var CodeBlockExtension = CodeBlockSchema.extend({
1891
+ addNodeView() {
1892
+ return ReactNodeViewRenderer(CodeBlockNodeView, {
1893
+ contentDOMElementTag: "code"
1894
+ });
1895
+ }
1896
+ });
1897
+ var CODE_GROUP_NAME = CODE_GROUP_TOKEN;
1898
+ var CODE_GROUP_ATTR = "data-code-group";
1899
+ function getCodeGroupLabel(child, normalizeLanguage) {
1900
+ return getCodeBlockTitle(child.attrs, normalizeLanguage);
1901
+ }
1902
+ function getCodeGroupSource(child) {
1903
+ return child.textContent;
1904
+ }
1905
+ function getCodeGroupTabKey(child, index) {
1906
+ const id = child.attrs.id;
1907
+ return id ? `id:${id}` : `index:${index}`;
1908
+ }
1909
+ function getCodeGroupTabs(group, normalizeLanguage) {
1910
+ const tabs = [];
1911
+ group.forEach((child, _offset, index) => {
1912
+ tabs.push({
1913
+ key: getCodeGroupTabKey(child, index),
1914
+ index,
1915
+ label: getCodeGroupLabel(child, normalizeLanguage),
1916
+ language: child.attrs.language ?? null,
1917
+ source: getCodeGroupSource(child)
1918
+ });
1919
+ });
1920
+ return tabs;
1921
+ }
1922
+ function findCodeGroupDepth($pos) {
1923
+ for (let depth = $pos.depth; depth > 0; depth -= 1) {
1924
+ if ($pos.node(depth).type.name === CODE_GROUP_NAME) return depth;
1925
+ }
1926
+ return null;
1927
+ }
1928
+ function exitCodeGroup(tr, editor) {
1929
+ const { $from } = tr.selection;
1930
+ const depth = findCodeGroupDepth($from);
1931
+ if (depth === null) return false;
1932
+ const after = $from.after(depth);
1933
+ const nodeAfter = tr.doc.nodeAt(after);
1934
+ if (nodeAfter) {
1935
+ tr.setSelection(Selection.near(tr.doc.resolve(after)));
1936
+ return true;
1937
+ }
1938
+ const paragraph = editor.schema.nodes.paragraph;
1939
+ if (!paragraph) return false;
1940
+ tr.insert(after, paragraph.create());
1941
+ tr.setSelection(TextSelection.create(tr.doc, after + 1));
1942
+ return true;
1943
+ }
1944
+ function elementChildren(el) {
1945
+ const out = [];
1946
+ const kids = el.childNodes;
1947
+ for (let i = 0; i < kids.length; i += 1) {
1948
+ const n = kids.item(i);
1949
+ if (n && n.nodeType === 1) out.push(n);
1950
+ }
1951
+ return out;
1952
+ }
1953
+ var CodeGroupSchema = Node$1.create({
1954
+ name: CODE_GROUP_NAME,
1955
+ group: "block",
1956
+ content: "codeBlock+",
1957
+ isolating: true,
1958
+ defining: true,
1959
+ selectable: true,
1960
+ // 快捷键要抢在 codeBlock(默认 100)前面
1961
+ priority: 110,
1962
+ // Markdown:`::: code-group … :::` 块级 tokenizer 与解析 / 输出
1963
+ markdownTokenName: CODE_GROUP_TOKEN,
1964
+ markdownTokenizer: codeGroupTokenizer,
1965
+ parseMarkdown: parseCodeGroupToken,
1966
+ renderMarkdown: renderCodeGroupMarkdown,
1967
+ parseHTML() {
1968
+ return [
1969
+ {
1970
+ tag: `div[${CODE_GROUP_ATTR}]`,
1971
+ getAttrs: (element) => {
1972
+ const kids = elementChildren(element);
1973
+ if (kids.length === 0) return false;
1974
+ return kids.every((k) => k.tagName.toLowerCase() === "pre") ? null : false;
1975
+ }
1976
+ }
1977
+ ];
1978
+ },
1979
+ renderHTML({ HTMLAttributes }) {
1980
+ return ["div", mergeAttributes$1(HTMLAttributes, { [CODE_GROUP_ATTR]: "" }), 0];
1981
+ },
1982
+ addKeyboardShortcuts() {
1983
+ const inGroupChild = (editor) => {
1984
+ const { $from, empty } = editor.state.selection;
1985
+ if (!empty) return false;
1986
+ if ($from.parent.type.name !== "codeBlock") return false;
1987
+ return findCodeGroupDepth($from) !== null;
1988
+ };
1989
+ return {
1990
+ // 三连回车:与官方 CodeBlock 相同的触发条件,但退出的是整个分组
1991
+ Enter: ({ editor }) => {
1992
+ if (!inGroupChild(editor)) return false;
1993
+ const { $from } = editor.state.selection;
1994
+ const isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2;
1995
+ if (!isAtEnd || !$from.parent.textContent.endsWith("\n\n")) return false;
1996
+ return editor.commands.command(({ tr }) => {
1997
+ tr.delete($from.pos - 2, $from.pos);
1998
+ return exitCodeGroup(tr, editor);
1999
+ });
2000
+ },
2001
+ // 末尾 ArrowDown:最后一个标签的末尾退出分组;中间标签交给官方处理(进入下一个子块)
2002
+ ArrowDown: ({ editor }) => {
2003
+ if (!inGroupChild(editor)) return false;
2004
+ const { $from } = editor.state.selection;
2005
+ const isAtEnd = $from.parentOffset === $from.parent.nodeSize - 2;
2006
+ if (!isAtEnd) return false;
2007
+ const depth = findCodeGroupDepth($from);
2008
+ if (depth === null) return false;
2009
+ const group = $from.node(depth);
2010
+ const isLastChild = $from.index(depth) === group.childCount - 1;
2011
+ if (!isLastChild) return false;
2012
+ return editor.commands.command(({ tr }) => exitCodeGroup(tr, editor));
2013
+ }
2014
+ };
2015
+ }
2016
+ });
2017
+
2018
+ // src/components/tiptap-node/code-block-node/code-group-view-state.ts
2019
+ function resolveActiveIndex(keys, activeKey, previousIndex) {
2020
+ if (keys.length === 0) return 0;
2021
+ if (activeKey !== null) {
2022
+ const found = keys.indexOf(activeKey);
2023
+ if (found >= 0) return found;
2024
+ }
2025
+ return Math.min(Math.max(previousIndex, 0), keys.length - 1);
2026
+ }
2027
+ function tabKeyAction(key, focusedIndex, count) {
2028
+ if (count <= 0) return null;
2029
+ switch (key) {
2030
+ case "ArrowRight":
2031
+ return { type: "focus", index: (focusedIndex + 1) % count };
2032
+ case "ArrowLeft":
2033
+ return { type: "focus", index: (focusedIndex - 1 + count) % count };
2034
+ case "Home":
2035
+ return { type: "focus", index: 0 };
2036
+ case "End":
2037
+ return { type: "focus", index: count - 1 };
2038
+ case "Enter":
2039
+ case " ":
2040
+ case "Spacebar":
2041
+ return { type: "activate" };
2042
+ default:
2043
+ return null;
2044
+ }
2045
+ }
2046
+ function childContentRange(group, groupPos, index) {
2047
+ if (index < 0 || index >= group.childCount) return null;
2048
+ let offset6 = groupPos + 1;
2049
+ for (let i = 0; i < index; i += 1) offset6 += group.child(i).nodeSize;
2050
+ const child = group.child(index);
2051
+ return { from: offset6 + 1, to: offset6 + 1 + child.content.size };
2052
+ }
2053
+ function childIndexAtPos(group, groupPos, pos) {
2054
+ let offset6 = groupPos + 1;
2055
+ for (let i = 0; i < group.childCount; i += 1) {
2056
+ const child = group.child(i);
2057
+ const from = offset6 + 1;
2058
+ const to = from + child.content.size;
2059
+ if (pos >= from && pos <= to) return i;
2060
+ offset6 += child.nodeSize;
2061
+ }
2062
+ return null;
2063
+ }
2064
+ function childEndPos(group, groupPos, index) {
2065
+ return childContentRange(group, groupPos, index)?.to ?? null;
2066
+ }
2067
+ var PANEL_ATTR = "data-code-panel";
2068
+ function useHighlighter2(editor) {
2069
+ const storage = editor.storage;
2070
+ return storage.codeHighlight?.highlighter ?? null;
2071
+ }
2072
+ function CodeGroupNodeView(props) {
2073
+ const { node, editor, getPos } = props;
2074
+ const highlighter = useHighlighter2(editor);
2075
+ const normalize = useCallback(
2076
+ (input) => highlighter?.normalizeLanguage(input) ?? (input ?? "").trim().toLowerCase(),
2077
+ [highlighter]
2078
+ );
2079
+ const tabs = useMemo(() => getCodeGroupTabs(node, normalize), [node, normalize]);
2080
+ const keys = useMemo(() => tabs.map((t) => t.key), [tabs]);
2081
+ const [activeKey, setActiveKey] = useState(() => keys[0] ?? null);
2082
+ const [previousIndex, setPreviousIndex] = useState(0);
2083
+ const activeIndex = resolveActiveIndex(keys, activeKey, previousIndex);
2084
+ const [focusedIndex, setFocusedIndex] = useState(0);
2085
+ const [copyState, copy] = useCopyFeedback();
2086
+ const panelsRef = useRef(null);
2087
+ const tabRefs = useRef([]);
2088
+ const activeIndexRef = useRef(activeIndex);
2089
+ activeIndexRef.current = activeIndex;
2090
+ const activate = useCallback(
2091
+ (index) => {
2092
+ if (index < 0 || index >= keys.length) return;
2093
+ const current = activeIndexRef.current;
2094
+ if (index !== current) {
2095
+ const pos = getPos();
2096
+ if (typeof pos === "number") {
2097
+ const { selection, doc } = editor.state;
2098
+ const group = doc.nodeAt(pos);
2099
+ if (group && childIndexAtPos(group, pos, selection.from) === current) {
2100
+ const target = childEndPos(group, pos, index);
2101
+ if (target !== null) {
2102
+ editor.view.dispatch(editor.state.tr.setSelection(TextSelection.create(doc, target)));
2103
+ }
2104
+ }
2105
+ }
2106
+ }
2107
+ setActiveKey(keys[index]);
2108
+ setPreviousIndex(index);
2109
+ setFocusedIndex(index);
2110
+ },
2111
+ [editor, getPos, keys]
2112
+ );
2113
+ const syncPanels = useCallback(() => {
2114
+ const panels = panelsRef.current;
2115
+ if (!panels) return;
2116
+ const container = panels.querySelector("[data-node-view-content-react]");
2117
+ if (!container) return;
2118
+ const children = Array.from(container.children);
2119
+ children.forEach((child, index) => {
2120
+ const active = index === activeIndexRef.current;
2121
+ child.setAttribute(PANEL_ATTR, active ? "active" : "hidden");
2122
+ child.setAttribute("role", "tabpanel");
2123
+ if (active) child.removeAttribute("aria-hidden");
2124
+ else child.setAttribute("aria-hidden", "true");
2125
+ });
2126
+ }, []);
2127
+ useLayoutEffect(() => {
2128
+ syncPanels();
2129
+ });
2130
+ useEffect(() => {
2131
+ const panels = panelsRef.current;
2132
+ if (!panels || typeof MutationObserver === "undefined") return;
2133
+ const observer = new MutationObserver(() => syncPanels());
2134
+ observer.observe(panels, { childList: true, subtree: true });
2135
+ return () => observer.disconnect();
2136
+ }, [syncPanels]);
2137
+ useEffect(() => {
2138
+ const onSelection = () => {
2139
+ const pos = getPos();
2140
+ if (typeof pos !== "number") return;
2141
+ const group = editor.state.doc.nodeAt(pos);
2142
+ if (!group || group.type !== node.type) return;
2143
+ const index = childIndexAtPos(group, pos, editor.state.selection.from);
2144
+ if (index === null || index === activeIndexRef.current) return;
2145
+ setActiveKey(getCodeGroupTabKey(group.child(index), index));
2146
+ setPreviousIndex(index);
2147
+ };
2148
+ editor.on("selectionUpdate", onSelection);
2149
+ return () => {
2150
+ editor.off("selectionUpdate", onSelection);
2151
+ };
2152
+ }, [editor, getPos, node.type]);
2153
+ const onKeyDown = (event) => {
2154
+ const action = tabKeyAction(event.key, focusedIndex, keys.length);
2155
+ if (!action) return;
2156
+ event.preventDefault();
2157
+ if (action.type === "focus") {
2158
+ setFocusedIndex(action.index);
2159
+ tabRefs.current[action.index]?.focus();
2160
+ } else {
2161
+ activate(focusedIndex);
2162
+ }
2163
+ };
2164
+ const activeTab = tabs[activeIndex];
2165
+ const groupId = node.attrs.id ?? "code-group";
2166
+ return /* @__PURE__ */ jsxs(NodeViewWrapper, { className: "tiptap-code-group", "data-code-group": "", children: [
2167
+ /* @__PURE__ */ jsxs("div", { className: "tiptap-code-group__bar", contentEditable: false, children: [
2168
+ /* @__PURE__ */ jsx(
2169
+ "div",
2170
+ {
2171
+ className: "tiptap-code-group__tabs",
2172
+ role: "tablist",
2173
+ "aria-label": "代码分组",
2174
+ onKeyDown,
2175
+ children: tabs.map((tab, index) => /* @__PURE__ */ jsx(
2176
+ "button",
2177
+ {
2178
+ ref: (el) => {
2179
+ tabRefs.current[index] = el;
2180
+ },
2181
+ type: "button",
2182
+ role: "tab",
2183
+ id: `${groupId}-tab-${index}`,
2184
+ className: "tiptap-code-group__tab",
2185
+ "aria-selected": index === activeIndex,
2186
+ tabIndex: index === focusedIndex ? 0 : -1,
2187
+ "data-active": index === activeIndex ? "" : void 0,
2188
+ onMouseDown: (event) => event.preventDefault(),
2189
+ onClick: () => activate(index),
2190
+ onFocus: () => setFocusedIndex(index),
2191
+ children: tab.label
2192
+ },
2193
+ tab.key
2194
+ ))
2195
+ }
2196
+ ),
2197
+ /* @__PURE__ */ jsx(
2198
+ "button",
2199
+ {
2200
+ type: "button",
2201
+ className: "tiptap-code-group__copy",
2202
+ "data-state": copyState,
2203
+ "aria-live": "polite",
2204
+ onMouseDown: (event) => event.preventDefault(),
2205
+ onClick: () => copy(activeTab?.source ?? ""),
2206
+ children: COPY_LABELS[copyState]
2207
+ }
2208
+ )
2209
+ ] }),
2210
+ /* @__PURE__ */ jsx("div", { ref: panelsRef, className: "tiptap-code-group__body", children: /* @__PURE__ */ jsx(NodeViewContent, { as: "div", className: "tiptap-code-group__panels" }) })
2211
+ ] });
2212
+ }
2213
+
2214
+ // src/components/tiptap-node/code-block-node/code-group-node-extension.ts
2215
+ var CodeGroupExtension = CodeGroupSchema.extend({
2216
+ addNodeView() {
2217
+ return ReactNodeViewRenderer(CodeGroupNodeView);
2218
+ }
2219
+ });
2220
+ var CONTENT_FORMATS = ["json", "html", "markdown"];
2221
+ function isContentFormat(value) {
2222
+ return typeof value === "string" && CONTENT_FORMATS.includes(value);
2223
+ }
2224
+ function freshMarked() {
2225
+ return new Marked();
2226
+ }
2227
+ function createMarkdownExtension() {
2228
+ return Markdown.configure({ marked: freshMarked() });
2229
+ }
2230
+ function createMarkdownManager(extensions) {
2231
+ return new MarkdownManager({ extensions, marked: freshMarked() });
2232
+ }
2233
+ var normalizeNewlines = (s) => s.replace(/\r\n?/g, "\n");
2234
+ function parseEditorContent(content, format, extensions) {
2235
+ if (content === void 0 || content === null || content === "") return { ok: true, json: null };
2236
+ if (format !== void 0 && !isContentFormat(format)) {
2237
+ return {
2238
+ ok: false,
2239
+ error: `contentFormat 只接受 "json" | "html" | "markdown",收到 ${JSON.stringify(format)}`
2240
+ };
2241
+ }
2242
+ const isString = typeof content === "string";
2243
+ const isObject = typeof content === "object";
2244
+ const resolved = format ?? (isString ? "html" : "json");
2245
+ if (resolved === "json") {
2246
+ if (!isObject)
2247
+ return { ok: false, error: `contentFormat="json" 需要传 JSON 对象,收到 ${typeof content}` };
2248
+ return { ok: true, json: content };
2249
+ }
2250
+ if (!isString)
2251
+ return { ok: false, error: `contentFormat="${resolved}" 需要传字符串,收到 ${typeof content}` };
2252
+ try {
2253
+ if (resolved === "markdown") {
2254
+ return { ok: true, json: createMarkdownManager(extensions).parse(normalizeNewlines(content)) };
2255
+ }
2256
+ return { ok: true, json: generateJSON(content, extensions) };
2257
+ } catch (error) {
2258
+ return {
2259
+ ok: false,
2260
+ error: `${resolved} 内容解析失败:${error?.message ?? String(error)}`
2261
+ };
2262
+ }
2263
+ }
2264
+ function serializeEditorMarkdown(json, extensions) {
2265
+ return createMarkdownManager(extensions).serialize(json);
2266
+ }
2267
+ function getEditorMarkdown(editor) {
2268
+ const manager = editor.markdown;
2269
+ if (manager) return manager.serialize(editor.getJSON());
2270
+ return serializeEditorMarkdown(editor.getJSON(), editor.extensionManager.extensions);
2271
+ }
2272
+ var escapeAttr = (s) => s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2273
+ function collectCodeBlocks(node, out = []) {
2274
+ if (node.type === "codeBlock") {
2275
+ out.push(node);
2276
+ return out;
2277
+ }
2278
+ for (const child of node.content ?? []) collectCodeBlocks(child, out);
2279
+ return out;
2280
+ }
2281
+ function codeBlockText(node) {
2282
+ return (node.content ?? []).map((n) => n.type === "text" ? n.text ?? "" : "").join("");
2283
+ }
2284
+ async function renderHighlightedHTML(source, options = {}) {
2285
+ const isEditor = typeof source.getJSON === "function";
2286
+ const json = isEditor ? source.getJSON() : source;
2287
+ const extensions = options.extensions ?? (isEditor ? source.extensionManager.extensions : void 0);
2288
+ if (!extensions) throw new Error("renderHighlightedHTML:传 JSON 时必须提供 extensions");
2289
+ const editorHighlighter = isEditor ? source.storage.codeHighlight?.highlighter : void 0;
2290
+ const highlighter = options.highlighter ?? editorHighlighter ?? createCodeHighlighter(options);
2291
+ const theme = options.theme === void 0 || options.theme === "light" || options.theme === "dark" ? resolveShikiTheme(options.shikiTheme, options.theme === "dark") : options.theme;
2292
+ const blocks = collectCodeBlocks(json);
2293
+ const rendered = await Promise.all(
2294
+ blocks.map(async (block) => {
2295
+ const attrs = block.attrs ?? {};
2296
+ const result = await highlighter.highlight(codeBlockText(block), attrs.language, theme);
2297
+ const extra = (attrs.language ? ` data-language="${escapeAttr(attrs.language)}"` : "") + (attrs.label ? ` data-label="${escapeAttr(attrs.label)}"` : "");
2298
+ return result.html.replace(/^<pre\b/, `<pre${extra}`);
2299
+ })
2300
+ );
2301
+ let index = 0;
2302
+ const semantic = generateHTML(json, extensions);
2303
+ return semantic.replace(/<pre\b[^>]*>[\s\S]*?<\/pre>/g, (match) => {
2304
+ const next = rendered[index];
2305
+ index += 1;
2306
+ return next ?? match;
2307
+ });
2308
+ }
2309
+
2310
+ // src/components/tiptap-node/collapsible-node/collapsible-view-state.ts
2311
+ function contentRange(node, pos) {
2312
+ return { from: pos + 1, to: pos + 1 + node.content.size };
2313
+ }
2314
+ function isSelectionInside(node, pos, selectionFrom) {
2315
+ const { from, to } = contentRange(node, pos);
2316
+ return selectionFrom >= from && selectionFrom <= to;
2317
+ }
2318
+ function positionAfter(node, pos) {
2319
+ return pos + node.nodeSize;
2320
+ }
2321
+ function apiParamPath($pos, ownName) {
2322
+ const names = [];
2323
+ for (let depth = 1; depth <= $pos.depth; depth += 1) {
2324
+ const node = $pos.node(depth);
2325
+ if (node.type.name === "apiParam") names.push(String(node.attrs.name ?? ""));
2326
+ }
2327
+ if (names.length) names[names.length - 1] = ownName;
2328
+ else names.push(ownName);
2329
+ return names.filter(Boolean).join(".");
2330
+ }
2331
+ function normalizeField(raw, nullable) {
2332
+ const trimmed = raw.trim();
2333
+ if (nullable) return trimmed || null;
2334
+ return trimmed;
2335
+ }
2336
+ function useCollapsible(props, options = {}) {
2337
+ const { editor, node, getPos } = props;
2338
+ const [open, setOpen] = useState(options.defaultOpen ?? false);
2339
+ const openRef = useRef(open);
2340
+ openRef.current = open;
2341
+ useEffect(() => {
2342
+ const follow = () => {
2343
+ if (openRef.current) return;
2344
+ const pos = getPos();
2345
+ if (typeof pos !== "number") return;
2346
+ const current = editor.state.doc.nodeAt(pos);
2347
+ if (!current || current.type !== node.type) return;
2348
+ if (isSelectionInside(current, pos, editor.state.selection.from)) setOpen(true);
2349
+ };
2350
+ editor.on("selectionUpdate", follow);
2351
+ return () => {
2352
+ editor.off("selectionUpdate", follow);
2353
+ };
2354
+ }, [editor, getPos, node.type]);
2355
+ const toggle = useCallback(() => {
2356
+ const next = !openRef.current;
2357
+ if (!next) {
2358
+ const pos = getPos();
2359
+ if (typeof pos === "number") {
2360
+ const current = editor.state.doc.nodeAt(pos);
2361
+ if (current && isSelectionInside(current, pos, editor.state.selection.from)) {
2362
+ const target = Math.min(positionAfter(current, pos), editor.state.doc.content.size);
2363
+ editor.view.dispatch(
2364
+ editor.state.tr.setSelection(TextSelection.near(editor.state.doc.resolve(target), 1))
2365
+ );
2366
+ }
2367
+ }
2368
+ }
2369
+ setOpen(next);
2370
+ }, [editor, getPos]);
2371
+ return { open, setOpen, toggle };
2372
+ }
2373
+ function CollapsibleShell({
2374
+ props,
2375
+ kind,
2376
+ open,
2377
+ onToggle,
2378
+ collapsible = true,
2379
+ header,
2380
+ actions,
2381
+ contentLead,
2382
+ className
2383
+ }) {
2384
+ const isEditable = props.editor.isEditable;
2385
+ return /* @__PURE__ */ jsxs(
2386
+ NodeViewWrapper,
2387
+ {
2388
+ className: ["tt-collapsible", `tt-collapsible--${kind}`, className].filter(Boolean).join(" "),
2389
+ "data-collapsible": kind,
2390
+ "data-open": open ? "true" : "false",
2391
+ children: [
2392
+ /* @__PURE__ */ jsxs("div", { className: "tt-collapsible__header", contentEditable: false, children: [
2393
+ collapsible ? /* @__PURE__ */ jsx(
2394
+ "button",
2395
+ {
2396
+ type: "button",
2397
+ className: "tt-collapsible__toggle",
2398
+ "aria-expanded": open,
2399
+ "aria-label": open ? "收起" : "展开",
2400
+ onMouseDown: (e) => e.preventDefault(),
2401
+ onClick: onToggle,
2402
+ onKeyDown: (e) => {
2403
+ if (e.key === "Enter" || e.key === " ") {
2404
+ e.preventDefault();
2405
+ onToggle();
2406
+ }
2407
+ },
2408
+ children: /* @__PURE__ */ jsx("span", { className: "tt-collapsible__chevron", "aria-hidden": "true" })
2409
+ }
2410
+ ) : /* @__PURE__ */ jsx(
2411
+ "span",
2412
+ {
2413
+ className: "tt-collapsible__toggle tt-collapsible__toggle--placeholder",
2414
+ "aria-hidden": "true"
2415
+ }
2416
+ ),
2417
+ /* @__PURE__ */ jsx("div", { className: "tt-collapsible__fields", children: header }),
2418
+ isEditable && actions ? /* @__PURE__ */ jsx("div", { className: "tt-collapsible__actions", children: actions }) : null
2419
+ ] }),
2420
+ contentLead && open ? /* @__PURE__ */ jsx("div", { className: "tt-collapsible__lead", contentEditable: false, children: contentLead }) : null,
2421
+ /* @__PURE__ */ jsx(NodeViewContent, { as: "div", className: "tt-collapsible__content", hidden: !open })
2422
+ ]
2423
+ }
2424
+ );
2425
+ }
2426
+ function textColumns(text) {
2427
+ let cols = 0;
2428
+ for (const ch of text) {
2429
+ const code = ch.codePointAt(0) ?? 0;
2430
+ const wide = code >= 4352 && code <= 4447 || code >= 11904 && code <= 42191 || code >= 44032 && code <= 55203 || code >= 63744 && code <= 64255 || code >= 65072 && code <= 65103 || code >= 65280 && code <= 65376 || code >= 65504 && code <= 65510 || code >= 126976;
2431
+ cols += wide ? 2 : 1;
2432
+ }
2433
+ return cols;
2434
+ }
2435
+ function InlineField({
2436
+ field,
2437
+ value,
2438
+ placeholder,
2439
+ ariaLabel,
2440
+ nullable,
2441
+ readOnly,
2442
+ onCommit,
2443
+ onEnter,
2444
+ className
2445
+ }) {
2446
+ const [draft, setDraft] = useState(value ?? "");
2447
+ const committed = useRef(value ?? "");
2448
+ const reverting = useRef(false);
2449
+ useEffect(() => {
2450
+ setDraft(value ?? "");
2451
+ committed.current = value ?? "";
2452
+ }, [value]);
2453
+ if (readOnly) {
2454
+ if (!value) return null;
2455
+ return /* @__PURE__ */ jsx(
2456
+ "span",
2457
+ {
2458
+ className: ["tt-field", `tt-field--${field}`, className].filter(Boolean).join(" "),
2459
+ "data-field": field,
2460
+ children: value
2461
+ }
2462
+ );
2463
+ }
2464
+ const commit = () => {
2465
+ if (reverting.current) {
2466
+ reverting.current = false;
2467
+ return;
2468
+ }
2469
+ const next = normalizeField(draft, nullable);
2470
+ if ((next ?? "") !== committed.current) {
2471
+ committed.current = next ?? "";
2472
+ onCommit(next);
2473
+ }
2474
+ };
2475
+ return /* @__PURE__ */ jsx(
2476
+ "input",
2477
+ {
2478
+ type: "text",
2479
+ "data-field": field,
2480
+ className: ["tt-field", `tt-field--${field}`, className].filter(Boolean).join(" "),
2481
+ value: draft,
2482
+ placeholder,
2483
+ "aria-label": ariaLabel,
2484
+ style: { width: `calc(${Math.max(textColumns(draft || placeholder), 2)}ch + 2px)` },
2485
+ onChange: (e) => setDraft(e.target.value),
2486
+ onBlur: commit,
2487
+ onKeyDown: (e) => {
2488
+ if (e.key === "Enter") {
2489
+ e.preventDefault();
2490
+ commit();
2491
+ if (onEnter) onEnter();
2492
+ else e.currentTarget.blur();
2493
+ } else if (e.key === "Escape") {
2494
+ e.preventDefault();
2495
+ setDraft(committed.current);
2496
+ reverting.current = true;
2497
+ e.currentTarget.blur();
2498
+ }
2499
+ }
2500
+ }
2501
+ );
2502
+ }
2503
+
2504
+ // src/components/tiptap-node/collapsible-node/collapsible-types.ts
2505
+ var CALLOUT_TYPES = ["info", "tip", "warning", "danger"];
2506
+ var CALLOUT_DEFAULT_TITLES = {
2507
+ info: "信息",
2508
+ tip: "提示",
2509
+ warning: "注意",
2510
+ danger: "危险"
2511
+ };
2512
+ function isCalloutType(value) {
2513
+ return typeof value === "string" && CALLOUT_TYPES.includes(value);
2514
+ }
2515
+ var CONTAINER_NODE_NAMES = ["callout", "details", "apiParam"];
2516
+ var CONTAINER_MARKDOWN_NAMES = {
2517
+ details: "details",
2518
+ info: "callout",
2519
+ tip: "callout",
2520
+ warning: "callout",
2521
+ danger: "callout",
2522
+ param: "apiParam"
2523
+ };
2524
+ function CalloutNodeView(props) {
2525
+ const { node, editor, updateAttributes } = props;
2526
+ const attrs = node.attrs;
2527
+ const { open, toggle } = useCollapsible(props, { defaultOpen: true });
2528
+ const isEditable = editor.isEditable;
2529
+ const shownTitle = attrs.title ?? CALLOUT_DEFAULT_TITLES[attrs.type];
2530
+ return /* @__PURE__ */ jsx(
2531
+ CollapsibleShell,
2532
+ {
2533
+ props,
2534
+ kind: "callout",
2535
+ open,
2536
+ onToggle: toggle,
2537
+ className: `tt-callout tt-callout--${attrs.type}`,
2538
+ header: isEditable ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
2539
+ /* @__PURE__ */ jsx("fieldset", { className: "tt-callout__types", "aria-label": "提示框类型", children: CALLOUT_TYPES.map((type) => /* @__PURE__ */ jsx(
2540
+ "button",
2541
+ {
2542
+ type: "button",
2543
+ className: `tt-callout__type tt-callout__type--${type}`,
2544
+ "data-callout-type": type,
2545
+ "aria-pressed": type === attrs.type,
2546
+ title: CALLOUT_DEFAULT_TITLES[type],
2547
+ onMouseDown: (e) => e.preventDefault(),
2548
+ onClick: () => updateAttributes({ type })
2549
+ },
2550
+ type
2551
+ )) }),
2552
+ /* @__PURE__ */ jsx(
2553
+ InlineField,
2554
+ {
2555
+ field: "title",
2556
+ value: attrs.title,
2557
+ placeholder: CALLOUT_DEFAULT_TITLES[attrs.type],
2558
+ ariaLabel: "提示框标题",
2559
+ nullable: true,
2560
+ readOnly: false,
2561
+ onCommit: (value) => updateAttributes({ title: value }),
2562
+ className: "tt-callout__title"
2563
+ }
2564
+ )
2565
+ ] }) : /* @__PURE__ */ jsx("span", { className: "tt-callout__title tt-field", "data-field": "title", children: shownTitle })
2566
+ }
2567
+ );
2568
+ }
2569
+ function parseContainerBody(token, helpers) {
2570
+ const tokens = token.tokens ?? [];
2571
+ const children = tokens.length ? helpers.parseChildren(tokens) : [];
2572
+ return children.length ? children : [helpers.createNode("paragraph")];
2573
+ }
2574
+ var CalloutSchema = Node$1.create({
2575
+ name: "callout",
2576
+ group: "block",
2577
+ content: "block+",
2578
+ isolating: true,
2579
+ defining: true,
2580
+ addAttributes() {
2581
+ return {
2582
+ type: {
2583
+ default: "info",
2584
+ parseHTML: (el) => {
2585
+ const raw = el.getAttribute("data-callout");
2586
+ return isCalloutType(raw) ? raw : "info";
2587
+ },
2588
+ rendered: false
2589
+ },
2590
+ title: {
2591
+ default: null,
2592
+ parseHTML: (el) => el.getAttribute("data-title") || null,
2593
+ rendered: false
2594
+ }
2595
+ };
2596
+ },
2597
+ parseHTML() {
2598
+ return [{ tag: "div[data-callout]" }];
2599
+ },
2600
+ renderHTML({ node, HTMLAttributes }) {
2601
+ const { type, title } = node.attrs;
2602
+ return [
2603
+ "div",
2604
+ mergeAttributes$1(HTMLAttributes, { "data-callout": type, "data-title": title || null }),
2605
+ 0
2606
+ ];
2607
+ },
2608
+ markdownTokenName: "callout",
2609
+ markdownTokenizer: createContainerTokenizer("callout", CALLOUT_TYPES),
2610
+ parseMarkdown: (token, helpers) => helpers.createNode(
2611
+ "callout",
2612
+ {
2613
+ type: isCalloutType(token.name) ? token.name : "info",
2614
+ title: token.info || null
2615
+ },
2616
+ parseContainerBody(token, helpers)
2617
+ ),
2618
+ renderMarkdown: (node, h) => {
2619
+ const { type, title } = node.attrs ?? {};
2620
+ return renderContainerMarkdown(
2621
+ isCalloutType(type) ? type : "info",
2622
+ title ?? "",
2623
+ h.renderChildren(node.content ?? [], "\n\n")
2624
+ );
2625
+ }
2626
+ });
2627
+
2628
+ // src/components/tiptap-node/collapsible-node/callout-node-extension.ts
2629
+ var CalloutExtension = CalloutSchema.extend({
2630
+ addNodeView() {
2631
+ return ReactNodeViewRenderer(CalloutNodeView);
2632
+ }
2633
+ });
2634
+ function DetailsNodeView(props) {
2635
+ const { node, editor, updateAttributes } = props;
2636
+ const attrs = node.attrs;
2637
+ const { open, toggle } = useCollapsible(props);
2638
+ const isEditable = editor.isEditable;
2639
+ return /* @__PURE__ */ jsx(
2640
+ CollapsibleShell,
2641
+ {
2642
+ props,
2643
+ kind: "details",
2644
+ open,
2645
+ onToggle: toggle,
2646
+ header: isEditable ? /* @__PURE__ */ jsx(
2647
+ InlineField,
2648
+ {
2649
+ field: "title",
2650
+ value: attrs.title,
2651
+ placeholder: "标题",
2652
+ ariaLabel: "折叠块标题",
2653
+ nullable: true,
2654
+ readOnly: false,
2655
+ onCommit: (value) => updateAttributes({ title: value }),
2656
+ className: "tt-details__title"
2657
+ }
2658
+ ) : /* @__PURE__ */ jsx("span", { className: "tt-details__title tt-field", "data-field": "title", children: attrs.title ?? "详情" })
2659
+ }
2660
+ );
2661
+ }
2662
+ var DEFAULT_SUMMARY = "详情";
2663
+ function childElements(el) {
2664
+ const out = [];
2665
+ for (let i = 0; i < el.childNodes.length; i += 1) {
2666
+ const n = el.childNodes.item(i);
2667
+ if (n && n.nodeType === 1) out.push(n);
2668
+ }
2669
+ return out;
2670
+ }
2671
+ function readTitle(el) {
2672
+ const direct = el.getAttribute("data-title");
2673
+ if (direct) return direct;
2674
+ const summary = childElements(el).find((c) => c.tagName.toLowerCase() === "summary");
2675
+ const text = summary?.textContent?.trim();
2676
+ return text || null;
2677
+ }
2678
+ function readContent(el) {
2679
+ const wrapper = childElements(el).find((c) => c.hasAttribute("data-details-content"));
2680
+ if (wrapper) return wrapper;
2681
+ const tmp = el.ownerDocument.createElement("div");
2682
+ for (const child of Array.from(el.childNodes)) {
2683
+ if (child.nodeName.toLowerCase() === "summary") continue;
2684
+ tmp.appendChild(child.cloneNode(true));
2685
+ }
2686
+ return tmp;
2687
+ }
2688
+ var DetailsSchema = Node$1.create({
2689
+ name: "details",
2690
+ group: "block",
2691
+ content: "block+",
2692
+ isolating: true,
2693
+ defining: true,
2694
+ addAttributes() {
2695
+ return {
2696
+ title: { default: null, parseHTML: (el) => readTitle(el), rendered: false }
2697
+ };
2698
+ },
2699
+ parseHTML() {
2700
+ return [
2701
+ {
2702
+ tag: "details",
2703
+ // 参数行也用 <details>,由 apiParam 的规则(优先级更高)处理
2704
+ getAttrs: (el) => el.hasAttribute("data-param") ? false : null,
2705
+ contentElement: (el) => readContent(el)
2706
+ }
2707
+ ];
2708
+ },
2709
+ renderHTML({ node, HTMLAttributes }) {
2710
+ const { title } = node.attrs;
2711
+ return [
2712
+ "details",
2713
+ mergeAttributes$1(HTMLAttributes, { "data-details": "", "data-title": title || null }),
2714
+ ["summary", {}, title || DEFAULT_SUMMARY],
2715
+ ["div", { "data-details-content": "" }, 0]
2716
+ ];
2717
+ },
2718
+ markdownTokenName: "details",
2719
+ markdownTokenizer: createContainerTokenizer("details", ["details"]),
2720
+ parseMarkdown: (token, helpers) => helpers.createNode(
2721
+ "details",
2722
+ { title: token.info || null },
2723
+ parseContainerBody(token, helpers)
2724
+ ),
2725
+ renderMarkdown: (node, h) => {
2726
+ const { title } = node.attrs ?? {};
2727
+ return renderContainerMarkdown(
2728
+ "details",
2729
+ title ?? "",
2730
+ h.renderChildren(node.content ?? [], "\n\n")
2731
+ );
2732
+ }
2733
+ });
2734
+
2735
+ // src/components/tiptap-node/collapsible-node/details-node-extension.ts
2736
+ var DetailsExtension = DetailsSchema.extend({
2737
+ addNodeView() {
2738
+ return ReactNodeViewRenderer(DetailsNodeView);
2739
+ }
2740
+ });
2741
+ function ApiParamNodeView(props) {
2742
+ const { node, editor, getPos, updateAttributes, deleteNode: deleteNode2 } = props;
2743
+ const attrs = node.attrs;
2744
+ const { open, setOpen, toggle } = useCollapsible(props);
2745
+ const isEditable = editor.isEditable;
2746
+ const hasBody = node.content.size > 0;
2747
+ const path = useMemo(() => {
2748
+ const pos = getPos();
2749
+ if (typeof pos !== "number") return null;
2750
+ const $inside = editor.state.doc.resolve(pos + 1);
2751
+ const full = apiParamPath($inside, attrs.name);
2752
+ return full.includes(".") ? full : null;
2753
+ }, [editor.state.doc, getPos, attrs.name]);
2754
+ const focusBody = () => {
2755
+ const pos = getPos();
2756
+ if (typeof pos !== "number") return;
2757
+ const current = editor.state.doc.nodeAt(pos);
2758
+ if (!current) return;
2759
+ let tr = editor.state.tr;
2760
+ if (current.content.size === 0) tr = tr.insert(pos + 1, editor.schema.nodes.paragraph.create());
2761
+ tr = tr.setSelection(TextSelection.create(tr.doc, pos + 2));
2762
+ editor.view.dispatch(tr);
2763
+ editor.view.focus();
2764
+ setOpen(true);
2765
+ };
2766
+ const addChild = () => {
2767
+ const pos = getPos();
2768
+ if (typeof pos !== "number") return;
2769
+ const current = editor.state.doc.nodeAt(pos);
2770
+ if (!current) return;
2771
+ const child = editor.schema.nodes.apiParam.create({
2772
+ name: "",
2773
+ type: null,
2774
+ required: false,
2775
+ default: null,
2776
+ summary: null
2777
+ });
2778
+ const at = pos + 1 + current.content.size;
2779
+ editor.view.dispatch(editor.state.tr.insert(at, child));
2780
+ setOpen(true);
2781
+ queueMicrotask(() => {
2782
+ const dom = editor.view.nodeDOM(at);
2783
+ dom?.querySelector('input[data-field="name"]')?.focus();
2784
+ });
2785
+ };
2786
+ return /* @__PURE__ */ jsx(
2787
+ CollapsibleShell,
2788
+ {
2789
+ props,
2790
+ kind: "apiParam",
2791
+ open,
2792
+ onToggle: toggle,
2793
+ collapsible: hasBody,
2794
+ contentLead: path ? /* @__PURE__ */ jsx("span", { className: "tt-api-param__path", children: path }) : null,
2795
+ header: /* @__PURE__ */ jsxs(Fragment$1, { children: [
2796
+ /* @__PURE__ */ jsx(
2797
+ InlineField,
2798
+ {
2799
+ field: "name",
2800
+ value: attrs.name,
2801
+ placeholder: "参数名",
2802
+ ariaLabel: "参数名",
2803
+ nullable: false,
2804
+ readOnly: !isEditable,
2805
+ onCommit: (v) => updateAttributes({ name: v ?? "" }),
2806
+ className: "tt-api-param__name"
2807
+ }
2808
+ ),
2809
+ /* @__PURE__ */ jsx(
2810
+ InlineField,
2811
+ {
2812
+ field: "type",
2813
+ value: attrs.type,
2814
+ placeholder: "类型",
2815
+ ariaLabel: "类型",
2816
+ nullable: true,
2817
+ readOnly: !isEditable,
2818
+ onCommit: (v) => updateAttributes({ type: v }),
2819
+ className: "tt-badge tt-badge--type"
2820
+ }
2821
+ ),
2822
+ isEditable ? /* @__PURE__ */ jsx(
2823
+ "button",
2824
+ {
2825
+ type: "button",
2826
+ "data-field": "required",
2827
+ className: "tt-badge tt-badge--required",
2828
+ "aria-pressed": attrs.required,
2829
+ onMouseDown: (e) => e.preventDefault(),
2830
+ onClick: () => updateAttributes({ required: !attrs.required }),
2831
+ children: attrs.required ? "必选" : "可选"
2832
+ }
2833
+ ) : attrs.required ? /* @__PURE__ */ jsx("span", { className: "tt-badge tt-badge--required", "data-field": "required", children: "必选" }) : null,
2834
+ isEditable || attrs.default !== null ? /* @__PURE__ */ jsxs("span", { className: "tt-badge tt-badge--default", children: [
2835
+ /* @__PURE__ */ jsx("span", { className: "tt-badge__label", children: "默认值" }),
2836
+ /* @__PURE__ */ jsx(
2837
+ InlineField,
2838
+ {
2839
+ field: "default",
2840
+ value: attrs.default,
2841
+ placeholder: "无",
2842
+ ariaLabel: "默认值",
2843
+ nullable: true,
2844
+ readOnly: !isEditable,
2845
+ onCommit: (v) => updateAttributes({ default: v })
2846
+ }
2847
+ )
2848
+ ] }) : null,
2849
+ /* @__PURE__ */ jsx("span", { className: "tt-api-param__sep", "aria-hidden": "true", children: "|" }),
2850
+ /* @__PURE__ */ jsx(
2851
+ InlineField,
2852
+ {
2853
+ field: "summary",
2854
+ value: attrs.summary,
2855
+ placeholder: "一句话描述",
2856
+ ariaLabel: "短描述",
2857
+ nullable: true,
2858
+ readOnly: !isEditable,
2859
+ onCommit: (v) => updateAttributes({ summary: v }),
2860
+ onEnter: focusBody,
2861
+ className: "tt-api-param__summary"
2862
+ }
2863
+ )
2864
+ ] }),
2865
+ actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [
2866
+ !hasBody ? /* @__PURE__ */ jsx(
2867
+ "button",
2868
+ {
2869
+ type: "button",
2870
+ "data-action": "add-body",
2871
+ onMouseDown: (e) => e.preventDefault(),
2872
+ onClick: focusBody,
2873
+ children: "添加说明"
2874
+ }
2875
+ ) : null,
2876
+ /* @__PURE__ */ jsx(
2877
+ "button",
2878
+ {
2879
+ type: "button",
2880
+ "data-action": "add-child",
2881
+ onMouseDown: (e) => e.preventDefault(),
2882
+ onClick: addChild,
2883
+ children: "添加子参数"
2884
+ }
2885
+ ),
2886
+ /* @__PURE__ */ jsx(
2887
+ "button",
2888
+ {
2889
+ type: "button",
2890
+ "data-action": "remove",
2891
+ onMouseDown: (e) => e.preventDefault(),
2892
+ onClick: () => deleteNode2(),
2893
+ children: "删除"
2894
+ }
2895
+ )
2896
+ ] })
2897
+ }
2898
+ );
2899
+ }
2900
+ function apiParamSummaryText(attrs) {
2901
+ const head = [
2902
+ attrs.name,
2903
+ attrs.type,
2904
+ attrs.required ? "必选" : null,
2905
+ attrs.default != null ? `默认值 ${attrs.default}` : null
2906
+ ].filter((x) => Boolean(x)).join(" ");
2907
+ return attrs.summary ? `${head} | ${attrs.summary}`.trim() : head;
2908
+ }
2909
+ function childElements2(el) {
2910
+ const out = [];
2911
+ for (let i = 0; i < el.childNodes.length; i += 1) {
2912
+ const n = el.childNodes.item(i);
2913
+ if (n && n.nodeType === 1) out.push(n);
2914
+ }
2915
+ return out;
2916
+ }
2917
+ function splitParamBody(token, helpers) {
2918
+ const tokens = [...token.tokens ?? []];
2919
+ let summary = null;
2920
+ while (tokens.length && tokens[0].type === "space") tokens.shift();
2921
+ if (tokens[0]?.type === "paragraph") {
2922
+ const first = tokens.shift();
2923
+ summary = String(first.text ?? "").replace(/\s*\n\s*/g, " ").trim() || null;
2924
+ }
2925
+ const content = tokens.length ? helpers.parseChildren(tokens) : [];
2926
+ return { summary, content };
2927
+ }
2928
+ var ApiParamSchema = Node$1.create({
2929
+ name: "apiParam",
2930
+ group: "block",
2931
+ content: "block*",
2932
+ isolating: true,
2933
+ defining: true,
2934
+ // 解析规则要排在 details 的裸 <details> 规则前面
2935
+ priority: 110,
2936
+ addAttributes() {
2937
+ const text = (attr) => ({
2938
+ default: null,
2939
+ parseHTML: (el) => el.getAttribute(attr) || null,
2940
+ rendered: false
2941
+ });
2942
+ return {
2943
+ name: {
2944
+ default: "",
2945
+ parseHTML: (el) => el.getAttribute("data-name") ?? "",
2946
+ rendered: false
2947
+ },
2948
+ type: text("data-type"),
2949
+ required: {
2950
+ default: false,
2951
+ parseHTML: (el) => {
2952
+ const v = el.getAttribute("data-required");
2953
+ return v === "true" || v === "";
2954
+ },
2955
+ rendered: false
2956
+ },
2957
+ default: text("data-default"),
2958
+ summary: text("data-summary")
2959
+ };
2960
+ },
2961
+ parseHTML() {
2962
+ return [
2963
+ {
2964
+ tag: "details[data-param]",
2965
+ contentElement: (el) => {
2966
+ const wrapper = childElements2(el).find(
2967
+ (c) => c.hasAttribute("data-param-content")
2968
+ );
2969
+ return wrapper ? wrapper : el.ownerDocument.createElement("div");
2970
+ }
2971
+ }
2972
+ ];
2973
+ },
2974
+ renderHTML({ node, HTMLAttributes }) {
2975
+ const attrs = node.attrs;
2976
+ return [
2977
+ "details",
2978
+ mergeAttributes$1(HTMLAttributes, {
2979
+ "data-param": "",
2980
+ "data-name": attrs.name || null,
2981
+ "data-type": attrs.type || null,
2982
+ "data-required": attrs.required ? "true" : null,
2983
+ "data-default": attrs.default ?? null,
2984
+ "data-summary": attrs.summary || null
2985
+ }),
2986
+ ["summary", {}, apiParamSummaryText(attrs)],
2987
+ ["div", { "data-param-content": "" }, 0]
2988
+ ];
2989
+ },
2990
+ markdownTokenName: "apiParam",
2991
+ markdownTokenizer: createContainerTokenizer("apiParam", ["param"]),
2992
+ parseMarkdown: (token, helpers) => {
2993
+ const info = parseParamInfo(String(token.info ?? ""));
2994
+ const { summary, content } = splitParamBody(token, helpers);
2995
+ return helpers.createNode("apiParam", { ...info, summary }, content);
2996
+ },
2997
+ renderMarkdown: (node, h) => {
2998
+ const attrs = node.attrs ?? {};
2999
+ const info = formatParamInfo({
3000
+ name: attrs.name ?? "",
3001
+ type: attrs.type ?? null,
3002
+ required: Boolean(attrs.required),
3003
+ default: attrs.default ?? null
3004
+ });
3005
+ const body = h.renderChildren(node.content ?? [], "\n\n");
3006
+ const parts = [attrs.summary ?? "", body].filter(Boolean);
3007
+ return renderContainerMarkdown("param", info, parts.join("\n\n"));
3008
+ }
3009
+ });
3010
+
3011
+ // src/components/tiptap-node/collapsible-node/api-param-node-extension.ts
3012
+ var ApiParamExtension = ApiParamSchema.extend({
3013
+ addNodeView() {
3014
+ return ReactNodeViewRenderer(ApiParamNodeView);
3015
+ }
3016
+ });
1350
3017
  var useIsomorphicLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
1351
3018
  function useEventListener(eventName, handler, element, options) {
1352
3019
  const savedHandler = useRef(handler);
@@ -4199,6 +5866,484 @@ var ImageUploadNode2 = Node.create({
4199
5866
  };
4200
5867
  }
4201
5868
  });
5869
+ var codeHighlightPluginKey = new PluginKey("codeHighlight");
5870
+ var CODE_TOKEN_CLASS = "code-token";
5871
+ var SEP = "\0";
5872
+ function codeBlockSignature(node, theme) {
5873
+ return `${theme}${SEP}${node.attrs.language ?? ""}${SEP}${node.textContent}`;
5874
+ }
5875
+ function tokenStyle(token) {
5876
+ const parts = [];
5877
+ if (token.color) parts.push(`--code-token-color:${token.color}`);
5878
+ const fs = token.fontStyle ?? 0;
5879
+ if (fs & 1) parts.push("--code-token-font-style:italic");
5880
+ if (fs & 2) parts.push("--code-token-font-weight:bold");
5881
+ const decorations = [];
5882
+ if (fs & 4) decorations.push("underline");
5883
+ if (fs & 8) decorations.push("line-through");
5884
+ if (decorations.length) parts.push(`--code-token-text-decoration:${decorations.join(" ")}`);
5885
+ return parts.join(";");
5886
+ }
5887
+ function buildBlockDecorations(pos, node, sig, tokens) {
5888
+ const out = [Decoration.node(pos, pos + node.nodeSize, {}, { codeSig: sig })];
5889
+ const contentStart = pos + 1;
5890
+ const max = node.content.size;
5891
+ for (const token of tokens) {
5892
+ const from = Math.max(0, Math.min(token.start, max));
5893
+ const to = Math.max(from, Math.min(token.end, max));
5894
+ if (to <= from) continue;
5895
+ out.push(
5896
+ Decoration.inline(
5897
+ contentStart + from,
5898
+ contentStart + to,
5899
+ { class: CODE_TOKEN_CLASS, style: tokenStyle(token) },
5900
+ { codeSig: sig }
5901
+ )
5902
+ );
5903
+ }
5904
+ return out;
5905
+ }
5906
+ function reconcile(doc, decorations, theme, nodeName) {
5907
+ const needs = /* @__PURE__ */ new Map();
5908
+ let next = decorations;
5909
+ doc.descendants((node, pos) => {
5910
+ if (node.type.name !== nodeName) return !node.isTextblock;
5911
+ const sig = codeBlockSignature(node, theme);
5912
+ const inside = next.find(pos, pos + node.nodeSize);
5913
+ const marker = inside.find((d) => d.spec.codeSig !== void 0 && d.from === pos);
5914
+ if (marker?.spec.codeSig === sig) return false;
5915
+ if (inside.length) next = next.remove(inside);
5916
+ if (!needs.has(sig)) {
5917
+ needs.set(sig, { source: node.textContent, language: node.attrs.language ?? null, theme });
5918
+ }
5919
+ return false;
5920
+ });
5921
+ return { decorations: next, needs };
5922
+ }
5923
+ function applyResult(doc, decorations, theme, nodeName, sig, tokens) {
5924
+ const additions = [];
5925
+ doc.descendants((node, pos) => {
5926
+ if (node.type.name !== nodeName) return !node.isTextblock;
5927
+ if (codeBlockSignature(node, theme) !== sig) return false;
5928
+ const inside = decorations.find(pos, pos + node.nodeSize);
5929
+ if (inside.some((d) => d.spec.codeSig === sig && d.from === pos)) return false;
5930
+ additions.push(...buildBlockDecorations(pos, node, sig, tokens));
5931
+ return false;
5932
+ });
5933
+ return additions.length ? decorations.add(doc, additions) : decorations;
5934
+ }
5935
+ function defaultResolveTheme(options) {
5936
+ return () => {
5937
+ const root = typeof document === "undefined" ? null : document.documentElement;
5938
+ const isDark = Boolean(root?.classList.contains("dark"));
5939
+ return resolveShikiTheme(options.shikiTheme, isDark);
5940
+ };
5941
+ }
5942
+ function createCodeHighlightPlugin(options) {
5943
+ const { highlighter, resolveTheme, debounceMs, nodeName } = options;
5944
+ return new Plugin({
5945
+ key: codeHighlightPluginKey,
5946
+ state: {
5947
+ init(_config, state) {
5948
+ const theme = resolveTheme();
5949
+ return { theme, ...reconcile(state.doc, DecorationSet.empty, theme, nodeName) };
5950
+ },
5951
+ apply(tr, prev, _old, next) {
5952
+ const meta = tr.getMeta(codeHighlightPluginKey);
5953
+ if (meta?.type === "theme") {
5954
+ if (meta.theme === prev.theme) return prev;
5955
+ return {
5956
+ theme: meta.theme,
5957
+ ...reconcile(next.doc, DecorationSet.empty, meta.theme, nodeName)
5958
+ };
5959
+ }
5960
+ let decorations = tr.docChanged ? prev.decorations.map(tr.mapping, tr.doc) : prev.decorations;
5961
+ if (meta?.type === "result") {
5962
+ decorations = applyResult(
5963
+ next.doc,
5964
+ decorations,
5965
+ prev.theme,
5966
+ nodeName,
5967
+ meta.sig,
5968
+ meta.tokens
5969
+ );
5970
+ const needs = new Map(prev.needs);
5971
+ needs.delete(meta.sig);
5972
+ if (!tr.docChanged) return { theme: prev.theme, decorations, needs };
5973
+ }
5974
+ if (!tr.docChanged) return prev;
5975
+ return { theme: prev.theme, ...reconcile(next.doc, decorations, prev.theme, nodeName) };
5976
+ }
5977
+ },
5978
+ props: {
5979
+ decorations(state) {
5980
+ return codeHighlightPluginKey.getState(state)?.decorations ?? null;
5981
+ }
5982
+ },
5983
+ view(view) {
5984
+ let destroyed = false;
5985
+ let timer = null;
5986
+ const inflight = /* @__PURE__ */ new Set();
5987
+ const dispatchMeta = (meta) => {
5988
+ if (destroyed) return;
5989
+ view.dispatch(
5990
+ view.state.tr.setMeta(codeHighlightPluginKey, meta).setMeta("addToHistory", false)
5991
+ );
5992
+ };
5993
+ const flush = () => {
5994
+ timer = null;
5995
+ if (destroyed) return;
5996
+ const state = codeHighlightPluginKey.getState(view.state);
5997
+ if (!state) return;
5998
+ for (const [sig, request] of state.needs) {
5999
+ if (inflight.has(sig)) continue;
6000
+ inflight.add(sig);
6001
+ highlighter.highlight(request.source, request.language, request.theme).then((result) => {
6002
+ inflight.delete(sig);
6003
+ dispatchMeta({ type: "result", sig, tokens: result.tokens });
6004
+ }).catch(() => {
6005
+ inflight.delete(sig);
6006
+ });
6007
+ }
6008
+ };
6009
+ const schedule = () => {
6010
+ if (destroyed) return;
6011
+ const state = codeHighlightPluginKey.getState(view.state);
6012
+ if (!state || state.needs.size === 0) return;
6013
+ if (timer !== null) clearTimeout(timer);
6014
+ timer = setTimeout(flush, debounceMs);
6015
+ };
6016
+ let observer = null;
6017
+ const root = typeof document === "undefined" ? null : document.documentElement;
6018
+ if (root && typeof MutationObserver !== "undefined") {
6019
+ observer = new MutationObserver(() => {
6020
+ const theme = resolveTheme();
6021
+ if (theme !== codeHighlightPluginKey.getState(view.state)?.theme) {
6022
+ dispatchMeta({ type: "theme", theme });
6023
+ }
6024
+ });
6025
+ observer.observe(root, { attributes: true, attributeFilter: ["class"] });
6026
+ }
6027
+ schedule();
6028
+ return {
6029
+ update() {
6030
+ schedule();
6031
+ },
6032
+ destroy() {
6033
+ destroyed = true;
6034
+ if (timer !== null) clearTimeout(timer);
6035
+ observer?.disconnect();
6036
+ }
6037
+ };
6038
+ }
6039
+ });
6040
+ }
6041
+ var CodeHighlight = Extension.create({
6042
+ name: "codeHighlight",
6043
+ addOptions() {
6044
+ return {};
6045
+ },
6046
+ addStorage() {
6047
+ return { highlighter: null };
6048
+ },
6049
+ onBeforeCreate() {
6050
+ this.storage.highlighter = this.options.highlighter ?? createCodeHighlighter(this.options);
6051
+ },
6052
+ addProseMirrorPlugins() {
6053
+ return [
6054
+ createCodeHighlightPlugin({
6055
+ highlighter: this.storage.highlighter,
6056
+ resolveTheme: this.options.resolveTheme ?? defaultResolveTheme(this.options),
6057
+ debounceMs: this.options.debounceMs ?? 30,
6058
+ nodeName: this.options.nodeName ?? "codeBlock"
6059
+ })
6060
+ ];
6061
+ }
6062
+ });
6063
+ var codeGroupInputPluginKey = new PluginKey("codeGroupInput");
6064
+ var OPENER_RE3 = /^ {0,3}:::[ \t]*code-group[ \t]*$/;
6065
+ var CLOSER_RE2 = /^ {0,3}:::[ \t]*$/;
6066
+ var MAX_LOOKBACK = 2e3;
6067
+ function paragraphLines(node) {
6068
+ return node.textBetween(0, node.content.size, void 0, "\n").split("\n");
6069
+ }
6070
+ function findCodeGroupDraft(state) {
6071
+ const { $from, empty } = state.selection;
6072
+ if (!empty) return null;
6073
+ return findDraftAt($from);
6074
+ }
6075
+ function findDraftAt($from) {
6076
+ if ($from.depth === 0 || $from.parent.type.name !== "paragraph") return null;
6077
+ const parent = $from.node(-1);
6078
+ const cursorIndex = $from.index(-1);
6079
+ const openers = [];
6080
+ const linesByIndex = /* @__PURE__ */ new Map();
6081
+ for (let i = cursorIndex; i >= 0 && cursorIndex - i < MAX_LOOKBACK; i -= 1) {
6082
+ const child = parent.child(i);
6083
+ if (child.type.name !== "paragraph") break;
6084
+ const lines = paragraphLines(child);
6085
+ linesByIndex.set(i, lines);
6086
+ if (lines.length === 1 && OPENER_RE3.test(lines[0])) openers.push(i);
6087
+ }
6088
+ if (openers.length === 0) return null;
6089
+ for (let k = openers.length - 1; k >= 0; k -= 1) {
6090
+ const startIndex = openers[k];
6091
+ const lines = [];
6092
+ for (let i = startIndex; i <= cursorIndex; i += 1) lines.push(...linesByIndex.get(i) ?? []);
6093
+ const result = scanCodeGroupLines(lines);
6094
+ if (result.status === "invalid") continue;
6095
+ if (result.status === "partial")
6096
+ return { startIndex, cursorIndex, status: "partial", blocks: result.blocks };
6097
+ const cursorLines = linesByIndex.get(cursorIndex) ?? [];
6098
+ if (result.lineCount === lines.length && cursorLines.length === 1 && CLOSER_RE2.test(cursorLines[0])) {
6099
+ return { startIndex, cursorIndex, status: "complete", blocks: result.blocks };
6100
+ }
6101
+ return null;
6102
+ }
6103
+ return null;
6104
+ }
6105
+ function stopYjsCapturing(state) {
6106
+ for (const plugin of state.plugins) {
6107
+ const pluginState = plugin.getState(state);
6108
+ pluginState?.undoManager?.stopCapturing?.();
6109
+ }
6110
+ }
6111
+ function dispatchAsOwnUndoStep(view, tr) {
6112
+ stopYjsCapturing(view.state);
6113
+ view.dispatch(closeHistory(tr));
6114
+ stopYjsCapturing(view.state);
6115
+ view.dispatch(closeHistory(view.state.tr));
6116
+ }
6117
+ function convertCodeGroupDraft(view, draft) {
6118
+ const { state } = view;
6119
+ const { schema } = state;
6120
+ const groupType = schema.nodes[CODE_GROUP_NAME];
6121
+ const codeBlockType = schema.nodes.codeBlock;
6122
+ const paragraphType = schema.nodes.paragraph;
6123
+ if (!groupType || !codeBlockType || !paragraphType) return false;
6124
+ const $from = state.selection.$from;
6125
+ const parentDepth = $from.depth - 1;
6126
+ const parent = $from.node(parentDepth);
6127
+ if (!parent.canReplaceWith(draft.startIndex, draft.cursorIndex + 1, groupType)) return false;
6128
+ let from = $from.start(parentDepth);
6129
+ for (let i = 0; i < draft.startIndex; i += 1) from += parent.child(i).nodeSize;
6130
+ let to = from;
6131
+ for (let i = draft.startIndex; i <= draft.cursorIndex; i += 1) to += parent.child(i).nodeSize;
6132
+ const children = draft.blocks.map(
6133
+ (b) => codeBlockType.create(
6134
+ { language: b.language, label: b.label },
6135
+ b.source ? schema.text(b.source) : null
6136
+ )
6137
+ );
6138
+ let group;
6139
+ try {
6140
+ group = groupType.createChecked(null, children);
6141
+ } catch {
6142
+ return false;
6143
+ }
6144
+ const trailing = paragraphType.create();
6145
+ const tr = state.tr.replaceWith(from, to, [group, trailing]);
6146
+ tr.setSelection(TextSelection.create(tr.doc, from + group.nodeSize + 1));
6147
+ dispatchAsOwnUndoStep(view, tr.scrollIntoView());
6148
+ return true;
6149
+ }
6150
+ function isComposing(view, event) {
6151
+ if (event?.isComposing) return true;
6152
+ return Boolean(view.composing);
6153
+ }
6154
+ function createCodeGroupInputPlugin(editor) {
6155
+ return new Plugin({
6156
+ key: codeGroupInputPluginKey,
6157
+ props: {
6158
+ // 草稿内原文插入:绕过所有输入规则
6159
+ handleTextInput(view, from, to, text) {
6160
+ if (!findCodeGroupDraft(view.state)) return false;
6161
+ view.dispatch(view.state.tr.insertText(text, from, to));
6162
+ return true;
6163
+ },
6164
+ handleKeyDown(view, event) {
6165
+ if (event.key !== "Enter" || event.shiftKey || event.metaKey || event.ctrlKey || event.altKey)
6166
+ return false;
6167
+ if (isComposing(view, event)) return false;
6168
+ const draft = findCodeGroupDraft(view.state);
6169
+ if (!draft) return false;
6170
+ const { $from } = view.state.selection;
6171
+ if (draft.status === "complete" && $from.parentOffset === $from.parent.content.size) {
6172
+ if (convertCodeGroupDraft(view, draft)) return true;
6173
+ }
6174
+ return editor.commands.splitBlock();
6175
+ },
6176
+ handlePaste(view, event) {
6177
+ const { state } = view;
6178
+ if (state.selection.$from.parent.type.name === "codeBlock") return false;
6179
+ const data = event.clipboardData;
6180
+ if (!data) return false;
6181
+ if (view.input?.shiftKey) return false;
6182
+ if (data.getData("text/html")) return false;
6183
+ const text = data.getData("text/plain");
6184
+ if (!text || !containsCompleteContainer(text)) return false;
6185
+ const manager = editor.markdown;
6186
+ if (!manager) return false;
6187
+ let json;
6188
+ try {
6189
+ json = manager.parse(text.replace(/\r\n?/g, "\n"));
6190
+ } catch {
6191
+ return false;
6192
+ }
6193
+ if (!json.content?.length) return false;
6194
+ stopYjsCapturing(view.state);
6195
+ view.dispatch(closeHistory(view.state.tr));
6196
+ const inserted = editor.commands.insertContentAt(
6197
+ { from: state.selection.from, to: state.selection.to },
6198
+ json.content,
6199
+ { updateSelection: true }
6200
+ );
6201
+ stopYjsCapturing(view.state);
6202
+ view.dispatch(closeHistory(view.state.tr));
6203
+ return inserted;
6204
+ }
6205
+ }
6206
+ });
6207
+ }
6208
+ var CodeGroupInput = Extension.create({
6209
+ name: "codeGroupInput",
6210
+ priority: 2e3,
6211
+ addProseMirrorPlugins() {
6212
+ return [createCodeGroupInputPlugin(this.editor)];
6213
+ }
6214
+ });
6215
+ var containerInputPluginKey = new PluginKey("containerInput");
6216
+ var isContainer = (node) => CONTAINER_NODE_NAMES.includes(node.type.name);
6217
+ function buildContainerNode(schema, opener) {
6218
+ const nodeName = CONTAINER_MARKDOWN_NAMES[opener.name];
6219
+ if (!nodeName || !schema.nodes[nodeName]) return null;
6220
+ const paragraph = schema.nodes.paragraph.create();
6221
+ if (nodeName === "callout") {
6222
+ return schema.nodes.callout.create(
6223
+ { type: isCalloutType(opener.name) ? opener.name : "info", title: opener.info || null },
6224
+ paragraph
6225
+ );
6226
+ }
6227
+ if (nodeName === "details")
6228
+ return schema.nodes.details.create({ title: opener.info || null }, paragraph);
6229
+ const info = parseParamInfo(opener.info);
6230
+ return schema.nodes.apiParam.create({ ...info, summary: null });
6231
+ }
6232
+ function paragraphSource(paragraph) {
6233
+ let out = "";
6234
+ paragraph.forEach((child) => {
6235
+ if (!child.isText) return;
6236
+ const text = child.text ?? "";
6237
+ out += child.marks.some((m) => m.type.name === "code") ? `\`${text}\`` : text;
6238
+ });
6239
+ return out;
6240
+ }
6241
+ function convertOpenerParagraph(view) {
6242
+ const { state } = view;
6243
+ const { $from, empty } = state.selection;
6244
+ if (!empty || $from.parent.type.name !== "paragraph") return false;
6245
+ if ($from.parentOffset !== $from.parent.content.size) return false;
6246
+ if (findCodeGroupDraft(state)) return false;
6247
+ const opener = parseContainerOpener(paragraphSource($from.parent));
6248
+ if (!opener) return false;
6249
+ const node = buildContainerNode(state.schema, opener);
6250
+ if (!node) return false;
6251
+ const parent = $from.node(-1);
6252
+ const index = $from.index(-1);
6253
+ if (!parent.canReplaceWith(index, index + 1, node.type)) return false;
6254
+ const from = $from.before();
6255
+ const to = $from.after();
6256
+ const trailing = state.schema.nodes.paragraph.create();
6257
+ const tr = state.tr.replaceWith(
6258
+ from,
6259
+ to,
6260
+ node.type.name === "apiParam" ? [node, trailing] : [node]
6261
+ );
6262
+ const cursor = node.type.name === "apiParam" ? from + node.nodeSize + 1 : from + 2;
6263
+ tr.setSelection(TextSelection.create(tr.doc, cursor));
6264
+ view.dispatch(closeHistory(tr).scrollIntoView());
6265
+ if (node.type.name === "apiParam") {
6266
+ queueMicrotask(() => {
6267
+ if (view.isDestroyed) return;
6268
+ const dom = view.nodeDOM(from);
6269
+ dom?.querySelector('input[data-field="summary"]')?.focus();
6270
+ });
6271
+ }
6272
+ return true;
6273
+ }
6274
+ function exitContainerOnEnter(view) {
6275
+ const { state } = view;
6276
+ const { $from, empty } = state.selection;
6277
+ if (!empty || $from.depth < 2) return false;
6278
+ const paragraph = $from.parent;
6279
+ const container = $from.node(-1);
6280
+ if (paragraph.type.name !== "paragraph" || !isContainer(container)) return false;
6281
+ if (paragraph.content.size !== 0) return false;
6282
+ if ($from.index(-1) !== container.childCount - 1) return false;
6283
+ const containerPos = $from.before(-1);
6284
+ const after = containerPos + container.nodeSize;
6285
+ let tr = state.tr;
6286
+ if (container.childCount > 1) tr = tr.delete($from.before(), $from.after());
6287
+ const insertAt = tr.mapping.map(after);
6288
+ tr = tr.insert(insertAt, state.schema.nodes.paragraph.create());
6289
+ tr = tr.setSelection(TextSelection.create(tr.doc, insertAt + 1));
6290
+ view.dispatch(tr.scrollIntoView());
6291
+ return true;
6292
+ }
6293
+ function unwrapContainerOnBackspace(view) {
6294
+ const { state } = view;
6295
+ const { $from, empty } = state.selection;
6296
+ if (!empty || $from.depth < 2 || $from.parentOffset !== 0) return false;
6297
+ const paragraph = $from.parent;
6298
+ const container = $from.node(-1);
6299
+ if (paragraph.type.name !== "paragraph" || !isContainer(container)) return false;
6300
+ if (container.childCount !== 1 || paragraph.content.size !== 0) return false;
6301
+ const pos = $from.before(-1);
6302
+ const tr = state.tr.replaceWith(pos, pos + container.nodeSize, container.content);
6303
+ tr.setSelection(TextSelection.create(tr.doc, pos + 1));
6304
+ view.dispatch(tr);
6305
+ return true;
6306
+ }
6307
+ function isComposing2(view, event) {
6308
+ return Boolean(event.isComposing) || Boolean(view.composing);
6309
+ }
6310
+ var ContainerInput = Extension.create({
6311
+ name: "containerInput",
6312
+ priority: 1500,
6313
+ addProseMirrorPlugins() {
6314
+ return [
6315
+ new Plugin({
6316
+ key: containerInputPluginKey,
6317
+ props: {
6318
+ handleKeyDown(view, event) {
6319
+ if (event.metaKey || event.ctrlKey || event.altKey || event.shiftKey) return false;
6320
+ if (isComposing2(view, event)) return false;
6321
+ if (event.key === "Enter")
6322
+ return convertOpenerParagraph(view) || exitContainerOnEnter(view);
6323
+ if (event.key === "Backspace") return unwrapContainerOnBackspace(view);
6324
+ return false;
6325
+ }
6326
+ }
6327
+ })
6328
+ ];
6329
+ }
6330
+ });
6331
+
6332
+ // src/components/tiptap-templates/notion-like/editor-content-config.ts
6333
+ function normalizeEditorContent(content, contentFormat, extensions, options = {}) {
6334
+ const result = parseEditorContent(content, contentFormat, extensions);
6335
+ if (!result.ok) {
6336
+ (options.onError ?? defaultOnError)(result.error);
6337
+ return null;
6338
+ }
6339
+ return result.json;
6340
+ }
6341
+ function defaultOnError(message) {
6342
+ console.error(`[editor] 内容未载入:${message}`);
6343
+ }
6344
+ function isSameDocument(current, next) {
6345
+ return JSON.stringify(current) === JSON.stringify(next);
6346
+ }
4202
6347
  var RESIZE_MIN_WIDTH = 35;
4203
6348
  var EMPTY_CELL_WIDTH = 120;
4204
6349
  var EMPTY_CELL_HEIGHT = 40;
@@ -12480,6 +14625,30 @@ var CodeBlockIcon = memo(({ className, ...props }) => {
12480
14625
  );
12481
14626
  });
12482
14627
  CodeBlockIcon.displayName = "CodeBlockIcon";
14628
+ var MessageSquareIcon = memo(({ className, ...props }) => {
14629
+ return /* @__PURE__ */ jsx(
14630
+ "svg",
14631
+ {
14632
+ width: "24",
14633
+ height: "24",
14634
+ className,
14635
+ viewBox: "0 0 24 24",
14636
+ fill: "currentColor",
14637
+ xmlns: "http://www.w3.org/2000/svg",
14638
+ ...props,
14639
+ children: /* @__PURE__ */ jsx(
14640
+ "path",
14641
+ {
14642
+ fillRule: "evenodd",
14643
+ clipRule: "evenodd",
14644
+ d: "M5 5C4.73478 5 4.48043 5.10536 4.29289 5.29289C4.10536 5.48043 4 5.73478 4 6V19.5858L6.29289 17.2929C6.48043 17.1054 6.73478 17 7 17H19C19.2652 17 19.5196 16.8946 19.7071 16.7071C19.8946 16.5196 20 16.2652 20 16V6C20 5.73478 19.8946 5.48043 19.7071 5.29289C19.5196 5.10536 19.2652 5 19 5H5ZM2.87868 3.87868C3.44129 3.31607 4.20435 3 5 3H19C19.7957 3 20.5587 3.31607 21.1213 3.87868C21.6839 4.44129 22 5.20435 22 6V16C22 16.7957 21.6839 17.5587 21.1213 18.1213C20.5587 18.6839 19.7957 19 19 19H7.41421L3.70711 22.7071C3.42111 22.9931 2.99099 23.0787 2.61732 22.9239C2.24364 22.7691 2 22.4045 2 22V6C2 5.20435 2.31607 4.44129 2.87868 3.87868Z",
14645
+ fill: "currentColor"
14646
+ }
14647
+ )
14648
+ }
14649
+ );
14650
+ });
14651
+ MessageSquareIcon.displayName = "MessageSquareIcon";
12483
14652
  var HeadingOneIcon = memo(({ className, ...props }) => {
12484
14653
  return /* @__PURE__ */ jsxs(
12485
14654
  "svg",
@@ -13272,6 +15441,27 @@ var texts = {
13272
15441
  badge: CodeBlockIcon,
13273
15442
  group: "样式"
13274
15443
  },
15444
+ callout: {
15445
+ title: "提示框",
15446
+ subtext: "信息 / 提示 / 注意 / 危险",
15447
+ keywords: ["callout", "note", "tip", "warning", "提示", "注意", "警告"],
15448
+ badge: MessageSquareIcon,
15449
+ group: "样式"
15450
+ },
15451
+ details: {
15452
+ title: "折叠块",
15453
+ subtext: "带标题的可折叠内容",
15454
+ keywords: ["details", "toggle", "collapse", "折叠"],
15455
+ badge: ChevronRightIcon,
15456
+ group: "样式"
15457
+ },
15458
+ api_param: {
15459
+ title: "参数",
15460
+ subtext: "API 参数行(名字 / 类型 / 必选 / 默认值)",
15461
+ keywords: ["param", "parameter", "api", "参数"],
15462
+ badge: ListIndentedIcon,
15463
+ group: "样式"
15464
+ },
13275
15465
  // Insert
13276
15466
  mention: {
13277
15467
  title: "提及",
@@ -13433,6 +15623,27 @@ var getItemImplementations = () => {
13433
15623
  editor.chain().focus().toggleNode("codeBlock", "paragraph").run();
13434
15624
  }
13435
15625
  },
15626
+ callout: {
15627
+ check: (editor) => isNodeInSchema("callout", editor),
15628
+ action: ({ editor }) => {
15629
+ editor.chain().focus().insertContent({ type: "callout", attrs: { type: "info", title: null }, content: [{ type: "paragraph" }] }).run();
15630
+ }
15631
+ },
15632
+ details: {
15633
+ check: (editor) => isNodeInSchema("details", editor),
15634
+ action: ({ editor }) => {
15635
+ editor.chain().focus().insertContent({ type: "details", attrs: { title: null }, content: [{ type: "paragraph" }] }).run();
15636
+ }
15637
+ },
15638
+ api_param: {
15639
+ check: (editor) => isNodeInSchema("apiParam", editor),
15640
+ action: ({ editor }) => {
15641
+ editor.chain().focus().insertContent([
15642
+ { type: "apiParam", attrs: { name: "", type: null, required: false, default: null, summary: null } },
15643
+ { type: "paragraph" }
15644
+ ]).run();
15645
+ }
15646
+ },
13436
15647
  // Insert
13437
15648
  mention: {
13438
15649
  check: (editor) => isExtensionAvailable(editor, ["mention", "mentionAdvanced"]),
@@ -20892,7 +23103,6 @@ var Indent = Extension.create({
20892
23103
  };
20893
23104
  }
20894
23105
  });
20895
- var lowlight = createLowlight(common);
20896
23106
  function LoadingSpinner({ text = "Connecting..." }) {
20897
23107
  return /* @__PURE__ */ jsx("div", { className: "spinner-container", children: /* @__PURE__ */ jsxs("div", { className: "spinner-content", children: [
20898
23108
  /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", children: [
@@ -20936,6 +23146,8 @@ function EditorProvider(props) {
20936
23146
  ydoc,
20937
23147
  placeholder = "开始书写...",
20938
23148
  content,
23149
+ contentFormat,
23150
+ codeBlock: codeBlockOptions,
20939
23151
  readOnly = false,
20940
23152
  hideHeader = false,
20941
23153
  embedded = false,
@@ -20962,178 +23174,196 @@ function EditorProvider(props) {
20962
23174
  const videoMaxSize = resolveMaxSize("video", editorConfig?.upload);
20963
23175
  const audioMaxSize = resolveMaxSize("audio", editorConfig?.upload);
20964
23176
  const fileMaxSize = resolveMaxSize("file", editorConfig?.upload);
23177
+ const extensions = [
23178
+ StarterKit.configure({
23179
+ undoRedo: false,
23180
+ horizontalRule: false,
23181
+ // 关掉 StarterKit 自带的纯 codeBlock,换成 Shiki 高亮 + 分组的版本
23182
+ codeBlock: false,
23183
+ dropcursor: {
23184
+ width: 2
23185
+ },
23186
+ link: { openOnClick: false }
23187
+ }),
23188
+ // 代码块 / 分组 / 高亮 / Markdown / 直接输入:顺序固定,且在协同与 UniqueID 之前
23189
+ CodeBlockExtension,
23190
+ CodeGroupExtension,
23191
+ CodeHighlight.configure(codeBlockOptions ?? {}),
23192
+ createMarkdownExtension(),
23193
+ CodeGroupInput,
23194
+ // 提示框 / 折叠块 / API 参数:与代码分组同一套容器语法与折叠机制
23195
+ CalloutExtension,
23196
+ DetailsExtension,
23197
+ ApiParamExtension,
23198
+ ContainerInput,
23199
+ HorizontalRule,
23200
+ TextAlign.configure({ types: ["heading", "paragraph"] }),
23201
+ // Collaboration 可以无 provider 运行(仅操作本地 yjs doc),CollaborationCaret
23202
+ // 强依赖 provider.awareness,没 provider 时不挂以避免 runtime 崩。
23203
+ Collaboration.configure({ document: ydoc }),
23204
+ ...provider ? [
23205
+ CollaborationCaret.configure({
23206
+ provider,
23207
+ user: { id: user.id, name: user.name, color: user.color }
23208
+ })
23209
+ ] : [],
23210
+ Placeholder.configure({
23211
+ placeholder,
23212
+ emptyNodeClass: "is-empty with-slash"
23213
+ }),
23214
+ Mention,
23215
+ Emoji.configure({
23216
+ emojis: gitHubEmojis.filter(
23217
+ (emoji) => !emoji.name.includes("regional")
23218
+ ),
23219
+ forceFallbackImages: true
23220
+ }),
23221
+ TableKit.configure({
23222
+ table: {
23223
+ resizable: true,
23224
+ cellMinWidth: 120
23225
+ }
23226
+ }),
23227
+ NodeBackground.configure({
23228
+ types: [
23229
+ "paragraph",
23230
+ "heading",
23231
+ "blockquote",
23232
+ "taskList",
23233
+ "bulletList",
23234
+ "orderedList",
23235
+ "tableCell",
23236
+ "tableHeader",
23237
+ "tocNode"
23238
+ ]
23239
+ }),
23240
+ NodeAlignment,
23241
+ // 行距 / 段前段后距:Word 把它们挂在每个段落上,导入时没有对应属性就会
23242
+ // 整篇塌回编辑器的统一边距,原文的疏密节奏全丢。
23243
+ BlockSpacing.configure({
23244
+ types: ["paragraph", "heading", "blockquote"]
23245
+ }),
23246
+ TextStyle,
23247
+ // 字号与字体是 textStyle 上的属性。不注册这两个扩展,schema 里就没有
23248
+ // fontSize / fontFamily,ProseMirror 会**静默丢弃**这两个属性 ——
23249
+ // 表现是 docx 导入后全文一个字号(见 docx/parse.ts 的 fmtToMarks)。
23250
+ FontSize,
23251
+ FontFamily,
23252
+ Mathematics,
23253
+ Superscript,
23254
+ Subscript,
23255
+ Indent,
23256
+ Color,
23257
+ TaskList,
23258
+ TaskItem.configure({ nested: true }),
23259
+ Highlight.configure({ multicolor: true }),
23260
+ Selection$1,
23261
+ Image,
23262
+ TableOfContents.configure({
23263
+ getIndex: getHierarchicalIndexes,
23264
+ // 见 resolveScrollParent 的说明:默认的 window 在「内层 div 滚动」的宿主里收不到事件
23265
+ scrollParent: resolveScrollParent,
23266
+ onUpdate(content2) {
23267
+ setTocContent(content2);
23268
+ }
23269
+ }),
23270
+ TableHandleExtension,
23271
+ ListNormalizationExtension,
23272
+ ImageUploadNode2.configure({
23273
+ accept: "image/*",
23274
+ maxSize: imageMaxSize,
23275
+ limit: 3,
23276
+ upload: onUpload,
23277
+ onError: onUploadError
23278
+ }),
23279
+ Video,
23280
+ Audio,
23281
+ FileAttachment,
23282
+ MediaUploadPlaceholder,
23283
+ Columns,
23284
+ Column,
23285
+ // 编辑器整面:粘贴/拖拽 文件 → 自动按 mime 分发到 image/video/audio/fileAttachment 节点;
23286
+ // 拖拽外链图片 → XHR rehost 到自家 CDN
23287
+ PasteDropMedia.configure({
23288
+ upload: onUpload,
23289
+ maxFileSizes: {
23290
+ image: imageMaxSize,
23291
+ video: videoMaxSize,
23292
+ audio: audioMaxSize,
23293
+ file: fileMaxSize
23294
+ },
23295
+ rehostExternalUrls: true,
23296
+ onError: onUploadError,
23297
+ onRehostProgress
23298
+ }),
23299
+ UniqueID.configure({
23300
+ /**
23301
+ * 带稳定 id 的节点类型 —— 也就是 AI 能直接寻址的那些(见 doc-runtime)。
23302
+ *
23303
+ * 原本只配了上面 9 种块级类型,于是 listItem / taskItem / tableCell /
23304
+ * columns / column / image 这些**结构**节点全都没 id:AI 能读到它们,
23305
+ * 却没法指着其中一个说「改这个」。doc_modify_nodes 的 modify 要求
23306
+ * litexml 带 id,所以「把第 3 个列表项加粗」「把这张图换掉」
23307
+ * 「改这个单元格的底色」在旧配置下根本表达不出来。
23308
+ *
23309
+ * 代价是每个此类节点在文档里多一个 id 属性(大表格尤其明显)。
23310
+ * 拿可寻址性换体积,这笔是划算的 —— 寻址不了的能力等于没有。
23311
+ */
23312
+ types: [
23313
+ // 块级容器
23314
+ "paragraph",
23315
+ "heading",
23316
+ "blockquote",
23317
+ "codeBlock",
23318
+ "codeGroup",
23319
+ "callout",
23320
+ "details",
23321
+ "apiParam",
23322
+ "tocNode",
23323
+ // 列表:容器和条目都要,否则改不了单条
23324
+ "bulletList",
23325
+ "orderedList",
23326
+ "listItem",
23327
+ "taskList",
23328
+ "taskItem",
23329
+ // 表格:结构性改动走 (tableId,row,col),但改单元格属性只能靠 id
23330
+ "table",
23331
+ "tableCell",
23332
+ "tableHeader",
23333
+ // 分栏
23334
+ "columns",
23335
+ "column",
23336
+ // 媒体
23337
+ "image",
23338
+ "video",
23339
+ "audio",
23340
+ "fileAttachment"
23341
+ ],
23342
+ filterTransaction: (transaction) => !isChangeOrigin(transaction)
23343
+ }),
23344
+ Typography,
23345
+ UiState,
23346
+ TocNode.configure({
23347
+ topOffset: 48
23348
+ }),
23349
+ // 业务方注入的扩展放在最后,可覆盖同名配置
23350
+ ...extraExtensions ?? []
23351
+ ];
23352
+ const [initialContent] = useState(
23353
+ () => provider ? void 0 : normalizeEditorContent(content, contentFormat, extensions) ?? void 0
23354
+ );
20965
23355
  const editor = useEditor({
20966
23356
  immediatelyRender: false,
20967
23357
  // 只读模式:禁用编辑。扩展集保持不变,自定义节点仍走各自 NodeView 渲染
20968
23358
  editable: !readOnly,
20969
23359
  // 仅本地编辑模式(无 provider)下用 content 作初始值;协同模式让 yjs sync 接管
20970
- content: provider ? void 0 : content,
23360
+ content: initialContent,
20971
23361
  editorProps: {
20972
23362
  attributes: {
20973
23363
  class: "notion-like-editor"
20974
23364
  }
20975
23365
  },
20976
- extensions: [
20977
- StarterKit.configure({
20978
- undoRedo: false,
20979
- horizontalRule: false,
20980
- // 关掉 StarterKit 自带的纯 codeBlock,换成带 lowlight 语法高亮的版本
20981
- codeBlock: false,
20982
- dropcursor: {
20983
- width: 2
20984
- },
20985
- link: { openOnClick: false }
20986
- }),
20987
- CodeBlockLowlight.configure({ lowlight }),
20988
- HorizontalRule,
20989
- TextAlign.configure({ types: ["heading", "paragraph"] }),
20990
- // Collaboration 可以无 provider 运行(仅操作本地 yjs doc),CollaborationCaret
20991
- // 强依赖 provider.awareness,没 provider 时不挂以避免 runtime 崩。
20992
- Collaboration.configure({ document: ydoc }),
20993
- ...provider ? [
20994
- CollaborationCaret.configure({
20995
- provider,
20996
- user: { id: user.id, name: user.name, color: user.color }
20997
- })
20998
- ] : [],
20999
- Placeholder.configure({
21000
- placeholder,
21001
- emptyNodeClass: "is-empty with-slash"
21002
- }),
21003
- Mention,
21004
- Emoji.configure({
21005
- emojis: gitHubEmojis.filter(
21006
- (emoji) => !emoji.name.includes("regional")
21007
- ),
21008
- forceFallbackImages: true
21009
- }),
21010
- TableKit.configure({
21011
- table: {
21012
- resizable: true,
21013
- cellMinWidth: 120
21014
- }
21015
- }),
21016
- NodeBackground.configure({
21017
- types: [
21018
- "paragraph",
21019
- "heading",
21020
- "blockquote",
21021
- "taskList",
21022
- "bulletList",
21023
- "orderedList",
21024
- "tableCell",
21025
- "tableHeader",
21026
- "tocNode"
21027
- ]
21028
- }),
21029
- NodeAlignment,
21030
- // 行距 / 段前段后距:Word 把它们挂在每个段落上,导入时没有对应属性就会
21031
- // 整篇塌回编辑器的统一边距,原文的疏密节奏全丢。
21032
- BlockSpacing.configure({
21033
- types: ["paragraph", "heading", "blockquote"]
21034
- }),
21035
- TextStyle,
21036
- // 字号与字体是 textStyle 上的属性。不注册这两个扩展,schema 里就没有
21037
- // fontSize / fontFamily,ProseMirror 会**静默丢弃**这两个属性 ——
21038
- // 表现是 docx 导入后全文一个字号(见 docx/parse.ts 的 fmtToMarks)。
21039
- FontSize,
21040
- FontFamily,
21041
- Mathematics,
21042
- Superscript,
21043
- Subscript,
21044
- Indent,
21045
- Color,
21046
- TaskList,
21047
- TaskItem.configure({ nested: true }),
21048
- Highlight.configure({ multicolor: true }),
21049
- Selection$1,
21050
- Image,
21051
- TableOfContents.configure({
21052
- getIndex: getHierarchicalIndexes,
21053
- // 见 resolveScrollParent 的说明:默认的 window 在「内层 div 滚动」的宿主里收不到事件
21054
- scrollParent: resolveScrollParent,
21055
- onUpdate(content2) {
21056
- setTocContent(content2);
21057
- }
21058
- }),
21059
- TableHandleExtension,
21060
- ListNormalizationExtension,
21061
- ImageUploadNode2.configure({
21062
- accept: "image/*",
21063
- maxSize: imageMaxSize,
21064
- limit: 3,
21065
- upload: onUpload,
21066
- onError: onUploadError
21067
- }),
21068
- Video,
21069
- Audio,
21070
- FileAttachment,
21071
- MediaUploadPlaceholder,
21072
- Columns,
21073
- Column,
21074
- // 编辑器整面:粘贴/拖拽 文件 → 自动按 mime 分发到 image/video/audio/fileAttachment 节点;
21075
- // 拖拽外链图片 → XHR rehost 到自家 CDN
21076
- PasteDropMedia.configure({
21077
- upload: onUpload,
21078
- maxFileSizes: {
21079
- image: imageMaxSize,
21080
- video: videoMaxSize,
21081
- audio: audioMaxSize,
21082
- file: fileMaxSize
21083
- },
21084
- rehostExternalUrls: true,
21085
- onError: onUploadError,
21086
- onRehostProgress
21087
- }),
21088
- UniqueID.configure({
21089
- /**
21090
- * 带稳定 id 的节点类型 —— 也就是 AI 能直接寻址的那些(见 doc-runtime)。
21091
- *
21092
- * 原本只配了上面 9 种块级类型,于是 listItem / taskItem / tableCell /
21093
- * columns / column / image 这些**结构**节点全都没 id:AI 能读到它们,
21094
- * 却没法指着其中一个说「改这个」。doc_modify_nodes 的 modify 要求
21095
- * litexml 带 id,所以「把第 3 个列表项加粗」「把这张图换掉」
21096
- * 「改这个单元格的底色」在旧配置下根本表达不出来。
21097
- *
21098
- * 代价是每个此类节点在文档里多一个 id 属性(大表格尤其明显)。
21099
- * 拿可寻址性换体积,这笔是划算的 —— 寻址不了的能力等于没有。
21100
- */
21101
- types: [
21102
- // 块级容器
21103
- "paragraph",
21104
- "heading",
21105
- "blockquote",
21106
- "codeBlock",
21107
- "tocNode",
21108
- // 列表:容器和条目都要,否则改不了单条
21109
- "bulletList",
21110
- "orderedList",
21111
- "listItem",
21112
- "taskList",
21113
- "taskItem",
21114
- // 表格:结构性改动走 (tableId,row,col),但改单元格属性只能靠 id
21115
- "table",
21116
- "tableCell",
21117
- "tableHeader",
21118
- // 分栏
21119
- "columns",
21120
- "column",
21121
- // 媒体
21122
- "image",
21123
- "video",
21124
- "audio",
21125
- "fileAttachment"
21126
- ],
21127
- filterTransaction: (transaction) => !isChangeOrigin(transaction)
21128
- }),
21129
- Typography,
21130
- UiState,
21131
- TocNode.configure({
21132
- topOffset: 48
21133
- }),
21134
- // 业务方注入的扩展放在最后,可覆盖同名配置
21135
- ...extraExtensions ?? []
21136
- ]
23366
+ extensions
21137
23367
  });
21138
23368
  useEffect(() => {
21139
23369
  if (!editor) return;
@@ -21157,18 +23387,18 @@ function EditorProvider(props) {
21157
23387
  if (provider) return;
21158
23388
  if (!editor) return;
21159
23389
  if (content === void 0 || content === null || content === "") return;
21160
- const currentJson = JSON.stringify(editor.getJSON());
21161
- const nextJson = typeof content === "string" ? null : JSON.stringify(content);
21162
- if (nextJson && nextJson === currentJson) return;
23390
+ const next = normalizeEditorContent(content, contentFormat, editor.extensionManager.extensions);
23391
+ if (!next) return;
23392
+ if (isSameDocument(editor.getJSON(), next)) return;
21163
23393
  let cancelled = false;
21164
23394
  queueMicrotask(() => {
21165
23395
  if (cancelled || editor.isDestroyed) return;
21166
- editor.commands.setContent(content);
23396
+ editor.commands.setContent(next, { emitUpdate: false });
21167
23397
  });
21168
23398
  return () => {
21169
23399
  cancelled = true;
21170
23400
  };
21171
- }, [content, editor, provider]);
23401
+ }, [content, contentFormat, editor, provider]);
21172
23402
  useEffect(() => {
21173
23403
  if (!editor) return;
21174
23404
  onEditorReady?.(editor);
@@ -21211,6 +23441,8 @@ function NotionEditor({
21211
23441
  room,
21212
23442
  placeholder = "开始书写...",
21213
23443
  content,
23444
+ contentFormat,
23445
+ codeBlock,
21214
23446
  readOnly = false,
21215
23447
  hideHeader = false,
21216
23448
  onEditorReady,
@@ -21221,6 +23453,8 @@ function NotionEditor({
21221
23453
  {
21222
23454
  placeholder,
21223
23455
  content,
23456
+ contentFormat,
23457
+ codeBlock,
21224
23458
  readOnly,
21225
23459
  hideHeader,
21226
23460
  onEditorReady,
@@ -21231,6 +23465,8 @@ function NotionEditor({
21231
23465
  function NotionEditorContent({
21232
23466
  placeholder,
21233
23467
  content,
23468
+ contentFormat,
23469
+ codeBlock,
21234
23470
  readOnly = false,
21235
23471
  hideHeader = false,
21236
23472
  onEditorReady,
@@ -21250,6 +23486,8 @@ function NotionEditorContent({
21250
23486
  ydoc,
21251
23487
  placeholder,
21252
23488
  content,
23489
+ contentFormat,
23490
+ codeBlock,
21253
23491
  readOnly,
21254
23492
  hideHeader,
21255
23493
  onEditorReady,
@@ -21257,7 +23495,7 @@ function NotionEditorContent({
21257
23495
  }
21258
23496
  );
21259
23497
  }
21260
- function ArticleViewer({ content, placeholder = "" }) {
23498
+ function ArticleViewer({ content, contentFormat, codeBlock, placeholder = "" }) {
21261
23499
  const ydoc = useMemo(() => new Doc(), []);
21262
23500
  return /* @__PURE__ */ jsx(UserProvider, { children: /* @__PURE__ */ jsx(TocProvider, { children: /* @__PURE__ */ jsx(
21263
23501
  EditorProvider,
@@ -21266,12 +23504,14 @@ function ArticleViewer({ content, placeholder = "" }) {
21266
23504
  ydoc,
21267
23505
  placeholder,
21268
23506
  content,
23507
+ contentFormat,
23508
+ codeBlock,
21269
23509
  readOnly: true,
21270
23510
  embedded: true
21271
23511
  }
21272
23512
  ) }) });
21273
23513
  }
21274
23514
 
21275
- export { ArticleViewer, Audio, CollabContext, CollabProvider, Column, Columns, DEFAULT_MAX_FILE_SIZES, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EditorConfigProvider, EditorContentArea, EditorProvider, FileAttachment, FloatingElement, HIDE_FLOATING_META, LoadingSpinner, MediaUploadButton, MediaUploadPlaceholder, NotionEditor, NotionEditorContent, PasteDropMedia, Separator, Spacer, Toolbar, ToolbarGroup, ToolbarSeparator, UiState, UserContext, UserProvider, Video, defaultUiState, detectMediaKind, findSelectionPosition, hasContentAbove, markHideFloatingOnNext, pickFile, resolveMaxSize, runMediaUpload, selectNodeAndHideFloating, useCollab, useCollaboration, useComposedRef, useCursorVisibility, useEditorConfig, useElementRect, useFloatingElement, useFloatingToolbarVisibility, useIsBreakpoint, useIsomorphicLayoutEffect, useMenuNavigation, useOnClickOutside, useThrottledCallback, useUiEditorState, useUnmount, useUser, useWindowSize, use_ui_editor_state_default };
21276
- //# sourceMappingURL=chunk-IUDLMN53.js.map
21277
- //# sourceMappingURL=chunk-IUDLMN53.js.map
23515
+ export { ApiParamExtension, ArticleViewer, Audio, CalloutExtension, CodeBlockExtension, CodeGroupExtension, CollabContext, CollabProvider, Column, Columns, DEFAULT_MAX_FILE_SIZES, DetailsExtension, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, EditorConfigProvider, EditorContentArea, EditorProvider, FileAttachment, FloatingElement, HIDE_FLOATING_META, LoadingSpinner, MediaUploadButton, MediaUploadPlaceholder, NotionEditor, NotionEditorContent, PasteDropMedia, Separator, Spacer, Toolbar, ToolbarGroup, ToolbarSeparator, UiState, UserContext, UserProvider, Video, defaultUiState, detectMediaKind, findSelectionPosition, getEditorMarkdown, hasContentAbove, markHideFloatingOnNext, parseEditorContent, pickFile, renderHighlightedHTML, resolveMaxSize, runMediaUpload, selectNodeAndHideFloating, serializeEditorMarkdown, useCollab, useCollaboration, useComposedRef, useCursorVisibility, useEditorConfig, useElementRect, useFloatingElement, useFloatingToolbarVisibility, useIsBreakpoint, useIsomorphicLayoutEffect, useMenuNavigation, useOnClickOutside, useThrottledCallback, useUiEditorState, useUnmount, useUser, useWindowSize, use_ui_editor_state_default };
23516
+ //# sourceMappingURL=chunk-SGLNT6Z2.js.map
23517
+ //# sourceMappingURL=chunk-SGLNT6Z2.js.map