tiptap-markdown-react 1.3.3 → 1.5.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.
@@ -16,7 +16,7 @@ import { Color, TextStyle, FontSize } from "@tiptap/extension-text-style";
16
16
  import StarterKit from "@tiptap/starter-kit";
17
17
  import { createLowlight, common } from "lowlight";
18
18
  import { Plugin, PluginKey } from "@tiptap/pm/state";
19
- import { p as parseChartFenceBody, s as serializeChartPayload, i as imageMarkdownTokenizer, c as imageParseMarkdown, d as imageRenderMarkdown, f as imageNodeHtml, g as imageFigureParseRule, h as imageCropAttribute, j as imageCaptionAttribute, k as imageAlignAttribute, n as normalizeChartMarkdown } from "./ReportContent-Bc5q1bYp.js";
19
+ import { p as parseChartFenceBody, s as serializeChartPayload, i as imageMarkdownTokenizer, c as imageParseMarkdown, d as imageRenderMarkdown, f as imageNodeHtml, g as imageFigureParseRule, h as imageCropAttribute, j as imageCaptionAttribute, k as imageAlignAttribute, n as normalizeChartMarkdown } from "./ReportContent-DUvhic0o.js";
20
20
  import { InlineMath, BlockMath } from "@tiptap/extension-mathematics";
21
21
  import katex from "katex";
22
22
  import { Emoji, shortcodeToEmoji, emojis, emojiToShortcode } from "@tiptap/extension-emoji";
