@xlxz/markdown-editor 2.0.0 → 2.2.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.
@@ -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
@@ -1254,7 +1255,7 @@ function createTableTheme(EditorView) {
1254
1255
  fontWeight: "600"
1255
1256
  },
1256
1257
  ".cm-table-separator": {
1257
- backgroundColor: "var(--table-separator-bg, rgba(80, 120, 200, 0.04))",
1258
+ backgroundColor: "transparent",
1258
1259
  color: "var(--text-faint)"
1259
1260
  },
1260
1261
  ".cm-table-active.cm-table-separator": {
@@ -1262,19 +1263,19 @@ function createTableTheme(EditorView) {
1262
1263
  color: "var(--text-muted)"
1263
1264
  },
1264
1265
  ".cm-table-row-even": {
1265
- backgroundColor: "var(--table-row-even-bg, rgba(80, 140, 220, 0.06))"
1266
+ backgroundColor: "transparent"
1266
1267
  },
1267
1268
  ".cm-table-row-odd": {
1268
- backgroundColor: "var(--table-row-odd-bg, rgba(80, 140, 220, 0.12))"
1269
+ backgroundColor: "var(--table-row-odd-bg, rgba(80, 140, 220, 0.08))"
1269
1270
  },
1270
1271
  ".cm-table-active.cm-table-header": {
1271
1272
  backgroundColor: "rgba(80, 120, 200, 0.10)"
1272
1273
  },
1273
1274
  ".cm-table-active.cm-table-row-even": {
1274
- backgroundColor: "rgba(80, 140, 220, 0.04)"
1275
+ backgroundColor: "transparent"
1275
1276
  },
1276
1277
  ".cm-table-active.cm-table-row-odd": {
1277
- backgroundColor: "rgba(80, 140, 220, 0.08)"
1278
+ backgroundColor: "rgba(80, 140, 220, 0.05)"
1278
1279
  },
1279
1280
  ".cm-table-copy-btn": {
1280
1281
  position: "absolute",
@@ -1405,6 +1406,293 @@ 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
+ builder.push(Decoration.replace({
1628
+ widget: new CalloutHeaderWidget(callout.type, label)
1629
+ }).range(line.from, line.to));
1630
+ }
1631
+ } else if (lineNum !== callout.firstLine && !isLineActive) {
1632
+ const text = line.text;
1633
+ const match = text.match(/^>\s?/);
1634
+ if (match) {
1635
+ const prefixEnd = line.from + match[0].length;
1636
+ builder.push(Decoration.mark({ class: "cm-callout-hide" }).range(line.from, prefixEnd));
1637
+ }
1638
+ }
1639
+ }
1640
+ }
1641
+ builder.sort((a, b) => a.from - b.from || (a.startSide || 0) - (b.startSide || 0));
1642
+ return Decoration.set(builder);
1643
+ }
1644
+ }, {
1645
+ decorations: (v) => v.decorations
1646
+ });
1647
+ const calloutTheme = createCalloutTheme(EditorView);
1648
+ return [calloutViewPlugin, calloutTheme];
1649
+ }
1650
+ function getCalloutIconName(type) {
1651
+ const icons = {
1652
+ note: "lucide-pencil",
1653
+ info: "lucide-info",
1654
+ tip: "lucide-flame",
1655
+ warning: "lucide-alert-triangle",
1656
+ danger: "lucide-zap",
1657
+ error: "lucide-zap",
1658
+ bug: "lucide-bug",
1659
+ example: "lucide-list",
1660
+ question: "help-circle",
1661
+ success: "lucide-check",
1662
+ abstract: "lucide-clipboard-list",
1663
+ todo: "lucide-check-circle-2",
1664
+ important: "lucide-flame",
1665
+ quote: "quote-glyph",
1666
+ fail: "lucide-x"
1667
+ };
1668
+ const aliases = {
1669
+ hint: "tip",
1670
+ caution: "warning",
1671
+ attention: "warning",
1672
+ failure: "fail",
1673
+ missing: "fail",
1674
+ check: "success",
1675
+ done: "success",
1676
+ help: "question",
1677
+ faq: "question",
1678
+ summary: "abstract",
1679
+ tldr: "abstract",
1680
+ cite: "quote"
1681
+ };
1682
+ const resolved = aliases[type] || type;
1683
+ return icons[resolved] || icons["note"];
1684
+ }
1685
+ function capitalizeFirst(str) {
1686
+ return str.charAt(0).toUpperCase() + str.slice(1);
1687
+ }
1688
+
1689
+ // src/plugins/callout.ts
1690
+ var calloutPlugin = {
1691
+ id: "callout",
1692
+ install(_ctx) {
1693
+ return createCalloutExtension();
1694
+ }
1695
+ };
1408
1696
  // src/plugins/table-continuation.ts
