@sendbird/actionbook-core 0.8.1 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui/index.js CHANGED
@@ -32,12 +32,9 @@ var actionbookSchema = new Schema({
32
32
  bulletList: {
33
33
  content: "listItem+",
34
34
  group: "block",
35
- parseDOM: [
36
- { tag: "div.ab-bullet-list" },
37
- { tag: "ul" }
38
- ],
35
+ parseDOM: [{ tag: "ul" }],
39
36
  toDOM() {
40
- return ["div", { class: "ab-bullet-list", role: "list" }, 0];
37
+ return ["ul", 0];
41
38
  }
42
39
  },
43
40
  orderedList: {
@@ -45,12 +42,6 @@ var actionbookSchema = new Schema({
45
42
  group: "block",
46
43
  attrs: { start: { default: 1 } },
47
44
  parseDOM: [
48
- {
49
- tag: "div.ab-ordered-list",
50
- getAttrs(dom) {
51
- return { start: Number(dom.dataset.start) || 1 };
52
- }
53
- },
54
45
  {
55
46
  tag: "ol",
56
47
  getAttrs(dom) {
@@ -59,25 +50,13 @@ var actionbookSchema = new Schema({
59
50
  }
60
51
  ],
61
52
  toDOM(node) {
62
- const attrs = { class: "ab-ordered-list", role: "list" };
63
- if (node.attrs.start !== 1) attrs["data-start"] = String(node.attrs.start);
64
- return ["div", attrs, 0];
53
+ return node.attrs.start === 1 ? ["ol", 0] : ["ol", { start: node.attrs.start }, 0];
65
54
  }
66
55
  },
67
56
  listItem: {
68
57
  content: "block+",
69
58
  attrs: { checked: { default: null } },
70
59
  parseDOM: [
71
- {
72
- tag: "div.ab-list-item",
73
- getAttrs(dom) {
74
- const el = dom;
75
- if (el.dataset.checked != null) {
76
- return { checked: el.dataset.checked === "true" };
77
- }
78
- return { checked: null };
79
- }
80
- },
81
60
  {
82
61
  tag: "li",
83
62
  getAttrs(dom) {
@@ -94,12 +73,10 @@ var actionbookSchema = new Schema({
94
73
  }
95
74
  ],
96
75
  toDOM(node) {
97
- const attrs = { class: "ab-list-item", role: "listitem" };
98
76
  if (node.attrs.checked != null) {
99
- attrs.class = "ab-list-item todo-item";
100
- attrs["data-checked"] = String(node.attrs.checked);
77
+ return ["li", { class: "todo-item", "data-checked": String(node.attrs.checked) }, 0];
101
78
  }
102
- return ["div", attrs, 0];
79
+ return ["li", 0];
103
80
  },
104
81
  defining: true
105
82
  },
@@ -119,22 +96,25 @@ var actionbookSchema = new Schema({
119
96
  defining: true,
120
97
  marks: "",
121
98
  attrs: { language: { default: null } },
122
- parseDOM: [
123
- {
124
- tag: "pre",
125
- preserveWhitespace: "full",
126
- getAttrs(dom) {
127
- const code3 = dom.querySelector("code");
128
- return { language: code3?.getAttribute("data-language") || null };
129
- }
99
+ parseDOM: [{
100
+ tag: "pre",
101
+ preserveWhitespace: "full",
102
+ getAttrs(dom) {
103
+ return { language: dom.getAttribute("data-language") || null };
130
104
  }
131
- ],
105
+ }],
132
106
  toDOM(node) {
133
- const attrs = {};
134
- if (node.attrs.language) attrs["data-language"] = node.attrs.language;
135
- return ["pre", ["code", attrs, 0]];
107
+ return ["pre", { "data-language": node.attrs.language || void 0 }, ["code", 0]];
108
+ }
109
+ },
110
+ horizontalRule: {
111
+ group: "block",
112
+ parseDOM: [{ tag: "hr" }],
113
+ toDOM() {
114
+ return ["hr"];
136
115
  }
137
116
  },
117
+ // ── Jinja conditional block ──
138
118
  jinjaIfBlock: {
139
119
  content: "jinjaIfBranch+",
140
120
  group: "block",
@@ -151,6 +131,7 @@ var actionbookSchema = new Schema({
151
131
  defining: true,
152
132
  attrs: {
153
133
  branchType: { default: "if" },
134
+ // 'if' | 'elif' | 'else'
154
135
  condition: { default: "" }
155
136
  },
156
137
  parseDOM: [
@@ -178,11 +159,14 @@ var actionbookSchema = new Schema({
178
159
  ];
179
160
  }
180
161
  },
181
- horizontalRule: {
162
+ noteBlock: {
163
+ content: "block+",
182
164
  group: "block",
183
- parseDOM: [{ tag: "hr" }],
165
+ defining: true,
166
+ isolating: true,
167
+ parseDOM: [{ tag: "div[data-note-block]" }],
184
168
  toDOM() {
185
- return ["hr"];
169
+ return ["div", { "data-note-block": "", class: "ab-note-block" }, 0];
186
170
  }
187
171
  },
188
172
  // ── Table nodes ──
@@ -472,6 +456,205 @@ function createPluginArray(plugins) {
472
456
  // src/ui/components/ActionbookRenderer.tsx
473
457
  import React from "react";
474
458
 
459
+ // src/jinja/conditionHighlighter.ts
460
+ var KEYWORDS = {
461
+ and: "AND",
462
+ or: "OR",
463
+ not: "NOT",
464
+ in: "IN",
465
+ is: "IS",
466
+ True: "BOOL",
467
+ False: "BOOL",
468
+ true: "BOOL",
469
+ false: "BOOL",
470
+ None: "NONE",
471
+ null: "NONE"
472
+ };
473
+ var CATEGORY_BY_TYPE = {
474
+ STRING: "value",
475
+ NUMBER: "value",
476
+ BOOL: "value",
477
+ NONE: "value",
478
+ IDENT: "variable",
479
+ AND: "operator",
480
+ OR: "operator",
481
+ NOT: "operator",
482
+ IN: "operator",
483
+ IS: "operator",
484
+ EQ: "operator",
485
+ NEQ: "operator",
486
+ LT: "operator",
487
+ GT: "operator",
488
+ LTE: "operator",
489
+ GTE: "operator",
490
+ LPAREN: "punctuation",
491
+ RPAREN: "punctuation"
492
+ };
493
+ var NEGATIVE_NUMBER_PRECEDERS = /* @__PURE__ */ new Set([
494
+ "AND",
495
+ "OR",
496
+ "NOT",
497
+ "IN",
498
+ "IS",
499
+ "EQ",
500
+ "NEQ",
501
+ "LT",
502
+ "GT",
503
+ "LTE",
504
+ "GTE",
505
+ "LPAREN"
506
+ ]);
507
+ function canStartNegativeNumber(tokens) {
508
+ if (tokens.length === 0) {
509
+ return true;
510
+ }
511
+ const previous = tokens[tokens.length - 1];
512
+ return NEGATIVE_NUMBER_PRECEDERS.has(previous.type);
513
+ }
514
+ function finalizeTokens(input, rawTokens) {
515
+ const highlighted = [];
516
+ for (let i = 0; i < rawTokens.length; i++) {
517
+ const token = rawTokens[i];
518
+ const next = rawTokens[i + 1];
519
+ if (token.type === "IS" && next?.type === "NOT") {
520
+ highlighted.push({
521
+ text: input.slice(token.start, next.end),
522
+ start: token.start,
523
+ end: next.end,
524
+ category: "operator"
525
+ });
526
+ i++;
527
+ continue;
528
+ }
529
+ highlighted.push({
530
+ text: token.text,
531
+ start: token.start,
532
+ end: token.end,
533
+ category: CATEGORY_BY_TYPE[token.type]
534
+ });
535
+ }
536
+ return highlighted;
537
+ }
538
+ function tokenizeCondition(input) {
539
+ const rawTokens = [];
540
+ let i = 0;
541
+ while (i < input.length) {
542
+ const char = input[i];
543
+ if (/\s/.test(char)) {
544
+ i++;
545
+ continue;
546
+ }
547
+ if (char === '"' || char === "'") {
548
+ const start = i;
549
+ const quote = char;
550
+ i++;
551
+ while (i < input.length) {
552
+ if (input[i] === "\\" && i + 1 < input.length) {
553
+ i += 2;
554
+ continue;
555
+ }
556
+ if (input[i] === quote) {
557
+ i++;
558
+ rawTokens.push({
559
+ type: "STRING",
560
+ text: input.slice(start, i),
561
+ start,
562
+ end: i
563
+ });
564
+ break;
565
+ }
566
+ i++;
567
+ }
568
+ if (rawTokens[rawTokens.length - 1]?.start !== start) {
569
+ return finalizeTokens(input, rawTokens);
570
+ }
571
+ continue;
572
+ }
573
+ if (/[0-9]/.test(char) || char === "-" && i + 1 < input.length && /[0-9]/.test(input[i + 1]) && canStartNegativeNumber(rawTokens)) {
574
+ const start = i;
575
+ if (input[i] === "-") {
576
+ i++;
577
+ }
578
+ while (i < input.length && /[0-9]/.test(input[i])) {
579
+ i++;
580
+ }
581
+ if (i < input.length && input[i] === ".") {
582
+ i++;
583
+ while (i < input.length && /[0-9]/.test(input[i])) {
584
+ i++;
585
+ }
586
+ }
587
+ rawTokens.push({
588
+ type: "NUMBER",
589
+ text: input.slice(start, i),
590
+ start,
591
+ end: i
592
+ });
593
+ continue;
594
+ }
595
+ if (/[a-zA-Z_]/.test(char)) {
596
+ const start = i;
597
+ i++;
598
+ while (i < input.length && /[a-zA-Z0-9_.]/.test(input[i])) {
599
+ i++;
600
+ }
601
+ const text2 = input.slice(start, i);
602
+ rawTokens.push({
603
+ type: KEYWORDS[text2] ?? "IDENT",
604
+ text: text2,
605
+ start,
606
+ end: i
607
+ });
608
+ continue;
609
+ }
610
+ if (i + 1 < input.length) {
611
+ const twoChar = input.slice(i, i + 2);
612
+ if (twoChar === "==") {
613
+ rawTokens.push({ type: "EQ", text: twoChar, start: i, end: i + 2 });
614
+ i += 2;
615
+ continue;
616
+ }
617
+ if (twoChar === "!=") {
618
+ rawTokens.push({ type: "NEQ", text: twoChar, start: i, end: i + 2 });
619
+ i += 2;
620
+ continue;
621
+ }
622
+ if (twoChar === "<=") {
623
+ rawTokens.push({ type: "LTE", text: twoChar, start: i, end: i + 2 });
624
+ i += 2;
625
+ continue;
626
+ }
627
+ if (twoChar === ">=") {
628
+ rawTokens.push({ type: "GTE", text: twoChar, start: i, end: i + 2 });
629
+ i += 2;
630
+ continue;
631
+ }
632
+ }
633
+ if (char === "<") {
634
+ rawTokens.push({ type: "LT", text: char, start: i, end: i + 1 });
635
+ i++;
636
+ continue;
637
+ }
638
+ if (char === ">") {
639
+ rawTokens.push({ type: "GT", text: char, start: i, end: i + 1 });
640
+ i++;
641
+ continue;
642
+ }
643
+ if (char === "(") {
644
+ rawTokens.push({ type: "LPAREN", text: char, start: i, end: i + 1 });
645
+ i++;
646
+ continue;
647
+ }
648
+ if (char === ")") {
649
+ rawTokens.push({ type: "RPAREN", text: char, start: i, end: i + 1 });
650
+ i++;
651
+ continue;
652
+ }
653
+ return finalizeTokens(input, rawTokens);
654
+ }
655
+ return finalizeTokens(input, rawTokens);
656
+ }
657
+
475
658
  // src/ui/components/icons.tsx
476
659
  import { jsx, jsxs } from "react/jsx-runtime";
477
660
  function IconTool({ size = 16, fill = "currentColor", style }) {
@@ -578,6 +761,12 @@ function IconTimeDiff({ size = 16, fill = "currentColor", style }) {
578
761
  }
579
762
  );
580
763
  }
764
+ function IconStop({ size = 16, fill = "currentColor", style }) {
765
+ return /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 8 8", width: size, height: size, fill: "none", style, children: [
766
+ /* @__PURE__ */ jsx("path", { d: "M5.4375 3.375L2.0625 3.375V4.125L5.4375 4.125V3.375Z", fill }),
767
+ /* @__PURE__ */ jsx("path", { fillRule: "evenodd", clipRule: "evenodd", d: "M3.75 7.5C5.82107 7.5 7.5 5.82107 7.5 3.75C7.5 1.67893 5.82107 0 3.75 0C1.67893 0 0 1.67893 0 3.75C0 5.82107 1.67893 7.5 3.75 7.5ZM6.75 3.75C6.75 5.40685 5.40685 6.75 3.75 6.75C2.09315 6.75 0.75 5.40685 0.75 3.75C0.75 2.09315 2.09315 0.75 3.75 0.75C5.40685 0.75 6.75 2.09315 6.75 3.75Z", fill })
768
+ ] });
769
+ }
581
770
  function IconAnchor({ size = 16, fill = "currentColor", style }) {
582
771
  return /* @__PURE__ */ jsxs(
583
772
  "svg",
@@ -753,27 +942,193 @@ function renderInline(node, key2, renderNode) {
753
942
  function renderInlineContent(content, keyPrefix, renderNode) {
754
943
  return content.map((node, i) => renderInline(node, `${keyPrefix}-${i}`, renderNode));
755
944
  }
756
- function renderBlock(node, key2, renderNode) {
757
- const defaultRender = () => {
758
- switch (node.type) {
759
- case "paragraph":
760
- return /* @__PURE__ */ jsx2("p", { children: renderInlineContent(node.content, key2, renderNode) }, key2);
761
- case "heading": {
762
- const Tag = `h${node.level}`;
763
- return /* @__PURE__ */ jsx2(Tag, { children: renderInlineContent(node.content, key2, renderNode) }, key2);
764
- }
765
- case "bulletList":
766
- return /* @__PURE__ */ jsx2("ul", { children: node.content.map((item, i) => renderBlock(item, `${key2}-${i}`, renderNode)) }, key2);
767
- case "orderedList":
768
- return /* @__PURE__ */ jsx2("ol", { start: node.start !== 1 ? node.start : void 0, children: node.content.map((item, i) => renderBlock(item, `${key2}-${i}`, renderNode)) }, key2);
769
- case "listItem":
770
- if (node.checked != null) {
771
- return /* @__PURE__ */ jsxs2("li", { style: { display: "flex", alignItems: "flex-start", listStyle: "none" }, children: [
772
- /* @__PURE__ */ jsx2(
773
- "input",
774
- {
775
- type: "checkbox",
776
- checked: node.checked,
945
+ var JINJA_RENDERER_STYLE_ID = "ab-renderer-jinja-styles";
946
+ var JINJA_RENDERER_STYLES = `
947
+ .ab-r-jinja-block {
948
+ margin: 8px 0;
949
+ }
950
+
951
+ .ab-r-jinja-branch {
952
+ position: relative;
953
+ }
954
+
955
+ .ab-r-jinja-header {
956
+ display: flex;
957
+ align-items: center;
958
+ gap: 12px;
959
+ padding: 8px;
960
+ border: 1px solid #E0E0E0;
961
+ border-radius: 4px;
962
+ background: #FFFFFF;
963
+ }
964
+
965
+ .ab-r-jinja-badge {
966
+ display: inline-flex;
967
+ align-items: center;
968
+ justify-content: center;
969
+ height: 32px;
970
+ padding: 0 8px;
971
+ border-radius: 4px;
972
+ font-family: "Roboto Mono", monospace;
973
+ font-size: 13px;
974
+ font-weight: 700;
975
+ line-height: 20px;
976
+ letter-spacing: -0.3px;
977
+ white-space: nowrap;
978
+ flex-shrink: 0;
979
+ }
980
+
981
+ .ab-r-jinja-badge-if,
982
+ .ab-r-jinja-badge-elif {
983
+ background: #E7F1FF;
984
+ color: #0D0D0D;
985
+ }
986
+
987
+ .ab-r-jinja-badge-else {
988
+ background: #F7F7F7;
989
+ color: #424242;
990
+ }
991
+
992
+ .ab-r-jinja-condition {
993
+ flex: 1;
994
+ min-width: 0;
995
+ display: flex;
996
+ flex-wrap: wrap;
997
+ gap: 8px;
998
+ align-items: center;
999
+ font-family: "Roboto Mono", monospace;
1000
+ font-size: 13px;
1001
+ line-height: 20px;
1002
+ letter-spacing: -0.3px;
1003
+ color: #0D0D0D;
1004
+ }
1005
+
1006
+ .ab-r-jinja-otherwise {
1007
+ flex: 1;
1008
+ color: #858585;
1009
+ font-family: "Roboto Mono", monospace;
1010
+ font-size: 13px;
1011
+ line-height: 20px;
1012
+ letter-spacing: -0.3px;
1013
+ }
1014
+
1015
+ .ab-r-jinja-body {
1016
+ display: flex;
1017
+ gap: 12px;
1018
+ padding: 0 8px;
1019
+ min-height: 32px;
1020
+ }
1021
+
1022
+ .ab-r-jinja-divider-col {
1023
+ width: 32px;
1024
+ flex-shrink: 0;
1025
+ display: flex;
1026
+ align-items: stretch;
1027
+ justify-content: center;
1028
+ }
1029
+
1030
+ .ab-r-jinja-divider {
1031
+ width: 1px;
1032
+ background: #E0E0E0;
1033
+ }
1034
+
1035
+ .ab-r-jinja-branch-last .ab-r-jinja-divider-col {
1036
+ align-items: flex-start;
1037
+ padding-left: 16px;
1038
+ }
1039
+
1040
+ .ab-r-jinja-branch-last .ab-r-jinja-divider {
1041
+ width: 16px;
1042
+ height: 28px;
1043
+ background: none;
1044
+ border-left: 1px solid #E0E0E0;
1045
+ border-bottom: 1px solid #E0E0E0;
1046
+ border-bottom-left-radius: 8px;
1047
+ }
1048
+
1049
+ .ab-r-jinja-content {
1050
+ flex: 1;
1051
+ min-width: 0;
1052
+ padding: 12px 0;
1053
+ }
1054
+
1055
+ .ab-r-jinja-token-variable { color: #4141B2; }
1056
+ .ab-r-jinja-token-operator { color: #858585; }
1057
+ .ab-r-jinja-token-value { color: #0D0D0D; }
1058
+ .ab-r-jinja-token-punctuation { color: #858585; }
1059
+ `;
1060
+ var jinjaRendererStyleRefCount = 0;
1061
+ var jinjaRendererStyleEl = null;
1062
+ function ensureJinjaRendererStyles() {
1063
+ if (jinjaRendererStyleRefCount++ > 0) return;
1064
+ if (typeof document === "undefined") return;
1065
+ const existing = document.getElementById(JINJA_RENDERER_STYLE_ID);
1066
+ if (existing instanceof HTMLStyleElement) {
1067
+ jinjaRendererStyleEl = existing;
1068
+ return;
1069
+ }
1070
+ jinjaRendererStyleEl = document.createElement("style");
1071
+ jinjaRendererStyleEl.id = JINJA_RENDERER_STYLE_ID;
1072
+ jinjaRendererStyleEl.textContent = JINJA_RENDERER_STYLES;
1073
+ document.head.appendChild(jinjaRendererStyleEl);
1074
+ }
1075
+ function renderConditionTokens(condition) {
1076
+ const tokens = tokenizeCondition(condition);
1077
+ return tokens.map((token, i) => /* @__PURE__ */ jsx2("span", { className: `ab-r-jinja-token-${token.category}`, children: token.text }, `t-${i}-${token.start}`));
1078
+ }
1079
+ function renderJinjaBranch(branch, key2, isLast, renderNode) {
1080
+ const branchType = branch.branchType;
1081
+ const badgeLabel = branchType === "elif" ? "ELSE IF" : branchType.toUpperCase();
1082
+ return /* @__PURE__ */ jsxs2(
1083
+ "div",
1084
+ {
1085
+ className: `ab-r-jinja-branch${isLast ? " ab-r-jinja-branch-last" : ""}`,
1086
+ children: [
1087
+ /* @__PURE__ */ jsxs2("div", { className: "ab-r-jinja-header", children: [
1088
+ /* @__PURE__ */ jsx2("span", { className: `ab-r-jinja-badge ab-r-jinja-badge-${branchType}`, children: badgeLabel }),
1089
+ branchType === "else" ? /* @__PURE__ */ jsx2("span", { className: "ab-r-jinja-otherwise", children: "otherwise" }) : /* @__PURE__ */ jsx2("span", { className: "ab-r-jinja-condition", children: branch.condition ? renderConditionTokens(branch.condition) : null })
1090
+ ] }),
1091
+ /* @__PURE__ */ jsxs2("div", { className: "ab-r-jinja-body", children: [
1092
+ /* @__PURE__ */ jsx2("div", { className: "ab-r-jinja-divider-col", children: /* @__PURE__ */ jsx2("div", { className: "ab-r-jinja-divider" }) }),
1093
+ /* @__PURE__ */ jsx2("div", { className: "ab-r-jinja-content", children: branch.content.map((child, i) => renderBlock(child, `${key2}-c${i}`, renderNode)) })
1094
+ ] })
1095
+ ]
1096
+ },
1097
+ key2
1098
+ );
1099
+ }
1100
+ function renderJinjaIfBlock(node, key2, renderNode) {
1101
+ ensureJinjaRendererStyles();
1102
+ return /* @__PURE__ */ jsx2("div", { className: "ab-r-jinja-block", children: node.branches.map(
1103
+ (branch, i) => renderJinjaBranch(
1104
+ branch,
1105
+ `${key2}-b${i}`,
1106
+ i === node.branches.length - 1,
1107
+ renderNode
1108
+ )
1109
+ ) }, key2);
1110
+ }
1111
+ function renderBlock(node, key2, renderNode) {
1112
+ const defaultRender = () => {
1113
+ switch (node.type) {
1114
+ case "paragraph":
1115
+ return /* @__PURE__ */ jsx2("p", { children: renderInlineContent(node.content, key2, renderNode) }, key2);
1116
+ case "heading": {
1117
+ const Tag = `h${node.level}`;
1118
+ return /* @__PURE__ */ jsx2(Tag, { children: renderInlineContent(node.content, key2, renderNode) }, key2);
1119
+ }
1120
+ case "bulletList":
1121
+ return /* @__PURE__ */ jsx2("ul", { children: node.content.map((item, i) => renderBlock(item, `${key2}-${i}`, renderNode)) }, key2);
1122
+ case "orderedList":
1123
+ return /* @__PURE__ */ jsx2("ol", { start: node.start !== 1 ? node.start : void 0, children: node.content.map((item, i) => renderBlock(item, `${key2}-${i}`, renderNode)) }, key2);
1124
+ case "listItem":
1125
+ if (node.checked != null) {
1126
+ return /* @__PURE__ */ jsxs2("li", { style: { display: "flex", alignItems: "flex-start", listStyle: "none" }, children: [
1127
+ /* @__PURE__ */ jsx2(
1128
+ "input",
1129
+ {
1130
+ type: "checkbox",
1131
+ checked: node.checked,
777
1132
  readOnly: true,
778
1133
  style: { marginRight: 6, marginTop: 4, flexShrink: 0 }
779
1134
  }
@@ -784,6 +1139,8 @@ function renderBlock(node, key2, renderNode) {
784
1139
  return /* @__PURE__ */ jsx2("li", { children: node.content.map((child, i) => renderBlock(child, `${key2}-${i}`, renderNode)) }, key2);
785
1140
  case "blockquote":
786
1141
  return /* @__PURE__ */ jsx2("blockquote", { children: node.content.map((child, i) => renderBlock(child, `${key2}-${i}`, renderNode)) }, key2);
1142
+ case "jinjaIfBlock":
1143
+ return renderJinjaIfBlock(node, key2, renderNode);
787
1144
  case "horizontalRule":
788
1145
  return /* @__PURE__ */ jsx2("hr", {}, key2);
789
1146
  case "table":
@@ -816,21 +1173,24 @@ var ReactNodeViewImpl = class {
816
1173
  dom;
817
1174
  root;
818
1175
  component;
1176
+ wrapper;
819
1177
  _node;
820
1178
  _view;
821
1179
  _getPos;
822
1180
  _selected = false;
823
1181
  _decorations = [];
824
- constructor(node, view, getPos, options) {
1182
+ constructor(node, view, getPos, decorations, options) {
825
1183
  this._node = node;
826
1184
  this._view = view;
827
1185
  this._getPos = getPos;
1186
+ this._decorations = decorations;
828
1187
  this.component = options.component;
1188
+ this.wrapper = options.wrapper;
829
1189
  const tag = options.as ?? (options.inline !== false ? "span" : "div");
830
1190
  this.dom = document.createElement(tag);
831
1191
  if (options.inline !== false) {
832
- this.dom.style.display = "inline-flex";
833
- this.dom.style.verticalAlign = "middle";
1192
+ this.dom.style.display = "inline";
1193
+ this.dom.style.verticalAlign = "baseline";
834
1194
  }
835
1195
  if (options.className) {
836
1196
  this.dom.className = options.className;
@@ -839,14 +1199,15 @@ var ReactNodeViewImpl = class {
839
1199
  this.render();
840
1200
  }
841
1201
  render() {
1202
+ const element = createElement(this.component, {
1203
+ node: this._node,
1204
+ view: this._view,
1205
+ getPos: this._getPos,
1206
+ selected: this._selected,
1207
+ decorations: this._decorations
1208
+ });
842
1209
  this.root.render(
843
- createElement(this.component, {
844
- node: this._node,
845
- view: this._view,
846
- getPos: this._getPos,
847
- selected: this._selected,
848
- decorations: this._decorations
849
- })
1210
+ this.wrapper ? createElement(this.wrapper, null, element) : element
850
1211
  );
851
1212
  }
852
1213
  update(node, decorations, _innerDecorations) {
@@ -875,7 +1236,7 @@ var ReactNodeViewImpl = class {
875
1236
  }
876
1237
  };
877
1238
  function createReactNodeView(options) {
878
- return (node, view, getPos) => new ReactNodeViewImpl(node, view, getPos, options);
1239
+ return (node, view, getPos, decorations) => new ReactNodeViewImpl(node, view, getPos, decorations, options);
879
1240
  }
880
1241
 
881
1242
  // src/ui/hooks/useEditorView.ts
@@ -884,9 +1245,19 @@ import { EditorState } from "prosemirror-state";
884
1245
  import { EditorView } from "prosemirror-view";
885
1246
  import { Node as PMNode } from "prosemirror-model";
886
1247
 
1248
+ // src/ast/types.ts
1249
+ var RESOURCE_TAG_TYPES = ["tool", "manual", "agent_message_template", "handoff", "time_diff", "time_difference"];
1250
+
887
1251
  // src/compat/prosemirror.ts
888
1252
  var MAX_DEPTH = 128;
889
1253
  var ALLOWED_URL_PROTOCOLS = /^(https?:|mailto:|tel:|#|\/)/i;
1254
+ var LIST_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList"]);
1255
+ function isLooseList(items) {
1256
+ return items.some((li) => {
1257
+ const nonListBlocks = li.content.filter((b) => !LIST_TYPES.has(b.type));
1258
+ return nonListBlocks.length > 1;
1259
+ });
1260
+ }
890
1261
  function convertPMMark(mark) {
891
1262
  switch (mark.type) {
892
1263
  case "bold":
@@ -925,12 +1296,17 @@ function convertPMInline(node, depth) {
925
1296
  }
926
1297
  case "inlineToolTag": {
927
1298
  const attrs = node.attrs ?? {};
1299
+ const tagType = attrs.type ?? "tool";
1300
+ const text2 = attrs.text ?? "";
1301
+ if (!RESOURCE_TAG_TYPES.includes(tagType)) {
1302
+ return text2 ? [{ type: "text", text: text2 }] : [];
1303
+ }
928
1304
  return [
929
1305
  {
930
1306
  type: "resourceTag",
931
- tagType: attrs.type ?? "tool",
1307
+ tagType,
932
1308
  resourceId: attrs.resourceId ?? "",
933
- text: attrs.text ?? ""
1309
+ text: text2
934
1310
  }
935
1311
  ];
936
1312
  }
@@ -961,20 +1337,27 @@ function convertPMBlock(node, depth = 0) {
961
1337
  const level = node.attrs?.level ?? 1;
962
1338
  return [{ type: "heading", level, content: children.flatMap((c) => convertPMInline(c, depth + 1)) }];
963
1339
  }
964
- case "bulletList":
1340
+ case "bulletList": {
1341
+ const items = children.map((c) => convertPMListItem(c, depth + 1));
1342
+ const spread = isLooseList(items);
965
1343
  return [
966
1344
  {
967
1345
  type: "bulletList",
968
- content: children.map((c) => convertPMListItem(c, depth + 1))
1346
+ ...spread ? { spread: true } : {},
1347
+ content: items.map((li) => spread ? { ...li, spread: true } : li)
969
1348
  }
970
1349
  ];
1350
+ }
971
1351
  case "orderedList": {
972
1352
  const start = node.attrs?.start ?? 1;
1353
+ const items = children.map((c) => convertPMListItem(c, depth + 1));
1354
+ const spread = isLooseList(items);
973
1355
  return [
974
1356
  {
975
1357
  type: "orderedList",
976
1358
  start,
977
- content: children.map((c) => convertPMListItem(c, depth + 1))
1359
+ ...spread ? { spread: true } : {},
1360
+ content: items.map((li) => spread ? { ...li, spread: true } : li)
978
1361
  }
979
1362
  ];
980
1363
  }
@@ -985,6 +1368,12 @@ function convertPMBlock(node, depth = 0) {
985
1368
  return [{ type: "blockquote", content: children.flatMap((c) => convertPMBlock(c, depth + 1)) }];
986
1369
  case "horizontalRule":
987
1370
  return [{ type: "horizontalRule" }];
1371
+ case "codeBlock": {
1372
+ const textContent2 = children.map((c) => c.text ?? "").join("");
1373
+ return [{ type: "codeBlock", content: textContent2, ...node.attrs?.language ? { language: node.attrs.language } : {} }];
1374
+ }
1375
+ case "noteBlock":
1376
+ return [{ type: "noteBlock", content: children.flatMap((c) => convertPMBlock(c, depth + 1)) }];
988
1377
  case "table": {
989
1378
  const rows = children.map((rowNode) => {
990
1379
  const cells = (rowNode.content ?? []).map((cellNode) => {
@@ -1001,6 +1390,22 @@ function convertPMBlock(node, depth = 0) {
1001
1390
  });
1002
1391
  return [{ type: "table", content: rows }];
1003
1392
  }
1393
+ case "jinjaIfBlock": {
1394
+ const branches = children.map((branchNode) => {
1395
+ const branchType = branchNode.attrs?.branchType ?? "if";
1396
+ const condition = branchNode.attrs?.condition;
1397
+ const branchContent = (branchNode.content ?? []).flatMap((c) => convertPMBlock(c, depth + 2));
1398
+ return {
1399
+ branchType,
1400
+ ...branchType !== "else" && condition ? { condition } : {},
1401
+ content: branchContent
1402
+ };
1403
+ });
1404
+ if (branches.length === 0) return [];
1405
+ return [{ type: "jinjaIfBlock", branches }];
1406
+ }
1407
+ case "jinjaIfBranch":
1408
+ return children.flatMap((c) => convertPMBlock(c, depth + 1));
1004
1409
  default:
1005
1410
  if (children.length > 0) {
1006
1411
  return children.flatMap((c) => convertPMBlock(c, depth + 1));
@@ -1012,11 +1417,11 @@ function convertPMListItem(node, depth = 0) {
1012
1417
  if (depth > MAX_DEPTH) return { type: "listItem", content: [{ type: "paragraph", content: [] }] };
1013
1418
  const children = node.content ?? [];
1014
1419
  const content = children.flatMap((c) => convertPMBlock(c, depth + 1));
1015
- const checked = node.attrs?.checked;
1016
1420
  const base = {
1017
1421
  type: "listItem",
1018
1422
  content: content.length > 0 ? content : [{ type: "paragraph", content: [] }]
1019
1423
  };
1424
+ const checked = node.attrs?.checked;
1020
1425
  if (typeof checked === "boolean") {
1021
1426
  base.checked = checked;
1022
1427
  }
@@ -1580,7 +1985,7 @@ function listItemWithTaskListItem(node, parent, state, info) {
1580
1985
 
1581
1986
  // src/markdown/plugins/resourceTag.ts
1582
1987
  var RESOURCE_TAG_RE = /\{\{([^:}]+):([^:}]*):([^}]+)\}\}/g;
1583
- var VALID_TYPES = /* @__PURE__ */ new Set(["tool", "manual", "agent_message_template", "handoff", "time_diff"]);
1988
+ var VALID_TYPES = /* @__PURE__ */ new Set(["tool", "manual", "agent_message_template", "handoff", "end_call", "time_diff"]);
1584
1989
  function splitTextWithResourceTags(text2) {
1585
1990
  const results = [];
1586
1991
  let lastIndex = 0;
@@ -1743,10 +2148,11 @@ function convertBlock(node, depth = 0) {
1743
2148
  case "list": {
1744
2149
  const list = node;
1745
2150
  const items = list.children.map((li) => convertListItem(li, depth + 1));
2151
+ const spread = list.spread ?? false;
1746
2152
  if (list.ordered) {
1747
- return [{ type: "orderedList", start: list.start ?? 1, content: items }];
2153
+ return [{ type: "orderedList", start: list.start ?? 1, spread, content: items }];
1748
2154
  }
1749
- return [{ type: "bulletList", content: items }];
2155
+ return [{ type: "bulletList", spread, content: items }];
1750
2156
  }
1751
2157
  case "blockquote": {
1752
2158
  const bq = node;
@@ -1755,6 +2161,10 @@ function convertBlock(node, depth = 0) {
1755
2161
  }
1756
2162
  case "thematicBreak":
1757
2163
  return [{ type: "horizontalRule" }];
2164
+ case "code": {
2165
+ const codeNode = node;
2166
+ return [{ type: "codeBlock", content: codeNode.value, ...codeNode.lang ? { language: codeNode.lang } : {} }];
2167
+ }
1758
2168
  case "jinjaIfBlockMdast": {
1759
2169
  const jNode = node;
1760
2170
  const branches = jNode.branches.map((b) => ({
@@ -1795,7 +2205,11 @@ function convertBlock(node, depth = 0) {
1795
2205
  function convertListItem(node, depth = 0) {
1796
2206
  if (depth > MAX_DEPTH2) throw new DepthError(depth);
1797
2207
  const content = node.children.flatMap((child) => convertBlock(child, depth + 1));
1798
- const base = { type: "listItem", content: content.length > 0 ? content : [{ type: "paragraph", content: [] }] };
2208
+ const base = {
2209
+ type: "listItem",
2210
+ spread: node.spread ?? false,
2211
+ content: content.length > 0 ? content : [{ type: "paragraph", content: [] }]
2212
+ };
1799
2213
  if (typeof node.checked === "boolean") {
1800
2214
  return { ...base, checked: node.checked };
1801
2215
  }
@@ -1894,31 +2308,39 @@ function blockToMdast(node, depth = 0) {
1894
2308
  return [{ type: "paragraph", children: node.content.flatMap((c) => inlineToMdast(c, depth + 1)) }];
1895
2309
  case "heading":
1896
2310
  return [{ type: "heading", depth: node.level, children: node.content.flatMap((c) => inlineToMdast(c, depth + 1)) }];
1897
- case "bulletList":
2311
+ case "bulletList": {
2312
+ const items = node.content.map((li) => listItemToMdast(li, depth + 1));
1898
2313
  return [
1899
2314
  {
1900
2315
  type: "list",
1901
2316
  ordered: false,
1902
- spread: false,
1903
- children: node.content.map((li) => listItemToMdast(li, depth + 1))
2317
+ spread: node.spread ?? false,
2318
+ children: items
1904
2319
  }
1905
2320
  ];
1906
- case "orderedList":
2321
+ }
2322
+ case "orderedList": {
2323
+ const items = node.content.map((li) => listItemToMdast(li, depth + 1));
1907
2324
  return [
1908
2325
  {
1909
2326
  type: "list",
1910
2327
  ordered: true,
1911
2328
  start: node.start,
1912
- spread: false,
1913
- children: node.content.map((li) => listItemToMdast(li, depth + 1))
2329
+ spread: node.spread ?? false,
2330
+ children: items
1914
2331
  }
1915
2332
  ];
2333
+ }
1916
2334
  case "listItem":
1917
2335
  return listItemToMdast(node, depth).children.flatMap((child) => [child]);
1918
2336
  case "blockquote":
1919
2337
  return [{ type: "blockquote", children: node.content.flatMap((c) => blockToMdast(c, depth + 1)) }];
1920
2338
  case "horizontalRule":
1921
2339
  return [{ type: "thematicBreak" }];
2340
+ case "codeBlock": {
2341
+ const cb = node;
2342
+ return [{ type: "code", value: cb.content, ...cb.language ? { lang: cb.language } : {} }];
2343
+ }
1922
2344
  case "jinjaIfBlock": {
1923
2345
  const jNode = node;
1924
2346
  const result = [];
@@ -1942,14 +2364,18 @@ function blockToMdast(node, depth = 0) {
1942
2364
  }));
1943
2365
  return [{ type: "table", children: rows }];
1944
2366
  }
2367
+ case "noteBlock":
2368
+ return [];
1945
2369
  }
1946
2370
  }
1947
2371
  function listItemToMdast(node, depth = 0) {
1948
2372
  if (depth > MAX_DEPTH2) throw new DepthError(depth);
2373
+ const children = node.content.flatMap((child) => blockToMdast(child, depth + 1));
2374
+ const isSpread = node.spread ?? false;
1949
2375
  const result = {
1950
2376
  type: "listItem",
1951
- spread: false,
1952
- children: node.content.flatMap((child) => blockToMdast(child, depth + 1))
2377
+ spread: isSpread,
2378
+ children
1953
2379
  };
1954
2380
  if (typeof node.checked === "boolean") {
1955
2381
  result.checked = node.checked;
@@ -1982,12 +2408,20 @@ function textHandler(node, parent, state, info) {
1982
2408
  state.unsafe = originalUnsafe;
1983
2409
  return result;
1984
2410
  }
2411
+ function linkHandler(node, parent, state, info) {
2412
+ const childrenText = state.containerPhrasing(node, { ...info, before: "[", after: "]" });
2413
+ const url = node.url ?? "";
2414
+ const title = node.title;
2415
+ const titlePart = title ? ` "${title.replace(/"/g, '\\"')}"` : "";
2416
+ return `[${childrenText}](${url}${titlePart})`;
2417
+ }
1985
2418
  function serializeToMarkdown(doc2) {
1986
2419
  const mdastTree = toMdast(doc2);
1987
- const result = toMarkdown(mdastTree, {
2420
+ const raw = toMarkdown(mdastTree, {
1988
2421
  bullet: "-",
1989
2422
  rule: "-",
1990
2423
  listItemIndent: "one",
2424
+ incrementListMarker: true,
1991
2425
  emphasis: "*",
1992
2426
  strong: "*",
1993
2427
  resourceLink: true,
@@ -1999,11 +2433,13 @@ function serializeToMarkdown(doc2) {
1999
2433
  ],
2000
2434
  handlers: {
2001
2435
  text: textHandler,
2436
+ link: linkHandler,
2002
2437
  ...resourceTagToMarkdown().handlers,
2003
2438
  ...jumpPointToMarkdown().handlers
2004
2439
  },
2005
2440
  extensions: [gfmNoAutolinkToMarkdown()]
2006
2441
  });
2442
+ let result = raw.replace(/^(\s*)\\>/gm, "$1&gt;").replace(/(\S) *>/g, (match, pre) => `${pre} &gt;`).replace(/\\\[([^\]]*)\]/g, "\\[$1\\]").replace(/&#x20;/g, " ");
2007
2443
  return result;
2008
2444
  }
2009
2445
 
@@ -2087,20 +2523,23 @@ function convertInline2(node) {
2087
2523
  }
2088
2524
  function convertBlockToArray(node) {
2089
2525
  if (node.type === "jinjaIfBlock") {
2090
- const jNode = node;
2091
- const result = [];
2092
- for (const branch of jNode.branches) {
2093
- const tagText = branch.branchType === "else" ? "{% else %}" : `{% ${branch.branchType} ${branch.condition ?? ""} %}`.trim();
2094
- result.push({ type: "paragraph", content: [{ type: "text", text: tagText }] });
2095
- for (const child of branch.content) {
2096
- result.push(...convertBlockToArray(child));
2097
- }
2098
- }
2099
- result.push({ type: "paragraph", content: [{ type: "text", text: "{% endif %}" }] });
2100
- return result;
2526
+ return [convertJinjaIfBlock(node)];
2101
2527
  }
2102
2528
  return [convertBlock2(node)];
2103
2529
  }
2530
+ function convertJinjaIfBlock(node) {
2531
+ return {
2532
+ type: "jinjaIfBlock",
2533
+ content: node.branches.map((branch) => ({
2534
+ type: "jinjaIfBranch",
2535
+ attrs: {
2536
+ branchType: branch.branchType,
2537
+ condition: branch.condition ?? ""
2538
+ },
2539
+ content: branch.content.length > 0 ? branch.content.flatMap(convertBlockToArray) : [{ type: "paragraph", content: [] }]
2540
+ }))
2541
+ };
2542
+ }
2104
2543
  function convertBlock2(node) {
2105
2544
  switch (node.type) {
2106
2545
  case "paragraph":
@@ -2142,6 +2581,19 @@ function convertBlock2(node) {
2142
2581
  };
2143
2582
  case "horizontalRule":
2144
2583
  return { type: "horizontalRule" };
2584
+ case "codeBlock": {
2585
+ const cb = node;
2586
+ return {
2587
+ type: "codeBlock",
2588
+ attrs: { language: cb.language ?? null },
2589
+ content: cb.content ? [{ type: "text", text: cb.content }] : void 0
2590
+ };
2591
+ }
2592
+ case "noteBlock":
2593
+ return {
2594
+ type: "noteBlock",
2595
+ content: node.content.flatMap(convertBlockToArray)
2596
+ };
2145
2597
  case "table":
2146
2598
  return {
2147
2599
  type: "table",
@@ -2153,19 +2605,8 @@ function convertBlock2(node) {
2153
2605
  }))
2154
2606
  }))
2155
2607
  };
2156
- case "jinjaIfBlock": {
2157
- const jNode = node;
2158
- const result = [];
2159
- for (const branch of jNode.branches) {
2160
- const tagText = branch.branchType === "else" ? "{% else %}" : `{% ${branch.branchType} ${branch.condition ?? ""} %}`.trim();
2161
- result.push({ type: "paragraph", content: [{ type: "text", text: tagText }] });
2162
- for (const child of branch.content) {
2163
- result.push(...convertBlockToArray(child));
2164
- }
2165
- }
2166
- result.push({ type: "paragraph", content: [{ type: "text", text: "{% endif %}" }] });
2167
- return result[0] ?? { type: "paragraph", content: [] };
2168
- }
2608
+ case "jinjaIfBlock":
2609
+ return convertJinjaIfBlock(node);
2169
2610
  default:
2170
2611
  return assertNever(node);
2171
2612
  }
@@ -2197,16 +2638,30 @@ function pmDocToAST(pmDoc) {
2197
2638
  return fromProseMirrorJSON(json);
2198
2639
  }
2199
2640
  function useEditorView(config) {
2200
- const viewRef = useRef(null);
2201
2641
  const viewInstanceRef = useRef(null);
2642
+ const containerRef = useRef(null);
2202
2643
  const [, forceUpdate] = useState(0);
2203
2644
  const configRef = useRef(config);
2204
2645
  configRef.current = config;
2205
2646
  useEffect(() => {
2206
- const container = viewRef.current;
2207
- if (!container) return;
2208
- const { pmPlugins, nodeViews } = createPluginArray(config.plugins ?? []);
2209
- const state = docToState(config.content, pmPlugins);
2647
+ return () => {
2648
+ viewInstanceRef.current?.destroy();
2649
+ viewInstanceRef.current = null;
2650
+ };
2651
+ }, []);
2652
+ const viewRef = useCallback((container) => {
2653
+ if (container === containerRef.current) return;
2654
+ if (viewInstanceRef.current) {
2655
+ viewInstanceRef.current.destroy();
2656
+ viewInstanceRef.current = null;
2657
+ }
2658
+ containerRef.current = container;
2659
+ if (!container) {
2660
+ forceUpdate((n) => n + 1);
2661
+ return;
2662
+ }
2663
+ const { pmPlugins, nodeViews } = createPluginArray(configRef.current.plugins ?? []);
2664
+ const state = docToState(configRef.current.content, pmPlugins);
2210
2665
  const view = new EditorView(container, {
2211
2666
  state,
2212
2667
  nodeViews,
@@ -2223,10 +2678,6 @@ function useEditorView(config) {
2223
2678
  });
2224
2679
  viewInstanceRef.current = view;
2225
2680
  forceUpdate((n) => n + 1);
2226
- return () => {
2227
- view.destroy();
2228
- viewInstanceRef.current = null;
2229
- };
2230
2681
  }, []);
2231
2682
  const getAST = useCallback(() => {
2232
2683
  const view = viewInstanceRef.current;
@@ -2244,6 +2695,7 @@ function useEditorView(config) {
2244
2695
  const pmJSON = toProseMirrorJSON(doc2);
2245
2696
  const pmDoc = PMNode.fromJSON(actionbookSchema, pmJSON);
2246
2697
  const tr = view.state.tr.replaceWith(0, view.state.doc.content.size, pmDoc.content);
2698
+ tr.setMeta("addToHistory", false);
2247
2699
  view.dispatch(tr);
2248
2700
  }, []);
2249
2701
  const focus = useCallback(() => {
@@ -2253,9 +2705,11 @@ function useEditorView(config) {
2253
2705
  viewInstanceRef.current?.destroy();
2254
2706
  viewInstanceRef.current = null;
2255
2707
  }, []);
2708
+ const getView = useCallback(() => viewInstanceRef.current, []);
2256
2709
  return {
2257
2710
  viewRef,
2258
2711
  view: viewInstanceRef.current,
2712
+ getView,
2259
2713
  getAST,
2260
2714
  getMarkdown,
2261
2715
  setContent,
@@ -2264,20 +2718,16 @@ function useEditorView(config) {
2264
2718
  };
2265
2719
  }
2266
2720
 
2267
- // src/ui/plugin/historyPlugin.ts
2268
- import { history } from "prosemirror-history";
2269
- function createHistoryPlugin() {
2270
- return {
2271
- name: "history",
2272
- plugins: () => [history()]
2273
- };
2274
- }
2275
-
2276
2721
  // src/ui/plugin/inputRulesPlugin.ts
2277
- import { InputRule, wrappingInputRule } from "prosemirror-inputrules";
2722
+ import { InputRule, textblockTypeInputRule, wrappingInputRule } from "prosemirror-inputrules";
2723
+ import { TextSelection } from "prosemirror-state";
2724
+ var HEADING_RE = /^(#{1,6})\s$/;
2725
+ var BLOCKQUOTE_RE = /^\s*>\s$/;
2726
+ var CODE_BLOCK_RE = /^```([a-zA-Z0-9]*)$/;
2727
+ var HR_RE = /^([-*_])\1{2,}$/;
2278
2728
  var BULLET_LIST_RE = /^\s*([-*])\s$/;
2279
2729
  var ORDERED_LIST_RE = /^(\d+)\.\s$/;
2280
- var JUMP_POINT_RE2 = /\^([A-Za-z_][A-Za-z0-9_]*)\^$/;
2730
+ var JUMP_POINT_RE2 = /\^([A-Za-z_]+)\^$/;
2281
2731
  var BOLD_STAR_RE = /(?:^|[^*])\*\*([^*]+)\*\*$/;
2282
2732
  var BOLD_UNDER_RE = /(?:^|[^_])__([^_]+)__$/;
2283
2733
  var ITALIC_STAR_RE = /(?:^|[^*])\*([^*]+)\*$/;
@@ -2288,11 +2738,14 @@ function markInputRule(pattern, markType, markerLen) {
2288
2738
  return new InputRule(pattern, (state, match, start, end) => {
2289
2739
  const textContent2 = match[1];
2290
2740
  const fullMatch = match[0];
2291
- const actualStart = end - fullMatch.length + (fullMatch.length - markerLen * 2 - textContent2.length);
2741
+ const markedLength = markerLen * 2 + textContent2.length;
2742
+ const prefixLen = fullMatch.length - markedLength;
2743
+ const deleteFrom = start + prefixLen;
2292
2744
  const tr = state.tr;
2293
- tr.delete(actualStart, end);
2745
+ tr.delete(deleteFrom, end);
2294
2746
  const textNode = state.schema.text(textContent2, [markType.create()]);
2295
- tr.insert(actualStart, textNode);
2747
+ tr.insert(deleteFrom, textNode);
2748
+ tr.removeStoredMark(markType);
2296
2749
  return tr;
2297
2750
  });
2298
2751
  }
@@ -2301,12 +2754,41 @@ function createInputRulesPlugin() {
2301
2754
  name: "inputRules",
2302
2755
  inputRules: () => {
2303
2756
  const rules = [];
2304
- rules.push(wrappingInputRule(BULLET_LIST_RE, actionbookSchema.nodes.bulletList));
2305
2757
  rules.push(
2306
- wrappingInputRule(ORDERED_LIST_RE, actionbookSchema.nodes.orderedList, (match) => ({
2307
- start: +match[1]
2758
+ textblockTypeInputRule(HEADING_RE, actionbookSchema.nodes.heading, (match) => ({
2759
+ level: match[1].length
2308
2760
  }))
2309
2761
  );
2762
+ rules.push(wrappingInputRule(BLOCKQUOTE_RE, actionbookSchema.nodes.blockquote));
2763
+ rules.push(
2764
+ new InputRule(CODE_BLOCK_RE, (state, match, start, end) => {
2765
+ const language = match[1] || null;
2766
+ const $start = state.doc.resolve(start);
2767
+ const blockStart = $start.before($start.depth);
2768
+ const blockEnd = $start.after($start.depth);
2769
+ const tr = state.tr;
2770
+ tr.replaceWith(
2771
+ blockStart,
2772
+ blockEnd,
2773
+ actionbookSchema.nodes.codeBlock.create({ language })
2774
+ );
2775
+ return tr;
2776
+ })
2777
+ );
2778
+ rules.push(
2779
+ new InputRule(HR_RE, (state, _match, start, end) => {
2780
+ const { horizontalRule: hrType, paragraph: pType } = actionbookSchema.nodes;
2781
+ const $start = state.doc.resolve(start);
2782
+ const blockStart = $start.before($start.depth);
2783
+ const blockEnd = $start.after($start.depth);
2784
+ const tr = state.tr;
2785
+ tr.replaceWith(blockStart, blockEnd, [hrType.create(), pType.create()]);
2786
+ tr.setSelection(
2787
+ TextSelection.near(tr.doc.resolve(blockStart + hrType.createAndFill().nodeSize + 1))
2788
+ );
2789
+ return tr;
2790
+ })
2791
+ );
2310
2792
  rules.push(
2311
2793
  new InputRule(/^\s*[-*]\s\[([ xX])\]\s$/, (state, match, start, end) => {
2312
2794
  const checked = match[1] !== " ";
@@ -2316,6 +2798,12 @@ function createInputRulesPlugin() {
2316
2798
  return state.tr.delete(start, end).insert(start, list);
2317
2799
  })
2318
2800
  );
2801
+ rules.push(wrappingInputRule(BULLET_LIST_RE, actionbookSchema.nodes.bulletList));
2802
+ rules.push(
2803
+ wrappingInputRule(ORDERED_LIST_RE, actionbookSchema.nodes.orderedList, (match) => ({
2804
+ start: +match[1]
2805
+ }))
2806
+ );
2319
2807
  const jumpPointType = actionbookSchema.nodes.jumpPoint;
2320
2808
  rules.push(
2321
2809
  new InputRule(JUMP_POINT_RE2, (state, match, start, end) => {
@@ -2342,24 +2830,85 @@ function createInputRulesPlugin() {
2342
2830
  };
2343
2831
  }
2344
2832
 
2833
+ // src/ui/plugin/historyPlugin.ts
2834
+ import { history, undo, redo } from "prosemirror-history";
2835
+ import { keymap as keymap2 } from "prosemirror-keymap";
2836
+ function createHistoryPlugin() {
2837
+ return {
2838
+ name: "history",
2839
+ plugins: () => [
2840
+ history(),
2841
+ keymap2({ "Mod-z": undo, "Mod-Shift-z": redo, "Mod-y": redo })
2842
+ ]
2843
+ };
2844
+ }
2845
+
2345
2846
  // src/ui/plugin/keymapPlugin.ts
2346
- import { chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock, toggleMark } from "prosemirror-commands";
2847
+ import { baseKeymap, chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock, toggleMark, setBlockType, joinBackward } from "prosemirror-commands";
2848
+ import { keymap as keymap3 } from "prosemirror-keymap";
2347
2849
  import { liftListItem, sinkListItem, splitListItem, wrapInList } from "prosemirror-schema-list";
2348
2850
  var TAB_CHAR = " ";
2349
- var { listItem, bulletList, orderedList, hardBreak } = actionbookSchema.nodes;
2851
+ var { listItem, bulletList, orderedList, hardBreak, heading, paragraph, horizontalRule, blockquote } = actionbookSchema.nodes;
2350
2852
  var { bold: boldMark, italic: italicMark, underline: underlineMark, strikethrough: strikethroughMark, code: codeMark } = actionbookSchema.marks;
2351
2853
  function cursorDirectlyInListItem(state) {
2352
2854
  const { $from } = state.selection;
2353
2855
  return $from.depth >= 2 && $from.node($from.depth - 1).type === listItem;
2354
2856
  }
2355
- var backspaceCommand = (state, dispatch) => {
2857
+ var backspaceAfterList = (state, dispatch) => {
2858
+ const { $from } = state.selection;
2859
+ if (!state.selection.empty) return false;
2860
+ if ($from.parentOffset !== 0) return false;
2861
+ if ($from.parent.type !== paragraph) return false;
2862
+ if ($from.parent.content.size !== 0) return false;
2863
+ const parentDepth = $from.depth - 1;
2864
+ if (parentDepth < 0) return false;
2865
+ const indexInParent = $from.index(parentDepth);
2866
+ if (indexInParent === 0) return false;
2867
+ const prevNode = $from.node(parentDepth).child(indexInParent - 1);
2868
+ const isList = prevNode.type === bulletList || prevNode.type === orderedList;
2869
+ if (!isList) return false;
2870
+ if (dispatch) {
2871
+ const from = $from.before($from.depth);
2872
+ const to = $from.after($from.depth);
2873
+ const tr = state.tr.delete(from, to);
2874
+ dispatch(tr.scrollIntoView());
2875
+ }
2876
+ return true;
2877
+ };
2878
+ var backspaceAfterLeafBlock = (state, dispatch) => {
2356
2879
  const { $from } = state.selection;
2357
2880
  if (!state.selection.empty) return false;
2358
2881
  if ($from.parentOffset !== 0) return false;
2359
- if (!cursorDirectlyInListItem(state)) return false;
2360
- if ($from.index($from.depth - 1) !== 0) return false;
2361
- return liftListItem(listItem)(state, dispatch);
2882
+ if ($from.parent.type !== paragraph) return false;
2883
+ if ($from.parent.content.size !== 0) return false;
2884
+ const parentDepth = $from.depth - 1;
2885
+ if (parentDepth < 0) return false;
2886
+ const indexInParent = $from.index(parentDepth);
2887
+ if (indexInParent === 0) return false;
2888
+ const prevNode = $from.node(parentDepth).child(indexInParent - 1);
2889
+ if (prevNode.type !== horizontalRule) return false;
2890
+ if (dispatch) {
2891
+ const hrPos = $from.posAtIndex(indexInParent - 1, parentDepth);
2892
+ dispatch(state.tr.delete(hrPos, hrPos + prevNode.nodeSize).scrollIntoView());
2893
+ }
2894
+ return true;
2362
2895
  };
2896
+ var backspaceCommand = chainCommands(
2897
+ backspaceAfterList,
2898
+ backspaceAfterLeafBlock,
2899
+ (state, dispatch) => {
2900
+ const { $from } = state.selection;
2901
+ if (!state.selection.empty) return false;
2902
+ if ($from.parentOffset !== 0) return false;
2903
+ if ($from.parent.type === heading) {
2904
+ return setBlockType(paragraph)(state, dispatch);
2905
+ }
2906
+ if (!cursorDirectlyInListItem(state)) return false;
2907
+ if ($from.index($from.depth - 1) !== 0) return false;
2908
+ return liftListItem(listItem)(state, dispatch);
2909
+ },
2910
+ joinBackward
2911
+ );
2363
2912
  var tabCommand = (state, dispatch) => {
2364
2913
  const { $from } = state.selection;
2365
2914
  if (cursorDirectlyInListItem(state) && $from.parentOffset === 0) {
@@ -2412,12 +2961,13 @@ function createKeymapPlugin() {
2412
2961
  "Mod-e": toggleMark(codeMark),
2413
2962
  "Mod-Shift-7": wrapInList(bulletList),
2414
2963
  "Mod-Shift-8": wrapInList(orderedList)
2415
- })
2964
+ }),
2965
+ plugins: () => [keymap3(baseKeymap)]
2416
2966
  };
2417
2967
  }
2418
2968
 
2419
2969
  // src/ui/plugin/markdownClipboard.ts
2420
- import { Fragment, Slice } from "prosemirror-model";
2970
+ import { DOMParser as ProseMirrorDOMParser, Fragment, Slice } from "prosemirror-model";
2421
2971
  import { Plugin, PluginKey } from "prosemirror-state";
2422
2972
 
2423
2973
  // src/ast/traverse.ts
@@ -2433,6 +2983,7 @@ function getChildren(node) {
2433
2983
  case "doc":
2434
2984
  case "blockquote":
2435
2985
  case "listItem":
2986
+ case "noteBlock":
2436
2987
  return node.content;
2437
2988
  case "paragraph":
2438
2989
  case "heading":
@@ -3904,6 +4455,23 @@ var gfmNoAutolinkFromMarkdown = () => [
3904
4455
  gfmTaskListItemFromMarkdown()
3905
4456
  ];
3906
4457
  var MAX_DEPTH5 = 128;
4458
+ var BRACKET_LINK_RE = /\[([^\]]+)\]\(([^)]*\s[^)]*)\)/g;
4459
+ function splitTextWithBracketLinks(value) {
4460
+ const results = [];
4461
+ let lastIndex = 0;
4462
+ for (const m of value.matchAll(BRACKET_LINK_RE)) {
4463
+ const idx = m.index;
4464
+ if (idx > lastIndex) results.push({ type: "text", value: value.slice(lastIndex, idx) });
4465
+ results.push({
4466
+ type: "link",
4467
+ url: m[2],
4468
+ children: [{ type: "text", value: m[1] }]
4469
+ });
4470
+ lastIndex = idx + m[0].length;
4471
+ }
4472
+ if (lastIndex < value.length) results.push({ type: "text", value: value.slice(lastIndex) });
4473
+ return results;
4474
+ }
3907
4475
  function combinedTokensFromMarkdown(node, depth = 0) {
3908
4476
  if (depth > MAX_DEPTH5) return;
3909
4477
  if (!node.children) return;
@@ -3915,16 +4483,23 @@ function combinedTokensFromMarkdown(node, depth = 0) {
3915
4483
  for (const child of node.children) {
3916
4484
  const c = child;
3917
4485
  if (c.type === "text") {
3918
- const afterResourceTags = splitTextWithResourceTags(child.value);
3919
- for (const part of afterResourceTags) {
3920
- if (part.type === "text") {
3921
- const afterJumpPoints = splitTextWithJumpPoints(part.value);
3922
- newChildren.push(...afterJumpPoints);
3923
- } else {
3924
- newChildren.push(part);
4486
+ const afterLinks = splitTextWithBracketLinks(child.value);
4487
+ for (const linkPart of afterLinks) {
4488
+ if (linkPart.type !== "text") {
4489
+ newChildren.push(linkPart);
4490
+ continue;
3925
4491
  }
3926
- }
3927
- } else {
4492
+ const afterResourceTags = splitTextWithResourceTags(linkPart.value);
4493
+ for (const part of afterResourceTags) {
4494
+ if (part.type === "text") {
4495
+ const afterJumpPoints = splitTextWithJumpPoints(part.value);
4496
+ newChildren.push(...afterJumpPoints);
4497
+ } else {
4498
+ newChildren.push(part);
4499
+ }
4500
+ }
4501
+ }
4502
+ } else {
3928
4503
  newChildren.push(child);
3929
4504
  }
3930
4505
  }
@@ -4128,8 +4703,198 @@ function analyzeJinjaBlocks(doc2) {
4128
4703
  });
4129
4704
  }
4130
4705
 
4706
+ // src/tree/documentTree.ts
4707
+ var MAX_DEPTH6 = 128;
4708
+ function extractInlineItems(content, blockIndex) {
4709
+ const items = [];
4710
+ for (const node of content) {
4711
+ if (node.type === "jumpPoint") {
4712
+ items.push({
4713
+ type: "jumpPoint",
4714
+ label: node.id,
4715
+ blockIndex,
4716
+ children: []
4717
+ });
4718
+ } else if (node.type === "resourceTag") {
4719
+ const isEndAction = node.tagType === "handoff";
4720
+ items.push({
4721
+ type: isEndAction ? "endAction" : "resourceTag",
4722
+ label: isEndAction ? `End ${node.text}` : node.text,
4723
+ blockIndex,
4724
+ children: [],
4725
+ meta: { tagType: node.tagType, resourceId: node.resourceId }
4726
+ });
4727
+ }
4728
+ }
4729
+ return items;
4730
+ }
4731
+ function extractBlockItems(blocks, startIndex, depth) {
4732
+ if (depth > MAX_DEPTH6) return [];
4733
+ const items = [];
4734
+ for (let i = 0; i < blocks.length; i++) {
4735
+ const block = blocks[i];
4736
+ const blockIndex = startIndex + i;
4737
+ switch (block.type) {
4738
+ case "heading": {
4739
+ const label = textContent(block) || `Heading ${block.level}`;
4740
+ const inlineItems = extractInlineItems(block.content, blockIndex);
4741
+ items.push({
4742
+ type: "heading",
4743
+ label,
4744
+ blockIndex,
4745
+ children: inlineItems,
4746
+ meta: { level: block.level }
4747
+ });
4748
+ break;
4749
+ }
4750
+ case "paragraph": {
4751
+ const inlineItems = extractInlineItems(block.content, blockIndex);
4752
+ items.push(...inlineItems);
4753
+ break;
4754
+ }
4755
+ case "jinjaIfBlock": {
4756
+ const branches = block.branches.map((branch) => {
4757
+ const branchLabel = branch.branchType === "else" ? "ELSE" : `${branch.branchType.toUpperCase()} ${branch.condition || ""}`.trim();
4758
+ const branchChildren = extractBlockItems(branch.content, blockIndex, depth + 1);
4759
+ return {
4760
+ type: "jinjaBranch",
4761
+ label: branchLabel,
4762
+ blockIndex,
4763
+ children: branchChildren
4764
+ };
4765
+ });
4766
+ items.push({
4767
+ type: "jinjaIf",
4768
+ label: "Jinja if",
4769
+ blockIndex,
4770
+ children: branches
4771
+ });
4772
+ break;
4773
+ }
4774
+ case "noteBlock": {
4775
+ const noteContent = textContent(block);
4776
+ items.push({
4777
+ type: "noteBlock",
4778
+ label: noteContent ? `Note: ${noteContent.slice(0, 30)}` : "Note",
4779
+ blockIndex,
4780
+ children: []
4781
+ });
4782
+ break;
4783
+ }
4784
+ case "bulletList":
4785
+ case "orderedList": {
4786
+ for (const li of block.content) {
4787
+ const childItems = extractBlockItems(li.content, blockIndex, depth + 1);
4788
+ items.push(...childItems);
4789
+ }
4790
+ break;
4791
+ }
4792
+ case "blockquote": {
4793
+ const childItems = extractBlockItems(block.content, blockIndex, depth + 1);
4794
+ items.push(...childItems);
4795
+ break;
4796
+ }
4797
+ default:
4798
+ break;
4799
+ }
4800
+ }
4801
+ return items;
4802
+ }
4803
+ function buildDocumentTree(doc2) {
4804
+ const items = extractBlockItems(doc2.content, 0, 0);
4805
+ try {
4806
+ const structures = analyzeJinjaBlocks(doc2);
4807
+ if (structures.length > 0) {
4808
+ const hasStructuredJinja = items.some((n) => n.type === "jinjaIf");
4809
+ if (!hasStructuredJinja) {
4810
+ for (const s of structures) {
4811
+ const branches = s.branches.map((b) => ({
4812
+ type: "jinjaBranch",
4813
+ label: b.type === "else" ? "ELSE" : `${b.type.toUpperCase()} ${b.condition || ""}`.trim(),
4814
+ blockIndex: b.tagBlockIndex,
4815
+ children: []
4816
+ }));
4817
+ const jinjaNode = {
4818
+ type: "jinjaIf",
4819
+ label: "Jinja if",
4820
+ blockIndex: s.ifTagBlockIndex,
4821
+ children: branches
4822
+ };
4823
+ let inserted = false;
4824
+ for (let i = 0; i < items.length; i++) {
4825
+ if (items[i].blockIndex > s.ifTagBlockIndex) {
4826
+ items.splice(i, 0, jinjaNode);
4827
+ inserted = true;
4828
+ break;
4829
+ }
4830
+ }
4831
+ if (!inserted) items.push(jinjaNode);
4832
+ }
4833
+ }
4834
+ }
4835
+ } catch {
4836
+ }
4837
+ return items;
4838
+ }
4839
+
4131
4840
  // src/ui/plugin/markdownClipboard.ts
4132
4841
  var key = new PluginKey("markdownClipboard");
4842
+ var MAX_PASTE_LIST_DEPTH = 3;
4843
+ var MAX_FLATTEN_DEPTH = 128;
4844
+ function flattenDeepLists(node, listDepth = 0, _recurseDepth = 0) {
4845
+ if (_recurseDepth > MAX_FLATTEN_DEPTH) return node;
4846
+ const isList = node.type.name === "bulletList" || node.type.name === "orderedList";
4847
+ const currentDepth = isList ? listDepth + 1 : listDepth;
4848
+ if (isList && currentDepth >= MAX_PASTE_LIST_DEPTH) {
4849
+ const flatItems = [];
4850
+ node.forEach((li) => {
4851
+ const nonListBlocks = [];
4852
+ li.forEach((block) => {
4853
+ if (block.type.name !== "bulletList" && block.type.name !== "orderedList") {
4854
+ nonListBlocks.push(block);
4855
+ }
4856
+ });
4857
+ if (nonListBlocks.length > 0) {
4858
+ flatItems.push(li.type.create(li.attrs, nonListBlocks));
4859
+ }
4860
+ });
4861
+ if (flatItems.length === 0) return node;
4862
+ return node.copy(Fragment.fromArray(flatItems));
4863
+ }
4864
+ const children = [];
4865
+ let changed = false;
4866
+ node.forEach((child) => {
4867
+ const flattened = flattenDeepLists(child, currentDepth, _recurseDepth + 1);
4868
+ if (flattened !== child) changed = true;
4869
+ children.push(flattened);
4870
+ });
4871
+ if (!changed) return node;
4872
+ return node.copy(Fragment.fromArray(children));
4873
+ }
4874
+ function htmlToSlice(html) {
4875
+ if (typeof document === "undefined") return null;
4876
+ try {
4877
+ const domParser = new DOMParser();
4878
+ const parsed = domParser.parseFromString(html, "text/html");
4879
+ const body = parsed.body;
4880
+ if (!body || !body.childNodes.length) return null;
4881
+ const pmParser = ProseMirrorDOMParser.fromSchema(actionbookSchema);
4882
+ const doc2 = pmParser.parse(body);
4883
+ const flattened = flattenDeepLists(doc2);
4884
+ const content = flattened.content;
4885
+ let hasContent = false;
4886
+ content.forEach((node) => {
4887
+ if (node.type.name !== "paragraph" || node.content.size > 0) {
4888
+ hasContent = true;
4889
+ }
4890
+ });
4891
+ if (!hasContent) return null;
4892
+ const openDepth = content.childCount === 1 && content.firstChild?.type.name === "paragraph" ? 1 : 0;
4893
+ return new Slice(content, openDepth, openDepth);
4894
+ } catch {
4895
+ return null;
4896
+ }
4897
+ }
4133
4898
  function textToSlice(text2, view) {
4134
4899
  try {
4135
4900
  const existingIds = /* @__PURE__ */ new Set();
@@ -4156,7 +4921,15 @@ function createPlugin() {
4156
4921
  key,
4157
4922
  props: {
4158
4923
  handlePaste(view, event) {
4924
+ const html = event.clipboardData?.getData("text/html");
4159
4925
  const text2 = event.clipboardData?.getData("text/plain");
4926
+ if (html) {
4927
+ const htmlSlice = htmlToSlice(html);
4928
+ if (htmlSlice) {
4929
+ view.dispatch(view.state.tr.replaceSelection(htmlSlice));
4930
+ return true;
4931
+ }
4932
+ }
4160
4933
  if (!text2) return false;
4161
4934
  const slice = textToSlice(text2, view);
4162
4935
  if (!slice) return false;
@@ -4172,9 +4945,14 @@ function createPlugin() {
4172
4945
  }
4173
4946
  return slice;
4174
4947
  },
4175
- // Discard pasted HTML always prefer plain-text markdown
4176
- transformPastedHTML() {
4177
- return "";
4948
+ // Transform pasted HTML: keep structure but flatten deep lists
4949
+ // to prevent Chrome contentEditable hanging
4950
+ transformPasted(slice) {
4951
+ const nodes = [];
4952
+ slice.content.forEach((node) => {
4953
+ nodes.push(flattenDeepLists(node));
4954
+ });
4955
+ return new Slice(Fragment.fromArray(nodes), slice.openStart, slice.openEnd);
4178
4956
  },
4179
4957
  clipboardTextSerializer(slice) {
4180
4958
  try {
@@ -4197,7 +4975,7 @@ function createMarkdownClipboardPlugin() {
4197
4975
  }
4198
4976
 
4199
4977
  // src/ui/plugin/jumpPointPlugin.ts
4200
- import { Plugin as Plugin2, PluginKey as PluginKey2, TextSelection } from "prosemirror-state";
4978
+ import { Plugin as Plugin2, PluginKey as PluginKey2, TextSelection as TextSelection2 } from "prosemirror-state";
4201
4979
  import { Decoration, DecorationSet } from "prosemirror-view";
4202
4980
  var adjacentKey = new PluginKey2("jumpPointAdjacent");
4203
4981
  var JUMP_POINT_ADJACENT_SPEC = { jumpPointAdjacent: true };
@@ -4268,8 +5046,15 @@ function createJumpPointEditPlugin() {
4268
5046
  }
4269
5047
  });
4270
5048
  }
4271
- function jumpPointBefore(state) {
5049
+ function jumpPointAtOrBefore(state) {
4272
5050
  const { selection } = state;
5051
+ if ("node" in selection) {
5052
+ const node = selection.node;
5053
+ if (node?.type?.name === "jumpPoint") {
5054
+ return { id: node.attrs.id, nodeStart: selection.from, nodeEnd: selection.to };
5055
+ }
5056
+ return null;
5057
+ }
4273
5058
  if (!selection.empty) return null;
4274
5059
  const { $from } = selection;
4275
5060
  const nodeBefore = $from.nodeBefore;
@@ -4279,23 +5064,23 @@ function jumpPointBefore(state) {
4279
5064
  return { id: nodeBefore.attrs.id, nodeStart, nodeEnd };
4280
5065
  }
4281
5066
  function explodeOnBackspace(state) {
4282
- const info = jumpPointBefore(state);
5067
+ const info = jumpPointAtOrBefore(state);
4283
5068
  if (!info) return null;
4284
5069
  const { id, nodeStart, nodeEnd } = info;
4285
5070
  const rawText = `^${id}`;
4286
5071
  const tr = state.tr.replaceWith(nodeStart, nodeEnd, state.schema.text(rawText));
4287
5072
  const cursorPos = Math.min(nodeStart + rawText.length, tr.doc.content.size);
4288
- tr.setSelection(TextSelection.near(tr.doc.resolve(cursorPos)));
5073
+ tr.setSelection(TextSelection2.near(tr.doc.resolve(cursorPos)));
4289
5074
  return tr;
4290
5075
  }
4291
5076
  function explodeOnArrowLeft(state) {
4292
- const info = jumpPointBefore(state);
5077
+ const info = jumpPointAtOrBefore(state);
4293
5078
  if (!info) return null;
4294
5079
  const { id, nodeStart, nodeEnd } = info;
4295
5080
  const rawText = `^${id}^`;
4296
5081
  const tr = state.tr.replaceWith(nodeStart, nodeEnd, state.schema.text(rawText));
4297
5082
  const cursorPos = Math.min(nodeStart + 1 + id.length, tr.doc.content.size);
4298
- tr.setSelection(TextSelection.near(tr.doc.resolve(cursorPos)));
5083
+ tr.setSelection(TextSelection2.near(tr.doc.resolve(cursorPos)));
4299
5084
  tr.setMeta(jumpPointEditKey, { from: nodeStart, to: nodeStart + rawText.length });
4300
5085
  return tr;
4301
5086
  }
@@ -4342,6 +5127,7 @@ function createJumpPointAdjacentPlugin() {
4342
5127
  }
4343
5128
 
4344
5129
  // src/ui/plugin/jumpPointNodeViewPlugin.tsx
5130
+ import { useCallback as useCallback2, useState as useState2 } from "react";
4345
5131
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
4346
5132
  var JUMP_POINT_STYLE = {
4347
5133
  display: "inline-flex",
@@ -4349,31 +5135,118 @@ var JUMP_POINT_STYLE = {
4349
5135
  backgroundColor: "#FFF2B6",
4350
5136
  border: "1px solid #FFC233",
4351
5137
  color: "#AA5D04",
4352
- borderRadius: "4px",
4353
- padding: "0 0.3em",
4354
- fontSize: "0.85em",
4355
- lineHeight: 1.6,
5138
+ borderRadius: "2px",
5139
+ padding: "2px",
5140
+ fontSize: "12px",
5141
+ lineHeight: "16px",
4356
5142
  userSelect: "none",
4357
5143
  cursor: "default",
4358
- boxSizing: "border-box"
5144
+ boxSizing: "border-box",
5145
+ height: "20px",
5146
+ overflow: "hidden"
4359
5147
  };
4360
5148
  var JUMP_POINT_ADJACENT_STYLE = {
4361
5149
  ...JUMP_POINT_STYLE,
4362
5150
  backgroundColor: "#FFE680",
4363
5151
  borderColor: "#FF9500"
4364
5152
  };
4365
- function JumpPointNodeViewComponent({ node, decorations }) {
5153
+ var JUMP_POINT_DUPLICATE_STYLE = {
5154
+ ...JUMP_POINT_STYLE,
5155
+ backgroundColor: "#FFD9DD",
5156
+ borderColor: "#D9352C",
5157
+ color: "#9D091E"
5158
+ };
5159
+ var LABEL_STYLE = {
5160
+ padding: "0 4px",
5161
+ whiteSpace: "nowrap"
5162
+ };
5163
+ var CLOSE_BTN_STYLE = {
5164
+ display: "inline-flex",
5165
+ alignItems: "center",
5166
+ justifyContent: "center",
5167
+ width: "16px",
5168
+ height: "16px",
5169
+ padding: 0,
5170
+ border: "none",
5171
+ background: "transparent",
5172
+ cursor: "pointer",
5173
+ borderRadius: "1px",
5174
+ flexShrink: 0,
5175
+ color: "inherit",
5176
+ fontSize: "10px",
5177
+ lineHeight: 1
5178
+ };
5179
+ var TOOLTIP_STYLE = {
5180
+ position: "absolute",
5181
+ bottom: "calc(100% + 8px)",
5182
+ left: "50%",
5183
+ transform: "translateX(-50%)",
5184
+ backgroundColor: "#fff",
5185
+ border: "1px solid #CCC",
5186
+ borderRadius: "4px",
5187
+ boxShadow: "0 8px 10px rgba(13,13,13,0.12), 0 3px 14px rgba(13,13,13,0.08), 0 3px 5px rgba(13,13,13,0.04)",
5188
+ padding: "16px 20px",
5189
+ fontSize: "14px",
5190
+ lineHeight: "20px",
5191
+ color: "#0D0D0D",
5192
+ whiteSpace: "nowrap",
5193
+ zIndex: 100,
5194
+ pointerEvents: "none"
5195
+ };
5196
+ function JumpPointNodeViewComponent({ node, view, getPos, decorations }) {
4366
5197
  const id = node.attrs.id;
4367
5198
  const isAdjacent = decorations?.some((d) => d.spec?.jumpPointAdjacent === true) ?? false;
5199
+ const isDuplicate = decorations?.some((d) => d.spec?.jumpPointDuplicate === true) ?? false;
5200
+ const [showTooltip, setShowTooltip] = useState2(false);
5201
+ const handleDelete2 = useCallback2(() => {
5202
+ const pos = getPos();
5203
+ if (pos == null) return;
5204
+ const tr = view.state.tr.delete(pos, pos + node.nodeSize);
5205
+ view.dispatch(tr);
5206
+ view.focus();
5207
+ }, [view, getPos, node.nodeSize]);
5208
+ if (isDuplicate) {
5209
+ return /* @__PURE__ */ jsxs3(
5210
+ "span",
5211
+ {
5212
+ style: { ...JUMP_POINT_DUPLICATE_STYLE, position: "relative" },
5213
+ id: `jp-${id}`,
5214
+ onMouseEnter: () => setShowTooltip(true),
5215
+ onMouseLeave: () => setShowTooltip(false),
5216
+ children: [
5217
+ /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#9D091E", style: { paddingLeft: "2px", flexShrink: 0 } }),
5218
+ /* @__PURE__ */ jsx4("span", { style: LABEL_STYLE, children: id }),
5219
+ /* @__PURE__ */ jsx4(
5220
+ "button",
5221
+ {
5222
+ style: CLOSE_BTN_STYLE,
5223
+ onMouseDown: (e) => {
5224
+ e.preventDefault();
5225
+ e.stopPropagation();
5226
+ handleDelete2();
5227
+ },
5228
+ title: "Remove anchor",
5229
+ children: "\u2715"
5230
+ }
5231
+ ),
5232
+ showTooltip && /* @__PURE__ */ jsxs3("span", { style: TOOLTIP_STYLE, children: [
5233
+ "\u201C",
5234
+ id,
5235
+ "\u201D is already used as an anchor"
5236
+ ] })
5237
+ ]
5238
+ }
5239
+ );
5240
+ }
4368
5241
  if (isAdjacent) {
4369
5242
  return /* @__PURE__ */ jsxs3("span", { style: JUMP_POINT_ADJACENT_STYLE, id: `jp-${id}`, children: [
4370
- /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { marginRight: "max(0.15em, 2px)", flexShrink: 0 } }),
4371
- `^${id}^`
5243
+ /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { paddingLeft: "2px", flexShrink: 0 } }),
5244
+ /* @__PURE__ */ jsx4("span", { style: LABEL_STYLE, children: `^${id}^` })
4372
5245
  ] });
4373
5246
  }
4374
5247
  return /* @__PURE__ */ jsxs3("span", { style: JUMP_POINT_STYLE, id: `jp-${id}`, children: [
4375
- /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { marginRight: "max(0.15em, 2px)", flexShrink: 0 } }),
4376
- id
5248
+ /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { paddingLeft: "2px", flexShrink: 0 } }),
5249
+ /* @__PURE__ */ jsx4("span", { style: LABEL_STYLE, children: id })
4377
5250
  ] });
4378
5251
  }
4379
5252
  function createJumpPointNodeViewPlugin() {
@@ -4388,108 +5261,100 @@ function createJumpPointNodeViewPlugin() {
4388
5261
  // src/ui/plugin/jumpPointValidationPlugin.ts
4389
5262
  import { Plugin as Plugin3, PluginKey as PluginKey3 } from "prosemirror-state";
4390
5263
  import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
4391
- var validationKey = new PluginKey3("jumpPointValidation");
4392
- function buildValidationDecorations(doc2) {
4393
- const jumpPointIds = /* @__PURE__ */ new Map();
4394
- const anchorRefs = [];
4395
- doc2.descendants((node, pos) => {
5264
+ var pluginKey = new PluginKey3("jumpPointValidation");
5265
+ function collectJumpPointIds(state) {
5266
+ const ids = /* @__PURE__ */ new Set();
5267
+ state.doc.descendants((node) => {
4396
5268
  if (node.type.name === "jumpPoint") {
4397
- const id = node.attrs.id;
4398
- if (!jumpPointIds.has(id)) jumpPointIds.set(id, []);
4399
- jumpPointIds.get(id).push(pos);
4400
- }
4401
- if (node.isText && node.marks) {
4402
- for (const mark of node.marks) {
4403
- if (mark.type.name === "link") {
4404
- const href = mark.attrs.href;
4405
- if (href?.startsWith("#")) {
4406
- anchorRefs.push({ from: pos, to: pos + node.nodeSize, id: href.slice(1) });
4407
- }
4408
- }
4409
- }
5269
+ ids.add(node.attrs.id);
4410
5270
  }
4411
5271
  });
4412
- const decorations = [];
4413
- for (const [, positions] of jumpPointIds) {
4414
- if (positions.length > 1) {
4415
- for (const pos of positions) {
4416
- decorations.push(
4417
- Decoration2.node(pos, pos + 1, { class: "duplicate-jump-point" })
4418
- );
4419
- }
5272
+ return ids;
5273
+ }
5274
+ function findDuplicatePositions(state) {
5275
+ const idCount = /* @__PURE__ */ new Map();
5276
+ const nodes = [];
5277
+ state.doc.descendants((node, pos) => {
5278
+ if (node.type.name === "jumpPoint") {
5279
+ const id = node.attrs.id;
5280
+ idCount.set(id, (idCount.get(id) ?? 0) + 1);
5281
+ nodes.push({ id, pos, size: node.nodeSize });
4420
5282
  }
4421
- }
4422
- for (const ref of anchorRefs) {
4423
- if (!jumpPointIds.has(ref.id)) {
4424
- decorations.push(
4425
- Decoration2.inline(ref.from, ref.to, { class: "broken-anchor-ref" })
4426
- );
5283
+ });
5284
+ return nodes.filter((n) => (idCount.get(n.id) ?? 0) > 1);
5285
+ }
5286
+ function findBrokenAnchorRefs(state, existingIds) {
5287
+ const broken = [];
5288
+ const linkMarkType = state.schema.marks.link;
5289
+ if (!linkMarkType) return broken;
5290
+ state.doc.descendants((node, pos) => {
5291
+ if (!node.isText) return;
5292
+ const linkMark2 = node.marks.find((m) => m.type === linkMarkType);
5293
+ if (!linkMark2) return;
5294
+ const href = linkMark2.attrs.href;
5295
+ if (!href || !href.startsWith("#")) return;
5296
+ const refId = href.slice(1);
5297
+ if (refId && !existingIds.has(refId)) {
5298
+ broken.push({ from: pos, to: pos + node.nodeSize, refId });
4427
5299
  }
4428
- }
4429
- return decorations.length > 0 ? DecorationSet2.create(doc2, decorations) : DecorationSet2.empty;
5300
+ });
5301
+ return broken;
5302
+ }
5303
+ function hasDuplicateJumpPoints(state) {
5304
+ return findDuplicatePositions(state).length > 0;
5305
+ }
5306
+ function hasBrokenAnchorRefs(state) {
5307
+ const ids = collectJumpPointIds(state);
5308
+ return findBrokenAnchorRefs(state, ids).length > 0;
4430
5309
  }
4431
5310
  function createJumpPointValidationPlugin() {
4432
5311
  return {
4433
5312
  name: "jumpPointValidation",
4434
5313
  plugins: () => [
4435
5314
  new Plugin3({
4436
- key: validationKey,
5315
+ key: pluginKey,
4437
5316
  state: {
4438
5317
  init(_, state) {
4439
- return buildValidationDecorations(state.doc);
5318
+ return buildDecorations2(state);
4440
5319
  },
4441
- apply(tr, oldDecos) {
4442
- if (!tr.docChanged) return oldDecos;
4443
- return buildValidationDecorations(tr.doc);
5320
+ apply(tr, oldSet, _oldState, newState) {
5321
+ if (tr.docChanged) {
5322
+ return buildDecorations2(newState);
5323
+ }
5324
+ return oldSet.map(tr.mapping, tr.doc);
4444
5325
  }
4445
5326
  },
4446
5327
  props: {
4447
5328
  decorations(state) {
4448
- return validationKey.getState(state);
5329
+ return pluginKey.getState(state) ?? DecorationSet2.empty;
4449
5330
  }
4450
5331
  }
4451
5332
  })
4452
5333
  ]
4453
5334
  };
4454
5335
  }
4455
- function hasDuplicateJumpPoints(state) {
4456
- const ids = /* @__PURE__ */ new Set();
4457
- let hasDup = false;
4458
- state.doc.descendants((node) => {
4459
- if (node.type.name === "jumpPoint") {
4460
- const id = node.attrs.id;
4461
- if (ids.has(id)) {
4462
- hasDup = true;
4463
- return false;
4464
- }
4465
- ids.add(id);
4466
- }
4467
- });
4468
- return hasDup;
4469
- }
4470
- function hasBrokenAnchorRefs(state) {
4471
- const jumpPointIds = /* @__PURE__ */ new Set();
4472
- state.doc.descendants((node) => {
4473
- if (node.type.name === "jumpPoint") {
4474
- jumpPointIds.add(node.attrs.id);
4475
- }
4476
- });
4477
- let hasBroken = false;
4478
- state.doc.descendants((node) => {
4479
- if (hasBroken) return false;
4480
- if (node.isText && node.marks) {
4481
- for (const mark of node.marks) {
4482
- if (mark.type.name === "link") {
4483
- const href = mark.attrs.href;
4484
- if (href?.startsWith("#") && !jumpPointIds.has(href.slice(1))) {
4485
- hasBroken = true;
4486
- return false;
4487
- }
4488
- }
4489
- }
4490
- }
4491
- });
4492
- return hasBroken;
5336
+ function buildDecorations2(state) {
5337
+ const decos = [];
5338
+ const duplicates = findDuplicatePositions(state);
5339
+ for (const { pos, size } of duplicates) {
5340
+ decos.push(
5341
+ Decoration2.node(pos, pos + size, { class: "jump-point-duplicate" }, { jumpPointDuplicate: true })
5342
+ );
5343
+ }
5344
+ const existingIds = collectJumpPointIds(state);
5345
+ const brokenRefs = findBrokenAnchorRefs(state, existingIds);
5346
+ for (const { from, to, refId } of brokenRefs) {
5347
+ decos.push(
5348
+ Decoration2.inline(from, to, {
5349
+ class: "broken-anchor-ref",
5350
+ "data-broken-ref": refId,
5351
+ title: `Anchor "${refId}" no longer exists`,
5352
+ style: "color: #D9352C; text-decoration-color: #D9352C;"
5353
+ })
5354
+ );
5355
+ }
5356
+ if (decos.length === 0) return DecorationSet2.empty;
5357
+ return DecorationSet2.create(state.doc, decos);
4493
5358
  }
4494
5359
 
4495
5360
  // src/ui/plugin/inlineToolTagNodeViewPlugin.tsx
@@ -4510,9 +5375,12 @@ var RESOURCE_TAG_ICONS2 = {
4510
5375
  time_diff: IconTimeDiff
4511
5376
  };
4512
5377
  var DEFAULT_ICON = IconTool;
5378
+ var RESOURCE_ID_OVERRIDES = {
5379
+ "close-happy-tiger": { color: "#0D0D0D", icon: IconStop }
5380
+ };
4513
5381
  var OUTER_STYLE = {
4514
5382
  display: "inline-flex",
4515
- padding: "1px",
5383
+ padding: "2px",
4516
5384
  border: "1px solid #CCCCCC",
4517
5385
  borderRadius: "2px",
4518
5386
  alignItems: "center",
@@ -4532,16 +5400,19 @@ var ICON_BLOCK_BASE = {
4532
5400
  };
4533
5401
  var LABEL_BASE = {
4534
5402
  padding: "0 4px",
4535
- fontSize: "11px",
4536
- lineHeight: 1,
5403
+ fontSize: "12px",
5404
+ lineHeight: "16px",
4537
5405
  userSelect: "none"
4538
5406
  };
4539
- function InlineToolTagNodeViewComponent({ node }) {
5407
+ function InlineToolTagNodeViewComponent({ node, selected }) {
4540
5408
  const type = node.attrs.type;
4541
5409
  const text2 = node.attrs.text;
4542
- const color = RESOURCE_TAG_COLORS2[type] ?? DEFAULT_COLOR;
4543
- const Icon = RESOURCE_TAG_ICONS2[type] ?? DEFAULT_ICON;
4544
- return /* @__PURE__ */ jsxs4("span", { style: OUTER_STYLE, "data-type": type, "data-resource-id": node.attrs.resourceId, children: [
5410
+ const resourceId = node.attrs.resourceId;
5411
+ const override = RESOURCE_ID_OVERRIDES[resourceId];
5412
+ const color = override?.color ?? RESOURCE_TAG_COLORS2[type] ?? DEFAULT_COLOR;
5413
+ const Icon = override?.icon ?? RESOURCE_TAG_ICONS2[type] ?? DEFAULT_ICON;
5414
+ const style = selected ? { ...OUTER_STYLE, boxShadow: `inset 0 0 0 1px ${color}` } : OUTER_STYLE;
5415
+ return /* @__PURE__ */ jsxs4("span", { style, "data-type": type, "data-resource-id": node.attrs.resourceId, children: [
4545
5416
  /* @__PURE__ */ jsx5("span", { style: { ...ICON_BLOCK_BASE, backgroundColor: color }, children: /* @__PURE__ */ jsx5(Icon, { size: 9, fill: "white" }) }),
4546
5417
  /* @__PURE__ */ jsx5("span", { style: { ...LABEL_BASE, color }, children: text2 })
4547
5418
  ] });
@@ -4576,292 +5447,103 @@ function addInlineChipDecorations(doc2, decorations) {
4576
5447
  JINJA_TAG_RE.lastIndex = 0;
4577
5448
  let match;
4578
5449
  while ((match = JINJA_TAG_RE.exec(text2)) !== null) {
4579
- const from = pos + match.index;
4580
- const to = from + match[0].length;
4581
- const keyword = match[1];
4582
- decorations.push(
4583
- Decoration3.inline(from, to, {
4584
- class: `jinja-chip jinja-chip-${keyword}`
4585
- })
4586
- );
4587
- }
4588
- });
4589
- }
4590
- function addStructureBorderDecorations(doc2, blocks, decorations) {
4591
- const json = doc2.toJSON();
4592
- let ast;
4593
- try {
4594
- ast = fromProseMirrorJSON(json);
4595
- } catch {
4596
- return;
4597
- }
4598
- const structures = analyzeJinjaBlocks(ast);
4599
- for (const structure of structures) {
4600
- const blockBranchType = /* @__PURE__ */ new Map();
4601
- for (const branch of structure.branches) {
4602
- blockBranchType.set(branch.tagBlockIndex, branch.type);
4603
- for (let i = branch.blockStartIndex; i < branch.blockEndIndex; i++) {
4604
- if (!blockBranchType.has(i)) {
4605
- blockBranchType.set(i, branch.type);
4606
- }
4607
- }
4608
- }
4609
- const lastBranch = structure.branches[structure.branches.length - 1];
4610
- if (lastBranch && !blockBranchType.has(structure.endifTagBlockIndex)) {
4611
- blockBranchType.set(structure.endifTagBlockIndex, lastBranch.type);
4612
- }
4613
- const first = structure.ifTagBlockIndex;
4614
- const last = structure.endifTagBlockIndex;
4615
- for (let i = first; i <= last; i++) {
4616
- const block = blocks[i];
4617
- if (!block) continue;
4618
- const branchType = blockBranchType.get(i) || "if";
4619
- const prevType = i > first ? blockBranchType.get(i - 1) : void 0;
4620
- const nextType = i < last ? blockBranchType.get(i + 1) : void 0;
4621
- const classes = ["jinja-bar", `jinja-bar-${branchType}`];
4622
- if (i === first || prevType !== branchType) classes.push("jinja-bar-first");
4623
- if (i === last || nextType !== branchType) classes.push("jinja-bar-last");
4624
- decorations.push(Decoration3.node(block.from, block.to, { class: classes.join(" ") }));
4625
- }
4626
- }
4627
- }
4628
- function buildDecorations2(doc2) {
4629
- const blocks = getBlockPositions(doc2);
4630
- const decorations = [];
4631
- addInlineChipDecorations(doc2, decorations);
4632
- addStructureBorderDecorations(doc2, blocks, decorations);
4633
- if (decorations.length === 0) return DecorationSet3.empty;
4634
- return DecorationSet3.create(doc2, decorations);
4635
- }
4636
- function createJinjaDecorationPlugin() {
4637
- return {
4638
- name: "jinjaDecoration",
4639
- plugins: () => [
4640
- new Plugin4({
4641
- key: jinjaPluginKey,
4642
- state: {
4643
- init(_, state) {
4644
- return buildDecorations2(state.doc);
4645
- },
4646
- apply(tr, oldDecos) {
4647
- if (!tr.docChanged) return oldDecos;
4648
- return buildDecorations2(tr.doc);
4649
- }
4650
- },
4651
- props: {
4652
- decorations(state) {
4653
- return jinjaPluginKey.getState(state);
4654
- }
4655
- }
4656
- })
4657
- ]
4658
- };
4659
- }
4660
-
4661
- // src/ui/plugin/jinjaIfBlockPlugin.tsx
4662
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
4663
- import { createRoot as createRoot2 } from "react-dom/client";
4664
-
4665
- // src/jinja/conditionHighlighter.ts
4666
- var KEYWORDS = {
4667
- and: "AND",
4668
- or: "OR",
4669
- not: "NOT",
4670
- in: "IN",
4671
- is: "IS",
4672
- True: "BOOL",
4673
- False: "BOOL",
4674
- true: "BOOL",
4675
- false: "BOOL",
4676
- None: "NONE",
4677
- null: "NONE"
4678
- };
4679
- var CATEGORY_BY_TYPE = {
4680
- STRING: "value",
4681
- NUMBER: "value",
4682
- BOOL: "value",
4683
- NONE: "value",
4684
- IDENT: "variable",
4685
- AND: "operator",
4686
- OR: "operator",
4687
- NOT: "operator",
4688
- IN: "operator",
4689
- IS: "operator",
4690
- EQ: "operator",
4691
- NEQ: "operator",
4692
- LT: "operator",
4693
- GT: "operator",
4694
- LTE: "operator",
4695
- GTE: "operator",
4696
- LPAREN: "punctuation",
4697
- RPAREN: "punctuation"
4698
- };
4699
- var NEGATIVE_NUMBER_PRECEDERS = /* @__PURE__ */ new Set([
4700
- "AND",
4701
- "OR",
4702
- "NOT",
4703
- "IN",
4704
- "IS",
4705
- "EQ",
4706
- "NEQ",
4707
- "LT",
4708
- "GT",
4709
- "LTE",
4710
- "GTE",
4711
- "LPAREN"
4712
- ]);
4713
- function canStartNegativeNumber(tokens) {
4714
- if (tokens.length === 0) {
4715
- return true;
4716
- }
4717
- const previous = tokens[tokens.length - 1];
4718
- return NEGATIVE_NUMBER_PRECEDERS.has(previous.type);
4719
- }
4720
- function finalizeTokens(input, rawTokens) {
4721
- const highlighted = [];
4722
- for (let i = 0; i < rawTokens.length; i++) {
4723
- const token = rawTokens[i];
4724
- const next = rawTokens[i + 1];
4725
- if (token.type === "IS" && next?.type === "NOT") {
4726
- highlighted.push({
4727
- text: input.slice(token.start, next.end),
4728
- start: token.start,
4729
- end: next.end,
4730
- category: "operator"
4731
- });
4732
- i++;
4733
- continue;
4734
- }
4735
- highlighted.push({
4736
- text: token.text,
4737
- start: token.start,
4738
- end: token.end,
4739
- category: CATEGORY_BY_TYPE[token.type]
4740
- });
4741
- }
4742
- return highlighted;
4743
- }
4744
- function tokenizeCondition(input) {
4745
- const rawTokens = [];
4746
- let i = 0;
4747
- while (i < input.length) {
4748
- const char = input[i];
4749
- if (/\s/.test(char)) {
4750
- i++;
4751
- continue;
4752
- }
4753
- if (char === '"' || char === "'") {
4754
- const start = i;
4755
- const quote = char;
4756
- i++;
4757
- while (i < input.length) {
4758
- if (input[i] === "\\" && i + 1 < input.length) {
4759
- i += 2;
4760
- continue;
4761
- }
4762
- if (input[i] === quote) {
4763
- i++;
4764
- rawTokens.push({
4765
- type: "STRING",
4766
- text: input.slice(start, i),
4767
- start,
4768
- end: i
4769
- });
4770
- break;
4771
- }
4772
- i++;
4773
- }
4774
- if (rawTokens[rawTokens.length - 1]?.start !== start) {
4775
- return finalizeTokens(input, rawTokens);
4776
- }
4777
- continue;
4778
- }
4779
- if (/[0-9]/.test(char) || char === "-" && i + 1 < input.length && /[0-9]/.test(input[i + 1]) && canStartNegativeNumber(rawTokens)) {
4780
- const start = i;
4781
- if (input[i] === "-") {
4782
- i++;
4783
- }
4784
- while (i < input.length && /[0-9]/.test(input[i])) {
4785
- i++;
4786
- }
4787
- if (i < input.length && input[i] === ".") {
4788
- i++;
4789
- while (i < input.length && /[0-9]/.test(input[i])) {
4790
- i++;
4791
- }
4792
- }
4793
- rawTokens.push({
4794
- type: "NUMBER",
4795
- text: input.slice(start, i),
4796
- start,
4797
- end: i
4798
- });
4799
- continue;
4800
- }
4801
- if (/[a-zA-Z_]/.test(char)) {
4802
- const start = i;
4803
- i++;
4804
- while (i < input.length && /[a-zA-Z0-9_.]/.test(input[i])) {
4805
- i++;
4806
- }
4807
- const text2 = input.slice(start, i);
4808
- rawTokens.push({
4809
- type: KEYWORDS[text2] ?? "IDENT",
4810
- text: text2,
4811
- start,
4812
- end: i
4813
- });
4814
- continue;
4815
- }
4816
- if (i + 1 < input.length) {
4817
- const twoChar = input.slice(i, i + 2);
4818
- if (twoChar === "==") {
4819
- rawTokens.push({ type: "EQ", text: twoChar, start: i, end: i + 2 });
4820
- i += 2;
4821
- continue;
4822
- }
4823
- if (twoChar === "!=") {
4824
- rawTokens.push({ type: "NEQ", text: twoChar, start: i, end: i + 2 });
4825
- i += 2;
4826
- continue;
4827
- }
4828
- if (twoChar === "<=") {
4829
- rawTokens.push({ type: "LTE", text: twoChar, start: i, end: i + 2 });
4830
- i += 2;
4831
- continue;
4832
- }
4833
- if (twoChar === ">=") {
4834
- rawTokens.push({ type: "GTE", text: twoChar, start: i, end: i + 2 });
4835
- i += 2;
4836
- continue;
4837
- }
4838
- }
4839
- if (char === "<") {
4840
- rawTokens.push({ type: "LT", text: char, start: i, end: i + 1 });
4841
- i++;
4842
- continue;
5450
+ const from = pos + match.index;
5451
+ const to = from + match[0].length;
5452
+ const keyword = match[1];
5453
+ decorations.push(
5454
+ Decoration3.inline(from, to, {
5455
+ class: `jinja-chip jinja-chip-${keyword}`
5456
+ })
5457
+ );
4843
5458
  }
4844
- if (char === ">") {
4845
- rawTokens.push({ type: "GT", text: char, start: i, end: i + 1 });
4846
- i++;
4847
- continue;
5459
+ });
5460
+ }
5461
+ function addStructureBorderDecorations(doc2, blocks, decorations) {
5462
+ const json = doc2.toJSON();
5463
+ let ast;
5464
+ try {
5465
+ ast = fromProseMirrorJSON(json);
5466
+ } catch {
5467
+ return;
5468
+ }
5469
+ const structures = analyzeJinjaBlocks(ast);
5470
+ for (const structure of structures) {
5471
+ const blockBranchType = /* @__PURE__ */ new Map();
5472
+ for (const branch of structure.branches) {
5473
+ blockBranchType.set(branch.tagBlockIndex, branch.type);
5474
+ for (let i = branch.blockStartIndex; i < branch.blockEndIndex; i++) {
5475
+ if (!blockBranchType.has(i)) {
5476
+ blockBranchType.set(i, branch.type);
5477
+ }
5478
+ }
4848
5479
  }
4849
- if (char === "(") {
4850
- rawTokens.push({ type: "LPAREN", text: char, start: i, end: i + 1 });
4851
- i++;
4852
- continue;
5480
+ const lastBranch = structure.branches[structure.branches.length - 1];
5481
+ if (lastBranch && !blockBranchType.has(structure.endifTagBlockIndex)) {
5482
+ blockBranchType.set(structure.endifTagBlockIndex, lastBranch.type);
4853
5483
  }
4854
- if (char === ")") {
4855
- rawTokens.push({ type: "RPAREN", text: char, start: i, end: i + 1 });
4856
- i++;
4857
- continue;
5484
+ const first = structure.ifTagBlockIndex;
5485
+ const last = structure.endifTagBlockIndex;
5486
+ for (let i = first; i <= last; i++) {
5487
+ const block = blocks[i];
5488
+ if (!block) continue;
5489
+ const branchType = blockBranchType.get(i) || "if";
5490
+ const prevType = i > first ? blockBranchType.get(i - 1) : void 0;
5491
+ const nextType = i < last ? blockBranchType.get(i + 1) : void 0;
5492
+ const classes = ["jinja-bar", `jinja-bar-${branchType}`];
5493
+ if (i === first || prevType !== branchType) classes.push("jinja-bar-first");
5494
+ if (i === last || nextType !== branchType) classes.push("jinja-bar-last");
5495
+ decorations.push(Decoration3.node(block.from, block.to, { class: classes.join(" ") }));
4858
5496
  }
4859
- return finalizeTokens(input, rawTokens);
4860
5497
  }
4861
- return finalizeTokens(input, rawTokens);
5498
+ }
5499
+ function hasJinjaTags(doc2) {
5500
+ let found = false;
5501
+ doc2.descendants((node) => {
5502
+ if (found) return false;
5503
+ if (node.isText && node.text.includes("{%")) {
5504
+ found = true;
5505
+ return false;
5506
+ }
5507
+ });
5508
+ return found;
5509
+ }
5510
+ function buildDecorations3(doc2) {
5511
+ if (!hasJinjaTags(doc2)) return DecorationSet3.empty;
5512
+ const blocks = getBlockPositions(doc2);
5513
+ const decorations = [];
5514
+ addInlineChipDecorations(doc2, decorations);
5515
+ addStructureBorderDecorations(doc2, blocks, decorations);
5516
+ if (decorations.length === 0) return DecorationSet3.empty;
5517
+ return DecorationSet3.create(doc2, decorations);
5518
+ }
5519
+ function createJinjaDecorationPlugin() {
5520
+ return {
5521
+ name: "jinjaDecoration",
5522
+ plugins: () => [
5523
+ new Plugin4({
5524
+ key: jinjaPluginKey,
5525
+ state: {
5526
+ init(_, state) {
5527
+ return buildDecorations3(state.doc);
5528
+ },
5529
+ apply(tr, oldDecos) {
5530
+ if (!tr.docChanged) return oldDecos;
5531
+ return buildDecorations3(tr.doc);
5532
+ }
5533
+ },
5534
+ props: {
5535
+ decorations(state) {
5536
+ return jinjaPluginKey.getState(state);
5537
+ }
5538
+ }
5539
+ })
5540
+ ]
5541
+ };
4862
5542
  }
4863
5543
 
4864
5544
  // src/ui/plugin/jinjaIfBlockPlugin.tsx
5545
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
5546
+ import { createRoot as createRoot2 } from "react-dom/client";
4865
5547
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
4866
5548
  var PLACEHOLDER_TEXT = "Describe what AI agent should do when this condition is met";
4867
5549
  var CONDITION_PLACEHOLDER = "Write a condition in natural language";
@@ -5312,8 +5994,8 @@ function ConditionDisplay({
5312
5994
  editable = true,
5313
5995
  onConditionChange
5314
5996
  }) {
5315
- const [isEditing, setIsEditing] = useState2(false);
5316
- const [draftValue, setDraftValue] = useState2(condition);
5997
+ const [isEditing, setIsEditing] = useState3(false);
5998
+ const [draftValue, setDraftValue] = useState3(condition);
5317
5999
  const inputRef = useRef2(null);
5318
6000
  useEffect2(() => {
5319
6001
  if (!isEditing) {
@@ -5419,7 +6101,7 @@ function JinjaBranchHeader({
5419
6101
  isLastBranch,
5420
6102
  hasElseBranch
5421
6103
  }) {
5422
- const [menuSource, setMenuSource] = useState2(null);
6104
+ const [menuSource, setMenuSource] = useState3(null);
5423
6105
  const kebabRef = useRef2(null);
5424
6106
  const footerRef = useRef2(null);
5425
6107
  useEffect2(() => {
@@ -5699,7 +6381,7 @@ function createJinjaIfBlockPlugin() {
5699
6381
 
5700
6382
  // src/ui/plugin/linkPlugin.ts
5701
6383
  import { InputRule as InputRule2, inputRules as inputRules2 } from "prosemirror-inputrules";
5702
- import { Plugin as Plugin5, PluginKey as PluginKey5, TextSelection as TextSelection2 } from "prosemirror-state";
6384
+ import { Plugin as Plugin5, PluginKey as PluginKey5, TextSelection as TextSelection3 } from "prosemirror-state";
5703
6385
  var LINK_INPUT_RE = /\[([^\]]*)\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
5704
6386
  var LINK_FULL_RE = /^\[([^\]]*)\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
5705
6387
  var linkEditKey = new PluginKey5("linkEdit");
@@ -5745,7 +6427,7 @@ function explodeLinkToRaw(state) {
5745
6427
  const tr = state.tr;
5746
6428
  tr.replaceWith(from, to, schema.text(rawText));
5747
6429
  const newPos = Math.min(from + rawCursorOffset, tr.doc.content.size);
5748
- tr.setSelection(TextSelection2.near(tr.doc.resolve(newPos)));
6430
+ tr.setSelection(TextSelection3.near(tr.doc.resolve(newPos)));
5749
6431
  tr.setMeta(linkEditKey, { from, to: from + rawText.length });
5750
6432
  return tr;
5751
6433
  }
@@ -5891,11 +6573,11 @@ var STYLES = (
5891
6573
  --ab-dh-color-idle: transparent;
5892
6574
  --ab-dh-color-hover: #c4c4c4;
5893
6575
  --ab-dh-color-visible: #8b8b8b;
5894
- --ab-dh-color-accent: #6366f1;
5895
- --ab-dh-bg-hover: rgba(0,0,0,0.04);
5896
- --ab-dh-bg-active: rgba(99,102,241,0.08);
6576
+ --ab-dh-color-accent: rgba(1,156,110,0.8);
6577
+ --ab-dh-bg-hover: rgba(13,13,13,0.04);
6578
+ --ab-dh-bg-active: rgba(13,13,13,0.04);
5897
6579
  --ab-dh-dragging-opacity: 0.4;
5898
- --ab-dh-handle-size: 22px;
6580
+ --ab-dh-handle-size: 24px;
5899
6581
  --ab-dh-handle-radius: 4px;
5900
6582
  --ab-dh-transition-duration: 0.15s;
5901
6583
 
@@ -5912,15 +6594,19 @@ var STYLES = (
5912
6594
  align-items: center;
5913
6595
  justify-content: center;
5914
6596
  cursor: grab;
5915
- color: var(--ab-dh-color-idle);
6597
+ color: transparent;
5916
6598
  border-radius: var(--ab-dh-handle-radius);
5917
- transition: color var(--ab-dh-transition-duration), background var(--ab-dh-transition-duration);
6599
+ transition: color var(--ab-dh-transition-duration), background var(--ab-dh-transition-duration), opacity var(--ab-dh-transition-duration);
5918
6600
  user-select: none;
5919
6601
  pointer-events: auto;
5920
6602
  touch-action: none;
6603
+ opacity: 0;
6604
+ pointer-events: none;
5921
6605
  }
5922
- .ab-drag-handle-layer[data-hover] .ab-drag-handle {
5923
- color: var(--ab-dh-color-hover);
6606
+ .ab-drag-handle.ab-dh-active {
6607
+ opacity: 1;
6608
+ pointer-events: auto;
6609
+ color: var(--ab-dh-color-visible);
5924
6610
  }
5925
6611
  .ab-drag-handle:hover {
5926
6612
  color: var(--ab-dh-color-visible) !important;
@@ -5934,18 +6620,20 @@ var STYLES = (
5934
6620
  .ab-drop-indicator {
5935
6621
  position: absolute;
5936
6622
  left: 0; right: 0;
5937
- height: 2px;
5938
- background: var(--ab-dh-color-accent, #6366f1);
5939
- border-radius: 1px;
6623
+ height: 4px;
6624
+ background: rgba(1,156,110,0.4);
6625
+ border: 1px solid rgba(1,156,110,0.6);
6626
+ border-radius: 12px;
5940
6627
  pointer-events: none;
5941
6628
  z-index: 20;
6629
+ box-sizing: border-box;
5942
6630
  transition: top 60ms ease-out;
5943
6631
  }
5944
6632
  .ab-block-dragging {
5945
6633
  opacity: var(--ab-dh-dragging-opacity, 0.4);
5946
6634
  }
5947
6635
  @keyframes ab-block-flash {
5948
- from { background: rgba(99,102,241,0.12); }
6636
+ from { background: rgba(1,156,110,0.08); }
5949
6637
  to { background: transparent; }
5950
6638
  }
5951
6639
  .ab-block-just-moved {
@@ -6101,23 +6789,25 @@ var DragHandleController = class {
6101
6789
  this.liveRegion.setAttribute("aria-live", "assertive");
6102
6790
  this.liveRegion.setAttribute("aria-atomic", "true");
6103
6791
  parent.appendChild(this.liveRegion);
6104
- const onEnter = () => {
6105
- this.layer.dataset.hover = "1";
6106
- };
6107
- const onLeave = () => {
6108
- if (!this.drag) delete this.layer.dataset.hover;
6109
- };
6110
- parent.addEventListener("mouseenter", onEnter);
6111
- parent.addEventListener("mouseleave", onLeave);
6112
- this.hoverBound = { enter: onEnter, leave: onLeave };
6792
+ this.hoverBound = { enter: () => {
6793
+ }, leave: () => {
6794
+ } };
6113
6795
  this.scrollContainer = this.findScrollContainer(view.dom);
6114
6796
  this.scrollContainer.addEventListener("scroll", this.onScroll, { passive: true });
6115
6797
  this.resizeObserver = new ResizeObserver(() => this.scheduleUpdate());
6116
6798
  this.resizeObserver.observe(view.dom);
6799
+ if (!view.editable) {
6800
+ this.layer.style.display = "none";
6801
+ }
6117
6802
  this.rebuildHandles();
6118
6803
  }
6119
6804
  update(view, prevState) {
6120
6805
  this.view = view;
6806
+ if (!view.editable) {
6807
+ this.layer.style.display = "none";
6808
+ return;
6809
+ }
6810
+ this.layer.style.display = "";
6121
6811
  if (this.drag) {
6122
6812
  this.pendingUpdate = true;
6123
6813
  return;
@@ -6125,6 +6815,21 @@ var DragHandleController = class {
6125
6815
  if (!view.state.doc.eq(prevState.doc)) {
6126
6816
  this.scheduleUpdate();
6127
6817
  }
6818
+ if (!view.state.selection.eq(prevState.selection) || !view.state.doc.eq(prevState.doc)) {
6819
+ this.updateActiveHandle();
6820
+ }
6821
+ }
6822
+ /** Show only the handle for the block containing the cursor. */
6823
+ updateActiveHandle() {
6824
+ const { $from } = this.view.state.selection;
6825
+ const cursorBlockOffset = $from.start(1) - 1;
6826
+ for (const h of this.handles) {
6827
+ if (h.block.offset === cursorBlockOffset) {
6828
+ h.el.classList.add("ab-dh-active");
6829
+ } else {
6830
+ h.el.classList.remove("ab-dh-active");
6831
+ }
6832
+ }
6128
6833
  }
6129
6834
  destroy() {
6130
6835
  this.cancelDrag();
@@ -6216,6 +6921,7 @@ var DragHandleController = class {
6216
6921
  el.setAttribute("role", "button");
6217
6922
  el.setAttribute("aria-roledescription", "drag handle");
6218
6923
  el.setAttribute("aria-label", `\uBE14\uB85D ${idx + 1} \uC774\uB3D9`);
6924
+ el.setAttribute("title", "Drag to move");
6219
6925
  el.setAttribute("tabindex", "-1");
6220
6926
  el.innerHTML = GRIP_SVG;
6221
6927
  el.style.left = `${handleLeft}px`;
@@ -6226,6 +6932,7 @@ var DragHandleController = class {
6226
6932
  }
6227
6933
  this.handles = newHandles;
6228
6934
  this.prevBlocks = newBlocks;
6935
+ this.updateActiveHandle();
6229
6936
  }
6230
6937
  bindHandleEvents(el, index) {
6231
6938
  let currentIndex = index;
@@ -6296,7 +7003,8 @@ var DragHandleController = class {
6296
7003
  pointerId: e.pointerId,
6297
7004
  startX: e.clientX,
6298
7005
  startY: e.clientY,
6299
- activated: false
7006
+ activated: false,
7007
+ ghost: null
6300
7008
  };
6301
7009
  this.addGlobalDragListeners(e.pointerId);
6302
7010
  }
@@ -6305,6 +7013,22 @@ var DragHandleController = class {
6305
7013
  this.drag.activated = true;
6306
7014
  this.drag.handle.classList.add("dragging");
6307
7015
  this.drag.blockDom.classList.add("ab-block-dragging");
7016
+ const blockRect = this.drag.blockDom.getBoundingClientRect();
7017
+ const ghost = this.drag.blockDom.cloneNode(true);
7018
+ ghost.className = "ab-drag-ghost";
7019
+ ghost.style.cssText = `
7020
+ position: fixed;
7021
+ top: ${blockRect.top}px;
7022
+ left: ${blockRect.left}px;
7023
+ width: ${blockRect.width}px;
7024
+ opacity: 0.4;
7025
+ pointer-events: none;
7026
+ z-index: 9999;
7027
+ transition: none;
7028
+ `;
7029
+ document.body.appendChild(ghost);
7030
+ this.drag.ghost = ghost;
7031
+ this.drag._ghostOffsetY = this.drag.startY - blockRect.top;
6308
7032
  const parent = this.view.dom.parentNode;
6309
7033
  this.dropIndicator = document.createElement("div");
6310
7034
  this.dropIndicator.className = "ab-drop-indicator";
@@ -6344,6 +7068,10 @@ var DragHandleController = class {
6344
7068
  this.activateDrag();
6345
7069
  }
6346
7070
  this.drag.pointerY = e.clientY;
7071
+ if (this.drag.ghost) {
7072
+ const offsetY = this.drag._ghostOffsetY ?? 0;
7073
+ this.drag.ghost.style.top = `${e.clientY - offsetY}px`;
7074
+ }
6347
7075
  this.updateDropTarget();
6348
7076
  }
6349
7077
  updateDropTarget() {
@@ -6415,6 +7143,10 @@ var DragHandleController = class {
6415
7143
  this.cleaningUp = true;
6416
7144
  this.drag.handle.classList.remove("dragging");
6417
7145
  this.drag.blockDom.classList.remove("ab-block-dragging");
7146
+ if (this.drag.ghost) {
7147
+ this.drag.ghost.remove();
7148
+ this.drag.ghost = null;
7149
+ }
6418
7150
  try {
6419
7151
  this.drag.handle.releasePointerCapture(this.drag.pointerId);
6420
7152
  } catch {
@@ -6620,6 +7352,124 @@ function createTodoNodeViewPlugin() {
6620
7352
  };
6621
7353
  }
6622
7354
 
7355
+ // src/ui/plugin/noteBlockPlugin.tsx
7356
+ import { Selection } from "prosemirror-state";
7357
+ var NoteBlockView = class {
7358
+ dom;
7359
+ contentDOM;
7360
+ constructor(_node, _view, _getPos) {
7361
+ this.dom = document.createElement("div");
7362
+ this.dom.setAttribute("data-note-block", "");
7363
+ this.dom.className = "ab-note-block";
7364
+ this.dom.style.cssText = [
7365
+ "position: relative",
7366
+ "margin: 8px 0",
7367
+ "padding: 16px 24px",
7368
+ "background: #fff",
7369
+ "border: 1px dashed #ccc",
7370
+ "border-radius: 4px",
7371
+ "display: flex",
7372
+ "flex-direction: column",
7373
+ "gap: 4px"
7374
+ ].join(";");
7375
+ const label = document.createElement("span");
7376
+ label.contentEditable = "false";
7377
+ label.textContent = "Note";
7378
+ label.style.cssText = [
7379
+ "display: inline-flex",
7380
+ "align-items: center",
7381
+ "justify-content: center",
7382
+ "align-self: flex-start",
7383
+ "font-size: 12px",
7384
+ "font-weight: 400",
7385
+ "font-style: normal",
7386
+ "line-height: 16px",
7387
+ "color: #858585",
7388
+ "background: #f7f7f7",
7389
+ "border: 1px solid #e0e0e0",
7390
+ "border-radius: 4px",
7391
+ "padding: 2px 4px",
7392
+ "user-select: none"
7393
+ ].join(";");
7394
+ this.dom.appendChild(label);
7395
+ this.contentDOM = document.createElement("div");
7396
+ this.contentDOM.className = "ab-note-block-content";
7397
+ this.contentDOM.style.cssText = [
7398
+ "font-size: 14px",
7399
+ "font-style: italic",
7400
+ "font-weight: 400",
7401
+ "line-height: 20px",
7402
+ "letter-spacing: -0.1px",
7403
+ "color: #858585"
7404
+ ].join(";");
7405
+ this.dom.appendChild(this.contentDOM);
7406
+ }
7407
+ };
7408
+ var emptyEnterCount = 0;
7409
+ var lastEnterTime = 0;
7410
+ var exitNoteBlockOnEnter = (state, dispatch) => {
7411
+ const { $from } = state.selection;
7412
+ let noteBlockDepth = -1;
7413
+ for (let d = $from.depth; d > 0; d--) {
7414
+ if ($from.node(d).type.name === "noteBlock") {
7415
+ noteBlockDepth = d;
7416
+ break;
7417
+ }
7418
+ }
7419
+ if (noteBlockDepth < 0) {
7420
+ emptyEnterCount = 0;
7421
+ return false;
7422
+ }
7423
+ const parentNode = $from.parent;
7424
+ if (parentNode.type.name !== "paragraph" || parentNode.content.size !== 0) {
7425
+ emptyEnterCount = 0;
7426
+ return false;
7427
+ }
7428
+ const now = Date.now();
7429
+ if (now - lastEnterTime > 2e3) {
7430
+ emptyEnterCount = 0;
7431
+ }
7432
+ lastEnterTime = now;
7433
+ emptyEnterCount++;
7434
+ if (emptyEnterCount < 2) {
7435
+ return false;
7436
+ }
7437
+ emptyEnterCount = 0;
7438
+ if (!dispatch) return true;
7439
+ const paragraphStart = $from.before($from.depth);
7440
+ const paragraphEnd = $from.after($from.depth);
7441
+ const tr = state.tr.delete(paragraphStart, paragraphEnd);
7442
+ const mappedFrom = tr.mapping.map($from.before($from.depth));
7443
+ const $mapped = tr.doc.resolve(Math.min(mappedFrom, tr.doc.content.size));
7444
+ for (let d = $mapped.depth; d > 0; d--) {
7445
+ if ($mapped.node(d).type.name === "noteBlock") {
7446
+ const lastChild = $mapped.node(d).lastChild;
7447
+ if (lastChild && lastChild.type.name === "paragraph" && lastChild.content.size === 0) {
7448
+ const lastChildPos = $mapped.end(d) - lastChild.nodeSize;
7449
+ tr.delete(lastChildPos, lastChildPos + lastChild.nodeSize);
7450
+ }
7451
+ break;
7452
+ }
7453
+ }
7454
+ const noteBlockEnd = tr.mapping.map($from.after(noteBlockDepth));
7455
+ const newParagraph = state.schema.nodes.paragraph.create();
7456
+ tr.insert(noteBlockEnd, newParagraph);
7457
+ tr.setSelection(Selection.near(tr.doc.resolve(noteBlockEnd + 1)));
7458
+ dispatch(tr.scrollIntoView());
7459
+ return true;
7460
+ };
7461
+ function createNoteBlockPlugin() {
7462
+ return {
7463
+ name: "noteBlockNodeView",
7464
+ keymap: () => ({
7465
+ Enter: exitNoteBlockOnEnter
7466
+ }),
7467
+ nodeViews: () => ({
7468
+ noteBlock: (node, view, getPos) => new NoteBlockView(node, view, getPos)
7469
+ })
7470
+ };
7471
+ }
7472
+
6623
7473
  // src/ui/plugin/slashCommandPlugin.ts
6624
7474
  import { Plugin as Plugin7, PluginKey as PluginKey7 } from "prosemirror-state";
6625
7475
  var slashCommandKey = new PluginKey7("slashCommand");
@@ -6689,17 +7539,37 @@ function createSlashCommandPlugin() {
6689
7539
  }
6690
7540
 
6691
7541
  // src/ui/components/SlashCommandMenu.tsx
6692
- import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
7542
+ import React4, { useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState4 } from "react";
6693
7543
  import { createPortal } from "react-dom";
6694
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
7544
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
6695
7545
  function filterItems(items, query) {
6696
7546
  if (!query) return items;
6697
- const q = query.toLowerCase();
6698
- return items.filter(
6699
- ({ title, description }) => title.toLowerCase().includes(q) || (description?.toLowerCase().includes(q) ?? false)
7547
+ const colonIdx = query.indexOf(":");
7548
+ const searchPart = colonIdx >= 0 ? query.slice(colonIdx + 1).trim() : query;
7549
+ if (!searchPart) return items;
7550
+ const words = searchPart.toLowerCase().split(/\s+/);
7551
+ const filtered = items.filter(
7552
+ ({ title, description }) => {
7553
+ const hay = (title + " " + (description ?? "")).toLowerCase();
7554
+ return words.every((w) => hay.includes(w));
7555
+ }
6700
7556
  );
7557
+ return filtered.length > 0 ? filtered : items;
7558
+ }
7559
+ var SCROLLBAR_STYLE_ID = "ab-slash-menu-scrollbar";
7560
+ function injectScrollbarStyle() {
7561
+ if (document.getElementById(SCROLLBAR_STYLE_ID)) return;
7562
+ const style = document.createElement("style");
7563
+ style.id = SCROLLBAR_STYLE_ID;
7564
+ style.textContent = `
7565
+ .ab-slash-menu::-webkit-scrollbar { width: 6px; }
7566
+ .ab-slash-menu::-webkit-scrollbar-track { background: transparent; }
7567
+ .ab-slash-menu::-webkit-scrollbar-thumb { background: #c4c4c4; border-radius: 3px; }
7568
+ .ab-slash-menu::-webkit-scrollbar-thumb:hover { background: #999; }
7569
+ `;
7570
+ document.head.appendChild(style);
6701
7571
  }
6702
- var POPUP_SHADOW = "0 0 0 1px rgba(0,0,0,0.07), 0 4px 20px rgba(0,0,0,0.11), 0 1px 4px rgba(0,0,0,0.05)";
7572
+ var POPUP_SHADOW = "0 8px 10px rgba(13,13,13,0.12), 0 3px 14px rgba(13,13,13,0.08), 0 3px 5px rgba(13,13,13,0.04)";
6703
7573
  var BTN_RESET = {
6704
7574
  border: "none",
6705
7575
  padding: 0,
@@ -6712,11 +7582,14 @@ var BTN_RESET = {
6712
7582
  width: "100%"
6713
7583
  };
6714
7584
  var VPORT_MARGIN = 8;
6715
- var MENU_WIDTH = 280;
6716
- var MAX_MENU_H = 320;
7585
+ var MENU_WIDTH = 440;
7586
+ var MAX_MENU_H = 400;
6717
7587
  function SlashCommandMenu({ view, editorState, items }) {
6718
- const [selectedIndex, setSelectedIndex] = useState3(0);
7588
+ const [selectedIndex, setSelectedIndex] = useState4(0);
6719
7589
  const listRef = useRef3(null);
7590
+ useLayoutEffect(() => {
7591
+ injectScrollbarStyle();
7592
+ }, []);
6720
7593
  const pluginState = editorState ? slashCommandKey.getState(editorState) : void 0;
6721
7594
  const active = pluginState?.active ?? false;
6722
7595
  const range = pluginState?.range ?? null;
@@ -6728,7 +7601,7 @@ function SlashCommandMenu({ view, editorState, items }) {
6728
7601
  useEffect3(() => {
6729
7602
  const list = listRef.current;
6730
7603
  if (!list) return;
6731
- const item = list.children[selectedIndex];
7604
+ const item = list.querySelector(`[data-slash-index="${selectedIndex}"]`);
6732
7605
  item?.scrollIntoView({ block: "nearest" });
6733
7606
  }, [selectedIndex]);
6734
7607
  useEffect3(() => {
@@ -6770,6 +7643,7 @@ function SlashCommandMenu({ view, editorState, items }) {
6770
7643
  /* @__PURE__ */ jsx7(
6771
7644
  "div",
6772
7645
  {
7646
+ className: "ab-slash-menu",
6773
7647
  style: {
6774
7648
  position: "fixed",
6775
7649
  top,
@@ -6777,10 +7651,12 @@ function SlashCommandMenu({ view, editorState, items }) {
6777
7651
  width: MENU_WIDTH,
6778
7652
  maxHeight: MAX_MENU_H,
6779
7653
  overflowY: "auto",
7654
+ scrollbarWidth: "thin",
7655
+ scrollbarColor: "#c4c4c4 transparent",
6780
7656
  background: "#fff",
6781
- borderRadius: 10,
7657
+ borderRadius: 4,
6782
7658
  boxShadow: POPUP_SHADOW,
6783
- padding: 6,
7659
+ padding: "8px 0",
6784
7660
  zIndex: 1100,
6785
7661
  animation: "ab-float-in 0.12s ease"
6786
7662
  },
@@ -6794,100 +7670,103 @@ function SlashCommandMenu({ view, editorState, items }) {
6794
7670
  },
6795
7671
  children: "No results"
6796
7672
  }
6797
- ) : filtered.map((item, i) => /* @__PURE__ */ jsx7(
6798
- SlashMenuItem,
6799
- {
6800
- item,
6801
- selected: i === selectedIndex,
6802
- onMouseEnter: () => setSelectedIndex(i),
6803
- onMouseDown: (e) => {
6804
- e.preventDefault();
6805
- if (range) {
6806
- item.command({ view, range });
6807
- view.focus();
7673
+ ) : filtered.map((item, i) => {
7674
+ const prevGroup = i > 0 ? filtered[i - 1].group : void 0;
7675
+ const showGroupHeader = item.group && item.group !== prevGroup;
7676
+ return /* @__PURE__ */ jsxs6(React4.Fragment, { children: [
7677
+ showGroupHeader && /* @__PURE__ */ jsxs6(Fragment3, { children: [
7678
+ i > 0 && /* @__PURE__ */ jsx7("div", { style: { height: 1, background: "rgba(0,0,0,0.06)", margin: "4px 10px" } }),
7679
+ /* @__PURE__ */ jsx7(
7680
+ "div",
7681
+ {
7682
+ style: {
7683
+ padding: "8px 16px 4px",
7684
+ fontSize: 13,
7685
+ fontWeight: 400,
7686
+ color: "#5e5e5e"
7687
+ },
7688
+ children: item.group
7689
+ }
7690
+ )
7691
+ ] }),
7692
+ /* @__PURE__ */ jsx7(
7693
+ SlashMenuItem,
7694
+ {
7695
+ item,
7696
+ index: i,
7697
+ selected: i === selectedIndex,
7698
+ onMouseEnter: () => setSelectedIndex(i),
7699
+ onMouseDown: (e) => {
7700
+ e.preventDefault();
7701
+ if (range) {
7702
+ item.command({ view, range });
7703
+ view.focus();
7704
+ }
7705
+ }
6808
7706
  }
6809
- }
6810
- },
6811
- item.id ?? item.title
6812
- )) })
7707
+ )
7708
+ ] }, item.id ?? item.title);
7709
+ }) })
6813
7710
  }
6814
7711
  ),
6815
7712
  document.body
6816
7713
  );
6817
7714
  }
6818
- function SlashMenuItem({ item, selected, onMouseEnter, onMouseDown }) {
7715
+ function SlashMenuItem({ item, index, selected, onMouseEnter, onMouseDown }) {
6819
7716
  return /* @__PURE__ */ jsxs6(
6820
7717
  "button",
6821
7718
  {
7719
+ "data-slash-index": index,
6822
7720
  style: {
6823
7721
  ...BTN_RESET,
6824
7722
  display: "flex",
6825
7723
  alignItems: "center",
6826
- gap: 10,
6827
- padding: "7px 10px",
6828
- borderRadius: 7,
6829
- background: selected ? "rgba(99,102,241,0.07)" : "transparent",
7724
+ gap: 12,
7725
+ padding: "6px 16px",
7726
+ background: selected ? "rgba(13,13,13,0.04)" : "transparent",
6830
7727
  transition: "background 0.08s"
6831
7728
  },
6832
7729
  onMouseEnter,
6833
7730
  onMouseDown,
6834
7731
  children: [
6835
- item.icon !== void 0 && /* @__PURE__ */ jsx7(
7732
+ item.icon !== void 0 && /* @__PURE__ */ jsx7("span", { style: { flexShrink: 0, display: "flex", alignItems: "center" }, children: item.icon }),
7733
+ /* @__PURE__ */ jsx7(
6836
7734
  "span",
6837
7735
  {
6838
7736
  style: {
6839
- width: 28,
6840
- height: 28,
6841
- flexShrink: 0,
6842
- display: "flex",
6843
- alignItems: "center",
6844
- justifyContent: "center",
6845
- borderRadius: 6,
6846
- background: "rgba(0,0,0,0.04)",
6847
- fontSize: 13,
6848
- fontWeight: 700,
6849
- color: "#6366f1",
6850
- fontFamily: "monospace"
7737
+ fontSize: 14,
7738
+ fontWeight: 400,
7739
+ color: "#0d0d0d",
7740
+ whiteSpace: "nowrap",
7741
+ overflow: "hidden",
7742
+ textOverflow: "ellipsis",
7743
+ minWidth: 0
6851
7744
  },
6852
- children: item.icon
7745
+ children: item.title
6853
7746
  }
6854
7747
  ),
6855
- /* @__PURE__ */ jsxs6("span", { style: { display: "flex", flexDirection: "column", gap: 1, minWidth: 0 }, children: [
6856
- /* @__PURE__ */ jsx7(
6857
- "span",
6858
- {
6859
- style: {
6860
- fontSize: 13,
6861
- fontWeight: 500,
6862
- color: "#171717",
6863
- whiteSpace: "nowrap",
6864
- overflow: "hidden",
6865
- textOverflow: "ellipsis"
6866
- },
6867
- children: item.title
6868
- }
6869
- ),
6870
- item.description && /* @__PURE__ */ jsx7(
6871
- "span",
6872
- {
6873
- style: {
6874
- fontSize: 11,
6875
- color: "#9ca3af",
6876
- whiteSpace: "nowrap",
6877
- overflow: "hidden",
6878
- textOverflow: "ellipsis"
6879
- },
6880
- children: item.description
6881
- }
6882
- )
6883
- ] })
7748
+ /* @__PURE__ */ jsx7("span", { style: { flex: 1 } }),
7749
+ (item.description || item.shortcut) && /* @__PURE__ */ jsx7(
7750
+ "span",
7751
+ {
7752
+ style: {
7753
+ fontSize: 12,
7754
+ color: "#5e5e5e",
7755
+ whiteSpace: "nowrap",
7756
+ flexShrink: 0,
7757
+ textAlign: "right",
7758
+ fontFamily: item.shortcut ? "monospace" : "inherit"
7759
+ },
7760
+ children: item.shortcut ?? item.description
7761
+ }
7762
+ )
6884
7763
  ]
6885
7764
  }
6886
7765
  );
6887
7766
  }
6888
7767
 
6889
7768
  // src/ui/components/JinjaTreeView.tsx
6890
- import { useState as useState4 } from "react";
7769
+ import { useState as useState5 } from "react";
6891
7770
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
6892
7771
  var BRANCH_COLORS = {
6893
7772
  if: "#6366f1",
@@ -6932,7 +7811,7 @@ function BranchNode({
6932
7811
  ] });
6933
7812
  }
6934
7813
  function StructureNode({ structure, doc: doc2 }) {
6935
- const [expanded, setExpanded] = useState4(true);
7814
+ const [expanded, setExpanded] = useState5(true);
6936
7815
  const firstBranch = structure.branches[0];
6937
7816
  const label = firstBranch?.condition ? `if ${firstBranch.condition}` : "if";
6938
7817
  return /* @__PURE__ */ jsxs7("div", { className: "jinja-tree-structure", children: [
@@ -6965,13 +7844,179 @@ function JinjaTreeView({ doc: doc2, className }) {
6965
7844
  return /* @__PURE__ */ jsx8("div", { className: `jinja-tree ${className || ""}`, children: structures.map((structure) => /* @__PURE__ */ jsx8(StructureNode, { structure, doc: doc2 }, structure.id)) });
6966
7845
  }
6967
7846
 
7847
+ // src/ui/components/DocumentTreeView.tsx
7848
+ import { useState as useState6 } from "react";
7849
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
7850
+ var ICONS = {
7851
+ heading: "H",
7852
+ jumpPoint: "\u2299",
7853
+ jinjaIf: "\u2325",
7854
+ jinjaBranch: "\u2387",
7855
+ resourceTag: "\u229B",
7856
+ noteBlock: "\u270D",
7857
+ endAction: "\u2192"
7858
+ };
7859
+ var COLORS = {
7860
+ heading: "#333",
7861
+ jumpPoint: "#d97706",
7862
+ jinjaIf: "#6366f1",
7863
+ jinjaBranch: "#8b5cf6",
7864
+ resourceTag: "#059669",
7865
+ noteBlock: "#999",
7866
+ endAction: "#059669"
7867
+ };
7868
+ function TreeNodeItem({
7869
+ node,
7870
+ onNavigate,
7871
+ depth,
7872
+ selectedPath,
7873
+ onSelect,
7874
+ path
7875
+ }) {
7876
+ const [collapsed, setCollapsed] = useState6(false);
7877
+ const hasChildren = node.children.length > 0;
7878
+ const icon = ICONS[node.type] || "\u2022";
7879
+ const color = COLORS[node.type] || "#666";
7880
+ const isSelected = selectedPath === path;
7881
+ const headingLevel = node.meta?.level;
7882
+ const headingLabel = headingLevel ? `H${headingLevel}` : icon;
7883
+ return /* @__PURE__ */ jsxs8("div", { style: { paddingLeft: depth > 0 ? 16 : 0 }, children: [
7884
+ /* @__PURE__ */ jsxs8(
7885
+ "div",
7886
+ {
7887
+ className: "doc-tree-node",
7888
+ style: {
7889
+ display: "flex",
7890
+ alignItems: "center",
7891
+ gap: 6,
7892
+ padding: "3px 6px",
7893
+ borderRadius: 4,
7894
+ cursor: onNavigate ? "pointer" : "default",
7895
+ fontSize: 13,
7896
+ lineHeight: "1.5",
7897
+ background: isSelected ? "rgba(99, 102, 241, 0.1)" : "transparent",
7898
+ transition: "background 0.12s ease"
7899
+ },
7900
+ onClick: (e) => {
7901
+ e.stopPropagation();
7902
+ if (hasChildren) setCollapsed(!collapsed);
7903
+ onSelect(path);
7904
+ onNavigate?.(node.blockIndex);
7905
+ },
7906
+ onMouseEnter: (e) => {
7907
+ if (!isSelected) e.currentTarget.style.background = "#f3f4f6";
7908
+ },
7909
+ onMouseLeave: (e) => {
7910
+ if (!isSelected) e.currentTarget.style.background = "transparent";
7911
+ },
7912
+ children: [
7913
+ hasChildren && /* @__PURE__ */ jsx9(
7914
+ "span",
7915
+ {
7916
+ style: {
7917
+ width: 14,
7918
+ textAlign: "center",
7919
+ fontSize: 10,
7920
+ color: "#999",
7921
+ flexShrink: 0,
7922
+ userSelect: "none"
7923
+ },
7924
+ children: collapsed ? "\u25B8" : "\u25BE"
7925
+ }
7926
+ ),
7927
+ !hasChildren && /* @__PURE__ */ jsx9("span", { style: { width: 14, flexShrink: 0 } }),
7928
+ /* @__PURE__ */ jsx9(
7929
+ "span",
7930
+ {
7931
+ style: {
7932
+ display: "inline-flex",
7933
+ alignItems: "center",
7934
+ justifyContent: "center",
7935
+ width: 22,
7936
+ height: 18,
7937
+ borderRadius: 3,
7938
+ fontSize: 10,
7939
+ fontWeight: 700,
7940
+ color: isSelected ? "#6366f1" : color,
7941
+ background: isSelected ? "rgba(99, 102, 241, 0.15)" : `${color}11`,
7942
+ border: `1px solid ${isSelected ? "rgba(99, 102, 241, 0.3)" : `${color}33`}`,
7943
+ flexShrink: 0
7944
+ },
7945
+ children: node.type === "heading" ? headingLabel : icon
7946
+ }
7947
+ ),
7948
+ /* @__PURE__ */ jsx9(
7949
+ "span",
7950
+ {
7951
+ style: {
7952
+ overflow: "hidden",
7953
+ textOverflow: "ellipsis",
7954
+ whiteSpace: "nowrap",
7955
+ color: isSelected ? "#6366f1" : node.type === "noteBlock" ? "#999" : node.type === "endAction" ? "#059669" : "#333",
7956
+ fontStyle: node.type === "noteBlock" ? "italic" : "normal",
7957
+ fontWeight: isSelected ? 600 : node.type === "heading" && headingLevel && headingLevel <= 2 ? 600 : 400
7958
+ },
7959
+ children: node.label
7960
+ }
7961
+ ),
7962
+ node.meta?.tagType != null && /* @__PURE__ */ jsx9(
7963
+ "span",
7964
+ {
7965
+ style: {
7966
+ fontSize: 10,
7967
+ padding: "1px 5px",
7968
+ borderRadius: 999,
7969
+ background: node.type === "endAction" ? "rgba(5, 150, 105, 0.1)" : "rgba(0, 0, 0, 0.04)",
7970
+ color: node.type === "endAction" ? "#059669" : "#999",
7971
+ fontWeight: 500
7972
+ },
7973
+ children: String(node.meta.tagType)
7974
+ }
7975
+ )
7976
+ ]
7977
+ }
7978
+ ),
7979
+ hasChildren && !collapsed && /* @__PURE__ */ jsx9("div", { children: node.children.map((child, i) => /* @__PURE__ */ jsx9(
7980
+ TreeNodeItem,
7981
+ {
7982
+ node: child,
7983
+ onNavigate,
7984
+ depth: depth + 1,
7985
+ selectedPath,
7986
+ onSelect,
7987
+ path: `${path}/${i}`
7988
+ },
7989
+ `${child.type}-${child.blockIndex}-${i}`
7990
+ )) })
7991
+ ] });
7992
+ }
7993
+ function DocumentTreeView({ doc: doc2, className, onNavigate }) {
7994
+ const tree = buildDocumentTree(doc2);
7995
+ const [selectedPath, setSelectedPath] = useState6(null);
7996
+ if (tree.length === 0) {
7997
+ return /* @__PURE__ */ jsx9("div", { className, style: { color: "#999", fontSize: 13, padding: 16 }, children: "No document structure to display." });
7998
+ }
7999
+ return /* @__PURE__ */ jsx9("div", { className, style: { padding: "8px 0", fontFamily: "Inter, sans-serif" }, children: tree.map((node, i) => /* @__PURE__ */ jsx9(
8000
+ TreeNodeItem,
8001
+ {
8002
+ node,
8003
+ onNavigate,
8004
+ depth: 0,
8005
+ selectedPath,
8006
+ onSelect: setSelectedPath,
8007
+ path: `${i}`
8008
+ },
8009
+ `${node.type}-${node.blockIndex}-${i}`
8010
+ )) });
8011
+ }
8012
+
6968
8013
  // src/ui/components/FloatingMenu.tsx
6969
- import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
8014
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
6970
8015
  import { createPortal as createPortal2 } from "react-dom";
6971
- import { setBlockType, toggleMark as toggleMark2, wrapIn } from "prosemirror-commands";
8016
+ import { setBlockType as setBlockType2, toggleMark as toggleMark2, wrapIn } from "prosemirror-commands";
6972
8017
  import { wrapInList as wrapInList2 } from "prosemirror-schema-list";
6973
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
6974
- var { paragraph: paragraph2, heading: heading2, bulletList: bulletList3, orderedList: orderedList3, blockquote: blockquote2 } = actionbookSchema.nodes;
8018
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
8019
+ var { paragraph: paragraph3, heading: heading3, bulletList: bulletList3, orderedList: orderedList3, blockquote: blockquote3 } = actionbookSchema.nodes;
6975
8020
  var {
6976
8021
  bold: bold2,
6977
8022
  italic: italic2,
@@ -6993,7 +8038,7 @@ function hasMark(state, type) {
6993
8038
  function activeHeadingLevel(state) {
6994
8039
  const { $from } = state.selection;
6995
8040
  const node = $from.parent;
6996
- if (node.type === heading2) return node.attrs.level;
8041
+ if (node.type === heading3) return node.attrs.level;
6997
8042
  return null;
6998
8043
  }
6999
8044
  function activeListType(state) {
@@ -7027,7 +8072,7 @@ function currentBlockLabel(state) {
7027
8072
  if (lt === "ordered") return "1. List";
7028
8073
  const { $from } = state.selection;
7029
8074
  for (let d = $from.depth; d > 0; d--) {
7030
- if ($from.node(d).type === blockquote2) return "Quote";
8075
+ if ($from.node(d).type === blockquote3) return "Quote";
7031
8076
  }
7032
8077
  return "Text";
7033
8078
  }
@@ -7036,7 +8081,7 @@ function emptyBlockCursorPos(state) {
7036
8081
  if (!selection.empty) return null;
7037
8082
  const { $from } = selection;
7038
8083
  const parent = $from.parent;
7039
- if ((parent.type === paragraph2 || parent.type === heading2) && parent.content.size === 0) {
8084
+ if ((parent.type === paragraph3 || parent.type === heading3) && parent.content.size === 0) {
7040
8085
  return selection.from;
7041
8086
  }
7042
8087
  return null;
@@ -7058,8 +8103,8 @@ var BTN_RESET2 = {
7058
8103
  lineHeight: 1
7059
8104
  };
7060
8105
  function TBtn({ children, active, title, onMouseDown, style }) {
7061
- const [hover, setHover] = useState5(false);
7062
- return /* @__PURE__ */ jsx9(
8106
+ const [hover, setHover] = useState7(false);
8107
+ return /* @__PURE__ */ jsx10(
7063
8108
  "button",
7064
8109
  {
7065
8110
  ...{ [FLOAT_ATTR]: "" },
@@ -7090,12 +8135,12 @@ function TBtn({ children, active, title, onMouseDown, style }) {
7090
8135
  );
7091
8136
  }
7092
8137
  function Divider() {
7093
- return /* @__PURE__ */ jsx9("div", { style: { width: 1, height: 18, background: "rgba(0,0,0,0.07)", margin: "0 2px", flexShrink: 0 } });
8138
+ return /* @__PURE__ */ jsx10("div", { style: { width: 1, height: 18, background: "rgba(0,0,0,0.07)", margin: "0 2px", flexShrink: 0 } });
7094
8139
  }
7095
8140
  function BlockTypeDropdown({ view, state, label }) {
7096
- const [open, setOpen] = useState5(false);
8141
+ const [open, setOpen] = useState7(false);
7097
8142
  const btnRef = useRef4(null);
7098
- const [hover, setHover] = useState5(false);
8143
+ const [hover, setHover] = useState7(false);
7099
8144
  useEffect4(() => {
7100
8145
  if (!open) return;
7101
8146
  const onDown = (e) => {
@@ -7110,19 +8155,19 @@ function BlockTypeDropdown({ view, state, label }) {
7110
8155
  const listType = activeListType(state);
7111
8156
  const items = [
7112
8157
  { label: "Text", shortLabel: "\xB6", active: !headingLevel && !listType, run: () => {
7113
- setBlockType(paragraph2)(view.state, view.dispatch);
8158
+ setBlockType2(paragraph3)(view.state, view.dispatch);
7114
8159
  view.focus();
7115
8160
  } },
7116
8161
  { label: "Heading 1", shortLabel: "H1", active: headingLevel === 1, run: () => {
7117
- setBlockType(heading2, { level: 1 })(view.state, view.dispatch);
8162
+ setBlockType2(heading3, { level: 1 })(view.state, view.dispatch);
7118
8163
  view.focus();
7119
8164
  } },
7120
8165
  { label: "Heading 2", shortLabel: "H2", active: headingLevel === 2, run: () => {
7121
- setBlockType(heading2, { level: 2 })(view.state, view.dispatch);
8166
+ setBlockType2(heading3, { level: 2 })(view.state, view.dispatch);
7122
8167
  view.focus();
7123
8168
  } },
7124
8169
  { label: "Heading 3", shortLabel: "H3", active: headingLevel === 3, run: () => {
7125
- setBlockType(heading2, { level: 3 })(view.state, view.dispatch);
8170
+ setBlockType2(heading3, { level: 3 })(view.state, view.dispatch);
7126
8171
  view.focus();
7127
8172
  } },
7128
8173
  { label: "Bullet List", shortLabel: "\u2022", active: listType === "bullet", run: () => {
@@ -7144,13 +8189,17 @@ function BlockTypeDropdown({ view, state, label }) {
7144
8189
  view.focus();
7145
8190
  } },
7146
8191
  { label: "Quote", shortLabel: ">", active: false, run: () => {
7147
- wrapIn(blockquote2)(view.state, view.dispatch);
8192
+ const { $from } = view.state.selection;
8193
+ for (let d = $from.depth; d > 0; d--) {
8194
+ if ($from.node(d).type.name === "listItem") return;
8195
+ }
8196
+ wrapIn(blockquote3)(view.state, view.dispatch);
7148
8197
  view.focus();
7149
8198
  } }
7150
8199
  ];
7151
8200
  const btnRect = btnRef.current?.getBoundingClientRect();
7152
- return /* @__PURE__ */ jsxs8(Fragment3, { children: [
7153
- /* @__PURE__ */ jsxs8(
8201
+ return /* @__PURE__ */ jsxs9(Fragment4, { children: [
8202
+ /* @__PURE__ */ jsxs9(
7154
8203
  "button",
7155
8204
  {
7156
8205
  ref: btnRef,
@@ -7178,12 +8227,12 @@ function BlockTypeDropdown({ view, state, label }) {
7178
8227
  },
7179
8228
  children: [
7180
8229
  label,
7181
- /* @__PURE__ */ jsx9("span", { style: { fontSize: 8, opacity: 0.6 }, children: "\u25BE" })
8230
+ /* @__PURE__ */ jsx10("span", { style: { fontSize: 8, opacity: 0.6 }, children: "\u25BE" })
7182
8231
  ]
7183
8232
  }
7184
8233
  ),
7185
8234
  open && btnRect && createPortal2(
7186
- /* @__PURE__ */ jsx9(
8235
+ /* @__PURE__ */ jsx10(
7187
8236
  "div",
7188
8237
  {
7189
8238
  ...{ [FLOAT_ATTR]: "" },
@@ -7199,7 +8248,7 @@ function BlockTypeDropdown({ view, state, label }) {
7199
8248
  minWidth: 168,
7200
8249
  animation: "ab-float-in 0.12s ease"
7201
8250
  },
7202
- children: items.map((item) => /* @__PURE__ */ jsx9(DropdownItem, { item, onRun: () => setOpen(false) }, item.label))
8251
+ children: items.map((item) => /* @__PURE__ */ jsx10(DropdownItem, { item, onRun: () => setOpen(false) }, item.label))
7203
8252
  }
7204
8253
  ),
7205
8254
  document.body
@@ -7207,8 +8256,8 @@ function BlockTypeDropdown({ view, state, label }) {
7207
8256
  ] });
7208
8257
  }
7209
8258
  function DropdownItem({ item, onRun }) {
7210
- const [hover, setHover] = useState5(false);
7211
- return /* @__PURE__ */ jsxs8(
8259
+ const [hover, setHover] = useState7(false);
8260
+ return /* @__PURE__ */ jsxs9(
7212
8261
  "button",
7213
8262
  {
7214
8263
  ...{ [FLOAT_ATTR]: "" },
@@ -7235,15 +8284,15 @@ function DropdownItem({ item, onRun }) {
7235
8284
  transition: "background 0.1s"
7236
8285
  },
7237
8286
  children: [
7238
- /* @__PURE__ */ jsx9("span", { style: { width: 24, flexShrink: 0, textAlign: "center", fontWeight: 700, fontSize: 12, fontFamily: "monospace", color: "#6366f1" }, children: item.shortLabel }),
8287
+ /* @__PURE__ */ jsx10("span", { style: { width: 24, flexShrink: 0, textAlign: "center", fontWeight: 700, fontSize: 12, fontFamily: "monospace", color: "#6366f1" }, children: item.shortLabel }),
7239
8288
  item.label
7240
8289
  ]
7241
8290
  }
7242
8291
  );
7243
8292
  }
7244
8293
  function SelectionToolbar({ view, state, selectionRect }) {
7245
- const [linkMode, setLinkMode] = useState5(false);
7246
- const [linkHref, setLinkHref] = useState5("");
8294
+ const [linkMode, setLinkMode] = useState7(false);
8295
+ const [linkHref, setLinkHref] = useState7("");
7247
8296
  const inputRef = useRef4(null);
7248
8297
  const isBold = hasMark(state, bold2);
7249
8298
  const isItalic = hasMark(state, italic2);
@@ -7259,7 +8308,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7259
8308
  if (top < VPORT_MARGIN2) top = selectionRect.bottom + TOOLBAR_GAP;
7260
8309
  left = Math.max(VPORT_MARGIN2, Math.min(left, window.innerWidth - toolbarW - VPORT_MARGIN2));
7261
8310
  top = Math.max(VPORT_MARGIN2, Math.min(top, window.innerHeight - toolbarH - VPORT_MARGIN2));
7262
- const run = useCallback2(
8311
+ const run = useCallback3(
7263
8312
  (cmd) => {
7264
8313
  cmd(view.state, view.dispatch);
7265
8314
  view.focus();
@@ -7296,10 +8345,10 @@ function SelectionToolbar({ view, state, selectionRect }) {
7296
8345
  view.dispatch(view.state.tr.removeMark(from, to, linkMark));
7297
8346
  view.focus();
7298
8347
  };
7299
- return /* @__PURE__ */ jsx9(
8348
+ return /* @__PURE__ */ jsx10(
7300
8349
  "div",
7301
8350
  {
7302
- ...{ [FLOAT_ATTR]: "" },
8351
+ ...{ [FLOAT_ATTR]: "selection-toolbar" },
7303
8352
  style: {
7304
8353
  position: "fixed",
7305
8354
  left,
@@ -7319,9 +8368,9 @@ function SelectionToolbar({ view, state, selectionRect }) {
7319
8368
  },
7320
8369
  children: linkMode ? (
7321
8370
  /* ── Link input mode ── */
7322
- /* @__PURE__ */ jsxs8(Fragment3, { children: [
7323
- /* @__PURE__ */ jsx9("span", { style: { fontSize: 13, color: "#9ca3af", paddingLeft: 4, flexShrink: 0 }, children: "\u2197" }),
7324
- /* @__PURE__ */ jsx9(
8371
+ /* @__PURE__ */ jsxs9(Fragment4, { children: [
8372
+ /* @__PURE__ */ jsx10("span", { style: { fontSize: 13, color: "#9ca3af", paddingLeft: 4, flexShrink: 0 }, children: "\u2197" }),
8373
+ /* @__PURE__ */ jsx10(
7325
8374
  "input",
7326
8375
  {
7327
8376
  ref: inputRef,
@@ -7352,7 +8401,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7352
8401
  }
7353
8402
  }
7354
8403
  ),
7355
- /* @__PURE__ */ jsx9(
8404
+ /* @__PURE__ */ jsx10(
7356
8405
  TBtn,
7357
8406
  {
7358
8407
  title: "Apply (Enter)",
@@ -7364,7 +8413,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7364
8413
  children: "\u21B5"
7365
8414
  }
7366
8415
  ),
7367
- /* @__PURE__ */ jsx9(
8416
+ /* @__PURE__ */ jsx10(
7368
8417
  TBtn,
7369
8418
  {
7370
8419
  title: "Cancel (Esc)",
@@ -7380,8 +8429,8 @@ function SelectionToolbar({ view, state, selectionRect }) {
7380
8429
  ] })
7381
8430
  ) : (
7382
8431
  /* ── Normal toolbar mode ── */
7383
- /* @__PURE__ */ jsxs8(Fragment3, { children: [
7384
- /* @__PURE__ */ jsx9(
8432
+ /* @__PURE__ */ jsxs9(Fragment4, { children: [
8433
+ /* @__PURE__ */ jsx10(
7385
8434
  TBtn,
7386
8435
  {
7387
8436
  active: isBold,
@@ -7391,7 +8440,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7391
8440
  children: "B"
7392
8441
  }
7393
8442
  ),
7394
- /* @__PURE__ */ jsx9(
8443
+ /* @__PURE__ */ jsx10(
7395
8444
  TBtn,
7396
8445
  {
7397
8446
  active: isItalic,
@@ -7401,7 +8450,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7401
8450
  children: "I"
7402
8451
  }
7403
8452
  ),
7404
- /* @__PURE__ */ jsx9(
8453
+ /* @__PURE__ */ jsx10(
7405
8454
  TBtn,
7406
8455
  {
7407
8456
  active: isUnderline,
@@ -7411,7 +8460,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7411
8460
  children: "U"
7412
8461
  }
7413
8462
  ),
7414
- /* @__PURE__ */ jsx9(
8463
+ /* @__PURE__ */ jsx10(
7415
8464
  TBtn,
7416
8465
  {
7417
8466
  active: isStrike,
@@ -7421,7 +8470,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7421
8470
  children: "S"
7422
8471
  }
7423
8472
  ),
7424
- /* @__PURE__ */ jsx9(
8473
+ /* @__PURE__ */ jsx10(
7425
8474
  TBtn,
7426
8475
  {
7427
8476
  active: isCode,
@@ -7431,7 +8480,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7431
8480
  children: "`\xB7`"
7432
8481
  }
7433
8482
  ),
7434
- /* @__PURE__ */ jsx9(
8483
+ /* @__PURE__ */ jsx10(
7435
8484
  TBtn,
7436
8485
  {
7437
8486
  active: isLink,
@@ -7441,8 +8490,8 @@ function SelectionToolbar({ view, state, selectionRect }) {
7441
8490
  children: "\u2197"
7442
8491
  }
7443
8492
  ),
7444
- /* @__PURE__ */ jsx9(Divider, {}),
7445
- /* @__PURE__ */ jsx9(BlockTypeDropdown, { view, state, label: blockLabel })
8493
+ /* @__PURE__ */ jsx10(Divider, {}),
8494
+ /* @__PURE__ */ jsx10(BlockTypeDropdown, { view, state, label: blockLabel })
7446
8495
  ] })
7447
8496
  )
7448
8497
  }
@@ -7453,7 +8502,7 @@ var BLOCK_ITEMS = [
7453
8502
  shortLabel: "\xB6",
7454
8503
  label: "Text",
7455
8504
  run: (v) => {
7456
- setBlockType(paragraph2)(v.state, v.dispatch);
8505
+ setBlockType2(paragraph3)(v.state, v.dispatch);
7457
8506
  v.focus();
7458
8507
  }
7459
8508
  },
@@ -7461,7 +8510,7 @@ var BLOCK_ITEMS = [
7461
8510
  shortLabel: "H1",
7462
8511
  label: "Heading 1",
7463
8512
  run: (v) => {
7464
- setBlockType(heading2, { level: 1 })(v.state, v.dispatch);
8513
+ setBlockType2(heading3, { level: 1 })(v.state, v.dispatch);
7465
8514
  v.focus();
7466
8515
  }
7467
8516
  },
@@ -7469,7 +8518,7 @@ var BLOCK_ITEMS = [
7469
8518
  shortLabel: "H2",
7470
8519
  label: "Heading 2",
7471
8520
  run: (v) => {
7472
- setBlockType(heading2, { level: 2 })(v.state, v.dispatch);
8521
+ setBlockType2(heading3, { level: 2 })(v.state, v.dispatch);
7473
8522
  v.focus();
7474
8523
  }
7475
8524
  },
@@ -7477,7 +8526,7 @@ var BLOCK_ITEMS = [
7477
8526
  shortLabel: "H3",
7478
8527
  label: "Heading 3",
7479
8528
  run: (v) => {
7480
- setBlockType(heading2, { level: 3 })(v.state, v.dispatch);
8529
+ setBlockType2(heading3, { level: 3 })(v.state, v.dispatch);
7481
8530
  v.focus();
7482
8531
  }
7483
8532
  },
@@ -7512,7 +8561,7 @@ var BLOCK_ITEMS = [
7512
8561
  shortLabel: "\u275D",
7513
8562
  label: "Quote",
7514
8563
  run: (v) => {
7515
- wrapIn(blockquote2)(v.state, v.dispatch);
8564
+ wrapIn(blockquote3)(v.state, v.dispatch);
7516
8565
  v.focus();
7517
8566
  }
7518
8567
  },
@@ -7532,7 +8581,7 @@ var BLOCK_ITEMS = [
7532
8581
  }
7533
8582
  ];
7534
8583
  function EmptyParaHandle({ view, cursorPos }) {
7535
- const [menuOpen, setMenuOpen] = useState5(false);
8584
+ const [menuOpen, setMenuOpen] = useState7(false);
7536
8585
  const coords = view.coordsAtPos(cursorPos);
7537
8586
  const lineH = Math.max(coords.bottom - coords.top, 18);
7538
8587
  const btnSize = 22;
@@ -7550,8 +8599,8 @@ function EmptyParaHandle({ view, cursorPos }) {
7550
8599
  document.addEventListener("mousedown", onDown, true);
7551
8600
  return () => document.removeEventListener("mousedown", onDown, true);
7552
8601
  }, [menuOpen]);
7553
- return /* @__PURE__ */ jsxs8(Fragment3, { children: [
7554
- /* @__PURE__ */ jsx9(
8602
+ return /* @__PURE__ */ jsxs9(Fragment4, { children: [
8603
+ /* @__PURE__ */ jsx10(
7555
8604
  "button",
7556
8605
  {
7557
8606
  ...{ [FLOAT_ATTR]: "" },
@@ -7583,7 +8632,7 @@ function EmptyParaHandle({ view, cursorPos }) {
7583
8632
  children: "+"
7584
8633
  }
7585
8634
  ),
7586
- menuOpen && /* @__PURE__ */ jsx9(
8635
+ menuOpen && /* @__PURE__ */ jsx10(
7587
8636
  "div",
7588
8637
  {
7589
8638
  ...{ [FLOAT_ATTR]: "" },
@@ -7599,7 +8648,7 @@ function EmptyParaHandle({ view, cursorPos }) {
7599
8648
  minWidth: 168,
7600
8649
  animation: "ab-float-in 0.12s ease"
7601
8650
  },
7602
- children: BLOCK_ITEMS.map((item) => /* @__PURE__ */ jsx9(
8651
+ children: BLOCK_ITEMS.map((item) => /* @__PURE__ */ jsx10(
7603
8652
  BlockMenuItem,
7604
8653
  {
7605
8654
  item,
@@ -7617,8 +8666,8 @@ function BlockMenuItem({
7617
8666
  view,
7618
8667
  onRun
7619
8668
  }) {
7620
- const [hover, setHover] = useState5(false);
7621
- return /* @__PURE__ */ jsxs8(
8669
+ const [hover, setHover] = useState7(false);
8670
+ return /* @__PURE__ */ jsxs9(
7622
8671
  "button",
7623
8672
  {
7624
8673
  ...{ [FLOAT_ATTR]: "" },
@@ -7644,7 +8693,7 @@ function BlockMenuItem({
7644
8693
  transition: "background 0.1s"
7645
8694
  },
7646
8695
  children: [
7647
- /* @__PURE__ */ jsx9(
8696
+ /* @__PURE__ */ jsx10(
7648
8697
  "span",
7649
8698
  {
7650
8699
  style: {
@@ -7665,10 +8714,10 @@ function BlockMenuItem({
7665
8714
  );
7666
8715
  }
7667
8716
  function FloatingMenu({ view, editorState }) {
7668
- const [showEmptyHandle, setShowEmptyHandle] = useState5(false);
8717
+ const [showEmptyHandle, setShowEmptyHandle] = useState7(false);
7669
8718
  const dwellTimerRef = useRef4(null);
7670
8719
  const lastEmptyPosRef = useRef4(null);
7671
- const [, setScrollTick] = useState5(0);
8720
+ const [, setScrollTick] = useState7(0);
7672
8721
  useEffect4(() => {
7673
8722
  const editorDom = view?.dom;
7674
8723
  if (!editorDom) return;
@@ -7708,8 +8757,8 @@ function FloatingMenu({ view, editorState }) {
7708
8757
  }
7709
8758
  const selectionRect = hasSelection ? getSelectionDOMRect() : null;
7710
8759
  return createPortal2(
7711
- /* @__PURE__ */ jsxs8(Fragment3, { children: [
7712
- hasSelection && selectionRect && /* @__PURE__ */ jsx9(
8760
+ /* @__PURE__ */ jsxs9(Fragment4, { children: [
8761
+ hasSelection && selectionRect && /* @__PURE__ */ jsx10(
7713
8762
  SelectionToolbar,
7714
8763
  {
7715
8764
  view,
@@ -7717,7 +8766,7 @@ function FloatingMenu({ view, editorState }) {
7717
8766
  selectionRect
7718
8767
  }
7719
8768
  ),
7720
- !hasSelection && showEmptyHandle && emptyPos !== null && /* @__PURE__ */ jsx9(EmptyParaHandle, { view, cursorPos: emptyPos })
8769
+ !hasSelection && showEmptyHandle && emptyPos !== null && /* @__PURE__ */ jsx10(EmptyParaHandle, { view, cursorPos: emptyPos })
7721
8770
  ] }),
7722
8771
  document.body
7723
8772
  );
@@ -7886,6 +8935,7 @@ function createInlineSuggestPlugin(provider, endpoint, options) {
7886
8935
  }
7887
8936
  export {
7888
8937
  ActionbookRenderer,
8938
+ DocumentTreeView,
7889
8939
  EditorShell,
7890
8940
  FloatingMenu,
7891
8941
  JUMP_POINT_ADJACENT_SPEC,
@@ -7908,6 +8958,7 @@ export {
7908
8958
  createKeymapPlugin,
7909
8959
  createLinkPlugin,
7910
8960
  createMarkdownClipboardPlugin,
8961
+ createNoteBlockPlugin,
7911
8962
  createPluginArray,
7912
8963
  createReactNodeView,
7913
8964
  createSlashCommandPlugin,