@sendbird/actionbook-core 0.7.3 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui/index.js CHANGED
@@ -89,6 +89,24 @@ var actionbookSchema = new Schema({
89
89
  },
90
90
  defining: true
91
91
  },
92
+ codeBlock: {
93
+ content: "text*",
94
+ group: "block",
95
+ code: true,
96
+ defining: true,
97
+ marks: "",
98
+ attrs: { language: { default: null } },
99
+ parseDOM: [{
100
+ tag: "pre",
101
+ preserveWhitespace: "full",
102
+ getAttrs(dom) {
103
+ return { language: dom.getAttribute("data-language") || null };
104
+ }
105
+ }],
106
+ toDOM(node) {
107
+ return ["pre", { "data-language": node.attrs.language || void 0 }, ["code", 0]];
108
+ }
109
+ },
92
110
  horizontalRule: {
93
111
  group: "block",
94
112
  parseDOM: [{ tag: "hr" }],
@@ -96,6 +114,61 @@ var actionbookSchema = new Schema({
96
114
  return ["hr"];
97
115
  }
98
116
  },
117
+ // ── Jinja conditional block ──
118
+ jinjaIfBlock: {
119
+ content: "jinjaIfBranch+",
120
+ group: "block",
121
+ isolating: true,
122
+ defining: true,
123
+ parseDOM: [{ tag: "div[data-jinja-if-block]" }],
124
+ toDOM() {
125
+ return ["div", { "data-jinja-if-block": "", class: "jinja-if-block" }, 0];
126
+ }
127
+ },
128
+ jinjaIfBranch: {
129
+ content: "block+",
130
+ isolating: true,
131
+ defining: true,
132
+ attrs: {
133
+ branchType: { default: "if" },
134
+ // 'if' | 'elif' | 'else'
135
+ condition: { default: "" }
136
+ },
137
+ parseDOM: [
138
+ {
139
+ tag: "div[data-jinja-branch]",
140
+ getAttrs(dom) {
141
+ const el = dom;
142
+ return {
143
+ branchType: el.getAttribute("data-branch-type") || "if",
144
+ condition: el.getAttribute("data-condition") || ""
145
+ };
146
+ }
147
+ }
148
+ ],
149
+ toDOM(node) {
150
+ return [
151
+ "div",
152
+ {
153
+ "data-jinja-branch": "",
154
+ "data-branch-type": node.attrs.branchType,
155
+ "data-condition": node.attrs.condition,
156
+ class: `jinja-branch jinja-branch-${node.attrs.branchType}`
157
+ },
158
+ 0
159
+ ];
160
+ }
161
+ },
162
+ noteBlock: {
163
+ content: "block+",
164
+ group: "block",
165
+ defining: true,
166
+ isolating: true,
167
+ parseDOM: [{ tag: "div[data-note-block]" }],
168
+ toDOM() {
169
+ return ["div", { "data-note-block": "", class: "ab-note-block" }, 0];
170
+ }
171
+ },
99
172
  // ── Table nodes ──
100
173
  table: {
101
174
  content: "tableRow+",
@@ -489,6 +562,12 @@ function IconTimeDiff({ size = 16, fill = "currentColor", style }) {
489
562
  }
490
563
  );
491
564
  }
