@sendbird/actionbook-core 0.10.0 → 0.10.2

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
@@ -2020,7 +2020,7 @@ function resourceTagToMarkdown() {
2020
2020
  }
2021
2021
 
2022
2022
  // src/markdown/plugins/jumpPoint.ts
2023
- var JUMP_POINT_RE = /\^([A-Za-z_]+)\^/g;
2023
+ var JUMP_POINT_RE = /\^([\p{L}\p{N}_-]+)\^/gu;
2024
2024
  function splitTextWithJumpPoints(text2) {
2025
2025
  const results = [];
2026
2026
  let lastIndex = 0;
@@ -2677,7 +2677,7 @@ function useEditorView(config) {
2677
2677
  const view = new EditorView(container, {
2678
2678
  state,
2679
2679
  nodeViews,
2680
- editable: () => configRef.current.editable !== false,
2680
+ editable: () => configRef.current.editable === true,
2681
2681
  dispatchTransaction(tr) {
2682
2682
  const newState = view.state.apply(tr);
2683
2683
  view.updateState(newState);
@@ -2733,13 +2733,87 @@ function useEditorView(config) {
2733
2733
  // src/ui/plugin/inputRulesPlugin.ts
2734
2734
  import { InputRule, textblockTypeInputRule, wrappingInputRule } from "prosemirror-inputrules";
2735
2735
  import { TextSelection } from "prosemirror-state";
2736
+
2737
+ // src/ui/plugin/slashCommandPlugin.ts
2738
+ import { Plugin, PluginKey } from "prosemirror-state";
2739
+ var slashCommandKey = new PluginKey("slashCommand");
2740
+ var TRIGGER_RE = /(?:^|\s)(\/[^\s]*)$/;
2741
+ function deriveState(state, dismissedFrom) {
2742
+ const inactive = { active: false, range: null, query: "", parentType: "" };
2743
+ const { selection } = state;
2744
+ if (!selection.empty) return inactive;
2745
+ const { $from } = selection;
2746
+ const parentType = $from.parent.type.name;
2747
+ for (let d = $from.depth; d > 0; d--) {
2748
+ if ($from.node(d).type.name === "noteBlock") return inactive;
2749
+ }
2750
+ const textBefore = $from.parent.textBetween(0, $from.parentOffset, "\n", "\0");
2751
+ const match = TRIGGER_RE.exec(textBefore);
2752
+ if (!match) return inactive;
2753
+ const slashText = match[1];
2754
+ const slashStartInParent = textBefore.lastIndexOf(slashText);
2755
+ const from = $from.start() + slashStartInParent;
2756
+ const to = $from.pos;
2757
+ if (dismissedFrom === from) return inactive;
2758
+ return {
2759
+ active: true,
2760
+ range: { from, to },
2761
+ query: slashText.slice(1),
2762
+ parentType
2763
+ };
2764
+ }
2765
+ function createSlashCommandPlugin() {
2766
+ const plugin = new Plugin({
2767
+ key: slashCommandKey,
2768
+ state: {
2769
+ init(_, state) {
2770
+ const derived = deriveState(state, null);
2771
+ return { ...derived, _dismissedFrom: null };
2772
+ },
2773
+ apply(tr, prev, _old, newState) {
2774
+ const meta = tr.getMeta(slashCommandKey);
2775
+ let dismissedFrom = prev._dismissedFrom;
2776
+ if (meta?.dismiss !== void 0) {
2777
+ dismissedFrom = meta.dismiss;
2778
+ } else if (dismissedFrom !== null) {
2779
+ const derived2 = deriveState(newState, null);
2780
+ if (derived2.active && derived2.range && derived2.range.from !== dismissedFrom) {
2781
+ dismissedFrom = null;
2782
+ }
2783
+ }
2784
+ const derived = deriveState(newState, dismissedFrom);
2785
+ return { ...derived, _dismissedFrom: dismissedFrom };
2786
+ }
2787
+ }
2788
+ // Expose only the public fields via the key
2789
+ // (InternalState is a superset of SlashCommandState so reads work fine)
2790
+ });
2791
+ return {
2792
+ name: "slashCommand",
2793
+ plugins: () => [plugin],
2794
+ keymap: () => ({
2795
+ Escape: (state, dispatch) => {
2796
+ const s = slashCommandKey.getState(state);
2797
+ if (!s?.active || !s.range) return false;
2798
+ if (dispatch) {
2799
+ const tr = state.tr;
2800
+ tr.setMeta(slashCommandKey, { dismiss: s.range.from });
2801
+ dispatch(tr);
2802
+ }
2803
+ return true;
2804
+ }
2805
+ })
2806
+ };
2807
+ }
2808
+
2809
+ // src/ui/plugin/inputRulesPlugin.ts
2736
2810
  var HEADING_RE = /^(#{1,6})\s$/;
2737
2811
  var BLOCKQUOTE_RE = /^\s*>\s$/;
2738
2812
  var CODE_BLOCK_RE = /^```([a-zA-Z0-9]*)$/;
2739
2813
  var HR_RE = /^([-*_])\1{2,}$/;
2740
2814
  var BULLET_LIST_RE = /^\s*([-*])\s$/;
2741
2815
  var ORDERED_LIST_RE = /^(\d+)\.\s$/;
2742
- var JUMP_POINT_RE2 = /\^([A-Za-z_]+)\^$/;
2816
+ var JUMP_POINT_RE2 = /\^([\p{L}\p{N}_-]+)\^$/u;
2743
2817
  var BOLD_STAR_RE = /(?:^|[^*])\*\*([^*]+)\*\*$/;
2744
2818
  var BOLD_UNDER_RE = /(?:^|[^_])__([^_]+)__$/;
2745
2819
  var ITALIC_STAR_RE = /(?:^|[^*])\*([^*]+)\*$/;
@@ -2806,6 +2880,8 @@ function handleListInputRule(state, start, end, listType, attrs) {
2806
2880
  }
2807
2881
  function markInputRule(pattern, markType, markerLen) {
2808
2882
  return new InputRule(pattern, (state, match, start, end) => {
2883
+ const slashState = slashCommandKey.getState(state);
2884
+ if (slashState?.active) return null;
2809
2885
  const textContent2 = match[1];
2810
2886
  const fullMatch = match[0];
2811
2887
  const markedLength = markerLen * 2 + textContent2.length;
@@ -3298,9 +3374,20 @@ var enterCommand = chainCommands(
3298
3374
  );
3299
3375
  var shiftEnterCommand = (state, dispatch) => {
3300
3376
  if (dispatch) {
3301
- dispatch(
3302
- state.tr.replaceSelectionWith(hardBreak.create()).scrollIntoView()
3303
- );
3377
+ const { from, to, $from } = state.selection;
3378
+ const tr = state.tr;
3379
+ if (from !== to) tr.delete(from, to);
3380
+ const insertPos = tr.mapping.map(from);
3381
+ const marks = $from.marks();
3382
+ tr.insert(insertPos, hardBreak.create());
3383
+ const afterBr = insertPos + 1;
3384
+ tr.insertText("\u200B", afterBr);
3385
+ const cursorPos = afterBr + 1;
3386
+ if (cursorPos <= tr.doc.content.size) {
3387
+ tr.setSelection(TextSelection2.create(tr.doc, cursorPos));
3388
+ }
3389
+ if (marks.length > 0) tr.setStoredMarks(marks);
3390
+ dispatch(tr.scrollIntoView());
3304
3391
  }
3305
3392
  return true;
3306
3393
  };
@@ -3327,7 +3414,7 @@ function createKeymapPlugin() {
3327
3414
 
3328
3415
  // src/ui/plugin/markdownClipboard.ts
3329
3416
  import { DOMParser as ProseMirrorDOMParser, Fragment, Slice } from "prosemirror-model";
3330
- import { Plugin, PluginKey } from "prosemirror-state";
3417
+ import { Plugin as Plugin2, PluginKey as PluginKey2 } from "prosemirror-state";
3331
3418
 
3332
3419
  // src/ast/traverse.ts
3333
3420
  var MAX_DEPTH3 = 128;
@@ -5062,6 +5149,320 @@ function analyzeJinjaBlocks(doc2) {
5062
5149
  });
5063
5150
  }
5064
5151
 
5152
+ // src/jinja/evaluator.ts
5153
+ var KEYWORDS2 = {
5154
+ and: "AND",
5155
+ or: "OR",
5156
+ not: "NOT",
5157
+ in: "IN",
5158
+ is: "IS",
5159
+ True: "BOOL",
5160
+ False: "BOOL",
5161
+ true: "BOOL",
5162
+ false: "BOOL",
5163
+ None: "NONE",
5164
+ null: "NONE"
5165
+ };
5166
+ function tokenize(input) {
5167
+ const tokens = [];
5168
+ let i = 0;
5169
+ while (i < input.length) {
5170
+ if (/\s/.test(input[i])) {
5171
+ i++;
5172
+ continue;
5173
+ }
5174
+ if (input[i] === '"' || input[i] === "'") {
5175
+ const quote = input[i];
5176
+ let str = "";
5177
+ i++;
5178
+ while (i < input.length && input[i] !== quote) {
5179
+ if (input[i] === "\\" && i + 1 < input.length) {
5180
+ str += input[i + 1];
5181
+ i += 2;
5182
+ } else {
5183
+ str += input[i];
5184
+ i++;
5185
+ }
5186
+ }
5187
+ if (i >= input.length) return { tokens: [], error: "Unterminated string literal" };
5188
+ i++;
5189
+ tokens.push({ type: "STRING", value: str });
5190
+ continue;
5191
+ }
5192
+ if (/[0-9]/.test(input[i]) || input[i] === "-" && i + 1 < input.length && /[0-9]/.test(input[i + 1]) && (tokens.length === 0 || ["AND", "OR", "NOT", "EQ", "NEQ", "LT", "GT", "LTE", "GTE", "LPAREN", "IN", "IS"].includes(tokens[tokens.length - 1].type))) {
5193
+ let num = "";
5194
+ if (input[i] === "-") {
5195
+ num = "-";
5196
+ i++;
5197
+ }
5198
+ while (i < input.length && /[0-9]/.test(input[i])) {
5199
+ num += input[i];
5200
+ i++;
5201
+ }
5202
+ if (i < input.length && input[i] === ".") {
5203
+ num += ".";
5204
+ i++;
5205
+ while (i < input.length && /[0-9]/.test(input[i])) {
5206
+ num += input[i];
5207
+ i++;
5208
+ }
5209
+ }
5210
+ tokens.push({ type: "NUMBER", value: num });
5211
+ continue;
5212
+ }
5213
+ if (/[a-zA-Z_]/.test(input[i])) {
5214
+ let ident = "";
5215
+ while (i < input.length && /[a-zA-Z0-9_.]/.test(input[i])) {
5216
+ ident += input[i];
5217
+ i++;
5218
+ }
5219
+ const kwType = KEYWORDS2[ident];
5220
+ if (kwType) {
5221
+ tokens.push({ type: kwType, value: ident });
5222
+ } else {
5223
+ tokens.push({ type: "IDENT", value: ident });
5224
+ }
5225
+ continue;
5226
+ }
5227
+ if (i + 1 < input.length) {
5228
+ const two = input[i] + input[i + 1];
5229
+ if (two === "==") {
5230
+ tokens.push({ type: "EQ", value: "==" });
5231
+ i += 2;
5232
+ continue;
5233
+ }
5234
+ if (two === "!=") {
5235
+ tokens.push({ type: "NEQ", value: "!=" });
5236
+ i += 2;
5237
+ continue;
5238
+ }
5239
+ if (two === "<=") {
5240
+ tokens.push({ type: "LTE", value: "<=" });
5241
+ i += 2;
5242
+ continue;
5243
+ }
5244
+ if (two === ">=") {
5245
+ tokens.push({ type: "GTE", value: ">=" });
5246
+ i += 2;
5247
+ continue;
5248
+ }
5249
+ }
5250
+ if (input[i] === "<") {
5251
+ tokens.push({ type: "LT", value: "<" });
5252
+ i++;
5253
+ continue;
5254
+ }
5255
+ if (input[i] === ">") {
5256
+ tokens.push({ type: "GT", value: ">" });
5257
+ i++;
5258
+ continue;
5259
+ }
5260
+ if (input[i] === "(") {
5261
+ tokens.push({ type: "LPAREN", value: "(" });
5262
+ i++;
5263
+ continue;
5264
+ }
5265
+ if (input[i] === ")") {
5266
+ tokens.push({ type: "RPAREN", value: ")" });
5267
+ i++;
5268
+ continue;
5269
+ }
5270
+ return { tokens: [], error: `Unexpected character: ${input[i]}` };
5271
+ }
5272
+ tokens.push({ type: "EOF", value: "" });
5273
+ return { tokens };
5274
+ }
5275
+ var Parser = class {
5276
+ pos = 0;
5277
+ tokens;
5278
+ variables;
5279
+ constructor(tokens, variables) {
5280
+ this.tokens = tokens;
5281
+ this.variables = variables;
5282
+ }
5283
+ peek() {
5284
+ return this.tokens[this.pos] ?? { type: "EOF", value: "" };
5285
+ }
5286
+ advance() {
5287
+ const t = this.tokens[this.pos];
5288
+ this.pos++;
5289
+ return t;
5290
+ }
5291
+ expect(type) {
5292
+ const t = this.peek();
5293
+ if (t.type !== type) {
5294
+ throw new Error(`Expected ${type}, got ${t.type}`);
5295
+ }
5296
+ return this.advance();
5297
+ }
5298
+ // Grammar (precedence low→high):
5299
+ // expr → or_expr
5300
+ // or_expr → and_expr ('or' and_expr)*
5301
+ // and_expr → not_expr ('and' not_expr)*
5302
+ // not_expr → 'not' not_expr | cmp_expr
5303
+ // cmp_expr → primary (('=='|'!='|'<'|'>'|'<='|'>='|'in'|'is'|'is not') primary)?
5304
+ // primary → STRING | NUMBER | BOOL | NONE | IDENT | '(' expr ')'
5305
+ evaluate() {
5306
+ const result = this.orExpr();
5307
+ if (this.peek().type !== "EOF") {
5308
+ throw new Error(`Unexpected token: ${this.peek().value}`);
5309
+ }
5310
+ return result;
5311
+ }
5312
+ orExpr() {
5313
+ let left = this.andExpr();
5314
+ while (this.peek().type === "OR") {
5315
+ this.advance();
5316
+ const right = this.andExpr();
5317
+ left = isTruthy(left) || isTruthy(right);
5318
+ }
5319
+ return left;
5320
+ }
5321
+ andExpr() {
5322
+ let left = this.notExpr();
5323
+ while (this.peek().type === "AND") {
5324
+ this.advance();
5325
+ const right = this.notExpr();
5326
+ left = isTruthy(left) && isTruthy(right);
5327
+ }
5328
+ return left;
5329
+ }
5330
+ notExpr() {
5331
+ if (this.peek().type === "NOT") {
5332
+ this.advance();
5333
+ const val = this.notExpr();
5334
+ return !isTruthy(val);
5335
+ }
5336
+ return this.cmpExpr();
5337
+ }
5338
+ cmpExpr() {
5339
+ const left = this.primary();
5340
+ const op = this.peek().type;
5341
+ switch (op) {
5342
+ case "EQ":
5343
+ this.advance();
5344
+ return looseEqual(left, this.primary());
5345
+ case "NEQ":
5346
+ this.advance();
5347
+ return !looseEqual(left, this.primary());
5348
+ case "LT":
5349
+ this.advance();
5350
+ return toNumber(left) < toNumber(this.primary());
5351
+ case "GT":
5352
+ this.advance();
5353
+ return toNumber(left) > toNumber(this.primary());
5354
+ case "LTE":
5355
+ this.advance();
5356
+ return toNumber(left) <= toNumber(this.primary());
5357
+ case "GTE":
5358
+ this.advance();
5359
+ return toNumber(left) >= toNumber(this.primary());
5360
+ case "IN": {
5361
+ this.advance();
5362
+ const collection = this.primary();
5363
+ if (Array.isArray(collection)) {
5364
+ return collection.some((item) => looseEqual(item, left));
5365
+ }
5366
+ if (typeof collection === "string" && typeof left === "string") {
5367
+ return collection.includes(left);
5368
+ }
5369
+ return false;
5370
+ }
5371
+ case "IS": {
5372
+ this.advance();
5373
+ if (this.peek().type === "NOT") {
5374
+ this.advance();
5375
+ return !looseEqual(left, this.primary());
5376
+ }
5377
+ return looseEqual(left, this.primary());
5378
+ }
5379
+ default:
5380
+ return left;
5381
+ }
5382
+ }
5383
+ primary() {
5384
+ const t = this.peek();
5385
+ switch (t.type) {
5386
+ case "STRING":
5387
+ this.advance();
5388
+ return t.value;
5389
+ case "NUMBER":
5390
+ this.advance();
5391
+ return parseFloat(t.value);
5392
+ case "BOOL":
5393
+ this.advance();
5394
+ return t.value === "True" || t.value === "true";
5395
+ case "NONE":
5396
+ this.advance();
5397
+ return null;
5398
+ case "IDENT": {
5399
+ this.advance();
5400
+ return this.resolveVariable(t.value);
5401
+ }
5402
+ case "LPAREN": {
5403
+ this.advance();
5404
+ const val = this.orExpr();
5405
+ this.expect("RPAREN");
5406
+ return val;
5407
+ }
5408
+ default:
5409
+ throw new Error(`Unexpected token in primary: ${t.type} "${t.value}"`);
5410
+ }
5411
+ }
5412
+ resolveVariable(name) {
5413
+ const entry = this.variables.get(name);
5414
+ if (entry === void 0) {
5415
+ return null;
5416
+ }
5417
+ switch (entry.type) {
5418
+ case "string":
5419
+ return String(entry.value);
5420
+ case "number":
5421
+ return Number(entry.value);
5422
+ case "boolean":
5423
+ return entry.value === true || entry.value === "true";
5424
+ default:
5425
+ return String(entry.value);
5426
+ }
5427
+ }
5428
+ };
5429
+ function isTruthy(v) {
5430
+ if (v === null || v === void 0) return false;
5431
+ if (typeof v === "boolean") return v;
5432
+ if (typeof v === "number") return v !== 0;
5433
+ if (typeof v === "string") return v.length > 0;
5434
+ if (Array.isArray(v)) return v.length > 0;
5435
+ return true;
5436
+ }
5437
+ function looseEqual(a, b) {
5438
+ if (a === b) return true;
5439
+ if (a === null || b === null) return a === b;
5440
+ if (typeof a === "number" && typeof b === "string") return a === parseFloat(b);
5441
+ if (typeof a === "string" && typeof b === "number") return parseFloat(a) === b;
5442
+ return false;
5443
+ }
5444
+ function toNumber(v) {
5445
+ if (typeof v === "number") return v;
5446
+ if (typeof v === "string") return parseFloat(v) || 0;
5447
+ if (typeof v === "boolean") return v ? 1 : 0;
5448
+ return 0;
5449
+ }
5450
+ function validateCondition(condition) {
5451
+ const trimmed = condition.trim();
5452
+ if (trimmed === "") return { valid: true };
5453
+ const { tokens, error: tokenError } = tokenize(trimmed);
5454
+ if (tokenError) return { valid: false, error: tokenError };
5455
+ if (tokens.length === 0) return { valid: false, error: "Empty expression" };
5456
+ try {
5457
+ const parser = new Parser(tokens, /* @__PURE__ */ new Map());
5458
+ parser.evaluate();
5459
+ return { valid: true };
5460
+ } catch (e) {
5461
+ const msg = e instanceof Error ? e.message : "Invalid expression";
5462
+ return { valid: false, error: msg };
5463
+ }
5464
+ }
5465
+
5065
5466
  // src/tree/documentTree.ts
5066
5467
  var MAX_DEPTH6 = 128;
5067
5468
  var END_ACTION_RESOURCE_IDS = /* @__PURE__ */ new Set([
@@ -5210,7 +5611,7 @@ function buildDocumentTree(doc2) {
5210
5611
  }
5211
5612
 
5212
5613
  // src/ui/plugin/markdownClipboard.ts
5213
- var key = new PluginKey("markdownClipboard");
5614
+ var key = new PluginKey2("markdownClipboard");
5214
5615
  var MAX_PASTE_LIST_DEPTH = 3;
5215
5616
  var MAX_FLATTEN_DEPTH = 128;
5216
5617
  function flattenDeepLists(node, listDepth = 0, _recurseDepth = 0) {
@@ -5273,7 +5674,7 @@ function textToSlice(text2, view) {
5273
5674
  view.state.doc.descendants((node) => {
5274
5675
  if (node.type.name === "jumpPoint") existingIds.add(node.attrs.id);
5275
5676
  });
5276
- const deduped = text2.replace(/\^([A-Za-z_][A-Za-z0-9_]*)\^/gm, (match, id) => {
5677
+ const deduped = text2.replace(/\^([\p{L}\p{N}_-]+)\^/gmu, (match, id) => {
5277
5678
  if (existingIds.has(id)) return id;
5278
5679
  existingIds.add(id);
5279
5680
  return match;
@@ -5290,7 +5691,7 @@ function textToSlice(text2, view) {
5290
5691
  }
5291
5692
  var URL_RE = /^https?:\/\/[^\s]+$/i;
5292
5693
  function createPlugin() {
5293
- return new Plugin({
5694
+ return new Plugin2({
5294
5695
  key,
5295
5696
  props: {
5296
5697
  handlePaste(view, event) {
@@ -5356,9 +5757,9 @@ function createMarkdownClipboardPlugin() {
5356
5757
  }
5357
5758
 
5358
5759
  // src/ui/plugin/jumpPointPlugin.ts
5359
- import { Plugin as Plugin2, PluginKey as PluginKey2, TextSelection as TextSelection3 } from "prosemirror-state";
5760
+ import { Plugin as Plugin3, PluginKey as PluginKey3, TextSelection as TextSelection3 } from "prosemirror-state";
5360
5761
  import { Decoration, DecorationSet } from "prosemirror-view";
5361
- var adjacentKey = new PluginKey2("jumpPointAdjacent");
5762
+ var adjacentKey = new PluginKey3("jumpPointAdjacent");
5362
5763
  var JUMP_POINT_ADJACENT_SPEC = { jumpPointAdjacent: true };
5363
5764
  function buildDecorations(state) {
5364
5765
  const { selection } = state;
@@ -5374,10 +5775,10 @@ function buildDecorations(state) {
5374
5775
  });
5375
5776
  return DecorationSet.create(state.doc, decorations);
5376
5777
  }
5377
- var jumpPointEditKey = new PluginKey2("jumpPointEdit");
5378
- var JUMP_POINT_FULL_RE = /^\^([A-Za-z_][A-Za-z0-9_]*)\^$/;
5778
+ var jumpPointEditKey = new PluginKey3("jumpPointEdit");
5779
+ var JUMP_POINT_FULL_RE = /^\^([\p{L}\p{N}_-]+)\^$/u;
5379
5780
  function createJumpPointEditPlugin() {
5380
- return new Plugin2({
5781
+ return new Plugin3({
5381
5782
  key: jumpPointEditKey,
5382
5783
  state: {
5383
5784
  init: () => ({ rawRange: null }),
@@ -5479,7 +5880,7 @@ function createJumpPointAdjacentPlugin() {
5479
5880
  }),
5480
5881
  plugins: () => [
5481
5882
  createJumpPointEditPlugin(),
5482
- new Plugin2({
5883
+ new Plugin3({
5483
5884
  key: adjacentKey,
5484
5885
  state: {
5485
5886
  init(_, state) {
@@ -5645,9 +6046,9 @@ function createJumpPointNodeViewPlugin() {
5645
6046
  }
5646
6047
 
5647
6048
  // src/ui/plugin/jumpPointValidationPlugin.ts
5648
- import { Plugin as Plugin3, PluginKey as PluginKey3 } from "prosemirror-state";
6049
+ import { Plugin as Plugin4, PluginKey as PluginKey4 } from "prosemirror-state";
5649
6050
  import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
5650
- var pluginKey = new PluginKey3("jumpPointValidation");
6051
+ var pluginKey = new PluginKey4("jumpPointValidation");
5651
6052
  function collectJumpPointIds(state) {
5652
6053
  const ids = /* @__PURE__ */ new Set();
5653
6054
  state.doc.descendants((node) => {
@@ -5697,7 +6098,7 @@ function createJumpPointValidationPlugin() {
5697
6098
  return {
5698
6099
  name: "jumpPointValidation",
5699
6100
  plugins: () => [
5700
- new Plugin3({
6101
+ new Plugin4({
5701
6102
  key: pluginKey,
5702
6103
  state: {
5703
6104
  init(_, state) {
@@ -5813,9 +6214,9 @@ function createInlineToolTagNodeViewPlugin() {
5813
6214
  }
5814
6215
 
5815
6216
  // src/ui/plugin/jinjaDecoration.ts
5816
- import { Plugin as Plugin4, PluginKey as PluginKey4 } from "prosemirror-state";
6217
+ import { Plugin as Plugin5, PluginKey as PluginKey5 } from "prosemirror-state";
5817
6218
  import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "prosemirror-view";
5818
- var jinjaPluginKey = new PluginKey4("jinjaDecoration");
6219
+ var jinjaPluginKey = new PluginKey5("jinjaDecoration");
5819
6220
  var JINJA_TAG_RE = /\{%\s*(if|elif|else|endif)\s*([^%]*?)\s*%\}/g;
5820
6221
  function getBlockPositions(doc2) {
5821
6222
  const blocks = [];
@@ -5906,7 +6307,7 @@ function createJinjaDecorationPlugin() {
5906
6307
  return {
5907
6308
  name: "jinjaDecoration",
5908
6309
  plugins: () => [
5909
- new Plugin4({
6310
+ new Plugin5({
5910
6311
  key: jinjaPluginKey,
5911
6312
  state: {
5912
6313
  init(_, state) {
@@ -5930,7 +6331,7 @@ function createJinjaDecorationPlugin() {
5930
6331
  // src/ui/plugin/jinjaIfBlockPlugin.tsx
5931
6332
  import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
5932
6333
  import { createRoot as createRoot2 } from "react-dom/client";
5933
- import { Plugin as Plugin5, TextSelection as TextSelection4 } from "prosemirror-state";
6334
+ import { Plugin as Plugin6, TextSelection as TextSelection4 } from "prosemirror-state";
5934
6335
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
5935
6336
  var PLACEHOLDER_TEXT = "Describe what AI agent should do when this condition is met";
5936
6337
  var CONDITION_PLACEHOLDER = "Write a condition in natural language";
@@ -6029,6 +6430,18 @@ var JINJA_STYLES = `
6029
6430
  letter-spacing: -0.3px;
6030
6431
  }
6031
6432
 
6433
+ .jinja-branch-condition-invalid {
6434
+ text-decoration: wavy underline #D9352C;
6435
+ text-underline-offset: 3px;
6436
+ }
6437
+
6438
+ .jinja-condition-error-icon {
6439
+ color: #D9352C;
6440
+ margin-left: 4px;
6441
+ font-size: 12px;
6442
+ flex-shrink: 0;
6443
+ }
6444
+
6032
6445
  .jinja-token-variable {
6033
6446
  color: #4141B2;
6034
6447
  }
@@ -6477,11 +6890,14 @@ function ConditionDisplay({
6477
6890
  }
6478
6891
  );
6479
6892
  }
6893
+ const validation = condition.length > 0 ? validateCondition(condition) : null;
6894
+ const isInvalid = validation != null && !validation.valid;
6480
6895
  return /* @__PURE__ */ jsx6(
6481
6896
  "button",
6482
6897
  {
6483
6898
  type: "button",
6484
- className: "jinja-branch-condition",
6899
+ className: `jinja-branch-condition${isInvalid ? " jinja-branch-condition-invalid" : ""}`,
6900
+ title: isInvalid ? validation.error : void 0,
6485
6901
  onMouseDown: (event) => event.preventDefault(),
6486
6902
  onClick: () => {
6487
6903
  if (editable) {
@@ -6489,7 +6905,10 @@ function ConditionDisplay({
6489
6905
  setIsEditing(true);
6490
6906
  }
6491
6907
  },
6492
- children: condition.length > 0 ? renderCondition(condition) : /* @__PURE__ */ jsx6("span", { className: "jinja-condition-placeholder", children: CONDITION_PLACEHOLDER })
6908
+ children: condition.length > 0 ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
6909
+ renderCondition(condition),
6910
+ isInvalid && /* @__PURE__ */ jsx6("span", { className: "jinja-condition-error-icon", children: "\u26A0" })
6911
+ ] }) : /* @__PURE__ */ jsx6("span", { className: "jinja-condition-placeholder", children: CONDITION_PLACEHOLDER })
6493
6912
  }
6494
6913
  );
6495
6914
  }
@@ -6766,7 +7185,7 @@ var JinjaIfBranchView = class {
6766
7185
  {
6767
7186
  branchType,
6768
7187
  condition,
6769
- editable: this.view.editable,
7188
+ editable: this.view.editable && this.view.dom.getAttribute("contenteditable") !== "false",
6770
7189
  isLastBranch: showAddButton,
6771
7190
  hasElseBranch: context && nodePos != null ? getElseBranch(this.view, nodePos) : false,
6772
7191
  onConditionChange: (value) => {
@@ -6824,7 +7243,7 @@ var JinjaIfBranchView = class {
6824
7243
  };
6825
7244
  function createEditableWatcherPlugin() {
6826
7245
  let lastEditable = null;
6827
- return new Plugin5({
7246
+ return new Plugin6({
6828
7247
  view(editorView) {
6829
7248
  lastEditable = editorView.editable;
6830
7249
  return {
@@ -6855,10 +7274,10 @@ function createJinjaIfBlockPlugin() {
6855
7274
 
6856
7275
  // src/ui/plugin/linkPlugin.ts
6857
7276
  import { InputRule as InputRule2, inputRules as inputRules2 } from "prosemirror-inputrules";
6858
- import { Plugin as Plugin6, PluginKey as PluginKey5, TextSelection as TextSelection5 } from "prosemirror-state";
7277
+ import { Plugin as Plugin7, PluginKey as PluginKey6, TextSelection as TextSelection5 } from "prosemirror-state";
6859
7278
  var LINK_INPUT_RE = /\\?\[([^\]]*?)\\?\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
6860
7279
  var LINK_FULL_RE = /^\\?\[([^\]]*?)\\?\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
6861
- var linkEditKey = new PluginKey5("linkEdit");
7280
+ var linkEditKey = new PluginKey6("linkEdit");
6862
7281
  function getMarkRange($pos, type) {
6863
7282
  const { parentOffset } = $pos;
6864
7283
  let child = $pos.parent.childAfter(parentOffset);
@@ -6906,7 +7325,7 @@ function explodeLinkToRaw(state) {
6906
7325
  return tr;
6907
7326
  }
6908
7327
  function createLinkEditPlugin() {
6909
- return new Plugin6({
7328
+ return new Plugin7({
6910
7329
  key: linkEditKey,
6911
7330
  state: {
6912
7331
  init: () => ({ rawRange: null }),
@@ -6976,8 +7395,8 @@ function createLinkInputRule() {
6976
7395
  );
6977
7396
  }
6978
7397
  function createLinkClickPlugin() {
6979
- return new Plugin6({
6980
- key: new PluginKey5("linkClick"),
7398
+ return new Plugin7({
7399
+ key: new PluginKey6("linkClick"),
6981
7400
  props: {
6982
7401
  handleDOMEvents: {
6983
7402
  click: (_view, event) => {
@@ -7029,8 +7448,8 @@ function createLinkPlugin() {
7029
7448
  }
7030
7449
 
7031
7450
  // src/ui/plugin/dragHandlePlugin.ts
7032
- import { Plugin as Plugin7, PluginKey as PluginKey6 } from "prosemirror-state";
7033
- var PLUGIN_KEY = new PluginKey6("dragHandle");
7451
+ import { Plugin as Plugin8, PluginKey as PluginKey7 } from "prosemirror-state";
7452
+ var PLUGIN_KEY = new PluginKey7("dragHandle");
7034
7453
  var GRIP_SVG = `<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
7035
7454
  <circle cx="4" cy="2.5" r="1.2"/><circle cx="8" cy="2.5" r="1.2"/>
7036
7455
  <circle cx="4" cy="6" r="1.2"/><circle cx="8" cy="6" r="1.2"/>
@@ -7837,7 +8256,7 @@ function createDragHandlePlugin() {
7837
8256
  plugins: () => {
7838
8257
  let controller = null;
7839
8258
  return [
7840
- new Plugin7({
8259
+ new Plugin8({
7841
8260
  key: PLUGIN_KEY,
7842
8261
  view(editorView) {
7843
8262
  controller = new DragHandleController(editorView);
@@ -7960,10 +8379,10 @@ function createTodoNodeViewPlugin() {
7960
8379
  }
7961
8380
 
7962
8381
  // src/ui/plugin/placeholderPlugin.ts
7963
- import { Plugin as Plugin8, PluginKey as PluginKey7 } from "prosemirror-state";
8382
+ import { Plugin as Plugin9, PluginKey as PluginKey8 } from "prosemirror-state";
7964
8383
  import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "prosemirror-view";
7965
8384
  var PLACEHOLDER_TEXT2 = "Type to start writing, or press / to insert an action.";
7966
- var pluginKey2 = new PluginKey7("placeholder");
8385
+ var pluginKey2 = new PluginKey8("placeholder");
7967
8386
  function buildDecorations4(state) {
7968
8387
  const { doc: doc2, selection } = state;
7969
8388
  if (!selection.empty) return DecorationSet4.empty;
@@ -7989,7 +8408,7 @@ function createPlaceholderPlugin() {
7989
8408
  return {
7990
8409
  name: "placeholder",
7991
8410
  plugins: () => [
7992
- new Plugin8({
8411
+ new Plugin9({
7993
8412
  key: pluginKey2,
7994
8413
  state: {
7995
8414
  init(_, state) {
@@ -8012,78 +8431,6 @@ function createPlaceholderPlugin() {
8012
8431
  };
8013
8432
  }
8014
8433
 
8015
- // src/ui/plugin/slashCommandPlugin.ts
8016
- import { Plugin as Plugin9, PluginKey as PluginKey8 } from "prosemirror-state";
8017
- var slashCommandKey = new PluginKey8("slashCommand");
8018
- var TRIGGER_RE = /(?:^|\s)(\/[^\s]*)$/;
8019
- function deriveState(state, dismissedFrom) {
8020
- const inactive = { active: false, range: null, query: "", parentType: "" };
8021
- const { selection } = state;
8022
- if (!selection.empty) return inactive;
8023
- const { $from } = selection;
8024
- const parentType = $from.parent.type.name;
8025
- for (let d = $from.depth; d > 0; d--) {
8026
- if ($from.node(d).type.name === "noteBlock") return inactive;
8027
- }
8028
- const textBefore = $from.parent.textBetween(0, $from.parentOffset, "\n", "\0");
8029
- const match = TRIGGER_RE.exec(textBefore);
8030
- if (!match) return inactive;
8031
- const slashText = match[1];
8032
- const slashStartInParent = textBefore.lastIndexOf(slashText);
8033
- const from = $from.start() + slashStartInParent;
8034
- const to = $from.pos;
8035
- if (dismissedFrom === from) return inactive;
8036
- return {
8037
- active: true,
8038
- range: { from, to },
8039
- query: slashText.slice(1),
8040
- parentType
8041
- };
8042
- }
8043
- function createSlashCommandPlugin() {
8044
- const plugin = new Plugin9({
8045
- key: slashCommandKey,
8046
- state: {
8047
- init(_, state) {
8048
- const derived = deriveState(state, null);
8049
- return { ...derived, _dismissedFrom: null };
8050
- },
8051
- apply(tr, prev, _old, newState) {
8052
- const meta = tr.getMeta(slashCommandKey);
8053
- let dismissedFrom = prev._dismissedFrom;
8054
- if (meta?.dismiss !== void 0) {
8055
- dismissedFrom = meta.dismiss;
8056
- } else if (dismissedFrom !== null) {
8057
- const derived2 = deriveState(newState, null);
8058
- if (derived2.active && derived2.range && derived2.range.from !== dismissedFrom) {
8059
- dismissedFrom = null;
8060
- }
8061
- }
8062
- const derived = deriveState(newState, dismissedFrom);
8063
- return { ...derived, _dismissedFrom: dismissedFrom };
8064
- }
8065
- }
8066
- // Expose only the public fields via the key
8067
- // (InternalState is a superset of SlashCommandState so reads work fine)
8068
- });
8069
- return {
8070
- name: "slashCommand",
8071
- plugins: () => [plugin],
8072
- keymap: () => ({
8073
- Escape: (state, dispatch) => {
8074
- const s = slashCommandKey.getState(state);
8075
- if (!s?.active || !s.range) return false;
8076
- if (dispatch) {
8077
- const tr = state.tr;
8078
- tr.setMeta(slashCommandKey, { dismiss: s.range.from });
8079
- dispatch(tr);
8080
- }
8081
- return true;
8082
- }
8083
- })
8084
- };
8085
- }
8086
-
8087
8434
  // src/ui/components/SlashCommandMenu.tsx
8088
8435
  import React4, { useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState4 } from "react";
8089
8436
  import { createPortal } from "react-dom";
@@ -9956,6 +10303,22 @@ function createInlineSuggestPlugin(provider, endpoint, options) {
9956
10303
  ]
9957
10304
  };
9958
10305
  }
10306
+
10307
+ // src/ui/bridge/jinjaValidation.ts
10308
+ function hasInvalidJinjaConditions(state) {
10309
+ let found = false;
10310
+ state.doc.descendants((node) => {
10311
+ if (found) return false;
10312
+ if (node.type.name === "jinjaIfBranch") {
10313
+ const condition = String(node.attrs.condition ?? "");
10314
+ if (condition && !validateCondition(condition).valid) {
10315
+ found = true;
10316
+ }
10317
+ return false;
10318
+ }
10319
+ });
10320
+ return found;
10321
+ }
9959
10322
  export {
9960
10323
  ActionbookRenderer,
9961
10324
  DocumentTreeView,
@@ -9989,6 +10352,7 @@ export {
9989
10352
  createTodoNodeViewPlugin,
9990
10353
  hasBrokenAnchorRefs,
9991
10354
  hasDuplicateJumpPoints,
10355
+ hasInvalidJinjaConditions,
9992
10356
  inlineSuggestKey,
9993
10357
  slashCommandKey,
9994
10358
  toProseMirrorJSON,