@xlxz/markdown-editor 2.1.0 → 2.2.1

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.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Callout 检测:识别文档中的 Obsidian callout 区域
3
+ *
4
+ * 规则:连续的 `> ` 开头行,首行匹配 `> [!TYPE]` 格式
5
+ */
6
+ export interface CalloutRange {
7
+ from: number;
8
+ to: number;
9
+ firstLine: number;
10
+ lastLine: number;
11
+ type: string;
12
+ }
13
+ export declare function isCalloutHeader(text: string): boolean;
14
+ export declare function parseCalloutType(text: string): string;
15
+ export declare function isQuoteLine(text: string): boolean;
16
+ export declare function findCalloutRanges(doc: any): CalloutRange[];
17
+ export declare function cursorInCallout(state: any, callout: CalloutRange): boolean;
18
+ export declare function cursorOnLine(state: any, lineFrom: number, lineTo: number): boolean;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Callout Live Preview 扩展 — 入口
3
+ *
4
+ * 设计:
5
+ * - 首行(`> [!TYPE] title`):
6
+ * 非激活:icon + 标题文字(正常字号、加粗、类型颜色),上下各半行 padding
7
+ * 激活:显示原始 `> [!TYPE] title`,保持相同 padding
8
+ * - 中间行:带底色,非激活时 `> ` 前缀透明但占位
9
+ * - 末行:额外 padding-bottom 半行
10
+ * - 所有行正常显示行号
11
+ * - 去掉 blockquote 竖条(通过 CSS 隐藏 ::before)
12
+ */
13
+ export declare function createCalloutExtension(): any[];
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Callout 主题样式
3
+ *
4
+ * 选择器使用 `.cm-content .cm-line.cm-callout-*` 提升特异度 (0,4,0+),
5
+ * 确保覆盖 Obsidian 的 HyperMD-quote 和 .cm-line 默认样式。
6
+ */
7
+ export declare function createCalloutTheme(EditorView: any): any;
package/dist/index.cjs CHANGED
@@ -46,6 +46,7 @@ __export(exports_src, {
46
46
  expandTextPlugin: () => expandTextPlugin,
47
47
  createEditor: () => createEditor,
48
48
  closeBracketsPlugin: () => closeBracketsPlugin,
49
+ calloutPlugin: () => calloutPlugin,
49
50
  baseExtensionsPlugin: () => baseExtensionsPlugin,
50
51
  autoLoad: () => autoLoad,
51
52
  attachmentPlugin: () => attachmentPlugin
@@ -1405,6 +1406,298 @@ var tablePlugin = {
1405
1406
  return createTableExtension();
1406
1407
  }
1407
1408
  };
1409
+ // src/callout/detect.ts
1410
+ var CALLOUT_HEADER_RE = /^>\s*\[!(\w+)\]\s*(.*)$/;
1411
+ var QUOTE_LINE_RE = /^>\s?(.*)$/;
1412
+ function isCalloutHeader(text) {
1413
+ return CALLOUT_HEADER_RE.test(text.trim());
1414
+ }
1415
+ function parseCalloutType(text) {
1416
+ const m = text.trim().match(CALLOUT_HEADER_RE);
1417
+ return m ? m[1].toLowerCase() : "";
1418
+ }
1419
+ function isQuoteLine(text) {
1420
+ return QUOTE_LINE_RE.test(text.trim());
1421
+ }
1422
+ function findCalloutRanges(doc) {
1423
+ const ranges = [];
1424
+ const lineCount = doc.lines;
1425
+ let i = 1;
1426
+ while (i <= lineCount) {
1427
+ const line = doc.line(i);
1428
+ if (isCalloutHeader(line.text)) {
1429
+ const type = parseCalloutType(line.text);
1430
+ const firstLine = i;
1431
+ let lastLine = i;
1432
+ let j = i + 1;
1433
+ while (j <= lineCount) {
1434
+ const nextLine = doc.line(j);
1435
+ if (isQuoteLine(nextLine.text)) {
1436
+ lastLine = j;
1437
+ j++;
1438
+ } else {
1439
+ break;
1440
+ }
1441
+ }
1442
+ ranges.push({
1443
+ from: line.from,
1444
+ to: doc.line(lastLine).to,
1445
+ firstLine,
1446
+ lastLine,
1447
+ type
1448
+ });
1449
+ i = lastLine + 1;
1450
+ } else {
1451
+ i++;
1452
+ }
1453
+ }
1454
+ return ranges;
1455
+ }
1456
+ function cursorOnLine(state, lineFrom, lineTo) {
1457
+ for (const range of state.selection.ranges) {
1458
+ if (range.from <= lineTo && range.to >= lineFrom)
1459
+ return true;
1460
+ }
1461
+ return false;
1462
+ }
1463
+
1464
+ // src/callout/theme.ts
1465
+ function createCalloutTheme(EditorView) {
1466
+ return EditorView.baseTheme({
1467
+ ".cm-content .cm-line.cm-callout-line.cm-callout-line": {
1468
+ backgroundColor: "rgba(var(--callout-line-color, var(--callout-default)), 0.1)"
1469
+ },
1470
+ ".cm-content .cm-line.cm-callout-line::before": {
1471
+ display: "none !important"
1472
+ },
1473
+ ".cm-content .cm-line.cm-callout-first": {
1474
+ borderTopLeftRadius: "var(--callout-radius, var(--radius-s))",
1475
+ borderTopRightRadius: "var(--callout-radius, var(--radius-s))",
1476
+ paddingTop: "calc(var(--line-height-normal, 1.5) * var(--font-text-size, 16px) * 0.5)",
1477
+ paddingBottom: "calc(var(--line-height-normal, 1.5) * var(--font-text-size, 16px) * 0.5)",
1478
+ height: "calc(var(--line-height-normal, 1.5) * var(--font-text-size, 16px) * 2)",
1479
+ boxSizing: "border-box"
1480
+ },
1481
+ ".cm-content .cm-line.cm-callout-first:not(.cm-callout-active)": {
1482
+ fontWeight: "var(--callout-title-weight, 600)",
1483
+ color: "rgb(var(--callout-line-color, var(--callout-default)))"
1484
+ },
1485
+ ".cm-content .cm-line.cm-callout-first.cm-callout-active": {
1486
+ fontWeight: "inherit",
1487
+ color: "inherit"
1488
+ },
1489
+ ".cm-content .cm-line.cm-callout-last": {
1490
+ borderBottomLeftRadius: "var(--callout-radius, var(--radius-s))",
1491
+ borderBottomRightRadius: "var(--callout-radius, var(--radius-s))",
1492
+ paddingBottom: "calc(var(--line-height-normal, 1.5) * var(--font-text-size, 16px) * 0.5)"
1493
+ },
1494
+ ".cm-content .cm-line.cm-callout-first.cm-callout-last": {
1495
+ borderRadius: "var(--callout-radius, var(--radius-s))"
1496
+ },
1497
+ ".cm-content .cm-line.cm-callout-line .cm-callout-hide": {
1498
+ color: "transparent"
1499
+ },
1500
+ ".cm-callout-header-widget": {
1501
+ display: "inline-flex",
1502
+ alignItems: "center",
1503
+ gap: "0.5ch",
1504
+ marginRight: "1ch",
1505
+ verticalAlign: "middle",
1506
+ color: "rgb(var(--callout-line-color, var(--callout-default)))"
1507
+ },
1508
+ ".cm-callout-icon": {
1509
+ display: "inline-flex",
1510
+ alignItems: "center",
1511
+ justifyContent: "center",
1512
+ flexShrink: "0"
1513
+ },
1514
+ ".cm-callout-icon svg": {
1515
+ width: "1em",
1516
+ height: "1em",
1517
+ strokeWidth: "2"
1518
+ },
1519
+ ".cm-callout-title-label": {
1520
+ whiteSpace: "nowrap"
1521
+ },
1522
+ ".cm-content .cm-line.cm-callout-note": { "--callout-line-color": "var(--callout-info)" },
1523
+ ".cm-content .cm-line.cm-callout-info": { "--callout-line-color": "var(--callout-info)" },
1524
+ ".cm-content .cm-line.cm-callout-tip": { "--callout-line-color": "var(--callout-tip)" },
1525
+ ".cm-content .cm-line.cm-callout-hint": { "--callout-line-color": "var(--callout-tip)" },
1526
+ ".cm-content .cm-line.cm-callout-warning": { "--callout-line-color": "var(--callout-warning)" },
1527
+ ".cm-content .cm-line.cm-callout-caution": { "--callout-line-color": "var(--callout-warning)" },
1528
+ ".cm-content .cm-line.cm-callout-attention": { "--callout-line-color": "var(--callout-warning)" },
1529
+ ".cm-content .cm-line.cm-callout-danger": { "--callout-line-color": "var(--callout-error)" },
1530
+ ".cm-content .cm-line.cm-callout-error": { "--callout-line-color": "var(--callout-error)" },
1531
+ ".cm-content .cm-line.cm-callout-bug": { "--callout-line-color": "var(--callout-bug)" },
1532
+ ".cm-content .cm-line.cm-callout-success": { "--callout-line-color": "var(--callout-success)" },
1533
+ ".cm-content .cm-line.cm-callout-check": { "--callout-line-color": "var(--callout-success)" },
1534
+ ".cm-content .cm-line.cm-callout-done": { "--callout-line-color": "var(--callout-success)" },
1535
+ ".cm-content .cm-line.cm-callout-question": { "--callout-line-color": "var(--callout-question)" },
1536
+ ".cm-content .cm-line.cm-callout-help": { "--callout-line-color": "var(--callout-question)" },
1537
+ ".cm-content .cm-line.cm-callout-faq": { "--callout-line-color": "var(--callout-question)" },
1538
+ ".cm-content .cm-line.cm-callout-example": { "--callout-line-color": "var(--callout-example)" },
1539
+ ".cm-content .cm-line.cm-callout-abstract": { "--callout-line-color": "var(--callout-summary)" },
1540
+ ".cm-content .cm-line.cm-callout-summary": { "--callout-line-color": "var(--callout-summary)" },
1541
+ ".cm-content .cm-line.cm-callout-tldr": { "--callout-line-color": "var(--callout-summary)" },
1542
+ ".cm-content .cm-line.cm-callout-important": { "--callout-line-color": "var(--callout-important)" },
1543
+ ".cm-content .cm-line.cm-callout-todo": { "--callout-line-color": "var(--callout-todo)" },
1544
+ ".cm-content .cm-line.cm-callout-fail": { "--callout-line-color": "var(--callout-fail)" },
1545
+ ".cm-content .cm-line.cm-callout-failure": { "--callout-line-color": "var(--callout-fail)" },
1546
+ ".cm-content .cm-line.cm-callout-missing": { "--callout-line-color": "var(--callout-fail)" },
1547
+ ".cm-content .cm-line.cm-callout-quote": { "--callout-line-color": "var(--callout-quote)" },
1548
+ ".cm-content .cm-line.cm-callout-cite": { "--callout-line-color": "var(--callout-quote)" }
1549
+ });
1550
+ }
1551
+
1552
+ // src/callout/index.ts
1553
+ function createCalloutExtension() {
1554
+ const { EditorView, ViewPlugin, Decoration, WidgetType } = window.__cm6;
1555
+
1556
+ class CalloutHeaderWidget extends WidgetType {
1557
+ type;
1558
+ label;
1559
+ constructor(type, label) {
1560
+ super();
1561
+ this.type = type;
1562
+ this.label = label;
1563
+ }
1564
+ toDOM() {
1565
+ const wrapper = document.createElement("span");
1566
+ wrapper.className = "cm-callout-header-widget";
1567
+ wrapper.setAttribute("aria-hidden", "true");
1568
+ const iconEl = document.createElement("span");
1569
+ iconEl.className = "cm-callout-icon";
1570
+ const iconName = getCalloutIconName(this.type);
1571
+ const setIcon = window.__obsidian?.setIcon;
1572
+ if (setIcon) {
1573
+ setIcon(iconEl, iconName);
1574
+ }
1575
+ wrapper.appendChild(iconEl);
1576
+ if (this.label) {
1577
+ const labelEl = document.createElement("span");
1578
+ labelEl.className = "cm-callout-title-label";
1579
+ labelEl.textContent = this.label;
1580
+ wrapper.appendChild(labelEl);
1581
+ }
1582
+ return wrapper;
1583
+ }
1584
+ eq(other) {
1585
+ return this.type === other.type && this.label === other.label;
1586
+ }
1587
+ ignoreEvent() {
1588
+ return true;
1589
+ }
1590
+ }
1591
+ const calloutViewPlugin = ViewPlugin.fromClass(class {
1592
+ decorations;
1593
+ constructor(view) {
1594
+ this.decorations = this.buildDecorations(view);
1595
+ }
1596
+ update(update) {
1597
+ if (update.docChanged || update.selectionSet || update.viewportChanged) {
1598
+ this.decorations = this.buildDecorations(update.view);
1599
+ }
1600
+ }
1601
+ buildDecorations(view) {
1602
+ const { state } = view;
1603
+ const doc = state.doc;
1604
+ const callouts = findCalloutRanges(doc);
1605
+ const builder = [];
1606
+ for (const callout of callouts) {
1607
+ const typeClass = `cm-callout-${callout.type}`;
1608
+ for (let lineNum = callout.firstLine;lineNum <= callout.lastLine; lineNum++) {
1609
+ const line = doc.line(lineNum);
1610
+ const isLineActive = cursorOnLine(state, line.from, line.to);
1611
+ const classes = ["cm-callout-line", typeClass];
1612
+ if (lineNum === callout.firstLine)
1613
+ classes.push("cm-callout-first");
1614
+ if (lineNum === callout.lastLine)
1615
+ classes.push("cm-callout-last");
1616
+ if (lineNum !== callout.firstLine && lineNum !== callout.lastLine)
1617
+ classes.push("cm-callout-body");
1618
+ if (isLineActive)
1619
+ classes.push("cm-callout-active");
1620
+ builder.push(Decoration.line({ class: classes.join(" ") }).range(line.from));
1621
+ if (lineNum === callout.firstLine && !isLineActive) {
1622
+ const text = line.text;
1623
+ const match = text.match(/^>\s*\[!\w+\]\s*(.*)?$/);
1624
+ if (match) {
1625
+ const title = match[1]?.trim();
1626
+ const label = title || capitalizeFirst(callout.type);
1627
+ const prefixMatch = text.match(/^>\s?/);
1628
+ const prefixLen = prefixMatch ? prefixMatch[0].length : 0;
1629
+ if (prefixLen > 0) {
1630
+ builder.push(Decoration.mark({ class: "cm-callout-hide" }).range(line.from, line.from + prefixLen));
1631
+ }
1632
+ builder.push(Decoration.replace({
1633
+ widget: new CalloutHeaderWidget(callout.type, label)
1634
+ }).range(line.from + prefixLen, line.to));
1635
+ }
1636
+ } else if (lineNum !== callout.firstLine && !isLineActive) {
1637
+ const text = line.text;
1638
+ const match = text.match(/^>\s?/);
1639
+ if (match) {
1640
+ const prefixEnd = line.from + match[0].length;
1641
+ builder.push(Decoration.mark({ class: "cm-callout-hide" }).range(line.from, prefixEnd));
1642
+ }
1643
+ }
1644
+ }
1645
+ }
1646
+ builder.sort((a, b) => a.from - b.from || (a.startSide || 0) - (b.startSide || 0));
1647
+ return Decoration.set(builder);
1648
+ }
1649
+ }, {
1650
+ decorations: (v) => v.decorations
1651
+ });
1652
+ const calloutTheme = createCalloutTheme(EditorView);
1653
+ return [calloutViewPlugin, calloutTheme];
1654
+ }
1655
+ function getCalloutIconName(type) {
1656
+ const icons = {
1657
+ note: "lucide-pencil",
1658
+ info: "lucide-info",
1659
+ tip: "lucide-flame",
1660
+ warning: "lucide-alert-triangle",
1661
+ danger: "lucide-zap",
1662
+ error: "lucide-zap",
1663
+ bug: "lucide-bug",
1664
+ example: "lucide-list",
1665
+ question: "help-circle",
1666
+ success: "lucide-check",
1667
+ abstract: "lucide-clipboard-list",
1668
+ todo: "lucide-check-circle-2",
1669
+ important: "lucide-flame",
1670
+ quote: "quote-glyph",
1671
+ fail: "lucide-x"
1672
+ };
1673
+ const aliases = {
1674
+ hint: "tip",
1675
+ caution: "warning",
1676
+ attention: "warning",
1677
+ failure: "fail",
1678
+ missing: "fail",
1679
+ check: "success",
1680
+ done: "success",
1681
+ help: "question",
1682
+ faq: "question",
1683
+ summary: "abstract",
1684
+ tldr: "abstract",
1685
+ cite: "quote"
1686
+ };
1687
+ const resolved = aliases[type] || type;
1688
+ return icons[resolved] || icons["note"];
1689
+ }
1690
+ function capitalizeFirst(str) {
1691
+ return str.charAt(0).toUpperCase() + str.slice(1);
1692
+ }
1693
+
1694
+ // src/plugins/callout.ts
1695
+ var calloutPlugin = {
1696
+ id: "callout",
1697
+ install(_ctx) {
1698
+ return createCalloutExtension();
1699
+ }
1700
+ };
1408
1701
  // src/plugins/table-continuation.ts
1409
1702
  function isEmptyRow(text) {
1410
1703
  if (!isTableLine(text))
@@ -1630,6 +1923,7 @@ function getDefaultPlugins(opts) {
1630
1923
  plugins.push(linkHandlerPlugin);
1631
1924
  plugins.push(attachmentPlugin);
1632
1925
  plugins.push(tablePlugin);
1926
+ plugins.push(calloutPlugin);
1633
1927
  plugins.push(tableContinuationPlugin);
1634
1928
  plugins.push(onChangePlugin);
1635
1929
  return plugins;
@@ -1687,7 +1981,6 @@ function createEditor(container, options = {}, backend = {}) {
1687
1981
  const activePlugins = new Map;
1688
1982
  pluginStates.set("__mockEditor", mockEditor);
1689
1983
  pluginStates.set("__mockOwner", mockOwner);
1690
- let currentMode = "ir";
1691
1984
  function makeContext() {
1692
1985
  return {
1693
1986
  view,
@@ -1812,28 +2105,6 @@ function createEditor(container, options = {}, backend = {}) {
1812
2105
  }
1813
2106
  document.body.classList.toggle("theme-dark", theme === "dark");
1814
2107
  document.body.classList.toggle("theme-light", theme === "light");
1815
- },
1816
- setMode(mode) {
1817
- if (mode === currentMode)
1818
- return;
1819
- currentMode = mode;
1820
- const { EditorView: EV, Compartment } = window.__cm6;
1821
- if (mode === "ir" || mode === "view") {
1822
- container.classList.add("is-live-preview");
1823
- container.classList.remove("is-source-mode");
1824
- } else {
1825
- container.classList.remove("is-live-preview");
1826
- container.classList.add("is-source-mode");
1827
- }
1828
- view.dispatch({
1829
- effects: window.__cm6.StateEffect.appendConfig.of(EV.editable.of(mode !== "view"))
1830
- });
1831
- view.dispatch({
1832
- effects: window.__cm6.StateEffect.appendConfig.of([])
1833
- });
1834
- },
1835
- getMode() {
1836
- return currentMode;
1837
2108
  }
1838
2109
  };
1839
2110
  return instance;
package/dist/index.d.ts CHANGED
@@ -18,6 +18,6 @@
18
18
  * });
19
19
  */
20
20
  export { createEditor, autoLoad } from './kernel.js';
21
- export type { EditorBackend, EditorOptions, EditorInstance, EditorMode, EditorPlugin, PluginContext, LinkTarget, SuggestConfig, SuggestItem, I18nProvider, AssetLoader, AutoLoadOptions, } from './types.js';
22
- export { livePreviewPlugin, markdownLanguagePlugin, hangingIndentPlugin, listContinuationPlugin, closeBracketsPlugin, expandTextPlugin, foldPlugin, lineNumbersPlugin, indentGuidePlugin, suggestPlugin, linkHandlerPlugin, attachmentPlugin, tablePlugin, tableContinuationPlugin, themePlugin, baseExtensionsPlugin, keymapPlugin, onChangePlugin, } from './plugins/index.js';
21
+ export type { EditorBackend, EditorOptions, EditorInstance, EditorPlugin, PluginContext, LinkTarget, SuggestConfig, SuggestItem, I18nProvider, AssetLoader, AutoLoadOptions, } from './types.js';
22
+ export { livePreviewPlugin, markdownLanguagePlugin, hangingIndentPlugin, listContinuationPlugin, closeBracketsPlugin, expandTextPlugin, foldPlugin, lineNumbersPlugin, indentGuidePlugin, suggestPlugin, linkHandlerPlugin, attachmentPlugin, tablePlugin, calloutPlugin, tableContinuationPlugin, themePlugin, baseExtensionsPlugin, keymapPlugin, onChangePlugin, } from './plugins/index.js';
23
23
  export type { CompletionProvider } from './plugins/types.js';
package/dist/index.js CHANGED
@@ -1351,6 +1351,298 @@ var tablePlugin = {
1351
1351
  return createTableExtension();
1352
1352
  }
1353
1353
  };
1354
+ // src/callout/detect.ts
1355
+ var CALLOUT_HEADER_RE = /^>\s*\[!(\w+)\]\s*(.*)$/;
1356
+ var QUOTE_LINE_RE = /^>\s?(.*)$/;
1357
+ function isCalloutHeader(text) {
1358
+ return CALLOUT_HEADER_RE.test(text.trim());
1359
+ }
1360
+ function parseCalloutType(text) {
1361
+ const m = text.trim().match(CALLOUT_HEADER_RE);
1362
+ return m ? m[1].toLowerCase() : "";
1363
+ }
1364
+ function isQuoteLine(text) {
1365
+ return QUOTE_LINE_RE.test(text.trim());
1366
+ }
1367
+ function findCalloutRanges(doc) {
1368
+ const ranges = [];
1369
+ const lineCount = doc.lines;
1370
+ let i = 1;
1371
+ while (i <= lineCount) {
1372
+ const line = doc.line(i);
1373
+ if (isCalloutHeader(line.text)) {
1374
+ const type = parseCalloutType(line.text);
1375
+ const firstLine = i;
1376
+ let lastLine = i;
1377
+ let j = i + 1;
1378
+ while (j <= lineCount) {
1379
+ const nextLine = doc.line(j);
1380
+ if (isQuoteLine(nextLine.text)) {
1381
+ lastLine = j;
1382
+ j++;
1383
+ } else {
1384
+ break;
1385
+ }
1386
+ }
1387
+ ranges.push({
1388
+ from: line.from,
1389
+ to: doc.line(lastLine).to,
1390
+ firstLine,
1391
+ lastLine,
1392
+ type
1393
+ });
1394
+ i = lastLine + 1;
1395
+ } else {
1396
+ i++;
1397
+ }
1398
+ }
1399
+ return ranges;
1400
+ }
1401
+ function cursorOnLine(state, lineFrom, lineTo) {
1402
+ for (const range of state.selection.ranges) {
1403
+ if (range.from <= lineTo && range.to >= lineFrom)
1404
+ return true;
1405
+ }
1406
+ return false;
1407
+ }
1408
+
1409
+ // src/callout/theme.ts
1410
+ function createCalloutTheme(EditorView) {
1411
+ return EditorView.baseTheme({
1412
+ ".cm-content .cm-line.cm-callout-line.cm-callout-line": {
1413
+ backgroundColor: "rgba(var(--callout-line-color, var(--callout-default)), 0.1)"
1414
+ },
1415
+ ".cm-content .cm-line.cm-callout-line::before": {
1416
+ display: "none !important"
1417
+ },
1418
+ ".cm-content .cm-line.cm-callout-first": {
1419
+ borderTopLeftRadius: "var(--callout-radius, var(--radius-s))",
1420
+ borderTopRightRadius: "var(--callout-radius, var(--radius-s))",
1421
+ paddingTop: "calc(var(--line-height-normal, 1.5) * var(--font-text-size, 16px) * 0.5)",
1422
+ paddingBottom: "calc(var(--line-height-normal, 1.5) * var(--font-text-size, 16px) * 0.5)",
1423
+ height: "calc(var(--line-height-normal, 1.5) * var(--font-text-size, 16px) * 2)",
1424
+ boxSizing: "border-box"
1425
+ },
1426
+ ".cm-content .cm-line.cm-callout-first:not(.cm-callout-active)": {
1427
+ fontWeight: "var(--callout-title-weight, 600)",
1428
+ color: "rgb(var(--callout-line-color, var(--callout-default)))"
1429
+ },
1430
+ ".cm-content .cm-line.cm-callout-first.cm-callout-active": {
1431
+ fontWeight: "inherit",
1432
+ color: "inherit"
1433
+ },
1434
+ ".cm-content .cm-line.cm-callout-last": {
1435
+ borderBottomLeftRadius: "var(--callout-radius, var(--radius-s))",
1436
+ borderBottomRightRadius: "var(--callout-radius, var(--radius-s))",
1437
+ paddingBottom: "calc(var(--line-height-normal, 1.5) * var(--font-text-size, 16px) * 0.5)"
1438
+ },
1439
+ ".cm-content .cm-line.cm-callout-first.cm-callout-last": {
1440
+ borderRadius: "var(--callout-radius, var(--radius-s))"
1441
+ },
1442
+ ".cm-content .cm-line.cm-callout-line .cm-callout-hide": {
1443
+ color: "transparent"
1444
+ },
1445
+ ".cm-callout-header-widget": {
1446
+ display: "inline-flex",
1447
+ alignItems: "center",
1448
+ gap: "0.5ch",
1449
+ marginRight: "1ch",
1450
+ verticalAlign: "middle",
1451
+ color: "rgb(var(--callout-line-color, var(--callout-default)))"
1452
+ },
1453
+ ".cm-callout-icon": {
1454
+ display: "inline-flex",
1455
+ alignItems: "center",
1456
+ justifyContent: "center",
1457
+ flexShrink: "0"
1458
+ },
1459
+ ".cm-callout-icon svg": {
1460
+ width: "1em",
1461
+ height: "1em",
1462
+ strokeWidth: "2"
1463
+ },
1464
+ ".cm-callout-title-label": {
1465
+ whiteSpace: "nowrap"
1466
+ },
1467
+ ".cm-content .cm-line.cm-callout-note": { "--callout-line-color": "var(--callout-info)" },
1468
+ ".cm-content .cm-line.cm-callout-info": { "--callout-line-color": "var(--callout-info)" },
1469
+ ".cm-content .cm-line.cm-callout-tip": { "--callout-line-color": "var(--callout-tip)" },
1470
+ ".cm-content .cm-line.cm-callout-hint": { "--callout-line-color": "var(--callout-tip)" },
1471
+ ".cm-content .cm-line.cm-callout-warning": { "--callout-line-color": "var(--callout-warning)" },
1472
+ ".cm-content .cm-line.cm-callout-caution": { "--callout-line-color": "var(--callout-warning)" },
1473
+ ".cm-content .cm-line.cm-callout-attention": { "--callout-line-color": "var(--callout-warning)" },
1474
+ ".cm-content .cm-line.cm-callout-danger": { "--callout-line-color": "var(--callout-error)" },
1475
+ ".cm-content .cm-line.cm-callout-error": { "--callout-line-color": "var(--callout-error)" },
1476
+ ".cm-content .cm-line.cm-callout-bug": { "--callout-line-color": "var(--callout-bug)" },
1477
+ ".cm-content .cm-line.cm-callout-success": { "--callout-line-color": "var(--callout-success)" },
1478
+ ".cm-content .cm-line.cm-callout-check": { "--callout-line-color": "var(--callout-success)" },
1479
+ ".cm-content .cm-line.cm-callout-done": { "--callout-line-color": "var(--callout-success)" },
1480
+ ".cm-content .cm-line.cm-callout-question": { "--callout-line-color": "var(--callout-question)" },
1481
+ ".cm-content .cm-line.cm-callout-help": { "--callout-line-color": "var(--callout-question)" },
1482
+ ".cm-content .cm-line.cm-callout-faq": { "--callout-line-color": "var(--callout-question)" },
1483
+ ".cm-content .cm-line.cm-callout-example": { "--callout-line-color": "var(--callout-example)" },
1484
+ ".cm-content .cm-line.cm-callout-abstract": { "--callout-line-color": "var(--callout-summary)" },
1485
+ ".cm-content .cm-line.cm-callout-summary": { "--callout-line-color": "var(--callout-summary)" },
1486
+ ".cm-content .cm-line.cm-callout-tldr": { "--callout-line-color": "var(--callout-summary)" },
1487
+ ".cm-content .cm-line.cm-callout-important": { "--callout-line-color": "var(--callout-important)" },
1488
+ ".cm-content .cm-line.cm-callout-todo": { "--callout-line-color": "var(--callout-todo)" },
1489
+ ".cm-content .cm-line.cm-callout-fail": { "--callout-line-color": "var(--callout-fail)" },
1490
+ ".cm-content .cm-line.cm-callout-failure": { "--callout-line-color": "var(--callout-fail)" },
1491
+ ".cm-content .cm-line.cm-callout-missing": { "--callout-line-color": "var(--callout-fail)" },
1492
+ ".cm-content .cm-line.cm-callout-quote": { "--callout-line-color": "var(--callout-quote)" },
1493
+ ".cm-content .cm-line.cm-callout-cite": { "--callout-line-color": "var(--callout-quote)" }
1494
+ });
1495
+ }
1496
+
1497
+ // src/callout/index.ts
1498
+ function createCalloutExtension() {
1499
+ const { EditorView, ViewPlugin, Decoration, WidgetType } = window.__cm6;
1500
+
1501
+ class CalloutHeaderWidget extends WidgetType {
1502
+ type;
1503
+ label;
1504
+ constructor(type, label) {
1505
+ super();
1506
+ this.type = type;
1507
+ this.label = label;
1508
+ }
1509
+ toDOM() {
1510
+ const wrapper = document.createElement("span");
1511
+ wrapper.className = "cm-callout-header-widget";
1512
+ wrapper.setAttribute("aria-hidden", "true");
1513
+ const iconEl = document.createElement("span");
1514
+ iconEl.className = "cm-callout-icon";
1515
+ const iconName = getCalloutIconName(this.type);
1516
+ const setIcon = window.__obsidian?.setIcon;
1517
+ if (setIcon) {
1518
+ setIcon(iconEl, iconName);
1519
+ }
1520
+ wrapper.appendChild(iconEl);
1521
+ if (this.label) {
1522
+ const labelEl = document.createElement("span");
1523
+ labelEl.className = "cm-callout-title-label";
1524
+ labelEl.textContent = this.label;
1525
+ wrapper.appendChild(labelEl);
1526
+ }
1527
+ return wrapper;
1528
+ }
1529
+ eq(other) {
1530
+ return this.type === other.type && this.label === other.label;
1531
+ }
1532
+ ignoreEvent() {
1533
+ return true;
1534
+ }
1535
+ }
1536
+ const calloutViewPlugin = ViewPlugin.fromClass(class {
1537
+ decorations;
1538
+ constructor(view) {
1539
+ this.decorations = this.buildDecorations(view);
1540
+ }
1541
+ update(update) {
1542
+ if (update.docChanged || update.selectionSet || update.viewportChanged) {
1543
+ this.decorations = this.buildDecorations(update.view);
1544
+ }
1545
+ }
1546
+ buildDecorations(view) {
1547
+ const { state } = view;
1548
+ const doc = state.doc;
1549
+ const callouts = findCalloutRanges(doc);
1550
+ const builder = [];
1551
+ for (const callout of callouts) {
1552
+ const typeClass = `cm-callout-${callout.type}`;
1553
+ for (let lineNum = callout.firstLine;lineNum <= callout.lastLine; lineNum++) {
1554
+ const line = doc.line(lineNum);
1555
+ const isLineActive = cursorOnLine(state, line.from, line.to);
1556
+ const classes = ["cm-callout-line", typeClass];
1557
+ if (lineNum === callout.firstLine)
1558
+ classes.push("cm-callout-first");
1559
+ if (lineNum === callout.lastLine)
1560
+ classes.push("cm-callout-last");
1561
+ if (lineNum !== callout.firstLine && lineNum !== callout.lastLine)
1562
+ classes.push("cm-callout-body");
1563
+ if (isLineActive)
1564
+ classes.push("cm-callout-active");
1565
+ builder.push(Decoration.line({ class: classes.join(" ") }).range(line.from));
1566
+ if (lineNum === callout.firstLine && !isLineActive) {
1567
+ const text = line.text;
1568
+ const match = text.match(/^>\s*\[!\w+\]\s*(.*)?$/);
1569
+ if (match) {
1570
+ const title = match[1]?.trim();
1571
+ const label = title || capitalizeFirst(callout.type);
1572
+ const prefixMatch = text.match(/^>\s?/);
1573
+ const prefixLen = prefixMatch ? prefixMatch[0].length : 0;
1574
+ if (prefixLen > 0) {
1575
+ builder.push(Decoration.mark({ class: "cm-callout-hide" }).range(line.from, line.from + prefixLen));
1576
+ }
1577
+ builder.push(Decoration.replace({
1578
+ widget: new CalloutHeaderWidget(callout.type, label)
1579
+ }).range(line.from + prefixLen, line.to));
1580
+ }
1581
+ } else if (lineNum !== callout.firstLine && !isLineActive) {
1582
+ const text = line.text;
1583
+ const match = text.match(/^>\s?/);
1584
+ if (match) {
1585
+ const prefixEnd = line.from + match[0].length;
1586
+ builder.push(Decoration.mark({ class: "cm-callout-hide" }).range(line.from, prefixEnd));
1587
+ }
1588
+ }
1589
+ }
1590
+ }
1591
+ builder.sort((a, b) => a.from - b.from || (a.startSide || 0) - (b.startSide || 0));
1592
+ return Decoration.set(builder);
1593
+ }
1594
+ }, {
1595
+ decorations: (v) => v.decorations
1596
+ });
1597
+ const calloutTheme = createCalloutTheme(EditorView);
1598
+ return [calloutViewPlugin, calloutTheme];
1599
+ }
1600
+ function getCalloutIconName(type) {
1601
+ const icons = {
1602
+ note: "lucide-pencil",
1603
+ info: "lucide-info",
1604
+ tip: "lucide-flame",
1605
+ warning: "lucide-alert-triangle",
1606
+ danger: "lucide-zap",
1607
+ error: "lucide-zap",
1608
+ bug: "lucide-bug",
1609
+ example: "lucide-list",
1610
+ question: "help-circle",
1611
+ success: "lucide-check",
1612
+ abstract: "lucide-clipboard-list",
1613
+ todo: "lucide-check-circle-2",
1614
+ important: "lucide-flame",
1615
+ quote: "quote-glyph",
1616
+ fail: "lucide-x"
1617
+ };
1618
+ const aliases = {
1619
+ hint: "tip",
1620
+ caution: "warning",
1621
+ attention: "warning",
1622
+ failure: "fail",
1623
+ missing: "fail",
1624
+ check: "success",
1625
+ done: "success",
1626
+ help: "question",
1627
+ faq: "question",
1628
+ summary: "abstract",
1629
+ tldr: "abstract",
1630
+ cite: "quote"
1631
+ };
1632
+ const resolved = aliases[type] || type;
1633
+ return icons[resolved] || icons["note"];
1634
+ }
1635
+ function capitalizeFirst(str) {
1636
+ return str.charAt(0).toUpperCase() + str.slice(1);
1637
+ }
1638
+
1639
+ // src/plugins/callout.ts
1640
+ var calloutPlugin = {
1641
+ id: "callout",
1642
+ install(_ctx) {
1643
+ return createCalloutExtension();
1644
+ }
1645
+ };
1354
1646
  // src/plugins/table-continuation.ts
1355
1647
  function isEmptyRow(text) {
1356
1648
  if (!isTableLine(text))
@@ -1576,6 +1868,7 @@ function getDefaultPlugins(opts) {
1576
1868
  plugins.push(linkHandlerPlugin);
1577
1869
  plugins.push(attachmentPlugin);
1578
1870
  plugins.push(tablePlugin);
1871
+ plugins.push(calloutPlugin);
1579
1872
  plugins.push(tableContinuationPlugin);
1580
1873
  plugins.push(onChangePlugin);
1581
1874
  return plugins;
@@ -1633,7 +1926,6 @@ function createEditor(container, options = {}, backend = {}) {
1633
1926
  const activePlugins = new Map;
1634
1927
  pluginStates.set("__mockEditor", mockEditor);
1635
1928
  pluginStates.set("__mockOwner", mockOwner);
1636
- let currentMode = "ir";
1637
1929
  function makeContext() {
1638
1930
  return {
1639
1931
  view,
@@ -1758,28 +2050,6 @@ function createEditor(container, options = {}, backend = {}) {
1758
2050
  }
1759
2051
  document.body.classList.toggle("theme-dark", theme === "dark");
1760
2052
  document.body.classList.toggle("theme-light", theme === "light");
1761
- },
1762
- setMode(mode) {
1763
- if (mode === currentMode)
1764
- return;
1765
- currentMode = mode;
1766
- const { EditorView: EV, Compartment } = window.__cm6;
1767
- if (mode === "ir" || mode === "view") {
1768
- container.classList.add("is-live-preview");
1769
- container.classList.remove("is-source-mode");
1770
- } else {
1771
- container.classList.remove("is-live-preview");
1772
- container.classList.add("is-source-mode");
1773
- }
1774
- view.dispatch({
1775
- effects: window.__cm6.StateEffect.appendConfig.of(EV.editable.of(mode !== "view"))
1776
- });
1777
- view.dispatch({
1778
- effects: window.__cm6.StateEffect.appendConfig.of([])
1779
- });
1780
- },
1781
- getMode() {
1782
- return currentMode;
1783
2053
  }
1784
2054
  };
1785
2055
  return instance;
@@ -1802,6 +2072,7 @@ export {
1802
2072
  expandTextPlugin,
1803
2073
  createEditor,
1804
2074
  closeBracketsPlugin,
2075
+ calloutPlugin,
1805
2076
  baseExtensionsPlugin,
1806
2077
  autoLoad,
1807
2078
  attachmentPlugin
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Callout 插件
3
+ *
4
+ * 纯文本 Callout Live Preview:行内装饰、类型颜色、圆角背景、hover 描边。
5
+ * 替代 Obsidian 原生的 block widget 渲染。
6
+ */
7
+ import type { EditorPlugin } from '../types.js';
8
+ export declare const calloutPlugin: EditorPlugin;
@@ -14,6 +14,7 @@ export { suggestPlugin } from './suggest.js';
14
14
  export { linkHandlerPlugin } from './link-handler.js';
15
15
  export { attachmentPlugin } from './attachment.js';
16
16
  export { tablePlugin } from './table.js';
17
+ export { calloutPlugin } from './callout.js';
17
18
  export { tableContinuationPlugin } from './table-continuation.js';
18
19
  export { themePlugin } from './theme.js';
19
20
  export { baseExtensionsPlugin } from './base-extensions.js';
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "@xlxz/markdown-editor",
3
- "version": "2.1.0",
3
+ "version": "2.2.1",
4
4
  "description": "Obsidian-quality Markdown live preview editor — microkernel + plugin architecture. Internal use.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
7
- "publishConfig": { "access": "public" },
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
8
10
  "exports": {
9
11
  ".": {
10
12
  "types": "./dist/index.d.ts",
@@ -20,7 +22,8 @@
20
22
  "vendor"
21
23
  ],
22
24
  "scripts": {
23
- "build": "bun build src/index.ts --outfile dist/index.js --format esm --target browser --external vue && bun build src/index.ts --outfile dist/index.cjs --format cjs --target browser --external vue && tsc -p tsconfig.json --emitDeclarationOnly --declaration && rm -f dist/kernel.d.ts dist/types.d.ts dist/defaults.d.ts dist/mocks.d.ts dist/auto-load.d.ts dist/plugin-defaults.d.ts"
25
+ "build": "bun build src/index.ts --outfile dist/index.js --format esm --target browser --external vue && bun build src/index.ts --outfile dist/index.cjs --format cjs --target browser --external vue && tsc -p tsconfig.json --emitDeclarationOnly --declaration && rm -f dist/kernel.d.ts dist/types.d.ts dist/defaults.d.ts dist/mocks.d.ts dist/auto-load.d.ts dist/plugin-defaults.d.ts",
26
+ "prepublishOnly": "cd ../.. && bun run changelog && cd packages/core && bun run build"
24
27
  },
25
28
  "peerDependencies": {
26
29
  "vue": "^3.5.0"
package/vendor/mock.js CHANGED
@@ -7,7 +7,7 @@ window.OBSIDIAN_DEFAULT_I18N = {};
7
7
  i18next.init({ fallbackLng: 'en', ns: ['app'], defaultNS: 'app', initImmediate: false, interpolation: { escapeValue: false } });
8
8
 
9
9
  // Load translation bundle asynchronously
10
- fetch('/i18n/en.json')
10
+ fetch((window.__assetBase || '') + '/i18n/en.json') // [PATCHED] use configurable base path
11
11
  .then(r => r.json())
12
12
  .then(data => {
13
13
  window.OBSIDIAN_DEFAULT_I18N = data;
@@ -42056,7 +42056,7 @@ window.require = function (m) {
42056
42056
  return new Promise(function (t, n) {
42057
42057
  var i = document.createElement("script");
42058
42058
  i.type = "text/javascript";
42059
- i.src = e;
42059
+ i.src = (window.__assetBase || '') + e; // [PATCHED] prepend configurable base path for sub-path deployments (e.g. GitHub Pages)
42060
42060
  i.addEventListener("load", function () {
42061
42061
  return t(i);
42062
42062
  });
@@ -91610,12 +91610,12 @@ ${a}`);
91610
91610
  if (Q) {
91611
91611
  var J;
91612
91612
  var ee = Q.split(" ");
91613
- if ((J = new Set(ee)).has("HyperMD-callout") && J.has("HyperMD-quote-1")) {
91613
+ (J = new Set(ee)); if (false /* callout widget disabled — using plain text extension */ && J.has("HyperMD-callout") && J.has("HyperMD-quote-1")) {
91614
91614
  Y(r);
91615
91615
  U = r;
91616
91616
  _ = a;
91617
91617
  }
91618
- if (J.has("HyperMD-quote") && U !== -1) {
91618
+ if (false /* callout widget disabled */ && J.has("HyperMD-quote") && U !== -1) {
91619
91619
  if (r - _ > 1) {
91620
91620
  Y(r);
91621
91621
  } else {