565
+ function IconStop({ size = 16, fill = "currentColor", style }) {
566
+ return /* @__PURE__ */ jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 8 8", width: size, height: size, fill: "none", style, children: [
567
+ /* @__PURE__ */ jsx("path", { d: "M5.4375 3.375L2.0625 3.375V4.125L5.4375 4.125V3.375Z", fill }),
568
+ /* @__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 })
569
+ ] });
570
+ }
492
571
  function IconAnchor({ size = 16, fill = "currentColor", style }) {
493
572
  return /* @__PURE__ */ jsxs(
494
573
  "svg",
@@ -727,21 +806,24 @@ var ReactNodeViewImpl = class {
727
806
  dom;
728
807
  root;
729
808
  component;
809
+ wrapper;
730
810
  _node;
731
811
  _view;
732
812
  _getPos;
733
813
  _selected = false;
734
814
  _decorations = [];
735
- constructor(node, view, getPos, options) {
815
+ constructor(node, view, getPos, decorations, options) {
736
816
  this._node = node;
737
817
  this._view = view;
738
818
  this._getPos = getPos;
819
+ this._decorations = decorations;
739
820
  this.component = options.component;
821
+ this.wrapper = options.wrapper;
740
822
  const tag = options.as ?? (options.inline !== false ? "span" : "div");
741
823
  this.dom = document.createElement(tag);
742
824
  if (options.inline !== false) {
743
- this.dom.style.display = "inline-flex";
744
- this.dom.style.verticalAlign = "middle";
825
+ this.dom.style.display = "inline";
826
+ this.dom.style.verticalAlign = "baseline";
745
827
  }
746
828
  if (options.className) {
747
829
  this.dom.className = options.className;
@@ -750,14 +832,15 @@ var ReactNodeViewImpl = class {
750
832
  this.render();
751
833
  }
752
834
  render() {
835
+ const element = createElement(this.component, {
836
+ node: this._node,
837
+ view: this._view,
838
+ getPos: this._getPos,
839
+ selected: this._selected,
840
+ decorations: this._decorations
841
+ });
753
842
  this.root.render(
754
- createElement(this.component, {
755
- node: this._node,
756
- view: this._view,
757
- getPos: this._getPos,
758
- selected: this._selected,
759
- decorations: this._decorations
760
- })
843
+ this.wrapper ? createElement(this.wrapper, null, element) : element
761
844
  );
762
845
  }
763
846
  update(node, decorations, _innerDecorations) {
@@ -786,7 +869,7 @@ var ReactNodeViewImpl = class {
786
869
  }
787
870
  };
788
871
  function createReactNodeView(options) {
789
- return (node, view, getPos) => new ReactNodeViewImpl(node, view, getPos, options);
872
+ return (node, view, getPos, decorations) => new ReactNodeViewImpl(node, view, getPos, decorations, options);
790
873
  }
791
874
 
792
875
  // src/ui/hooks/useEditorView.ts
@@ -795,9 +878,19 @@ import { EditorState } from "prosemirror-state";
795
878
  import { EditorView } from "prosemirror-view";
796
879
  import { Node as PMNode } from "prosemirror-model";
797
880
 
881
+ // src/ast/types.ts
882
+ var RESOURCE_TAG_TYPES = ["tool", "manual", "agent_message_template", "handoff", "time_diff", "time_difference"];
883
+
798
884
  // src/compat/prosemirror.ts
799
885
  var MAX_DEPTH = 128;
800
886
  var ALLOWED_URL_PROTOCOLS = /^(https?:|mailto:|tel:|#|\/)/i;
887
+ var LIST_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList"]);
888
+ function isLooseList(items) {
889
+ return items.some((li) => {
890
+ const nonListBlocks = li.content.filter((b) => !LIST_TYPES.has(b.type));
891
+ return nonListBlocks.length > 1;
892
+ });
893
+ }
801
894
  function convertPMMark(mark) {
802
895
  switch (mark.type) {
803
896
  case "bold":
@@ -836,12 +929,17 @@ function convertPMInline(node, depth) {
836
929
  }
837
930
  case "inlineToolTag": {
838
931
  const attrs = node.attrs ?? {};
932
+ const tagType = attrs.type ?? "tool";
933
+ const text2 = attrs.text ?? "";
934
+ if (!RESOURCE_TAG_TYPES.includes(tagType)) {
935
+ return text2 ? [{ type: "text", text: text2 }] : [];
936
+ }
839
937
  return [
840
938
  {
841
939
  type: "resourceTag",
842
- tagType: attrs.type ?? "tool",
940
+ tagType,
843
941
  resourceId: attrs.resourceId ?? "",
844
- text: attrs.text ?? ""
942
+ text: text2
845
943
  }
846
944
  ];
847
945
  }
@@ -872,20 +970,27 @@ function convertPMBlock(node, depth = 0) {
872
970
  const level = node.attrs?.level ?? 1;
873
971
  return [{ type: "heading", level, content: children.flatMap((c) => convertPMInline(c, depth + 1)) }];
874
972
  }
875
- case "bulletList":
973
+ case "bulletList": {
974
+ const items = children.map((c) => convertPMListItem(c, depth + 1));
975
+ const spread = isLooseList(items);
876
976
  return [
877
977
  {
878
978
  type: "bulletList",
879
- content: children.map((c) => convertPMListItem(c, depth + 1))
979
+ ...spread ? { spread: true } : {},
980
+ content: items.map((li) => spread ? { ...li, spread: true } : li)
880
981
  }
881
982
  ];
983
+ }
882
984
  case "orderedList": {
883
985
  const start = node.attrs?.start ?? 1;
986
+ const items = children.map((c) => convertPMListItem(c, depth + 1));
987
+ const spread = isLooseList(items);
884
988
  return [
885
989
  {
886
990
  type: "orderedList",
887
991
  start,
888
- content: children.map((c) => convertPMListItem(c, depth + 1))
992
+ ...spread ? { spread: true } : {},
993
+ content: items.map((li) => spread ? { ...li, spread: true } : li)
889
994
  }
890
995
  ];
891
996
  }
@@ -896,6 +1001,12 @@ function convertPMBlock(node, depth = 0) {
896
1001
  return [{ type: "blockquote", content: children.flatMap((c) => convertPMBlock(c, depth + 1)) }];
897
1002
  case "horizontalRule":
898
1003
  return [{ type: "horizontalRule" }];
1004
+ case "codeBlock": {
1005
+ const textContent2 = children.map((c) => c.text ?? "").join("");
1006
+ return [{ type: "codeBlock", content: textContent2, ...node.attrs?.language ? { language: node.attrs.language } : {} }];
1007
+ }
1008
+ case "noteBlock":
1009
+ return [{ type: "noteBlock", content: children.flatMap((c) => convertPMBlock(c, depth + 1)) }];
899
1010
  case "table": {
900
1011
  const rows = children.map((rowNode) => {
901
1012
  const cells = (rowNode.content ?? []).map((cellNode) => {
@@ -912,6 +1023,22 @@ function convertPMBlock(node, depth = 0) {
912
1023
  });
913
1024
  return [{ type: "table", content: rows }];
914
1025
  }
1026
+ case "jinjaIfBlock": {
1027
+ const branches = children.map((branchNode) => {
1028
+ const branchType = branchNode.attrs?.branchType ?? "if";
1029
+ const condition = branchNode.attrs?.condition;
1030
+ const branchContent = (branchNode.content ?? []).flatMap((c) => convertPMBlock(c, depth + 2));
1031
+ return {
1032
+ branchType,
1033
+ ...branchType !== "else" && condition ? { condition } : {},
1034
+ content: branchContent
1035
+ };
1036
+ });
1037
+ if (branches.length === 0) return [];
1038
+ return [{ type: "jinjaIfBlock", branches }];
1039
+ }
1040
+ case "jinjaIfBranch":
1041
+ return children.flatMap((c) => convertPMBlock(c, depth + 1));
915
1042
  default:
916
1043
  if (children.length > 0) {
917
1044
  return children.flatMap((c) => convertPMBlock(c, depth + 1));
@@ -923,11 +1050,11 @@ function convertPMListItem(node, depth = 0) {
923
1050
  if (depth > MAX_DEPTH) return { type: "listItem", content: [{ type: "paragraph", content: [] }] };
924
1051
  const children = node.content ?? [];
925
1052
  const content = children.flatMap((c) => convertPMBlock(c, depth + 1));
926
- const checked = node.attrs?.checked;
927
1053
  const base = {
928
1054
  type: "listItem",
929
1055
  content: content.length > 0 ? content : [{ type: "paragraph", content: [] }]
930
1056
  };
1057
+ const checked = node.attrs?.checked;
931
1058
  if (typeof checked === "boolean") {
932
1059
  base.checked = checked;
933
1060
  }
@@ -1491,7 +1618,7 @@ function listItemWithTaskListItem(node, parent, state, info) {
1491
1618
 
1492
1619
  // src/markdown/plugins/resourceTag.ts
1493
1620
  var RESOURCE_TAG_RE = /\{\{([^:}]+):([^:}]*):([^}]+)\}\}/g;
1494
- var VALID_TYPES = /* @__PURE__ */ new Set(["tool", "manual", "agent_message_template", "handoff", "time_diff"]);
1621
+ var VALID_TYPES = /* @__PURE__ */ new Set(["tool", "manual", "agent_message_template", "handoff", "end_call", "time_diff"]);
1495
1622
  function splitTextWithResourceTags(text2) {
1496
1623
  const results = [];
1497
1624
  let lastIndex = 0;
@@ -1654,10 +1781,11 @@ function convertBlock(node, depth = 0) {
1654
1781
  case "list": {
1655
1782
  const list = node;
1656
1783
  const items = list.children.map((li) => convertListItem(li, depth + 1));
1784
+ const spread = list.spread ?? false;
1657
1785
  if (list.ordered) {
1658
- return [{ type: "orderedList", start: list.start ?? 1, content: items }];
1786
+ return [{ type: "orderedList", start: list.start ?? 1, spread, content: items }];
1659
1787
  }
1660
- return [{ type: "bulletList", content: items }];
1788
+ return [{ type: "bulletList", spread, content: items }];
1661
1789
  }
1662
1790
  case "blockquote": {
1663
1791
  const bq = node;
@@ -1666,6 +1794,10 @@ function convertBlock(node, depth = 0) {
1666
1794
  }
1667
1795
  case "thematicBreak":
1668
1796
  return [{ type: "horizontalRule" }];
1797
+ case "code": {
1798
+ const codeNode = node;
1799
+ return [{ type: "codeBlock", content: codeNode.value, ...codeNode.lang ? { language: codeNode.lang } : {} }];
1800
+ }
1669
1801
  case "jinjaIfBlockMdast": {
1670
1802
  const jNode = node;
1671
1803
  const branches = jNode.branches.map((b) => ({
@@ -1706,7 +1838,11 @@ function convertBlock(node, depth = 0) {
1706
1838
  function convertListItem(node, depth = 0) {
1707
1839
  if (depth > MAX_DEPTH2) throw new DepthError(depth);
1708
1840
  const content = node.children.flatMap((child) => convertBlock(child, depth + 1));
1709
- const base = { type: "listItem", content: content.length > 0 ? content : [{ type: "paragraph", content: [] }] };
1841
+ const base = {
1842
+ type: "listItem",
1843
+ spread: node.spread ?? false,
1844
+ content: content.length > 0 ? content : [{ type: "paragraph", content: [] }]
1845
+ };
1710
1846
  if (typeof node.checked === "boolean") {
1711
1847
  return { ...base, checked: node.checked };
1712
1848
  }
@@ -1805,31 +1941,39 @@ function blockToMdast(node, depth = 0) {
1805
1941
  return [{ type: "paragraph", children: node.content.flatMap((c) => inlineToMdast(c, depth + 1)) }];
1806
1942
  case "heading":
1807
1943
  return [{ type: "heading", depth: node.level, children: node.content.flatMap((c) => inlineToMdast(c, depth + 1)) }];
1808
- case "bulletList":
1944
+ case "bulletList": {
1945
+ const items = node.content.map((li) => listItemToMdast(li, depth + 1));
1809
1946
  return [
1810
1947
  {
1811
1948
  type: "list",
1812
1949
  ordered: false,
1813
- spread: false,
1814
- children: node.content.map((li) => listItemToMdast(li, depth + 1))
1950
+ spread: node.spread ?? false,
1951
+ children: items
1815
1952
  }
1816
1953
  ];
1817
- case "orderedList":
1954
+ }
1955
+ case "orderedList": {
1956
+ const items = node.content.map((li) => listItemToMdast(li, depth + 1));
1818
1957
  return [
1819
1958
  {
1820
1959
  type: "list",
1821
1960
  ordered: true,
1822
1961
  start: node.start,
1823
- spread: false,
1824
- children: node.content.map((li) => listItemToMdast(li, depth + 1))
1962
+ spread: node.spread ?? false,
1963
+ children: items
1825
1964
  }
1826
1965
  ];
1966
+ }
1827
1967
  case "listItem":
1828
1968
  return listItemToMdast(node, depth).children.flatMap((child) => [child]);
1829
1969
  case "blockquote":
1830
1970
  return [{ type: "blockquote", children: node.content.flatMap((c) => blockToMdast(c, depth + 1)) }];
1831
1971
  case "horizontalRule":
1832
1972
  return [{ type: "thematicBreak" }];
1973
+ case "codeBlock": {
1974
+ const cb = node;
1975
+ return [{ type: "code", value: cb.content, ...cb.language ? { lang: cb.language } : {} }];
1976
+ }
1833
1977
  case "jinjaIfBlock": {
1834
1978
  const jNode = node;
1835
1979
  const result = [];
@@ -1853,14 +1997,18 @@ function blockToMdast(node, depth = 0) {
1853
1997
  }));
1854
1998
  return [{ type: "table", children: rows }];
1855
1999
  }
2000
+ case "noteBlock":
2001
+ return [];
1856
2002
  }
1857
2003
  }
1858
2004
  function listItemToMdast(node, depth = 0) {
1859
2005
  if (depth > MAX_DEPTH2) throw new DepthError(depth);
2006
+ const children = node.content.flatMap((child) => blockToMdast(child, depth + 1));
2007
+ const isSpread = node.spread ?? false;
1860
2008
  const result = {
1861
2009
  type: "listItem",
1862
- spread: false,
1863
- children: node.content.flatMap((child) => blockToMdast(child, depth + 1))
2010
+ spread: isSpread,
2011
+ children
1864
2012
  };
1865
2013
  if (typeof node.checked === "boolean") {
1866
2014
  result.checked = node.checked;
@@ -1893,12 +2041,20 @@ function textHandler(node, parent, state, info) {
1893
2041
  state.unsafe = originalUnsafe;
1894
2042
  return result;
1895
2043
  }
2044
+ function linkHandler(node, parent, state, info) {
2045
+ const childrenText = state.containerPhrasing(node, { ...info, before: "[", after: "]" });
2046
+ const url = node.url ?? "";
2047
+ const title = node.title;
2048
+ const titlePart = title ? ` "${title.replace(/"/g, '\\"')}"` : "";
2049
+ return `[${childrenText}](${url}${titlePart})`;
2050
+ }
1896
2051
  function serializeToMarkdown(doc2) {
1897
2052
  const mdastTree = toMdast(doc2);
1898
- const result = toMarkdown(mdastTree, {
2053
+ const raw = toMarkdown(mdastTree, {
1899
2054
  bullet: "-",
1900
2055
  rule: "-",
1901
2056
  listItemIndent: "one",
2057
+ incrementListMarker: true,
1902
2058
  emphasis: "*",
1903
2059
  strong: "*",
1904
2060
  resourceLink: true,
@@ -1910,11 +2066,13 @@ function serializeToMarkdown(doc2) {
1910
2066
  ],
1911
2067
  handlers: {
1912
2068
  text: textHandler,
2069
+ link: linkHandler,
1913
2070
  ...resourceTagToMarkdown().handlers,
1914
2071
  ...jumpPointToMarkdown().handlers
1915
2072
  },
1916
2073
  extensions: [gfmNoAutolinkToMarkdown()]
1917
2074
  });
2075
+ let result = raw.replace(/^(\s*)\\>/gm, "$1>").replace(/(\S) *>/g, (match, pre) => `${pre} >`).replace(/\\\[([^\]]*)\]/g, "\\[$1\\]").replace(/ /g, " ");
1918
2076
  return result;
1919
2077
  }
1920
2078
 
@@ -1996,24 +2154,26 @@ function convertInline2(node) {
1996
2154
  return assertNever(node);
1997
2155
  }
1998
2156
  }
1999
- var MAX_LIST_DEPTH = 3;
2000
- function convertBlockToArray(node, listDepth = 0) {
2157
+ function convertBlockToArray(node) {
2001
2158
  if (node.type === "jinjaIfBlock") {
2002
- const jNode = node;
2003
- const result = [];
2004
- for (const branch of jNode.branches) {
2005
- const tagText = branch.branchType === "else" ? "{% else %}" : `{% ${branch.branchType} ${branch.condition ?? ""} %}`.trim();
2006
- result.push({ type: "paragraph", content: [{ type: "text", text: tagText }] });
2007
- for (const child of branch.content) {
2008
- result.push(...convertBlockToArray(child, listDepth));
2009
- }
2010
- }
2011
- result.push({ type: "paragraph", content: [{ type: "text", text: "{% endif %}" }] });
2012
- return result;
2159
+ return [convertJinjaIfBlock(node)];
2013
2160
  }
2014
- return [convertBlock2(node, listDepth)];
2161
+ return [convertBlock2(node)];
2162
+ }
2163
+ function convertJinjaIfBlock(node) {
2164
+ return {
2165
+ type: "jinjaIfBlock",
2166
+ content: node.branches.map((branch) => ({
2167
+ type: "jinjaIfBranch",
2168
+ attrs: {
2169
+ branchType: branch.branchType,
2170
+ condition: branch.condition ?? ""
2171
+ },
2172
+ content: branch.content.length > 0 ? branch.content.flatMap(convertBlockToArray) : [{ type: "paragraph", content: [] }]
2173
+ }))
2174
+ };
2015
2175
  }
2016
- function convertBlock2(node, listDepth = 0) {
2176
+ function convertBlock2(node) {
2017
2177
  switch (node.type) {
2018
2178
  case "paragraph":
2019
2179
  return {
@@ -2026,29 +2186,21 @@ function convertBlock2(node, listDepth = 0) {
2026
2186
  attrs: { level: node.level },
2027
2187
  content: node.content.flatMap(convertInlineToArray)
2028
2188
  };
2029
- case "bulletList": {
2030
- if (listDepth >= MAX_LIST_DEPTH) {
2031
- return flattenListToBlock(node);
2032
- }
2189
+ case "bulletList":
2033
2190
  return {
2034
2191
  type: "bulletList",
2035
- content: node.content.map((c) => convertBlock2(c, listDepth + 1))
2192
+ content: node.content.map(convertBlock2)
2036
2193
  };
2037
- }
2038
- case "orderedList": {
2039
- if (listDepth >= MAX_LIST_DEPTH) {
2040
- return flattenListToBlock(node);
2041
- }
2194
+ case "orderedList":
2042
2195
  return {
2043
2196
  type: "orderedList",
2044
2197
  attrs: { start: node.start },
2045
- content: node.content.map((c) => convertBlock2(c, listDepth + 1))
2198
+ content: node.content.map(convertBlock2)
2046
2199
  };
2047
- }
2048
2200
  case "listItem": {
2049
2201
  const result = {
2050
2202
  type: "listItem",
2051
- content: node.content.flatMap((c) => convertBlockToArray(c, listDepth))
2203
+ content: node.content.flatMap(convertBlockToArray)
2052
2204
  };
2053
2205
  if (node.checked != null) {
2054
2206
  result.attrs = { checked: node.checked };
@@ -2058,10 +2210,23 @@ function convertBlock2(node, listDepth = 0) {
2058
2210
  case "blockquote":
2059
2211
  return {
2060
2212
  type: "blockquote",
2061
- content: node.content.flatMap((c) => convertBlockToArray(c, listDepth))
2213
+ content: node.content.flatMap(convertBlockToArray)
2062
2214
  };
2063
2215
  case "horizontalRule":
2064
2216
  return { type: "horizontalRule" };
2217
+ case "codeBlock": {
2218
+ const cb = node;
2219
+ return {
2220
+ type: "codeBlock",
2221
+ attrs: { language: cb.language ?? null },
2222
+ content: cb.content ? [{ type: "text", text: cb.content }] : void 0
2223
+ };
2224
+ }
2225
+ case "noteBlock":
2226
+ return {
2227
+ type: "noteBlock",
2228
+ content: node.content.flatMap(convertBlockToArray)
2229
+ };
2065
2230
  case "table":
2066
2231
  return {
2067
2232
  type: "table",
@@ -2073,53 +2238,16 @@ function convertBlock2(node, listDepth = 0) {
2073
2238
  }))
2074
2239
  }))
2075
2240
  };
2076
- case "jinjaIfBlock": {
2077
- const jNode = node;
2078
- const result = [];
2079
- for (const branch of jNode.branches) {
2080
- const tagText = branch.branchType === "else" ? "{% else %}" : `{% ${branch.branchType} ${branch.condition ?? ""} %}`.trim();
2081
- result.push({ type: "paragraph", content: [{ type: "text", text: tagText }] });
2082
- for (const child of branch.content) {
2083
- result.push(...convertBlockToArray(child, listDepth));
2084
- }
2085
- }
2086
- result.push({ type: "paragraph", content: [{ type: "text", text: "{% endif %}" }] });
2087
- return result[0] ?? { type: "paragraph", content: [] };
2088
- }
2241
+ case "jinjaIfBlock":
2242
+ return convertJinjaIfBlock(node);
2089
2243
  default:
2090
2244
  return assertNever(node);
2091
2245
  }
2092
2246
  }
2093
- function flattenListToBlock(node) {
2094
- const paragraphs = [];
2095
- function extractInlines(n) {
2096
- switch (n.type) {
2097
- case "paragraph":
2098
- case "heading":
2099
- paragraphs.push({
2100
- type: "paragraph",
2101
- content: n.content.flatMap(convertInlineToArray)
2102
- });
2103
- break;
2104
- case "listItem":
2105
- case "blockquote":
2106
- for (const child of n.content) extractInlines(child);
2107
- break;
2108
- case "bulletList":
2109
- case "orderedList":
2110
- for (const item of n.content) extractInlines(item);
2111
- break;
2112
- default:
2113
- break;
2114
- }
2115
- }
2116
- extractInlines(node);
2117
- return paragraphs.length > 0 ? paragraphs[0] : { type: "paragraph", content: [] };
2118
- }
2119
2247
  function toProseMirrorJSON(doc2) {
2120
2248
  return {
2121
2249
  type: "doc",
2122
- content: doc2.content.flatMap((c) => convertBlockToArray(c, 0))
2250
+ content: doc2.content.flatMap(convertBlockToArray)
2123
2251
  };
2124
2252
  }
2125
2253
  function astNodesToJSONContent(nodes) {
@@ -2143,16 +2271,30 @@ function pmDocToAST(pmDoc) {
2143
2271
  return fromProseMirrorJSON(json);
2144
2272
  }
2145
2273
  function useEditorView(config) {
2146
- const viewRef = useRef(null);
2147
2274
  const viewInstanceRef = useRef(null);
2275
+ const containerRef = useRef(null);
2148
2276
  const [, forceUpdate] = useState(0);
2149
2277
  const configRef = useRef(config);
2150
2278
  configRef.current = config;
2151
2279
  useEffect(() => {
2152
- const container = viewRef.current;
2153
- if (!container) return;
2154
- const { pmPlugins, nodeViews } = createPluginArray(config.plugins ?? []);
2155
- const state = docToState(config.content, pmPlugins);
2280
+ return () => {
2281
+ viewInstanceRef.current?.destroy();
2282
+ viewInstanceRef.current = null;
2283
+ };
2284
+ }, []);
2285
+ const viewRef = useCallback((container) => {
2286
+ if (container === containerRef.current) return;
2287
+ if (viewInstanceRef.current) {
2288
+ viewInstanceRef.current.destroy();
2289
+ viewInstanceRef.current = null;
2290
+ }
2291
+ containerRef.current = container;
2292
+ if (!container) {
2293
+ forceUpdate((n) => n + 1);
2294
+ return;
2295
+ }
2296
+ const { pmPlugins, nodeViews } = createPluginArray(configRef.current.plugins ?? []);
2297
+ const state = docToState(configRef.current.content, pmPlugins);
2156
2298
  const view = new EditorView(container, {
2157
2299
  state,
2158
2300
  nodeViews,
@@ -2169,10 +2311,6 @@ function useEditorView(config) {
2169
2311
  });
2170
2312
  viewInstanceRef.current = view;
2171
2313
  forceUpdate((n) => n + 1);
2172
- return () => {
2173
- view.destroy();
2174
- viewInstanceRef.current = null;
2175
- };
2176
2314
  }, []);
2177
2315
  const getAST = useCallback(() => {
2178
2316
  const view = viewInstanceRef.current;
@@ -2190,6 +2328,7 @@ function useEditorView(config) {
2190
2328
  const pmJSON = toProseMirrorJSON(doc2);
2191
2329
  const pmDoc = PMNode.fromJSON(actionbookSchema, pmJSON);
2192
2330
  const tr = view.state.tr.replaceWith(0, view.state.doc.content.size, pmDoc.content);
2331
+ tr.setMeta("addToHistory", false);
2193
2332
  view.dispatch(tr);
2194
2333
  }, []);
2195
2334
  const focus = useCallback(() => {
@@ -2199,9 +2338,11 @@ function useEditorView(config) {
2199
2338
  viewInstanceRef.current?.destroy();
2200
2339
  viewInstanceRef.current = null;
2201
2340
  }, []);
2341
+ const getView = useCallback(() => viewInstanceRef.current, []);
2202
2342
  return {
2203
2343
  viewRef,
2204
2344
  view: viewInstanceRef.current,
2345
+ getView,
2205
2346
  getAST,
2206
2347
  getMarkdown,
2207
2348
  setContent,
@@ -2211,10 +2352,15 @@ function useEditorView(config) {
2211
2352
  }
2212
2353
 
2213
2354
  // src/ui/plugin/inputRulesPlugin.ts
2214
- import { InputRule, wrappingInputRule } from "prosemirror-inputrules";
2355
+ import { InputRule, textblockTypeInputRule, wrappingInputRule } from "prosemirror-inputrules";
2356
+ import { TextSelection } from "prosemirror-state";
2357
+ var HEADING_RE = /^(#{1,6})\s$/;
2358
+ var BLOCKQUOTE_RE = /^\s*>\s$/;
2359
+ var CODE_BLOCK_RE = /^```([a-zA-Z0-9]*)$/;
2360
+ var HR_RE = /^([-*_])\1{2,}$/;
2215
2361
  var BULLET_LIST_RE = /^\s*([-*])\s$/;
2216
2362
  var ORDERED_LIST_RE = /^(\d+)\.\s$/;
2217
- var JUMP_POINT_RE2 = /\^([A-Za-z_][A-Za-z0-9_]*)\^$/;
2363
+ var JUMP_POINT_RE2 = /\^([A-Za-z_]+)\^$/;
2218
2364
  var BOLD_STAR_RE = /(?:^|[^*])\*\*([^*]+)\*\*$/;
2219
2365
  var BOLD_UNDER_RE = /(?:^|[^_])__([^_]+)__$/;
2220
2366
  var ITALIC_STAR_RE = /(?:^|[^*])\*([^*]+)\*$/;
@@ -2225,11 +2371,14 @@ function markInputRule(pattern, markType, markerLen) {
2225
2371
  return new InputRule(pattern, (state, match, start, end) => {
2226
2372
  const textContent2 = match[1];
2227
2373
  const fullMatch = match[0];
2228
- const actualStart = end - fullMatch.length + (fullMatch.length - markerLen * 2 - textContent2.length);
2374
+ const markedLength = markerLen * 2 + textContent2.length;
2375
+ const prefixLen = fullMatch.length - markedLength;
2376
+ const deleteFrom = start + prefixLen;
2229
2377
  const tr = state.tr;
2230
- tr.delete(actualStart, end);
2378
+ tr.delete(deleteFrom, end);
2231
2379
  const textNode = state.schema.text(textContent2, [markType.create()]);
2232
- tr.insert(actualStart, textNode);
2380
+ tr.insert(deleteFrom, textNode);
2381
+ tr.removeStoredMark(markType);
2233
2382
  return tr;
2234
2383
  });
2235
2384
  }
@@ -2238,12 +2387,41 @@ function createInputRulesPlugin() {
2238
2387
  name: "inputRules",
2239
2388
  inputRules: () => {
2240
2389
  const rules = [];
2241
- rules.push(wrappingInputRule(BULLET_LIST_RE, actionbookSchema.nodes.bulletList));
2242
2390
  rules.push(
2243
- wrappingInputRule(ORDERED_LIST_RE, actionbookSchema.nodes.orderedList, (match) => ({
2244
- start: +match[1]
2391
+ textblockTypeInputRule(HEADING_RE, actionbookSchema.nodes.heading, (match) => ({
2392
+ level: match[1].length
2245
2393
  }))
2246
2394
  );
2395
+ rules.push(wrappingInputRule(BLOCKQUOTE_RE, actionbookSchema.nodes.blockquote));
2396
+ rules.push(
2397
+ new InputRule(CODE_BLOCK_RE, (state, match, start, end) => {
2398
+ const language = match[1] || null;
2399
+ const $start = state.doc.resolve(start);
2400
+ const blockStart = $start.before($start.depth);
2401
+ const blockEnd = $start.after($start.depth);
2402
+ const tr = state.tr;
2403
+ tr.replaceWith(
2404
+ blockStart,
2405
+ blockEnd,
2406
+ actionbookSchema.nodes.codeBlock.create({ language })
2407
+ );
2408
+ return tr;
2409
+ })
2410
+ );
2411
+ rules.push(
2412
+ new InputRule(HR_RE, (state, _match, start, end) => {
2413
+ const { horizontalRule: hrType, paragraph: pType } = actionbookSchema.nodes;
2414
+ const $start = state.doc.resolve(start);
2415
+ const blockStart = $start.before($start.depth);
2416
+ const blockEnd = $start.after($start.depth);
2417
+ const tr = state.tr;
2418
+ tr.replaceWith(blockStart, blockEnd, [hrType.create(), pType.create()]);
2419
+ tr.setSelection(
2420
+ TextSelection.near(tr.doc.resolve(blockStart + hrType.createAndFill().nodeSize + 1))
2421
+ );
2422
+ return tr;
2423
+ })
2424
+ );
2247
2425
  rules.push(
2248
2426
  new InputRule(/^\s*[-*]\s\[([ xX])\]\s$/, (state, match, start, end) => {
2249
2427
  const checked = match[1] !== " ";
@@ -2253,6 +2431,12 @@ function createInputRulesPlugin() {
2253
2431
  return state.tr.delete(start, end).insert(start, list);
2254
2432
  })
2255
2433
  );
2434
+ rules.push(wrappingInputRule(BULLET_LIST_RE, actionbookSchema.nodes.bulletList));
2435
+ rules.push(
2436
+ wrappingInputRule(ORDERED_LIST_RE, actionbookSchema.nodes.orderedList, (match) => ({
2437
+ start: +match[1]
2438
+ }))
2439
+ );
2256
2440
  const jumpPointType = actionbookSchema.nodes.jumpPoint;
2257
2441
  rules.push(
2258
2442
  new InputRule(JUMP_POINT_RE2, (state, match, start, end) => {
@@ -2279,24 +2463,85 @@ function createInputRulesPlugin() {
2279
2463
  };
2280
2464
  }
2281
2465
 
2466
+ // src/ui/plugin/historyPlugin.ts
2467
+ import { history, undo, redo } from "prosemirror-history";
2468
+ import { keymap as keymap2 } from "prosemirror-keymap";
2469
+ function createHistoryPlugin() {
2470
+ return {
2471
+ name: "history",
2472
+ plugins: () => [
2473
+ history(),
2474
+ keymap2({ "Mod-z": undo, "Mod-Shift-z": redo, "Mod-y": redo })
2475
+ ]
2476
+ };
2477
+ }
2478
+
2282
2479
  // src/ui/plugin/keymapPlugin.ts
2283
- import { chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock, toggleMark } from "prosemirror-commands";
2480
+ import { baseKeymap, chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock, toggleMark, setBlockType, joinBackward } from "prosemirror-commands";
2481
+ import { keymap as keymap3 } from "prosemirror-keymap";
2284
2482
  import { liftListItem, sinkListItem, splitListItem, wrapInList } from "prosemirror-schema-list";
2285
2483
  var TAB_CHAR = " ";
2286
- var { listItem, bulletList, orderedList, hardBreak } = actionbookSchema.nodes;
2484
+ var { listItem, bulletList, orderedList, hardBreak, heading, paragraph, horizontalRule, blockquote } = actionbookSchema.nodes;
2287
2485
  var { bold: boldMark, italic: italicMark, underline: underlineMark, strikethrough: strikethroughMark, code: codeMark } = actionbookSchema.marks;
2288
2486
  function cursorDirectlyInListItem(state) {
2289
2487
  const { $from } = state.selection;
2290
2488
  return $from.depth >= 2 && $from.node($from.depth - 1).type === listItem;
2291
2489
  }
2292
- var backspaceCommand = (state, dispatch) => {
2490
+ var backspaceAfterList = (state, dispatch) => {
2293
2491
  const { $from } = state.selection;
2294
2492
  if (!state.selection.empty) return false;
2295
2493
  if ($from.parentOffset !== 0) return false;
2296
- if (!cursorDirectlyInListItem(state)) return false;
2297
- if ($from.index($from.depth - 1) !== 0) return false;
2298
- return liftListItem(listItem)(state, dispatch);
2494
+ if ($from.parent.type !== paragraph) return false;
2495
+ if ($from.parent.content.size !== 0) return false;
2496
+ const parentDepth = $from.depth - 1;
2497
+ if (parentDepth < 0) return false;
2498
+ const indexInParent = $from.index(parentDepth);
2499
+ if (indexInParent === 0) return false;
2500
+ const prevNode = $from.node(parentDepth).child(indexInParent - 1);
2501
+ const isList = prevNode.type === bulletList || prevNode.type === orderedList;
2502
+ if (!isList) return false;
2503
+ if (dispatch) {
2504
+ const from = $from.before($from.depth);
2505
+ const to = $from.after($from.depth);
2506
+ const tr = state.tr.delete(from, to);
2507
+ dispatch(tr.scrollIntoView());
2508
+ }
2509
+ return true;
2510
+ };
2511
+ var backspaceAfterLeafBlock = (state, dispatch) => {
2512
+ const { $from } = state.selection;
2513
+ if (!state.selection.empty) return false;
2514
+ if ($from.parentOffset !== 0) return false;
2515
+ if ($from.parent.type !== paragraph) return false;
2516
+ if ($from.parent.content.size !== 0) return false;
2517
+ const parentDepth = $from.depth - 1;
2518
+ if (parentDepth < 0) return false;
2519
+ const indexInParent = $from.index(parentDepth);
2520
+ if (indexInParent === 0) return false;
2521
+ const prevNode = $from.node(parentDepth).child(indexInParent - 1);
2522
+ if (prevNode.type !== horizontalRule) return false;
2523
+ if (dispatch) {
2524
+ const hrPos = $from.posAtIndex(indexInParent - 1, parentDepth);
2525
+ dispatch(state.tr.delete(hrPos, hrPos + prevNode.nodeSize).scrollIntoView());
2526
+ }
2527
+ return true;
2299
2528
  };
2529
+ var backspaceCommand = chainCommands(
2530
+ backspaceAfterList,
2531
+ backspaceAfterLeafBlock,
2532
+ (state, dispatch) => {
2533
+ const { $from } = state.selection;
2534
+ if (!state.selection.empty) return false;
2535
+ if ($from.parentOffset !== 0) return false;
2536
+ if ($from.parent.type === heading) {
2537
+ return setBlockType(paragraph)(state, dispatch);
2538
+ }
2539
+ if (!cursorDirectlyInListItem(state)) return false;
2540
+ if ($from.index($from.depth - 1) !== 0) return false;
2541
+ return liftListItem(listItem)(state, dispatch);
2542
+ },
2543
+ joinBackward
2544
+ );
2300
2545
  var tabCommand = (state, dispatch) => {
2301
2546
  const { $from } = state.selection;
2302
2547
  if (cursorDirectlyInListItem(state) && $from.parentOffset === 0) {
@@ -2349,12 +2594,13 @@ function createKeymapPlugin() {
2349
2594
  "Mod-e": toggleMark(codeMark),
2350
2595
  "Mod-Shift-7": wrapInList(bulletList),
2351
2596
  "Mod-Shift-8": wrapInList(orderedList)
2352
- })
2597
+ }),
2598
+ plugins: () => [keymap3(baseKeymap)]
2353
2599
  };
2354
2600
  }
2355
2601
 
2356
2602
  // src/ui/plugin/markdownClipboard.ts
2357
- import { Fragment, Slice } from "prosemirror-model";
2603
+ import { DOMParser as ProseMirrorDOMParser, Fragment, Slice } from "prosemirror-model";
2358
2604
  import { Plugin, PluginKey } from "prosemirror-state";
2359
2605
 
2360
2606
  // src/ast/traverse.ts
@@ -2370,6 +2616,7 @@ function getChildren(node) {
2370
2616
  case "doc":
2371
2617
  case "blockquote":
2372
2618
  case "listItem":
2619
+ case "noteBlock":
2373
2620
  return node.content;
2374
2621
  case "paragraph":
2375
2622
  case "heading":
@@ -3841,6 +4088,23 @@ var gfmNoAutolinkFromMarkdown = () => [
3841
4088
  gfmTaskListItemFromMarkdown()
3842
4089
  ];
3843
4090
  var MAX_DEPTH5 = 128;
4091
+ var BRACKET_LINK_RE = /\[([^\]]+)\]\(([^)]*\s[^)]*)\)/g;
4092
+ function splitTextWithBracketLinks(value) {
4093
+ const results = [];
4094
+ let lastIndex = 0;
4095
+ for (const m of value.matchAll(BRACKET_LINK_RE)) {
4096
+ const idx = m.index;
4097
+ if (idx > lastIndex) results.push({ type: "text", value: value.slice(lastIndex, idx) });
4098
+ results.push({
4099
+ type: "link",
4100
+ url: m[2],
4101
+ children: [{ type: "text", value: m[1] }]
4102
+ });
4103
+ lastIndex = idx + m[0].length;
4104
+ }
4105
+ if (lastIndex < value.length) results.push({ type: "text", value: value.slice(lastIndex) });
4106
+ return results;
4107
+ }
3844
4108
  function combinedTokensFromMarkdown(node, depth = 0) {
3845
4109
  if (depth > MAX_DEPTH5) return;
3846
4110
  if (!node.children) return;
@@ -3852,13 +4116,20 @@ function combinedTokensFromMarkdown(node, depth = 0) {
3852
4116
  for (const child of node.children) {
3853
4117
  const c = child;
3854
4118
  if (c.type === "text") {
3855
- const afterResourceTags = splitTextWithResourceTags(child.value);
3856
- for (const part of afterResourceTags) {
3857
- if (part.type === "text") {
3858
- const afterJumpPoints = splitTextWithJumpPoints(part.value);
3859
- newChildren.push(...afterJumpPoints);
3860
- } else {
3861
- newChildren.push(part);
4119
+ const afterLinks = splitTextWithBracketLinks(child.value);
4120
+ for (const linkPart of afterLinks) {
4121
+ if (linkPart.type !== "text") {
4122
+ newChildren.push(linkPart);
4123
+ continue;
4124
+ }
4125
+ const afterResourceTags = splitTextWithResourceTags(linkPart.value);
4126
+ for (const part of afterResourceTags) {
4127
+ if (part.type === "text") {
4128
+ const afterJumpPoints = splitTextWithJumpPoints(part.value);
4129
+ newChildren.push(...afterJumpPoints);
4130
+ } else {
4131
+ newChildren.push(part);
4132
+ }
3862
4133
  }
3863
4134
  }
3864
4135
  } else {
@@ -4065,77 +4336,479 @@ function analyzeJinjaBlocks(doc2) {
4065
4336
  });
4066
4337
  }
4067
4338
 
4068
- // src/ui/plugin/markdownClipboard.ts
4069
- var key = new PluginKey("markdownClipboard");
4070
- function textToSlice(text2, view) {
4071
- try {
4072
- const existingIds = /* @__PURE__ */ new Set();
4073
- view.state.doc.descendants((node) => {
4074
- if (node.type.name === "jumpPoint") existingIds.add(node.attrs.id);
4075
- });
4076
- const deduped = text2.replace(/\^([A-Za-z_][A-Za-z0-9_]*)\^/gm, (match, id) => {
4077
- if (existingIds.has(id)) return id;
4078
- existingIds.add(id);
4079
- return match;
4339
+ // src/jinja/conditionHighlighter.ts
4340
+ var KEYWORDS = {
4341
+ and: "AND",
4342
+ or: "OR",
4343
+ not: "NOT",
4344
+ in: "IN",
4345
+ is: "IS",
4346
+ True: "BOOL",
4347
+ False: "BOOL",
4348
+ true: "BOOL",
4349
+ false: "BOOL",
4350
+ None: "NONE",
4351
+ null: "NONE"
4352
+ };
4353
+ var CATEGORY_BY_TYPE = {
4354
+ STRING: "value",
4355
+ NUMBER: "value",
4356
+ BOOL: "value",
4357
+ NONE: "value",
4358
+ IDENT: "variable",
4359
+ AND: "operator",
4360
+ OR: "operator",
4361
+ NOT: "operator",
4362
+ IN: "operator",
4363
+ IS: "operator",
4364
+ EQ: "operator",
4365
+ NEQ: "operator",
4366
+ LT: "operator",
4367
+ GT: "operator",
4368
+ LTE: "operator",
4369
+ GTE: "operator",
4370
+ LPAREN: "punctuation",
4371
+ RPAREN: "punctuation"
4372
+ };
4373
+ var NEGATIVE_NUMBER_PRECEDERS = /* @__PURE__ */ new Set([
4374
+ "AND",
4375
+ "OR",
4376
+ "NOT",
4377
+ "IN",
4378
+ "IS",
4379
+ "EQ",
4380
+ "NEQ",
4381
+ "LT",
4382
+ "GT",
4383
+ "LTE",
4384
+ "GTE",
4385
+ "LPAREN"
4386
+ ]);
4387
+ function canStartNegativeNumber(tokens) {
4388
+ if (tokens.length === 0) {
4389
+ return true;
4390
+ }
4391
+ const previous = tokens[tokens.length - 1];
4392
+ return NEGATIVE_NUMBER_PRECEDERS.has(previous.type);
4393
+ }
4394
+ function finalizeTokens(input, rawTokens) {
4395
+ const highlighted = [];
4396
+ for (let i = 0; i < rawTokens.length; i++) {
4397
+ const token = rawTokens[i];
4398
+ const next = rawTokens[i + 1];
4399
+ if (token.type === "IS" && next?.type === "NOT") {
4400
+ highlighted.push({
4401
+ text: input.slice(token.start, next.end),
4402
+ start: token.start,
4403
+ end: next.end,
4404
+ category: "operator"
4405
+ });
4406
+ i++;
4407
+ continue;
4408
+ }
4409
+ highlighted.push({
4410
+ text: token.text,
4411
+ start: token.start,
4412
+ end: token.end,
4413
+ category: CATEGORY_BY_TYPE[token.type]
4080
4414
  });
4081
- const astNodes = parseFragment(deduped);
4082
- const pmJSONNodes = astNodesToJSONContent(astNodes);
4083
- const pmNodes = pmJSONNodes.map((json) => actionbookSchema.nodeFromJSON(json));
4084
- const fragment = Fragment.fromArray(pmNodes);
4085
- const openDepth = pmJSONNodes.length === 1 && pmJSONNodes[0].type === "paragraph" ? 1 : 0;
4086
- return new Slice(fragment, openDepth, openDepth);
4087
- } catch {
4088
- return null;
4089
4415
  }
4416
+ return highlighted;
4090
4417
  }
4091
- function createPlugin() {
4092
- return new Plugin({
4093
- key,
4094
- props: {
4095
- handlePaste(view, event) {
4096
- const text2 = event.clipboardData?.getData("text/plain");
4097
- if (!text2) return false;
4098
- const slice = textToSlice(text2, view);
4099
- if (!slice) return false;
4100
- view.dispatch(view.state.tr.replaceSelection(slice));
4101
- return true;
4102
- },
4103
- clipboardTextParser(text2, _$context, _plain, view) {
4104
- if (!text2) return Slice.empty;
4105
- const slice = textToSlice(text2, view);
4106
- if (!slice) {
4107
- const textNode = actionbookSchema.text(text2);
4108
- return new Slice(Fragment.from(textNode), 0, 0);
4418
+ function tokenizeCondition(input) {
4419
+ const rawTokens = [];
4420
+ let i = 0;
4421
+ while (i < input.length) {
4422
+ const char = input[i];
4423
+ if (/\s/.test(char)) {
4424
+ i++;
4425
+ continue;
4426
+ }
4427
+ if (char === '"' || char === "'") {
4428
+ const start = i;
4429
+ const quote = char;
4430
+ i++;
4431
+ while (i < input.length) {
4432
+ if (input[i] === "\\" && i + 1 < input.length) {
4433
+ i += 2;
4434
+ continue;
4109
4435
  }
4110
- return slice;
4111
- },
4112
- // Discard pasted HTML — always prefer plain-text markdown
4113
- transformPastedHTML() {
4114
- return "";
4115
- },
4116
- clipboardTextSerializer(slice) {
4117
- try {
4118
- const jsonNodes = [];
4119
- slice.content.forEach((node) => jsonNodes.push(node.toJSON()));
4120
- const doc2 = fromProseMirrorJSON({ type: "doc", content: jsonNodes });
4121
- return serializeFragment(doc2.content);
4122
- } catch {
4123
- return "";
4436
+ if (input[i] === quote) {
4437
+ i++;
4438
+ rawTokens.push({
4439
+ type: "STRING",
4440
+ text: input.slice(start, i),
4441
+ start,
4442
+ end: i
4443
+ });
4444
+ break;
4124
4445
  }
4446
+ i++;
4447
+ }
4448
+ if (rawTokens[rawTokens.length - 1]?.start !== start) {
4449
+ return finalizeTokens(input, rawTokens);
4125
4450
  }
4451
+ continue;
4126
4452
  }
4127
- });
4128
- }
4129
- function createMarkdownClipboardPlugin() {
4130
- return {
4131
- name: "markdownClipboard",
4132
- plugins: () => [createPlugin()]
4133
- };
4134
- }
4135
-
4136
- // src/ui/plugin/jumpPointPlugin.ts
4137
- import { Plugin as Plugin2, PluginKey as PluginKey2, TextSelection } from "prosemirror-state";
4138
- import { Decoration, DecorationSet } from "prosemirror-view";
4453
+ if (/[0-9]/.test(char) || char === "-" && i + 1 < input.length && /[0-9]/.test(input[i + 1]) && canStartNegativeNumber(rawTokens)) {
4454
+ const start = i;
4455
+ if (input[i] === "-") {
4456
+ i++;
4457
+ }
4458
+ while (i < input.length && /[0-9]/.test(input[i])) {
4459
+ i++;
4460
+ }
4461
+ if (i < input.length && input[i] === ".") {
4462
+ i++;
4463
+ while (i < input.length && /[0-9]/.test(input[i])) {
4464
+ i++;
4465
+ }
4466
+ }
4467
+ rawTokens.push({
4468
+ type: "NUMBER",
4469
+ text: input.slice(start, i),
4470
+ start,
4471
+ end: i
4472
+ });
4473
+ continue;
4474
+ }
4475
+ if (/[a-zA-Z_]/.test(char)) {
4476
+ const start = i;
4477
+ i++;
4478
+ while (i < input.length && /[a-zA-Z0-9_.]/.test(input[i])) {
4479
+ i++;
4480
+ }
4481
+ const text2 = input.slice(start, i);
4482
+ rawTokens.push({
4483
+ type: KEYWORDS[text2] ?? "IDENT",
4484
+ text: text2,
4485
+ start,
4486
+ end: i
4487
+ });
4488
+ continue;
4489
+ }
4490
+ if (i + 1 < input.length) {
4491
+ const twoChar = input.slice(i, i + 2);
4492
+ if (twoChar === "==") {
4493
+ rawTokens.push({ type: "EQ", text: twoChar, start: i, end: i + 2 });
4494
+ i += 2;
4495
+ continue;
4496
+ }
4497
+ if (twoChar === "!=") {
4498
+ rawTokens.push({ type: "NEQ", text: twoChar, start: i, end: i + 2 });
4499
+ i += 2;
4500
+ continue;
4501
+ }
4502
+ if (twoChar === "<=") {
4503
+ rawTokens.push({ type: "LTE", text: twoChar, start: i, end: i + 2 });
4504
+ i += 2;
4505
+ continue;
4506
+ }
4507
+ if (twoChar === ">=") {
4508
+ rawTokens.push({ type: "GTE", text: twoChar, start: i, end: i + 2 });
4509
+ i += 2;
4510
+ continue;
4511
+ }
4512
+ }
4513
+ if (char === "<") {
4514
+ rawTokens.push({ type: "LT", text: char, start: i, end: i + 1 });
4515
+ i++;
4516
+ continue;
4517
+ }
4518
+ if (char === ">") {
4519
+ rawTokens.push({ type: "GT", text: char, start: i, end: i + 1 });
4520
+ i++;
4521
+ continue;
4522
+ }
4523
+ if (char === "(") {
4524
+ rawTokens.push({ type: "LPAREN", text: char, start: i, end: i + 1 });
4525
+ i++;
4526
+ continue;
4527
+ }
4528
+ if (char === ")") {
4529
+ rawTokens.push({ type: "RPAREN", text: char, start: i, end: i + 1 });
4530
+ i++;
4531
+ continue;
4532
+ }
4533
+ return finalizeTokens(input, rawTokens);
4534
+ }
4535
+ return finalizeTokens(input, rawTokens);
4536
+ }
4537
+
4538
+ // src/tree/documentTree.ts
4539
+ var MAX_DEPTH6 = 128;
4540
+ function extractInlineItems(content, blockIndex) {
4541
+ const items = [];
4542
+ for (const node of content) {
4543
+ if (node.type === "jumpPoint") {
4544
+ items.push({
4545
+ type: "jumpPoint",
4546
+ label: node.id,
4547
+ blockIndex,
4548
+ children: []
4549
+ });
4550
+ } else if (node.type === "resourceTag") {
4551
+ const isEndAction = node.tagType === "handoff";
4552
+ items.push({
4553
+ type: isEndAction ? "endAction" : "resourceTag",
4554
+ label: isEndAction ? `End ${node.text}` : node.text,
4555
+ blockIndex,
4556
+ children: [],
4557
+ meta: { tagType: node.tagType, resourceId: node.resourceId }
4558
+ });
4559
+ }
4560
+ }
4561
+ return items;
4562
+ }
4563
+ function extractBlockItems(blocks, startIndex, depth) {
4564
+ if (depth > MAX_DEPTH6) return [];
4565
+ const items = [];
4566
+ for (let i = 0; i < blocks.length; i++) {
4567
+ const block = blocks[i];
4568
+ const blockIndex = startIndex + i;
4569
+ switch (block.type) {
4570
+ case "heading": {
4571
+ const label = textContent(block) || `Heading ${block.level}`;
4572
+ const inlineItems = extractInlineItems(block.content, blockIndex);
4573
+ items.push({
4574
+ type: "heading",
4575
+ label,
4576
+ blockIndex,
4577
+ children: inlineItems,
4578
+ meta: { level: block.level }
4579
+ });
4580
+ break;
4581
+ }
4582
+ case "paragraph": {
4583
+ const inlineItems = extractInlineItems(block.content, blockIndex);
4584
+ items.push(...inlineItems);
4585
+ break;
4586
+ }
4587
+ case "jinjaIfBlock": {
4588
+ const branches = block.branches.map((branch) => {
4589
+ const branchLabel = branch.branchType === "else" ? "ELSE" : `${branch.branchType.toUpperCase()} ${branch.condition || ""}`.trim();
4590
+ const branchChildren = extractBlockItems(branch.content, blockIndex, depth + 1);
4591
+ return {
4592
+ type: "jinjaBranch",
4593
+ label: branchLabel,
4594
+ blockIndex,
4595
+ children: branchChildren
4596
+ };
4597
+ });
4598
+ items.push({
4599
+ type: "jinjaIf",
4600
+ label: "Jinja if",
4601
+ blockIndex,
4602
+ children: branches
4603
+ });
4604
+ break;
4605
+ }
4606
+ case "noteBlock": {
4607
+ const noteContent = textContent(block);
4608
+ items.push({
4609
+ type: "noteBlock",
4610
+ label: noteContent ? `Note: ${noteContent.slice(0, 30)}` : "Note",
4611
+ blockIndex,
4612
+ children: []
4613
+ });
4614
+ break;
4615
+ }
4616
+ case "bulletList":
4617
+ case "orderedList": {
4618
+ for (const li of block.content) {
4619
+ const childItems = extractBlockItems(li.content, blockIndex, depth + 1);
4620
+ items.push(...childItems);
4621
+ }
4622
+ break;
4623
+ }
4624
+ case "blockquote": {
4625
+ const childItems = extractBlockItems(block.content, blockIndex, depth + 1);
4626
+ items.push(...childItems);
4627
+ break;
4628
+ }
4629
+ default:
4630
+ break;
4631
+ }
4632
+ }
4633
+ return items;
4634
+ }
4635
+ function buildDocumentTree(doc2) {
4636
+ const items = extractBlockItems(doc2.content, 0, 0);
4637
+ try {
4638
+ const structures = analyzeJinjaBlocks(doc2);
4639
+ if (structures.length > 0) {
4640
+ const hasStructuredJinja = items.some((n) => n.type === "jinjaIf");
4641
+ if (!hasStructuredJinja) {
4642
+ for (const s of structures) {
4643
+ const branches = s.branches.map((b) => ({
4644
+ type: "jinjaBranch",
4645
+ label: b.type === "else" ? "ELSE" : `${b.type.toUpperCase()} ${b.condition || ""}`.trim(),
4646
+ blockIndex: b.tagBlockIndex,
4647
+ children: []
4648
+ }));
4649
+ const jinjaNode = {
4650
+ type: "jinjaIf",
4651
+ label: "Jinja if",
4652
+ blockIndex: s.ifTagBlockIndex,
4653
+ children: branches
4654
+ };
4655
+ let inserted = false;
4656
+ for (let i = 0; i < items.length; i++) {
4657
+ if (items[i].blockIndex > s.ifTagBlockIndex) {
4658
+ items.splice(i, 0, jinjaNode);
4659
+ inserted = true;
4660
+ break;
4661
+ }
4662
+ }
4663
+ if (!inserted) items.push(jinjaNode);
4664
+ }
4665
+ }
4666
+ }
4667
+ } catch {
4668
+ }
4669
+ return items;
4670
+ }
4671
+
4672
+ // src/ui/plugin/markdownClipboard.ts
4673
+ var key = new PluginKey("markdownClipboard");
4674
+ var MAX_PASTE_LIST_DEPTH = 3;
4675
+ var MAX_FLATTEN_DEPTH = 128;
4676
+ function flattenDeepLists(node, listDepth = 0, _recurseDepth = 0) {
4677
+ if (_recurseDepth > MAX_FLATTEN_DEPTH) return node;
4678
+ const isList = node.type.name === "bulletList" || node.type.name === "orderedList";
4679
+ const currentDepth = isList ? listDepth + 1 : listDepth;
4680
+ if (isList && currentDepth >= MAX_PASTE_LIST_DEPTH) {
4681
+ const flatItems = [];
4682
+ node.forEach((li) => {
4683
+ const nonListBlocks = [];
4684
+ li.forEach((block) => {
4685
+ if (block.type.name !== "bulletList" && block.type.name !== "orderedList") {
4686
+ nonListBlocks.push(block);
4687
+ }
4688
+ });
4689
+ if (nonListBlocks.length > 0) {
4690
+ flatItems.push(li.type.create(li.attrs, nonListBlocks));
4691
+ }
4692
+ });
4693
+ if (flatItems.length === 0) return node;
4694
+ return node.copy(Fragment.fromArray(flatItems));
4695
+ }
4696
+ const children = [];
4697
+ let changed = false;
4698
+ node.forEach((child) => {
4699
+ const flattened = flattenDeepLists(child, currentDepth, _recurseDepth + 1);
4700
+ if (flattened !== child) changed = true;
4701
+ children.push(flattened);
4702
+ });
4703
+ if (!changed) return node;
4704
+ return node.copy(Fragment.fromArray(children));
4705
+ }
4706
+ function htmlToSlice(html) {
4707
+ if (typeof document === "undefined") return null;
4708
+ try {
4709
+ const domParser = new DOMParser();
4710
+ const parsed = domParser.parseFromString(html, "text/html");
4711
+ const body = parsed.body;
4712
+ if (!body || !body.childNodes.length) return null;
4713
+ const pmParser = ProseMirrorDOMParser.fromSchema(actionbookSchema);
4714
+ const doc2 = pmParser.parse(body);
4715
+ const flattened = flattenDeepLists(doc2);
4716
+ const content = flattened.content;
4717
+ let hasContent = false;
4718
+ content.forEach((node) => {
4719
+ if (node.type.name !== "paragraph" || node.content.size > 0) {
4720
+ hasContent = true;
4721
+ }
4722
+ });
4723
+ if (!hasContent) return null;
4724
+ const openDepth = content.childCount === 1 && content.firstChild?.type.name === "paragraph" ? 1 : 0;
4725
+ return new Slice(content, openDepth, openDepth);
4726
+ } catch {
4727
+ return null;
4728
+ }
4729
+ }
4730
+ function textToSlice(text2, view) {
4731
+ try {
4732
+ const existingIds = /* @__PURE__ */ new Set();
4733
+ view.state.doc.descendants((node) => {
4734
+ if (node.type.name === "jumpPoint") existingIds.add(node.attrs.id);
4735
+ });
4736
+ const deduped = text2.replace(/\^([A-Za-z_][A-Za-z0-9_]*)\^/gm, (match, id) => {
4737
+ if (existingIds.has(id)) return id;
4738
+ existingIds.add(id);
4739
+ return match;
4740
+ });
4741
+ const astNodes = parseFragment(deduped);
4742
+ const pmJSONNodes = astNodesToJSONContent(astNodes);
4743
+ const pmNodes = pmJSONNodes.map((json) => actionbookSchema.nodeFromJSON(json));
4744
+ const fragment = Fragment.fromArray(pmNodes);
4745
+ const openDepth = pmJSONNodes.length === 1 && pmJSONNodes[0].type === "paragraph" ? 1 : 0;
4746
+ return new Slice(fragment, openDepth, openDepth);
4747
+ } catch {
4748
+ return null;
4749
+ }
4750
+ }
4751
+ function createPlugin() {
4752
+ return new Plugin({
4753
+ key,
4754
+ props: {
4755
+ handlePaste(view, event) {
4756
+ const html = event.clipboardData?.getData("text/html");
4757
+ const text2 = event.clipboardData?.getData("text/plain");
4758
+ if (html) {
4759
+ const htmlSlice = htmlToSlice(html);
4760
+ if (htmlSlice) {
4761
+ view.dispatch(view.state.tr.replaceSelection(htmlSlice));
4762
+ return true;
4763
+ }
4764
+ }
4765
+ if (!text2) return false;
4766
+ const slice = textToSlice(text2, view);
4767
+ if (!slice) return false;
4768
+ view.dispatch(view.state.tr.replaceSelection(slice));
4769
+ return true;
4770
+ },
4771
+ clipboardTextParser(text2, _$context, _plain, view) {
4772
+ if (!text2) return Slice.empty;
4773
+ const slice = textToSlice(text2, view);
4774
+ if (!slice) {
4775
+ const textNode = actionbookSchema.text(text2);
4776
+ return new Slice(Fragment.from(textNode), 0, 0);
4777
+ }
4778
+ return slice;
4779
+ },
4780
+ // Transform pasted HTML: keep structure but flatten deep lists
4781
+ // to prevent Chrome contentEditable hanging
4782
+ transformPasted(slice) {
4783
+ const nodes = [];
4784
+ slice.content.forEach((node) => {
4785
+ nodes.push(flattenDeepLists(node));
4786
+ });
4787
+ return new Slice(Fragment.fromArray(nodes), slice.openStart, slice.openEnd);
4788
+ },
4789
+ clipboardTextSerializer(slice) {
4790
+ try {
4791
+ const jsonNodes = [];
4792
+ slice.content.forEach((node) => jsonNodes.push(node.toJSON()));
4793
+ const doc2 = fromProseMirrorJSON({ type: "doc", content: jsonNodes });
4794
+ return serializeFragment(doc2.content);
4795
+ } catch {
4796
+ return "";
4797
+ }
4798
+ }
4799
+ }
4800
+ });
4801
+ }
4802
+ function createMarkdownClipboardPlugin() {
4803
+ return {
4804
+ name: "markdownClipboard",
4805
+ plugins: () => [createPlugin()]
4806
+ };
4807
+ }
4808
+
4809
+ // src/ui/plugin/jumpPointPlugin.ts
4810
+ import { Plugin as Plugin2, PluginKey as PluginKey2, TextSelection as TextSelection2 } from "prosemirror-state";
4811
+ import { Decoration, DecorationSet } from "prosemirror-view";
4139
4812
  var adjacentKey = new PluginKey2("jumpPointAdjacent");
4140
4813
  var JUMP_POINT_ADJACENT_SPEC = { jumpPointAdjacent: true };
4141
4814
  function buildDecorations(state) {
@@ -4205,8 +4878,15 @@ function createJumpPointEditPlugin() {
4205
4878
  }
4206
4879
  });
4207
4880
  }
4208
- function jumpPointBefore(state) {
4881
+ function jumpPointAtOrBefore(state) {
4209
4882
  const { selection } = state;
4883
+ if ("node" in selection) {
4884
+ const node = selection.node;
4885
+ if (node?.type?.name === "jumpPoint") {
4886
+ return { id: node.attrs.id, nodeStart: selection.from, nodeEnd: selection.to };
4887
+ }
4888
+ return null;
4889
+ }
4210
4890
  if (!selection.empty) return null;
4211
4891
  const { $from } = selection;
4212
4892
  const nodeBefore = $from.nodeBefore;
@@ -4216,23 +4896,23 @@ function jumpPointBefore(state) {
4216
4896
  return { id: nodeBefore.attrs.id, nodeStart, nodeEnd };
4217
4897
  }
4218
4898
  function explodeOnBackspace(state) {
4219
- const info = jumpPointBefore(state);
4899
+ const info = jumpPointAtOrBefore(state);
4220
4900
  if (!info) return null;
4221
4901
  const { id, nodeStart, nodeEnd } = info;
4222
4902
  const rawText = `^${id}`;
4223
4903
  const tr = state.tr.replaceWith(nodeStart, nodeEnd, state.schema.text(rawText));
4224
4904
  const cursorPos = Math.min(nodeStart + rawText.length, tr.doc.content.size);
4225
- tr.setSelection(TextSelection.near(tr.doc.resolve(cursorPos)));
4905
+ tr.setSelection(TextSelection2.near(tr.doc.resolve(cursorPos)));
4226
4906
  return tr;
4227
4907
  }
4228
4908
  function explodeOnArrowLeft(state) {
4229
- const info = jumpPointBefore(state);
4909
+ const info = jumpPointAtOrBefore(state);
4230
4910
  if (!info) return null;
4231
4911
  const { id, nodeStart, nodeEnd } = info;
4232
4912
  const rawText = `^${id}^`;
4233
4913
  const tr = state.tr.replaceWith(nodeStart, nodeEnd, state.schema.text(rawText));
4234
4914
  const cursorPos = Math.min(nodeStart + 1 + id.length, tr.doc.content.size);
4235
- tr.setSelection(TextSelection.near(tr.doc.resolve(cursorPos)));
4915
+ tr.setSelection(TextSelection2.near(tr.doc.resolve(cursorPos)));
4236
4916
  tr.setMeta(jumpPointEditKey, { from: nodeStart, to: nodeStart + rawText.length });
4237
4917
  return tr;
4238
4918
  }
@@ -4279,6 +4959,7 @@ function createJumpPointAdjacentPlugin() {
4279
4959
  }
4280
4960
 
4281
4961
  // src/ui/plugin/jumpPointNodeViewPlugin.tsx
4962
+ import { useCallback as useCallback2, useState as useState2 } from "react";
4282
4963
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
4283
4964
  var JUMP_POINT_STYLE = {
4284
4965
  display: "inline-flex",
@@ -4286,31 +4967,118 @@ var JUMP_POINT_STYLE = {
4286
4967
  backgroundColor: "#FFF2B6",
4287
4968
  border: "1px solid #FFC233",
4288
4969
  color: "#AA5D04",
4289
- borderRadius: "4px",
4290
- padding: "0 0.3em",
4291
- fontSize: "0.85em",
4292
- lineHeight: 1.6,
4970
+ borderRadius: "2px",
4971
+ padding: "2px",
4972
+ fontSize: "12px",
4973
+ lineHeight: "16px",
4293
4974
  userSelect: "none",
4294
4975
  cursor: "default",
4295
- boxSizing: "border-box"
4976
+ boxSizing: "border-box",
4977
+ height: "20px",
4978
+ overflow: "hidden"
4296
4979
  };
4297
4980
  var JUMP_POINT_ADJACENT_STYLE = {
4298
4981
  ...JUMP_POINT_STYLE,
4299
4982
  backgroundColor: "#FFE680",
4300
4983
  borderColor: "#FF9500"
4301
4984
  };
4302
- function JumpPointNodeViewComponent({ node, decorations }) {
4985
+ var JUMP_POINT_DUPLICATE_STYLE = {
4986
+ ...JUMP_POINT_STYLE,
4987
+ backgroundColor: "#FFD9DD",
4988
+ borderColor: "#D9352C",
4989
+ color: "#9D091E"
4990
+ };
4991
+ var LABEL_STYLE = {
4992
+ padding: "0 4px",
4993
+ whiteSpace: "nowrap"
4994
+ };
4995
+ var CLOSE_BTN_STYLE = {
4996
+ display: "inline-flex",
4997
+ alignItems: "center",
4998
+ justifyContent: "center",
4999
+ width: "16px",
5000
+ height: "16px",
5001
+ padding: 0,
5002
+ border: "none",
5003
+ background: "transparent",
5004
+ cursor: "pointer",
5005
+ borderRadius: "1px",
5006
+ flexShrink: 0,
5007
+ color: "inherit",
5008
+ fontSize: "10px",
5009
+ lineHeight: 1
5010
+ };
5011
+ var TOOLTIP_STYLE = {
5012
+ position: "absolute",
5013
+ bottom: "calc(100% + 8px)",
5014
+ left: "50%",
5015
+ transform: "translateX(-50%)",
5016
+ backgroundColor: "#fff",
5017
+ border: "1px solid #CCC",
5018
+ borderRadius: "4px",
5019
+ 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)",
5020
+ padding: "16px 20px",
5021
+ fontSize: "14px",
5022
+ lineHeight: "20px",
5023
+ color: "#0D0D0D",
5024
+ whiteSpace: "nowrap",
5025
+ zIndex: 100,
5026
+ pointerEvents: "none"
5027
+ };
5028
+ function JumpPointNodeViewComponent({ node, view, getPos, decorations }) {
4303
5029
  const id = node.attrs.id;
4304
5030
  const isAdjacent = decorations?.some((d) => d.spec?.jumpPointAdjacent === true) ?? false;
5031
+ const isDuplicate = decorations?.some((d) => d.spec?.jumpPointDuplicate === true) ?? false;
5032
+ const [showTooltip, setShowTooltip] = useState2(false);
5033
+ const handleDelete2 = useCallback2(() => {
5034
+ const pos = getPos();
5035
+ if (pos == null) return;
5036
+ const tr = view.state.tr.delete(pos, pos + node.nodeSize);
5037
+ view.dispatch(tr);
5038
+ view.focus();
5039
+ }, [view, getPos, node.nodeSize]);
5040
+ if (isDuplicate) {
5041
+ return /* @__PURE__ */ jsxs3(
5042
+ "span",
5043
+ {
5044
+ style: { ...JUMP_POINT_DUPLICATE_STYLE, position: "relative" },
5045
+ id: `jp-${id}`,
5046
+ onMouseEnter: () => setShowTooltip(true),
5047
+ onMouseLeave: () => setShowTooltip(false),
5048
+ children: [
5049
+ /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#9D091E", style: { paddingLeft: "2px", flexShrink: 0 } }),
5050
+ /* @__PURE__ */ jsx4("span", { style: LABEL_STYLE, children: id }),
5051
+ /* @__PURE__ */ jsx4(
5052
+ "button",
5053
+ {
5054
+ style: CLOSE_BTN_STYLE,
5055
+ onMouseDown: (e) => {
5056
+ e.preventDefault();
5057
+ e.stopPropagation();
5058
+ handleDelete2();
5059
+ },
5060
+ title: "Remove anchor",
5061
+ children: "\u2715"
5062
+ }
5063
+ ),
5064
+ showTooltip && /* @__PURE__ */ jsxs3("span", { style: TOOLTIP_STYLE, children: [
5065
+ "\u201C",
5066
+ id,
5067
+ "\u201D is already used as an anchor"
5068
+ ] })
5069
+ ]
5070
+ }
5071
+ );
5072
+ }
4305
5073
  if (isAdjacent) {
4306
5074
  return /* @__PURE__ */ jsxs3("span", { style: JUMP_POINT_ADJACENT_STYLE, id: `jp-${id}`, children: [
4307
- /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { marginRight: "max(0.15em, 2px)", flexShrink: 0 } }),
4308
- `^${id}^`
5075
+ /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { paddingLeft: "2px", flexShrink: 0 } }),
5076
+ /* @__PURE__ */ jsx4("span", { style: LABEL_STYLE, children: `^${id}^` })
4309
5077
  ] });
4310
5078
  }
4311
5079
  return /* @__PURE__ */ jsxs3("span", { style: JUMP_POINT_STYLE, id: `jp-${id}`, children: [
4312
- /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { marginRight: "max(0.15em, 2px)", flexShrink: 0 } }),
4313
- id
5080
+ /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { paddingLeft: "2px", flexShrink: 0 } }),
5081
+ /* @__PURE__ */ jsx4("span", { style: LABEL_STYLE, children: id })
4314
5082
  ] });
4315
5083
  }
4316
5084
  function createJumpPointNodeViewPlugin() {
@@ -4322,6 +5090,105 @@ function createJumpPointNodeViewPlugin() {
4322
5090
  };
4323
5091
  }
4324
5092
 
5093
+ // src/ui/plugin/jumpPointValidationPlugin.ts
5094
+ import { Plugin as Plugin3, PluginKey as PluginKey3 } from "prosemirror-state";
5095
+ import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
5096
+ var pluginKey = new PluginKey3("jumpPointValidation");
5097
+ function collectJumpPointIds(state) {
5098
+ const ids = /* @__PURE__ */ new Set();
5099
+ state.doc.descendants((node) => {
5100
+ if (node.type.name === "jumpPoint") {
5101
+ ids.add(node.attrs.id);
5102
+ }
5103
+ });
5104
+ return ids;
5105
+ }
5106
+ function findDuplicatePositions(state) {
5107
+ const idCount = /* @__PURE__ */ new Map();
5108
+ const nodes = [];
5109
+ state.doc.descendants((node, pos) => {
5110
+ if (node.type.name === "jumpPoint") {
5111
+ const id = node.attrs.id;
5112
+ idCount.set(id, (idCount.get(id) ?? 0) + 1);
5113
+ nodes.push({ id, pos, size: node.nodeSize });
5114
+ }
5115
+ });
5116
+ return nodes.filter((n) => (idCount.get(n.id) ?? 0) > 1);
5117
+ }
5118
+ function findBrokenAnchorRefs(state, existingIds) {
5119
+ const broken = [];
5120
+ const linkMarkType = state.schema.marks.link;
5121
+ if (!linkMarkType) return broken;
5122
+ state.doc.descendants((node, pos) => {
5123
+ if (!node.isText) return;
5124
+ const linkMark2 = node.marks.find((m) => m.type === linkMarkType);
5125
+ if (!linkMark2) return;
5126
+ const href = linkMark2.attrs.href;
5127
+ if (!href || !href.startsWith("#")) return;
5128
+ const refId = href.slice(1);
5129
+ if (refId && !existingIds.has(refId)) {
5130
+ broken.push({ from: pos, to: pos + node.nodeSize, refId });
5131
+ }
5132
+ });
5133
+ return broken;
5134
+ }
5135
+ function hasDuplicateJumpPoints(state) {
5136
+ return findDuplicatePositions(state).length > 0;
5137
+ }
5138
+ function hasBrokenAnchorRefs(state) {
5139
+ const ids = collectJumpPointIds(state);
5140
+ return findBrokenAnchorRefs(state, ids).length > 0;
5141
+ }
5142
+ function createJumpPointValidationPlugin() {
5143
+ return {
5144
+ name: "jumpPointValidation",
5145
+ plugins: () => [
5146
+ new Plugin3({
5147
+ key: pluginKey,
5148
+ state: {
5149
+ init(_, state) {
5150
+ return buildDecorations2(state);
5151
+ },
5152
+ apply(tr, oldSet, _oldState, newState) {
5153
+ if (tr.docChanged) {
5154
+ return buildDecorations2(newState);
5155
+ }
5156
+ return oldSet.map(tr.mapping, tr.doc);
5157
+ }
5158
+ },
5159
+ props: {
5160
+ decorations(state) {
5161
+ return pluginKey.getState(state) ?? DecorationSet2.empty;
5162
+ }
5163
+ }
5164
+ })
5165
+ ]
5166
+ };
5167
+ }
5168
+ function buildDecorations2(state) {
5169
+ const decos = [];
5170
+ const duplicates = findDuplicatePositions(state);
5171
+ for (const { pos, size } of duplicates) {
5172
+ decos.push(
5173
+ Decoration2.node(pos, pos + size, { class: "jump-point-duplicate" }, { jumpPointDuplicate: true })
5174
+ );
5175
+ }
5176
+ const existingIds = collectJumpPointIds(state);
5177
+ const brokenRefs = findBrokenAnchorRefs(state, existingIds);
5178
+ for (const { from, to, refId } of brokenRefs) {
5179
+ decos.push(
5180
+ Decoration2.inline(from, to, {
5181
+ class: "broken-anchor-ref",
5182
+ "data-broken-ref": refId,
5183
+ title: `Anchor "${refId}" no longer exists`,
5184
+ style: "color: #D9352C; text-decoration-color: #D9352C;"
5185
+ })
5186
+ );
5187
+ }
5188
+ if (decos.length === 0) return DecorationSet2.empty;
5189
+ return DecorationSet2.create(state.doc, decos);
5190
+ }
5191
+
4325
5192
  // src/ui/plugin/inlineToolTagNodeViewPlugin.tsx
4326
5193
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
4327
5194
  var RESOURCE_TAG_COLORS2 = {
@@ -4340,9 +5207,12 @@ var RESOURCE_TAG_ICONS2 = {
4340
5207
  time_diff: IconTimeDiff
4341
5208
  };
4342
5209
  var DEFAULT_ICON = IconTool;
5210
+ var RESOURCE_ID_OVERRIDES = {
5211
+ "close-happy-tiger": { color: "#0D0D0D", icon: IconStop }
5212
+ };
4343
5213
  var OUTER_STYLE = {
4344
5214
  display: "inline-flex",
4345
- padding: "1px",
5215
+ padding: "2px",
4346
5216
  border: "1px solid #CCCCCC",
4347
5217
  borderRadius: "2px",
4348
5218
  alignItems: "center",
@@ -4362,16 +5232,19 @@ var ICON_BLOCK_BASE = {
4362
5232
  };
4363
5233
  var LABEL_BASE = {
4364
5234
  padding: "0 4px",
4365
- fontSize: "11px",
4366
- lineHeight: 1,
5235
+ fontSize: "12px",
5236
+ lineHeight: "16px",
4367
5237
  userSelect: "none"
4368
5238
  };
4369
- function InlineToolTagNodeViewComponent({ node }) {
5239
+ function InlineToolTagNodeViewComponent({ node, selected }) {
4370
5240
  const type = node.attrs.type;
4371
5241
  const text2 = node.attrs.text;
4372
- const color = RESOURCE_TAG_COLORS2[type] ?? DEFAULT_COLOR;
4373
- const Icon = RESOURCE_TAG_ICONS2[type] ?? DEFAULT_ICON;
4374
- return /* @__PURE__ */ jsxs4("span", { style: OUTER_STYLE, "data-type": type, "data-resource-id": node.attrs.resourceId, children: [
5242
+ const resourceId = node.attrs.resourceId;
5243
+ const override = RESOURCE_ID_OVERRIDES[resourceId];
5244
+ const color = override?.color ?? RESOURCE_TAG_COLORS2[type] ?? DEFAULT_COLOR;
5245
+ const Icon = override?.icon ?? RESOURCE_TAG_ICONS2[type] ?? DEFAULT_ICON;
5246
+ const style = selected ? { ...OUTER_STYLE, boxShadow: `inset 0 0 0 1px ${color}` } : OUTER_STYLE;
5247
+ return /* @__PURE__ */ jsxs4("span", { style, "data-type": type, "data-resource-id": node.attrs.resourceId, children: [
4375
5248
  /* @__PURE__ */ jsx5("span", { style: { ...ICON_BLOCK_BASE, backgroundColor: color }, children: /* @__PURE__ */ jsx5(Icon, { size: 9, fill: "white" }) }),
4376
5249
  /* @__PURE__ */ jsx5("span", { style: { ...LABEL_BASE, color }, children: text2 })
4377
5250
  ] });
@@ -4386,9 +5259,9 @@ function createInlineToolTagNodeViewPlugin() {
4386
5259
  }
4387
5260
 
4388
5261
  // src/ui/plugin/jinjaDecoration.ts
4389
- import { Plugin as Plugin3, PluginKey as PluginKey3 } from "prosemirror-state";
4390
- import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
4391
- var jinjaPluginKey = new PluginKey3("jinjaDecoration");
5262
+ import { Plugin as Plugin4, PluginKey as PluginKey4 } from "prosemirror-state";
5263
+ import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "prosemirror-view";
5264
+ var jinjaPluginKey = new PluginKey4("jinjaDecoration");
4392
5265
  var JINJA_TAG_RE = /\{%\s*(if|elif|else|endif)\s*([^%]*?)\s*%\}/g;
4393
5266
  function getBlockPositions(doc2) {
4394
5267
  const blocks = [];
@@ -4410,90 +5283,940 @@ function addInlineChipDecorations(doc2, decorations) {
4410
5283
  const to = from + match[0].length;
4411
5284
  const keyword = match[1];
4412
5285
  decorations.push(
4413
- Decoration2.inline(from, to, {
5286
+ Decoration3.inline(from, to, {
4414
5287
  class: `jinja-chip jinja-chip-${keyword}`
4415
5288
  })
4416
5289
  );
4417
5290
  }
4418
5291
  });
4419
5292
  }
4420
- function addStructureBorderDecorations(doc2, blocks, decorations) {
4421
- const json = doc2.toJSON();
4422
- let ast;
4423
- try {
4424
- ast = fromProseMirrorJSON(json);
4425
- } catch {
4426
- return;
5293
+ function addStructureBorderDecorations(doc2, blocks, decorations) {
5294
+ const json = doc2.toJSON();
5295
+ let ast;
5296
+ try {
5297
+ ast = fromProseMirrorJSON(json);
5298
+ } catch {
5299
+ return;
5300
+ }
5301
+ const structures = analyzeJinjaBlocks(ast);
5302
+ for (const structure of structures) {
5303
+ const blockBranchType = /* @__PURE__ */ new Map();
5304
+ for (const branch of structure.branches) {
5305
+ blockBranchType.set(branch.tagBlockIndex, branch.type);
5306
+ for (let i = branch.blockStartIndex; i < branch.blockEndIndex; i++) {
5307
+ if (!blockBranchType.has(i)) {
5308
+ blockBranchType.set(i, branch.type);
5309
+ }
5310
+ }
5311
+ }
5312
+ const lastBranch = structure.branches[structure.branches.length - 1];
5313
+ if (lastBranch && !blockBranchType.has(structure.endifTagBlockIndex)) {
5314
+ blockBranchType.set(structure.endifTagBlockIndex, lastBranch.type);
5315
+ }
5316
+ const first = structure.ifTagBlockIndex;
5317
+ const last = structure.endifTagBlockIndex;
5318
+ for (let i = first; i <= last; i++) {
5319
+ const block = blocks[i];
5320
+ if (!block) continue;
5321
+ const branchType = blockBranchType.get(i) || "if";
5322
+ const prevType = i > first ? blockBranchType.get(i - 1) : void 0;
5323
+ const nextType = i < last ? blockBranchType.get(i + 1) : void 0;
5324
+ const classes = ["jinja-bar", `jinja-bar-${branchType}`];
5325
+ if (i === first || prevType !== branchType) classes.push("jinja-bar-first");
5326
+ if (i === last || nextType !== branchType) classes.push("jinja-bar-last");
5327
+ decorations.push(Decoration3.node(block.from, block.to, { class: classes.join(" ") }));
5328
+ }
5329
+ }
5330
+ }
5331
+ function hasJinjaTags(doc2) {
5332
+ let found = false;
5333
+ doc2.descendants((node) => {
5334
+ if (found) return false;
5335
+ if (node.isText && node.text.includes("{%")) {
5336
+ found = true;
5337
+ return false;
5338
+ }
5339
+ });
5340
+ return found;
5341
+ }
5342
+ function buildDecorations3(doc2) {
5343
+ if (!hasJinjaTags(doc2)) return DecorationSet3.empty;
5344
+ const blocks = getBlockPositions(doc2);
5345
+ const decorations = [];
5346
+ addInlineChipDecorations(doc2, decorations);
5347
+ addStructureBorderDecorations(doc2, blocks, decorations);
5348
+ if (decorations.length === 0) return DecorationSet3.empty;
5349
+ return DecorationSet3.create(doc2, decorations);
5350
+ }
5351
+ function createJinjaDecorationPlugin() {
5352
+ return {
5353
+ name: "jinjaDecoration",
5354
+ plugins: () => [
5355
+ new Plugin4({
5356
+ key: jinjaPluginKey,
5357
+ state: {
5358
+ init(_, state) {
5359
+ return buildDecorations3(state.doc);
5360
+ },
5361
+ apply(tr, oldDecos) {
5362
+ if (!tr.docChanged) return oldDecos;
5363
+ return buildDecorations3(tr.doc);
5364
+ }
5365
+ },
5366
+ props: {
5367
+ decorations(state) {
5368
+ return jinjaPluginKey.getState(state);
5369
+ }
5370
+ }
5371
+ })
5372
+ ]
5373
+ };
5374
+ }
5375
+
5376
+ // src/ui/plugin/jinjaIfBlockPlugin.tsx
5377
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
5378
+ import { createRoot as createRoot2 } from "react-dom/client";
5379
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
5380
+ var PLACEHOLDER_TEXT = "Describe what AI agent should do when this condition is met";
5381
+ var CONDITION_PLACEHOLDER = "Write a condition in natural language";
5382
+ var STYLE_ID = "ab-jinja-if-block-styles";
5383
+ var JINJA_STYLES = `
5384
+ .jinja-if-block {
5385
+ margin: 8px 0;
5386
+ }
5387
+
5388
+ .jinja-branch {
5389
+ position: relative;
5390
+ display: grid;
5391
+ grid-template-areas:
5392
+ "header"
5393
+ "body"
5394
+ "footer";
5395
+ gap: 0;
5396
+ }
5397
+
5398
+ .jinja-branch-controls {
5399
+ display: contents;
5400
+ }
5401
+
5402
+ .jinja-branch-header {
5403
+ grid-area: header;
5404
+ display: flex;
5405
+ align-items: center;
5406
+ gap: 12px;
5407
+ padding: 8px;
5408
+ border: 1px solid #E0E0E0;
5409
+ border-radius: 4px;
5410
+ background: #FFFFFF;
5411
+ }
5412
+
5413
+ .jinja-branch-badge {
5414
+ display: inline-flex;
5415
+ align-items: center;
5416
+ justify-content: center;
5417
+ height: 32px;
5418
+ padding: 0 8px;
5419
+ border-radius: 4px;
5420
+ font-family: "Roboto Mono", monospace;
5421
+ font-size: 13px;
5422
+ font-weight: 700;
5423
+ line-height: 20px;
5424
+ letter-spacing: -0.3px;
5425
+ white-space: nowrap;
5426
+ flex-shrink: 0;
5427
+ }
5428
+
5429
+ .jinja-branch-badge-if,
5430
+ .jinja-branch-badge-elif {
5431
+ background: #E7F1FF;
5432
+ color: #0D0D0D;
5433
+ }
5434
+
5435
+ .jinja-branch-badge-else {
5436
+ background: #F7F7F7;
5437
+ color: #424242;
5438
+ }
5439
+
5440
+ .jinja-branch-condition {
5441
+ flex: 1;
5442
+ min-width: 0;
5443
+ display: flex;
5444
+ flex-wrap: wrap;
5445
+ gap: 8px;
5446
+ align-items: center;
5447
+ width: 100%;
5448
+ padding: 0;
5449
+ margin: 0;
5450
+ background: transparent;
5451
+ border: none;
5452
+ color: #0D0D0D;
5453
+ cursor: text;
5454
+ text-align: left;
5455
+ font-family: "Roboto Mono", monospace;
5456
+ font-size: 13px;
5457
+ line-height: 20px;
5458
+ letter-spacing: -0.3px;
5459
+ white-space: nowrap;
5460
+ }
5461
+
5462
+ .jinja-branch-condition:focus-visible,
5463
+ .jinja-ghost-btn:focus-visible,
5464
+ .jinja-popup-item:focus-visible {
5465
+ outline: 2px solid rgba(98, 16, 204, 0.28);
5466
+ outline-offset: 2px;
5467
+ }
5468
+
5469
+ .jinja-condition-placeholder {
5470
+ color: #A6A6A6;
5471
+ font-family: "Roboto Mono", monospace;
5472
+ font-size: 13px;
5473
+ line-height: 20px;
5474
+ letter-spacing: -0.3px;
5475
+ }
5476
+
5477
+ .jinja-token-variable {
5478
+ color: #4141B2;
5479
+ }
5480
+
5481
+ .jinja-token-operator {
5482
+ color: #858585;
5483
+ }
5484
+
5485
+ .jinja-token-value {
5486
+ color: #0D0D0D;
5487
+ }
5488
+
5489
+ .jinja-token-punctuation {
5490
+ color: #858585;
5491
+ }
5492
+
5493
+ .jinja-otherwise {
5494
+ flex: 1;
5495
+ color: #858585;
5496
+ font-family: "Roboto Mono", monospace;
5497
+ font-size: 13px;
5498
+ line-height: 20px;
5499
+ letter-spacing: -0.3px;
5500
+ }
5501
+
5502
+ .jinja-branch-actions,
5503
+ .jinja-add-footer-actions {
5504
+ position: relative;
5505
+ display: inline-flex;
5506
+ align-items: center;
5507
+ }
5508
+
5509
+ .jinja-branch-body {
5510
+ grid-area: body;
5511
+ display: flex;
5512
+ gap: 12px;
5513
+ padding: 0 8px;
5514
+ min-height: 52px;
5515
+ }
5516
+
5517
+ .jinja-branch-divider-col {
5518
+ width: 32px;
5519
+ flex-shrink: 0;
5520
+ display: flex;
5521
+ align-items: stretch;
5522
+ justify-content: center;
5523
+ }
5524
+
5525
+ .jinja-branch-divider {
5526
+ width: 1px;
5527
+ background: #E0E0E0;
5528
+ }
5529
+
5530
+ /* L-shaped divider for the last branch (ELSE) */
5531
+ .jinja-branch-last .jinja-branch-divider-col {
5532
+ align-items: flex-start;
5533
+ padding-left: 16px;
5534
+ }
5535
+
5536
+ .jinja-branch-last .jinja-branch-divider {
5537
+ width: 16px;
5538
+ height: 28px;
5539
+ background: none;
5540
+ border-left: 1px solid #E0E0E0;
5541
+ border-bottom: 1px solid #E0E0E0;
5542
+ border-bottom-left-radius: 8px;
5543
+ }
5544
+
5545
+ .jinja-branch-content {
5546
+ position: relative;
5547
+ flex: 1;
5548
+ min-width: 0;
5549
+ padding: 16px 0;
5550
+ }
5551
+
5552
+ .jinja-branch-content.jinja-branch-content-empty::before {
5553
+ content: attr(data-placeholder);
5554
+ position: absolute;
5555
+ top: 16px;
5556
+ left: 0;
5557
+ color: #A6A6A6;
5558
+ font-size: 14px;
5559
+ line-height: 20px;
5560
+ pointer-events: none;
5561
+ }
5562
+
5563
+ .jinja-add-footer {
5564
+ grid-area: footer;
5565
+ display: flex;
5566
+ padding: 8px;
5567
+ }
5568
+
5569
+ .jinja-add-footer .jinja-add-footer-col {
5570
+ width: 32px;
5571
+ flex-shrink: 0;
5572
+ display: flex;
5573
+ align-items: center;
5574
+ justify-content: center;
5575
+ }
5576
+
5577
+ .jinja-ghost-btn {
5578
+ width: 24px;
5579
+ height: 24px;
5580
+ display: inline-flex;
5581
+ align-items: center;
5582
+ justify-content: center;
5583
+ padding: 0;
5584
+ border: none;
5585
+ border-radius: 4px;
5586
+ background: transparent;
5587
+ color: #858585;
5588
+ cursor: pointer;
5589
+ }
5590
+
5591
+ .jinja-ghost-btn:hover:not(:disabled) {
5592
+ background: rgba(13, 13, 13, 0.04);
5593
+ }
5594
+
5595
+ .jinja-ghost-btn:disabled {
5596
+ opacity: 0.45;
5597
+ cursor: default;
5598
+ }
5599
+
5600
+ .jinja-popup-menu {
5601
+ position: absolute;
5602
+ z-index: 20;
5603
+ min-width: 200px;
5604
+ padding: 8px 0;
5605
+ background: #FFFFFF;
5606
+ border-radius: 4px;
5607
+ box-shadow: 0px 8px 10px 0px rgba(13,13,13,0.12), 0px 3px 14px 0px rgba(13,13,13,0.08), 0px 3px 5px 0px rgba(13,13,13,0.04);
5608
+ }
5609
+
5610
+ .jinja-popup-item {
5611
+ width: 100%;
5612
+ display: block;
5613
+ padding: 6px 16px;
5614
+ border: none;
5615
+ background: transparent;
5616
+ color: #0D0D0D;
5617
+ text-align: left;
5618
+ cursor: pointer;
5619
+ font-size: 14px;
5620
+ line-height: 20px;
5621
+ letter-spacing: -0.1px;
5622
+ }
5623
+
5624
+ .jinja-popup-item:hover:not(:disabled) {
5625
+ background: rgba(13, 13, 13, 0.04);
5626
+ }
5627
+
5628
+ .jinja-popup-item:disabled {
5629
+ color: #A6A6A6;
5630
+ cursor: default;
5631
+ }
5632
+
5633
+ .jinja-condition-input {
5634
+ width: 100%;
5635
+ min-width: 0;
5636
+ padding: 0;
5637
+ margin: 0;
5638
+ border: none;
5639
+ outline: none;
5640
+ background: transparent;
5641
+ color: #0D0D0D;
5642
+ caret-color: #6210CC;
5643
+ font-family: "Roboto Mono", monospace;
5644
+ font-size: 13px;
5645
+ line-height: 20px;
5646
+ letter-spacing: -0.3px;
5647
+ }
5648
+
5649
+ .jinja-condition-input::placeholder {
5650
+ color: #A6A6A6;
5651
+ }
5652
+ `;
5653
+ var StyleManager = class {
5654
+ static refCount = 0;
5655
+ static styleEl = null;
5656
+ static acquire() {
5657
+ if (this.refCount++ > 0) return;
5658
+ const existing = document.getElementById(STYLE_ID);
5659
+ if (existing instanceof HTMLStyleElement) {
5660
+ this.styleEl = existing;
5661
+ return;
5662
+ }
5663
+ this.styleEl = document.createElement("style");
5664
+ this.styleEl.id = STYLE_ID;
5665
+ this.styleEl.textContent = JINJA_STYLES;
5666
+ document.head.appendChild(this.styleEl);
5667
+ }
5668
+ static release() {
5669
+ if (this.refCount === 0) return;
5670
+ if (--this.refCount > 0) return;
5671
+ if (this.styleEl?.parentNode) {
5672
+ this.styleEl.remove();
5673
+ }
5674
+ this.styleEl = null;
5675
+ }
5676
+ };
5677
+ function getBranchContext(view, nodePos) {
5678
+ try {
5679
+ const { doc: doc2 } = view.state;
5680
+ const resolved = doc2.resolve(nodePos);
5681
+ let blockDepth = -1;
5682
+ for (let depth = resolved.depth; depth >= 0; depth--) {
5683
+ if (resolved.node(depth).type.name === "jinjaIfBlock") {
5684
+ blockDepth = depth;
5685
+ break;
5686
+ }
5687
+ }
5688
+ if (blockDepth < 0) {
5689
+ return null;
5690
+ }
5691
+ const blockNode = resolved.node(blockDepth);
5692
+ const blockPos = blockDepth === 0 ? 0 : resolved.before(blockDepth);
5693
+ let branchOffset = blockPos + 1;
5694
+ for (let index = 0; index < blockNode.childCount; index++) {
5695
+ const branchNode = blockNode.child(index);
5696
+ if (branchOffset === nodePos) {
5697
+ return {
5698
+ blockNode,
5699
+ blockPos,
5700
+ branchNode,
5701
+ branchPos: branchOffset,
5702
+ branchIndex: index
5703
+ };
5704
+ }
5705
+ branchOffset += branchNode.nodeSize;
5706
+ }
5707
+ } catch {
5708
+ return null;
5709
+ }
5710
+ return null;
5711
+ }
5712
+ function isBranchBodyEmpty(node) {
5713
+ if (node.childCount !== 1) return false;
5714
+ const firstChild = node.firstChild;
5715
+ return firstChild?.type.name === "paragraph" && firstChild.content.size === 0;
5716
+ }
5717
+ function isBranchNode(node) {
5718
+ return node?.type.name === "jinjaIfBranch";
5719
+ }
5720
+ function createBranchNode(schema, branchType, condition) {
5721
+ const branchNodeType = schema.nodes.jinjaIfBranch;
5722
+ const paragraphNode = schema.nodes.paragraph?.createAndFill();
5723
+ if (!branchNodeType || !paragraphNode) {
5724
+ return null;
5725
+ }
5726
+ return branchNodeType.create(
5727
+ {
5728
+ branchType,
5729
+ condition: branchType === "else" ? "" : condition
5730
+ },
5731
+ paragraphNode
5732
+ );
5733
+ }
5734
+ function getLastBranchOfBlock(view, nodePos) {
5735
+ const context = getBranchContext(view, nodePos);
5736
+ if (!context || context.blockNode.childCount === 0) {
5737
+ return null;
5738
+ }
5739
+ const index = context.blockNode.childCount - 1;
5740
+ return {
5741
+ index,
5742
+ node: context.blockNode.child(index)
5743
+ };
5744
+ }
5745
+ function getLastNonElseBranchIndex(view, nodePos) {
5746
+ const context = getBranchContext(view, nodePos);
5747
+ if (!context) return -1;
5748
+ for (let i = context.blockNode.childCount - 1; i >= 0; i--) {
5749
+ if (context.blockNode.child(i).attrs.branchType !== "else") return i;
5750
+ }
5751
+ return -1;
5752
+ }
5753
+ function getElseBranch(view, nodePos) {
5754
+ const context = getBranchContext(view, nodePos);
5755
+ if (!context) return false;
5756
+ for (let index = 0; index < context.blockNode.childCount; index++) {
5757
+ if (context.blockNode.child(index).attrs.branchType === "else") {
5758
+ return true;
5759
+ }
5760
+ }
5761
+ return false;
5762
+ }
5763
+ function deleteBlock(view, blockPos) {
5764
+ const blockNode = view.state.doc.nodeAt(blockPos);
5765
+ if (blockNode?.type.name !== "jinjaIfBlock") {
5766
+ return false;
5767
+ }
5768
+ view.dispatch(view.state.tr.delete(blockPos, blockPos + blockNode.nodeSize).scrollIntoView());
5769
+ view.focus();
5770
+ return true;
5771
+ }
5772
+ function insertNewBranch(view, nodePos, branchType, condition = "") {
5773
+ const context = getBranchContext(view, nodePos);
5774
+ if (!context) return false;
5775
+ if (context.branchNode.attrs.branchType === "else") return false;
5776
+ const lastBranch = getLastBranchOfBlock(view, nodePos);
5777
+ if (!lastBranch) return false;
5778
+ if (branchType === "else") {
5779
+ if (getElseBranch(view, nodePos)) return false;
5780
+ if (lastBranch.index !== context.branchIndex) return false;
5781
+ }
5782
+ const newBranch = createBranchNode(view.state.schema, branchType, condition);
5783
+ if (!newBranch) return false;
5784
+ const insertPos = context.branchPos + context.branchNode.nodeSize;
5785
+ view.dispatch(view.state.tr.insert(insertPos, newBranch).scrollIntoView());
5786
+ view.focus();
5787
+ return true;
5788
+ }
5789
+ function deleteBranch(view, nodePos, branchType) {
5790
+ const context = getBranchContext(view, nodePos);
5791
+ if (!context) return false;
5792
+ if (branchType === "if") {
5793
+ if (context.blockNode.childCount === 1) {
5794
+ return deleteBlock(view, context.blockPos);
5795
+ }
5796
+ const nextBranch = context.blockNode.child(context.branchIndex + 1);
5797
+ const promotedCondition = typeof nextBranch.attrs.condition === "string" ? nextBranch.attrs.condition : "";
5798
+ const tr = view.state.tr.delete(context.branchPos, context.branchPos + context.branchNode.nodeSize);
5799
+ tr.setNodeMarkup(context.branchPos, void 0, {
5800
+ ...nextBranch.attrs,
5801
+ branchType: "if",
5802
+ condition: promotedCondition
5803
+ });
5804
+ view.dispatch(tr.scrollIntoView());
5805
+ view.focus();
5806
+ return true;
5807
+ }
5808
+ view.dispatch(view.state.tr.delete(context.branchPos, context.branchPos + context.branchNode.nodeSize).scrollIntoView());
5809
+ view.focus();
5810
+ return true;
5811
+ }
5812
+ function renderCondition(condition) {
5813
+ const tokens = tokenizeCondition(condition);
5814
+ const rendered = [];
5815
+ for (let index = 0; index < tokens.length; index++) {
5816
+ const token = tokens[index];
5817
+ rendered.push(
5818
+ /* @__PURE__ */ jsx6("span", { className: `jinja-token-${token.category}`, children: token.text }, `token-${index}-${token.start}`)
5819
+ );
5820
+ }
5821
+ return rendered;
5822
+ }
5823
+ function ConditionDisplay({
5824
+ branchType,
5825
+ condition,
5826
+ editable = true,
5827
+ onConditionChange
5828
+ }) {
5829
+ const [isEditing, setIsEditing] = useState3(false);
5830
+ const [draftValue, setDraftValue] = useState3(condition);
5831
+ const inputRef = useRef2(null);
5832
+ useEffect2(() => {
5833
+ if (!isEditing) {
5834
+ setDraftValue(condition);
5835
+ }
5836
+ }, [condition, isEditing]);
5837
+ useEffect2(() => {
5838
+ if (isEditing) {
5839
+ inputRef.current?.focus();
5840
+ inputRef.current?.select();
5841
+ }
5842
+ }, [isEditing]);
5843
+ if (branchType === "else") {
5844
+ return /* @__PURE__ */ jsx6("span", { className: "jinja-otherwise", children: "Otherwise" });
5845
+ }
5846
+ const commit = () => {
5847
+ setIsEditing(false);
5848
+ if (draftValue !== condition) {
5849
+ onConditionChange(draftValue);
5850
+ }
5851
+ };
5852
+ const cancel = () => {
5853
+ setDraftValue(condition);
5854
+ setIsEditing(false);
5855
+ };
5856
+ if (isEditing && editable) {
5857
+ return /* @__PURE__ */ jsx6(
5858
+ "input",
5859
+ {
5860
+ ref: inputRef,
5861
+ className: "jinja-condition-input",
5862
+ value: draftValue,
5863
+ placeholder: CONDITION_PLACEHOLDER,
5864
+ onChange: (event) => setDraftValue(event.target.value),
5865
+ onBlur: commit,
5866
+ onKeyDown: (event) => {
5867
+ if (event.key === "Enter") {
5868
+ event.preventDefault();
5869
+ commit();
5870
+ }
5871
+ if (event.key === "Escape") {
5872
+ event.preventDefault();
5873
+ cancel();
5874
+ }
5875
+ }
5876
+ }
5877
+ );
5878
+ }
5879
+ return /* @__PURE__ */ jsx6(
5880
+ "button",
5881
+ {
5882
+ type: "button",
5883
+ className: "jinja-branch-condition",
5884
+ onMouseDown: (event) => event.preventDefault(),
5885
+ onClick: () => {
5886
+ if (editable) {
5887
+ setDraftValue(condition);
5888
+ setIsEditing(true);
5889
+ }
5890
+ },
5891
+ children: condition.length > 0 ? renderCondition(condition) : /* @__PURE__ */ jsx6("span", { className: "jinja-condition-placeholder", children: CONDITION_PLACEHOLDER })
5892
+ }
5893
+ );
5894
+ }
5895
+ function BranchPopupMenu({ items, position }) {
5896
+ return /* @__PURE__ */ jsx6(
5897
+ "div",
5898
+ {
5899
+ className: "jinja-popup-menu",
5900
+ style: {
5901
+ top: position.top,
5902
+ left: position.left,
5903
+ right: position.right
5904
+ },
5905
+ role: "menu",
5906
+ children: items.map((item) => /* @__PURE__ */ jsx6(
5907
+ "button",
5908
+ {
5909
+ type: "button",
5910
+ className: "jinja-popup-item",
5911
+ disabled: item.disabled,
5912
+ onMouseDown: (event) => event.preventDefault(),
5913
+ onClick: () => {
5914
+ if (!item.disabled) {
5915
+ item.onSelect();
5916
+ }
5917
+ },
5918
+ role: "menuitem",
5919
+ children: item.label
5920
+ },
5921
+ item.label
5922
+ ))
5923
+ }
5924
+ );
5925
+ }
5926
+ function JinjaBranchHeader({
5927
+ branchType,
5928
+ condition,
5929
+ editable = true,
5930
+ onConditionChange,
5931
+ onDelete,
5932
+ onAddBranch,
5933
+ isLastBranch,
5934
+ hasElseBranch
5935
+ }) {
5936
+ const [menuSource, setMenuSource] = useState3(null);
5937
+ const kebabRef = useRef2(null);
5938
+ const footerRef = useRef2(null);
5939
+ useEffect2(() => {
5940
+ if (!menuSource) return;
5941
+ const handlePointerDown = (event) => {
5942
+ const target = event.target;
5943
+ if (!target) return;
5944
+ if (kebabRef.current?.contains(target) || footerRef.current?.contains(target)) {
5945
+ return;
5946
+ }
5947
+ setMenuSource(null);
5948
+ };
5949
+ const handleKeyDown = (event) => {
5950
+ if (event.key === "Escape") {
5951
+ setMenuSource(null);
5952
+ }
5953
+ };
5954
+ document.addEventListener("pointerdown", handlePointerDown, true);
5955
+ document.addEventListener("keydown", handleKeyDown);
5956
+ return () => {
5957
+ document.removeEventListener("pointerdown", handlePointerDown, true);
5958
+ document.removeEventListener("keydown", handleKeyDown);
5959
+ };
5960
+ }, [menuSource]);
5961
+ const menuItems = [];
5962
+ const isOnlyIfBranch = branchType === "if" && isLastBranch && !hasElseBranch;
5963
+ const canAddElif = editable && branchType !== "else" && (branchType === "elif" || isLastBranch);
5964
+ const canAddElse = editable && branchType !== "else" && isLastBranch && !hasElseBranch;
5965
+ if (canAddElif) {
5966
+ menuItems.push({
5967
+ label: "Else if",
5968
+ onSelect: () => onAddBranch("elif")
5969
+ });
5970
+ }
5971
+ if (canAddElse) {
5972
+ menuItems.push({
5973
+ label: "Else",
5974
+ onSelect: () => onAddBranch("else")
5975
+ });
5976
+ }
5977
+ if (editable) {
5978
+ menuItems.push({
5979
+ label: isOnlyIfBranch ? "Delete all" : "Delete",
5980
+ onSelect: onDelete
5981
+ });
5982
+ }
5983
+ const canOpenFooterMenu = editable && isLastBranch && branchType !== "else";
5984
+ const isMenuOpen = (source) => menuSource === source && menuItems.length > 0;
5985
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
5986
+ /* @__PURE__ */ jsxs5("div", { className: "jinja-branch-header", children: [
5987
+ /* @__PURE__ */ jsx6("span", { className: `jinja-branch-badge jinja-branch-badge-${branchType}`, children: branchType === "elif" ? "ELSE IF" : branchType.toUpperCase() }),
5988
+ /* @__PURE__ */ jsx6(
5989
+ ConditionDisplay,
5990
+ {
5991
+ branchType,
5992
+ condition,
5993
+ editable,
5994
+ onConditionChange
5995
+ }
5996
+ ),
5997
+ editable ? /* @__PURE__ */ jsxs5("div", { className: "jinja-branch-actions", ref: kebabRef, children: [
5998
+ /* @__PURE__ */ jsx6(
5999
+ "button",
6000
+ {
6001
+ type: "button",
6002
+ className: "jinja-ghost-btn",
6003
+ "aria-label": `${branchType} branch options`,
6004
+ onMouseDown: (event) => event.preventDefault(),
6005
+ onClick: () => {
6006
+ if (menuItems.length > 0) {
6007
+ setMenuSource((current) => current === "kebab" ? null : "kebab");
6008
+ }
6009
+ },
6010
+ children: /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: [
6011
+ /* @__PURE__ */ jsx6("circle", { cx: "8", cy: "3", r: "1.5" }),
6012
+ /* @__PURE__ */ jsx6("circle", { cx: "8", cy: "8", r: "1.5" }),
6013
+ /* @__PURE__ */ jsx6("circle", { cx: "8", cy: "13", r: "1.5" })
6014
+ ] })
6015
+ }
6016
+ ),
6017
+ isMenuOpen("kebab") ? /* @__PURE__ */ jsx6(
6018
+ BranchPopupMenu,
6019
+ {
6020
+ position: { top: 28, right: 0 },
6021
+ items: menuItems.map((item) => ({
6022
+ ...item,
6023
+ onSelect: () => {
6024
+ setMenuSource(null);
6025
+ item.onSelect();
6026
+ }
6027
+ }))
6028
+ }
6029
+ ) : null
6030
+ ] }) : null
6031
+ ] }),
6032
+ isLastBranch ? /* @__PURE__ */ jsx6("div", { className: "jinja-add-footer", children: /* @__PURE__ */ jsx6("div", { className: "jinja-add-footer-col", children: /* @__PURE__ */ jsxs5("div", { className: "jinja-add-footer-actions", ref: footerRef, children: [
6033
+ /* @__PURE__ */ jsx6(
6034
+ "button",
6035
+ {
6036
+ type: "button",
6037
+ className: "jinja-ghost-btn",
6038
+ "aria-label": "Add branch",
6039
+ disabled: !canOpenFooterMenu,
6040
+ onMouseDown: (event) => event.preventDefault(),
6041
+ onClick: () => {
6042
+ if (canOpenFooterMenu && menuItems.length > 0) {
6043
+ setMenuSource((current) => current === "footer" ? null : "footer");
6044
+ }
6045
+ },
6046
+ children: /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
6047
+ /* @__PURE__ */ jsx6("line", { x1: "8", y1: "3", x2: "8", y2: "13" }),
6048
+ /* @__PURE__ */ jsx6("line", { x1: "3", y1: "8", x2: "13", y2: "8" })
6049
+ ] })
6050
+ }
6051
+ ),
6052
+ isMenuOpen("footer") ? /* @__PURE__ */ jsx6(
6053
+ BranchPopupMenu,
6054
+ {
6055
+ position: { top: 28, left: 0 },
6056
+ items: menuItems.filter((item) => item.label === "Else if" || item.label === "Else").map((item) => ({
6057
+ ...item,
6058
+ onSelect: () => {
6059
+ setMenuSource(null);
6060
+ item.onSelect();
6061
+ }
6062
+ }))
6063
+ }
6064
+ ) : null
6065
+ ] }) }) }) : null
6066
+ ] });
6067
+ }
6068
+ var JinjaIfBlockView = class {
6069
+ dom;
6070
+ contentDOM;
6071
+ constructor() {
6072
+ this.dom = document.createElement("div");
6073
+ this.dom.className = "jinja-if-block";
6074
+ this.dom.setAttribute("data-jinja-if-block", "");
6075
+ this.contentDOM = this.dom;
6076
+ requestAnimationFrame(() => this.notifyChildren());
6077
+ }
6078
+ notifyChildren() {
6079
+ this.dom.querySelectorAll(".jinja-branch").forEach((el) => {
6080
+ el.dispatchEvent(new CustomEvent("jinja-siblings-changed", { bubbles: false }));
6081
+ });
4427
6082
  }
4428
- const structures = analyzeJinjaBlocks(ast);
4429
- for (const structure of structures) {
4430
- const blockBranchType = /* @__PURE__ */ new Map();
4431
- for (const branch of structure.branches) {
4432
- blockBranchType.set(branch.tagBlockIndex, branch.type);
4433
- for (let i = branch.blockStartIndex; i < branch.blockEndIndex; i++) {
4434
- if (!blockBranchType.has(i)) {
4435
- blockBranchType.set(i, branch.type);
4436
- }
4437
- }
4438
- }
4439
- const lastBranch = structure.branches[structure.branches.length - 1];
4440
- if (lastBranch && !blockBranchType.has(structure.endifTagBlockIndex)) {
4441
- blockBranchType.set(structure.endifTagBlockIndex, lastBranch.type);
4442
- }
4443
- const first = structure.ifTagBlockIndex;
4444
- const last = structure.endifTagBlockIndex;
4445
- for (let i = first; i <= last; i++) {
4446
- const block = blocks[i];
4447
- if (!block) continue;
4448
- const branchType = blockBranchType.get(i) || "if";
4449
- const prevType = i > first ? blockBranchType.get(i - 1) : void 0;
4450
- const nextType = i < last ? blockBranchType.get(i + 1) : void 0;
4451
- const classes = ["jinja-bar", `jinja-bar-${branchType}`];
4452
- if (i === first || prevType !== branchType) classes.push("jinja-bar-first");
4453
- if (i === last || nextType !== branchType) classes.push("jinja-bar-last");
4454
- decorations.push(Decoration2.node(block.from, block.to, { class: classes.join(" ") }));
4455
- }
6083
+ update(node) {
6084
+ if (node.type.name !== "jinjaIfBlock") return false;
6085
+ requestAnimationFrame(() => this.notifyChildren());
6086
+ return true;
4456
6087
  }
4457
- }
4458
- function buildDecorations2(doc2) {
4459
- const blocks = getBlockPositions(doc2);
4460
- const decorations = [];
4461
- addInlineChipDecorations(doc2, decorations);
4462
- addStructureBorderDecorations(doc2, blocks, decorations);
4463
- if (decorations.length === 0) return DecorationSet2.empty;
4464
- return DecorationSet2.create(doc2, decorations);
4465
- }
4466
- function createJinjaDecorationPlugin() {
4467
- return {
4468
- name: "jinjaDecoration",
4469
- plugins: () => [
4470
- new Plugin3({
4471
- key: jinjaPluginKey,
4472
- state: {
4473
- init(_, state) {
4474
- return buildDecorations2(state.doc);
6088
+ };
6089
+ var JinjaIfBranchView = class {
6090
+ dom;
6091
+ contentDOM;
6092
+ headerContainer;
6093
+ root;
6094
+ node;
6095
+ view;
6096
+ getPos;
6097
+ constructor(node, view, getPos) {
6098
+ this.node = node;
6099
+ this.view = view;
6100
+ this.getPos = getPos;
6101
+ StyleManager.acquire();
6102
+ this.dom = document.createElement("div");
6103
+ this.headerContainer = document.createElement("div");
6104
+ const body = document.createElement("div");
6105
+ const dividerColumn = document.createElement("div");
6106
+ const divider = document.createElement("div");
6107
+ this.dom.appendChild(this.headerContainer);
6108
+ this.dom.appendChild(body);
6109
+ this.headerContainer.className = "jinja-branch-controls";
6110
+ this.headerContainer.contentEditable = "false";
6111
+ body.className = "jinja-branch-body";
6112
+ dividerColumn.className = "jinja-branch-divider-col";
6113
+ dividerColumn.contentEditable = "false";
6114
+ divider.className = "jinja-branch-divider";
6115
+ dividerColumn.appendChild(divider);
6116
+ this.contentDOM = document.createElement("div");
6117
+ this.contentDOM.className = "jinja-branch-content";
6118
+ this.contentDOM.setAttribute("data-placeholder", PLACEHOLDER_TEXT);
6119
+ body.appendChild(dividerColumn);
6120
+ body.appendChild(this.contentDOM);
6121
+ this.root = createRoot2(this.headerContainer);
6122
+ this._onSiblingsChanged = () => this.render();
6123
+ this.dom.addEventListener("jinja-siblings-changed", this._onSiblingsChanged);
6124
+ this.syncDOM();
6125
+ this.render();
6126
+ }
6127
+ _onSiblingsChanged = null;
6128
+ syncDOM() {
6129
+ const branchType = this.node.attrs.branchType;
6130
+ this.dom.className = `jinja-branch jinja-branch-${branchType}`;
6131
+ this.dom.setAttribute("data-jinja-branch", "");
6132
+ this.dom.setAttribute("data-branch-type", branchType);
6133
+ this.dom.setAttribute("data-condition", String(this.node.attrs.condition ?? ""));
6134
+ this.contentDOM.classList.toggle("jinja-branch-content-empty", isBranchBodyEmpty(this.node));
6135
+ }
6136
+ render() {
6137
+ const nodePos = this.getPos();
6138
+ if (nodePos == null) return;
6139
+ const lastBranch = getLastBranchOfBlock(this.view, nodePos);
6140
+ const context = getBranchContext(this.view, nodePos);
6141
+ if (!lastBranch || !context) return;
6142
+ const branchType = this.node.attrs.branchType;
6143
+ const condition = String(this.node.attrs.condition ?? "");
6144
+ const isLast = lastBranch.index === context.branchIndex;
6145
+ const lastNonElseIdx = getLastNonElseBranchIndex(this.view, nodePos);
6146
+ const showAddButton = context.branchIndex === lastNonElseIdx;
6147
+ this.dom.classList.toggle("jinja-branch-last", isLast);
6148
+ this.root.render(
6149
+ /* @__PURE__ */ jsx6(
6150
+ JinjaBranchHeader,
6151
+ {
6152
+ branchType,
6153
+ condition,
6154
+ editable: this.view.editable,
6155
+ isLastBranch: showAddButton,
6156
+ hasElseBranch: getElseBranch(this.view, nodePos),
6157
+ onConditionChange: (value) => {
6158
+ const currentPos = this.getPos();
6159
+ if (currentPos == null) return;
6160
+ this.view.dispatch(
6161
+ this.view.state.tr.setNodeMarkup(currentPos, void 0, {
6162
+ ...this.node.attrs,
6163
+ condition: value
6164
+ })
6165
+ );
6166
+ this.view.focus();
4475
6167
  },
4476
- apply(tr, oldDecos) {
4477
- if (!tr.docChanged) return oldDecos;
4478
- return buildDecorations2(tr.doc);
4479
- }
4480
- },
4481
- props: {
4482
- decorations(state) {
4483
- return jinjaPluginKey.getState(state);
6168
+ onDelete: () => {
6169
+ const currentPos = this.getPos();
6170
+ if (currentPos == null) return;
6171
+ deleteBranch(this.view, currentPos, branchType);
6172
+ },
6173
+ onAddBranch: (nextBranchType) => {
6174
+ const currentPos = this.getPos();
6175
+ if (currentPos == null) return;
6176
+ insertNewBranch(this.view, currentPos, nextBranchType, "");
4484
6177
  }
4485
6178
  }
4486
- })
4487
- ]
6179
+ )
6180
+ );
6181
+ }
6182
+ update(node) {
6183
+ if (!isBranchNode(node)) return false;
6184
+ this.node = node;
6185
+ this.syncDOM();
6186
+ this.render();
6187
+ return true;
6188
+ }
6189
+ stopEvent(event) {
6190
+ const target = event.target;
6191
+ return target != null && this.headerContainer.contains(target);
6192
+ }
6193
+ ignoreMutation(mutation) {
6194
+ return this.headerContainer.contains(mutation.target);
6195
+ }
6196
+ destroy() {
6197
+ if (this._onSiblingsChanged) {
6198
+ this.dom.removeEventListener("jinja-siblings-changed", this._onSiblingsChanged);
6199
+ }
6200
+ StyleManager.release();
6201
+ setTimeout(() => this.root.unmount(), 0);
6202
+ }
6203
+ };
6204
+ function createJinjaIfBlockPlugin() {
6205
+ return {
6206
+ name: "jinjaIfBlock",
6207
+ nodeViews: () => ({
6208
+ jinjaIfBlock: (() => new JinjaIfBlockView()),
6209
+ jinjaIfBranch: ((node, view, getPos) => new JinjaIfBranchView(node, view, getPos))
6210
+ })
4488
6211
  };
4489
6212
  }
4490
6213
 
4491
6214
  // src/ui/plugin/linkPlugin.ts
4492
6215
  import { InputRule as InputRule2, inputRules as inputRules2 } from "prosemirror-inputrules";
4493
- import { Plugin as Plugin4, PluginKey as PluginKey4, TextSelection as TextSelection2 } from "prosemirror-state";
6216
+ import { Plugin as Plugin5, PluginKey as PluginKey5, TextSelection as TextSelection3 } from "prosemirror-state";
4494
6217
  var LINK_INPUT_RE = /\[([^\]]*)\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
4495
6218
  var LINK_FULL_RE = /^\[([^\]]*)\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
4496
- var linkEditKey = new PluginKey4("linkEdit");
6219
+ var linkEditKey = new PluginKey5("linkEdit");
4497
6220
  function getMarkRange($pos, type) {
4498
6221
  const { parentOffset } = $pos;
4499
6222
  let child = $pos.parent.childAfter(parentOffset);
@@ -4536,12 +6259,12 @@ function explodeLinkToRaw(state) {
4536
6259
  const tr = state.tr;
4537
6260
  tr.replaceWith(from, to, schema.text(rawText));
4538
6261
  const newPos = Math.min(from + rawCursorOffset, tr.doc.content.size);
4539
- tr.setSelection(TextSelection2.near(tr.doc.resolve(newPos)));
6262
+ tr.setSelection(TextSelection3.near(tr.doc.resolve(newPos)));
4540
6263
  tr.setMeta(linkEditKey, { from, to: from + rawText.length });
4541
6264
  return tr;
4542
6265
  }
4543
6266
  function createLinkEditPlugin() {
4544
- return new Plugin4({
6267
+ return new Plugin5({
4545
6268
  key: linkEditKey,
4546
6269
  state: {
4547
6270
  init: () => ({ rawRange: null }),
@@ -4611,8 +6334,8 @@ function createLinkInputRule() {
4611
6334
  );
4612
6335
  }
4613
6336
  function createLinkClickPlugin() {
4614
- return new Plugin4({
4615
- key: new PluginKey4("linkClick"),
6337
+ return new Plugin5({
6338
+ key: new PluginKey5("linkClick"),
4616
6339
  props: {
4617
6340
  handleDOMEvents: {
4618
6341
  click: (_view, event) => {
@@ -4664,8 +6387,8 @@ function createLinkPlugin() {
4664
6387
  }
4665
6388
 
4666
6389
  // src/ui/plugin/dragHandlePlugin.ts
4667
- import { Plugin as Plugin5, PluginKey as PluginKey5 } from "prosemirror-state";
4668
- var PLUGIN_KEY = new PluginKey5("dragHandle");
6390
+ import { Plugin as Plugin6, PluginKey as PluginKey6 } from "prosemirror-state";
6391
+ var PLUGIN_KEY = new PluginKey6("dragHandle");
4669
6392
  var GRIP_SVG = `<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
4670
6393
  <circle cx="4" cy="2.5" r="1.2"/><circle cx="8" cy="2.5" r="1.2"/>
4671
6394
  <circle cx="4" cy="6" r="1.2"/><circle cx="8" cy="6" r="1.2"/>
@@ -4682,11 +6405,11 @@ var STYLES = (
4682
6405
  --ab-dh-color-idle: transparent;
4683
6406
  --ab-dh-color-hover: #c4c4c4;
4684
6407
  --ab-dh-color-visible: #8b8b8b;
4685
- --ab-dh-color-accent: #6366f1;
4686
- --ab-dh-bg-hover: rgba(0,0,0,0.04);
4687
- --ab-dh-bg-active: rgba(99,102,241,0.08);
6408
+ --ab-dh-color-accent: rgba(1,156,110,0.8);
6409
+ --ab-dh-bg-hover: rgba(13,13,13,0.04);
6410
+ --ab-dh-bg-active: rgba(13,13,13,0.04);
4688
6411
  --ab-dh-dragging-opacity: 0.4;
4689
- --ab-dh-handle-size: 22px;
6412
+ --ab-dh-handle-size: 24px;
4690
6413
  --ab-dh-handle-radius: 4px;
4691
6414
  --ab-dh-transition-duration: 0.15s;
4692
6415
 
@@ -4703,15 +6426,19 @@ var STYLES = (
4703
6426
  align-items: center;
4704
6427
  justify-content: center;
4705
6428
  cursor: grab;
4706
- color: var(--ab-dh-color-idle);
6429
+ color: transparent;
4707
6430
  border-radius: var(--ab-dh-handle-radius);
4708
- transition: color var(--ab-dh-transition-duration), background var(--ab-dh-transition-duration);
6431
+ transition: color var(--ab-dh-transition-duration), background var(--ab-dh-transition-duration), opacity var(--ab-dh-transition-duration);
4709
6432
  user-select: none;
4710
6433
  pointer-events: auto;
4711
6434
  touch-action: none;
6435
+ opacity: 0;
6436
+ pointer-events: none;
4712
6437
  }
4713
- .ab-drag-handle-layer[data-hover] .ab-drag-handle {
4714
- color: var(--ab-dh-color-hover);
6438
+ .ab-drag-handle.ab-dh-active {
6439
+ opacity: 1;
6440
+ pointer-events: auto;
6441
+ color: var(--ab-dh-color-visible);
4715
6442
  }
4716
6443
  .ab-drag-handle:hover {
4717
6444
  color: var(--ab-dh-color-visible) !important;
@@ -4725,18 +6452,20 @@ var STYLES = (
4725
6452
  .ab-drop-indicator {
4726
6453
  position: absolute;
4727
6454
  left: 0; right: 0;
4728
- height: 2px;
4729
- background: var(--ab-dh-color-accent, #6366f1);
4730
- border-radius: 1px;
6455
+ height: 4px;
6456
+ background: rgba(1,156,110,0.4);
6457
+ border: 1px solid rgba(1,156,110,0.6);
6458
+ border-radius: 12px;
4731
6459
  pointer-events: none;
4732
6460
  z-index: 20;
6461
+ box-sizing: border-box;
4733
6462
  transition: top 60ms ease-out;
4734
6463
  }
4735
6464
  .ab-block-dragging {
4736
6465
  opacity: var(--ab-dh-dragging-opacity, 0.4);
4737
6466
  }
4738
6467
  @keyframes ab-block-flash {
4739
- from { background: rgba(99,102,241,0.12); }
6468
+ from { background: rgba(1,156,110,0.08); }
4740
6469
  to { background: transparent; }
4741
6470
  }
4742
6471
  .ab-block-just-moved {
@@ -4753,7 +6482,7 @@ var STYLES = (
4753
6482
  }
4754
6483
  `
4755
6484
  );
4756
- var StyleManager = class {
6485
+ var StyleManager2 = class {
4757
6486
  static refCount = 0;
4758
6487
  static sheet = null;
4759
6488
  static styleEl = null;
@@ -4880,7 +6609,7 @@ var DragHandleController = class {
4880
6609
  globalCleanup = null;
4881
6610
  constructor(view) {
4882
6611
  this.view = view;
4883
- StyleManager.acquire();
6612
+ StyleManager2.acquire();
4884
6613
  const parent = view.dom.parentNode;
4885
6614
  const pos = getComputedStyle(parent).position;
4886
6615
  if (pos === "static" || pos === "") parent.style.position = "relative";
@@ -4892,23 +6621,25 @@ var DragHandleController = class {
4892
6621
  this.liveRegion.setAttribute("aria-live", "assertive");
4893
6622
  this.liveRegion.setAttribute("aria-atomic", "true");
4894
6623
  parent.appendChild(this.liveRegion);
4895
- const onEnter = () => {
4896
- this.layer.dataset.hover = "1";
4897
- };
4898
- const onLeave = () => {
4899
- if (!this.drag) delete this.layer.dataset.hover;
4900
- };
4901
- parent.addEventListener("mouseenter", onEnter);
4902
- parent.addEventListener("mouseleave", onLeave);
4903
- this.hoverBound = { enter: onEnter, leave: onLeave };
6624
+ this.hoverBound = { enter: () => {
6625
+ }, leave: () => {
6626
+ } };
4904
6627
  this.scrollContainer = this.findScrollContainer(view.dom);
4905
6628
  this.scrollContainer.addEventListener("scroll", this.onScroll, { passive: true });
4906
6629
  this.resizeObserver = new ResizeObserver(() => this.scheduleUpdate());
4907
6630
  this.resizeObserver.observe(view.dom);
6631
+ if (!view.editable) {
6632
+ this.layer.style.display = "none";
6633
+ }
4908
6634
  this.rebuildHandles();
4909
6635
  }
4910
6636
  update(view, prevState) {
4911
6637
  this.view = view;
6638
+ if (!view.editable) {
6639
+ this.layer.style.display = "none";
6640
+ return;
6641
+ }
6642
+ this.layer.style.display = "";
4912
6643
  if (this.drag) {
4913
6644
  this.pendingUpdate = true;
4914
6645
  return;
@@ -4916,6 +6647,21 @@ var DragHandleController = class {
4916
6647
  if (!view.state.doc.eq(prevState.doc)) {
4917
6648
  this.scheduleUpdate();
4918
6649
  }
6650
+ if (!view.state.selection.eq(prevState.selection) || !view.state.doc.eq(prevState.doc)) {
6651
+ this.updateActiveHandle();
6652
+ }
6653
+ }
6654
+ /** Show only the handle for the block containing the cursor. */
6655
+ updateActiveHandle() {
6656
+ const { $from } = this.view.state.selection;
6657
+ const cursorBlockOffset = $from.start(1) - 1;
6658
+ for (const h of this.handles) {
6659
+ if (h.block.offset === cursorBlockOffset) {
6660
+ h.el.classList.add("ab-dh-active");
6661
+ } else {
6662
+ h.el.classList.remove("ab-dh-active");
6663
+ }
6664
+ }
4919
6665
  }
4920
6666
  destroy() {
4921
6667
  this.cancelDrag();
@@ -4938,7 +6684,7 @@ var DragHandleController = class {
4938
6684
  }
4939
6685
  this.liveRegion.remove();
4940
6686
  this.layer.remove();
4941
- StyleManager.release();
6687
+ StyleManager2.release();
4942
6688
  }
4943
6689
  // ── Private: update pipeline ──
4944
6690
  scheduleUpdate() {
@@ -5007,6 +6753,7 @@ var DragHandleController = class {
5007
6753
  el.setAttribute("role", "button");
5008
6754
  el.setAttribute("aria-roledescription", "drag handle");
5009
6755
  el.setAttribute("aria-label", `\uBE14\uB85D ${idx + 1} \uC774\uB3D9`);
6756
+ el.setAttribute("title", "Drag to move");
5010
6757
  el.setAttribute("tabindex", "-1");
5011
6758
  el.innerHTML = GRIP_SVG;
5012
6759
  el.style.left = `${handleLeft}px`;
@@ -5017,6 +6764,7 @@ var DragHandleController = class {
5017
6764
  }
5018
6765
  this.handles = newHandles;
5019
6766
  this.prevBlocks = newBlocks;
6767
+ this.updateActiveHandle();
5020
6768
  }
5021
6769
  bindHandleEvents(el, index) {
5022
6770
  let currentIndex = index;
@@ -5087,7 +6835,8 @@ var DragHandleController = class {
5087
6835
  pointerId: e.pointerId,
5088
6836
  startX: e.clientX,
5089
6837
  startY: e.clientY,
5090
- activated: false
6838
+ activated: false,
6839
+ ghost: null
5091
6840
  };
5092
6841
  this.addGlobalDragListeners(e.pointerId);
5093
6842
  }
@@ -5096,6 +6845,22 @@ var DragHandleController = class {
5096
6845
  this.drag.activated = true;
5097
6846
  this.drag.handle.classList.add("dragging");
5098
6847
  this.drag.blockDom.classList.add("ab-block-dragging");
6848
+ const blockRect = this.drag.blockDom.getBoundingClientRect();
6849
+ const ghost = this.drag.blockDom.cloneNode(true);
6850
+ ghost.className = "ab-drag-ghost";
6851
+ ghost.style.cssText = `
6852
+ position: fixed;
6853
+ top: ${blockRect.top}px;
6854
+ left: ${blockRect.left}px;
6855
+ width: ${blockRect.width}px;
6856
+ opacity: 0.4;
6857
+ pointer-events: none;
6858
+ z-index: 9999;
6859
+ transition: none;
6860
+ `;
6861
+ document.body.appendChild(ghost);
6862
+ this.drag.ghost = ghost;
6863
+ this.drag._ghostOffsetY = this.drag.startY - blockRect.top;
5099
6864
  const parent = this.view.dom.parentNode;
5100
6865
  this.dropIndicator = document.createElement("div");
5101
6866
  this.dropIndicator.className = "ab-drop-indicator";
@@ -5135,6 +6900,10 @@ var DragHandleController = class {
5135
6900
  this.activateDrag();
5136
6901
  }
5137
6902
  this.drag.pointerY = e.clientY;
6903
+ if (this.drag.ghost) {
6904
+ const offsetY = this.drag._ghostOffsetY ?? 0;
6905
+ this.drag.ghost.style.top = `${e.clientY - offsetY}px`;
6906
+ }
5138
6907
  this.updateDropTarget();
5139
6908
  }
5140
6909
  updateDropTarget() {
@@ -5206,6 +6975,10 @@ var DragHandleController = class {
5206
6975
  this.cleaningUp = true;
5207
6976
  this.drag.handle.classList.remove("dragging");
5208
6977
  this.drag.blockDom.classList.remove("ab-block-dragging");
6978
+ if (this.drag.ghost) {
6979
+ this.drag.ghost.remove();
6980
+ this.drag.ghost = null;
6981
+ }
5209
6982
  try {
5210
6983
  this.drag.handle.releasePointerCapture(this.drag.pointerId);
5211
6984
  } catch {
@@ -5289,7 +7062,7 @@ function createDragHandlePlugin() {
5289
7062
  plugins: () => {
5290
7063
  let controller = null;
5291
7064
  return [
5292
- new Plugin5({
7065
+ new Plugin6({
5293
7066
  key: PLUGIN_KEY,
5294
7067
  view(editorView) {
5295
7068
  controller = new DragHandleController(editorView);
@@ -5411,9 +7184,58 @@ function createTodoNodeViewPlugin() {
5411
7184
  };
5412
7185
  }
5413
7186
 
7187
+ // src/ui/plugin/noteBlockPlugin.tsx
7188
+ var NoteBlockView = class {
7189
+ dom;
7190
+ contentDOM;
7191
+ constructor(_node, _view, _getPos) {
7192
+ this.dom = document.createElement("div");
7193
+ this.dom.setAttribute("data-note-block", "");
7194
+ this.dom.className = "ab-note-block";
7195
+ this.dom.style.cssText = `
7196
+ position: relative;
7197
+ margin: 8px 0;
7198
+ padding: 12px 16px 12px 16px;
7199
+ background: rgba(0, 0, 0, 0.015);
7200
+ border-top: 1px solid rgba(0, 0, 0, 0.08);
7201
+ color: #999;
7202
+ font-style: italic;
7203
+ `;
7204
+ const label = document.createElement("span");
7205
+ label.contentEditable = "false";
7206
+ label.textContent = "Note";
7207
+ label.style.cssText = `
7208
+ display: inline-block;
7209
+ font-size: 11px;
7210
+ font-weight: 600;
7211
+ font-style: normal;
7212
+ color: #aaa;
7213
+ background: rgba(0, 0, 0, 0.03);
7214
+ border: 1px solid rgba(0, 0, 0, 0.1);
7215
+ border-radius: 3px;
7216
+ padding: 1px 6px;
7217
+ margin-bottom: 6px;
7218
+ user-select: none;
7219
+ line-height: 1.4;
7220
+ `;
7221
+ this.dom.appendChild(label);
7222
+ this.contentDOM = document.createElement("div");
7223
+ this.contentDOM.className = "ab-note-block-content";
7224
+ this.dom.appendChild(this.contentDOM);
7225
+ }
7226
+ };
7227
+ function createNoteBlockPlugin() {
7228
+ return {
7229
+ name: "noteBlockNodeView",
7230
+ nodeViews: () => ({
7231
+ noteBlock: (node, view, getPos) => new NoteBlockView(node, view, getPos)
7232
+ })
7233
+ };
7234
+ }
7235
+
5414
7236
  // src/ui/plugin/slashCommandPlugin.ts
5415
- import { Plugin as Plugin6, PluginKey as PluginKey6 } from "prosemirror-state";
5416
- var slashCommandKey = new PluginKey6("slashCommand");
7237
+ import { Plugin as Plugin7, PluginKey as PluginKey7 } from "prosemirror-state";
7238
+ var slashCommandKey = new PluginKey7("slashCommand");
5417
7239
  var TRIGGER_RE = /(?:^|\s)(\/[^\s]*)$/;
5418
7240
  function deriveState(state, dismissedFrom) {
5419
7241
  const { selection } = state;
@@ -5436,7 +7258,7 @@ function deriveState(state, dismissedFrom) {
5436
7258
  };
5437
7259
  }
5438
7260
  function createSlashCommandPlugin() {
5439
- const plugin = new Plugin6({
7261
+ const plugin = new Plugin7({
5440
7262
  key: slashCommandKey,
5441
7263
  state: {
5442
7264
  init(_, state) {
@@ -5480,17 +7302,37 @@ function createSlashCommandPlugin() {
5480
7302
  }
5481
7303
 
5482
7304
  // src/ui/components/SlashCommandMenu.tsx
5483
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
7305
+ import React4, { useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState4 } from "react";
5484
7306
  import { createPortal } from "react-dom";
5485
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
7307
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
5486
7308
  function filterItems(items, query) {
5487
7309
  if (!query) return items;
5488
- const q = query.toLowerCase();
5489
- return items.filter(
5490
- ({ title, description }) => title.toLowerCase().includes(q) || (description?.toLowerCase().includes(q) ?? false)
7310
+ const colonIdx = query.indexOf(":");
7311
+ const searchPart = colonIdx >= 0 ? query.slice(colonIdx + 1).trim() : query;
7312
+ if (!searchPart) return items;
7313
+ const words = searchPart.toLowerCase().split(/\s+/);
7314
+ const filtered = items.filter(
7315
+ ({ title, description }) => {
7316
+ const hay = (title + " " + (description ?? "")).toLowerCase();
7317
+ return words.every((w) => hay.includes(w));
7318
+ }
5491
7319
  );
5492
- }
5493
- 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)";
7320
+ return filtered.length > 0 ? filtered : items;
7321
+ }
7322
+ var SCROLLBAR_STYLE_ID = "ab-slash-menu-scrollbar";
7323
+ function injectScrollbarStyle() {
7324
+ if (document.getElementById(SCROLLBAR_STYLE_ID)) return;
7325
+ const style = document.createElement("style");
7326
+ style.id = SCROLLBAR_STYLE_ID;
7327
+ style.textContent = `
7328
+ .ab-slash-menu::-webkit-scrollbar { width: 6px; }
7329
+ .ab-slash-menu::-webkit-scrollbar-track { background: transparent; }
7330
+ .ab-slash-menu::-webkit-scrollbar-thumb { background: #c4c4c4; border-radius: 3px; }
7331
+ .ab-slash-menu::-webkit-scrollbar-thumb:hover { background: #999; }
7332
+ `;
7333
+ document.head.appendChild(style);
7334
+ }
7335
+ 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)";
5494
7336
  var BTN_RESET = {
5495
7337
  border: "none",
5496
7338
  padding: 0,
@@ -5503,26 +7345,29 @@ var BTN_RESET = {
5503
7345
  width: "100%"
5504
7346
  };
5505
7347
  var VPORT_MARGIN = 8;
5506
- var MENU_WIDTH = 280;
5507
- var MAX_MENU_H = 320;
7348
+ var MENU_WIDTH = 440;
7349
+ var MAX_MENU_H = 400;
5508
7350
  function SlashCommandMenu({ view, editorState, items }) {
5509
- const [selectedIndex, setSelectedIndex] = useState2(0);
5510
- const listRef = useRef2(null);
7351
+ const [selectedIndex, setSelectedIndex] = useState4(0);
7352
+ const listRef = useRef3(null);
7353
+ useLayoutEffect(() => {
7354
+ injectScrollbarStyle();
7355
+ }, []);
5511
7356
  const pluginState = editorState ? slashCommandKey.getState(editorState) : void 0;
5512
7357
  const active = pluginState?.active ?? false;
5513
7358
  const range = pluginState?.range ?? null;
5514
7359
  const query = pluginState?.query ?? "";
5515
7360
  const filtered = filterItems(items, query);
5516
- useEffect2(() => {
7361
+ useEffect3(() => {
5517
7362
  setSelectedIndex(0);
5518
7363
  }, [query, active]);
5519
- useEffect2(() => {
7364
+ useEffect3(() => {
5520
7365
  const list = listRef.current;
5521
7366
  if (!list) return;
5522
- const item = list.children[selectedIndex];
7367
+ const item = list.querySelector(`[data-slash-index="${selectedIndex}"]`);
5523
7368
  item?.scrollIntoView({ block: "nearest" });
5524
7369
  }, [selectedIndex]);
5525
- useEffect2(() => {
7370
+ useEffect3(() => {
5526
7371
  if (!active || !view) return;
5527
7372
  const onKeyDown = (e) => {
5528
7373
  if (e.key === "ArrowDown") {
@@ -5558,9 +7403,10 @@ function SlashCommandMenu({ view, editorState, items }) {
5558
7403
  top = coords.top - MAX_MENU_H - 4;
5559
7404
  }
5560
7405
  return createPortal(
5561
- /* @__PURE__ */ jsx6(
7406
+ /* @__PURE__ */ jsx7(
5562
7407
  "div",
5563
7408
  {
7409
+ className: "ab-slash-menu",
5564
7410
  style: {
5565
7411
  position: "fixed",
5566
7412
  top,
@@ -5568,14 +7414,16 @@ function SlashCommandMenu({ view, editorState, items }) {
5568
7414
  width: MENU_WIDTH,
5569
7415
  maxHeight: MAX_MENU_H,
5570
7416
  overflowY: "auto",
7417
+ scrollbarWidth: "thin",
7418
+ scrollbarColor: "#c4c4c4 transparent",
5571
7419
  background: "#fff",
5572
- borderRadius: 10,
7420
+ borderRadius: 4,
5573
7421
  boxShadow: POPUP_SHADOW,
5574
- padding: 6,
7422
+ padding: "8px 0",
5575
7423
  zIndex: 1100,
5576
7424
  animation: "ab-float-in 0.12s ease"
5577
7425
  },
5578
- children: /* @__PURE__ */ jsx6("div", { ref: listRef, children: filtered.length === 0 ? /* @__PURE__ */ jsx6(
7426
+ children: /* @__PURE__ */ jsx7("div", { ref: listRef, children: filtered.length === 0 ? /* @__PURE__ */ jsx7(
5579
7427
  "div",
5580
7428
  {
5581
7429
  style: {
@@ -5585,101 +7433,104 @@ function SlashCommandMenu({ view, editorState, items }) {
5585
7433
  },
5586
7434
  children: "No results"
5587
7435
  }
5588
- ) : filtered.map((item, i) => /* @__PURE__ */ jsx6(
5589
- SlashMenuItem,
5590
- {
5591
- item,
5592
- selected: i === selectedIndex,
5593
- onMouseEnter: () => setSelectedIndex(i),
5594
- onMouseDown: (e) => {
5595
- e.preventDefault();
5596
- if (range) {
5597
- item.command({ view, range });
5598
- view.focus();
7436
+ ) : filtered.map((item, i) => {
7437
+ const prevGroup = i > 0 ? filtered[i - 1].group : void 0;
7438
+ const showGroupHeader = item.group && item.group !== prevGroup;
7439
+ return /* @__PURE__ */ jsxs6(React4.Fragment, { children: [
7440
+ showGroupHeader && /* @__PURE__ */ jsxs6(Fragment3, { children: [
7441
+ i > 0 && /* @__PURE__ */ jsx7("div", { style: { height: 1, background: "rgba(0,0,0,0.06)", margin: "4px 10px" } }),
7442
+ /* @__PURE__ */ jsx7(
7443
+ "div",
7444
+ {
7445
+ style: {
7446
+ padding: "8px 16px 4px",
7447
+ fontSize: 13,
7448
+ fontWeight: 400,
7449
+ color: "#5e5e5e"
7450
+ },
7451
+ children: item.group
7452
+ }
7453
+ )
7454
+ ] }),
7455
+ /* @__PURE__ */ jsx7(
7456
+ SlashMenuItem,
7457
+ {
7458
+ item,
7459
+ index: i,
7460
+ selected: i === selectedIndex,
7461
+ onMouseEnter: () => setSelectedIndex(i),
7462
+ onMouseDown: (e) => {
7463
+ e.preventDefault();
7464
+ if (range) {
7465
+ item.command({ view, range });
7466
+ view.focus();
7467
+ }
7468
+ }
5599
7469
  }
5600
- }
5601
- },
5602
- item.id ?? item.title
5603
- )) })
7470
+ )
7471
+ ] }, item.id ?? item.title);
7472
+ }) })
5604
7473
  }
5605
7474
  ),
5606
7475
  document.body
5607
7476
  );
5608
7477
  }
5609
- function SlashMenuItem({ item, selected, onMouseEnter, onMouseDown }) {
5610
- return /* @__PURE__ */ jsxs5(
7478
+ function SlashMenuItem({ item, index, selected, onMouseEnter, onMouseDown }) {
7479
+ return /* @__PURE__ */ jsxs6(
5611
7480
  "button",
5612
7481
  {
7482
+ "data-slash-index": index,
5613
7483
  style: {
5614
7484
  ...BTN_RESET,
5615
7485
  display: "flex",
5616
7486
  alignItems: "center",
5617
- gap: 10,
5618
- padding: "7px 10px",
5619
- borderRadius: 7,
5620
- background: selected ? "rgba(99,102,241,0.07)" : "transparent",
7487
+ gap: 12,
7488
+ padding: "6px 16px",
7489
+ background: selected ? "rgba(13,13,13,0.04)" : "transparent",
5621
7490
  transition: "background 0.08s"
5622
7491
  },
5623
7492
  onMouseEnter,
5624
7493
  onMouseDown,
5625
7494
  children: [
5626
- item.icon !== void 0 && /* @__PURE__ */ jsx6(
7495
+ item.icon !== void 0 && /* @__PURE__ */ jsx7("span", { style: { flexShrink: 0, display: "flex", alignItems: "center" }, children: item.icon }),
7496
+ /* @__PURE__ */ jsx7(
5627
7497
  "span",
5628
7498
  {
5629
7499
  style: {
5630
- width: 28,
5631
- height: 28,
5632
- flexShrink: 0,
5633
- display: "flex",
5634
- alignItems: "center",
5635
- justifyContent: "center",
5636
- borderRadius: 6,
5637
- background: "rgba(0,0,0,0.04)",
5638
- fontSize: 13,
5639
- fontWeight: 700,
5640
- color: "#6366f1",
5641
- fontFamily: "monospace"
7500
+ fontSize: 14,
7501
+ fontWeight: 400,
7502
+ color: "#0d0d0d",
7503
+ whiteSpace: "nowrap",
7504
+ overflow: "hidden",
7505
+ textOverflow: "ellipsis",
7506
+ minWidth: 0
5642
7507
  },
5643
- children: item.icon
7508
+ children: item.title
5644
7509
  }
5645
7510
  ),
5646
- /* @__PURE__ */ jsxs5("span", { style: { display: "flex", flexDirection: "column", gap: 1, minWidth: 0 }, children: [
5647
- /* @__PURE__ */ jsx6(
5648
- "span",
5649
- {
5650
- style: {
5651
- fontSize: 13,
5652
- fontWeight: 500,
5653
- color: "#171717",
5654
- whiteSpace: "nowrap",
5655
- overflow: "hidden",
5656
- textOverflow: "ellipsis"
5657
- },
5658
- children: item.title
5659
- }
5660
- ),
5661
- item.description && /* @__PURE__ */ jsx6(
5662
- "span",
5663
- {
5664
- style: {
5665
- fontSize: 11,
5666
- color: "#9ca3af",
5667
- whiteSpace: "nowrap",
5668
- overflow: "hidden",
5669
- textOverflow: "ellipsis"
5670
- },
5671
- children: item.description
5672
- }
5673
- )
5674
- ] })
7511
+ /* @__PURE__ */ jsx7("span", { style: { flex: 1 } }),
7512
+ (item.description || item.shortcut) && /* @__PURE__ */ jsx7(
7513
+ "span",
7514
+ {
7515
+ style: {
7516
+ fontSize: 12,
7517
+ color: "#5e5e5e",
7518
+ whiteSpace: "nowrap",
7519
+ flexShrink: 0,
7520
+ textAlign: "right",
7521
+ fontFamily: item.shortcut ? "monospace" : "inherit"
7522
+ },
7523
+ children: item.shortcut ?? item.description
7524
+ }
7525
+ )
5675
7526
  ]
5676
7527
  }
5677
7528
  );
5678
7529
  }
5679
7530
 
5680
7531
  // src/ui/components/JinjaTreeView.tsx
5681
- import { useState as useState3 } from "react";
5682
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
7532
+ import { useState as useState5 } from "react";
7533
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
5683
7534
  var BRANCH_COLORS = {
5684
7535
  if: "#6366f1",
5685
7536
  elif: "#f59e0b",
@@ -5699,21 +7550,21 @@ function BranchNode({
5699
7550
  const blockCount = branch.blockEndIndex - branch.blockStartIndex;
5700
7551
  const preview = getBlockPreview(doc2, branch.blockStartIndex, branch.blockEndIndex);
5701
7552
  const connector = isLast ? "\u2514\u2500" : "\u251C\u2500";
5702
- return /* @__PURE__ */ jsxs6("div", { className: "jinja-tree-branch", children: [
5703
- /* @__PURE__ */ jsxs6("span", { className: "jinja-tree-connector", children: [
7553
+ return /* @__PURE__ */ jsxs7("div", { className: "jinja-tree-branch", children: [
7554
+ /* @__PURE__ */ jsxs7("span", { className: "jinja-tree-connector", children: [
5704
7555
  connector,
5705
7556
  " "
5706
7557
  ] }),
5707
- /* @__PURE__ */ jsxs6("span", { className: "jinja-tree-badge", style: { color, borderColor: color }, children: [
7558
+ /* @__PURE__ */ jsxs7("span", { className: "jinja-tree-badge", style: { color, borderColor: color }, children: [
5708
7559
  "[",
5709
7560
  branch.type,
5710
7561
  "]"
5711
7562
  ] }),
5712
- branch.condition && /* @__PURE__ */ jsxs6("span", { className: "jinja-tree-condition", children: [
7563
+ branch.condition && /* @__PURE__ */ jsxs7("span", { className: "jinja-tree-condition", children: [
5713
7564
  " ",
5714
7565
  branch.condition
5715
7566
  ] }),
5716
- /* @__PURE__ */ jsxs6("span", { className: "jinja-tree-meta", children: [
7567
+ /* @__PURE__ */ jsxs7("span", { className: "jinja-tree-meta", children: [
5717
7568
  " ",
5718
7569
  blockCount,
5719
7570
  " block",
@@ -5723,23 +7574,23 @@ function BranchNode({
5723
7574
  ] });
5724
7575
  }
5725
7576
  function StructureNode({ structure, doc: doc2 }) {
5726
- const [expanded, setExpanded] = useState3(true);
7577
+ const [expanded, setExpanded] = useState5(true);
5727
7578
  const firstBranch = structure.branches[0];
5728
7579
  const label = firstBranch?.condition ? `if ${firstBranch.condition}` : "if";
5729
- return /* @__PURE__ */ jsxs6("div", { className: "jinja-tree-structure", children: [
5730
- /* @__PURE__ */ jsxs6(
7580
+ return /* @__PURE__ */ jsxs7("div", { className: "jinja-tree-structure", children: [
7581
+ /* @__PURE__ */ jsxs7(
5731
7582
  "button",
5732
7583
  {
5733
7584
  className: "jinja-tree-toggle",
5734
7585
  onClick: () => setExpanded(!expanded),
5735
7586
  type: "button",
5736
7587
  children: [
5737
- /* @__PURE__ */ jsx7("span", { className: "jinja-tree-arrow", children: expanded ? "\u25BC" : "\u25B6" }),
5738
- /* @__PURE__ */ jsx7("span", { className: "jinja-tree-label", children: label })
7588
+ /* @__PURE__ */ jsx8("span", { className: "jinja-tree-arrow", children: expanded ? "\u25BC" : "\u25B6" }),
7589
+ /* @__PURE__ */ jsx8("span", { className: "jinja-tree-label", children: label })
5739
7590
  ]
5740
7591
  }
5741
7592
  ),
5742
- expanded && /* @__PURE__ */ jsx7("div", { className: "jinja-tree-branches", children: structure.branches.map((branch, i) => /* @__PURE__ */ jsx7(
7593
+ expanded && /* @__PURE__ */ jsx8("div", { className: "jinja-tree-branches", children: structure.branches.map((branch, i) => /* @__PURE__ */ jsx8(
5743
7594
  BranchNode,
5744
7595
  {
5745
7596
  branch,
@@ -5753,16 +7604,182 @@ function StructureNode({ structure, doc: doc2 }) {
5753
7604
  function JinjaTreeView({ doc: doc2, className }) {
5754
7605
  const structures = analyzeJinjaBlocks(doc2);
5755
7606
  if (structures.length === 0) return null;
5756
- return /* @__PURE__ */ jsx7("div", { className: `jinja-tree ${className || ""}`, children: structures.map((structure) => /* @__PURE__ */ jsx7(StructureNode, { structure, doc: doc2 }, structure.id)) });
7607
+ return /* @__PURE__ */ jsx8("div", { className: `jinja-tree ${className || ""}`, children: structures.map((structure) => /* @__PURE__ */ jsx8(StructureNode, { structure, doc: doc2 }, structure.id)) });
7608
+ }
7609
+
7610
+ // src/ui/components/DocumentTreeView.tsx
7611
+ import { useState as useState6 } from "react";
7612
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
7613
+ var ICONS = {
7614
+ heading: "H",
7615
+ jumpPoint: "\u2299",
7616
+ jinjaIf: "\u2325",
7617
+ jinjaBranch: "\u2387",
7618
+ resourceTag: "\u229B",
7619
+ noteBlock: "\u270D",
7620
+ endAction: "\u2192"
7621
+ };
7622
+ var COLORS = {
7623
+ heading: "#333",
7624
+ jumpPoint: "#d97706",
7625
+ jinjaIf: "#6366f1",
7626
+ jinjaBranch: "#8b5cf6",
7627
+ resourceTag: "#059669",
7628
+ noteBlock: "#999",
7629
+ endAction: "#059669"
7630
+ };
7631
+ function TreeNodeItem({
7632
+ node,
7633
+ onNavigate,
7634
+ depth,
7635
+ selectedPath,
7636
+ onSelect,
7637
+ path
7638
+ }) {
7639
+ const [collapsed, setCollapsed] = useState6(false);
7640
+ const hasChildren = node.children.length > 0;
7641
+ const icon = ICONS[node.type] || "\u2022";
7642
+ const color = COLORS[node.type] || "#666";
7643
+ const isSelected = selectedPath === path;
7644
+ const headingLevel = node.meta?.level;
7645
+ const headingLabel = headingLevel ? `H${headingLevel}` : icon;
7646
+ return /* @__PURE__ */ jsxs8("div", { style: { paddingLeft: depth > 0 ? 16 : 0 }, children: [
7647
+ /* @__PURE__ */ jsxs8(
7648
+ "div",
7649
+ {
7650
+ className: "doc-tree-node",
7651
+ style: {
7652
+ display: "flex",
7653
+ alignItems: "center",
7654
+ gap: 6,
7655
+ padding: "3px 6px",
7656
+ borderRadius: 4,
7657
+ cursor: onNavigate ? "pointer" : "default",
7658
+ fontSize: 13,
7659
+ lineHeight: "1.5",
7660
+ background: isSelected ? "rgba(99, 102, 241, 0.1)" : "transparent",
7661
+ transition: "background 0.12s ease"
7662
+ },
7663
+ onClick: (e) => {
7664
+ e.stopPropagation();
7665
+ if (hasChildren) setCollapsed(!collapsed);
7666
+ onSelect(path);
7667
+ onNavigate?.(node.blockIndex);
7668
+ },
7669
+ onMouseEnter: (e) => {
7670
+ if (!isSelected) e.currentTarget.style.background = "#f3f4f6";
7671
+ },
7672
+ onMouseLeave: (e) => {
7673
+ if (!isSelected) e.currentTarget.style.background = "transparent";
7674
+ },
7675
+ children: [
7676
+ hasChildren && /* @__PURE__ */ jsx9(
7677
+ "span",
7678
+ {
7679
+ style: {
7680
+ width: 14,
7681
+ textAlign: "center",
7682
+ fontSize: 10,
7683
+ color: "#999",
7684
+ flexShrink: 0,
7685
+ userSelect: "none"
7686
+ },
7687
+ children: collapsed ? "\u25B8" : "\u25BE"
7688
+ }
7689
+ ),
7690
+ !hasChildren && /* @__PURE__ */ jsx9("span", { style: { width: 14, flexShrink: 0 } }),
7691
+ /* @__PURE__ */ jsx9(
7692
+ "span",
7693
+ {
7694
+ style: {
7695
+ display: "inline-flex",
7696
+ alignItems: "center",
7697
+ justifyContent: "center",
7698
+ width: 22,
7699
+ height: 18,
7700
+ borderRadius: 3,
7701
+ fontSize: 10,
7702
+ fontWeight: 700,
7703
+ color: isSelected ? "#6366f1" : color,
7704
+ background: isSelected ? "rgba(99, 102, 241, 0.15)" : `${color}11`,
7705
+ border: `1px solid ${isSelected ? "rgba(99, 102, 241, 0.3)" : `${color}33`}`,
7706
+ flexShrink: 0
7707
+ },
7708
+ children: node.type === "heading" ? headingLabel : icon
7709
+ }
7710
+ ),
7711
+ /* @__PURE__ */ jsx9(
7712
+ "span",
7713
+ {
7714
+ style: {
7715
+ overflow: "hidden",
7716
+ textOverflow: "ellipsis",
7717
+ whiteSpace: "nowrap",
7718
+ color: isSelected ? "#6366f1" : node.type === "noteBlock" ? "#999" : node.type === "endAction" ? "#059669" : "#333",
7719
+ fontStyle: node.type === "noteBlock" ? "italic" : "normal",
7720
+ fontWeight: isSelected ? 600 : node.type === "heading" && headingLevel && headingLevel <= 2 ? 600 : 400
7721
+ },
7722
+ children: node.label
7723
+ }
7724
+ ),
7725
+ node.meta?.tagType != null && /* @__PURE__ */ jsx9(
7726
+ "span",
7727
+ {
7728
+ style: {
7729
+ fontSize: 10,
7730
+ padding: "1px 5px",
7731
+ borderRadius: 999,
7732
+ background: node.type === "endAction" ? "rgba(5, 150, 105, 0.1)" : "rgba(0, 0, 0, 0.04)",
7733
+ color: node.type === "endAction" ? "#059669" : "#999",
7734
+ fontWeight: 500
7735
+ },
7736
+ children: String(node.meta.tagType)
7737
+ }
7738
+ )
7739
+ ]
7740
+ }
7741
+ ),
7742
+ hasChildren && !collapsed && /* @__PURE__ */ jsx9("div", { children: node.children.map((child, i) => /* @__PURE__ */ jsx9(
7743
+ TreeNodeItem,
7744
+ {
7745
+ node: child,
7746
+ onNavigate,
7747
+ depth: depth + 1,
7748
+ selectedPath,
7749
+ onSelect,
7750
+ path: `${path}/${i}`
7751
+ },
7752
+ `${child.type}-${child.blockIndex}-${i}`
7753
+ )) })
7754
+ ] });
7755
+ }
7756
+ function DocumentTreeView({ doc: doc2, className, onNavigate }) {
7757
+ const tree = buildDocumentTree(doc2);
7758
+ const [selectedPath, setSelectedPath] = useState6(null);
7759
+ if (tree.length === 0) {
7760
+ return /* @__PURE__ */ jsx9("div", { className, style: { color: "#999", fontSize: 13, padding: 16 }, children: "No document structure to display." });
7761
+ }
7762
+ return /* @__PURE__ */ jsx9("div", { className, style: { padding: "8px 0", fontFamily: "Inter, sans-serif" }, children: tree.map((node, i) => /* @__PURE__ */ jsx9(
7763
+ TreeNodeItem,
7764
+ {
7765
+ node,
7766
+ onNavigate,
7767
+ depth: 0,
7768
+ selectedPath,
7769
+ onSelect: setSelectedPath,
7770
+ path: `${i}`
7771
+ },
7772
+ `${node.type}-${node.blockIndex}-${i}`
7773
+ )) });
5757
7774
  }
5758
7775
 
5759
7776
  // src/ui/components/FloatingMenu.tsx
5760
- import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
7777
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
5761
7778
  import { createPortal as createPortal2 } from "react-dom";
5762
- import { setBlockType, toggleMark as toggleMark2, wrapIn } from "prosemirror-commands";
7779
+ import { setBlockType as setBlockType2, toggleMark as toggleMark2, wrapIn } from "prosemirror-commands";
5763
7780
  import { wrapInList as wrapInList2 } from "prosemirror-schema-list";
5764
- import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
5765
- var { paragraph: paragraph2, heading: heading2, bulletList: bulletList3, orderedList: orderedList3, blockquote: blockquote2 } = actionbookSchema.nodes;
7781
+ import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
7782
+ var { paragraph: paragraph3, heading: heading3, bulletList: bulletList3, orderedList: orderedList3, blockquote: blockquote3 } = actionbookSchema.nodes;
5766
7783
  var {
5767
7784
  bold: bold2,
5768
7785
  italic: italic2,
@@ -5784,7 +7801,7 @@ function hasMark(state, type) {
5784
7801
  function activeHeadingLevel(state) {
5785
7802
  const { $from } = state.selection;
5786
7803
  const node = $from.parent;
5787
- if (node.type === heading2) return node.attrs.level;
7804
+ if (node.type === heading3) return node.attrs.level;
5788
7805
  return null;
5789
7806
  }
5790
7807
  function activeListType(state) {
@@ -5818,7 +7835,7 @@ function currentBlockLabel(state) {
5818
7835
  if (lt === "ordered") return "1. List";
5819
7836
  const { $from } = state.selection;
5820
7837
  for (let d = $from.depth; d > 0; d--) {
5821
- if ($from.node(d).type === blockquote2) return "Quote";
7838
+ if ($from.node(d).type === blockquote3) return "Quote";
5822
7839
  }
5823
7840
  return "Text";
5824
7841
  }
@@ -5827,7 +7844,7 @@ function emptyBlockCursorPos(state) {
5827
7844
  if (!selection.empty) return null;
5828
7845
  const { $from } = selection;
5829
7846
  const parent = $from.parent;
5830
- if ((parent.type === paragraph2 || parent.type === heading2) && parent.content.size === 0) {
7847
+ if ((parent.type === paragraph3 || parent.type === heading3) && parent.content.size === 0) {
5831
7848
  return selection.from;
5832
7849
  }
5833
7850
  return null;
@@ -5849,8 +7866,8 @@ var BTN_RESET2 = {
5849
7866
  lineHeight: 1
5850
7867
  };
5851
7868
  function TBtn({ children, active, title, onMouseDown, style }) {
5852
- const [hover, setHover] = useState4(false);
5853
- return /* @__PURE__ */ jsx8(
7869
+ const [hover, setHover] = useState7(false);
7870
+ return /* @__PURE__ */ jsx10(
5854
7871
  "button",
5855
7872
  {
5856
7873
  ...{ [FLOAT_ATTR]: "" },
@@ -5881,13 +7898,13 @@ function TBtn({ children, active, title, onMouseDown, style }) {
5881
7898
  );
5882
7899
  }
5883
7900
  function Divider() {
5884
- return /* @__PURE__ */ jsx8("div", { style: { width: 1, height: 18, background: "rgba(0,0,0,0.07)", margin: "0 2px", flexShrink: 0 } });
7901
+ return /* @__PURE__ */ jsx10("div", { style: { width: 1, height: 18, background: "rgba(0,0,0,0.07)", margin: "0 2px", flexShrink: 0 } });
5885
7902
  }
5886
7903
  function BlockTypeDropdown({ view, state, label }) {
5887
- const [open, setOpen] = useState4(false);
5888
- const btnRef = useRef3(null);
5889
- const [hover, setHover] = useState4(false);
5890
- useEffect3(() => {
7904
+ const [open, setOpen] = useState7(false);
7905
+ const btnRef = useRef4(null);
7906
+ const [hover, setHover] = useState7(false);
7907
+ useEffect4(() => {
5891
7908
  if (!open) return;
5892
7909
  const onDown = (e) => {
5893
7910
  if (!e.target.closest(`[${FLOAT_ATTR}]`)) {
@@ -5901,19 +7918,19 @@ function BlockTypeDropdown({ view, state, label }) {
5901
7918
  const listType = activeListType(state);
5902
7919
  const items = [
5903
7920
  { label: "Text", shortLabel: "\xB6", active: !headingLevel && !listType, run: () => {
5904
- setBlockType(paragraph2)(view.state, view.dispatch);
7921
+ setBlockType2(paragraph3)(view.state, view.dispatch);
5905
7922
  view.focus();
5906
7923
  } },
5907
7924
  { label: "Heading 1", shortLabel: "H1", active: headingLevel === 1, run: () => {
5908
- setBlockType(heading2, { level: 1 })(view.state, view.dispatch);
7925
+ setBlockType2(heading3, { level: 1 })(view.state, view.dispatch);
5909
7926
  view.focus();
5910
7927
  } },
5911
7928
  { label: "Heading 2", shortLabel: "H2", active: headingLevel === 2, run: () => {
5912
- setBlockType(heading2, { level: 2 })(view.state, view.dispatch);
7929
+ setBlockType2(heading3, { level: 2 })(view.state, view.dispatch);
5913
7930
  view.focus();
5914
7931
  } },
5915
7932
  { label: "Heading 3", shortLabel: "H3", active: headingLevel === 3, run: () => {
5916
- setBlockType(heading2, { level: 3 })(view.state, view.dispatch);
7933
+ setBlockType2(heading3, { level: 3 })(view.state, view.dispatch);
5917
7934
  view.focus();
5918
7935
  } },
5919
7936
  { label: "Bullet List", shortLabel: "\u2022", active: listType === "bullet", run: () => {
@@ -5935,13 +7952,17 @@ function BlockTypeDropdown({ view, state, label }) {
5935
7952
  view.focus();
5936
7953
  } },
5937
7954
  { label: "Quote", shortLabel: ">", active: false, run: () => {
5938
- wrapIn(blockquote2)(view.state, view.dispatch);
7955
+ const { $from } = view.state.selection;
7956
+ for (let d = $from.depth; d > 0; d--) {
7957
+ if ($from.node(d).type.name === "listItem") return;
7958
+ }
7959
+ wrapIn(blockquote3)(view.state, view.dispatch);
5939
7960
  view.focus();
5940
7961
  } }
5941
7962
  ];
5942
7963
  const btnRect = btnRef.current?.getBoundingClientRect();
5943
- return /* @__PURE__ */ jsxs7(Fragment2, { children: [
5944
- /* @__PURE__ */ jsxs7(
7964
+ return /* @__PURE__ */ jsxs9(Fragment4, { children: [
7965
+ /* @__PURE__ */ jsxs9(
5945
7966
  "button",
5946
7967
  {
5947
7968
  ref: btnRef,
@@ -5969,12 +7990,12 @@ function BlockTypeDropdown({ view, state, label }) {
5969
7990
  },
5970
7991
  children: [
5971
7992
  label,
5972
- /* @__PURE__ */ jsx8("span", { style: { fontSize: 8, opacity: 0.6 }, children: "\u25BE" })
7993
+ /* @__PURE__ */ jsx10("span", { style: { fontSize: 8, opacity: 0.6 }, children: "\u25BE" })
5973
7994
  ]
5974
7995
  }
5975
7996
  ),
5976
7997
  open && btnRect && createPortal2(
5977
- /* @__PURE__ */ jsx8(
7998
+ /* @__PURE__ */ jsx10(
5978
7999
  "div",
5979
8000
  {
5980
8001
  ...{ [FLOAT_ATTR]: "" },
@@ -5990,7 +8011,7 @@ function BlockTypeDropdown({ view, state, label }) {
5990
8011
  minWidth: 168,
5991
8012
  animation: "ab-float-in 0.12s ease"
5992
8013
  },
5993
- children: items.map((item) => /* @__PURE__ */ jsx8(DropdownItem, { item, onRun: () => setOpen(false) }, item.label))
8014
+ children: items.map((item) => /* @__PURE__ */ jsx10(DropdownItem, { item, onRun: () => setOpen(false) }, item.label))
5994
8015
  }
5995
8016
  ),
5996
8017
  document.body
@@ -5998,8 +8019,8 @@ function BlockTypeDropdown({ view, state, label }) {
5998
8019
  ] });
5999
8020
  }
6000
8021
  function DropdownItem({ item, onRun }) {
6001
- const [hover, setHover] = useState4(false);
6002
- return /* @__PURE__ */ jsxs7(
8022
+ const [hover, setHover] = useState7(false);
8023
+ return /* @__PURE__ */ jsxs9(
6003
8024
  "button",
6004
8025
  {
6005
8026
  ...{ [FLOAT_ATTR]: "" },
@@ -6026,16 +8047,16 @@ function DropdownItem({ item, onRun }) {
6026
8047
  transition: "background 0.1s"
6027
8048
  },
6028
8049
  children: [
6029
- /* @__PURE__ */ jsx8("span", { style: { width: 24, flexShrink: 0, textAlign: "center", fontWeight: 700, fontSize: 12, fontFamily: "monospace", color: "#6366f1" }, children: item.shortLabel }),
8050
+ /* @__PURE__ */ jsx10("span", { style: { width: 24, flexShrink: 0, textAlign: "center", fontWeight: 700, fontSize: 12, fontFamily: "monospace", color: "#6366f1" }, children: item.shortLabel }),
6030
8051
  item.label
6031
8052
  ]
6032
8053
  }
6033
8054
  );
6034
8055
  }
6035
8056
  function SelectionToolbar({ view, state, selectionRect }) {
6036
- const [linkMode, setLinkMode] = useState4(false);
6037
- const [linkHref, setLinkHref] = useState4("");
6038
- const inputRef = useRef3(null);
8057
+ const [linkMode, setLinkMode] = useState7(false);
8058
+ const [linkHref, setLinkHref] = useState7("");
8059
+ const inputRef = useRef4(null);
6039
8060
  const isBold = hasMark(state, bold2);
6040
8061
  const isItalic = hasMark(state, italic2);
6041
8062
  const isUnderline = hasMark(state, underline2);
@@ -6050,7 +8071,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6050
8071
  if (top < VPORT_MARGIN2) top = selectionRect.bottom + TOOLBAR_GAP;
6051
8072
  left = Math.max(VPORT_MARGIN2, Math.min(left, window.innerWidth - toolbarW - VPORT_MARGIN2));
6052
8073
  top = Math.max(VPORT_MARGIN2, Math.min(top, window.innerHeight - toolbarH - VPORT_MARGIN2));
6053
- const run = useCallback2(
8074
+ const run = useCallback3(
6054
8075
  (cmd) => {
6055
8076
  cmd(view.state, view.dispatch);
6056
8077
  view.focus();
@@ -6087,10 +8108,10 @@ function SelectionToolbar({ view, state, selectionRect }) {
6087
8108
  view.dispatch(view.state.tr.removeMark(from, to, linkMark));
6088
8109
  view.focus();
6089
8110
  };
6090
- return /* @__PURE__ */ jsx8(
8111
+ return /* @__PURE__ */ jsx10(
6091
8112
  "div",
6092
8113
  {
6093
- ...{ [FLOAT_ATTR]: "" },
8114
+ ...{ [FLOAT_ATTR]: "selection-toolbar" },
6094
8115
  style: {
6095
8116
  position: "fixed",
6096
8117
  left,
@@ -6110,9 +8131,9 @@ function SelectionToolbar({ view, state, selectionRect }) {
6110
8131
  },
6111
8132
  children: linkMode ? (
6112
8133
  /* ── Link input mode ── */
6113
- /* @__PURE__ */ jsxs7(Fragment2, { children: [
6114
- /* @__PURE__ */ jsx8("span", { style: { fontSize: 13, color: "#9ca3af", paddingLeft: 4, flexShrink: 0 }, children: "\u2197" }),
6115
- /* @__PURE__ */ jsx8(
8134
+ /* @__PURE__ */ jsxs9(Fragment4, { children: [
8135
+ /* @__PURE__ */ jsx10("span", { style: { fontSize: 13, color: "#9ca3af", paddingLeft: 4, flexShrink: 0 }, children: "\u2197" }),
8136
+ /* @__PURE__ */ jsx10(
6116
8137
  "input",
6117
8138
  {
6118
8139
  ref: inputRef,
@@ -6143,7 +8164,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6143
8164
  }
6144
8165
  }
6145
8166
  ),
6146
- /* @__PURE__ */ jsx8(
8167
+ /* @__PURE__ */ jsx10(
6147
8168
  TBtn,
6148
8169
  {
6149
8170
  title: "Apply (Enter)",
@@ -6155,7 +8176,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6155
8176
  children: "\u21B5"
6156
8177
  }
6157
8178
  ),
6158
- /* @__PURE__ */ jsx8(
8179
+ /* @__PURE__ */ jsx10(
6159
8180
  TBtn,
6160
8181
  {
6161
8182
  title: "Cancel (Esc)",
@@ -6171,8 +8192,8 @@ function SelectionToolbar({ view, state, selectionRect }) {
6171
8192
  ] })
6172
8193
  ) : (
6173
8194
  /* ── Normal toolbar mode ── */
6174
- /* @__PURE__ */ jsxs7(Fragment2, { children: [
6175
- /* @__PURE__ */ jsx8(
8195
+ /* @__PURE__ */ jsxs9(Fragment4, { children: [
8196
+ /* @__PURE__ */ jsx10(
6176
8197
  TBtn,
6177
8198
  {
6178
8199
  active: isBold,
@@ -6182,7 +8203,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6182
8203
  children: "B"
6183
8204
  }
6184
8205
  ),
6185
- /* @__PURE__ */ jsx8(
8206
+ /* @__PURE__ */ jsx10(
6186
8207
  TBtn,
6187
8208
  {
6188
8209
  active: isItalic,
@@ -6192,7 +8213,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6192
8213
  children: "I"
6193
8214
  }
6194
8215
  ),
6195
- /* @__PURE__ */ jsx8(
8216
+ /* @__PURE__ */ jsx10(
6196
8217
  TBtn,
6197
8218
  {
6198
8219
  active: isUnderline,
@@ -6202,7 +8223,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6202
8223
  children: "U"
6203
8224
  }
6204
8225
  ),
6205
- /* @__PURE__ */ jsx8(
8226
+ /* @__PURE__ */ jsx10(
6206
8227
  TBtn,
6207
8228
  {
6208
8229
  active: isStrike,
@@ -6212,7 +8233,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6212
8233
  children: "S"
6213
8234
  }
6214
8235
  ),
6215
- /* @__PURE__ */ jsx8(
8236
+ /* @__PURE__ */ jsx10(
6216
8237
  TBtn,
6217
8238
  {
6218
8239
  active: isCode,
@@ -6222,7 +8243,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6222
8243
  children: "`\xB7`"
6223
8244
  }
6224
8245
  ),
6225
- /* @__PURE__ */ jsx8(
8246
+ /* @__PURE__ */ jsx10(
6226
8247
  TBtn,
6227
8248
  {
6228
8249
  active: isLink,
@@ -6232,8 +8253,8 @@ function SelectionToolbar({ view, state, selectionRect }) {
6232
8253
  children: "\u2197"
6233
8254
  }
6234
8255
  ),
6235
- /* @__PURE__ */ jsx8(Divider, {}),
6236
- /* @__PURE__ */ jsx8(BlockTypeDropdown, { view, state, label: blockLabel })
8256
+ /* @__PURE__ */ jsx10(Divider, {}),
8257
+ /* @__PURE__ */ jsx10(BlockTypeDropdown, { view, state, label: blockLabel })
6237
8258
  ] })
6238
8259
  )
6239
8260
  }
@@ -6244,7 +8265,7 @@ var BLOCK_ITEMS = [
6244
8265
  shortLabel: "\xB6",
6245
8266
  label: "Text",
6246
8267
  run: (v) => {
6247
- setBlockType(paragraph2)(v.state, v.dispatch);
8268
+ setBlockType2(paragraph3)(v.state, v.dispatch);
6248
8269
  v.focus();
6249
8270
  }
6250
8271
  },
@@ -6252,7 +8273,7 @@ var BLOCK_ITEMS = [
6252
8273
  shortLabel: "H1",
6253
8274
  label: "Heading 1",
6254
8275
  run: (v) => {
6255
- setBlockType(heading2, { level: 1 })(v.state, v.dispatch);
8276
+ setBlockType2(heading3, { level: 1 })(v.state, v.dispatch);
6256
8277
  v.focus();
6257
8278
  }
6258
8279
  },
@@ -6260,7 +8281,7 @@ var BLOCK_ITEMS = [
6260
8281
  shortLabel: "H2",
6261
8282
  label: "Heading 2",
6262
8283
  run: (v) => {
6263
- setBlockType(heading2, { level: 2 })(v.state, v.dispatch);
8284
+ setBlockType2(heading3, { level: 2 })(v.state, v.dispatch);
6264
8285
  v.focus();
6265
8286
  }
6266
8287
  },
@@ -6268,7 +8289,7 @@ var BLOCK_ITEMS = [
6268
8289
  shortLabel: "H3",
6269
8290
  label: "Heading 3",
6270
8291
  run: (v) => {
6271
- setBlockType(heading2, { level: 3 })(v.state, v.dispatch);
8292
+ setBlockType2(heading3, { level: 3 })(v.state, v.dispatch);
6272
8293
  v.focus();
6273
8294
  }
6274
8295
  },
@@ -6303,7 +8324,7 @@ var BLOCK_ITEMS = [
6303
8324
  shortLabel: "\u275D",
6304
8325
  label: "Quote",
6305
8326
  run: (v) => {
6306
- wrapIn(blockquote2)(v.state, v.dispatch);
8327
+ wrapIn(blockquote3)(v.state, v.dispatch);
6307
8328
  v.focus();
6308
8329
  }
6309
8330
  },
@@ -6323,7 +8344,7 @@ var BLOCK_ITEMS = [
6323
8344
  }
6324
8345
  ];
6325
8346
  function EmptyParaHandle({ view, cursorPos }) {
6326
- const [menuOpen, setMenuOpen] = useState4(false);
8347
+ const [menuOpen, setMenuOpen] = useState7(false);
6327
8348
  const coords = view.coordsAtPos(cursorPos);
6328
8349
  const lineH = Math.max(coords.bottom - coords.top, 18);
6329
8350
  const btnSize = 22;
@@ -6331,7 +8352,7 @@ function EmptyParaHandle({ view, cursorPos }) {
6331
8352
  const btnTop = coords.top + (lineH - btnSize) / 2;
6332
8353
  const menuLeft = Math.max(VPORT_MARGIN2, btnLeft);
6333
8354
  const menuTop = btnTop + btnSize + 4;
6334
- useEffect3(() => {
8355
+ useEffect4(() => {
6335
8356
  if (!menuOpen) return;
6336
8357
  const onDown = (e) => {
6337
8358
  if (!e.target.closest(`[${FLOAT_ATTR}]`)) {
@@ -6341,8 +8362,8 @@ function EmptyParaHandle({ view, cursorPos }) {
6341
8362
  document.addEventListener("mousedown", onDown, true);
6342
8363
  return () => document.removeEventListener("mousedown", onDown, true);
6343
8364
  }, [menuOpen]);
6344
- return /* @__PURE__ */ jsxs7(Fragment2, { children: [
6345
- /* @__PURE__ */ jsx8(
8365
+ return /* @__PURE__ */ jsxs9(Fragment4, { children: [
8366
+ /* @__PURE__ */ jsx10(
6346
8367
  "button",
6347
8368
  {
6348
8369
  ...{ [FLOAT_ATTR]: "" },
@@ -6374,7 +8395,7 @@ function EmptyParaHandle({ view, cursorPos }) {
6374
8395
  children: "+"
6375
8396
  }
6376
8397
  ),
6377
- menuOpen && /* @__PURE__ */ jsx8(
8398
+ menuOpen && /* @__PURE__ */ jsx10(
6378
8399
  "div",
6379
8400
  {
6380
8401
  ...{ [FLOAT_ATTR]: "" },
@@ -6390,7 +8411,7 @@ function EmptyParaHandle({ view, cursorPos }) {
6390
8411
  minWidth: 168,
6391
8412
  animation: "ab-float-in 0.12s ease"
6392
8413
  },
6393
- children: BLOCK_ITEMS.map((item) => /* @__PURE__ */ jsx8(
8414
+ children: BLOCK_ITEMS.map((item) => /* @__PURE__ */ jsx10(
6394
8415
  BlockMenuItem,
6395
8416
  {
6396
8417
  item,
@@ -6408,8 +8429,8 @@ function BlockMenuItem({
6408
8429
  view,
6409
8430
  onRun
6410
8431
  }) {
6411
- const [hover, setHover] = useState4(false);
6412
- return /* @__PURE__ */ jsxs7(
8432
+ const [hover, setHover] = useState7(false);
8433
+ return /* @__PURE__ */ jsxs9(
6413
8434
  "button",
6414
8435
  {
6415
8436
  ...{ [FLOAT_ATTR]: "" },
@@ -6435,7 +8456,7 @@ function BlockMenuItem({
6435
8456
  transition: "background 0.1s"
6436
8457
  },
6437
8458
  children: [
6438
- /* @__PURE__ */ jsx8(
8459
+ /* @__PURE__ */ jsx10(
6439
8460
  "span",
6440
8461
  {
6441
8462
  style: {
@@ -6456,11 +8477,11 @@ function BlockMenuItem({
6456
8477
  );
6457
8478
  }
6458
8479
  function FloatingMenu({ view, editorState }) {
6459
- const [showEmptyHandle, setShowEmptyHandle] = useState4(false);
6460
- const dwellTimerRef = useRef3(null);
6461
- const lastEmptyPosRef = useRef3(null);
6462
- const [, setScrollTick] = useState4(0);
6463
- useEffect3(() => {
8480
+ const [showEmptyHandle, setShowEmptyHandle] = useState7(false);
8481
+ const dwellTimerRef = useRef4(null);
8482
+ const lastEmptyPosRef = useRef4(null);
8483
+ const [, setScrollTick] = useState7(0);
8484
+ useEffect4(() => {
6464
8485
  const editorDom = view?.dom;
6465
8486
  if (!editorDom) return;
6466
8487
  const container = editorDom.closest(".editor-shell") ?? editorDom.parentElement;
@@ -6469,7 +8490,7 @@ function FloatingMenu({ view, editorState }) {
6469
8490
  container.addEventListener("scroll", onScroll, { passive: true });
6470
8491
  return () => container.removeEventListener("scroll", onScroll);
6471
8492
  }, [view]);
6472
- useEffect3(() => {
8493
+ useEffect4(() => {
6473
8494
  const id = "ab-float-keyframes";
6474
8495
  if (document.getElementById(id)) return;
6475
8496
  const style = document.createElement("style");
@@ -6499,8 +8520,8 @@ function FloatingMenu({ view, editorState }) {
6499
8520
  }
6500
8521
  const selectionRect = hasSelection ? getSelectionDOMRect() : null;
6501
8522
  return createPortal2(
6502
- /* @__PURE__ */ jsxs7(Fragment2, { children: [
6503
- hasSelection && selectionRect && /* @__PURE__ */ jsx8(
8523
+ /* @__PURE__ */ jsxs9(Fragment4, { children: [
8524
+ hasSelection && selectionRect && /* @__PURE__ */ jsx10(
6504
8525
  SelectionToolbar,
6505
8526
  {
6506
8527
  view,
@@ -6508,23 +8529,23 @@ function FloatingMenu({ view, editorState }) {
6508
8529
  selectionRect
6509
8530
  }
6510
8531
  ),
6511
- !hasSelection && showEmptyHandle && emptyPos !== null && /* @__PURE__ */ jsx8(EmptyParaHandle, { view, cursorPos: emptyPos })
8532
+ !hasSelection && showEmptyHandle && emptyPos !== null && /* @__PURE__ */ jsx10(EmptyParaHandle, { view, cursorPos: emptyPos })
6512
8533
  ] }),
6513
8534
  document.body
6514
8535
  );
6515
8536
  }
6516
8537
 
6517
8538
  // src/ui/plugin/inlineSuggestPlugin.ts
6518
- import { Plugin as Plugin7, PluginKey as PluginKey7 } from "prosemirror-state";
6519
- import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "prosemirror-view";
6520
- var inlineSuggestKey = new PluginKey7("inlineSuggest");
8539
+ import { Plugin as Plugin8, PluginKey as PluginKey8 } from "prosemirror-state";
8540
+ import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "prosemirror-view";
8541
+ var inlineSuggestKey = new PluginKey8("inlineSuggest");
6521
8542
  var DEBOUNCE_MS = 600;
6522
8543
  function createInlineSuggestPlugin(provider, endpoint, options) {
6523
8544
  const { onContextChange } = options ?? {};
6524
8545
  return {
6525
8546
  name: "inlineSuggest",
6526
8547
  plugins: () => [
6527
- new Plugin7({
8548
+ new Plugin8({
6528
8549
  key: inlineSuggestKey,
6529
8550
  state: {
6530
8551
  init: () => ({ suggestion: null, anchorPos: null }),
@@ -6540,13 +8561,13 @@ function createInlineSuggestPlugin(provider, endpoint, options) {
6540
8561
  props: {
6541
8562
  decorations(state) {
6542
8563
  const ps = inlineSuggestKey.getState(state);
6543
- if (!ps?.suggestion || !state.selection.empty) return DecorationSet3.empty;
8564
+ if (!ps?.suggestion || !state.selection.empty) return DecorationSet4.empty;
6544
8565
  const ghost = document.createElement("span");
6545
8566
  ghost.textContent = ps.suggestion;
6546
8567
  ghost.style.cssText = "color: rgba(0,0,0,0.3); pointer-events: none; user-select: none;";
6547
8568
  ghost.setAttribute("aria-hidden", "true");
6548
- return DecorationSet3.create(state.doc, [
6549
- Decoration3.widget(state.selection.from, ghost, {
8569
+ return DecorationSet4.create(state.doc, [
8570
+ Decoration4.widget(state.selection.from, ghost, {
6550
8571
  side: 1,
6551
8572
  key: "inline-suggest"
6552
8573
  })
@@ -6677,6 +8698,7 @@ function createInlineSuggestPlugin(provider, endpoint, options) {
6677
8698
  }
6678
8699
  export {
6679
8700
  ActionbookRenderer,
8701
+ DocumentTreeView,
6680
8702
  EditorShell,
6681
8703
  FloatingMenu,
6682
8704
  JUMP_POINT_ADJACENT_SPEC,
@@ -6687,19 +8709,25 @@ export {
6687
8709
  convertBlock2 as convertBlock,
6688
8710
  convertInline2 as convertInline,
6689
8711
  createDragHandlePlugin,
8712
+ createHistoryPlugin,
6690
8713
  createInlineSuggestPlugin,
6691
8714
  createInlineToolTagNodeViewPlugin,
6692
8715
  createInputRulesPlugin,
6693
8716
  createJinjaDecorationPlugin,
8717
+ createJinjaIfBlockPlugin,
6694
8718
  createJumpPointAdjacentPlugin,
6695
8719
  createJumpPointNodeViewPlugin,
8720
+ createJumpPointValidationPlugin,
6696
8721
  createKeymapPlugin,
6697
8722
  createLinkPlugin,
6698
8723
  createMarkdownClipboardPlugin,
8724
+ createNoteBlockPlugin,
6699
8725
  createPluginArray,
6700
8726
  createReactNodeView,
6701
8727
  createSlashCommandPlugin,
6702
8728
  createTodoNodeViewPlugin,
8729
+ hasBrokenAnchorRefs,
8730
+ hasDuplicateJumpPoints,
6703
8731
  inlineSuggestKey,
6704
8732
  slashCommandKey,
6705
8733
  toProseMirrorJSON,