@sendbird/actionbook-core 0.7.2 → 0.8.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,9 +32,12 @@ var actionbookSchema = new Schema({
32
32
  bulletList: {
33
33
  content: "listItem+",
34
34
  group: "block",
35
- parseDOM: [{ tag: "ul" }],
35
+ parseDOM: [
36
+ { tag: "div.ab-bullet-list" },
37
+ { tag: "ul" }
38
+ ],
36
39
  toDOM() {
37
- return ["ul", 0];
40
+ return ["div", { class: "ab-bullet-list", role: "list" }, 0];
38
41
  }
39
42
  },
40
43
  orderedList: {
@@ -42,6 +45,12 @@ var actionbookSchema = new Schema({
42
45
  group: "block",
43
46
  attrs: { start: { default: 1 } },
44
47
  parseDOM: [
48
+ {
49
+ tag: "div.ab-ordered-list",
50
+ getAttrs(dom) {
51
+ return { start: Number(dom.dataset.start) || 1 };
52
+ }
53
+ },
45
54
  {
46
55
  tag: "ol",
47
56
  getAttrs(dom) {
@@ -50,13 +59,25 @@ var actionbookSchema = new Schema({
50
59
  }
51
60
  ],
52
61
  toDOM(node) {
53
- return node.attrs.start === 1 ? ["ol", 0] : ["ol", { start: node.attrs.start }, 0];
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];
54
65
  }
55
66
  },
56
67
  listItem: {
57
68
  content: "block+",
58
69
  attrs: { checked: { default: null } },
59
70
  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
+ },
60
81
  {
61
82
  tag: "li",
62
83
  getAttrs(dom) {
@@ -73,10 +94,12 @@ var actionbookSchema = new Schema({
73
94
  }
74
95
  ],
75
96
  toDOM(node) {
97
+ const attrs = { class: "ab-list-item", role: "listitem" };
76
98
  if (node.attrs.checked != null) {
77
- return ["li", { class: "todo-item", "data-checked": String(node.attrs.checked) }, 0];
99
+ attrs.class = "ab-list-item todo-item";
100
+ attrs["data-checked"] = String(node.attrs.checked);
78
101
  }
79
- return ["li", 0];
102
+ return ["div", attrs, 0];
80
103
  },
81
104
  defining: true
82
105
  },
@@ -89,6 +112,72 @@ var actionbookSchema = new Schema({
89
112
  },
90
113
  defining: true
91
114
  },
115
+ codeBlock: {
116
+ content: "text*",
117
+ group: "block",
118
+ code: true,
119
+ defining: true,
120
+ marks: "",
121
+ 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
+ }
130
+ }
131
+ ],
132
+ toDOM(node) {
133
+ const attrs = {};
134
+ if (node.attrs.language) attrs["data-language"] = node.attrs.language;
135
+ return ["pre", ["code", attrs, 0]];
136
+ }
137
+ },
138
+ jinjaIfBlock: {
139
+ content: "jinjaIfBranch+",
140
+ group: "block",
141
+ isolating: true,
142
+ defining: true,
143
+ parseDOM: [{ tag: "div[data-jinja-if-block]" }],
144
+ toDOM() {
145
+ return ["div", { "data-jinja-if-block": "", class: "jinja-if-block" }, 0];
146
+ }
147
+ },
148
+ jinjaIfBranch: {
149
+ content: "block+",
150
+ isolating: true,
151
+ defining: true,
152
+ attrs: {
153
+ branchType: { default: "if" },
154
+ condition: { default: "" }
155
+ },
156
+ parseDOM: [
157
+ {
158
+ tag: "div[data-jinja-branch]",
159
+ getAttrs(dom) {
160
+ const el = dom;
161
+ return {
162
+ branchType: el.getAttribute("data-branch-type") || "if",
163
+ condition: el.getAttribute("data-condition") || ""
164
+ };
165
+ }
166
+ }
167
+ ],
168
+ toDOM(node) {
169
+ return [
170
+ "div",
171
+ {
172
+ "data-jinja-branch": "",
173
+ "data-branch-type": node.attrs.branchType,
174
+ "data-condition": node.attrs.condition,
175
+ class: `jinja-branch jinja-branch-${node.attrs.branchType}`
176
+ },
177
+ 0
178
+ ];
179
+ }
180
+ },
92
181
  horizontalRule: {
93
182
  group: "block",
94
183
  parseDOM: [{ tag: "hr" }],
@@ -1996,8 +2085,7 @@ function convertInline2(node) {
1996
2085
  return assertNever(node);
1997
2086
  }
1998
2087
  }
1999
- var MAX_LIST_DEPTH = 3;
2000
- function convertBlockToArray(node, listDepth = 0) {
2088
+ function convertBlockToArray(node) {
2001
2089
  if (node.type === "jinjaIfBlock") {
2002
2090
  const jNode = node;
2003
2091
  const result = [];
@@ -2005,15 +2093,15 @@ function convertBlockToArray(node, listDepth = 0) {
2005
2093
  const tagText = branch.branchType === "else" ? "{% else %}" : `{% ${branch.branchType} ${branch.condition ?? ""} %}`.trim();
2006
2094
  result.push({ type: "paragraph", content: [{ type: "text", text: tagText }] });
2007
2095
  for (const child of branch.content) {
2008
- result.push(...convertBlockToArray(child, listDepth));
2096
+ result.push(...convertBlockToArray(child));
2009
2097
  }
2010
2098
  }
2011
2099
  result.push({ type: "paragraph", content: [{ type: "text", text: "{% endif %}" }] });
2012
2100
  return result;
2013
2101
  }
2014
- return [convertBlock2(node, listDepth)];
2102
+ return [convertBlock2(node)];
2015
2103
  }
2016
- function convertBlock2(node, listDepth = 0) {
2104
+ function convertBlock2(node) {
2017
2105
  switch (node.type) {
2018
2106
  case "paragraph":
2019
2107
  return {
@@ -2026,29 +2114,21 @@ function convertBlock2(node, listDepth = 0) {
2026
2114
  attrs: { level: node.level },
2027
2115
  content: node.content.flatMap(convertInlineToArray)
2028
2116
  };
2029
- case "bulletList": {
2030
- if (listDepth >= MAX_LIST_DEPTH) {
2031
- return flattenListToBlock(node);
2032
- }
2117
+ case "bulletList":
2033
2118
  return {
2034
2119
  type: "bulletList",
2035
- content: node.content.map((c) => convertBlock2(c, listDepth + 1))
2120
+ content: node.content.map(convertBlock2)
2036
2121
  };
2037
- }
2038
- case "orderedList": {
2039
- if (listDepth >= MAX_LIST_DEPTH) {
2040
- return flattenListToBlock(node);
2041
- }
2122
+ case "orderedList":
2042
2123
  return {
2043
2124
  type: "orderedList",
2044
2125
  attrs: { start: node.start },
2045
- content: node.content.map((c) => convertBlock2(c, listDepth + 1))
2126
+ content: node.content.map(convertBlock2)
2046
2127
  };
2047
- }
2048
2128
  case "listItem": {
2049
2129
  const result = {
2050
2130
  type: "listItem",
2051
- content: node.content.flatMap((c) => convertBlockToArray(c, listDepth))
2131
+ content: node.content.flatMap(convertBlockToArray)
2052
2132
  };
2053
2133
  if (node.checked != null) {
2054
2134
  result.attrs = { checked: node.checked };
@@ -2058,7 +2138,7 @@ function convertBlock2(node, listDepth = 0) {
2058
2138
  case "blockquote":
2059
2139
  return {
2060
2140
  type: "blockquote",
2061
- content: node.content.flatMap((c) => convertBlockToArray(c, listDepth))
2141
+ content: node.content.flatMap(convertBlockToArray)
2062
2142
  };
2063
2143
  case "horizontalRule":
2064
2144
  return { type: "horizontalRule" };
@@ -2080,7 +2160,7 @@ function convertBlock2(node, listDepth = 0) {
2080
2160
  const tagText = branch.branchType === "else" ? "{% else %}" : `{% ${branch.branchType} ${branch.condition ?? ""} %}`.trim();
2081
2161
  result.push({ type: "paragraph", content: [{ type: "text", text: tagText }] });
2082
2162
  for (const child of branch.content) {
2083
- result.push(...convertBlockToArray(child, listDepth));
2163
+ result.push(...convertBlockToArray(child));
2084
2164
  }
2085
2165
  }
2086
2166
  result.push({ type: "paragraph", content: [{ type: "text", text: "{% endif %}" }] });
@@ -2090,36 +2170,10 @@ function convertBlock2(node, listDepth = 0) {
2090
2170
  return assertNever(node);
2091
2171
  }
2092
2172
  }
2093
- function flattenListToBlock(node) {
2094
- const paragraphs = [];
2095
- function extractInlines(n) {
2096
- switch (n.type) {
2097
- case "paragraph":
2098
- case "heading":
2099
- paragraphs.push({
2100
- type: "paragraph",
2101
- content: n.content.flatMap(convertInlineToArray)
2102
- });
2103
- break;
2104
- case "listItem":
2105
- case "blockquote":
2106
- for (const child of n.content) extractInlines(child);
2107
- break;
2108
- case "bulletList":
2109
- case "orderedList":
2110
- for (const item of n.content) extractInlines(item);
2111
- break;
2112
- default:
2113
- break;
2114
- }
2115
- }
2116
- extractInlines(node);
2117
- return paragraphs.length > 0 ? paragraphs[0] : { type: "paragraph", content: [] };
2118
- }
2119
2173
  function toProseMirrorJSON(doc2) {
2120
2174
  return {
2121
2175
  type: "doc",
2122
- content: doc2.content.flatMap((c) => convertBlockToArray(c, 0))
2176
+ content: doc2.content.flatMap(convertBlockToArray)
2123
2177
  };
2124
2178
  }
2125
2179
  function astNodesToJSONContent(nodes) {
@@ -2210,6 +2264,15 @@ function useEditorView(config) {
2210
2264
  };
2211
2265
  }
2212
2266
 
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
+
2213
2276
  // src/ui/plugin/inputRulesPlugin.ts
2214
2277
  import { InputRule, wrappingInputRule } from "prosemirror-inputrules";
2215
2278
  var BULLET_LIST_RE = /^\s*([-*])\s$/;
@@ -4322,6 +4385,113 @@ function createJumpPointNodeViewPlugin() {
4322
4385
  };
4323
4386
  }
4324
4387
 
4388
+ // src/ui/plugin/jumpPointValidationPlugin.ts
4389
+ import { Plugin as Plugin3, PluginKey as PluginKey3 } from "prosemirror-state";
4390
+ import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
4391
+ var validationKey = new PluginKey3("jumpPointValidation");
4392
+ function buildValidationDecorations(doc2) {
4393
+ const jumpPointIds = /* @__PURE__ */ new Map();
4394
+ const anchorRefs = [];
4395
+ doc2.descendants((node, pos) => {
4396
+ 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
+ }
4410
+ }
4411
+ });
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
+ }
4420
+ }
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
+ );
4427
+ }
4428
+ }
4429
+ return decorations.length > 0 ? DecorationSet2.create(doc2, decorations) : DecorationSet2.empty;
4430
+ }
4431
+ function createJumpPointValidationPlugin() {
4432
+ return {
4433
+ name: "jumpPointValidation",
4434
+ plugins: () => [
4435
+ new Plugin3({
4436
+ key: validationKey,
4437
+ state: {
4438
+ init(_, state) {
4439
+ return buildValidationDecorations(state.doc);
4440
+ },
4441
+ apply(tr, oldDecos) {
4442
+ if (!tr.docChanged) return oldDecos;
4443
+ return buildValidationDecorations(tr.doc);
4444
+ }
4445
+ },
4446
+ props: {
4447
+ decorations(state) {
4448
+ return validationKey.getState(state);
4449
+ }
4450
+ }
4451
+ })
4452
+ ]
4453
+ };
4454
+ }
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;
4493
+ }
4494
+
4325
4495
  // src/ui/plugin/inlineToolTagNodeViewPlugin.tsx
4326
4496
  import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
4327
4497
  var RESOURCE_TAG_COLORS2 = {
@@ -4386,9 +4556,9 @@ function createInlineToolTagNodeViewPlugin() {
4386
4556
  }
4387
4557
 
4388
4558
  // src/ui/plugin/jinjaDecoration.ts
4389
- import { Plugin as Plugin3, PluginKey as PluginKey3 } from "prosemirror-state";
4390
- import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
4391
- var jinjaPluginKey = new PluginKey3("jinjaDecoration");
4559
+ import { Plugin as Plugin4, PluginKey as PluginKey4 } from "prosemirror-state";
4560
+ import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "prosemirror-view";
4561
+ var jinjaPluginKey = new PluginKey4("jinjaDecoration");
4392
4562
  var JINJA_TAG_RE = /\{%\s*(if|elif|else|endif)\s*([^%]*?)\s*%\}/g;
4393
4563
  function getBlockPositions(doc2) {
4394
4564
  const blocks = [];
@@ -4410,7 +4580,7 @@ function addInlineChipDecorations(doc2, decorations) {
4410
4580
  const to = from + match[0].length;
4411
4581
  const keyword = match[1];
4412
4582
  decorations.push(
4413
- Decoration2.inline(from, to, {
4583
+ Decoration3.inline(from, to, {
4414
4584
  class: `jinja-chip jinja-chip-${keyword}`
4415
4585
  })
4416
4586
  );
@@ -4451,7 +4621,7 @@ function addStructureBorderDecorations(doc2, blocks, decorations) {
4451
4621
  const classes = ["jinja-bar", `jinja-bar-${branchType}`];
4452
4622
  if (i === first || prevType !== branchType) classes.push("jinja-bar-first");
4453
4623
  if (i === last || nextType !== branchType) classes.push("jinja-bar-last");
4454
- decorations.push(Decoration2.node(block.from, block.to, { class: classes.join(" ") }));
4624
+ decorations.push(Decoration3.node(block.from, block.to, { class: classes.join(" ") }));
4455
4625
  }
4456
4626
  }
4457
4627
  }
@@ -4460,14 +4630,14 @@ function buildDecorations2(doc2) {
4460
4630
  const decorations = [];
4461
4631
  addInlineChipDecorations(doc2, decorations);
4462
4632
  addStructureBorderDecorations(doc2, blocks, decorations);
4463
- if (decorations.length === 0) return DecorationSet2.empty;
4464
- return DecorationSet2.create(doc2, decorations);
4633
+ if (decorations.length === 0) return DecorationSet3.empty;
4634
+ return DecorationSet3.create(doc2, decorations);
4465
4635
  }
4466
4636
  function createJinjaDecorationPlugin() {
4467
4637
  return {
4468
4638
  name: "jinjaDecoration",
4469
4639
  plugins: () => [
4470
- new Plugin3({
4640
+ new Plugin4({
4471
4641
  key: jinjaPluginKey,
4472
4642
  state: {
4473
4643
  init(_, state) {
@@ -4488,12 +4658,1051 @@ function createJinjaDecorationPlugin() {
4488
4658
  };
4489
4659
  }
4490
4660
 
4661
+ // src/ui/plugin/jinjaIfBlockPlugin.tsx
4662
+ import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
4663
+ import { createRoot as createRoot2 } from "react-dom/client";
4664
+
4665
+ // src/jinja/conditionHighlighter.ts
4666
+ var KEYWORDS = {
4667
+ and: "AND",
4668
+ or: "OR",
4669
+ not: "NOT",
4670
+ in: "IN",
4671
+ is: "IS",
4672
+ True: "BOOL",
4673
+ False: "BOOL",
4674
+ true: "BOOL",
4675
+ false: "BOOL",
4676
+ None: "NONE",
4677
+ null: "NONE"
4678
+ };
4679
+ var CATEGORY_BY_TYPE = {
4680
+ STRING: "value",
4681
+ NUMBER: "value",
4682
+ BOOL: "value",
4683
+ NONE: "value",
4684
+ IDENT: "variable",
4685
+ AND: "operator",
4686
+ OR: "operator",
4687
+ NOT: "operator",
4688
+ IN: "operator",
4689
+ IS: "operator",
4690
+ EQ: "operator",
4691
+ NEQ: "operator",
4692
+ LT: "operator",
4693
+ GT: "operator",
4694
+ LTE: "operator",
4695
+ GTE: "operator",
4696
+ LPAREN: "punctuation",
4697
+ RPAREN: "punctuation"
4698
+ };
4699
+ var NEGATIVE_NUMBER_PRECEDERS = /* @__PURE__ */ new Set([
4700
+ "AND",
4701
+ "OR",
4702
+ "NOT",
4703
+ "IN",
4704
+ "IS",
4705
+ "EQ",
4706
+ "NEQ",
4707
+ "LT",
4708
+ "GT",
4709
+ "LTE",
4710
+ "GTE",
4711
+ "LPAREN"
4712
+ ]);
4713
+ function canStartNegativeNumber(tokens) {
4714
+ if (tokens.length === 0) {
4715
+ return true;
4716
+ }
4717
+ const previous = tokens[tokens.length - 1];
4718
+ return NEGATIVE_NUMBER_PRECEDERS.has(previous.type);
4719
+ }
4720
+ function finalizeTokens(input, rawTokens) {
4721
+ const highlighted = [];
4722
+ for (let i = 0; i < rawTokens.length; i++) {
4723
+ const token = rawTokens[i];
4724
+ const next = rawTokens[i + 1];
4725
+ if (token.type === "IS" && next?.type === "NOT") {
4726
+ highlighted.push({
4727
+ text: input.slice(token.start, next.end),
4728
+ start: token.start,
4729
+ end: next.end,
4730
+ category: "operator"
4731
+ });
4732
+ i++;
4733
+ continue;
4734
+ }
4735
+ highlighted.push({
4736
+ text: token.text,
4737
+ start: token.start,
4738
+ end: token.end,
4739
+ category: CATEGORY_BY_TYPE[token.type]
4740
+ });
4741
+ }
4742
+ return highlighted;
4743
+ }
4744
+ function tokenizeCondition(input) {
4745
+ const rawTokens = [];
4746
+ let i = 0;
4747
+ while (i < input.length) {
4748
+ const char = input[i];
4749
+ if (/\s/.test(char)) {
4750
+ i++;
4751
+ continue;
4752
+ }
4753
+ if (char === '"' || char === "'") {
4754
+ const start = i;
4755
+ const quote = char;
4756
+ i++;
4757
+ while (i < input.length) {
4758
+ if (input[i] === "\\" && i + 1 < input.length) {
4759
+ i += 2;
4760
+ continue;
4761
+ }
4762
+ if (input[i] === quote) {
4763
+ i++;
4764
+ rawTokens.push({
4765
+ type: "STRING",
4766
+ text: input.slice(start, i),
4767
+ start,
4768
+ end: i
4769
+ });
4770
+ break;
4771
+ }
4772
+ i++;
4773
+ }
4774
+ if (rawTokens[rawTokens.length - 1]?.start !== start) {
4775
+ return finalizeTokens(input, rawTokens);
4776
+ }
4777
+ continue;
4778
+ }
4779
+ if (/[0-9]/.test(char) || char === "-" && i + 1 < input.length && /[0-9]/.test(input[i + 1]) && canStartNegativeNumber(rawTokens)) {
4780
+ const start = i;
4781
+ if (input[i] === "-") {
4782
+ i++;
4783
+ }
4784
+ while (i < input.length && /[0-9]/.test(input[i])) {
4785
+ i++;
4786
+ }
4787
+ if (i < input.length && input[i] === ".") {
4788
+ i++;
4789
+ while (i < input.length && /[0-9]/.test(input[i])) {
4790
+ i++;
4791
+ }
4792
+ }
4793
+ rawTokens.push({
4794
+ type: "NUMBER",
4795
+ text: input.slice(start, i),
4796
+ start,
4797
+ end: i
4798
+ });
4799
+ continue;
4800
+ }
4801
+ if (/[a-zA-Z_]/.test(char)) {
4802
+ const start = i;
4803
+ i++;
4804
+ while (i < input.length && /[a-zA-Z0-9_.]/.test(input[i])) {
4805
+ i++;
4806
+ }
4807
+ const text2 = input.slice(start, i);
4808
+ rawTokens.push({
4809
+ type: KEYWORDS[text2] ?? "IDENT",
4810
+ text: text2,
4811
+ start,
4812
+ end: i
4813
+ });
4814
+ continue;
4815
+ }
4816
+ if (i + 1 < input.length) {
4817
+ const twoChar = input.slice(i, i + 2);
4818
+ if (twoChar === "==") {
4819
+ rawTokens.push({ type: "EQ", text: twoChar, start: i, end: i + 2 });
4820
+ i += 2;
4821
+ continue;
4822
+ }
4823
+ if (twoChar === "!=") {
4824
+ rawTokens.push({ type: "NEQ", text: twoChar, start: i, end: i + 2 });
4825
+ i += 2;
4826
+ continue;
4827
+ }
4828
+ if (twoChar === "<=") {
4829
+ rawTokens.push({ type: "LTE", text: twoChar, start: i, end: i + 2 });
4830
+ i += 2;
4831
+ continue;
4832
+ }
4833
+ if (twoChar === ">=") {
4834
+ rawTokens.push({ type: "GTE", text: twoChar, start: i, end: i + 2 });
4835
+ i += 2;
4836
+ continue;
4837
+ }
4838
+ }
4839
+ if (char === "<") {
4840
+ rawTokens.push({ type: "LT", text: char, start: i, end: i + 1 });
4841
+ i++;
4842
+ continue;
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;
4853
+ }
4854
+ if (char === ")") {
4855
+ rawTokens.push({ type: "RPAREN", text: char, start: i, end: i + 1 });
4856
+ i++;
4857
+ continue;
4858
+ }
4859
+ return finalizeTokens(input, rawTokens);
4860
+ }
4861
+ return finalizeTokens(input, rawTokens);
4862
+ }
4863
+
4864
+ // src/ui/plugin/jinjaIfBlockPlugin.tsx
4865
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
4866
+ var PLACEHOLDER_TEXT = "Describe what AI agent should do when this condition is met";
4867
+ var CONDITION_PLACEHOLDER = "Write a condition in natural language";
4868
+ var STYLE_ID = "ab-jinja-if-block-styles";
4869
+ var JINJA_STYLES = `
4870
+ .jinja-if-block {
4871
+ margin: 8px 0;
4872
+ }
4873
+
4874
+ .jinja-branch {
4875
+ position: relative;
4876
+ display: grid;
4877
+ grid-template-areas:
4878
+ "header"
4879
+ "body"
4880
+ "footer";
4881
+ gap: 0;
4882
+ }
4883
+
4884
+ .jinja-branch-controls {
4885
+ display: contents;
4886
+ }
4887
+
4888
+ .jinja-branch-header {
4889
+ grid-area: header;
4890
+ display: flex;
4891
+ align-items: center;
4892
+ gap: 12px;
4893
+ padding: 8px;
4894
+ border: 1px solid #E0E0E0;
4895
+ border-radius: 4px;
4896
+ background: #FFFFFF;
4897
+ }
4898
+
4899
+ .jinja-branch-badge {
4900
+ display: inline-flex;
4901
+ align-items: center;
4902
+ justify-content: center;
4903
+ height: 32px;
4904
+ padding: 0 8px;
4905
+ border-radius: 4px;
4906
+ font-family: "Roboto Mono", monospace;
4907
+ font-size: 13px;
4908
+ font-weight: 700;
4909
+ line-height: 20px;
4910
+ letter-spacing: -0.3px;
4911
+ white-space: nowrap;
4912
+ flex-shrink: 0;
4913
+ }
4914
+
4915
+ .jinja-branch-badge-if,
4916
+ .jinja-branch-badge-elif {
4917
+ background: #E7F1FF;
4918
+ color: #0D0D0D;
4919
+ }
4920
+
4921
+ .jinja-branch-badge-else {
4922
+ background: #F7F7F7;
4923
+ color: #424242;
4924
+ }
4925
+
4926
+ .jinja-branch-condition {
4927
+ flex: 1;
4928
+ min-width: 0;
4929
+ display: flex;
4930
+ flex-wrap: wrap;
4931
+ gap: 8px;
4932
+ align-items: center;
4933
+ width: 100%;
4934
+ padding: 0;
4935
+ margin: 0;
4936
+ background: transparent;
4937
+ border: none;
4938
+ color: #0D0D0D;
4939
+ cursor: text;
4940
+ text-align: left;
4941
+ font-family: "Roboto Mono", monospace;
4942
+ font-size: 13px;
4943
+ line-height: 20px;
4944
+ letter-spacing: -0.3px;
4945
+ white-space: nowrap;
4946
+ }
4947
+
4948
+ .jinja-branch-condition:focus-visible,
4949
+ .jinja-ghost-btn:focus-visible,
4950
+ .jinja-popup-item:focus-visible {
4951
+ outline: 2px solid rgba(98, 16, 204, 0.28);
4952
+ outline-offset: 2px;
4953
+ }
4954
+
4955
+ .jinja-condition-placeholder {
4956
+ color: #A6A6A6;
4957
+ font-family: "Roboto Mono", monospace;
4958
+ font-size: 13px;
4959
+ line-height: 20px;
4960
+ letter-spacing: -0.3px;
4961
+ }
4962
+
4963
+ .jinja-token-variable {
4964
+ color: #4141B2;
4965
+ }
4966
+
4967
+ .jinja-token-operator {
4968
+ color: #858585;
4969
+ }
4970
+
4971
+ .jinja-token-value {
4972
+ color: #0D0D0D;
4973
+ }
4974
+
4975
+ .jinja-token-punctuation {
4976
+ color: #858585;
4977
+ }
4978
+
4979
+ .jinja-otherwise {
4980
+ flex: 1;
4981
+ color: #858585;
4982
+ font-family: "Roboto Mono", monospace;
4983
+ font-size: 13px;
4984
+ line-height: 20px;
4985
+ letter-spacing: -0.3px;
4986
+ }
4987
+
4988
+ .jinja-branch-actions,
4989
+ .jinja-add-footer-actions {
4990
+ position: relative;
4991
+ display: inline-flex;
4992
+ align-items: center;
4993
+ }
4994
+
4995
+ .jinja-branch-body {
4996
+ grid-area: body;
4997
+ display: flex;
4998
+ gap: 12px;
4999
+ padding: 0 8px;
5000
+ min-height: 52px;
5001
+ }
5002
+
5003
+ .jinja-branch-divider-col {
5004
+ width: 32px;
5005
+ flex-shrink: 0;
5006
+ display: flex;
5007
+ align-items: stretch;
5008
+ justify-content: center;
5009
+ }
5010
+
5011
+ .jinja-branch-divider {
5012
+ width: 1px;
5013
+ background: #E0E0E0;
5014
+ }
5015
+
5016
+ /* L-shaped divider for the last branch (ELSE) */
5017
+ .jinja-branch-last .jinja-branch-divider-col {
5018
+ align-items: flex-start;
5019
+ padding-left: 16px;
5020
+ }
5021
+
5022
+ .jinja-branch-last .jinja-branch-divider {
5023
+ width: 16px;
5024
+ height: 28px;
5025
+ background: none;
5026
+ border-left: 1px solid #E0E0E0;
5027
+ border-bottom: 1px solid #E0E0E0;
5028
+ border-bottom-left-radius: 8px;
5029
+ }
5030
+
5031
+ .jinja-branch-content {
5032
+ position: relative;
5033
+ flex: 1;
5034
+ min-width: 0;
5035
+ padding: 16px 0;
5036
+ }
5037
+
5038
+ .jinja-branch-content.jinja-branch-content-empty::before {
5039
+ content: attr(data-placeholder);
5040
+ position: absolute;
5041
+ top: 16px;
5042
+ left: 0;
5043
+ color: #A6A6A6;
5044
+ font-size: 14px;
5045
+ line-height: 20px;
5046
+ pointer-events: none;
5047
+ }
5048
+
5049
+ .jinja-add-footer {
5050
+ grid-area: footer;
5051
+ display: flex;
5052
+ padding: 8px;
5053
+ }
5054
+
5055
+ .jinja-add-footer .jinja-add-footer-col {
5056
+ width: 32px;
5057
+ flex-shrink: 0;
5058
+ display: flex;
5059
+ align-items: center;
5060
+ justify-content: center;
5061
+ }
5062
+
5063
+ .jinja-ghost-btn {
5064
+ width: 24px;
5065
+ height: 24px;
5066
+ display: inline-flex;
5067
+ align-items: center;
5068
+ justify-content: center;
5069
+ padding: 0;
5070
+ border: none;
5071
+ border-radius: 4px;
5072
+ background: transparent;
5073
+ color: #858585;
5074
+ cursor: pointer;
5075
+ }
5076
+
5077
+ .jinja-ghost-btn:hover:not(:disabled) {
5078
+ background: rgba(13, 13, 13, 0.04);
5079
+ }
5080
+
5081
+ .jinja-ghost-btn:disabled {
5082
+ opacity: 0.45;
5083
+ cursor: default;
5084
+ }
5085
+
5086
+ .jinja-popup-menu {
5087
+ position: absolute;
5088
+ z-index: 20;
5089
+ min-width: 200px;
5090
+ padding: 8px 0;
5091
+ background: #FFFFFF;
5092
+ border-radius: 4px;
5093
+ box-shadow: 0px 8px 10px 0px rgba(13,13,13,0.12), 0px 3px 14px 0px rgba(13,13,13,0.08), 0px 3px 5px 0px rgba(13,13,13,0.04);
5094
+ }
5095
+
5096
+ .jinja-popup-item {
5097
+ width: 100%;
5098
+ display: block;
5099
+ padding: 6px 16px;
5100
+ border: none;
5101
+ background: transparent;
5102
+ color: #0D0D0D;
5103
+ text-align: left;
5104
+ cursor: pointer;
5105
+ font-size: 14px;
5106
+ line-height: 20px;
5107
+ letter-spacing: -0.1px;
5108
+ }
5109
+
5110
+ .jinja-popup-item:hover:not(:disabled) {
5111
+ background: rgba(13, 13, 13, 0.04);
5112
+ }
5113
+
5114
+ .jinja-popup-item:disabled {
5115
+ color: #A6A6A6;
5116
+ cursor: default;
5117
+ }
5118
+
5119
+ .jinja-condition-input {
5120
+ width: 100%;
5121
+ min-width: 0;
5122
+ padding: 0;
5123
+ margin: 0;
5124
+ border: none;
5125
+ outline: none;
5126
+ background: transparent;
5127
+ color: #0D0D0D;
5128
+ caret-color: #6210CC;
5129
+ font-family: "Roboto Mono", monospace;
5130
+ font-size: 13px;
5131
+ line-height: 20px;
5132
+ letter-spacing: -0.3px;
5133
+ }
5134
+
5135
+ .jinja-condition-input::placeholder {
5136
+ color: #A6A6A6;
5137
+ }
5138
+ `;
5139
+ var StyleManager = class {
5140
+ static refCount = 0;
5141
+ static styleEl = null;
5142
+ static acquire() {
5143
+ if (this.refCount++ > 0) return;
5144
+ const existing = document.getElementById(STYLE_ID);
5145
+ if (existing instanceof HTMLStyleElement) {
5146
+ this.styleEl = existing;
5147
+ return;
5148
+ }
5149
+ this.styleEl = document.createElement("style");
5150
+ this.styleEl.id = STYLE_ID;
5151
+ this.styleEl.textContent = JINJA_STYLES;
5152
+ document.head.appendChild(this.styleEl);
5153
+ }
5154
+ static release() {
5155
+ if (this.refCount === 0) return;
5156
+ if (--this.refCount > 0) return;
5157
+ if (this.styleEl?.parentNode) {
5158
+ this.styleEl.remove();
5159
+ }
5160
+ this.styleEl = null;
5161
+ }
5162
+ };
5163
+ function getBranchContext(view, nodePos) {
5164
+ try {
5165
+ const { doc: doc2 } = view.state;
5166
+ const resolved = doc2.resolve(nodePos);
5167
+ let blockDepth = -1;
5168
+ for (let depth = resolved.depth; depth >= 0; depth--) {
5169
+ if (resolved.node(depth).type.name === "jinjaIfBlock") {
5170
+ blockDepth = depth;
5171
+ break;
5172
+ }
5173
+ }
5174
+ if (blockDepth < 0) {
5175
+ return null;
5176
+ }
5177
+ const blockNode = resolved.node(blockDepth);
5178
+ const blockPos = blockDepth === 0 ? 0 : resolved.before(blockDepth);
5179
+ let branchOffset = blockPos + 1;
5180
+ for (let index = 0; index < blockNode.childCount; index++) {
5181
+ const branchNode = blockNode.child(index);
5182
+ if (branchOffset === nodePos) {
5183
+ return {
5184
+ blockNode,
5185
+ blockPos,
5186
+ branchNode,
5187
+ branchPos: branchOffset,
5188
+ branchIndex: index
5189
+ };
5190
+ }
5191
+ branchOffset += branchNode.nodeSize;
5192
+ }
5193
+ } catch {
5194
+ return null;
5195
+ }
5196
+ return null;
5197
+ }
5198
+ function isBranchBodyEmpty(node) {
5199
+ if (node.childCount !== 1) return false;
5200
+ const firstChild = node.firstChild;
5201
+ return firstChild?.type.name === "paragraph" && firstChild.content.size === 0;
5202
+ }
5203
+ function isBranchNode(node) {
5204
+ return node?.type.name === "jinjaIfBranch";
5205
+ }
5206
+ function createBranchNode(schema, branchType, condition) {
5207
+ const branchNodeType = schema.nodes.jinjaIfBranch;
5208
+ const paragraphNode = schema.nodes.paragraph?.createAndFill();
5209
+ if (!branchNodeType || !paragraphNode) {
5210
+ return null;
5211
+ }
5212
+ return branchNodeType.create(
5213
+ {
5214
+ branchType,
5215
+ condition: branchType === "else" ? "" : condition
5216
+ },
5217
+ paragraphNode
5218
+ );
5219
+ }
5220
+ function getLastBranchOfBlock(view, nodePos) {
5221
+ const context = getBranchContext(view, nodePos);
5222
+ if (!context || context.blockNode.childCount === 0) {
5223
+ return null;
5224
+ }
5225
+ const index = context.blockNode.childCount - 1;
5226
+ return {
5227
+ index,
5228
+ node: context.blockNode.child(index)
5229
+ };
5230
+ }
5231
+ function getLastNonElseBranchIndex(view, nodePos) {
5232
+ const context = getBranchContext(view, nodePos);
5233
+ if (!context) return -1;
5234
+ for (let i = context.blockNode.childCount - 1; i >= 0; i--) {
5235
+ if (context.blockNode.child(i).attrs.branchType !== "else") return i;
5236
+ }
5237
+ return -1;
5238
+ }
5239
+ function getElseBranch(view, nodePos) {
5240
+ const context = getBranchContext(view, nodePos);
5241
+ if (!context) return false;
5242
+ for (let index = 0; index < context.blockNode.childCount; index++) {
5243
+ if (context.blockNode.child(index).attrs.branchType === "else") {
5244
+ return true;
5245
+ }
5246
+ }
5247
+ return false;
5248
+ }
5249
+ function deleteBlock(view, blockPos) {
5250
+ const blockNode = view.state.doc.nodeAt(blockPos);
5251
+ if (blockNode?.type.name !== "jinjaIfBlock") {
5252
+ return false;
5253
+ }
5254
+ view.dispatch(view.state.tr.delete(blockPos, blockPos + blockNode.nodeSize).scrollIntoView());
5255
+ view.focus();
5256
+ return true;
5257
+ }
5258
+ function insertNewBranch(view, nodePos, branchType, condition = "") {
5259
+ const context = getBranchContext(view, nodePos);
5260
+ if (!context) return false;
5261
+ if (context.branchNode.attrs.branchType === "else") return false;
5262
+ const lastBranch = getLastBranchOfBlock(view, nodePos);
5263
+ if (!lastBranch) return false;
5264
+ if (branchType === "else") {
5265
+ if (getElseBranch(view, nodePos)) return false;
5266
+ if (lastBranch.index !== context.branchIndex) return false;
5267
+ }
5268
+ const newBranch = createBranchNode(view.state.schema, branchType, condition);
5269
+ if (!newBranch) return false;
5270
+ const insertPos = context.branchPos + context.branchNode.nodeSize;
5271
+ view.dispatch(view.state.tr.insert(insertPos, newBranch).scrollIntoView());
5272
+ view.focus();
5273
+ return true;
5274
+ }
5275
+ function deleteBranch(view, nodePos, branchType) {
5276
+ const context = getBranchContext(view, nodePos);
5277
+ if (!context) return false;
5278
+ if (branchType === "if") {
5279
+ if (context.blockNode.childCount === 1) {
5280
+ return deleteBlock(view, context.blockPos);
5281
+ }
5282
+ const nextBranch = context.blockNode.child(context.branchIndex + 1);
5283
+ const promotedCondition = typeof nextBranch.attrs.condition === "string" ? nextBranch.attrs.condition : "";
5284
+ const tr = view.state.tr.delete(context.branchPos, context.branchPos + context.branchNode.nodeSize);
5285
+ tr.setNodeMarkup(context.branchPos, void 0, {
5286
+ ...nextBranch.attrs,
5287
+ branchType: "if",
5288
+ condition: promotedCondition
5289
+ });
5290
+ view.dispatch(tr.scrollIntoView());
5291
+ view.focus();
5292
+ return true;
5293
+ }
5294
+ view.dispatch(view.state.tr.delete(context.branchPos, context.branchPos + context.branchNode.nodeSize).scrollIntoView());
5295
+ view.focus();
5296
+ return true;
5297
+ }
5298
+ function renderCondition(condition) {
5299
+ const tokens = tokenizeCondition(condition);
5300
+ const rendered = [];
5301
+ for (let index = 0; index < tokens.length; index++) {
5302
+ const token = tokens[index];
5303
+ rendered.push(
5304
+ /* @__PURE__ */ jsx6("span", { className: `jinja-token-${token.category}`, children: token.text }, `token-${index}-${token.start}`)
5305
+ );
5306
+ }
5307
+ return rendered;
5308
+ }
5309
+ function ConditionDisplay({
5310
+ branchType,
5311
+ condition,
5312
+ editable = true,
5313
+ onConditionChange
5314
+ }) {
5315
+ const [isEditing, setIsEditing] = useState2(false);
5316
+ const [draftValue, setDraftValue] = useState2(condition);
5317
+ const inputRef = useRef2(null);
5318
+ useEffect2(() => {
5319
+ if (!isEditing) {
5320
+ setDraftValue(condition);
5321
+ }
5322
+ }, [condition, isEditing]);
5323
+ useEffect2(() => {
5324
+ if (isEditing) {
5325
+ inputRef.current?.focus();
5326
+ inputRef.current?.select();
5327
+ }
5328
+ }, [isEditing]);
5329
+ if (branchType === "else") {
5330
+ return /* @__PURE__ */ jsx6("span", { className: "jinja-otherwise", children: "Otherwise" });
5331
+ }
5332
+ const commit = () => {
5333
+ setIsEditing(false);
5334
+ if (draftValue !== condition) {
5335
+ onConditionChange(draftValue);
5336
+ }
5337
+ };
5338
+ const cancel = () => {
5339
+ setDraftValue(condition);
5340
+ setIsEditing(false);
5341
+ };
5342
+ if (isEditing && editable) {
5343
+ return /* @__PURE__ */ jsx6(
5344
+ "input",
5345
+ {
5346
+ ref: inputRef,
5347
+ className: "jinja-condition-input",
5348
+ value: draftValue,
5349
+ placeholder: CONDITION_PLACEHOLDER,
5350
+ onChange: (event) => setDraftValue(event.target.value),
5351
+ onBlur: commit,
5352
+ onKeyDown: (event) => {
5353
+ if (event.key === "Enter") {
5354
+ event.preventDefault();
5355
+ commit();
5356
+ }
5357
+ if (event.key === "Escape") {
5358
+ event.preventDefault();
5359
+ cancel();
5360
+ }
5361
+ }
5362
+ }
5363
+ );
5364
+ }
5365
+ return /* @__PURE__ */ jsx6(
5366
+ "button",
5367
+ {
5368
+ type: "button",
5369
+ className: "jinja-branch-condition",
5370
+ onMouseDown: (event) => event.preventDefault(),
5371
+ onClick: () => {
5372
+ if (editable) {
5373
+ setDraftValue(condition);
5374
+ setIsEditing(true);
5375
+ }
5376
+ },
5377
+ children: condition.length > 0 ? renderCondition(condition) : /* @__PURE__ */ jsx6("span", { className: "jinja-condition-placeholder", children: CONDITION_PLACEHOLDER })
5378
+ }
5379
+ );
5380
+ }
5381
+ function BranchPopupMenu({ items, position }) {
5382
+ return /* @__PURE__ */ jsx6(
5383
+ "div",
5384
+ {
5385
+ className: "jinja-popup-menu",
5386
+ style: {
5387
+ top: position.top,
5388
+ left: position.left,
5389
+ right: position.right
5390
+ },
5391
+ role: "menu",
5392
+ children: items.map((item) => /* @__PURE__ */ jsx6(
5393
+ "button",
5394
+ {
5395
+ type: "button",
5396
+ className: "jinja-popup-item",
5397
+ disabled: item.disabled,
5398
+ onMouseDown: (event) => event.preventDefault(),
5399
+ onClick: () => {
5400
+ if (!item.disabled) {
5401
+ item.onSelect();
5402
+ }
5403
+ },
5404
+ role: "menuitem",
5405
+ children: item.label
5406
+ },
5407
+ item.label
5408
+ ))
5409
+ }
5410
+ );
5411
+ }
5412
+ function JinjaBranchHeader({
5413
+ branchType,
5414
+ condition,
5415
+ editable = true,
5416
+ onConditionChange,
5417
+ onDelete,
5418
+ onAddBranch,
5419
+ isLastBranch,
5420
+ hasElseBranch
5421
+ }) {
5422
+ const [menuSource, setMenuSource] = useState2(null);
5423
+ const kebabRef = useRef2(null);
5424
+ const footerRef = useRef2(null);
5425
+ useEffect2(() => {
5426
+ if (!menuSource) return;
5427
+ const handlePointerDown = (event) => {
5428
+ const target = event.target;
5429
+ if (!target) return;
5430
+ if (kebabRef.current?.contains(target) || footerRef.current?.contains(target)) {
5431
+ return;
5432
+ }
5433
+ setMenuSource(null);
5434
+ };
5435
+ const handleKeyDown = (event) => {
5436
+ if (event.key === "Escape") {
5437
+ setMenuSource(null);
5438
+ }
5439
+ };
5440
+ document.addEventListener("pointerdown", handlePointerDown, true);
5441
+ document.addEventListener("keydown", handleKeyDown);
5442
+ return () => {
5443
+ document.removeEventListener("pointerdown", handlePointerDown, true);
5444
+ document.removeEventListener("keydown", handleKeyDown);
5445
+ };
5446
+ }, [menuSource]);
5447
+ const menuItems = [];
5448
+ const isOnlyIfBranch = branchType === "if" && isLastBranch && !hasElseBranch;
5449
+ const canAddElif = editable && branchType !== "else" && (branchType === "elif" || isLastBranch);
5450
+ const canAddElse = editable && branchType !== "else" && isLastBranch && !hasElseBranch;
5451
+ if (canAddElif) {
5452
+ menuItems.push({
5453
+ label: "Else if",
5454
+ onSelect: () => onAddBranch("elif")
5455
+ });
5456
+ }
5457
+ if (canAddElse) {
5458
+ menuItems.push({
5459
+ label: "Else",
5460
+ onSelect: () => onAddBranch("else")
5461
+ });
5462
+ }
5463
+ if (editable) {
5464
+ menuItems.push({
5465
+ label: isOnlyIfBranch ? "Delete all" : "Delete",
5466
+ onSelect: onDelete
5467
+ });
5468
+ }
5469
+ const canOpenFooterMenu = editable && isLastBranch && branchType !== "else";
5470
+ const isMenuOpen = (source) => menuSource === source && menuItems.length > 0;
5471
+ return /* @__PURE__ */ jsxs5(Fragment2, { children: [
5472
+ /* @__PURE__ */ jsxs5("div", { className: "jinja-branch-header", children: [
5473
+ /* @__PURE__ */ jsx6("span", { className: `jinja-branch-badge jinja-branch-badge-${branchType}`, children: branchType === "elif" ? "ELSE IF" : branchType.toUpperCase() }),
5474
+ /* @__PURE__ */ jsx6(
5475
+ ConditionDisplay,
5476
+ {
5477
+ branchType,
5478
+ condition,
5479
+ editable,
5480
+ onConditionChange
5481
+ }
5482
+ ),
5483
+ editable ? /* @__PURE__ */ jsxs5("div", { className: "jinja-branch-actions", ref: kebabRef, children: [
5484
+ /* @__PURE__ */ jsx6(
5485
+ "button",
5486
+ {
5487
+ type: "button",
5488
+ className: "jinja-ghost-btn",
5489
+ "aria-label": `${branchType} branch options`,
5490
+ onMouseDown: (event) => event.preventDefault(),
5491
+ onClick: () => {
5492
+ if (menuItems.length > 0) {
5493
+ setMenuSource((current) => current === "kebab" ? null : "kebab");
5494
+ }
5495
+ },
5496
+ children: /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "currentColor", children: [
5497
+ /* @__PURE__ */ jsx6("circle", { cx: "8", cy: "3", r: "1.5" }),
5498
+ /* @__PURE__ */ jsx6("circle", { cx: "8", cy: "8", r: "1.5" }),
5499
+ /* @__PURE__ */ jsx6("circle", { cx: "8", cy: "13", r: "1.5" })
5500
+ ] })
5501
+ }
5502
+ ),
5503
+ isMenuOpen("kebab") ? /* @__PURE__ */ jsx6(
5504
+ BranchPopupMenu,
5505
+ {
5506
+ position: { top: 28, right: 0 },
5507
+ items: menuItems.map((item) => ({
5508
+ ...item,
5509
+ onSelect: () => {
5510
+ setMenuSource(null);
5511
+ item.onSelect();
5512
+ }
5513
+ }))
5514
+ }
5515
+ ) : null
5516
+ ] }) : null
5517
+ ] }),
5518
+ isLastBranch ? /* @__PURE__ */ jsx6("div", { className: "jinja-add-footer", children: /* @__PURE__ */ jsx6("div", { className: "jinja-add-footer-col", children: /* @__PURE__ */ jsxs5("div", { className: "jinja-add-footer-actions", ref: footerRef, children: [
5519
+ /* @__PURE__ */ jsx6(
5520
+ "button",
5521
+ {
5522
+ type: "button",
5523
+ className: "jinja-ghost-btn",
5524
+ "aria-label": "Add branch",
5525
+ disabled: !canOpenFooterMenu,
5526
+ onMouseDown: (event) => event.preventDefault(),
5527
+ onClick: () => {
5528
+ if (canOpenFooterMenu && menuItems.length > 0) {
5529
+ setMenuSource((current) => current === "footer" ? null : "footer");
5530
+ }
5531
+ },
5532
+ children: /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
5533
+ /* @__PURE__ */ jsx6("line", { x1: "8", y1: "3", x2: "8", y2: "13" }),
5534
+ /* @__PURE__ */ jsx6("line", { x1: "3", y1: "8", x2: "13", y2: "8" })
5535
+ ] })
5536
+ }
5537
+ ),
5538
+ isMenuOpen("footer") ? /* @__PURE__ */ jsx6(
5539
+ BranchPopupMenu,
5540
+ {
5541
+ position: { top: 28, left: 0 },
5542
+ items: menuItems.filter((item) => item.label === "Else if" || item.label === "Else").map((item) => ({
5543
+ ...item,
5544
+ onSelect: () => {
5545
+ setMenuSource(null);
5546
+ item.onSelect();
5547
+ }
5548
+ }))
5549
+ }
5550
+ ) : null
5551
+ ] }) }) }) : null
5552
+ ] });
5553
+ }
5554
+ var JinjaIfBlockView = class {
5555
+ dom;
5556
+ contentDOM;
5557
+ constructor() {
5558
+ this.dom = document.createElement("div");
5559
+ this.dom.className = "jinja-if-block";
5560
+ this.dom.setAttribute("data-jinja-if-block", "");
5561
+ this.contentDOM = this.dom;
5562
+ requestAnimationFrame(() => this.notifyChildren());
5563
+ }
5564
+ notifyChildren() {
5565
+ this.dom.querySelectorAll(".jinja-branch").forEach((el) => {
5566
+ el.dispatchEvent(new CustomEvent("jinja-siblings-changed", { bubbles: false }));
5567
+ });
5568
+ }
5569
+ update(node) {
5570
+ if (node.type.name !== "jinjaIfBlock") return false;
5571
+ requestAnimationFrame(() => this.notifyChildren());
5572
+ return true;
5573
+ }
5574
+ };
5575
+ var JinjaIfBranchView = class {
5576
+ dom;
5577
+ contentDOM;
5578
+ headerContainer;
5579
+ root;
5580
+ node;
5581
+ view;
5582
+ getPos;
5583
+ constructor(node, view, getPos) {
5584
+ this.node = node;
5585
+ this.view = view;
5586
+ this.getPos = getPos;
5587
+ StyleManager.acquire();
5588
+ this.dom = document.createElement("div");
5589
+ this.headerContainer = document.createElement("div");
5590
+ const body = document.createElement("div");
5591
+ const dividerColumn = document.createElement("div");
5592
+ const divider = document.createElement("div");
5593
+ this.dom.appendChild(this.headerContainer);
5594
+ this.dom.appendChild(body);
5595
+ this.headerContainer.className = "jinja-branch-controls";
5596
+ this.headerContainer.contentEditable = "false";
5597
+ body.className = "jinja-branch-body";
5598
+ dividerColumn.className = "jinja-branch-divider-col";
5599
+ dividerColumn.contentEditable = "false";
5600
+ divider.className = "jinja-branch-divider";
5601
+ dividerColumn.appendChild(divider);
5602
+ this.contentDOM = document.createElement("div");
5603
+ this.contentDOM.className = "jinja-branch-content";
5604
+ this.contentDOM.setAttribute("data-placeholder", PLACEHOLDER_TEXT);
5605
+ body.appendChild(dividerColumn);
5606
+ body.appendChild(this.contentDOM);
5607
+ this.root = createRoot2(this.headerContainer);
5608
+ this._onSiblingsChanged = () => this.render();
5609
+ this.dom.addEventListener("jinja-siblings-changed", this._onSiblingsChanged);
5610
+ this.syncDOM();
5611
+ this.render();
5612
+ }
5613
+ _onSiblingsChanged = null;
5614
+ syncDOM() {
5615
+ const branchType = this.node.attrs.branchType;
5616
+ this.dom.className = `jinja-branch jinja-branch-${branchType}`;
5617
+ this.dom.setAttribute("data-jinja-branch", "");
5618
+ this.dom.setAttribute("data-branch-type", branchType);
5619
+ this.dom.setAttribute("data-condition", String(this.node.attrs.condition ?? ""));
5620
+ this.contentDOM.classList.toggle("jinja-branch-content-empty", isBranchBodyEmpty(this.node));
5621
+ }
5622
+ render() {
5623
+ const nodePos = this.getPos();
5624
+ if (nodePos == null) return;
5625
+ const lastBranch = getLastBranchOfBlock(this.view, nodePos);
5626
+ const context = getBranchContext(this.view, nodePos);
5627
+ if (!lastBranch || !context) return;
5628
+ const branchType = this.node.attrs.branchType;
5629
+ const condition = String(this.node.attrs.condition ?? "");
5630
+ const isLast = lastBranch.index === context.branchIndex;
5631
+ const lastNonElseIdx = getLastNonElseBranchIndex(this.view, nodePos);
5632
+ const showAddButton = context.branchIndex === lastNonElseIdx;
5633
+ this.dom.classList.toggle("jinja-branch-last", isLast);
5634
+ this.root.render(
5635
+ /* @__PURE__ */ jsx6(
5636
+ JinjaBranchHeader,
5637
+ {
5638
+ branchType,
5639
+ condition,
5640
+ editable: this.view.editable,
5641
+ isLastBranch: showAddButton,
5642
+ hasElseBranch: getElseBranch(this.view, nodePos),
5643
+ onConditionChange: (value) => {
5644
+ const currentPos = this.getPos();
5645
+ if (currentPos == null) return;
5646
+ this.view.dispatch(
5647
+ this.view.state.tr.setNodeMarkup(currentPos, void 0, {
5648
+ ...this.node.attrs,
5649
+ condition: value
5650
+ })
5651
+ );
5652
+ this.view.focus();
5653
+ },
5654
+ onDelete: () => {
5655
+ const currentPos = this.getPos();
5656
+ if (currentPos == null) return;
5657
+ deleteBranch(this.view, currentPos, branchType);
5658
+ },
5659
+ onAddBranch: (nextBranchType) => {
5660
+ const currentPos = this.getPos();
5661
+ if (currentPos == null) return;
5662
+ insertNewBranch(this.view, currentPos, nextBranchType, "");
5663
+ }
5664
+ }
5665
+ )
5666
+ );
5667
+ }
5668
+ update(node) {
5669
+ if (!isBranchNode(node)) return false;
5670
+ this.node = node;
5671
+ this.syncDOM();
5672
+ this.render();
5673
+ return true;
5674
+ }
5675
+ stopEvent(event) {
5676
+ const target = event.target;
5677
+ return target != null && this.headerContainer.contains(target);
5678
+ }
5679
+ ignoreMutation(mutation) {
5680
+ return this.headerContainer.contains(mutation.target);
5681
+ }
5682
+ destroy() {
5683
+ if (this._onSiblingsChanged) {
5684
+ this.dom.removeEventListener("jinja-siblings-changed", this._onSiblingsChanged);
5685
+ }
5686
+ StyleManager.release();
5687
+ setTimeout(() => this.root.unmount(), 0);
5688
+ }
5689
+ };
5690
+ function createJinjaIfBlockPlugin() {
5691
+ return {
5692
+ name: "jinjaIfBlock",
5693
+ nodeViews: () => ({
5694
+ jinjaIfBlock: (() => new JinjaIfBlockView()),
5695
+ jinjaIfBranch: ((node, view, getPos) => new JinjaIfBranchView(node, view, getPos))
5696
+ })
5697
+ };
5698
+ }
5699
+
4491
5700
  // src/ui/plugin/linkPlugin.ts
4492
5701
  import { InputRule as InputRule2, inputRules as inputRules2 } from "prosemirror-inputrules";
4493
- import { Plugin as Plugin4, PluginKey as PluginKey4, TextSelection as TextSelection2 } from "prosemirror-state";
5702
+ import { Plugin as Plugin5, PluginKey as PluginKey5, TextSelection as TextSelection2 } from "prosemirror-state";
4494
5703
  var LINK_INPUT_RE = /\[([^\]]*)\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
4495
5704
  var LINK_FULL_RE = /^\[([^\]]*)\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
4496
- var linkEditKey = new PluginKey4("linkEdit");
5705
+ var linkEditKey = new PluginKey5("linkEdit");
4497
5706
  function getMarkRange($pos, type) {
4498
5707
  const { parentOffset } = $pos;
4499
5708
  let child = $pos.parent.childAfter(parentOffset);
@@ -4541,7 +5750,7 @@ function explodeLinkToRaw(state) {
4541
5750
  return tr;
4542
5751
  }
4543
5752
  function createLinkEditPlugin() {
4544
- return new Plugin4({
5753
+ return new Plugin5({
4545
5754
  key: linkEditKey,
4546
5755
  state: {
4547
5756
  init: () => ({ rawRange: null }),
@@ -4611,8 +5820,8 @@ function createLinkInputRule() {
4611
5820
  );
4612
5821
  }
4613
5822
  function createLinkClickPlugin() {
4614
- return new Plugin4({
4615
- key: new PluginKey4("linkClick"),
5823
+ return new Plugin5({
5824
+ key: new PluginKey5("linkClick"),
4616
5825
  props: {
4617
5826
  handleDOMEvents: {
4618
5827
  click: (_view, event) => {
@@ -4664,8 +5873,8 @@ function createLinkPlugin() {
4664
5873
  }
4665
5874
 
4666
5875
  // src/ui/plugin/dragHandlePlugin.ts
4667
- import { Plugin as Plugin5, PluginKey as PluginKey5 } from "prosemirror-state";
4668
- var PLUGIN_KEY = new PluginKey5("dragHandle");
5876
+ import { Plugin as Plugin6, PluginKey as PluginKey6 } from "prosemirror-state";
5877
+ var PLUGIN_KEY = new PluginKey6("dragHandle");
4669
5878
  var GRIP_SVG = `<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
4670
5879
  <circle cx="4" cy="2.5" r="1.2"/><circle cx="8" cy="2.5" r="1.2"/>
4671
5880
  <circle cx="4" cy="6" r="1.2"/><circle cx="8" cy="6" r="1.2"/>
@@ -4753,7 +5962,7 @@ var STYLES = (
4753
5962
  }
4754
5963
  `
4755
5964
  );
4756
- var StyleManager = class {
5965
+ var StyleManager2 = class {
4757
5966
  static refCount = 0;
4758
5967
  static sheet = null;
4759
5968
  static styleEl = null;
@@ -4880,7 +6089,7 @@ var DragHandleController = class {
4880
6089
  globalCleanup = null;
4881
6090
  constructor(view) {
4882
6091
  this.view = view;
4883
- StyleManager.acquire();
6092
+ StyleManager2.acquire();
4884
6093
  const parent = view.dom.parentNode;
4885
6094
  const pos = getComputedStyle(parent).position;
4886
6095
  if (pos === "static" || pos === "") parent.style.position = "relative";
@@ -4938,7 +6147,7 @@ var DragHandleController = class {
4938
6147
  }
4939
6148
  this.liveRegion.remove();
4940
6149
  this.layer.remove();
4941
- StyleManager.release();
6150
+ StyleManager2.release();
4942
6151
  }
4943
6152
  // ── Private: update pipeline ──
4944
6153
  scheduleUpdate() {
@@ -5289,7 +6498,7 @@ function createDragHandlePlugin() {
5289
6498
  plugins: () => {
5290
6499
  let controller = null;
5291
6500
  return [
5292
- new Plugin5({
6501
+ new Plugin6({
5293
6502
  key: PLUGIN_KEY,
5294
6503
  view(editorView) {
5295
6504
  controller = new DragHandleController(editorView);
@@ -5412,8 +6621,8 @@ function createTodoNodeViewPlugin() {
5412
6621
  }
5413
6622
 
5414
6623
  // src/ui/plugin/slashCommandPlugin.ts
5415
- import { Plugin as Plugin6, PluginKey as PluginKey6 } from "prosemirror-state";
5416
- var slashCommandKey = new PluginKey6("slashCommand");
6624
+ import { Plugin as Plugin7, PluginKey as PluginKey7 } from "prosemirror-state";
6625
+ var slashCommandKey = new PluginKey7("slashCommand");
5417
6626
  var TRIGGER_RE = /(?:^|\s)(\/[^\s]*)$/;
5418
6627
  function deriveState(state, dismissedFrom) {
5419
6628
  const { selection } = state;
@@ -5436,7 +6645,7 @@ function deriveState(state, dismissedFrom) {
5436
6645
  };
5437
6646
  }
5438
6647
  function createSlashCommandPlugin() {
5439
- const plugin = new Plugin6({
6648
+ const plugin = new Plugin7({
5440
6649
  key: slashCommandKey,
5441
6650
  state: {
5442
6651
  init(_, state) {
@@ -5480,9 +6689,9 @@ function createSlashCommandPlugin() {
5480
6689
  }
5481
6690
 
5482
6691
  // src/ui/components/SlashCommandMenu.tsx
5483
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
6692
+ import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
5484
6693
  import { createPortal } from "react-dom";
5485
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
6694
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
5486
6695
  function filterItems(items, query) {
5487
6696
  if (!query) return items;
5488
6697
  const q = query.toLowerCase();
@@ -5506,23 +6715,23 @@ var VPORT_MARGIN = 8;
5506
6715
  var MENU_WIDTH = 280;
5507
6716
  var MAX_MENU_H = 320;
5508
6717
  function SlashCommandMenu({ view, editorState, items }) {
5509
- const [selectedIndex, setSelectedIndex] = useState2(0);
5510
- const listRef = useRef2(null);
6718
+ const [selectedIndex, setSelectedIndex] = useState3(0);
6719
+ const listRef = useRef3(null);
5511
6720
  const pluginState = editorState ? slashCommandKey.getState(editorState) : void 0;
5512
6721
  const active = pluginState?.active ?? false;
5513
6722
  const range = pluginState?.range ?? null;
5514
6723
  const query = pluginState?.query ?? "";
5515
6724
  const filtered = filterItems(items, query);
5516
- useEffect2(() => {
6725
+ useEffect3(() => {
5517
6726
  setSelectedIndex(0);
5518
6727
  }, [query, active]);
5519
- useEffect2(() => {
6728
+ useEffect3(() => {
5520
6729
  const list = listRef.current;
5521
6730
  if (!list) return;
5522
6731
  const item = list.children[selectedIndex];
5523
6732
  item?.scrollIntoView({ block: "nearest" });
5524
6733
  }, [selectedIndex]);
5525
- useEffect2(() => {
6734
+ useEffect3(() => {
5526
6735
  if (!active || !view) return;
5527
6736
  const onKeyDown = (e) => {
5528
6737
  if (e.key === "ArrowDown") {
@@ -5558,7 +6767,7 @@ function SlashCommandMenu({ view, editorState, items }) {
5558
6767
  top = coords.top - MAX_MENU_H - 4;
5559
6768
  }
5560
6769
  return createPortal(
5561
- /* @__PURE__ */ jsx6(
6770
+ /* @__PURE__ */ jsx7(
5562
6771
  "div",
5563
6772
  {
5564
6773
  style: {
@@ -5575,7 +6784,7 @@ function SlashCommandMenu({ view, editorState, items }) {
5575
6784
  zIndex: 1100,
5576
6785
  animation: "ab-float-in 0.12s ease"
5577
6786
  },
5578
- children: /* @__PURE__ */ jsx6("div", { ref: listRef, children: filtered.length === 0 ? /* @__PURE__ */ jsx6(
6787
+ children: /* @__PURE__ */ jsx7("div", { ref: listRef, children: filtered.length === 0 ? /* @__PURE__ */ jsx7(
5579
6788
  "div",
5580
6789
  {
5581
6790
  style: {
@@ -5585,7 +6794,7 @@ function SlashCommandMenu({ view, editorState, items }) {
5585
6794
  },
5586
6795
  children: "No results"
5587
6796
  }
5588
- ) : filtered.map((item, i) => /* @__PURE__ */ jsx6(
6797
+ ) : filtered.map((item, i) => /* @__PURE__ */ jsx7(
5589
6798
  SlashMenuItem,
5590
6799
  {
5591
6800
  item,
@@ -5607,7 +6816,7 @@ function SlashCommandMenu({ view, editorState, items }) {
5607
6816
  );
5608
6817
  }
5609
6818
  function SlashMenuItem({ item, selected, onMouseEnter, onMouseDown }) {
5610
- return /* @__PURE__ */ jsxs5(
6819
+ return /* @__PURE__ */ jsxs6(
5611
6820
  "button",
5612
6821
  {
5613
6822
  style: {
@@ -5623,7 +6832,7 @@ function SlashMenuItem({ item, selected, onMouseEnter, onMouseDown }) {
5623
6832
  onMouseEnter,
5624
6833
  onMouseDown,
5625
6834
  children: [
5626
- item.icon !== void 0 && /* @__PURE__ */ jsx6(
6835
+ item.icon !== void 0 && /* @__PURE__ */ jsx7(
5627
6836
  "span",
5628
6837
  {
5629
6838
  style: {
@@ -5643,8 +6852,8 @@ function SlashMenuItem({ item, selected, onMouseEnter, onMouseDown }) {
5643
6852
  children: item.icon
5644
6853
  }
5645
6854
  ),
5646
- /* @__PURE__ */ jsxs5("span", { style: { display: "flex", flexDirection: "column", gap: 1, minWidth: 0 }, children: [
5647
- /* @__PURE__ */ jsx6(
6855
+ /* @__PURE__ */ jsxs6("span", { style: { display: "flex", flexDirection: "column", gap: 1, minWidth: 0 }, children: [
6856
+ /* @__PURE__ */ jsx7(
5648
6857
  "span",
5649
6858
  {
5650
6859
  style: {
@@ -5658,7 +6867,7 @@ function SlashMenuItem({ item, selected, onMouseEnter, onMouseDown }) {
5658
6867
  children: item.title
5659
6868
  }
5660
6869
  ),
5661
- item.description && /* @__PURE__ */ jsx6(
6870
+ item.description && /* @__PURE__ */ jsx7(
5662
6871
  "span",
5663
6872
  {
5664
6873
  style: {
@@ -5678,8 +6887,8 @@ function SlashMenuItem({ item, selected, onMouseEnter, onMouseDown }) {
5678
6887
  }
5679
6888
 
5680
6889
  // src/ui/components/JinjaTreeView.tsx
5681
- import { useState as useState3 } from "react";
5682
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
6890
+ import { useState as useState4 } from "react";
6891
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
5683
6892
  var BRANCH_COLORS = {
5684
6893
  if: "#6366f1",
5685
6894
  elif: "#f59e0b",
@@ -5699,21 +6908,21 @@ function BranchNode({
5699
6908
  const blockCount = branch.blockEndIndex - branch.blockStartIndex;
5700
6909
  const preview = getBlockPreview(doc2, branch.blockStartIndex, branch.blockEndIndex);
5701
6910
  const connector = isLast ? "\u2514\u2500" : "\u251C\u2500";
5702
- return /* @__PURE__ */ jsxs6("div", { className: "jinja-tree-branch", children: [
5703
- /* @__PURE__ */ jsxs6("span", { className: "jinja-tree-connector", children: [
6911
+ return /* @__PURE__ */ jsxs7("div", { className: "jinja-tree-branch", children: [
6912
+ /* @__PURE__ */ jsxs7("span", { className: "jinja-tree-connector", children: [
5704
6913
  connector,
5705
6914
  " "
5706
6915
  ] }),
5707
- /* @__PURE__ */ jsxs6("span", { className: "jinja-tree-badge", style: { color, borderColor: color }, children: [
6916
+ /* @__PURE__ */ jsxs7("span", { className: "jinja-tree-badge", style: { color, borderColor: color }, children: [
5708
6917
  "[",
5709
6918
  branch.type,
5710
6919
  "]"
5711
6920
  ] }),
5712
- branch.condition && /* @__PURE__ */ jsxs6("span", { className: "jinja-tree-condition", children: [
6921
+ branch.condition && /* @__PURE__ */ jsxs7("span", { className: "jinja-tree-condition", children: [
5713
6922
  " ",
5714
6923
  branch.condition
5715
6924
  ] }),
5716
- /* @__PURE__ */ jsxs6("span", { className: "jinja-tree-meta", children: [
6925
+ /* @__PURE__ */ jsxs7("span", { className: "jinja-tree-meta", children: [
5717
6926
  " ",
5718
6927
  blockCount,
5719
6928
  " block",
@@ -5723,23 +6932,23 @@ function BranchNode({
5723
6932
  ] });
5724
6933
  }
5725
6934
  function StructureNode({ structure, doc: doc2 }) {
5726
- const [expanded, setExpanded] = useState3(true);
6935
+ const [expanded, setExpanded] = useState4(true);
5727
6936
  const firstBranch = structure.branches[0];
5728
6937
  const label = firstBranch?.condition ? `if ${firstBranch.condition}` : "if";
5729
- return /* @__PURE__ */ jsxs6("div", { className: "jinja-tree-structure", children: [
5730
- /* @__PURE__ */ jsxs6(
6938
+ return /* @__PURE__ */ jsxs7("div", { className: "jinja-tree-structure", children: [
6939
+ /* @__PURE__ */ jsxs7(
5731
6940
  "button",
5732
6941
  {
5733
6942
  className: "jinja-tree-toggle",
5734
6943
  onClick: () => setExpanded(!expanded),
5735
6944
  type: "button",
5736
6945
  children: [
5737
- /* @__PURE__ */ jsx7("span", { className: "jinja-tree-arrow", children: expanded ? "\u25BC" : "\u25B6" }),
5738
- /* @__PURE__ */ jsx7("span", { className: "jinja-tree-label", children: label })
6946
+ /* @__PURE__ */ jsx8("span", { className: "jinja-tree-arrow", children: expanded ? "\u25BC" : "\u25B6" }),
6947
+ /* @__PURE__ */ jsx8("span", { className: "jinja-tree-label", children: label })
5739
6948
  ]
5740
6949
  }
5741
6950
  ),
5742
- expanded && /* @__PURE__ */ jsx7("div", { className: "jinja-tree-branches", children: structure.branches.map((branch, i) => /* @__PURE__ */ jsx7(
6951
+ expanded && /* @__PURE__ */ jsx8("div", { className: "jinja-tree-branches", children: structure.branches.map((branch, i) => /* @__PURE__ */ jsx8(
5743
6952
  BranchNode,
5744
6953
  {
5745
6954
  branch,
@@ -5753,15 +6962,15 @@ function StructureNode({ structure, doc: doc2 }) {
5753
6962
  function JinjaTreeView({ doc: doc2, className }) {
5754
6963
  const structures = analyzeJinjaBlocks(doc2);
5755
6964
  if (structures.length === 0) return null;
5756
- return /* @__PURE__ */ jsx7("div", { className: `jinja-tree ${className || ""}`, children: structures.map((structure) => /* @__PURE__ */ jsx7(StructureNode, { structure, doc: doc2 }, structure.id)) });
6965
+ return /* @__PURE__ */ jsx8("div", { className: `jinja-tree ${className || ""}`, children: structures.map((structure) => /* @__PURE__ */ jsx8(StructureNode, { structure, doc: doc2 }, structure.id)) });
5757
6966
  }
5758
6967
 
5759
6968
  // src/ui/components/FloatingMenu.tsx
5760
- import { useCallback as useCallback2, useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
6969
+ import { useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4, useState as useState5 } from "react";
5761
6970
  import { createPortal as createPortal2 } from "react-dom";
5762
6971
  import { setBlockType, toggleMark as toggleMark2, wrapIn } from "prosemirror-commands";
5763
6972
  import { wrapInList as wrapInList2 } from "prosemirror-schema-list";
5764
- import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
6973
+ import { Fragment as Fragment3, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
5765
6974
  var { paragraph: paragraph2, heading: heading2, bulletList: bulletList3, orderedList: orderedList3, blockquote: blockquote2 } = actionbookSchema.nodes;
5766
6975
  var {
5767
6976
  bold: bold2,
@@ -5849,8 +7058,8 @@ var BTN_RESET2 = {
5849
7058
  lineHeight: 1
5850
7059
  };
5851
7060
  function TBtn({ children, active, title, onMouseDown, style }) {
5852
- const [hover, setHover] = useState4(false);
5853
- return /* @__PURE__ */ jsx8(
7061
+ const [hover, setHover] = useState5(false);
7062
+ return /* @__PURE__ */ jsx9(
5854
7063
  "button",
5855
7064
  {
5856
7065
  ...{ [FLOAT_ATTR]: "" },
@@ -5881,13 +7090,13 @@ function TBtn({ children, active, title, onMouseDown, style }) {
5881
7090
  );
5882
7091
  }
5883
7092
  function Divider() {
5884
- return /* @__PURE__ */ jsx8("div", { style: { width: 1, height: 18, background: "rgba(0,0,0,0.07)", margin: "0 2px", flexShrink: 0 } });
7093
+ return /* @__PURE__ */ jsx9("div", { style: { width: 1, height: 18, background: "rgba(0,0,0,0.07)", margin: "0 2px", flexShrink: 0 } });
5885
7094
  }
5886
7095
  function BlockTypeDropdown({ view, state, label }) {
5887
- const [open, setOpen] = useState4(false);
5888
- const btnRef = useRef3(null);
5889
- const [hover, setHover] = useState4(false);
5890
- useEffect3(() => {
7096
+ const [open, setOpen] = useState5(false);
7097
+ const btnRef = useRef4(null);
7098
+ const [hover, setHover] = useState5(false);
7099
+ useEffect4(() => {
5891
7100
  if (!open) return;
5892
7101
  const onDown = (e) => {
5893
7102
  if (!e.target.closest(`[${FLOAT_ATTR}]`)) {
@@ -5940,8 +7149,8 @@ function BlockTypeDropdown({ view, state, label }) {
5940
7149
  } }
5941
7150
  ];
5942
7151
  const btnRect = btnRef.current?.getBoundingClientRect();
5943
- return /* @__PURE__ */ jsxs7(Fragment2, { children: [
5944
- /* @__PURE__ */ jsxs7(
7152
+ return /* @__PURE__ */ jsxs8(Fragment3, { children: [
7153
+ /* @__PURE__ */ jsxs8(
5945
7154
  "button",
5946
7155
  {
5947
7156
  ref: btnRef,
@@ -5969,12 +7178,12 @@ function BlockTypeDropdown({ view, state, label }) {
5969
7178
  },
5970
7179
  children: [
5971
7180
  label,
5972
- /* @__PURE__ */ jsx8("span", { style: { fontSize: 8, opacity: 0.6 }, children: "\u25BE" })
7181
+ /* @__PURE__ */ jsx9("span", { style: { fontSize: 8, opacity: 0.6 }, children: "\u25BE" })
5973
7182
  ]
5974
7183
  }
5975
7184
  ),
5976
7185
  open && btnRect && createPortal2(
5977
- /* @__PURE__ */ jsx8(
7186
+ /* @__PURE__ */ jsx9(
5978
7187
  "div",
5979
7188
  {
5980
7189
  ...{ [FLOAT_ATTR]: "" },
@@ -5990,7 +7199,7 @@ function BlockTypeDropdown({ view, state, label }) {
5990
7199
  minWidth: 168,
5991
7200
  animation: "ab-float-in 0.12s ease"
5992
7201
  },
5993
- children: items.map((item) => /* @__PURE__ */ jsx8(DropdownItem, { item, onRun: () => setOpen(false) }, item.label))
7202
+ children: items.map((item) => /* @__PURE__ */ jsx9(DropdownItem, { item, onRun: () => setOpen(false) }, item.label))
5994
7203
  }
5995
7204
  ),
5996
7205
  document.body
@@ -5998,8 +7207,8 @@ function BlockTypeDropdown({ view, state, label }) {
5998
7207
  ] });
5999
7208
  }
6000
7209
  function DropdownItem({ item, onRun }) {
6001
- const [hover, setHover] = useState4(false);
6002
- return /* @__PURE__ */ jsxs7(
7210
+ const [hover, setHover] = useState5(false);
7211
+ return /* @__PURE__ */ jsxs8(
6003
7212
  "button",
6004
7213
  {
6005
7214
  ...{ [FLOAT_ATTR]: "" },
@@ -6026,16 +7235,16 @@ function DropdownItem({ item, onRun }) {
6026
7235
  transition: "background 0.1s"
6027
7236
  },
6028
7237
  children: [
6029
- /* @__PURE__ */ jsx8("span", { style: { width: 24, flexShrink: 0, textAlign: "center", fontWeight: 700, fontSize: 12, fontFamily: "monospace", color: "#6366f1" }, children: item.shortLabel }),
7238
+ /* @__PURE__ */ jsx9("span", { style: { width: 24, flexShrink: 0, textAlign: "center", fontWeight: 700, fontSize: 12, fontFamily: "monospace", color: "#6366f1" }, children: item.shortLabel }),
6030
7239
  item.label
6031
7240
  ]
6032
7241
  }
6033
7242
  );
6034
7243
  }
6035
7244
  function SelectionToolbar({ view, state, selectionRect }) {
6036
- const [linkMode, setLinkMode] = useState4(false);
6037
- const [linkHref, setLinkHref] = useState4("");
6038
- const inputRef = useRef3(null);
7245
+ const [linkMode, setLinkMode] = useState5(false);
7246
+ const [linkHref, setLinkHref] = useState5("");
7247
+ const inputRef = useRef4(null);
6039
7248
  const isBold = hasMark(state, bold2);
6040
7249
  const isItalic = hasMark(state, italic2);
6041
7250
  const isUnderline = hasMark(state, underline2);
@@ -6087,7 +7296,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6087
7296
  view.dispatch(view.state.tr.removeMark(from, to, linkMark));
6088
7297
  view.focus();
6089
7298
  };
6090
- return /* @__PURE__ */ jsx8(
7299
+ return /* @__PURE__ */ jsx9(
6091
7300
  "div",
6092
7301
  {
6093
7302
  ...{ [FLOAT_ATTR]: "" },
@@ -6110,9 +7319,9 @@ function SelectionToolbar({ view, state, selectionRect }) {
6110
7319
  },
6111
7320
  children: linkMode ? (
6112
7321
  /* ── Link input mode ── */
6113
- /* @__PURE__ */ jsxs7(Fragment2, { children: [
6114
- /* @__PURE__ */ jsx8("span", { style: { fontSize: 13, color: "#9ca3af", paddingLeft: 4, flexShrink: 0 }, children: "\u2197" }),
6115
- /* @__PURE__ */ jsx8(
7322
+ /* @__PURE__ */ jsxs8(Fragment3, { children: [
7323
+ /* @__PURE__ */ jsx9("span", { style: { fontSize: 13, color: "#9ca3af", paddingLeft: 4, flexShrink: 0 }, children: "\u2197" }),
7324
+ /* @__PURE__ */ jsx9(
6116
7325
  "input",
6117
7326
  {
6118
7327
  ref: inputRef,
@@ -6143,7 +7352,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6143
7352
  }
6144
7353
  }
6145
7354
  ),
6146
- /* @__PURE__ */ jsx8(
7355
+ /* @__PURE__ */ jsx9(
6147
7356
  TBtn,
6148
7357
  {
6149
7358
  title: "Apply (Enter)",
@@ -6155,7 +7364,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6155
7364
  children: "\u21B5"
6156
7365
  }
6157
7366
  ),
6158
- /* @__PURE__ */ jsx8(
7367
+ /* @__PURE__ */ jsx9(
6159
7368
  TBtn,
6160
7369
  {
6161
7370
  title: "Cancel (Esc)",
@@ -6171,8 +7380,8 @@ function SelectionToolbar({ view, state, selectionRect }) {
6171
7380
  ] })
6172
7381
  ) : (
6173
7382
  /* ── Normal toolbar mode ── */
6174
- /* @__PURE__ */ jsxs7(Fragment2, { children: [
6175
- /* @__PURE__ */ jsx8(
7383
+ /* @__PURE__ */ jsxs8(Fragment3, { children: [
7384
+ /* @__PURE__ */ jsx9(
6176
7385
  TBtn,
6177
7386
  {
6178
7387
  active: isBold,
@@ -6182,7 +7391,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6182
7391
  children: "B"
6183
7392
  }
6184
7393
  ),
6185
- /* @__PURE__ */ jsx8(
7394
+ /* @__PURE__ */ jsx9(
6186
7395
  TBtn,
6187
7396
  {
6188
7397
  active: isItalic,
@@ -6192,7 +7401,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6192
7401
  children: "I"
6193
7402
  }
6194
7403
  ),
6195
- /* @__PURE__ */ jsx8(
7404
+ /* @__PURE__ */ jsx9(
6196
7405
  TBtn,
6197
7406
  {
6198
7407
  active: isUnderline,
@@ -6202,7 +7411,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6202
7411
  children: "U"
6203
7412
  }
6204
7413
  ),
6205
- /* @__PURE__ */ jsx8(
7414
+ /* @__PURE__ */ jsx9(
6206
7415
  TBtn,
6207
7416
  {
6208
7417
  active: isStrike,
@@ -6212,7 +7421,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6212
7421
  children: "S"
6213
7422
  }
6214
7423
  ),
6215
- /* @__PURE__ */ jsx8(
7424
+ /* @__PURE__ */ jsx9(
6216
7425
  TBtn,
6217
7426
  {
6218
7427
  active: isCode,
@@ -6222,7 +7431,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
6222
7431
  children: "`\xB7`"
6223
7432
  }
6224
7433
  ),
6225
- /* @__PURE__ */ jsx8(
7434
+ /* @__PURE__ */ jsx9(
6226
7435
  TBtn,
6227
7436
  {
6228
7437
  active: isLink,
@@ -6232,8 +7441,8 @@ function SelectionToolbar({ view, state, selectionRect }) {
6232
7441
  children: "\u2197"
6233
7442
  }
6234
7443
  ),
6235
- /* @__PURE__ */ jsx8(Divider, {}),
6236
- /* @__PURE__ */ jsx8(BlockTypeDropdown, { view, state, label: blockLabel })
7444
+ /* @__PURE__ */ jsx9(Divider, {}),
7445
+ /* @__PURE__ */ jsx9(BlockTypeDropdown, { view, state, label: blockLabel })
6237
7446
  ] })
6238
7447
  )
6239
7448
  }
@@ -6323,7 +7532,7 @@ var BLOCK_ITEMS = [
6323
7532
  }
6324
7533
  ];
6325
7534
  function EmptyParaHandle({ view, cursorPos }) {
6326
- const [menuOpen, setMenuOpen] = useState4(false);
7535
+ const [menuOpen, setMenuOpen] = useState5(false);
6327
7536
  const coords = view.coordsAtPos(cursorPos);
6328
7537
  const lineH = Math.max(coords.bottom - coords.top, 18);
6329
7538
  const btnSize = 22;
@@ -6331,7 +7540,7 @@ function EmptyParaHandle({ view, cursorPos }) {
6331
7540
  const btnTop = coords.top + (lineH - btnSize) / 2;
6332
7541
  const menuLeft = Math.max(VPORT_MARGIN2, btnLeft);
6333
7542
  const menuTop = btnTop + btnSize + 4;
6334
- useEffect3(() => {
7543
+ useEffect4(() => {
6335
7544
  if (!menuOpen) return;
6336
7545
  const onDown = (e) => {
6337
7546
  if (!e.target.closest(`[${FLOAT_ATTR}]`)) {
@@ -6341,8 +7550,8 @@ function EmptyParaHandle({ view, cursorPos }) {
6341
7550
  document.addEventListener("mousedown", onDown, true);
6342
7551
  return () => document.removeEventListener("mousedown", onDown, true);
6343
7552
  }, [menuOpen]);
6344
- return /* @__PURE__ */ jsxs7(Fragment2, { children: [
6345
- /* @__PURE__ */ jsx8(
7553
+ return /* @__PURE__ */ jsxs8(Fragment3, { children: [
7554
+ /* @__PURE__ */ jsx9(
6346
7555
  "button",
6347
7556
  {
6348
7557
  ...{ [FLOAT_ATTR]: "" },
@@ -6374,7 +7583,7 @@ function EmptyParaHandle({ view, cursorPos }) {
6374
7583
  children: "+"
6375
7584
  }
6376
7585
  ),
6377
- menuOpen && /* @__PURE__ */ jsx8(
7586
+ menuOpen && /* @__PURE__ */ jsx9(
6378
7587
  "div",
6379
7588
  {
6380
7589
  ...{ [FLOAT_ATTR]: "" },
@@ -6390,7 +7599,7 @@ function EmptyParaHandle({ view, cursorPos }) {
6390
7599
  minWidth: 168,
6391
7600
  animation: "ab-float-in 0.12s ease"
6392
7601
  },
6393
- children: BLOCK_ITEMS.map((item) => /* @__PURE__ */ jsx8(
7602
+ children: BLOCK_ITEMS.map((item) => /* @__PURE__ */ jsx9(
6394
7603
  BlockMenuItem,
6395
7604
  {
6396
7605
  item,
@@ -6408,8 +7617,8 @@ function BlockMenuItem({
6408
7617
  view,
6409
7618
  onRun
6410
7619
  }) {
6411
- const [hover, setHover] = useState4(false);
6412
- return /* @__PURE__ */ jsxs7(
7620
+ const [hover, setHover] = useState5(false);
7621
+ return /* @__PURE__ */ jsxs8(
6413
7622
  "button",
6414
7623
  {
6415
7624
  ...{ [FLOAT_ATTR]: "" },
@@ -6435,7 +7644,7 @@ function BlockMenuItem({
6435
7644
  transition: "background 0.1s"
6436
7645
  },
6437
7646
  children: [
6438
- /* @__PURE__ */ jsx8(
7647
+ /* @__PURE__ */ jsx9(
6439
7648
  "span",
6440
7649
  {
6441
7650
  style: {
@@ -6456,11 +7665,11 @@ function BlockMenuItem({
6456
7665
  );
6457
7666
  }
6458
7667
  function FloatingMenu({ view, editorState }) {
6459
- const [showEmptyHandle, setShowEmptyHandle] = useState4(false);
6460
- const dwellTimerRef = useRef3(null);
6461
- const lastEmptyPosRef = useRef3(null);
6462
- const [, setScrollTick] = useState4(0);
6463
- useEffect3(() => {
7668
+ const [showEmptyHandle, setShowEmptyHandle] = useState5(false);
7669
+ const dwellTimerRef = useRef4(null);
7670
+ const lastEmptyPosRef = useRef4(null);
7671
+ const [, setScrollTick] = useState5(0);
7672
+ useEffect4(() => {
6464
7673
  const editorDom = view?.dom;
6465
7674
  if (!editorDom) return;
6466
7675
  const container = editorDom.closest(".editor-shell") ?? editorDom.parentElement;
@@ -6469,7 +7678,7 @@ function FloatingMenu({ view, editorState }) {
6469
7678
  container.addEventListener("scroll", onScroll, { passive: true });
6470
7679
  return () => container.removeEventListener("scroll", onScroll);
6471
7680
  }, [view]);
6472
- useEffect3(() => {
7681
+ useEffect4(() => {
6473
7682
  const id = "ab-float-keyframes";
6474
7683
  if (document.getElementById(id)) return;
6475
7684
  const style = document.createElement("style");
@@ -6499,8 +7708,8 @@ function FloatingMenu({ view, editorState }) {
6499
7708
  }
6500
7709
  const selectionRect = hasSelection ? getSelectionDOMRect() : null;
6501
7710
  return createPortal2(
6502
- /* @__PURE__ */ jsxs7(Fragment2, { children: [
6503
- hasSelection && selectionRect && /* @__PURE__ */ jsx8(
7711
+ /* @__PURE__ */ jsxs8(Fragment3, { children: [
7712
+ hasSelection && selectionRect && /* @__PURE__ */ jsx9(
6504
7713
  SelectionToolbar,
6505
7714
  {
6506
7715
  view,
@@ -6508,23 +7717,23 @@ function FloatingMenu({ view, editorState }) {
6508
7717
  selectionRect
6509
7718
  }
6510
7719
  ),
6511
- !hasSelection && showEmptyHandle && emptyPos !== null && /* @__PURE__ */ jsx8(EmptyParaHandle, { view, cursorPos: emptyPos })
7720
+ !hasSelection && showEmptyHandle && emptyPos !== null && /* @__PURE__ */ jsx9(EmptyParaHandle, { view, cursorPos: emptyPos })
6512
7721
  ] }),
6513
7722
  document.body
6514
7723
  );
6515
7724
  }
6516
7725
 
6517
7726
  // src/ui/plugin/inlineSuggestPlugin.ts
6518
- import { Plugin as Plugin7, PluginKey as PluginKey7 } from "prosemirror-state";
6519
- import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "prosemirror-view";
6520
- var inlineSuggestKey = new PluginKey7("inlineSuggest");
7727
+ import { Plugin as Plugin8, PluginKey as PluginKey8 } from "prosemirror-state";
7728
+ import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "prosemirror-view";
7729
+ var inlineSuggestKey = new PluginKey8("inlineSuggest");
6521
7730
  var DEBOUNCE_MS = 600;
6522
7731
  function createInlineSuggestPlugin(provider, endpoint, options) {
6523
7732
  const { onContextChange } = options ?? {};
6524
7733
  return {
6525
7734
  name: "inlineSuggest",
6526
7735
  plugins: () => [
6527
- new Plugin7({
7736
+ new Plugin8({
6528
7737
  key: inlineSuggestKey,
6529
7738
  state: {
6530
7739
  init: () => ({ suggestion: null, anchorPos: null }),
@@ -6540,13 +7749,13 @@ function createInlineSuggestPlugin(provider, endpoint, options) {
6540
7749
  props: {
6541
7750
  decorations(state) {
6542
7751
  const ps = inlineSuggestKey.getState(state);
6543
- if (!ps?.suggestion || !state.selection.empty) return DecorationSet3.empty;
7752
+ if (!ps?.suggestion || !state.selection.empty) return DecorationSet4.empty;
6544
7753
  const ghost = document.createElement("span");
6545
7754
  ghost.textContent = ps.suggestion;
6546
7755
  ghost.style.cssText = "color: rgba(0,0,0,0.3); pointer-events: none; user-select: none;";
6547
7756
  ghost.setAttribute("aria-hidden", "true");
6548
- return DecorationSet3.create(state.doc, [
6549
- Decoration3.widget(state.selection.from, ghost, {
7757
+ return DecorationSet4.create(state.doc, [
7758
+ Decoration4.widget(state.selection.from, ghost, {
6550
7759
  side: 1,
6551
7760
  key: "inline-suggest"
6552
7761
  })
@@ -6687,12 +7896,15 @@ export {
6687
7896
  convertBlock2 as convertBlock,
6688
7897
  convertInline2 as convertInline,
6689
7898
  createDragHandlePlugin,
7899
+ createHistoryPlugin,
6690
7900
  createInlineSuggestPlugin,
6691
7901
  createInlineToolTagNodeViewPlugin,
6692
7902
  createInputRulesPlugin,
6693
7903
  createJinjaDecorationPlugin,
7904
+ createJinjaIfBlockPlugin,
6694
7905
  createJumpPointAdjacentPlugin,
6695
7906
  createJumpPointNodeViewPlugin,
7907
+ createJumpPointValidationPlugin,
6696
7908
  createKeymapPlugin,
6697
7909
  createLinkPlugin,
6698
7910
  createMarkdownClipboardPlugin,
@@ -6700,6 +7912,8 @@ export {
6700
7912
  createReactNodeView,
6701
7913
  createSlashCommandPlugin,
6702
7914
  createTodoNodeViewPlugin,
7915
+ hasBrokenAnchorRefs,
7916
+ hasDuplicateJumpPoints,
6703
7917
  inlineSuggestKey,
6704
7918
  slashCommandKey,
6705
7919
  toProseMirrorJSON,