1409
1697
  function isEmptyRow(text) {
1410
1698
  if (!isTableLine(text))
@@ -1630,6 +1918,7 @@ function getDefaultPlugins(opts) {
1630
1918
  plugins.push(linkHandlerPlugin);
1631
1919
  plugins.push(attachmentPlugin);
1632
1920
  plugins.push(tablePlugin);
1921
+ plugins.push(calloutPlugin);
1633
1922
  plugins.push(tableContinuationPlugin);
1634
1923
  plugins.push(onChangePlugin);
1635
1924
  return plugins;
@@ -1800,7 +2089,18 @@ function createEditor(container, options = {}, backend = {}) {
1800
2089
  },
1801
2090
  use,
1802
2091
  unuse,
1803
- registerSuggest
2092
+ registerSuggest,
2093
+ setTheme(theme) {
2094
+ if (theme === "light") {
2095
+ container.classList.add("theme-light");
2096
+ container.classList.remove("theme-dark");
2097
+ } else {
2098
+ container.classList.add("theme-dark");
2099
+ container.classList.remove("theme-light");
2100
+ }
2101
+ document.body.classList.toggle("theme-dark", theme === "dark");
2102
+ document.body.classList.toggle("theme-light", theme === "light");
2103
+ }
1804
2104
  };
1805
2105
  return instance;
1806
2106
  }
package/dist/index.d.ts CHANGED
@@ -19,5 +19,5 @@
19
19
  */
20
20
  export { createEditor, autoLoad } from './kernel.js';
21
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, tableContinuationPlugin, themePlugin, baseExtensionsPlugin, keymapPlugin, onChangePlugin, } from './plugins/index.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
@@ -1200,7 +1200,7 @@ function createTableTheme(EditorView) {
1200
1200
  fontWeight: "600"
1201
1201
  },
1202
1202
  ".cm-table-separator": {
1203
- backgroundColor: "var(--table-separator-bg, rgba(80, 120, 200, 0.04))",
1203
+ backgroundColor: "transparent",
1204
1204
  color: "var(--text-faint)"
1205
1205
  },
