@sendbird/actionbook-core 0.10.6 → 0.10.8

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
@@ -55,7 +55,7 @@ var actionbookSchema = new Schema({
55
55
  },
56
56
  listItem: {
57
57
  content: "block+",
58
- attrs: { checked: { default: null } },
58
+ attrs: { checked: { default: null }, value: { default: null } },
59
59
  parseDOM: [
60
60
  {
61
61
  tag: "li",
@@ -63,20 +63,25 @@ var actionbookSchema = new Schema({
63
63
  const el = dom;
64
64
  const checkbox = el.querySelector('input[type="checkbox"]');
65
65
  if (checkbox) {
66
- return { checked: checkbox.checked };
66
+ return { checked: checkbox.checked, value: el.value || null };
67
67
  }
68
68
  if (el.dataset.checked != null) {
69
- return { checked: el.dataset.checked === "true" };
69
+ return { checked: el.dataset.checked === "true", value: el.value || null };
70
70
  }
71
- return { checked: null };
71
+ return { checked: null, value: el.value || null };
72
72
  }
73
73
  }
74
74
  ],
75
75
  toDOM(node) {
76
+ const liAttrs = {};
76
77
  if (node.attrs.checked != null) {
77
- return ["li", { class: "todo-item", "data-checked": String(node.attrs.checked) }, 0];
78
+ liAttrs.class = "todo-item";
79
+ liAttrs["data-checked"] = String(node.attrs.checked);
78
80
  }
79
- return ["li", 0];
81
+ if (node.attrs.value != null) {
82
+ liAttrs.value = String(node.attrs.value);
83
+ }
84
+ return Object.keys(liAttrs).length > 0 ? ["li", liAttrs, 0] : ["li", 0];
80
85
  },
81
86
  defining: true
82
87
  },
@@ -1425,6 +1430,10 @@ function convertPMListItem(node, depth = 0) {
1425
1430
  if (typeof checked === "boolean") {
1426
1431
  base.checked = checked;
1427
1432
  }
1433
+ const value = node.attrs?.value;
1434
+ if (value != null) {
1435
+ base.value = value;
1436
+ }
1428
1437
  return base;
1429
1438
  }
1430
1439
  function fromProseMirrorJSON(pmJSON) {
@@ -2320,13 +2329,21 @@ function blockToMdast(node, depth = 0) {
2320
2329
  ];
2321
2330
  }
2322
2331
  case "orderedList": {
2323
- const items = node.content.map((li) => listItemToMdast(li, depth + 1));
2332
+ const spread = node.spread ?? false;
2333
+ let counter = node.start;
2334
+ const items = node.content.map((li) => {
2335
+ const mdastLi = listItemToMdast(li, depth + 1);
2336
+ if (li.value != null) counter = li.value;
2337
+ mdastLi._ordinalValue = counter;
2338
+ counter++;
2339
+ return mdastLi;
2340
+ });
2324
2341
  return [
2325
2342
  {
2326
2343
  type: "list",
2327
2344
  ordered: true,
2328
- start: node.start,
2329
- spread: node.spread ?? false,
2345
+ start: node.content[0]?.value ?? node.start,
2346
+ spread,
2330
2347
  children: items
2331
2348
  }
2332
2349
  ];
@@ -2401,6 +2418,14 @@ var gfmNoAutolinkToMarkdown = () => ({
2401
2418
  function isEmptyParagraph(node) {
2402
2419
  return node.type === "paragraph" && (!("children" in node) || !node.children || node.children.length === 0);
2403
2420
  }
2421
+ var JINJA_TAG_RE = /^\{%\s*(if|elif|else|endif)\b/;
2422
+ function isJinjaTagParagraph(node) {
2423
+ if (node.type !== "paragraph") return false;
2424
+ const children = node.children;
2425
+ if (!children || children.length !== 1) return false;
2426
+ const child = children[0];
2427
+ return child.type === "text" && typeof child.value === "string" && JINJA_TAG_RE.test(child.value);
2428
+ }
2404
2429
  function textHandler(node, parent, state, info) {
2405
2430
  const originalUnsafe = state.unsafe;
2406
2431
  state.unsafe = originalUnsafe.filter((p) => !(p.character === "_" && !p.atBreak));
@@ -2415,10 +2440,24 @@ function linkHandler(node, parent, state, info) {
2415
2440
  const titlePart = title ? ` "${title.replace(/"/g, '\\"')}"` : "";
2416
2441
  return `[${childrenText}](${url}${titlePart})`;
2417
2442
  }
2443
+ function listItemHandler(node, parent, state, info) {
2444
+ const value = node._ordinalValue;
2445
+ if (value != null && parent && parent.ordered) {
2446
+ const list = parent;
2447
+ const savedStart = list.start;
2448
+ const idx = list.children.indexOf(node);
2449
+ list.start = value - idx;
2450
+ const result = defaultHandlers3.listItem(node, parent, state, info);
2451
+ list.start = savedStart;
2452
+ return result;
2453
+ }
2454
+ return defaultHandlers3.listItem(node, parent, state, info);
2455
+ }
2418
2456
  function serializeToMarkdown(doc2) {
2419
2457
  const mdastTree = toMdast(doc2);
2420
2458
  const raw = toMarkdown(mdastTree, {
2421
2459
  bullet: "-",
2460
+ bulletOrdered: ".",
2422
2461
  rule: "-",
2423
2462
  listItemIndent: "one",
2424
2463
  incrementListMarker: true,
@@ -2429,11 +2468,15 @@ function serializeToMarkdown(doc2) {
2429
2468
  // Empty paragraphs represent blank-line spacers.
2430
2469
  // Reduce the separator after an empty paragraph from \n\n to \n
2431
2470
  // so that 1 empty paragraph = 1 extra blank line in the output.
2432
- (left) => isEmptyParagraph(left) ? 0 : null
2471
+ (left) => isEmptyParagraph(left) ? 0 : null,
2472
+ // Consecutive paragraphs: single newline instead of blank line
2473
+ // (skip jinja tag paragraphs — they need blank lines for proper roundtrip)
2474
+ (left, right) => left.type === "paragraph" && right.type === "paragraph" && !isJinjaTagParagraph(left) && !isJinjaTagParagraph(right) ? 0 : null
2433
2475
  ],
2434
2476
  handlers: {
2435
2477
  text: textHandler,
2436
2478
  link: linkHandler,
2479
+ listItem: listItemHandler,
2437
2480
  ...resourceTagToMarkdown().handlers,
2438
2481
  ...jumpPointToMarkdown().handlers
2439
2482
  },
@@ -2569,8 +2612,11 @@ function convertBlock2(node) {
2569
2612
  type: "listItem",
2570
2613
  content: node.content.flatMap(convertBlockToArray)
2571
2614
  };
2572
- if (node.checked != null) {
2573
- result.attrs = { checked: node.checked };
2615
+ if (node.checked != null || node.value != null) {
2616
+ result.attrs = {
2617
+ ...node.checked != null ? { checked: node.checked } : {},
2618
+ ...node.value != null ? { value: node.value } : {}
2619
+ };
2574
2620
  }
2575
2621
  return result;
2576
2622
  }
@@ -3025,15 +3071,22 @@ var STRIKE_RE = /(?:^|[^~])~~([^~]+)~~$/;
3025
3071
  var CODE_RE = /(?:^|[^`])`([^`]+)`$/;
3026
3072
  function findParentList(state, pos) {
3027
3073
  const $pos = state.doc.resolve(pos);
3028
- const { bulletList: blType, orderedList: olType } = actionbookSchema.nodes;
3029
3074
  for (let d = $pos.depth; d > 0; d--) {
3030
3075
  const node = $pos.node(d);
3031
- if (node.type === blType || node.type === olType) {
3076
+ const name = node.type.name;
3077
+ if (name === "bulletList" || name === "orderedList") {
3032
3078
  return { depth: d, node };
3033
3079
  }
3034
3080
  }
3035
3081
  return null;
3036
3082
  }
3083
+ function isInsideList(state, pos) {
3084
+ const $pos = state.doc.resolve(pos);
3085
+ for (let d = $pos.depth; d > 0; d--) {
3086
+ if ($pos.node(d).type.name === "listItem") return true;
3087
+ }
3088
+ return false;
3089
+ }
3037
3090
  var NOOP = /* @__PURE__ */ Symbol("noop");
3038
3091
  function handleListInputRule(state, start, end, listType, attrs) {
3039
3092
  const resolvePos = Math.max(start, end - 1);
@@ -3044,27 +3097,46 @@ function handleListInputRule(state, start, end, listType, attrs) {
3044
3097
  const paraContentSize = $from.parent.content.size;
3045
3098
  const matchedLen = end - start;
3046
3099
  const isMarkerOnly = paraContentSize === matchedLen;
3047
- if (listNode.type === listType) {
3048
- if (isMarkerOnly && listType === actionbookSchema.nodes.orderedList && attrs?.start != null) {
3049
- const newStart = attrs.start;
3050
- const currentStart = listNode.attrs.start ?? 1;
3051
- if (newStart !== currentStart) {
3052
- const listStart = $from.before(listDepth);
3100
+ if (listNode.type.name === listType.name) {
3101
+ if (listType.name === "orderedList" && attrs?.start != null) {
3102
+ const newValue = attrs.start;
3103
+ let liDepth2 = -1;
3104
+ for (let d = $from.depth; d > 0; d--) {
3105
+ if ($from.node(d).type.name === "listItem") {
3106
+ liDepth2 = d;
3107
+ break;
3108
+ }
3109
+ }
3110
+ if (liDepth2 > 0) {
3111
+ const liIndex2 = $from.index(listDepth);
3112
+ const listStartNum = listNode.attrs.start ?? 1;
3113
+ const naturalNumber = listStartNum + liIndex2;
3114
+ const currentValue = $from.node(liDepth2).attrs.value;
3115
+ if (currentValue === newValue || currentValue == null && newValue === naturalNumber) {
3116
+ const tr3 = state.tr.delete(start, end);
3117
+ tr3.setSelection(TextSelection.near(tr3.doc.resolve(tr3.mapping.map(start))));
3118
+ return tr3;
3119
+ }
3053
3120
  const tr2 = state.tr.delete(start, end);
3054
- tr2.setNodeMarkup(tr2.mapping.map(listStart), void 0, { ...listNode.attrs, start: newStart });
3055
- tr2.setSelection(TextSelection.near(tr2.doc.resolve(tr2.mapping.map(start))));
3056
- return tr2;
3121
+ const $afterDelete = tr2.doc.resolve(tr2.mapping.map(resolvePos));
3122
+ for (let d = $afterDelete.depth; d > 0; d--) {
3123
+ if ($afterDelete.node(d).type.name === "listItem") {
3124
+ const pos = $afterDelete.before(d);
3125
+ const node = $afterDelete.node(d);
3126
+ tr2.setNodeMarkup(pos, void 0, { ...node.attrs, value: newValue });
3127
+ tr2.setSelection(TextSelection.near(tr2.doc.resolve(tr2.mapping.map(start))));
3128
+ return tr2;
3129
+ }
3130
+ }
3057
3131
  }
3058
3132
  }
3059
3133
  return NOOP;
3060
3134
  }
3061
- if (!isMarkerOnly) return NOOP;
3062
- const { listItem: liType } = actionbookSchema.nodes;
3063
3135
  const tr = state.tr.delete(start, end);
3064
3136
  const $pos = tr.doc.resolve(tr.mapping.map(resolvePos));
3065
3137
  let liDepth = -1;
3066
3138
  for (let d = $pos.depth; d > 0; d--) {
3067
- if ($pos.node(d).type === liType) {
3139
+ if ($pos.node(d).type.name === "listItem") {
3068
3140
  liDepth = d;
3069
3141
  break;
3070
3142
  }
@@ -3120,9 +3192,16 @@ function createInputRulesPlugin() {
3120
3192
  level: match[1].length
3121
3193
  }))
3122
3194
  );
3123
- rules.push(wrappingInputRule(BLOCKQUOTE_RE, actionbookSchema.nodes.blockquote));
3195
+ rules.push(
3196
+ new InputRule(BLOCKQUOTE_RE, (state, match, start, end) => {
3197
+ if (isInsideList(state, start)) return null;
3198
+ const fallback = wrappingInputRule(BLOCKQUOTE_RE, actionbookSchema.nodes.blockquote);
3199
+ return fallback.handler(state, match, start, end);
3200
+ })
3201
+ );
3124
3202
  rules.push(
3125
3203
  new InputRule(CODE_BLOCK_RE, (state, match, start, end) => {
3204
+ if (isInsideList(state, start)) return null;
3126
3205
  const language = match[1] || null;
3127
3206
  const $start = state.doc.resolve(start);
3128
3207
  const blockStart = $start.before($start.depth);
@@ -3138,6 +3217,7 @@ function createInputRulesPlugin() {
3138
3217
  );
3139
3218
  rules.push(
3140
3219
  new InputRule(HR_RE, (state, _match, start, end) => {
3220
+ if (isInsideList(state, start)) return null;
3141
3221
  const { horizontalRule: hrType, paragraph: pType } = actionbookSchema.nodes;
3142
3222
  const $start = state.doc.resolve(start);
3143
3223
  const blockStart = $start.before($start.depth);
@@ -3177,9 +3257,29 @@ function createInputRulesPlugin() {
3177
3257
  const fallback = wrappingInputRule(ORDERED_LIST_RE, olType, (m) => ({ start: +m[1] }));
3178
3258
  rules.push(
3179
3259
  new InputRule(ORDERED_LIST_RE, (state, match, start, end) => {
3180
- const result = handleListInputRule(state, start, end, olType, { start: +match[1] });
3181
- if (result === NOOP) return null;
3182
- if (result) return result;
3260
+ const typedNumber = +match[1];
3261
+ const resolvePos = Math.max(start, end - 1);
3262
+ const parentList = findParentList(state, resolvePos);
3263
+ if (parentList) {
3264
+ const { node: listNode } = parentList;
3265
+ if (listNode.type.name === "orderedList") {
3266
+ const tr = state.tr.delete(start, end);
3267
+ const $after = tr.doc.resolve(tr.mapping.map(resolvePos));
3268
+ for (let d = $after.depth; d > 0; d--) {
3269
+ if ($after.node(d).type.name === "listItem") {
3270
+ const liPos = $after.before(d);
3271
+ const liNode = $after.node(d);
3272
+ tr.setNodeMarkup(liPos, void 0, { ...liNode.attrs, value: typedNumber });
3273
+ tr.setSelection(TextSelection.near(tr.doc.resolve(tr.mapping.map(start))));
3274
+ return tr;
3275
+ }
3276
+ }
3277
+ return null;
3278
+ }
3279
+ const result = handleListInputRule(state, start, end, olType, { start: typedNumber });
3280
+ if (result === NOOP) return null;
3281
+ if (result) return result;
3282
+ }
3183
3283
  const handler = fallback.handler;
3184
3284
  return handler(state, match, start, end);
3185
3285
  })
@@ -5508,6 +5608,11 @@ function tokenize(input) {
5508
5608
  i++;
5509
5609
  continue;
5510
5610
  }
5611
+ if (input[i] === ",") {
5612
+ tokens.push({ type: "COMMA", value: "," });
5613
+ i++;
5614
+ continue;
5615
+ }
5511
5616
  return { tokens: [], error: `Unexpected character: ${input[i]}` };
5512
5617
  }
5513
5618
  tokens.push({ type: "EOF", value: "" });
@@ -5638,6 +5743,17 @@ var Parser = class {
5638
5743
  return null;
5639
5744
  case "IDENT": {
5640
5745
  this.advance();
5746
+ if (this.peek().type === "LPAREN") {
5747
+ this.advance();
5748
+ while (this.peek().type !== "RPAREN" && this.peek().type !== "EOF") {
5749
+ this.orExpr();
5750
+ if (this.peek().type === "COMMA") {
5751
+ this.advance();
5752
+ }
5753
+ }
5754
+ this.expect("RPAREN");
5755
+ return null;
5756
+ }
5641
5757
  return this.resolveVariable(t.value);
5642
5758
  }
5643
5759
  case "LPAREN": {
@@ -5853,7 +5969,7 @@ function buildDocumentTree(doc2) {
5853
5969
 
5854
5970
  // src/ui/plugin/markdownClipboard.ts
5855
5971
  var key = new PluginKey2("markdownClipboard");
5856
- var MAX_PASTE_LIST_DEPTH = 3;
5972
+ var MAX_PASTE_LIST_DEPTH = 15;
5857
5973
  var MAX_FLATTEN_DEPTH = 128;
5858
5974
  function flattenDeepLists(node, listDepth = 0, _recurseDepth = 0) {
5859
5975
  if (_recurseDepth > MAX_FLATTEN_DEPTH) return node;
@@ -5944,6 +6060,22 @@ function createPlugin() {
5944
6060
  keyup() {
5945
6061
  shiftHeld = false;
5946
6062
  return false;
6063
+ },
6064
+ paste(view, event) {
6065
+ if (shiftHeld) return false;
6066
+ const clipboardData = event.clipboardData;
6067
+ const text2 = clipboardData?.getData("text/plain");
6068
+ if (!text2 || !URL_RE.test(text2.trim())) return false;
6069
+ const { from, to } = view.state.selection;
6070
+ if (from >= to) return false;
6071
+ const selectedText = view.state.doc.textBetween(from, to);
6072
+ if (!selectedText) return false;
6073
+ event.preventDefault();
6074
+ const linkType = view.state.schema.marks.link;
6075
+ if (!linkType) return false;
6076
+ const linkMark2 = linkType.create({ href: text2.trim() });
6077
+ view.dispatch(view.state.tr.addMark(from, to, linkMark2));
6078
+ return true;
5947
6079
  }
5948
6080
  },
5949
6081
  handlePaste(view, event) {
@@ -6551,7 +6683,7 @@ function createInlineToolTagNodeViewPlugin() {
6551
6683
  import { Plugin as Plugin6, PluginKey as PluginKey5 } from "prosemirror-state";
6552
6684
  import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "prosemirror-view";
6553
6685
  var jinjaPluginKey = new PluginKey5("jinjaDecoration");
6554
- var JINJA_TAG_RE = /\{%\s*(if|elif|else|endif)\s*([^%]*?)\s*%\}/g;
6686
+ var JINJA_TAG_RE2 = /\{%\s*(if|elif|else|endif)\s*([^%]*?)\s*%\}/g;
6555
6687
  function getBlockPositions(doc2) {
6556
6688
  const blocks = [];
6557
6689
  let index = 0;
@@ -6565,9 +6697,9 @@ function addInlineChipDecorations(doc2, decorations) {
6565
6697
  doc2.descendants((node, pos) => {
6566
6698
  if (!node.isText) return;
6567
6699
  const text2 = node.text;
6568
- JINJA_TAG_RE.lastIndex = 0;
6700
+ JINJA_TAG_RE2.lastIndex = 0;
6569
6701
  let match;
6570
- while ((match = JINJA_TAG_RE.exec(text2)) !== null) {
6702
+ while ((match = JINJA_TAG_RE2.exec(text2)) !== null) {
6571
6703
  const from = pos + match.index;
6572
6704
  const to = from + match[0].length;
6573
6705
  const keyword = match[1];
@@ -6732,6 +6864,7 @@ var JINJA_STYLES = `
6732
6864
  .jinja-branch-condition {
6733
6865
  flex: 1;
6734
6866
  min-width: 0;
6867
+ min-height: 20px;
6735
6868
  display: flex;
6736
6869
  flex-wrap: wrap;
6737
6870
  gap: 8px;
@@ -6909,6 +7042,15 @@ var JINJA_STYLES = `
6909
7042
  background: rgba(13, 13, 13, 0.04);
6910
7043
  }
6911
7044
 
7045
+ .jinja-ghost-btn.jinja-add-btn {
7046
+ color: #5E5E5E;
7047
+ }
7048
+
7049
+ .jinja-ghost-btn.jinja-add-btn.active {
7050
+ background: #DCDBFF;
7051
+ color: #6210CC;
7052
+ }
7053
+
6912
7054
  .jinja-ghost-btn:disabled {
6913
7055
  opacity: 0.45;
6914
7056
  cursor: default;
@@ -7103,60 +7245,18 @@ function deleteBranch(view, nodePos, branchType) {
7103
7245
  const context = getBranchContext(view, nodePos);
7104
7246
  if (!context) return false;
7105
7247
  const { branchNode, branchPos, branchIndex, blockNode, blockPos } = context;
7106
- const branchContent = branchNode.content;
7107
7248
  if (branchType === "if") {
7108
- if (blockNode.childCount === 1) {
7109
- const tr2 = view.state.tr;
7110
- if (isBranchBodyEmpty(branchNode)) {
7111
- tr2.replaceWith(blockPos, blockPos + blockNode.nodeSize, view.state.schema.nodes.paragraph.create());
7112
- tr2.setSelection(TextSelection4.near(tr2.doc.resolve(blockPos)));
7113
- } else {
7114
- tr2.replaceWith(blockPos, blockPos + blockNode.nodeSize, branchContent);
7115
- tr2.setSelection(TextSelection4.near(tr2.doc.resolve(blockPos)));
7116
- }
7117
- view.dispatch(tr2.scrollIntoView());
7118
- view.focus();
7119
- return true;
7120
- }
7121
- const nextBranch = blockNode.child(branchIndex + 1);
7122
- const promotedCondition = typeof nextBranch.attrs.condition === "string" ? nextBranch.attrs.condition : "";
7123
- const tr = view.state.tr;
7124
- if (!isBranchBodyEmpty(branchNode)) {
7125
- const nextBranchPos = branchPos + branchNode.nodeSize;
7126
- const nextBranchContentStart = nextBranchPos + 1;
7127
- for (let i = branchContent.childCount - 1; i >= 0; i--) {
7128
- tr.insert(nextBranchContentStart, branchContent.child(i));
7129
- }
7130
- }
7131
- const mappedBranchPos = tr.mapping.map(branchPos);
7132
- const mappedBranchEnd = tr.mapping.map(branchPos + branchNode.nodeSize);
7133
- tr.delete(mappedBranchPos, mappedBranchEnd);
7134
- tr.setNodeMarkup(mappedBranchPos, void 0, {
7135
- ...nextBranch.attrs,
7136
- branchType: "if",
7137
- condition: promotedCondition
7138
- });
7139
- view.dispatch(tr.scrollIntoView());
7249
+ const tr2 = view.state.tr;
7250
+ tr2.replaceWith(blockPos, blockPos + blockNode.nodeSize, view.state.schema.nodes.paragraph.create());
7251
+ tr2.setSelection(TextSelection4.near(tr2.doc.resolve(blockPos)));
7252
+ view.dispatch(tr2.scrollIntoView());
7140
7253
  view.focus();
7141
7254
  return true;
7142
7255
  }
7143
- if (!isBranchBodyEmpty(branchNode) && branchIndex > 0) {
7144
- const prevBranch = blockNode.child(branchIndex - 1);
7145
- const prevBranchPos = branchPos - prevBranch.nodeSize;
7146
- const prevBranchContentEnd = prevBranchPos + prevBranch.nodeSize - 1;
7147
- const tr = view.state.tr;
7148
- for (let i = 0; i < branchContent.childCount; i++) {
7149
- tr.insert(prevBranchContentEnd + i, branchContent.child(i));
7150
- }
7151
- const mappedBranchPos = tr.mapping.map(branchPos);
7152
- const mappedBranchEnd = tr.mapping.map(branchPos + branchNode.nodeSize);
7153
- tr.delete(mappedBranchPos, mappedBranchEnd);
7154
- tr.setSelection(TextSelection4.near(tr.doc.resolve(tr.mapping.map(prevBranchContentEnd)), -1));
7155
- view.dispatch(tr.scrollIntoView());
7156
- view.focus();
7157
- return true;
7158
- }
7159
- view.dispatch(view.state.tr.delete(branchPos, branchPos + branchNode.nodeSize).scrollIntoView());
7256
+ const tr = view.state.tr;
7257
+ tr.delete(branchPos, branchPos + branchNode.nodeSize);
7258
+ tr.setSelection(TextSelection4.near(tr.doc.resolve(tr.mapping.map(branchPos)), -1));
7259
+ view.dispatch(tr.scrollIntoView());
7160
7260
  view.focus();
7161
7261
  return true;
7162
7262
  }
@@ -7204,11 +7304,11 @@ function ConditionDisplay({
7204
7304
  return /* @__PURE__ */ jsx6("span", { className: "jinja-otherwise", children: "Otherwise" });
7205
7305
  }
7206
7306
  const commit = () => {
7207
- setIsEditing(false);
7208
- onConditionBlur?.();
7209
7307
  if (draftValue !== condition) {
7210
7308
  onConditionChange(draftValue);
7211
7309
  }
7310
+ setIsEditing(false);
7311
+ onConditionBlur?.();
7212
7312
  };
7213
7313
  const cancel = () => {
7214
7314
  setDraftValue(condition);
@@ -7253,7 +7353,7 @@ function ConditionDisplay({
7253
7353
  setIsEditing(true);
7254
7354
  }
7255
7355
  },
7256
- children: condition.length > 0 ? renderCondition(condition) : /* @__PURE__ */ jsx6("span", { className: "jinja-condition-placeholder", children: CONDITION_PLACEHOLDER })
7356
+ children: condition.length > 0 ? condition.trim().length > 0 ? renderCondition(condition) : /* @__PURE__ */ jsx6("span", { className: "jinja-token-identifier", children: condition }) : /* @__PURE__ */ jsx6("span", { className: "jinja-condition-placeholder", children: CONDITION_PLACEHOLDER })
7257
7357
  }
7258
7358
  );
7259
7359
  }
@@ -7326,24 +7426,19 @@ function JinjaBranchHeader({
7326
7426
  };
7327
7427
  }, [menuSource]);
7328
7428
  const menuItems = [];
7329
- const isOnlyIfBranch = branchType === "if" && isLastBranch && !hasElseBranch;
7330
- const canAddElif = editable && branchType !== "else" && (branchType === "elif" || isLastBranch);
7331
- const canAddElse = editable && branchType !== "else" && isLastBranch && !hasElseBranch;
7332
- if (canAddElif) {
7333
- menuItems.push({
7334
- label: "Else if",
7335
- onSelect: () => onAddBranch("elif")
7336
- });
7337
- }
7338
- if (canAddElse) {
7339
- menuItems.push({
7340
- label: "Else",
7341
- onSelect: () => onAddBranch("else")
7342
- });
7343
- }
7344
7429
  if (editable) {
7430
+ if (!hasElseBranch) {
7431
+ menuItems.push({
7432
+ label: "Else if",
7433
+ onSelect: () => onAddBranch("elif")
7434
+ });
7435
+ menuItems.push({
7436
+ label: "Else",
7437
+ onSelect: () => onAddBranch("else")
7438
+ });
7439
+ }
7345
7440
  menuItems.push({
7346
- label: isOnlyIfBranch ? "Delete all" : "Delete",
7441
+ label: "Delete",
7347
7442
  onSelect: onDelete
7348
7443
  });
7349
7444
  }
@@ -7396,8 +7491,9 @@ function JinjaBranchHeader({
7396
7491
  BranchPopupMenu,
7397
7492
  {
7398
7493
  position: { top: 28, right: 0 },
7399
- items: menuItems.map((item) => ({
7494
+ items: menuItems.filter((item) => item.label === "Delete").map((item) => ({
7400
7495
  ...item,
7496
+ label: branchType === "if" ? "Delete all" : "Delete",
7401
7497
  onSelect: () => {
7402
7498
  setMenuSource(null);
7403
7499
  item.onSelect();
@@ -7414,7 +7510,7 @@ function JinjaBranchHeader({
7414
7510
  "button",
7415
7511
  {
7416
7512
  type: "button",
7417
- className: "jinja-ghost-btn",
7513
+ className: `jinja-ghost-btn jinja-add-btn${menuSource === "footer" ? " active" : ""}`,
7418
7514
  "aria-label": "Add branch",
7419
7515
  disabled: !canOpenFooterMenu,
7420
7516
  onMouseDown: (event) => event.preventDefault(),
@@ -7423,9 +7519,9 @@ function JinjaBranchHeader({
7423
7519
  setMenuSource((current) => current === "footer" ? null : "footer");
7424
7520
  }
7425
7521
  },
7426
- children: /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", children: [
7427
- /* @__PURE__ */ jsx6("line", { x1: "8", y1: "3", x2: "8", y2: "13" }),
7428
- /* @__PURE__ */ jsx6("line", { x1: "3", y1: "8", x2: "13", y2: "8" })
7522
+ children: /* @__PURE__ */ jsxs5("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
7523
+ /* @__PURE__ */ jsx6("path", { d: "M7.33337 11L7.33337 8.66666L5.00004 8.66666V7.33333L7.33337 7.33333V4.99999H8.66671V7.33333H11V8.66666H8.66671L8.66671 11H7.33337Z", fill: "currentColor" }),
7524
+ /* @__PURE__ */ jsx6("path", { "fill-rule": "evenodd", "clip-rule": "evenodd", d: "M8.00004 14.6667C11.6819 14.6667 14.6667 11.6819 14.6667 7.99999C14.6667 4.3181 11.6819 1.33333 8.00004 1.33333C4.31814 1.33333 1.33337 4.3181 1.33337 7.99999C1.33337 11.6819 4.31814 14.6667 8.00004 14.6667ZM13.3334 7.99999C13.3334 10.9455 10.9456 13.3333 8.00004 13.3333C5.05452 13.3333 2.66671 10.9455 2.66671 7.99999C2.66671 5.05448 5.05452 2.66666 8.00004 2.66666C10.9456 2.66666 13.3334 5.05448 13.3334 7.99999Z", fill: "currentColor" })
7429
7525
  ] })
7430
7526
  }
7431
7527
  ),
@@ -7823,7 +7919,7 @@ function createLinkPlugin() {
7823
7919
  // src/ui/plugin/dragHandlePlugin.ts
7824
7920
  import { Plugin as Plugin9, PluginKey as PluginKey7 } from "prosemirror-state";
7825
7921
  var PLUGIN_KEY = new PluginKey7("dragHandle");
7826
- var GRIP_SVG = `<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
7922
+ var GRIP_SVG = `<svg width="16" height="16" viewBox="0 0 12 12" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
7827
7923
  <circle cx="4" cy="2.5" r="1.2"/><circle cx="8" cy="2.5" r="1.2"/>
7828
7924
  <circle cx="4" cy="6" r="1.2"/><circle cx="8" cy="6" r="1.2"/>
7829
7925
  <circle cx="4" cy="9.5" r="1.2"/><circle cx="8" cy="9.5" r="1.2"/>
@@ -8433,6 +8529,7 @@ var DragHandleController = class {
8433
8529
  this.dropIndicator.className = "ab-drop-indicator";
8434
8530
  this.dropIndicator.style.display = "none";
8435
8531
  parent.appendChild(this.dropIndicator);
8532
+ this.scrollContainer = this.findScrollContainer(this.view.dom);
8436
8533
  this.tickAutoScroll();
8437
8534
  }
8438
8535
  addGlobalDragListeners(pointerId) {
@@ -8581,6 +8678,7 @@ var DragHandleController = class {
8581
8678
  while (current) {
8582
8679
  const overflow = getComputedStyle(current).overflowY;
8583
8680
  if (overflow === "auto" || overflow === "scroll") return current;
8681
+ if (overflow === "hidden" && (current.classList.contains("ps") || current.scrollHeight > current.clientHeight + 1)) return current;
8584
8682
  current = current.parentElement;
8585
8683
  }
8586
8684
  return el.parentElement ?? el;
@@ -8654,6 +8752,7 @@ function createDragHandlePlugin() {
8654
8752
  }
8655
8753
 
8656
8754
  // src/ui/plugin/todoNodeViewPlugin.tsx
8755
+ import { Plugin as Plugin10 } from "prosemirror-state";
8657
8756
  function createCheckboxDOM(checked) {
8658
8757
  const el = document.createElement("span");
8659
8758
  el.contentEditable = "false";
@@ -8736,14 +8835,63 @@ var TodoListItemView = class {
8736
8835
  return true;
8737
8836
  }
8738
8837
  };
8838
+ function createListItemValueResetPlugin() {
8839
+ return new Plugin10({
8840
+ appendTransaction(trs, oldState, newState) {
8841
+ if (!trs.some((tr2) => tr2.docChanged)) return null;
8842
+ const oldCount = oldState.doc.childCount;
8843
+ let hasNewListItems = false;
8844
+ newState.doc.descendants((node) => {
8845
+ if (hasNewListItems) return false;
8846
+ if (node.type.name === "listItem" && node.attrs.value != null) {
8847
+ hasNewListItems = true;
8848
+ }
8849
+ });
8850
+ if (!hasNewListItems) return null;
8851
+ let tr = newState.tr;
8852
+ let changed = false;
8853
+ newState.doc.descendants((node, pos) => {
8854
+ if (node.type.name !== "orderedList") return;
8855
+ let prevValue = null;
8856
+ node.forEach((li, offset) => {
8857
+ const liValue = li.attrs.value;
8858
+ if (liValue != null && liValue === prevValue) {
8859
+ const liPos = pos + 1 + offset;
8860
+ tr = tr.setNodeMarkup(liPos, void 0, { ...li.attrs, value: null });
8861
+ changed = true;
8862
+ }
8863
+ prevValue = liValue;
8864
+ });
8865
+ });
8866
+ return changed ? tr : null;
8867
+ }
8868
+ });
8869
+ }
8739
8870
  function createTodoNodeViewPlugin() {
8740
8871
  return {
8741
8872
  name: "todoNodeView",
8873
+ plugins: () => [createListItemValueResetPlugin()],
8742
8874
  nodeViews: () => ({
8743
8875
  listItem: ((node, view, getPos) => {
8744
8876
  if (node.attrs.checked == null) {
8745
8877
  const li = document.createElement("li");
8746
- return { dom: li, contentDOM: li };
8878
+ if (node.attrs.value != null) {
8879
+ li.value = node.attrs.value;
8880
+ }
8881
+ return {
8882
+ dom: li,
8883
+ contentDOM: li,
8884
+ update(updatedNode) {
8885
+ if (updatedNode.type.name !== "listItem") return false;
8886
+ if (updatedNode.attrs.checked != null) return false;
8887
+ if (updatedNode.attrs.value != null) {
8888
+ li.value = updatedNode.attrs.value;
8889
+ } else {
8890
+ li.removeAttribute("value");
8891
+ }
8892
+ return true;
8893
+ }
8894
+ };
8747
8895
  }
8748
8896
  return new TodoListItemView(node, view, getPos);
8749
8897
  })
@@ -8752,7 +8900,7 @@ function createTodoNodeViewPlugin() {
8752
8900
  }
8753
8901
 
8754
8902
  // src/ui/plugin/placeholderPlugin.ts
8755
- import { Plugin as Plugin10, PluginKey as PluginKey8 } from "prosemirror-state";
8903
+ import { Plugin as Plugin11, PluginKey as PluginKey8 } from "prosemirror-state";
8756
8904
  import { Decoration as Decoration5, DecorationSet as DecorationSet5 } from "prosemirror-view";
8757
8905
  var pluginKey2 = new PluginKey8("placeholder");
8758
8906
  function buildDecorations4(state) {
@@ -8771,7 +8919,8 @@ function buildDecorations4(state) {
8771
8919
  }
8772
8920
  const pos = $from.before($from.depth);
8773
8921
  const deco = Decoration5.node(pos, pos + parent.nodeSize, {
8774
- class: "ab-empty-paragraph"
8922
+ class: "ab-empty-paragraph",
8923
+ "data-placeholder": "Type to start writing, or press / to insert an action."
8775
8924
  });
8776
8925
  return DecorationSet5.create(doc2, [deco]);
8777
8926
  }
@@ -8780,7 +8929,7 @@ function createPlaceholderPlugin() {
8780
8929
  return {
8781
8930
  name: "placeholder",
8782
8931
  plugins: () => [
8783
- new Plugin10({
8932
+ new Plugin11({
8784
8933
  key: pluginKey2,
8785
8934
  view(editorView) {
8786
8935
  isEditable = editorView.editable;
@@ -8899,27 +9048,49 @@ function SlashCommandMenu({ view, editorState, items }) {
8899
9048
  view.focus();
8900
9049
  }
8901
9050
  }
9051
+ if (e.key === "Escape") {
9052
+ e.preventDefault();
9053
+ e.stopPropagation();
9054
+ if (range) {
9055
+ view.dispatch(view.state.tr.delete(range.from, range.to));
9056
+ }
9057
+ view.focus();
9058
+ }
8902
9059
  };
8903
9060
  document.addEventListener("keydown", onKeyDown, true);
8904
9061
  return () => document.removeEventListener("keydown", onKeyDown, true);
8905
9062
  }, [active, view, filtered, selectedIndex, range]);
8906
- if (!active || !view || !range || filtered.length === 0) return null;
8907
- const coords = view.coordsAtPos(range.from);
8908
- let top = coords.bottom + 4;
8909
- let left = coords.left;
8910
- left = Math.max(VPORT_MARGIN, Math.min(left, window.innerWidth - MENU_WIDTH - VPORT_MARGIN));
8911
- if (top + MAX_MENU_H > window.innerHeight - VPORT_MARGIN) {
8912
- top = coords.top - MAX_MENU_H - 4;
8913
- }
9063
+ const [menuPos, setMenuPos] = useState4(null);
9064
+ const menuRef = useRef4(null);
9065
+ useLayoutEffect(() => {
9066
+ if (!active || !view || !range || filtered.length === 0) {
9067
+ setMenuPos(null);
9068
+ return;
9069
+ }
9070
+ try {
9071
+ const coords = view.coordsAtPos(range.to);
9072
+ let left = coords.left;
9073
+ left = Math.max(VPORT_MARGIN, Math.min(left, window.innerWidth - MENU_WIDTH - VPORT_MARGIN));
9074
+ const menuHeight = menuRef.current?.offsetHeight ?? MAX_MENU_H;
9075
+ let top = coords.bottom + 4;
9076
+ if (top + menuHeight > window.innerHeight - VPORT_MARGIN) {
9077
+ top = coords.top - menuHeight - 4;
9078
+ }
9079
+ setMenuPos({ top, left });
9080
+ } catch {
9081
+ }
9082
+ });
9083
+ if (!active || !view || !range || filtered.length === 0 || !menuPos) return null;
8914
9084
  return createPortal2(
8915
9085
  /* @__PURE__ */ jsx7(
8916
9086
  "div",
8917
9087
  {
9088
+ ref: menuRef,
8918
9089
  className: "ab-slash-menu",
8919
9090
  style: {
8920
9091
  position: "fixed",
8921
- top,
8922
- left,
9092
+ top: menuPos.top,
9093
+ left: menuPos.left,
8923
9094
  width: MENU_WIDTH,
8924
9095
  maxHeight: MAX_MENU_H,
8925
9096
  overflowY: "auto",
@@ -9594,7 +9765,7 @@ function DropdownItem({ item, onRun }) {
9594
9765
  transition: "background 0.1s"
9595
9766
  },
9596
9767
  children: [
9597
- /* @__PURE__ */ jsx10("span", { style: { width: 24, flexShrink: 0, textAlign: "center", fontWeight: 700, fontSize: 12, fontFamily: "monospace", color: "#6210CC" }, children: item.shortLabel }),
9768
+ /* @__PURE__ */ jsx10("span", { style: { width: 24, flexShrink: 0, textAlign: "center", fontWeight: 700, fontSize: 12, fontFamily: "monospace", color: item.active ? "#6210CC" : "#666" }, children: item.shortLabel }),
9598
9769
  item.label
9599
9770
  ]
9600
9771
  }
@@ -10718,7 +10889,7 @@ function FloatingMenu({ view, editorState }) {
10718
10889
  }
10719
10890
 
10720
10891
  // src/ui/plugin/inlineSuggestPlugin.ts
10721
- import { Plugin as Plugin11, PluginKey as PluginKey9 } from "prosemirror-state";
10892
+ import { Plugin as Plugin12, PluginKey as PluginKey9 } from "prosemirror-state";
10722
10893
  import { Decoration as Decoration6, DecorationSet as DecorationSet6 } from "prosemirror-view";
10723
10894
  var inlineSuggestKey = new PluginKey9("inlineSuggest");
10724
10895
  var DEBOUNCE_MS = 600;
@@ -10727,7 +10898,7 @@ function createInlineSuggestPlugin(provider, endpoint, options) {
10727
10898
  return {
10728
10899
  name: "inlineSuggest",
10729
10900
  plugins: () => [
10730
- new Plugin11({
10901
+ new Plugin12({
10731
10902
  key: inlineSuggestKey,
10732
10903
  state: {
10733
10904
  init: () => ({ suggestion: null, anchorPos: null }),