@sendbird/actionbook-core 0.8.1 → 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
@@ -32,12 +32,9 @@ var actionbookSchema = new Schema({
32
32
  bulletList: {
33
33
  content: "listItem+",
34
34
  group: "block",
35
- parseDOM: [
36
- { tag: "div.ab-bullet-list" },
37
- { tag: "ul" }
38
- ],
35
+ parseDOM: [{ tag: "ul" }],
39
36
  toDOM() {
40
- return ["div", { class: "ab-bullet-list", role: "list" }, 0];
37
+ return ["ul", 0];
41
38
  }
42
39
  },
43
40
  orderedList: {
@@ -45,12 +42,6 @@ var actionbookSchema = new Schema({
45
42
  group: "block",
46
43
  attrs: { start: { default: 1 } },
47
44
  parseDOM: [
48
- {
49
- tag: "div.ab-ordered-list",
50
- getAttrs(dom) {
51
- return { start: Number(dom.dataset.start) || 1 };
52
- }
53
- },
54
45
  {
55
46
  tag: "ol",
56
47
  getAttrs(dom) {
@@ -59,25 +50,13 @@ var actionbookSchema = new Schema({
59
50
  }
60
51
  ],
61
52
  toDOM(node) {
62
- const attrs = { class: "ab-ordered-list", role: "list" };
63
- if (node.attrs.start !== 1) attrs["data-start"] = String(node.attrs.start);
64
- return ["div", attrs, 0];
53
+ return node.attrs.start === 1 ? ["ol", 0] : ["ol", { start: node.attrs.start }, 0];
65
54
  }
66
55
  },
67
56
  listItem: {
68
57
  content: "block+",
69
58
  attrs: { checked: { default: null } },
70
59
  parseDOM: [
71
- {
72
- tag: "div.ab-list-item",
73
- getAttrs(dom) {
74
- const el = dom;
75
- if (el.dataset.checked != null) {
76
- return { checked: el.dataset.checked === "true" };
77
- }
78
- return { checked: null };
79
- }
80
- },
81
60
  {
82
61
  tag: "li",
83
62
  getAttrs(dom) {
@@ -94,12 +73,10 @@ var actionbookSchema = new Schema({
94
73
  }
95
74
  ],
96
75
  toDOM(node) {
97
- const attrs = { class: "ab-list-item", role: "listitem" };
98
76
  if (node.attrs.checked != null) {
99
- attrs.class = "ab-list-item todo-item";
100
- attrs["data-checked"] = String(node.attrs.checked);
77
+ return ["li", { class: "todo-item", "data-checked": String(node.attrs.checked) }, 0];
101
78
  }
102
- return ["div", attrs, 0];
79
+ return ["li", 0];
103
80
  },
104
81
  defining: true
105
82
  },
@@ -119,22 +96,25 @@ var actionbookSchema = new Schema({
119
96
  defining: true,
120
97
  marks: "",
121
98
  attrs: { language: { default: null } },
122
- parseDOM: [
123
- {
124
- tag: "pre",
125
- preserveWhitespace: "full",
126
- getAttrs(dom) {
127
- const code3 = dom.querySelector("code");
128
- return { language: code3?.getAttribute("data-language") || null };
129
- }
99
+ parseDOM: [{
100
+ tag: "pre",
101
+ preserveWhitespace: "full",
102
+ getAttrs(dom) {
103
+ return { language: dom.getAttribute("data-language") || null };
130
104
  }
131
- ],
105
+ }],
132
106
  toDOM(node) {
133
- const attrs = {};
134
- if (node.attrs.language) attrs["data-language"] = node.attrs.language;
135
- return ["pre", ["code", attrs, 0]];
107
+ return ["pre", { "data-language": node.attrs.language || void 0 }, ["code", 0]];
108
+ }
109
+ },
110
+ horizontalRule: {
111
+ group: "block",
112
+ parseDOM: [{ tag: "hr" }],
113
+ toDOM() {
114
+ return ["hr"];
136
115
  }
137
116
  },
117
+ // ── Jinja conditional block ──
138
118
  jinjaIfBlock: {
139
119
  content: "jinjaIfBranch+",
140
120
  group: "block",
@@ -151,6 +131,7 @@ var actionbookSchema = new Schema({
151
131
  defining: true,
152
132
  attrs: {
153
133
  branchType: { default: "if" },
134
+ // 'if' | 'elif' | 'else'
154
135
  condition: { default: "" }
155
136
  },
156
137
  parseDOM: [
@@ -178,11 +159,14 @@ var actionbookSchema = new Schema({
178
159
  ];
179
160
  }
180
161
  },
181
- horizontalRule: {
162
+ noteBlock: {
163
+ content: "block+",
182
164
  group: "block",
183
- parseDOM: [{ tag: "hr" }],
165
+ defining: true,
166
+ isolating: true,
167
+ parseDOM: [{ tag: "div[data-note-block]" }],
184
168
  toDOM() {
185
- return ["hr"];
169
+ return ["div", { "data-note-block": "", class: "ab-note-block" }, 0];
186
170
  }
187
171
  },
188
172
  // ── Table nodes ──
@@ -578,6 +562,12 @@ function IconTimeDiff({ size = 16, fill = "currentColor", style }) {
578
562
  }
579
563
  );
580
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
+ }
581
571
  function IconAnchor({ size = 16, fill = "currentColor", style }) {
582
572
  return /* @__PURE__ */ jsxs(
583
573
  "svg",
@@ -816,21 +806,24 @@ var ReactNodeViewImpl = class {
816
806
  dom;
817
807
  root;
818
808
  component;
809
+ wrapper;
819
810
  _node;
820
811
  _view;
821
812
  _getPos;
822
813
  _selected = false;
823
814
  _decorations = [];
824
- constructor(node, view, getPos, options) {
815
+ constructor(node, view, getPos, decorations, options) {
825
816
  this._node = node;
826
817
  this._view = view;
827
818
  this._getPos = getPos;
819
+ this._decorations = decorations;
828
820
  this.component = options.component;
821
+ this.wrapper = options.wrapper;
829
822
  const tag = options.as ?? (options.inline !== false ? "span" : "div");
830
823
  this.dom = document.createElement(tag);
831
824
  if (options.inline !== false) {
832
- this.dom.style.display = "inline-flex";
833
- this.dom.style.verticalAlign = "middle";
825
+ this.dom.style.display = "inline";
826
+ this.dom.style.verticalAlign = "baseline";
834
827
  }
835
828
  if (options.className) {
836
829
  this.dom.className = options.className;
@@ -839,14 +832,15 @@ var ReactNodeViewImpl = class {
839
832
  this.render();
840
833
  }
841
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
+ });
842
842
  this.root.render(
843
- createElement(this.component, {
844
- node: this._node,
845
- view: this._view,
846
- getPos: this._getPos,
847
- selected: this._selected,
848
- decorations: this._decorations
849
- })
843
+ this.wrapper ? createElement(this.wrapper, null, element) : element
850
844
  );
851
845
  }
852
846
  update(node, decorations, _innerDecorations) {
@@ -875,7 +869,7 @@ var ReactNodeViewImpl = class {
875
869
  }
876
870
  };
877
871
  function createReactNodeView(options) {
878
- return (node, view, getPos) => new ReactNodeViewImpl(node, view, getPos, options);
872
+ return (node, view, getPos, decorations) => new ReactNodeViewImpl(node, view, getPos, decorations, options);
879
873
  }
880
874
 
881
875
  // src/ui/hooks/useEditorView.ts
@@ -884,9 +878,19 @@ import { EditorState } from "prosemirror-state";
884
878
  import { EditorView } from "prosemirror-view";
885
879
  import { Node as PMNode } from "prosemirror-model";
886
880
 
881
+ // src/ast/types.ts
882
+ var RESOURCE_TAG_TYPES = ["tool", "manual", "agent_message_template", "handoff", "time_diff", "time_difference"];
883
+
887
884
  // src/compat/prosemirror.ts
888
885
  var MAX_DEPTH = 128;
889
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
+ }
890
894
  function convertPMMark(mark) {
891
895
  switch (mark.type) {
892
896
  case "bold":
@@ -925,12 +929,17 @@ function convertPMInline(node, depth) {
925
929
  }
926
930
  case "inlineToolTag": {
927
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
+ }
928
937
  return [
929
938
  {
930
939
  type: "resourceTag",
931
- tagType: attrs.type ?? "tool",
940
+ tagType,
932
941
  resourceId: attrs.resourceId ?? "",
933
- text: attrs.text ?? ""
942
+ text: text2
934
943
  }
935
944
  ];
936
945
  }
@@ -961,20 +970,27 @@ function convertPMBlock(node, depth = 0) {
961
970
  const level = node.attrs?.level ?? 1;
962
971
  return [{ type: "heading", level, content: children.flatMap((c) => convertPMInline(c, depth + 1)) }];
963
972
  }
964
- case "bulletList":
973
+ case "bulletList": {
974
+ const items = children.map((c) => convertPMListItem(c, depth + 1));
975
+ const spread = isLooseList(items);
965
976
  return [
966
977
  {
967
978
  type: "bulletList",
968
- content: children.map((c) => convertPMListItem(c, depth + 1))
979
+ ...spread ? { spread: true } : {},
980
+ content: items.map((li) => spread ? { ...li, spread: true } : li)
969
981
  }
970
982
  ];
983
+ }
971
984
  case "orderedList": {
972
985
  const start = node.attrs?.start ?? 1;
986
+ const items = children.map((c) => convertPMListItem(c, depth + 1));
987
+ const spread = isLooseList(items);
973
988
  return [
974
989
  {
975
990
  type: "orderedList",
976
991
  start,
977
- content: children.map((c) => convertPMListItem(c, depth + 1))
992
+ ...spread ? { spread: true } : {},
993
+ content: items.map((li) => spread ? { ...li, spread: true } : li)
978
994
  }
979
995
  ];
980
996
  }
@@ -985,6 +1001,12 @@ function convertPMBlock(node, depth = 0) {
985
1001
  return [{ type: "blockquote", content: children.flatMap((c) => convertPMBlock(c, depth + 1)) }];
986
1002
  case "horizontalRule":
987
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)) }];
988
1010
  case "table": {
989
1011
  const rows = children.map((rowNode) => {
990
1012
  const cells = (rowNode.content ?? []).map((cellNode) => {
@@ -1001,6 +1023,22 @@ function convertPMBlock(node, depth = 0) {
1001
1023
  });
1002
1024
  return [{ type: "table", content: rows }];
1003
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));
1004
1042
  default:
1005
1043
  if (children.length > 0) {
1006
1044
  return children.flatMap((c) => convertPMBlock(c, depth + 1));
@@ -1012,11 +1050,11 @@ function convertPMListItem(node, depth = 0) {
1012
1050
  if (depth > MAX_DEPTH) return { type: "listItem", content: [{ type: "paragraph", content: [] }] };
1013
1051
  const children = node.content ?? [];
1014
1052
  const content = children.flatMap((c) => convertPMBlock(c, depth + 1));
1015
- const checked = node.attrs?.checked;
1016
1053
  const base = {
1017
1054
  type: "listItem",
1018
1055
  content: content.length > 0 ? content : [{ type: "paragraph", content: [] }]
1019
1056
  };
1057
+ const checked = node.attrs?.checked;
1020
1058
  if (typeof checked === "boolean") {
1021
1059
  base.checked = checked;
1022
1060
  }
@@ -1580,7 +1618,7 @@ function listItemWithTaskListItem(node, parent, state, info) {
1580
1618
 
1581
1619
  // src/markdown/plugins/resourceTag.ts
1582
1620
  var RESOURCE_TAG_RE = /\{\{([^:}]+):([^:}]*):([^}]+)\}\}/g;
1583
- 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"]);
1584
1622
  function splitTextWithResourceTags(text2) {
1585
1623
  const results = [];
1586
1624
  let lastIndex = 0;
@@ -1743,10 +1781,11 @@ function convertBlock(node, depth = 0) {
1743
1781
  case "list": {
1744
1782
  const list = node;
1745
1783
  const items = list.children.map((li) => convertListItem(li, depth + 1));
1784
+ const spread = list.spread ?? false;
1746
1785
  if (list.ordered) {
1747
- return [{ type: "orderedList", start: list.start ?? 1, content: items }];
1786
+ return [{ type: "orderedList", start: list.start ?? 1, spread, content: items }];
1748
1787
  }
1749
- return [{ type: "bulletList", content: items }];
1788
+ return [{ type: "bulletList", spread, content: items }];
1750
1789
  }
1751
1790
  case "blockquote": {
1752
1791
  const bq = node;
@@ -1755,6 +1794,10 @@ function convertBlock(node, depth = 0) {
1755
1794
  }
1756
1795
  case "thematicBreak":
1757
1796
  return [{ type: "horizontalRule" }];
1797
+ case "code": {
1798
+ const codeNode = node;
1799
+ return [{ type: "codeBlock", content: codeNode.value, ...codeNode.lang ? { language: codeNode.lang } : {} }];
1800
+ }
1758
1801
  case "jinjaIfBlockMdast": {
1759
1802
  const jNode = node;
1760
1803
  const branches = jNode.branches.map((b) => ({
@@ -1795,7 +1838,11 @@ function convertBlock(node, depth = 0) {
1795
1838
  function convertListItem(node, depth = 0) {
1796
1839
  if (depth > MAX_DEPTH2) throw new DepthError(depth);
1797
1840
  const content = node.children.flatMap((child) => convertBlock(child, depth + 1));
1798
- 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
+ };
1799
1846
  if (typeof node.checked === "boolean") {
1800
1847
  return { ...base, checked: node.checked };
1801
1848
  }
@@ -1894,31 +1941,39 @@ function blockToMdast(node, depth = 0) {
1894
1941
  return [{ type: "paragraph", children: node.content.flatMap((c) => inlineToMdast(c, depth + 1)) }];
1895
1942
  case "heading":
1896
1943
  return [{ type: "heading", depth: node.level, children: node.content.flatMap((c) => inlineToMdast(c, depth + 1)) }];
1897
- case "bulletList":
1944
+ case "bulletList": {
1945
+ const items = node.content.map((li) => listItemToMdast(li, depth + 1));
1898
1946
  return [
1899
1947
  {
1900
1948
  type: "list",
1901
1949
  ordered: false,
1902
- spread: false,
1903
- children: node.content.map((li) => listItemToMdast(li, depth + 1))
1950
+ spread: node.spread ?? false,
1951
+ children: items
1904
1952
  }
1905
1953
  ];
1906
- case "orderedList":
1954
+ }
1955
+ case "orderedList": {
1956
+ const items = node.content.map((li) => listItemToMdast(li, depth + 1));
1907
1957
  return [
1908
1958
  {
1909
1959
  type: "list",
1910
1960
  ordered: true,
1911
1961
  start: node.start,
1912
- spread: false,
1913
- children: node.content.map((li) => listItemToMdast(li, depth + 1))
1962
+ spread: node.spread ?? false,
1963
+ children: items
1914
1964
  }
1915
1965
  ];
1966
+ }
1916
1967
  case "listItem":
1917
1968
  return listItemToMdast(node, depth).children.flatMap((child) => [child]);
1918
1969
  case "blockquote":
1919
1970
  return [{ type: "blockquote", children: node.content.flatMap((c) => blockToMdast(c, depth + 1)) }];
1920
1971
  case "horizontalRule":
1921
1972
  return [{ type: "thematicBreak" }];
1973
+ case "codeBlock": {
1974
+ const cb = node;
1975
+ return [{ type: "code", value: cb.content, ...cb.language ? { lang: cb.language } : {} }];
1976
+ }
1922
1977
  case "jinjaIfBlock": {
1923
1978
  const jNode = node;
1924
1979
  const result = [];
@@ -1942,14 +1997,18 @@ function blockToMdast(node, depth = 0) {
1942
1997
  }));
1943
1998
  return [{ type: "table", children: rows }];
1944
1999
  }
2000
+ case "noteBlock":
2001
+ return [];
1945
2002
  }
1946
2003
  }
1947
2004
  function listItemToMdast(node, depth = 0) {
1948
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;
1949
2008
  const result = {
1950
2009
  type: "listItem",
1951
- spread: false,
1952
- children: node.content.flatMap((child) => blockToMdast(child, depth + 1))
2010
+ spread: isSpread,
2011
+ children
1953
2012
  };
1954
2013
  if (typeof node.checked === "boolean") {
1955
2014
  result.checked = node.checked;
@@ -1982,12 +2041,20 @@ function textHandler(node, parent, state, info) {
1982
2041
  state.unsafe = originalUnsafe;
1983
2042
  return result;
1984
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
+ }
1985
2051
  function serializeToMarkdown(doc2) {
1986
2052
  const mdastTree = toMdast(doc2);
1987
- const result = toMarkdown(mdastTree, {
2053
+ const raw = toMarkdown(mdastTree, {
1988
2054
  bullet: "-",
1989
2055
  rule: "-",
1990
2056
  listItemIndent: "one",
2057
+ incrementListMarker: true,
1991
2058
  emphasis: "*",
1992
2059
  strong: "*",
1993
2060
  resourceLink: true,
@@ -1999,11 +2066,13 @@ function serializeToMarkdown(doc2) {
1999
2066
  ],
2000
2067
  handlers: {
2001
2068
  text: textHandler,
2069
+ link: linkHandler,
2002
2070
  ...resourceTagToMarkdown().handlers,
2003
2071
  ...jumpPointToMarkdown().handlers
2004
2072
  },
2005
2073
  extensions: [gfmNoAutolinkToMarkdown()]
2006
2074
  });
2075
+ let result = raw.replace(/^(\s*)\\>/gm, "$1>").replace(/(\S) *>/g, (match, pre) => `${pre} >`).replace(/\\\[([^\]]*)\]/g, "\\[$1\\]").replace(/ /g, " ");
2007
2076
  return result;
2008
2077
  }
2009
2078
 
@@ -2087,20 +2156,23 @@ function convertInline2(node) {
2087
2156
  }
2088
2157
  function convertBlockToArray(node) {
2089
2158
  if (node.type === "jinjaIfBlock") {
2090
- const jNode = node;
2091
- const result = [];
2092
- for (const branch of jNode.branches) {
2093
- const tagText = branch.branchType === "else" ? "{% else %}" : `{% ${branch.branchType} ${branch.condition ?? ""} %}`.trim();
2094
- result.push({ type: "paragraph", content: [{ type: "text", text: tagText }] });
2095
- for (const child of branch.content) {
2096
- result.push(...convertBlockToArray(child));
2097
- }
2098
- }
2099
- result.push({ type: "paragraph", content: [{ type: "text", text: "{% endif %}" }] });
2100
- return result;
2159
+ return [convertJinjaIfBlock(node)];
2101
2160
  }
2102
2161
  return [convertBlock2(node)];
2103
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
+ };
2175
+ }
2104
2176
  function convertBlock2(node) {
2105
2177
  switch (node.type) {
2106
2178
  case "paragraph":
@@ -2142,6 +2214,19 @@ function convertBlock2(node) {
2142
2214
  };
2143
2215
  case "horizontalRule":
2144
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
+ };
2145
2230
  case "table":
2146
2231
  return {
2147
2232
  type: "table",
@@ -2153,19 +2238,8 @@ function convertBlock2(node) {
2153
2238
  }))
2154
2239
  }))
2155
2240
  };
2156
- case "jinjaIfBlock": {
2157
- const jNode = node;
2158
- const result = [];
2159
- for (const branch of jNode.branches) {
2160
- const tagText = branch.branchType === "else" ? "{% else %}" : `{% ${branch.branchType} ${branch.condition ?? ""} %}`.trim();
2161
- result.push({ type: "paragraph", content: [{ type: "text", text: tagText }] });
2162
- for (const child of branch.content) {
2163
- result.push(...convertBlockToArray(child));
2164
- }
2165
- }
2166
- result.push({ type: "paragraph", content: [{ type: "text", text: "{% endif %}" }] });
2167
- return result[0] ?? { type: "paragraph", content: [] };
2168
- }
2241
+ case "jinjaIfBlock":
2242
+ return convertJinjaIfBlock(node);
2169
2243
  default:
2170
2244
  return assertNever(node);
2171
2245
  }
@@ -2197,16 +2271,30 @@ function pmDocToAST(pmDoc) {
2197
2271
  return fromProseMirrorJSON(json);
2198
2272
  }
2199
2273
  function useEditorView(config) {
2200
- const viewRef = useRef(null);
2201
2274
  const viewInstanceRef = useRef(null);
2275
+ const containerRef = useRef(null);
2202
2276
  const [, forceUpdate] = useState(0);
2203
2277
  const configRef = useRef(config);
2204
2278
  configRef.current = config;
2205
2279
  useEffect(() => {
2206
- const container = viewRef.current;
2207
- if (!container) return;
2208
- const { pmPlugins, nodeViews } = createPluginArray(config.plugins ?? []);
2209
- 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);
2210
2298
  const view = new EditorView(container, {
2211
2299
  state,
2212
2300
  nodeViews,
@@ -2223,10 +2311,6 @@ function useEditorView(config) {
2223
2311
  });
2224
2312
  viewInstanceRef.current = view;
2225
2313
  forceUpdate((n) => n + 1);
2226
- return () => {
2227
- view.destroy();
2228
- viewInstanceRef.current = null;
2229
- };
2230
2314
  }, []);
2231
2315
  const getAST = useCallback(() => {
2232
2316
  const view = viewInstanceRef.current;
@@ -2244,6 +2328,7 @@ function useEditorView(config) {
2244
2328
  const pmJSON = toProseMirrorJSON(doc2);
2245
2329
  const pmDoc = PMNode.fromJSON(actionbookSchema, pmJSON);
2246
2330
  const tr = view.state.tr.replaceWith(0, view.state.doc.content.size, pmDoc.content);
2331
+ tr.setMeta("addToHistory", false);
2247
2332
  view.dispatch(tr);
2248
2333
  }, []);
2249
2334
  const focus = useCallback(() => {
@@ -2253,9 +2338,11 @@ function useEditorView(config) {
2253
2338
  viewInstanceRef.current?.destroy();
2254
2339
  viewInstanceRef.current = null;
2255
2340
  }, []);
2341
+ const getView = useCallback(() => viewInstanceRef.current, []);
2256
2342
  return {
2257
2343
  viewRef,
2258
2344
  view: viewInstanceRef.current,
2345
+ getView,
2259
2346
  getAST,
2260
2347
  getMarkdown,
2261
2348
  setContent,
@@ -2264,20 +2351,16 @@ function useEditorView(config) {
2264
2351
  };
2265
2352
  }
2266
2353
 
2267
- // src/ui/plugin/historyPlugin.ts
2268
- import { history } from "prosemirror-history";
2269
- function createHistoryPlugin() {
2270
- return {
2271
- name: "history",
2272
- plugins: () => [history()]
2273
- };
2274
- }
2275
-
2276
2354
  // src/ui/plugin/inputRulesPlugin.ts
2277
- 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,}$/;
2278
2361
  var BULLET_LIST_RE = /^\s*([-*])\s$/;
2279
2362
  var ORDERED_LIST_RE = /^(\d+)\.\s$/;
2280
- var JUMP_POINT_RE2 = /\^([A-Za-z_][A-Za-z0-9_]*)\^$/;
2363
+ var JUMP_POINT_RE2 = /\^([A-Za-z_]+)\^$/;
2281
2364
  var BOLD_STAR_RE = /(?:^|[^*])\*\*([^*]+)\*\*$/;
2282
2365
  var BOLD_UNDER_RE = /(?:^|[^_])__([^_]+)__$/;
2283
2366
  var ITALIC_STAR_RE = /(?:^|[^*])\*([^*]+)\*$/;
@@ -2288,11 +2371,14 @@ function markInputRule(pattern, markType, markerLen) {
2288
2371
  return new InputRule(pattern, (state, match, start, end) => {
2289
2372
  const textContent2 = match[1];
2290
2373
  const fullMatch = match[0];
2291
- 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;
2292
2377
  const tr = state.tr;
2293
- tr.delete(actualStart, end);
2378
+ tr.delete(deleteFrom, end);
2294
2379
  const textNode = state.schema.text(textContent2, [markType.create()]);
2295
- tr.insert(actualStart, textNode);
2380
+ tr.insert(deleteFrom, textNode);
2381
+ tr.removeStoredMark(markType);
2296
2382
  return tr;
2297
2383
  });
2298
2384
  }
@@ -2301,12 +2387,41 @@ function createInputRulesPlugin() {
2301
2387
  name: "inputRules",
2302
2388
  inputRules: () => {
2303
2389
  const rules = [];
2304
- rules.push(wrappingInputRule(BULLET_LIST_RE, actionbookSchema.nodes.bulletList));
2305
2390
  rules.push(
2306
- wrappingInputRule(ORDERED_LIST_RE, actionbookSchema.nodes.orderedList, (match) => ({
2307
- start: +match[1]
2391
+ textblockTypeInputRule(HEADING_RE, actionbookSchema.nodes.heading, (match) => ({
2392
+ level: match[1].length
2308
2393
  }))
2309
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
+ );
2310
2425
  rules.push(
2311
2426
  new InputRule(/^\s*[-*]\s\[([ xX])\]\s$/, (state, match, start, end) => {
2312
2427
  const checked = match[1] !== " ";
@@ -2316,6 +2431,12 @@ function createInputRulesPlugin() {
2316
2431
  return state.tr.delete(start, end).insert(start, list);
2317
2432
  })
2318
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
+ );
2319
2440
  const jumpPointType = actionbookSchema.nodes.jumpPoint;
2320
2441
  rules.push(
2321
2442
  new InputRule(JUMP_POINT_RE2, (state, match, start, end) => {
@@ -2342,24 +2463,85 @@ function createInputRulesPlugin() {
2342
2463
  };
2343
2464
  }
2344
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
+
2345
2479
  // src/ui/plugin/keymapPlugin.ts
2346
- 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";
2347
2482
  import { liftListItem, sinkListItem, splitListItem, wrapInList } from "prosemirror-schema-list";
2348
2483
  var TAB_CHAR = " ";
2349
- var { listItem, bulletList, orderedList, hardBreak } = actionbookSchema.nodes;
2484
+ var { listItem, bulletList, orderedList, hardBreak, heading, paragraph, horizontalRule, blockquote } = actionbookSchema.nodes;
2350
2485
  var { bold: boldMark, italic: italicMark, underline: underlineMark, strikethrough: strikethroughMark, code: codeMark } = actionbookSchema.marks;
2351
2486
  function cursorDirectlyInListItem(state) {
2352
2487
  const { $from } = state.selection;
2353
2488
  return $from.depth >= 2 && $from.node($from.depth - 1).type === listItem;
2354
2489
  }
2355
- var backspaceCommand = (state, dispatch) => {
2490
+ var backspaceAfterList = (state, dispatch) => {
2491
+ const { $from } = state.selection;
2492
+ if (!state.selection.empty) return false;
2493
+ if ($from.parentOffset !== 0) return false;
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) => {
2356
2512
  const { $from } = state.selection;
2357
2513
  if (!state.selection.empty) return false;
2358
2514
  if ($from.parentOffset !== 0) return false;
2359
- if (!cursorDirectlyInListItem(state)) return false;
2360
- if ($from.index($from.depth - 1) !== 0) return false;
2361
- return liftListItem(listItem)(state, dispatch);
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;
2362
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
+ );
2363
2545
  var tabCommand = (state, dispatch) => {
2364
2546
  const { $from } = state.selection;
2365
2547
  if (cursorDirectlyInListItem(state) && $from.parentOffset === 0) {
@@ -2412,12 +2594,13 @@ function createKeymapPlugin() {
2412
2594
  "Mod-e": toggleMark(codeMark),
2413
2595
  "Mod-Shift-7": wrapInList(bulletList),
2414
2596
  "Mod-Shift-8": wrapInList(orderedList)
2415
- })
2597
+ }),
2598
+ plugins: () => [keymap3(baseKeymap)]
2416
2599
  };
2417
2600
  }
2418
2601
 
2419
2602
  // src/ui/plugin/markdownClipboard.ts
2420
- import { Fragment, Slice } from "prosemirror-model";
2603
+ import { DOMParser as ProseMirrorDOMParser, Fragment, Slice } from "prosemirror-model";
2421
2604
  import { Plugin, PluginKey } from "prosemirror-state";
2422
2605
 
2423
2606
  // src/ast/traverse.ts
@@ -2433,6 +2616,7 @@ function getChildren(node) {
2433
2616
  case "doc":
2434
2617
  case "blockquote":
2435
2618
  case "listItem":
2619
+ case "noteBlock":
2436
2620
  return node.content;
2437
2621
  case "paragraph":
2438
2622
  case "heading":
@@ -3904,6 +4088,23 @@ var gfmNoAutolinkFromMarkdown = () => [
3904
4088
  gfmTaskListItemFromMarkdown()
3905
4089
  ];
3906
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
+ }
3907
4108
  function combinedTokensFromMarkdown(node, depth = 0) {
3908
4109
  if (depth > MAX_DEPTH5) return;
3909
4110
  if (!node.children) return;
@@ -3915,13 +4116,20 @@ function combinedTokensFromMarkdown(node, depth = 0) {
3915
4116
  for (const child of node.children) {
3916
4117
  const c = child;
3917
4118
  if (c.type === "text") {
3918
- const afterResourceTags = splitTextWithResourceTags(child.value);
3919
- for (const part of afterResourceTags) {
3920
- if (part.type === "text") {
3921
- const afterJumpPoints = splitTextWithJumpPoints(part.value);
3922
- newChildren.push(...afterJumpPoints);
3923
- } else {
3924
- newChildren.push(part);
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
+ }
3925
4133
  }
3926
4134
  }
3927
4135
  } else {
@@ -4128,53 +4336,455 @@ function analyzeJinjaBlocks(doc2) {
4128
4336
  });
4129
4337
  }
4130
4338
 
4131
- // src/ui/plugin/markdownClipboard.ts
4132
- var key = new PluginKey("markdownClipboard");
4133
- function textToSlice(text2, view) {
4134
- try {
4135
- const existingIds = /* @__PURE__ */ new Set();
4136
- view.state.doc.descendants((node) => {
4137
- if (node.type.name === "jumpPoint") existingIds.add(node.attrs.id);
4138
- });
4139
- const deduped = text2.replace(/\^([A-Za-z_][A-Za-z0-9_]*)\^/gm, (match, id) => {
4140
- if (existingIds.has(id)) return id;
4141
- existingIds.add(id);
4142
- return match;
4143
- });
4144
- const astNodes = parseFragment(deduped);
4145
- const pmJSONNodes = astNodesToJSONContent(astNodes);
4146
- const pmNodes = pmJSONNodes.map((json) => actionbookSchema.nodeFromJSON(json));
4147
- const fragment = Fragment.fromArray(pmNodes);
4148
- const openDepth = pmJSONNodes.length === 1 && pmJSONNodes[0].type === "paragraph" ? 1 : 0;
4149
- return new Slice(fragment, openDepth, openDepth);
4150
- } catch {
4151
- return null;
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;
4152
4390
  }
4391
+ const previous = tokens[tokens.length - 1];
4392
+ return NEGATIVE_NUMBER_PRECEDERS.has(previous.type);
4153
4393
  }
4154
- function createPlugin() {
4155
- return new Plugin({
4156
- key,
4157
- props: {
4158
- handlePaste(view, event) {
4159
- const text2 = event.clipboardData?.getData("text/plain");
4160
- if (!text2) return false;
4161
- const slice = textToSlice(text2, view);
4162
- if (!slice) return false;
4163
- view.dispatch(view.state.tr.replaceSelection(slice));
4164
- return true;
4165
- },
4166
- clipboardTextParser(text2, _$context, _plain, view) {
4167
- if (!text2) return Slice.empty;
4168
- const slice = textToSlice(text2, view);
4169
- if (!slice) {
4170
- const textNode = actionbookSchema.text(text2);
4171
- return new Slice(Fragment.from(textNode), 0, 0);
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]
4414
+ });
4415
+ }
4416
+ return highlighted;
4417
+ }
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;
4435
+ }
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;
4445
+ }
4446
+ i++;
4447
+ }
4448
+ if (rawTokens[rawTokens.length - 1]?.start !== start) {
4449
+ return finalizeTokens(input, rawTokens);
4450
+ }
4451
+ continue;
4452
+ }
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);
4172
4777
  }
4173
4778
  return slice;
4174
4779
  },
4175
- // Discard pasted HTML always prefer plain-text markdown
4176
- transformPastedHTML() {
4177
- return "";
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);
4178
4788
  },
4179
4789
  clipboardTextSerializer(slice) {
4180
4790
  try {
@@ -4197,7 +4807,7 @@ function createMarkdownClipboardPlugin() {
4197
4807
  }
4198
4808
 
4199
4809
  // src/ui/plugin/jumpPointPlugin.ts
4200
- import { Plugin as Plugin2, PluginKey as PluginKey2, TextSelection } from "prosemirror-state";
4810
+ import { Plugin as Plugin2, PluginKey as PluginKey2, TextSelection as TextSelection2 } from "prosemirror-state";
4201
4811
  import { Decoration, DecorationSet } from "prosemirror-view";
4202
4812
  var adjacentKey = new PluginKey2("jumpPointAdjacent");
4203
4813
  var JUMP_POINT_ADJACENT_SPEC = { jumpPointAdjacent: true };
@@ -4268,8 +4878,15 @@ function createJumpPointEditPlugin() {
4268
4878
  }
4269
4879
  });
4270
4880
  }
4271
- function jumpPointBefore(state) {
4881
+ function jumpPointAtOrBefore(state) {
4272
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
+ }
4273
4890
  if (!selection.empty) return null;
4274
4891
  const { $from } = selection;
4275
4892
  const nodeBefore = $from.nodeBefore;
@@ -4279,23 +4896,23 @@ function jumpPointBefore(state) {
4279
4896
  return { id: nodeBefore.attrs.id, nodeStart, nodeEnd };
4280
4897
  }
4281
4898
  function explodeOnBackspace(state) {
4282
- const info = jumpPointBefore(state);
4899
+ const info = jumpPointAtOrBefore(state);
4283
4900
  if (!info) return null;
4284
4901
  const { id, nodeStart, nodeEnd } = info;
4285
4902
  const rawText = `^${id}`;
4286
4903
  const tr = state.tr.replaceWith(nodeStart, nodeEnd, state.schema.text(rawText));
4287
4904
  const cursorPos = Math.min(nodeStart + rawText.length, tr.doc.content.size);
4288
- tr.setSelection(TextSelection.near(tr.doc.resolve(cursorPos)));
4905
+ tr.setSelection(TextSelection2.near(tr.doc.resolve(cursorPos)));
4289
4906
  return tr;
4290
4907
  }
4291
4908
  function explodeOnArrowLeft(state) {
4292
- const info = jumpPointBefore(state);
4909
+ const info = jumpPointAtOrBefore(state);
4293
4910
  if (!info) return null;
4294
4911
  const { id, nodeStart, nodeEnd } = info;
4295
4912
  const rawText = `^${id}^`;
4296
4913
  const tr = state.tr.replaceWith(nodeStart, nodeEnd, state.schema.text(rawText));
4297
4914
  const cursorPos = Math.min(nodeStart + 1 + id.length, tr.doc.content.size);
4298
- tr.setSelection(TextSelection.near(tr.doc.resolve(cursorPos)));
4915
+ tr.setSelection(TextSelection2.near(tr.doc.resolve(cursorPos)));
4299
4916
  tr.setMeta(jumpPointEditKey, { from: nodeStart, to: nodeStart + rawText.length });
4300
4917
  return tr;
4301
4918
  }
@@ -4342,6 +4959,7 @@ function createJumpPointAdjacentPlugin() {
4342
4959
  }
4343
4960
 
4344
4961
  // src/ui/plugin/jumpPointNodeViewPlugin.tsx
4962
+ import { useCallback as useCallback2, useState as useState2 } from "react";
4345
4963
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
4346
4964
  var JUMP_POINT_STYLE = {
4347
4965
  display: "inline-flex",
@@ -4349,31 +4967,118 @@ var JUMP_POINT_STYLE = {
4349
4967
  backgroundColor: "#FFF2B6",
4350
4968
  border: "1px solid #FFC233",
4351
4969
  color: "#AA5D04",
4352
- borderRadius: "4px",
4353
- padding: "0 0.3em",
4354
- fontSize: "0.85em",
4355
- lineHeight: 1.6,
4970
+ borderRadius: "2px",
4971
+ padding: "2px",
4972
+ fontSize: "12px",
4973
+ lineHeight: "16px",
4356
4974
  userSelect: "none",
4357
4975
  cursor: "default",
4358
- boxSizing: "border-box"
4976
+ boxSizing: "border-box",
4977
+ height: "20px",
4978
+ overflow: "hidden"
4359
4979
  };
4360
4980
  var JUMP_POINT_ADJACENT_STYLE = {
4361
4981
  ...JUMP_POINT_STYLE,
4362
4982
  backgroundColor: "#FFE680",
4363
4983
  borderColor: "#FF9500"
4364
4984
  };
4365
- function JumpPointNodeViewComponent({ node, decorations }) {
4366
- const id = node.attrs.id;
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 }) {
5029
+ const id = node.attrs.id;
4367
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
+ }
4368
5073
  if (isAdjacent) {
4369
5074
  return /* @__PURE__ */ jsxs3("span", { style: JUMP_POINT_ADJACENT_STYLE, id: `jp-${id}`, children: [
4370
- /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { marginRight: "max(0.15em, 2px)", flexShrink: 0 } }),
4371
- `^${id}^`
5075
+ /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { paddingLeft: "2px", flexShrink: 0 } }),
5076
+ /* @__PURE__ */ jsx4("span", { style: LABEL_STYLE, children: `^${id}^` })
4372
5077
  ] });
4373
5078
  }
4374
5079
  return /* @__PURE__ */ jsxs3("span", { style: JUMP_POINT_STYLE, id: `jp-${id}`, children: [
4375
- /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { marginRight: "max(0.15em, 2px)", flexShrink: 0 } }),
4376
- id
5080
+ /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { paddingLeft: "2px", flexShrink: 0 } }),
5081
+ /* @__PURE__ */ jsx4("span", { style: LABEL_STYLE, children: id })
4377
5082
  ] });
4378
5083
  }
4379
5084
  function createJumpPointNodeViewPlugin() {
@@ -4388,108 +5093,100 @@ function createJumpPointNodeViewPlugin() {
4388
5093
  // src/ui/plugin/jumpPointValidationPlugin.ts
4389
5094
  import { Plugin as Plugin3, PluginKey as PluginKey3 } from "prosemirror-state";
4390
5095
  import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
4391
- var validationKey = new PluginKey3("jumpPointValidation");
4392
- function buildValidationDecorations(doc2) {
4393
- const jumpPointIds = /* @__PURE__ */ new Map();
4394
- const anchorRefs = [];
4395
- doc2.descendants((node, pos) => {
5096
+ var pluginKey = new PluginKey3("jumpPointValidation");
5097
+ function collectJumpPointIds(state) {
5098
+ const ids = /* @__PURE__ */ new Set();
5099
+ state.doc.descendants((node) => {
4396
5100
  if (node.type.name === "jumpPoint") {
4397
- const id = node.attrs.id;
4398
- if (!jumpPointIds.has(id)) jumpPointIds.set(id, []);
4399
- jumpPointIds.get(id).push(pos);
4400
- }
4401
- if (node.isText && node.marks) {
4402
- for (const mark of node.marks) {
4403
- if (mark.type.name === "link") {
4404
- const href = mark.attrs.href;
4405
- if (href?.startsWith("#")) {
4406
- anchorRefs.push({ from: pos, to: pos + node.nodeSize, id: href.slice(1) });
4407
- }
4408
- }
4409
- }
5101
+ ids.add(node.attrs.id);
4410
5102
  }
4411
5103
  });
4412
- const decorations = [];
4413
- for (const [, positions] of jumpPointIds) {
4414
- if (positions.length > 1) {
4415
- for (const pos of positions) {
4416
- decorations.push(
4417
- Decoration2.node(pos, pos + 1, { class: "duplicate-jump-point" })
4418
- );
4419
- }
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 });
4420
5114
  }
4421
- }
4422
- for (const ref of anchorRefs) {
4423
- if (!jumpPointIds.has(ref.id)) {
4424
- decorations.push(
4425
- Decoration2.inline(ref.from, ref.to, { class: "broken-anchor-ref" })
4426
- );
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 });
4427
5131
  }
4428
- }
4429
- return decorations.length > 0 ? DecorationSet2.create(doc2, decorations) : DecorationSet2.empty;
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;
4430
5141
  }
4431
5142
  function createJumpPointValidationPlugin() {
4432
5143
  return {
4433
5144
  name: "jumpPointValidation",
4434
5145
  plugins: () => [
4435
5146
  new Plugin3({
4436
- key: validationKey,
5147
+ key: pluginKey,
4437
5148
  state: {
4438
5149
  init(_, state) {
4439
- return buildValidationDecorations(state.doc);
5150
+ return buildDecorations2(state);
4440
5151
  },
4441
- apply(tr, oldDecos) {
4442
- if (!tr.docChanged) return oldDecos;
4443
- return buildValidationDecorations(tr.doc);
5152
+ apply(tr, oldSet, _oldState, newState) {
5153
+ if (tr.docChanged) {
5154
+ return buildDecorations2(newState);
5155
+ }
5156
+ return oldSet.map(tr.mapping, tr.doc);
4444
5157
  }
4445
5158
  },
4446
5159
  props: {
4447
5160
  decorations(state) {
4448
- return validationKey.getState(state);
5161
+ return pluginKey.getState(state) ?? DecorationSet2.empty;
4449
5162
  }
4450
5163
  }
4451
5164
  })
4452
5165
  ]
4453
5166
  };
4454
5167
  }
4455
- function hasDuplicateJumpPoints(state) {
4456
- const ids = /* @__PURE__ */ new Set();
4457
- let hasDup = false;
4458
- state.doc.descendants((node) => {
4459
- if (node.type.name === "jumpPoint") {
4460
- const id = node.attrs.id;
4461
- if (ids.has(id)) {
4462
- hasDup = true;
4463
- return false;
4464
- }
4465
- ids.add(id);
4466
- }
4467
- });
4468
- return hasDup;
4469
- }
4470
- function hasBrokenAnchorRefs(state) {
4471
- const jumpPointIds = /* @__PURE__ */ new Set();
4472
- state.doc.descendants((node) => {
4473
- if (node.type.name === "jumpPoint") {
4474
- jumpPointIds.add(node.attrs.id);
4475
- }
4476
- });
4477
- let hasBroken = false;
4478
- state.doc.descendants((node) => {
4479
- if (hasBroken) return false;
4480
- if (node.isText && node.marks) {
4481
- for (const mark of node.marks) {
4482
- if (mark.type.name === "link") {
4483
- const href = mark.attrs.href;
4484
- if (href?.startsWith("#") && !jumpPointIds.has(href.slice(1))) {
4485
- hasBroken = true;
4486
- return false;
4487
- }
4488
- }
4489
- }
4490
- }
4491
- });
4492
- return hasBroken;
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);
4493
5190
  }
4494
5191
 
4495
5192
  // src/ui/plugin/inlineToolTagNodeViewPlugin.tsx
@@ -4510,9 +5207,12 @@ var RESOURCE_TAG_ICONS2 = {
4510
5207
  time_diff: IconTimeDiff
4511
5208
  };
4512
5209
  var DEFAULT_ICON = IconTool;
5210
+ var RESOURCE_ID_OVERRIDES = {
5211
+ "close-happy-tiger": { color: "#0D0D0D", icon: IconStop }
5212
+ };
4513
5213
  var OUTER_STYLE = {
4514
5214
  display: "inline-flex",
4515
- padding: "1px",
5215
+ padding: "2px",
4516
5216
  border: "1px solid #CCCCCC",
4517
5217
  borderRadius: "2px",
4518
5218
  alignItems: "center",
@@ -4532,16 +5232,19 @@ var ICON_BLOCK_BASE = {
4532
5232
  };
4533
5233
  var LABEL_BASE = {
4534
5234
  padding: "0 4px",
4535
- fontSize: "11px",
4536
- lineHeight: 1,
5235
+ fontSize: "12px",
5236
+ lineHeight: "16px",
4537
5237
  userSelect: "none"
4538
5238
  };
4539
- function InlineToolTagNodeViewComponent({ node }) {
5239
+ function InlineToolTagNodeViewComponent({ node, selected }) {
4540
5240
  const type = node.attrs.type;
4541
5241
  const text2 = node.attrs.text;
4542
- const color = RESOURCE_TAG_COLORS2[type] ?? DEFAULT_COLOR;
4543
- const Icon = RESOURCE_TAG_ICONS2[type] ?? DEFAULT_ICON;
4544
- return /* @__PURE__ */ jsxs4("span", { style: OUTER_STYLE, "data-type": type, "data-resource-id": node.attrs.resourceId, children: [
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: [
4545
5248
  /* @__PURE__ */ jsx5("span", { style: { ...ICON_BLOCK_BASE, backgroundColor: color }, children: /* @__PURE__ */ jsx5(Icon, { size: 9, fill: "white" }) }),
4546
5249
  /* @__PURE__ */ jsx5("span", { style: { ...LABEL_BASE, color }, children: text2 })
4547
5250
  ] });
@@ -4585,283 +5288,94 @@ function addInlineChipDecorations(doc2, decorations) {
4585
5288
  })
4586
5289
  );
4587
5290
  }
4588
- });
4589
- }
4590
- function addStructureBorderDecorations(doc2, blocks, decorations) {
4591
- const json = doc2.toJSON();
4592
- let ast;
4593
- try {
4594
- ast = fromProseMirrorJSON(json);
4595
- } catch {
4596
- return;
4597
- }
4598
- const structures = analyzeJinjaBlocks(ast);
4599
- for (const structure of structures) {
4600
- const blockBranchType = /* @__PURE__ */ new Map();
4601
- for (const branch of structure.branches) {
4602
- blockBranchType.set(branch.tagBlockIndex, branch.type);
4603
- for (let i = branch.blockStartIndex; i < branch.blockEndIndex; i++) {
4604
- if (!blockBranchType.has(i)) {
4605
- blockBranchType.set(i, branch.type);
4606
- }
4607
- }
4608
- }
4609
- const lastBranch = structure.branches[structure.branches.length - 1];
4610
- if (lastBranch && !blockBranchType.has(structure.endifTagBlockIndex)) {
4611
- blockBranchType.set(structure.endifTagBlockIndex, lastBranch.type);
4612
- }
4613
- const first = structure.ifTagBlockIndex;
4614
- const last = structure.endifTagBlockIndex;
4615
- for (let i = first; i <= last; i++) {
4616
- const block = blocks[i];
4617
- if (!block) continue;
4618
- const branchType = blockBranchType.get(i) || "if";
4619
- const prevType = i > first ? blockBranchType.get(i - 1) : void 0;
4620
- const nextType = i < last ? blockBranchType.get(i + 1) : void 0;
4621
- const classes = ["jinja-bar", `jinja-bar-${branchType}`];
4622
- if (i === first || prevType !== branchType) classes.push("jinja-bar-first");
4623
- if (i === last || nextType !== branchType) classes.push("jinja-bar-last");
4624
- decorations.push(Decoration3.node(block.from, block.to, { class: classes.join(" ") }));
4625
- }
4626
- }
4627
- }
4628
- function buildDecorations2(doc2) {
4629
- const blocks = getBlockPositions(doc2);
4630
- const decorations = [];
4631
- addInlineChipDecorations(doc2, decorations);
4632
- addStructureBorderDecorations(doc2, blocks, decorations);
4633
- if (decorations.length === 0) return DecorationSet3.empty;
4634
- return DecorationSet3.create(doc2, decorations);
4635
- }
4636
- function createJinjaDecorationPlugin() {
4637
- return {
4638
- name: "jinjaDecoration",
4639
- plugins: () => [
4640
- new Plugin4({
4641
- key: jinjaPluginKey,
4642
- state: {
4643
- init(_, state) {
4644
- return buildDecorations2(state.doc);
4645
- },
4646
- apply(tr, oldDecos) {
4647
- if (!tr.docChanged) return oldDecos;
4648
- return buildDecorations2(tr.doc);
4649
- }
4650
- },
4651
- props: {
4652
- decorations(state) {
4653
- return jinjaPluginKey.getState(state);
4654
- }
4655
- }
4656
- })
4657
- ]
4658
- };
4659
- }
4660
-
4661
- // src/ui/plugin/jinjaIfBlockPlugin.tsx
4662
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
4663
- import { createRoot as createRoot2 } from "react-dom/client";
4664
-
4665
- // src/jinja/conditionHighlighter.ts
4666
- var KEYWORDS = {
4667
- and: "AND",
4668
- or: "OR",
4669
- not: "NOT",
4670
- in: "IN",
4671
- is: "IS",
4672
- True: "BOOL",
4673
- False: "BOOL",
4674
- true: "BOOL",
4675
- false: "BOOL",
4676
- None: "NONE",
4677
- null: "NONE"
4678
- };
4679
- var CATEGORY_BY_TYPE = {
4680
- STRING: "value",
4681
- NUMBER: "value",
4682
- BOOL: "value",
4683
- NONE: "value",
4684
- IDENT: "variable",
4685
- AND: "operator",
4686
- OR: "operator",
4687
- NOT: "operator",
4688
- IN: "operator",
4689
- IS: "operator",
4690
- EQ: "operator",
4691
- NEQ: "operator",
4692
- LT: "operator",
4693
- GT: "operator",
4694
- LTE: "operator",
4695
- GTE: "operator",
4696
- LPAREN: "punctuation",
4697
- RPAREN: "punctuation"
4698
- };
4699
- var NEGATIVE_NUMBER_PRECEDERS = /* @__PURE__ */ new Set([
4700
- "AND",
4701
- "OR",
4702
- "NOT",
4703
- "IN",
4704
- "IS",
4705
- "EQ",
4706
- "NEQ",
4707
- "LT",
4708
- "GT",
4709
- "LTE",
4710
- "GTE",
4711
- "LPAREN"
4712
- ]);
4713
- function canStartNegativeNumber(tokens) {
4714
- if (tokens.length === 0) {
4715
- return true;
4716
- }
4717
- const previous = tokens[tokens.length - 1];
4718
- return NEGATIVE_NUMBER_PRECEDERS.has(previous.type);
4719
- }
4720
- function finalizeTokens(input, rawTokens) {
4721
- const highlighted = [];
4722
- for (let i = 0; i < rawTokens.length; i++) {
4723
- const token = rawTokens[i];
4724
- const next = rawTokens[i + 1];
4725
- if (token.type === "IS" && next?.type === "NOT") {
4726
- highlighted.push({
4727
- text: input.slice(token.start, next.end),
4728
- start: token.start,
4729
- end: next.end,
4730
- category: "operator"
4731
- });
4732
- i++;
4733
- continue;
4734
- }
4735
- highlighted.push({
4736
- text: token.text,
4737
- start: token.start,
4738
- end: token.end,
4739
- category: CATEGORY_BY_TYPE[token.type]
4740
- });
4741
- }
4742
- return highlighted;
4743
- }
4744
- function tokenizeCondition(input) {
4745
- const rawTokens = [];
4746
- let i = 0;
4747
- while (i < input.length) {
4748
- const char = input[i];
4749
- if (/\s/.test(char)) {
4750
- i++;
4751
- continue;
4752
- }
4753
- if (char === '"' || char === "'") {
4754
- const start = i;
4755
- const quote = char;
4756
- i++;
4757
- while (i < input.length) {
4758
- if (input[i] === "\\" && i + 1 < input.length) {
4759
- i += 2;
4760
- continue;
4761
- }
4762
- if (input[i] === quote) {
4763
- i++;
4764
- rawTokens.push({
4765
- type: "STRING",
4766
- text: input.slice(start, i),
4767
- start,
4768
- end: i
4769
- });
4770
- break;
4771
- }
4772
- i++;
4773
- }
4774
- if (rawTokens[rawTokens.length - 1]?.start !== start) {
4775
- return finalizeTokens(input, rawTokens);
4776
- }
4777
- continue;
4778
- }
4779
- if (/[0-9]/.test(char) || char === "-" && i + 1 < input.length && /[0-9]/.test(input[i + 1]) && canStartNegativeNumber(rawTokens)) {
4780
- const start = i;
4781
- if (input[i] === "-") {
4782
- i++;
4783
- }
4784
- while (i < input.length && /[0-9]/.test(input[i])) {
4785
- i++;
4786
- }
4787
- if (i < input.length && input[i] === ".") {
4788
- i++;
4789
- while (i < input.length && /[0-9]/.test(input[i])) {
4790
- i++;
4791
- }
4792
- }
4793
- rawTokens.push({
4794
- type: "NUMBER",
4795
- text: input.slice(start, i),
4796
- start,
4797
- end: i
4798
- });
4799
- continue;
4800
- }
4801
- if (/[a-zA-Z_]/.test(char)) {
4802
- const start = i;
4803
- i++;
4804
- while (i < input.length && /[a-zA-Z0-9_.]/.test(input[i])) {
4805
- i++;
4806
- }
4807
- const text2 = input.slice(start, i);
4808
- rawTokens.push({
4809
- type: KEYWORDS[text2] ?? "IDENT",
4810
- text: text2,
4811
- start,
4812
- end: i
4813
- });
4814
- continue;
4815
- }
4816
- if (i + 1 < input.length) {
4817
- const twoChar = input.slice(i, i + 2);
4818
- if (twoChar === "==") {
4819
- rawTokens.push({ type: "EQ", text: twoChar, start: i, end: i + 2 });
4820
- i += 2;
4821
- continue;
4822
- }
4823
- if (twoChar === "!=") {
4824
- rawTokens.push({ type: "NEQ", text: twoChar, start: i, end: i + 2 });
4825
- i += 2;
4826
- continue;
4827
- }
4828
- if (twoChar === "<=") {
4829
- rawTokens.push({ type: "LTE", text: twoChar, start: i, end: i + 2 });
4830
- i += 2;
4831
- continue;
4832
- }
4833
- if (twoChar === ">=") {
4834
- rawTokens.push({ type: "GTE", text: twoChar, start: i, end: i + 2 });
4835
- i += 2;
4836
- continue;
5291
+ });
5292
+ }
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
+ }
4837
5310
  }
4838
5311
  }
4839
- if (char === "<") {
4840
- rawTokens.push({ type: "LT", text: char, start: i, end: i + 1 });
4841
- i++;
4842
- continue;
4843
- }
4844
- if (char === ">") {
4845
- rawTokens.push({ type: "GT", text: char, start: i, end: i + 1 });
4846
- i++;
4847
- continue;
4848
- }
4849
- if (char === "(") {
4850
- rawTokens.push({ type: "LPAREN", text: char, start: i, end: i + 1 });
4851
- i++;
4852
- continue;
5312
+ const lastBranch = structure.branches[structure.branches.length - 1];
5313
+ if (lastBranch && !blockBranchType.has(structure.endifTagBlockIndex)) {
5314
+ blockBranchType.set(structure.endifTagBlockIndex, lastBranch.type);
4853
5315
  }
4854
- if (char === ")") {
4855
- rawTokens.push({ type: "RPAREN", text: char, start: i, end: i + 1 });
4856
- i++;
4857
- continue;
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(" ") }));
4858
5328
  }
4859
- return finalizeTokens(input, rawTokens);
4860
5329
  }
4861
- return finalizeTokens(input, rawTokens);
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
+ };
4862
5374
  }
4863
5375
 
4864
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";
4865
5379
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
4866
5380
  var PLACEHOLDER_TEXT = "Describe what AI agent should do when this condition is met";
4867
5381
  var CONDITION_PLACEHOLDER = "Write a condition in natural language";
@@ -5312,8 +5826,8 @@ function ConditionDisplay({
5312
5826
  editable = true,
5313
5827
  onConditionChange
5314
5828
  }) {
5315
- const [isEditing, setIsEditing] = useState2(false);
5316
- const [draftValue, setDraftValue] = useState2(condition);
5829
+ const [isEditing, setIsEditing] = useState3(false);
5830
+ const [draftValue, setDraftValue] = useState3(condition);
5317
5831
  const inputRef = useRef2(null);
5318
5832
  useEffect2(() => {
5319
5833
  if (!isEditing) {
@@ -5419,7 +5933,7 @@ function JinjaBranchHeader({
5419
5933
  isLastBranch,
5420
5934
  hasElseBranch
5421
5935
  }) {
5422
- const [menuSource, setMenuSource] = useState2(null);
5936
+ const [menuSource, setMenuSource] = useState3(null);
5423
5937
  const kebabRef = useRef2(null);
5424
5938
  const footerRef = useRef2(null);
5425
5939
  useEffect2(() => {
@@ -5699,7 +6213,7 @@ function createJinjaIfBlockPlugin() {
5699
6213
 
5700
6214
  // src/ui/plugin/linkPlugin.ts
5701
6215
  import { InputRule as InputRule2, inputRules as inputRules2 } from "prosemirror-inputrules";
5702
- import { Plugin as Plugin5, PluginKey as PluginKey5, TextSelection as TextSelection2 } from "prosemirror-state";
6216
+ import { Plugin as Plugin5, PluginKey as PluginKey5, TextSelection as TextSelection3 } from "prosemirror-state";
5703
6217
  var LINK_INPUT_RE = /\[([^\]]*)\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
5704
6218
  var LINK_FULL_RE = /^\[([^\]]*)\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
5705
6219
  var linkEditKey = new PluginKey5("linkEdit");
@@ -5745,7 +6259,7 @@ function explodeLinkToRaw(state) {
5745
6259
  const tr = state.tr;
5746
6260
  tr.replaceWith(from, to, schema.text(rawText));
5747
6261
  const newPos = Math.min(from + rawCursorOffset, tr.doc.content.size);
5748
- tr.setSelection(TextSelection2.near(tr.doc.resolve(newPos)));
6262
+ tr.setSelection(TextSelection3.near(tr.doc.resolve(newPos)));
5749
6263
  tr.setMeta(linkEditKey, { from, to: from + rawText.length });
5750
6264
  return tr;
5751
6265
  }
@@ -5891,11 +6405,11 @@ var STYLES = (
5891
6405
  --ab-dh-color-idle: transparent;
5892
6406
  --ab-dh-color-hover: #c4c4c4;
5893
6407
  --ab-dh-color-visible: #8b8b8b;
5894
- --ab-dh-color-accent: #6366f1;
5895
- --ab-dh-bg-hover: rgba(0,0,0,0.04);
5896
- --ab-dh-bg-active: rgba(99,102,241,0.08);
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);
5897
6411
  --ab-dh-dragging-opacity: 0.4;
5898
- --ab-dh-handle-size: 22px;
6412
+ --ab-dh-handle-size: 24px;
5899
6413
  --ab-dh-handle-radius: 4px;
5900
6414
  --ab-dh-transition-duration: 0.15s;
5901
6415
 
@@ -5912,15 +6426,19 @@ var STYLES = (
5912
6426
  align-items: center;
5913
6427
  justify-content: center;
5914
6428
  cursor: grab;
5915
- color: var(--ab-dh-color-idle);
6429
+ color: transparent;
5916
6430
  border-radius: var(--ab-dh-handle-radius);
5917
- 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);
5918
6432
  user-select: none;
5919
6433
  pointer-events: auto;
5920
6434
  touch-action: none;
6435
+ opacity: 0;
6436
+ pointer-events: none;
5921
6437
  }
5922
- .ab-drag-handle-layer[data-hover] .ab-drag-handle {
5923
- 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);
5924
6442
  }
5925
6443
  .ab-drag-handle:hover {
5926
6444
  color: var(--ab-dh-color-visible) !important;
@@ -5934,18 +6452,20 @@ var STYLES = (
5934
6452
  .ab-drop-indicator {
5935
6453
  position: absolute;
5936
6454
  left: 0; right: 0;
5937
- height: 2px;
5938
- background: var(--ab-dh-color-accent, #6366f1);
5939
- 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;
5940
6459
  pointer-events: none;
5941
6460
  z-index: 20;
6461
+ box-sizing: border-box;
5942
6462
  transition: top 60ms ease-out;
5943
6463
  }
5944
6464
  .ab-block-dragging {
5945
6465
  opacity: var(--ab-dh-dragging-opacity, 0.4);
5946
6466
  }
5947
6467
  @keyframes ab-block-flash {
5948
- from { background: rgba(99,102,241,0.12); }
6468
+ from { background: rgba(1,156,110,0.08); }
5949
6469
  to { background: transparent; }
5950
6470
  }
5951
6471
  .ab-block-just-moved {
@@ -6101,23 +6621,25 @@ var DragHandleController = class {
6101
6621
  this.liveRegion.setAttribute("aria-live", "assertive");
6102
6622
  this.liveRegion.setAttribute("aria-atomic", "true");
6103
6623
  parent.appendChild(this.liveRegion);
6104
- const onEnter = () => {
6105
- this.layer.dataset.hover = "1";
6106
- };
6107
- const onLeave = () => {
6108
- if (!this.drag) delete this.layer.dataset.hover;
6109
- };
6110
- parent.addEventListener("mouseenter", onEnter);
6111
- parent.addEventListener("mouseleave", onLeave);
6112
- this.hoverBound = { enter: onEnter, leave: onLeave };
6624
+ this.hoverBound = { enter: () => {
6625
+ }, leave: () => {
6626
+ } };
6113
6627
  this.scrollContainer = this.findScrollContainer(view.dom);
6114
6628
  this.scrollContainer.addEventListener("scroll", this.onScroll, { passive: true });
6115
6629
  this.resizeObserver = new ResizeObserver(() => this.scheduleUpdate());
6116
6630
  this.resizeObserver.observe(view.dom);
6631
+ if (!view.editable) {
6632
+ this.layer.style.display = "none";
6633
+ }
6117
6634
  this.rebuildHandles();
6118
6635
  }
6119
6636
  update(view, prevState) {
6120
6637
  this.view = view;
6638
+ if (!view.editable) {
6639
+ this.layer.style.display = "none";
6640
+ return;
6641
+ }
6642
+ this.layer.style.display = "";
6121
6643
  if (this.drag) {
6122
6644
  this.pendingUpdate = true;
6123
6645
  return;
@@ -6125,6 +6647,21 @@ var DragHandleController = class {
6125
6647
  if (!view.state.doc.eq(prevState.doc)) {
6126
6648
  this.scheduleUpdate();
6127
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
+ }
6128
6665
  }
6129
6666
  destroy() {
6130
6667
  this.cancelDrag();
@@ -6216,6 +6753,7 @@ var DragHandleController = class {
6216
6753
  el.setAttribute("role", "button");
6217
6754
  el.setAttribute("aria-roledescription", "drag handle");
6218
6755
  el.setAttribute("aria-label", `\uBE14\uB85D ${idx + 1} \uC774\uB3D9`);
6756
+ el.setAttribute("title", "Drag to move");
6219
6757
  el.setAttribute("tabindex", "-1");
6220
6758
  el.innerHTML = GRIP_SVG;
6221
6759
  el.style.left = `${handleLeft}px`;
@@ -6226,6 +6764,7 @@ var DragHandleController = class {
6226
6764
  }
6227
6765
  this.handles = newHandles;
6228
6766
  this.prevBlocks = newBlocks;
6767
+ this.updateActiveHandle();
6229
6768
  }
6230
6769
  bindHandleEvents(el, index) {
6231
6770
  let currentIndex = index;
@@ -6296,7 +6835,8 @@ var DragHandleController = class {
6296
6835
  pointerId: e.pointerId,
6297
6836
  startX: e.clientX,
6298
6837
  startY: e.clientY,
6299
- activated: false
6838
+ activated: false,
6839
+ ghost: null
6300
6840
  };
6301
6841
  this.addGlobalDragListeners(e.pointerId);
6302
6842
  }
@@ -6305,6 +6845,22 @@ var DragHandleController = class {
6305
6845
  this.drag.activated = true;
6306
6846
  this.drag.handle.classList.add("dragging");
6307
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;
6308
6864
  const parent = this.view.dom.parentNode;
6309
6865
  this.dropIndicator = document.createElement("div");
6310
6866
  this.dropIndicator.className = "ab-drop-indicator";
@@ -6344,6 +6900,10 @@ var DragHandleController = class {
6344
6900
  this.activateDrag();
6345
6901
  }
6346
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
+ }
6347
6907
  this.updateDropTarget();
6348
6908
  }
6349
6909
  updateDropTarget() {
@@ -6415,6 +6975,10 @@ var DragHandleController = class {
6415
6975
  this.cleaningUp = true;
6416
6976
  this.drag.handle.classList.remove("dragging");
6417
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
+ }
6418
6982
  try {
6419
6983
  this.drag.handle.releasePointerCapture(this.drag.pointerId);
6420
6984
  } catch {
@@ -6620,6 +7184,55 @@ function createTodoNodeViewPlugin() {
6620
7184
  };
6621
7185
  }
6622
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
+
6623
7236
  // src/ui/plugin/slashCommandPlugin.ts
6624
7237
  import { Plugin as Plugin7, PluginKey as PluginKey7 } from "prosemirror-state";
6625
7238
  var slashCommandKey = new PluginKey7("slashCommand");
@@ -6689,17 +7302,37 @@ function createSlashCommandPlugin() {
6689
7302
  }
6690
7303
 
6691
7304
  // src/ui/components/SlashCommandMenu.tsx
6692
- import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
7305
+ import React4, { useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState4 } from "react";
6693
7306
  import { createPortal } from "react-dom";
6694
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
7307
+ import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
6695
7308
  function filterItems(items, query) {
6696
7309
  if (!query) return items;
6697
- const q = query.toLowerCase();
6698
- return items.filter(
6699
- ({ title, description }) => title.toLowerCase().includes(q) || (description?.toLowerCase().includes(q) ?? false)
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
+ }
6700
7319
  );
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);
6701
7334
  }
6702
- var POPUP_SHADOW = "0 0 0 1px rgba(0,0,0,0.07), 0 4px 20px rgba(0,0,0,0.11), 0 1px 4px rgba(0,0,0,0.05)";
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)";
6703
7336
  var BTN_RESET = {
6704
7337
  border: "none",
6705
7338
  padding: 0,
@@ -6712,11 +7345,14 @@ var BTN_RESET = {
6712
7345
  width: "100%"
6713
7346
  };
6714
7347
  var VPORT_MARGIN = 8;
6715
- var MENU_WIDTH = 280;
6716
- var MAX_MENU_H = 320;
7348
+ var MENU_WIDTH = 440;
7349
+ var MAX_MENU_H = 400;
6717
7350
  function SlashCommandMenu({ view, editorState, items }) {
6718
- const [selectedIndex, setSelectedIndex] = useState3(0);
7351
+ const [selectedIndex, setSelectedIndex] = useState4(0);
6719
7352
  const listRef = useRef3(null);
7353
+ useLayoutEffect(() => {
7354
+ injectScrollbarStyle();
7355
+ }, []);
6720
7356
  const pluginState = editorState ? slashCommandKey.getState(editorState) : void 0;
6721
7357
  const active = pluginState?.active ?? false;
6722
7358
  const range = pluginState?.range ?? null;
@@ -6728,7 +7364,7 @@ function SlashCommandMenu({ view, editorState, items }) {
6728
7364
  useEffect3(() => {
6729
7365
  const list = listRef.current;
6730
7366
  if (!list) return;
6731
- const item = list.children[selectedIndex];
7367
+ const item = list.querySelector(`[data-slash-index="${selectedIndex}"]`);
6732
7368
  item?.scrollIntoView({ block: "nearest" });
6733
7369
  }, [selectedIndex]);
6734
7370
  useEffect3(() => {
@@ -6770,6 +7406,7 @@ function SlashCommandMenu({ view, editorState, items }) {
6770
7406
  /* @__PURE__ */ jsx7(
6771
7407
  "div",
6772
7408
  {
7409
+ className: "ab-slash-menu",
6773
7410
  style: {
6774
7411
  position: "fixed",
6775
7412
  top,
@@ -6777,10 +7414,12 @@ function SlashCommandMenu({ view, editorState, items }) {
6777
7414
  width: MENU_WIDTH,
6778
7415
  maxHeight: MAX_MENU_H,
6779
7416
  overflowY: "auto",
7417
+ scrollbarWidth: "thin",
7418
+ scrollbarColor: "#c4c4c4 transparent",
6780
7419
  background: "#fff",
6781
- borderRadius: 10,
7420
+ borderRadius: 4,
6782
7421
  boxShadow: POPUP_SHADOW,
6783
- padding: 6,
7422
+ padding: "8px 0",
6784
7423
  zIndex: 1100,
6785
7424
  animation: "ab-float-in 0.12s ease"
6786
7425
  },
@@ -6794,100 +7433,103 @@ function SlashCommandMenu({ view, editorState, items }) {
6794
7433
  },
6795
7434
  children: "No results"
6796
7435
  }
6797
- ) : filtered.map((item, i) => /* @__PURE__ */ jsx7(
6798
- SlashMenuItem,
6799
- {
6800
- item,
6801
- selected: i === selectedIndex,
6802
- onMouseEnter: () => setSelectedIndex(i),
6803
- onMouseDown: (e) => {
6804
- e.preventDefault();
6805
- if (range) {
6806
- item.command({ view, range });
6807
- view.focus();
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
+ }
6808
7469
  }
6809
- }
6810
- },
6811
- item.id ?? item.title
6812
- )) })
7470
+ )
7471
+ ] }, item.id ?? item.title);
7472
+ }) })
6813
7473
  }
6814
7474
  ),
6815
7475
  document.body
6816
7476
  );
6817
7477
  }
6818
- function SlashMenuItem({ item, selected, onMouseEnter, onMouseDown }) {
7478
+ function SlashMenuItem({ item, index, selected, onMouseEnter, onMouseDown }) {
6819
7479
  return /* @__PURE__ */ jsxs6(
6820
7480
  "button",
6821
7481
  {
7482
+ "data-slash-index": index,
6822
7483
  style: {
6823
7484
  ...BTN_RESET,
6824
7485
  display: "flex",
6825
7486
  alignItems: "center",
6826
- gap: 10,
6827
- padding: "7px 10px",
6828
- borderRadius: 7,
6829
- 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",
6830
7490
  transition: "background 0.08s"
6831
7491
  },
6832
7492
  onMouseEnter,
6833
7493
  onMouseDown,
6834
7494
  children: [
6835
- item.icon !== void 0 && /* @__PURE__ */ jsx7(
7495
+ item.icon !== void 0 && /* @__PURE__ */ jsx7("span", { style: { flexShrink: 0, display: "flex", alignItems: "center" }, children: item.icon }),
7496
+ /* @__PURE__ */ jsx7(
6836
7497
  "span",
6837
7498
  {
6838
7499
  style: {
6839
- width: 28,
6840
- height: 28,
6841
- flexShrink: 0,
6842
- display: "flex",
6843
- alignItems: "center",
6844
- justifyContent: "center",
6845
- borderRadius: 6,
6846
- background: "rgba(0,0,0,0.04)",
6847
- fontSize: 13,
6848
- fontWeight: 700,
6849
- color: "#6366f1",
6850
- fontFamily: "monospace"
7500
+ fontSize: 14,
7501
+ fontWeight: 400,
7502
+ color: "#0d0d0d",
7503
+ whiteSpace: "nowrap",
7504
+ overflow: "hidden",
7505
+ textOverflow: "ellipsis",
7506
+ minWidth: 0
6851
7507
  },
6852
- children: item.icon
7508
+ children: item.title
6853
7509
  }
6854
7510
  ),
6855
- /* @__PURE__ */ jsxs6("span", { style: { display: "flex", flexDirection: "column", gap: 1, minWidth: 0 }, children: [
6856
- /* @__PURE__ */ jsx7(
6857
- "span",
6858
- {
6859
- style: {
6860
- fontSize: 13,
6861
- fontWeight: 500,
6862
- color: "#171717",
6863
- whiteSpace: "nowrap",
6864
- overflow: "hidden",
6865
- textOverflow: "ellipsis"
6866
- },
6867
- children: item.title
6868
- }
6869
- ),
6870
- item.description && /* @__PURE__ */ jsx7(
6871
- "span",
6872
- {
6873
- style: {
6874
- fontSize: 11,
6875
- color: "#9ca3af",
6876
- whiteSpace: "nowrap",
6877
- overflow: "hidden",
6878
- textOverflow: "ellipsis"
6879
- },
6880
- children: item.description
6881
- }
6882
- )
6883
- ] })
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
+ )
6884
7526
  ]
6885
7527
  }
6886
7528
  );
6887
7529
  }
6888
7530
 
6889
7531
  // src/ui/components/JinjaTreeView.tsx
6890
- import { useState as useState4 } from "react";
7532
+ import { useState as useState5 } from "react";
6891
7533
  import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
6892
7534
  var BRANCH_COLORS = {
6893
7535
  if: "#6366f1",
@@ -6932,7 +7574,7 @@ function BranchNode({
6932
7574
  ] });
6933
7575
  }
6934
7576
  function StructureNode({ structure, doc: doc2 }) {
6935
- const [expanded, setExpanded] = useState4(true);
7577
+ const [expanded, setExpanded] = useState5(true);
6936
7578
  const firstBranch = structure.branches[0];
6937
7579
  const label = firstBranch?.condition ? `if ${firstBranch.condition}` : "if";
6938
7580
  return /* @__PURE__ */ jsxs7("div", { className: "jinja-tree-structure", children: [
@@ -6965,13 +7607,179 @@ function JinjaTreeView({ doc: doc2, className }) {
6965
7607
  return /* @__PURE__ */ jsx8("div", { className: `jinja-tree ${className || ""}`, children: structures.map((structure) => /* @__PURE__ */ jsx8(StructureNode, { structure, doc: doc2 }, structure.id)) });
6966
7608
  }
6967
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
+ )) });
7774
+ }
7775
+
6968
7776
  // src/ui/components/FloatingMenu.tsx
6969
- import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
7777
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
6970
7778
  import { createPortal as createPortal2 } from "react-dom";
6971
- import { setBlockType, toggleMark as toggleMark2, wrapIn } from "prosemirror-commands";
7779
+ import { setBlockType as setBlockType2, toggleMark as toggleMark2, wrapIn } from "prosemirror-commands";
6972
7780
  import { wrapInList as wrapInList2 } from "prosemirror-schema-list";
6973
- import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
6974
- var { paragraph: paragraph2, heading: heading2, bulletList: bulletList3, orderedList: orderedList3, blockquote: blockquote2 } = actionbookSchema.nodes;
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;
6975
7783
  var {
6976
7784
  bold: bold2,
6977
7785
  italic: italic2,
@@ -6993,7 +7801,7 @@ function hasMark(state, type) {
6993
7801
  function activeHeadingLevel(state) {
6994
7802
  const { $from } = state.selection;
6995
7803
  const node = $from.parent;
6996
- if (node.type === heading2) return node.attrs.level;
7804
+ if (node.type === heading3) return node.attrs.level;
6997
7805
  return null;
6998
7806
  }
6999
7807
  function activeListType(state) {
@@ -7027,7 +7835,7 @@ function currentBlockLabel(state) {
7027
7835
  if (lt === "ordered") return "1. List";
7028
7836
  const { $from } = state.selection;
7029
7837
  for (let d = $from.depth; d > 0; d--) {
7030
- if ($from.node(d).type === blockquote2) return "Quote";
7838
+ if ($from.node(d).type === blockquote3) return "Quote";
7031
7839
  }
7032
7840
  return "Text";
7033
7841
  }
@@ -7036,7 +7844,7 @@ function emptyBlockCursorPos(state) {
7036
7844
  if (!selection.empty) return null;
7037
7845
  const { $from } = selection;
7038
7846
  const parent = $from.parent;
7039
- if ((parent.type === paragraph2 || parent.type === heading2) && parent.content.size === 0) {
7847
+ if ((parent.type === paragraph3 || parent.type === heading3) && parent.content.size === 0) {
7040
7848
  return selection.from;
7041
7849
  }
7042
7850
  return null;
@@ -7058,8 +7866,8 @@ var BTN_RESET2 = {
7058
7866
  lineHeight: 1
7059
7867
  };
7060
7868
  function TBtn({ children, active, title, onMouseDown, style }) {
7061
- const [hover, setHover] = useState5(false);
7062
- return /* @__PURE__ */ jsx9(
7869
+ const [hover, setHover] = useState7(false);
7870
+ return /* @__PURE__ */ jsx10(
7063
7871
  "button",
7064
7872
  {
7065
7873
  ...{ [FLOAT_ATTR]: "" },
@@ -7090,12 +7898,12 @@ function TBtn({ children, active, title, onMouseDown, style }) {
7090
7898
  );
7091
7899
  }
7092
7900
  function Divider() {
7093
- return /* @__PURE__ */ jsx9("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 } });
7094
7902
  }
7095
7903
  function BlockTypeDropdown({ view, state, label }) {
7096
- const [open, setOpen] = useState5(false);
7904
+ const [open, setOpen] = useState7(false);
7097
7905
  const btnRef = useRef4(null);
7098
- const [hover, setHover] = useState5(false);
7906
+ const [hover, setHover] = useState7(false);
7099
7907
  useEffect4(() => {
7100
7908
  if (!open) return;
7101
7909
  const onDown = (e) => {
@@ -7110,19 +7918,19 @@ function BlockTypeDropdown({ view, state, label }) {
7110
7918
  const listType = activeListType(state);
7111
7919
  const items = [
7112
7920
  { label: "Text", shortLabel: "\xB6", active: !headingLevel && !listType, run: () => {
7113
- setBlockType(paragraph2)(view.state, view.dispatch);
7921
+ setBlockType2(paragraph3)(view.state, view.dispatch);
7114
7922
  view.focus();
7115
7923
  } },
7116
7924
  { label: "Heading 1", shortLabel: "H1", active: headingLevel === 1, run: () => {
7117
- setBlockType(heading2, { level: 1 })(view.state, view.dispatch);
7925
+ setBlockType2(heading3, { level: 1 })(view.state, view.dispatch);
7118
7926
  view.focus();
7119
7927
  } },
7120
7928
  { label: "Heading 2", shortLabel: "H2", active: headingLevel === 2, run: () => {
7121
- setBlockType(heading2, { level: 2 })(view.state, view.dispatch);
7929
+ setBlockType2(heading3, { level: 2 })(view.state, view.dispatch);
7122
7930
  view.focus();
7123
7931
  } },
7124
7932
  { label: "Heading 3", shortLabel: "H3", active: headingLevel === 3, run: () => {
7125
- setBlockType(heading2, { level: 3 })(view.state, view.dispatch);
7933
+ setBlockType2(heading3, { level: 3 })(view.state, view.dispatch);
7126
7934
  view.focus();
7127
7935
  } },
7128
7936
  { label: "Bullet List", shortLabel: "\u2022", active: listType === "bullet", run: () => {
@@ -7144,13 +7952,17 @@ function BlockTypeDropdown({ view, state, label }) {
7144
7952
  view.focus();
7145
7953
  } },
7146
7954
  { label: "Quote", shortLabel: ">", active: false, run: () => {
7147
- 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);
7148
7960
  view.focus();
7149
7961
  } }
7150
7962
  ];
7151
7963
  const btnRect = btnRef.current?.getBoundingClientRect();
7152
- return /* @__PURE__ */ jsxs8(Fragment3, { children: [
7153
- /* @__PURE__ */ jsxs8(
7964
+ return /* @__PURE__ */ jsxs9(Fragment4, { children: [
7965
+ /* @__PURE__ */ jsxs9(
7154
7966
  "button",
7155
7967
  {
7156
7968
  ref: btnRef,
@@ -7178,12 +7990,12 @@ function BlockTypeDropdown({ view, state, label }) {
7178
7990
  },
7179
7991
  children: [
7180
7992
  label,
7181
- /* @__PURE__ */ jsx9("span", { style: { fontSize: 8, opacity: 0.6 }, children: "\u25BE" })
7993
+ /* @__PURE__ */ jsx10("span", { style: { fontSize: 8, opacity: 0.6 }, children: "\u25BE" })
7182
7994
  ]
7183
7995
  }
7184
7996
  ),
7185
7997
  open && btnRect && createPortal2(
7186
- /* @__PURE__ */ jsx9(
7998
+ /* @__PURE__ */ jsx10(
7187
7999
  "div",
7188
8000
  {
7189
8001
  ...{ [FLOAT_ATTR]: "" },
@@ -7199,7 +8011,7 @@ function BlockTypeDropdown({ view, state, label }) {
7199
8011
  minWidth: 168,
7200
8012
  animation: "ab-float-in 0.12s ease"
7201
8013
  },
7202
- children: items.map((item) => /* @__PURE__ */ jsx9(DropdownItem, { item, onRun: () => setOpen(false) }, item.label))
8014
+ children: items.map((item) => /* @__PURE__ */ jsx10(DropdownItem, { item, onRun: () => setOpen(false) }, item.label))
7203
8015
  }
7204
8016
  ),
7205
8017
  document.body
@@ -7207,8 +8019,8 @@ function BlockTypeDropdown({ view, state, label }) {
7207
8019
  ] });
7208
8020
  }
7209
8021
  function DropdownItem({ item, onRun }) {
7210
- const [hover, setHover] = useState5(false);
7211
- return /* @__PURE__ */ jsxs8(
8022
+ const [hover, setHover] = useState7(false);
8023
+ return /* @__PURE__ */ jsxs9(
7212
8024
  "button",
7213
8025
  {
7214
8026
  ...{ [FLOAT_ATTR]: "" },
@@ -7235,15 +8047,15 @@ function DropdownItem({ item, onRun }) {
7235
8047
  transition: "background 0.1s"
7236
8048
  },
7237
8049
  children: [
7238
- /* @__PURE__ */ jsx9("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 }),
7239
8051
  item.label
7240
8052
  ]
7241
8053
  }
7242
8054
  );
7243
8055
  }
7244
8056
  function SelectionToolbar({ view, state, selectionRect }) {
7245
- const [linkMode, setLinkMode] = useState5(false);
7246
- const [linkHref, setLinkHref] = useState5("");
8057
+ const [linkMode, setLinkMode] = useState7(false);
8058
+ const [linkHref, setLinkHref] = useState7("");
7247
8059
  const inputRef = useRef4(null);
7248
8060
  const isBold = hasMark(state, bold2);
7249
8061
  const isItalic = hasMark(state, italic2);
@@ -7259,7 +8071,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7259
8071
  if (top < VPORT_MARGIN2) top = selectionRect.bottom + TOOLBAR_GAP;
7260
8072
  left = Math.max(VPORT_MARGIN2, Math.min(left, window.innerWidth - toolbarW - VPORT_MARGIN2));
7261
8073
  top = Math.max(VPORT_MARGIN2, Math.min(top, window.innerHeight - toolbarH - VPORT_MARGIN2));
7262
- const run = useCallback2(
8074
+ const run = useCallback3(
7263
8075
  (cmd) => {
7264
8076
  cmd(view.state, view.dispatch);
7265
8077
  view.focus();
@@ -7296,10 +8108,10 @@ function SelectionToolbar({ view, state, selectionRect }) {
7296
8108
  view.dispatch(view.state.tr.removeMark(from, to, linkMark));
7297
8109
  view.focus();
7298
8110
  };
7299
- return /* @__PURE__ */ jsx9(
8111
+ return /* @__PURE__ */ jsx10(
7300
8112
  "div",
7301
8113
  {
7302
- ...{ [FLOAT_ATTR]: "" },
8114
+ ...{ [FLOAT_ATTR]: "selection-toolbar" },
7303
8115
  style: {
7304
8116
  position: "fixed",
7305
8117
  left,
@@ -7319,9 +8131,9 @@ function SelectionToolbar({ view, state, selectionRect }) {
7319
8131
  },
7320
8132
  children: linkMode ? (
7321
8133
  /* ── Link input mode ── */
7322
- /* @__PURE__ */ jsxs8(Fragment3, { children: [
7323
- /* @__PURE__ */ jsx9("span", { style: { fontSize: 13, color: "#9ca3af", paddingLeft: 4, flexShrink: 0 }, children: "\u2197" }),
7324
- /* @__PURE__ */ jsx9(
8134
+ /* @__PURE__ */ jsxs9(Fragment4, { children: [
8135
+ /* @__PURE__ */ jsx10("span", { style: { fontSize: 13, color: "#9ca3af", paddingLeft: 4, flexShrink: 0 }, children: "\u2197" }),
8136
+ /* @__PURE__ */ jsx10(
7325
8137
  "input",
7326
8138
  {
7327
8139
  ref: inputRef,
@@ -7352,7 +8164,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7352
8164
  }
7353
8165
  }
7354
8166
  ),
7355
- /* @__PURE__ */ jsx9(
8167
+ /* @__PURE__ */ jsx10(
7356
8168
  TBtn,
7357
8169
  {
7358
8170
  title: "Apply (Enter)",
@@ -7364,7 +8176,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7364
8176
  children: "\u21B5"
7365
8177
  }
7366
8178
  ),
7367
- /* @__PURE__ */ jsx9(
8179
+ /* @__PURE__ */ jsx10(
7368
8180
  TBtn,
7369
8181
  {
7370
8182
  title: "Cancel (Esc)",
@@ -7380,8 +8192,8 @@ function SelectionToolbar({ view, state, selectionRect }) {
7380
8192
  ] })
7381
8193
  ) : (
7382
8194
  /* ── Normal toolbar mode ── */
7383
- /* @__PURE__ */ jsxs8(Fragment3, { children: [
7384
- /* @__PURE__ */ jsx9(
8195
+ /* @__PURE__ */ jsxs9(Fragment4, { children: [
8196
+ /* @__PURE__ */ jsx10(
7385
8197
  TBtn,
7386
8198
  {
7387
8199
  active: isBold,
@@ -7391,7 +8203,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7391
8203
  children: "B"
7392
8204
  }
7393
8205
  ),
7394
- /* @__PURE__ */ jsx9(
8206
+ /* @__PURE__ */ jsx10(
7395
8207
  TBtn,
7396
8208
  {
7397
8209
  active: isItalic,
@@ -7401,7 +8213,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7401
8213
  children: "I"
7402
8214
  }
7403
8215
  ),
7404
- /* @__PURE__ */ jsx9(
8216
+ /* @__PURE__ */ jsx10(
7405
8217
  TBtn,
7406
8218
  {
7407
8219
  active: isUnderline,
@@ -7411,7 +8223,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7411
8223
  children: "U"
7412
8224
  }
7413
8225
  ),
7414
- /* @__PURE__ */ jsx9(
8226
+ /* @__PURE__ */ jsx10(
7415
8227
  TBtn,
7416
8228
  {
7417
8229
  active: isStrike,
@@ -7421,7 +8233,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7421
8233
  children: "S"
7422
8234
  }
7423
8235
  ),
7424
- /* @__PURE__ */ jsx9(
8236
+ /* @__PURE__ */ jsx10(
7425
8237
  TBtn,
7426
8238
  {
7427
8239
  active: isCode,
@@ -7431,7 +8243,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
7431
8243
  children: "`\xB7`"
7432
8244
  }
7433
8245
  ),
7434
- /* @__PURE__ */ jsx9(
8246
+ /* @__PURE__ */ jsx10(
7435
8247
  TBtn,
7436
8248
  {
7437
8249
  active: isLink,
@@ -7441,8 +8253,8 @@ function SelectionToolbar({ view, state, selectionRect }) {
7441
8253
  children: "\u2197"
7442
8254
  }
7443
8255
  ),
7444
- /* @__PURE__ */ jsx9(Divider, {}),
7445
- /* @__PURE__ */ jsx9(BlockTypeDropdown, { view, state, label: blockLabel })
8256
+ /* @__PURE__ */ jsx10(Divider, {}),
8257
+ /* @__PURE__ */ jsx10(BlockTypeDropdown, { view, state, label: blockLabel })
7446
8258
  ] })
7447
8259
  )
7448
8260
  }
@@ -7453,7 +8265,7 @@ var BLOCK_ITEMS = [
7453
8265
  shortLabel: "\xB6",
7454
8266
  label: "Text",
7455
8267
  run: (v) => {
7456
- setBlockType(paragraph2)(v.state, v.dispatch);
8268
+ setBlockType2(paragraph3)(v.state, v.dispatch);
7457
8269
  v.focus();
7458
8270
  }
7459
8271
  },
@@ -7461,7 +8273,7 @@ var BLOCK_ITEMS = [
7461
8273
  shortLabel: "H1",
7462
8274
  label: "Heading 1",
7463
8275
  run: (v) => {
7464
- setBlockType(heading2, { level: 1 })(v.state, v.dispatch);
8276
+ setBlockType2(heading3, { level: 1 })(v.state, v.dispatch);
7465
8277
  v.focus();
7466
8278
  }
7467
8279
  },
@@ -7469,7 +8281,7 @@ var BLOCK_ITEMS = [
7469
8281
  shortLabel: "H2",
7470
8282
  label: "Heading 2",
7471
8283
  run: (v) => {
7472
- setBlockType(heading2, { level: 2 })(v.state, v.dispatch);
8284
+ setBlockType2(heading3, { level: 2 })(v.state, v.dispatch);
7473
8285
  v.focus();
7474
8286
  }
7475
8287
  },
@@ -7477,7 +8289,7 @@ var BLOCK_ITEMS = [
7477
8289
  shortLabel: "H3",
7478
8290
  label: "Heading 3",
7479
8291
  run: (v) => {
7480
- setBlockType(heading2, { level: 3 })(v.state, v.dispatch);
8292
+ setBlockType2(heading3, { level: 3 })(v.state, v.dispatch);
7481
8293
  v.focus();
7482
8294
  }
7483
8295
  },
@@ -7512,7 +8324,7 @@ var BLOCK_ITEMS = [
7512
8324
  shortLabel: "\u275D",
7513
8325
  label: "Quote",
7514
8326
  run: (v) => {
7515
- wrapIn(blockquote2)(v.state, v.dispatch);
8327
+ wrapIn(blockquote3)(v.state, v.dispatch);
7516
8328
  v.focus();
7517
8329
  }
7518
8330
  },
@@ -7532,7 +8344,7 @@ var BLOCK_ITEMS = [
7532
8344
  }
7533
8345
  ];
7534
8346
  function EmptyParaHandle({ view, cursorPos }) {
7535
- const [menuOpen, setMenuOpen] = useState5(false);
8347
+ const [menuOpen, setMenuOpen] = useState7(false);
7536
8348
  const coords = view.coordsAtPos(cursorPos);
7537
8349
  const lineH = Math.max(coords.bottom - coords.top, 18);
7538
8350
  const btnSize = 22;
@@ -7550,8 +8362,8 @@ function EmptyParaHandle({ view, cursorPos }) {
7550
8362
  document.addEventListener("mousedown", onDown, true);
7551
8363
  return () => document.removeEventListener("mousedown", onDown, true);
7552
8364
  }, [menuOpen]);
7553
- return /* @__PURE__ */ jsxs8(Fragment3, { children: [
7554
- /* @__PURE__ */ jsx9(
8365
+ return /* @__PURE__ */ jsxs9(Fragment4, { children: [
8366
+ /* @__PURE__ */ jsx10(
7555
8367
  "button",
7556
8368
  {
7557
8369
  ...{ [FLOAT_ATTR]: "" },
@@ -7583,7 +8395,7 @@ function EmptyParaHandle({ view, cursorPos }) {
7583
8395
  children: "+"
7584
8396
  }
7585
8397
  ),
7586
- menuOpen && /* @__PURE__ */ jsx9(
8398
+ menuOpen && /* @__PURE__ */ jsx10(
7587
8399
  "div",
7588
8400
  {
7589
8401
  ...{ [FLOAT_ATTR]: "" },
@@ -7599,7 +8411,7 @@ function EmptyParaHandle({ view, cursorPos }) {
7599
8411
  minWidth: 168,
7600
8412
  animation: "ab-float-in 0.12s ease"
7601
8413
  },
7602
- children: BLOCK_ITEMS.map((item) => /* @__PURE__ */ jsx9(
8414
+ children: BLOCK_ITEMS.map((item) => /* @__PURE__ */ jsx10(
7603
8415
  BlockMenuItem,
7604
8416
  {
7605
8417
  item,
@@ -7617,8 +8429,8 @@ function BlockMenuItem({
7617
8429
  view,
7618
8430
  onRun
7619
8431
  }) {
7620
- const [hover, setHover] = useState5(false);
7621
- return /* @__PURE__ */ jsxs8(
8432
+ const [hover, setHover] = useState7(false);
8433
+ return /* @__PURE__ */ jsxs9(
7622
8434
  "button",
7623
8435
  {
7624
8436
  ...{ [FLOAT_ATTR]: "" },
@@ -7644,7 +8456,7 @@ function BlockMenuItem({
7644
8456
  transition: "background 0.1s"
7645
8457
  },
7646
8458
  children: [
7647
- /* @__PURE__ */ jsx9(
8459
+ /* @__PURE__ */ jsx10(
7648
8460
  "span",
7649
8461
  {
7650
8462
  style: {
@@ -7665,10 +8477,10 @@ function BlockMenuItem({
7665
8477
  );
7666
8478
  }
7667
8479
  function FloatingMenu({ view, editorState }) {
7668
- const [showEmptyHandle, setShowEmptyHandle] = useState5(false);
8480
+ const [showEmptyHandle, setShowEmptyHandle] = useState7(false);
7669
8481
  const dwellTimerRef = useRef4(null);
7670
8482
  const lastEmptyPosRef = useRef4(null);
7671
- const [, setScrollTick] = useState5(0);
8483
+ const [, setScrollTick] = useState7(0);
7672
8484
  useEffect4(() => {
7673
8485
  const editorDom = view?.dom;
7674
8486
  if (!editorDom) return;
@@ -7708,8 +8520,8 @@ function FloatingMenu({ view, editorState }) {
7708
8520
  }
7709
8521
  const selectionRect = hasSelection ? getSelectionDOMRect() : null;
7710
8522
  return createPortal2(
7711
- /* @__PURE__ */ jsxs8(Fragment3, { children: [
7712
- hasSelection && selectionRect && /* @__PURE__ */ jsx9(
8523
+ /* @__PURE__ */ jsxs9(Fragment4, { children: [
8524
+ hasSelection && selectionRect && /* @__PURE__ */ jsx10(
7713
8525
  SelectionToolbar,
7714
8526
  {
7715
8527
  view,
@@ -7717,7 +8529,7 @@ function FloatingMenu({ view, editorState }) {
7717
8529
  selectionRect
7718
8530
  }
7719
8531
  ),
7720
- !hasSelection && showEmptyHandle && emptyPos !== null && /* @__PURE__ */ jsx9(EmptyParaHandle, { view, cursorPos: emptyPos })
8532
+ !hasSelection && showEmptyHandle && emptyPos !== null && /* @__PURE__ */ jsx10(EmptyParaHandle, { view, cursorPos: emptyPos })
7721
8533
  ] }),
7722
8534
  document.body
7723
8535
  );
@@ -7886,6 +8698,7 @@ function createInlineSuggestPlugin(provider, endpoint, options) {
7886
8698
  }
7887
8699
  export {
7888
8700
  ActionbookRenderer,
8701
+ DocumentTreeView,
7889
8702
  EditorShell,
7890
8703
  FloatingMenu,
7891
8704
  JUMP_POINT_ADJACENT_SPEC,
@@ -7908,6 +8721,7 @@ export {
7908
8721
  createKeymapPlugin,
7909
8722
  createLinkPlugin,
7910
8723
  createMarkdownClipboardPlugin,
8724
+ createNoteBlockPlugin,
7911
8725
  createPluginArray,
7912
8726
  createReactNodeView,
7913
8727
  createSlashCommandPlugin,