@@ -135,6 +135,8 @@ const CommentMark = Mark.create({
135
135
  name: "commentAnchor",
136
136
  // 评论是「引用快照」:光标在区间边界输入不应自动扩展高亮。
137
137
  inclusive: false,
138
+ // 官方 unsetAllMarks 只卸 clearable 的 mark。评论不是排版,清除格式必须留下。
139
+ clearable: false,
138
140
  // 与加粗/斜体等格式可共存。
139
141
  excludes: "",
140
142
  addOptions() {
@@ -1505,6 +1507,66 @@ const InlineCode = Code.extend({
1505
1507
  excludes: ""
1506
1508
  });
1507
1509
  const lowlight = createLowlight(common);
1510
+ const FONT_SIZE_VALUE = /^\d+(?:\.\d+)?(?:px|em|rem|%)$/i;
1511
+ const HEX_COLOR = /^#[0-9a-fA-F]{3,8}$/;
1512
+ const RGB_COLOR = /^rgba?\(\s*(?:\d{1,3}\s*,\s*){2}\d{1,3}(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)$/;
1513
+ const HSL_COLOR = /^hsla?\(\s*[\d.]+\s*,\s*[\d.]+%\s*,\s*[\d.]+%(?:\s*,\s*(?:0|1|0?\.\d+))?\s*\)$/;
1514
+ const NAMED_COLOR = /^[a-zA-Z]{1,30}$/;
1515
+ function isSafeColor(value) {
1516
+ if (/[<>"'\\{};]/.test(value)) return false;
1517
+ return HEX_COLOR.test(value) || RGB_COLOR.test(value) || HSL_COLOR.test(value) || NAMED_COLOR.test(value);
1518
+ }
1519
+ function parseTextStyleAttrs(style) {
1520
+ const attrs = {};
1521
+ for (const entry of style.split(";")) {
1522
+ const index = entry.indexOf(":");
1523
+ if (index < 0) continue;
1524
+ const key = entry.slice(0, index).trim().toLowerCase();
1525
+ const value = entry.slice(index + 1).trim();
1526
+ if (key === "color" && isSafeColor(value)) attrs.color = value;
1527
+ else if (key === "font-size" && FONT_SIZE_VALUE.test(value)) {
1528
+ attrs.fontSize = value;
1529
+ }
1530
+ }
1531
+ if (!attrs.color && !attrs.fontSize) return null;
1532
+ return attrs;
1533
+ }
1534
+ function readStyleAttribute(attrs) {
1535
+ const match = /\bstyle\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+))/i.exec(
1536
+ attrs
1537
+ );
1538
+ if (!match) return void 0;
1539
+ return (match[1] ?? match[2] ?? match[3] ?? "").trim();
1540
+ }
1541
+ function matchTextStyleSpan(src) {
1542
+ const open = /^<span\b([^>]*)>/i.exec(src);
1543
+ if (!open || /\/\s*$/.test(open[1])) return void 0;
1544
+ const style = readStyleAttribute(open[1]);
1545
+ if (style == null || !parseTextStyleAttrs(style)) return void 0;
1546
+ const tagRe = /<\/?span\b[^>]*>/i;
1547
+ let depth = 1;
1548
+ let cursor = open[0].length;
1549
+ while (cursor < src.length) {
1550
+ const found = tagRe.exec(src.slice(cursor));
1551
+ if (!found) return void 0;
1552
+ const tag = found[0];
1553
+ const at = cursor + found.index;
1554
+ if (tag.startsWith("</")) {
1555
+ depth -= 1;
1556
+ if (depth === 0) {
1557
+ return {
1558
+ raw: src.slice(0, at + tag.length),
1559
+ style,
1560
+ inner: src.slice(open[0].length, at)
1561
+ };
1562
+ }
1563
+ } else if (!/\/\s*>$/.test(tag)) {
1564
+ depth += 1;
1565
+ }
1566
+ cursor = at + tag.length;
1567
+ }
1568
+ return void 0;
1569
+ }
1508
1570
  const MarkdownTextStyle = TextStyle.extend({
1509
1571
  renderMarkdown(node, helpers) {
1510
1572
  const { color, fontSize } = node.attrs ?? {};
@@ -1514,6 +1576,127 @@ const MarkdownTextStyle = TextStyle.extend({
1514
1576
  if (styles.length === 0) return "";
1515
1577
  const children = helpers.renderChildren();
1516
1578
  return `<span style="${styles.join(";")}">${children}</span>`;
1579
+ },
1580
+ markdownTokenizer: {
1581
+ name: "textStyle",
1582
+ level: "inline",
1583
+ start(src) {
1584
+ const match = /<span\b[^>]*\bstyle\s*=/i.exec(src);
1585
+ return match ? match.index : -1;
1586
+ },
1587
+ tokenize(src, _tokens, lexer) {
1588
+ const matched = matchTextStyleSpan(src);
1589
+ if (!matched) return void 0;
1590
+ return {
1591
+ type: "textStyle",
1592
+ raw: matched.raw,
1593
+ style: matched.style,
1594
+ tokens: lexer.inlineTokens(matched.inner)
1595
+ };
1596
+ }
1597
+ },
1598
+ parseMarkdown: (token, helpers) => {
1599
+ const attrs = parseTextStyleAttrs(String(token.style ?? ""));
1600
+ const content = helpers.parseInline(token.tokens ?? []);
1601
+ if (!attrs) return content;
1602
+ return helpers.applyMark("textStyle", content, attrs);
1603
+ }
1604
+ });
1605
+ function readHighlightColor(attrs) {
1606
+ const data = /\bdata-color\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(attrs);
1607
+ const fromData = ((data == null ? void 0 : data[1]) ?? (data == null ? void 0 : data[2]) ?? "").trim();
1608
+ if (fromData && isSafeColor(fromData)) return fromData;
1609
+ const style = /\bstyle\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(attrs);
1610
+ const styleText = (style == null ? void 0 : style[1]) ?? (style == null ? void 0 : style[2]) ?? "";
1611
+ for (const entry of styleText.split(";")) {
1612
+ const index = entry.indexOf(":");
1613
+ if (index < 0) continue;
1614
+ const key = entry.slice(0, index).trim().toLowerCase();
1615
+ const value = entry.slice(index + 1).trim();
1616
+ if (key === "background-color" && isSafeColor(value)) return value;
1617
+ }
1618
+ return null;
1619
+ }
1620
+ function matchHighlightMark(src) {
1621
+ const open = /^<mark\b([^>]*)>/i.exec(src);
1622
+ if (!open || /\/\s*$/.test(open[1])) return void 0;
1623
+ if (/data-comment-ids/i.test(open[1]) || /\btmr-comment\b/.test(open[1])) {
1624
+ return void 0;
1625
+ }
1626
+ const color = readHighlightColor(open[1]);
1627
+ const tagRe = /<\/?mark\b[^>]*>/i;
1628
+ let depth = 1;
1629
+ let cursor = open[0].length;
1630
+ while (cursor < src.length) {
1631
+ const found = tagRe.exec(src.slice(cursor));
1632
+ if (!found) return void 0;
1633
+ const tag = found[0];
1634
+ const at = cursor + found.index;
1635
+ if (tag.startsWith("</")) {
1636
+ depth -= 1;
1637
+ if (depth === 0) {
1638
+ return {
1639
+ raw: src.slice(0, at + tag.length),
1640
+ inner: src.slice(open[0].length, at),
1641
+ color
1642
+ };
1643
+ }
1644
+ } else if (!/\/\s*>$/.test(tag)) {
1645
+ depth += 1;
1646
+ }
1647
+ cursor = at + tag.length;
1648
+ }
1649
+ return void 0;
1650
+ }
1651
+ const MarkdownHighlight = Highlight.extend({
1652
+ renderMarkdown(node, helpers) {
1653
+ var _a;
1654
+ const children = helpers.renderChildren(node);
1655
+ const color = typeof ((_a = node.attrs) == null ? void 0 : _a.color) === "string" ? node.attrs.color : "";
1656
+ if (!color || !isSafeColor(color)) return `==${children}==`;
1657
+ const encoded = color.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
1658
+ return `<mark data-color="${encoded}" style="background-color:${encoded}">${children}</mark>`;
1659
+ },
1660
+ markdownTokenizer: {
1661
+ name: "highlight",
1662
+ level: "inline",
1663
+ start(src) {
1664
+ const mark = /<mark\b/i.exec(src);
1665
+ const eq = src.indexOf("==");
1666
+ const indexes = [mark == null ? void 0 : mark.index, eq >= 0 ? eq : void 0].filter(
1667
+ (index) => index != null
1668
+ );
1669
+ return indexes.length ? Math.min(...indexes) : -1;
1670
+ },
1671
+ tokenize(src, _tokens, lexer) {
1672
+ if (src.startsWith("==")) {
1673
+ const match = /^(==)([^=]+)(==)/.exec(src);
1674
+ if (!match) return void 0;
1675
+ const inner = match[2].trim();
1676
+ return {
1677
+ type: "highlight",
1678
+ raw: match[0],
1679
+ text: inner,
1680
+ tokens: lexer.inlineTokens(inner)
1681
+ };
1682
+ }
1683
+ const matched = matchHighlightMark(src);
1684
+ if (!matched) return void 0;
1685
+ return {
1686
+ type: "highlight",
1687
+ raw: matched.raw,
1688
+ color: matched.color,
1689
+ tokens: lexer.inlineTokens(matched.inner)
1690
+ };
1691
+ }
1692
+ },
1693
+ parseMarkdown: (token, helpers) => {
1694
+ const content = helpers.parseInline(token.tokens ?? []);
1695
+ const color = typeof token.color === "string" ? token.color : "";
1696
+ if (color && isSafeColor(color)) {
1697
+ return helpers.applyMark("highlight", content, { color });
1698
+ }
1699
+ return helpers.applyMark("highlight", content);
1517
1700
  }
1518
1701
  });
1519
1702
  function htmlTagTokenizer(tag, tokenType) {
@@ -1593,8 +1776,62 @@ const MarkdownParagraph = Paragraph.extend({
1593
1776
  markdownTokenizer: paragraphMarkdownTokenizer,
1594
1777
  parseMarkdown: paragraphParseMarkdown
1595
1778
  });
1779
+ function clearFontSizeInTextblocks({ state, tr }) {
1780
+ const markType = state.schema.marks.textStyle;
1781
+ const boldType = state.schema.marks.bold;
1782
+ const ranges = [];
1783
+ const { from, to } = state.selection;
1784
+ state.doc.nodesBetween(from, to, (node, pos) => {
1785
+ if (!node.isTextblock) return;
1786
+ const start = pos + 1;
1787
+ const end = pos + node.nodeSize - 1;
1788
+ if (boldType) tr.removeMark(start, end, boldType);
1789
+ if (!markType) return false;
1790
+ node.descendants((child, childPos) => {
1791
+ if (!child.isText) return;
1792
+ const mark = child.marks.find((item) => item.type === markType);
1793
+ if (!(mark == null ? void 0 : mark.attrs.fontSize)) return;
1794
+ const childStart = pos + 1 + childPos;
1795
+ ranges.push({
1796
+ from: childStart,
1797
+ to: childStart + child.nodeSize,
1798
+ attrs: { ...mark.attrs, fontSize: null }
1799
+ });
1800
+ });
1801
+ return false;
1802
+ });
1803
+ for (const range of ranges) {
1804
+ if (!markType) continue;
1805
+ tr.removeMark(range.from, range.to, markType);
1806
+ const keep = Object.values(range.attrs).some(
1807
+ (value) => value != null && value !== ""
1808
+ );
1809
+ if (keep) tr.addMark(range.from, range.to, markType.create(range.attrs));
1810
+ }
1811
+ }
1596
1812
  const MarkdownHeading = Heading.extend({
1597
- renderMarkdown: headingRenderMarkdown
1813
+ renderMarkdown: headingRenderMarkdown,
1814
+ addCommands() {
1815
+ var _a;
1816
+ const parent = (_a = this.parent) == null ? void 0 : _a.call(this);
1817
+ return {
1818
+ ...parent,
1819
+ setHeading: (attributes) => (props) => {
1820
+ var _a2, _b;
1821
+ if (!this.options.levels.includes(attributes.level)) return false;
1822
+ clearFontSizeInTextblocks(props);
1823
+ return ((_b = (_a2 = parent == null ? void 0 : parent.setHeading) == null ? void 0 : _a2.call(parent, attributes)) == null ? void 0 : _b(props)) ?? false;
1824
+ },
1825
+ toggleHeading: (attributes) => (props) => {
1826
+ var _a2, _b;
1827
+ if (!this.options.levels.includes(attributes.level)) return false;
1828
+ if (!props.editor.isActive(this.name, attributes)) {
1829
+ clearFontSizeInTextblocks(props);
1830
+ }
1831
+ return ((_b = (_a2 = parent == null ? void 0 : parent.toggleHeading) == null ? void 0 : _a2.call(parent, attributes)) == null ? void 0 : _b(props)) ?? false;
1832
+ }
1833
+ };
1834
+ }
1598
1835
  });
1599
1836
  const DetailsBlock = Details.extend({
1600
1837
  addAttributes() {
@@ -1638,7 +1875,7 @@ const baseExtensions = [
1638
1875
  // 正文字号:官方 FontSize(v3 起并入 text-style 包),以内联样式挂在 TextStyle
1639
1876
  // 的 <span> 上(如 style="font-size: 16px"),与 Color 同基座,三端共用。
1640
1877
  FontSize.configure({ types: [TextStyle.name] }),
1641
- Highlight.configure({ multicolor: true }),
1878
+ MarkdownHighlight.configure({ multicolor: true }),
1642
1879
  MarkdownSuperscript,
1643
1880
  MarkdownSubscript,
1644
1881
  TableKit.configure({
@@ -1760,4 +1997,4 @@ export {
1760
1997
  NUMBERING_FORMAT_LABELS as u,
1761
1998
  NUMBERING_FORMAT_PREVIEWS as v
1762
1999
  };
1763
- //# sourceMappingURL=extractToc-DiI2437D.js.map
2000
+ //# sourceMappingURL=extractToc-B-hg0XuH.js.map