1206
1206
  ".cm-table-active.cm-table-separator": {
@@ -1208,19 +1208,19 @@ function createTableTheme(EditorView) {
1208
1208
  color: "var(--text-muted)"
1209
1209
  },
1210
1210
  ".cm-table-row-even": {
1211
- backgroundColor: "var(--table-row-even-bg, rgba(80, 140, 220, 0.06))"
1211
+ backgroundColor: "transparent"
1212
1212
  },
1213
1213
  ".cm-table-row-odd": {
1214
- backgroundColor: "var(--table-row-odd-bg, rgba(80, 140, 220, 0.12))"
1214
+ backgroundColor: "var(--table-row-odd-bg, rgba(80, 140, 220, 0.08))"
1215
1215
  },
1216
1216
  ".cm-table-active.cm-table-header": {
1217
1217
  backgroundColor: "rgba(80, 120, 200, 0.10)"
1218
1218
  },
1219
1219
  ".cm-table-active.cm-table-row-even": {
1220
- backgroundColor: "rgba(80, 140, 220, 0.04)"
1220
+ backgroundColor: "transparent"
1221
1221
  },
1222
1222
  ".cm-table-active.cm-table-row-odd": {
1223
- backgroundColor: "rgba(80, 140, 220, 0.08)"
1223
+ backgroundColor: "rgba(80, 140, 220, 0.05)"
1224
1224
  },
1225
1225
  ".cm-table-copy-btn": {
1226
1226
  position: "absolute",
@@ -1351,6 +1351,293 @@ 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
+ builder.push(Decoration.replace({
1573
+ widget: new CalloutHeaderWidget(callout.type, label)
1574
+ }).range(line.from, line.to));
1575
+ }
1576
+ } else if (lineNum !== callout.firstLine && !isLineActive) {
1577
+ const text = line.text;
1578
+ const match = text.match(/^>\s?/);
1579
+ if (match) {
1580
+ const prefixEnd = line.from + match[0].length;
1581
+ builder.push(Decoration.mark({ class: "cm-callout-hide" }).range(line.from, prefixEnd));
1582
+ }
1583
+ }
1584
+ }
1585
+ }
1586
+ builder.sort((a, b) => a.from - b.from || (a.startSide || 0) - (b.startSide || 0));
1587
+ return Decoration.set(builder);
1588
+ }
1589
+ }, {
1590
+ decorations: (v) => v.decorations
1591
+ });
1592
+ const calloutTheme = createCalloutTheme(EditorView);
1593
+ return [calloutViewPlugin, calloutTheme];
1594
+ }
1595
+ function getCalloutIconName(type) {
1596
+ const icons = {
1597
+ note: "lucide-pencil",
1598
+ info: "lucide-info",
1599
+ tip: "lucide-flame",
1600
+ warning: "lucide-alert-triangle",
1601
+ danger: "lucide-zap",
1602
+ error: "lucide-zap",
1603
+ bug: "lucide-bug",
1604
+ example: "lucide-list",
1605
+ question: "help-circle",
1606
+ success: "lucide-check",
1607
+ abstract: "lucide-clipboard-list",
1608
+ todo: "lucide-check-circle-2",
1609
+ important: "lucide-flame",
1610
+ quote: "quote-glyph",
1611
+ fail: "lucide-x"
1612
+ };
1613
+ const aliases = {
1614
+ hint: "tip",
1615
+ caution: "warning",
1616
+ attention: "warning",
1617
+ failure: "fail",
1618
+ missing: "fail",
1619
+ check: "success",
1620
+ done: "success",
1621
+ help: "question",
1622
+ faq: "question",
1623
+ summary: "abstract",
1624
+ tldr: "abstract",
1625
+ cite: "quote"
1626
+ };
1627
+ const resolved = aliases[type] || type;
1628
+ return icons[resolved] || icons["note"];
1629
+ }
1630
+ function capitalizeFirst(str) {
1631
+ return str.charAt(0).toUpperCase() + str.slice(1);
1632
+ }
1633
+
1634
+ // src/plugins/callout.ts
1635
+ var calloutPlugin = {
1636
+ id: "callout",
1637
+ install(_ctx) {
1638
+ return createCalloutExtension();
1639
+ }
1640
+ };
1354
1641
  // src/plugins/table-continuation.ts
1355
1642
  function isEmptyRow(text) {
1356
1643
  if (!isTableLine(text))
@@ -1576,6 +1863,7 @@ function getDefaultPlugins(opts) {
1576
1863
  plugins.push(linkHandlerPlugin);
1577
1864
  plugins.push(attachmentPlugin);
1578
1865
  plugins.push(tablePlugin);
1866
+ plugins.push(calloutPlugin);
1579
1867
  plugins.push(tableContinuationPlugin);
1580
1868
  plugins.push(onChangePlugin);
1581
1869
  return plugins;
@@ -1746,7 +2034,18 @@ function createEditor(container, options = {}, backend = {}) {
1746
2034
  },
1747
2035
  use,
1748
2036
  unuse,
1749
- registerSuggest
2037
+ registerSuggest,
2038
+ setTheme(theme) {
2039
+ if (theme === "light") {
2040
+ container.classList.add("theme-light");
2041
+ container.classList.remove("theme-dark");
2042
+ } else {
2043
+ container.classList.add("theme-dark");
2044
+ container.classList.remove("theme-light");
2045
+ }
2046
+ document.body.classList.toggle("theme-dark", theme === "dark");
2047
+ document.body.classList.toggle("theme-light", theme === "light");
2048
+ }
1750
2049
  };
1751
2050
  return instance;
1752
2051
  }
@@ -1768,6 +2067,7 @@ export {
1768
2067
  expandTextPlugin,
1769
2068
  createEditor,
1770
2069
  closeBracketsPlugin,
2070
+ calloutPlugin,
1771
2071
  baseExtensionsPlugin,
1772
2072
  autoLoad,
1773
2073
  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.0.0",
3
+ "version": "2.2.0",
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 {