@sendbird/actionbook-core 0.10.5 → 0.10.7

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
  },
@@ -1250,7 +1255,7 @@ var RESOURCE_TAG_TYPES = ["tool", "manual", "agent_message_template", "handoff",
1250
1255
 
1251
1256
  // src/compat/prosemirror.ts
1252
1257
  var MAX_DEPTH = 128;
1253
- var ALLOWED_URL_PROTOCOLS = /^(https?:|mailto:|tel:|#|\/)/i;
1258
+ var ALLOWED_URL_PROTOCOLS = /^(https?:|mailto:|tel:|#|\/|www\.)/i;
1254
1259
  var LIST_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList"]);
1255
1260
  function isLooseList(items) {
1256
1261
  return items.some((li) => {
@@ -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) {
@@ -2055,7 +2064,7 @@ function jumpPointToMarkdown() {
2055
2064
 
2056
2065
  // src/markdown/mdastAdapter.ts
2057
2066
  var MAX_DEPTH2 = 128;
2058
- var ALLOWED_URL_PROTOCOLS2 = /^(https?:|mailto:|tel:|#|\/)/i;
2067
+ var ALLOWED_URL_PROTOCOLS2 = /^(https?:|mailto:|tel:|#|\/|www\.)/i;
2059
2068
  function sanitizeUrl(url) {
2060
2069
  if (ALLOWED_URL_PROTOCOLS2.test(url)) return url;
2061
2070
  return "";
@@ -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
  ];
@@ -2415,10 +2432,24 @@ function linkHandler(node, parent, state, info) {
2415
2432
  const titlePart = title ? ` "${title.replace(/"/g, '\\"')}"` : "";
2416
2433
  return `[${childrenText}](${url}${titlePart})`;
2417
2434
  }
2435
+ function listItemHandler(node, parent, state, info) {
2436
+ const value = node._ordinalValue;
2437
+ if (value != null && parent && parent.ordered) {
2438
+ const list = parent;
2439
+ const savedStart = list.start;
2440
+ const idx = list.children.indexOf(node);
2441
+ list.start = value - idx;
2442
+ const result = defaultHandlers3.listItem(node, parent, state, info);
2443
+ list.start = savedStart;
2444
+ return result;
2445
+ }
2446
+ return defaultHandlers3.listItem(node, parent, state, info);
2447
+ }
2418
2448
  function serializeToMarkdown(doc2) {
2419
2449
  const mdastTree = toMdast(doc2);
2420
2450
  const raw = toMarkdown(mdastTree, {
2421
2451
  bullet: "-",
2452
+ bulletOrdered: ".",
2422
2453
  rule: "-",
2423
2454
  listItemIndent: "one",
2424
2455
  incrementListMarker: true,
@@ -2434,6 +2465,7 @@ function serializeToMarkdown(doc2) {
2434
2465
  handlers: {
2435
2466
  text: textHandler,
2436
2467
  link: linkHandler,
2468
+ listItem: listItemHandler,
2437
2469
  ...resourceTagToMarkdown().handlers,
2438
2470
  ...jumpPointToMarkdown().handlers
2439
2471
  },
@@ -2444,7 +2476,7 @@ function serializeToMarkdown(doc2) {
2444
2476
  }
2445
2477
 
2446
2478
  // src/ui/bridge/toProseMirrorJSON.ts
2447
- var ALLOWED_URL_PROTOCOLS3 = /^(https?:|mailto:|tel:|#|\/)/i;
2479
+ var ALLOWED_URL_PROTOCOLS3 = /^(https?:|mailto:|tel:|#|\/|www\.)/i;
2448
2480
  function assertNever(value) {
2449
2481
  throw new Error(`Unexpected AST node: ${JSON.stringify(value)}`);
2450
2482
  }
@@ -2569,8 +2601,11 @@ function convertBlock2(node) {
2569
2601
  type: "listItem",
2570
2602
  content: node.content.flatMap(convertBlockToArray)
2571
2603
  };
2572
- if (node.checked != null) {
2573
- result.attrs = { checked: node.checked };
2604
+ if (node.checked != null || node.value != null) {
2605
+ result.attrs = {
2606
+ ...node.checked != null ? { checked: node.checked } : {},
2607
+ ...node.value != null ? { value: node.value } : {}
2608
+ };
2574
2609
  }
2575
2610
  return result;
2576
2611
  }
@@ -2627,7 +2662,184 @@ function astNodesToJSONContent(nodes) {
2627
2662
  return nodes.flatMap(convertBlockToArray);
2628
2663
  }
2629
2664
 
2665
+ // src/ui/styles/editorTextStyles.ts
2666
+ var EDITOR_TEXT_STYLES = `
2667
+ /* \u2500\u2500 CSS custom properties (override on .ProseMirror to customize) \u2500\u2500 */
2668
+ .ProseMirror {
2669
+ /* Font families */
2670
+ --ab-font-heading: 'Gellix', sans-serif;
2671
+ --ab-font-body: 'SF Pro Text', -apple-system, BlinkMacSystemFont, sans-serif;
2672
+ --ab-font-code: 'Roboto Mono', monospace;
2673
+
2674
+ /* Body text */
2675
+ --ab-body-size: 14px;
2676
+ --ab-body-weight: 400;
2677
+ --ab-body-line-height: 20px;
2678
+ --ab-body-letter-spacing: -0.1px;
2679
+ --ab-color-body: #0d0d0d;
2680
+
2681
+ /* H1 */
2682
+ --ab-h1-size: 24px;
2683
+ --ab-h1-weight: 700;
2684
+ --ab-h1-line-height: 32px;
2685
+ --ab-h1-letter-spacing: -0.25px;
2686
+
2687
+ /* H2 */
2688
+ --ab-h2-size: 20px;
2689
+ --ab-h2-weight: 700;
2690
+ --ab-h2-line-height: 24px;
2691
+ --ab-h2-letter-spacing: -0.25px;
2692
+
2693
+ /* H3 */
2694
+ --ab-h3-size: 18px;
2695
+ --ab-h3-weight: 700;
2696
+ --ab-h3-line-height: 24px;
2697
+ --ab-h3-letter-spacing: -0.25px;
2698
+
2699
+ /* Inline code */
2700
+ --ab-code-size: 13px;
2701
+ --ab-code-weight: 400;
2702
+ --ab-code-line-height: 20px;
2703
+ --ab-code-letter-spacing: -0.3px;
2704
+ --ab-code-bg: #ececec;
2705
+ --ab-code-padding: 0 8px;
2706
+ --ab-code-radius: 2px;
2707
+
2708
+ /* Code block */
2709
+ --ab-pre-bg: #ececec;
2710
+ --ab-pre-radius: 4px;
2711
+ --ab-pre-padding: 16px 8px 16px 24px;
2712
+
2713
+ /* Link */
2714
+ --ab-link-color: #6210cc;
2715
+ --ab-link-weight: 500;
2716
+
2717
+ /* Blockquote */
2718
+ --ab-blockquote-border-color: #0d0d0d;
2719
+
2720
+ /* HR */
2721
+ --ab-hr-color: #e0e0e0;
2722
+
2723
+ /* Block spacing */
2724
+ --ab-block-gap: 16px;
2725
+ }
2726
+
2727
+ /* \u2500\u2500 Base text \u2500\u2500 */
2728
+ .ProseMirror {
2729
+ font-family: var(--ab-font-body);
2730
+ font-size: var(--ab-body-size);
2731
+ font-weight: var(--ab-body-weight);
2732
+ line-height: var(--ab-body-line-height);
2733
+ letter-spacing: var(--ab-body-letter-spacing);
2734
+ color: var(--ab-color-body);
2735
+ }
2736
+
2737
+ .ProseMirror > * + * {
2738
+ margin-top: var(--ab-block-gap);
2739
+ }
2740
+
2741
+ /* \u2500\u2500 Headings \u2500\u2500 */
2742
+ .ProseMirror h1 {
2743
+ font-family: var(--ab-font-heading);
2744
+ font-size: var(--ab-h1-size);
2745
+ font-weight: var(--ab-h1-weight);
2746
+ line-height: var(--ab-h1-line-height);
2747
+ letter-spacing: var(--ab-h1-letter-spacing);
2748
+ }
2749
+
2750
+ .ProseMirror h2 {
2751
+ font-family: var(--ab-font-heading);
2752
+ font-size: var(--ab-h2-size);
2753
+ font-weight: var(--ab-h2-weight);
2754
+ line-height: var(--ab-h2-line-height);
2755
+ letter-spacing: var(--ab-h2-letter-spacing);
2756
+ }
2757
+
2758
+ .ProseMirror h3 {
2759
+ font-family: var(--ab-font-heading);
2760
+ font-size: var(--ab-h3-size);
2761
+ font-weight: var(--ab-h3-weight);
2762
+ line-height: var(--ab-h3-line-height);
2763
+ letter-spacing: var(--ab-h3-letter-spacing);
2764
+ }
2765
+
2766
+ /* \u2500\u2500 Inline code \u2500\u2500 */
2767
+ .ProseMirror code {
2768
+ font-family: var(--ab-font-code);
2769
+ font-size: var(--ab-code-size);
2770
+ font-weight: var(--ab-code-weight);
2771
+ line-height: var(--ab-code-line-height);
2772
+ letter-spacing: var(--ab-code-letter-spacing);
2773
+ background: var(--ab-code-bg);
2774
+ padding: var(--ab-code-padding);
2775
+ border-radius: var(--ab-code-radius);
2776
+ }
2777
+
2778
+ /* \u2500\u2500 Code block \u2500\u2500 */
2779
+ .ProseMirror pre {
2780
+ font-family: var(--ab-font-code);
2781
+ font-size: var(--ab-code-size);
2782
+ font-weight: var(--ab-code-weight);
2783
+ line-height: var(--ab-code-line-height);
2784
+ letter-spacing: var(--ab-code-letter-spacing);
2785
+ color: var(--ab-color-body);
2786
+ background: var(--ab-pre-bg);
2787
+ border-radius: var(--ab-pre-radius);
2788
+ padding: var(--ab-pre-padding);
2789
+ overflow-x: auto;
2790
+ white-space: pre;
2791
+ }
2792
+
2793
+ .ProseMirror pre code {
2794
+ background: transparent;
2795
+ padding: 0;
2796
+ border-radius: 0;
2797
+ font-size: inherit;
2798
+ }
2799
+
2800
+ /* \u2500\u2500 Link \u2500\u2500 */
2801
+ .ProseMirror a {
2802
+ color: var(--ab-link-color);
2803
+ font-weight: var(--ab-link-weight);
2804
+ text-decoration: none;
2805
+ }
2806
+
2807
+ .ProseMirror a:hover {
2808
+ text-decoration: none;
2809
+ }
2810
+
2811
+ /* \u2500\u2500 Blockquote \u2500\u2500 */
2812
+ .ProseMirror blockquote {
2813
+ border-left: 2px solid var(--ab-blockquote-border-color);
2814
+ border-radius: 0;
2815
+ padding-left: 20px;
2816
+ color: var(--ab-color-body);
2817
+ }
2818
+
2819
+ /* \u2500\u2500 HR \u2500\u2500 */
2820
+ .ProseMirror hr {
2821
+ border: none;
2822
+ height: 1px;
2823
+ background: var(--ab-hr-color);
2824
+ margin: 8px 0;
2825
+ }
2826
+ `;
2827
+
2630
2828
  // src/ui/hooks/useEditorView.ts
2829
+ var TEXT_STYLE_ID = "ab-editor-text-styles";
2830
+ var textStyleRefCount = 0;
2831
+ function acquireTextStyles() {
2832
+ if (textStyleRefCount++ > 0) return;
2833
+ if (document.getElementById(TEXT_STYLE_ID)) return;
2834
+ const el = document.createElement("style");
2835
+ el.id = TEXT_STYLE_ID;
2836
+ el.textContent = EDITOR_TEXT_STYLES;
2837
+ document.head.appendChild(el);
2838
+ }
2839
+ function releaseTextStyles() {
2840
+ if (--textStyleRefCount > 0) return;
2841
+ document.getElementById(TEXT_STYLE_ID)?.remove();
2842
+ }
2631
2843
  function docToState(doc2, plugins) {
2632
2844
  const pmJSON = doc2 ? toProseMirrorJSON(doc2) : { type: "doc", content: [{ type: "paragraph" }] };
2633
2845
  const pmDoc = PMNode.fromJSON(actionbookSchema, pmJSON);
@@ -2666,17 +2878,20 @@ function useEditorView(config) {
2666
2878
  if (viewInstanceRef.current) {
2667
2879
  viewInstanceRef.current.destroy();
2668
2880
  viewInstanceRef.current = null;
2881
+ releaseTextStyles();
2669
2882
  }
2670
2883
  containerRef.current = container;
2671
2884
  if (!container) {
2672
2885
  forceUpdate((n) => n + 1);
2673
2886
  return;
2674
2887
  }
2888
+ acquireTextStyles();
2675
2889
  const { pmPlugins, nodeViews } = createPluginArray(configRef.current.plugins ?? []);
2676
2890
  const state = docToState(configRef.current.content, pmPlugins);
2677
2891
  const view = new EditorView(container, {
2678
2892
  state,
2679
2893
  nodeViews,
2894
+ attributes: { spellcheck: "false" },
2680
2895
  editable: () => configRef.current.editable === true,
2681
2896
  dispatchTransaction(tr) {
2682
2897
  const newState = view.state.apply(tr);
@@ -2714,8 +2929,11 @@ function useEditorView(config) {
2714
2929
  viewInstanceRef.current?.focus();
2715
2930
  }, []);
2716
2931
  const destroy = useCallback(() => {
2717
- viewInstanceRef.current?.destroy();
2718
- viewInstanceRef.current = null;
2932
+ if (viewInstanceRef.current) {
2933
+ viewInstanceRef.current.destroy();
2934
+ viewInstanceRef.current = null;
2935
+ releaseTextStyles();
2936
+ }
2719
2937
  }, []);
2720
2938
  const getView = useCallback(() => viewInstanceRef.current, []);
2721
2939
  return {
@@ -2736,6 +2954,7 @@ import { TextSelection } from "prosemirror-state";
2736
2954
 
2737
2955
  // src/ui/plugin/slashCommandPlugin.ts
2738
2956
  import { Plugin, PluginKey } from "prosemirror-state";
2957
+ import { Decoration, DecorationSet } from "prosemirror-view";
2739
2958
  var slashCommandKey = new PluginKey("slashCommand");
2740
2959
  var TRIGGER_RE = /(?:^|\s)(\/[^\s]*)$/;
2741
2960
  function deriveState(state, dismissedFrom) {
@@ -2784,9 +3003,30 @@ function createSlashCommandPlugin() {
2784
3003
  const derived = deriveState(newState, dismissedFrom);
2785
3004
  return { ...derived, _dismissedFrom: dismissedFrom };
2786
3005
  }
3006
+ },
3007
+ props: {
3008
+ decorations(state) {
3009
+ const s = plugin.getState(state);
3010
+ if (!s?.active || !s.range) return DecorationSet.empty;
3011
+ const decos = [
3012
+ // Inline highlight for the slash command text
3013
+ Decoration.inline(s.range.from, s.range.to, {
3014
+ class: "ab-slash-trigger"
3015
+ })
3016
+ ];
3017
+ if (s.query.endsWith(":")) {
3018
+ decos.push(
3019
+ Decoration.widget(s.range.to, () => {
3020
+ const span = document.createElement("span");
3021
+ span.className = "ab-slash-trigger-placeholder";
3022
+ span.textContent = "Type a name";
3023
+ return span;
3024
+ }, { side: 1 })
3025
+ );
3026
+ }
3027
+ return DecorationSet.create(state.doc, decos);
3028
+ }
2787
3029
  }
2788
- // Expose only the public fields via the key
2789
- // (InternalState is a superset of SlashCommandState so reads work fine)
2790
3030
  });
2791
3031
  return {
2792
3032
  name: "slashCommand",
@@ -2814,23 +3054,28 @@ var HR_RE = /^([-*_])\1{2,}$/;
2814
3054
  var BULLET_LIST_RE = /^\s*([-*])\s$/;
2815
3055
  var ORDERED_LIST_RE = /^(\d+)\.\s$/;
2816
3056
  var JUMP_POINT_RE2 = /\^([\p{L}\p{N}_-]+)\^$/u;
2817
- var BOLD_STAR_RE = /(?:^|[^*])\*\*([^*]+)\*\*$/;
2818
- var BOLD_UNDER_RE = /(?:^|[^_])__([^_]+)__$/;
2819
- var ITALIC_STAR_RE = /(?:^|[^*])\*([^*]+)\*$/;
2820
- var ITALIC_UNDER_RE = /(?:^|[^_])_([^_]+)_$/;
3057
+ var BOLD_RE = /(?:^|[^*])\*\*([^*]+)\*\*$/;
3058
+ var ITALIC_RE = /(?:^|[^_])__([^_]+)__$/;
2821
3059
  var STRIKE_RE = /(?:^|[^~])~~([^~]+)~~$/;
2822
3060
  var CODE_RE = /(?:^|[^`])`([^`]+)`$/;
2823
3061
  function findParentList(state, pos) {
2824
3062
  const $pos = state.doc.resolve(pos);
2825
- const { bulletList: blType, orderedList: olType } = actionbookSchema.nodes;
2826
3063
  for (let d = $pos.depth; d > 0; d--) {
2827
3064
  const node = $pos.node(d);
2828
- if (node.type === blType || node.type === olType) {
3065
+ const name = node.type.name;
3066
+ if (name === "bulletList" || name === "orderedList") {
2829
3067
  return { depth: d, node };
2830
3068
  }
2831
3069
  }
2832
3070
  return null;
2833
3071
  }
3072
+ function isInsideList(state, pos) {
3073
+ const $pos = state.doc.resolve(pos);
3074
+ for (let d = $pos.depth; d > 0; d--) {
3075
+ if ($pos.node(d).type.name === "listItem") return true;
3076
+ }
3077
+ return false;
3078
+ }
2834
3079
  var NOOP = /* @__PURE__ */ Symbol("noop");
2835
3080
  function handleListInputRule(state, start, end, listType, attrs) {
2836
3081
  const resolvePos = Math.max(start, end - 1);
@@ -2841,27 +3086,46 @@ function handleListInputRule(state, start, end, listType, attrs) {
2841
3086
  const paraContentSize = $from.parent.content.size;
2842
3087
  const matchedLen = end - start;
2843
3088
  const isMarkerOnly = paraContentSize === matchedLen;
2844
- if (listNode.type === listType) {
2845
- if (isMarkerOnly && listType === actionbookSchema.nodes.orderedList && attrs?.start != null) {
2846
- const newStart = attrs.start;
2847
- const currentStart = listNode.attrs.start ?? 1;
2848
- if (newStart !== currentStart) {
2849
- const listStart = $from.before(listDepth);
3089
+ if (listNode.type.name === listType.name) {
3090
+ if (listType.name === "orderedList" && attrs?.start != null) {
3091
+ const newValue = attrs.start;
3092
+ let liDepth2 = -1;
3093
+ for (let d = $from.depth; d > 0; d--) {
3094
+ if ($from.node(d).type.name === "listItem") {
3095
+ liDepth2 = d;
3096
+ break;
3097
+ }
3098
+ }
3099
+ if (liDepth2 > 0) {
3100
+ const liIndex2 = $from.index(listDepth);
3101
+ const listStartNum = listNode.attrs.start ?? 1;
3102
+ const naturalNumber = listStartNum + liIndex2;
3103
+ const currentValue = $from.node(liDepth2).attrs.value;
3104
+ if (currentValue === newValue || currentValue == null && newValue === naturalNumber) {
3105
+ const tr3 = state.tr.delete(start, end);
3106
+ tr3.setSelection(TextSelection.near(tr3.doc.resolve(tr3.mapping.map(start))));
3107
+ return tr3;
3108
+ }
2850
3109
  const tr2 = state.tr.delete(start, end);
2851
- tr2.setNodeMarkup(tr2.mapping.map(listStart), void 0, { ...listNode.attrs, start: newStart });
2852
- tr2.setSelection(TextSelection.near(tr2.doc.resolve(tr2.mapping.map(start))));
2853
- return tr2;
3110
+ const $afterDelete = tr2.doc.resolve(tr2.mapping.map(resolvePos));
3111
+ for (let d = $afterDelete.depth; d > 0; d--) {
3112
+ if ($afterDelete.node(d).type.name === "listItem") {
3113
+ const pos = $afterDelete.before(d);
3114
+ const node = $afterDelete.node(d);
3115
+ tr2.setNodeMarkup(pos, void 0, { ...node.attrs, value: newValue });
3116
+ tr2.setSelection(TextSelection.near(tr2.doc.resolve(tr2.mapping.map(start))));
3117
+ return tr2;
3118
+ }
3119
+ }
2854
3120
  }
2855
3121
  }
2856
3122
  return NOOP;
2857
3123
  }
2858
- if (!isMarkerOnly) return NOOP;
2859
- const { listItem: liType } = actionbookSchema.nodes;
2860
3124
  const tr = state.tr.delete(start, end);
2861
3125
  const $pos = tr.doc.resolve(tr.mapping.map(resolvePos));
2862
3126
  let liDepth = -1;
2863
3127
  for (let d = $pos.depth; d > 0; d--) {
2864
- if ($pos.node(d).type === liType) {
3128
+ if ($pos.node(d).type.name === "listItem") {
2865
3129
  liDepth = d;
2866
3130
  break;
2867
3131
  }
@@ -2917,9 +3181,16 @@ function createInputRulesPlugin() {
2917
3181
  level: match[1].length
2918
3182
  }))
2919
3183
  );
2920
- rules.push(wrappingInputRule(BLOCKQUOTE_RE, actionbookSchema.nodes.blockquote));
3184
+ rules.push(
3185
+ new InputRule(BLOCKQUOTE_RE, (state, match, start, end) => {
3186
+ if (isInsideList(state, start)) return null;
3187
+ const fallback = wrappingInputRule(BLOCKQUOTE_RE, actionbookSchema.nodes.blockquote);
3188
+ return fallback.handler(state, match, start, end);
3189
+ })
3190
+ );
2921
3191
  rules.push(
2922
3192
  new InputRule(CODE_BLOCK_RE, (state, match, start, end) => {
3193
+ if (isInsideList(state, start)) return null;
2923
3194
  const language = match[1] || null;
2924
3195
  const $start = state.doc.resolve(start);
2925
3196
  const blockStart = $start.before($start.depth);
@@ -2935,6 +3206,7 @@ function createInputRulesPlugin() {
2935
3206
  );
2936
3207
  rules.push(
2937
3208
  new InputRule(HR_RE, (state, _match, start, end) => {
3209
+ if (isInsideList(state, start)) return null;
2938
3210
  const { horizontalRule: hrType, paragraph: pType } = actionbookSchema.nodes;
2939
3211
  const $start = state.doc.resolve(start);
2940
3212
  const blockStart = $start.before($start.depth);
@@ -2974,9 +3246,29 @@ function createInputRulesPlugin() {
2974
3246
  const fallback = wrappingInputRule(ORDERED_LIST_RE, olType, (m) => ({ start: +m[1] }));
2975
3247
  rules.push(
2976
3248
  new InputRule(ORDERED_LIST_RE, (state, match, start, end) => {
2977
- const result = handleListInputRule(state, start, end, olType, { start: +match[1] });
2978
- if (result === NOOP) return null;
2979
- if (result) return result;
3249
+ const typedNumber = +match[1];
3250
+ const resolvePos = Math.max(start, end - 1);
3251
+ const parentList = findParentList(state, resolvePos);
3252
+ if (parentList) {
3253
+ const { node: listNode } = parentList;
3254
+ if (listNode.type.name === "orderedList") {
3255
+ const tr = state.tr.delete(start, end);
3256
+ const $after = tr.doc.resolve(tr.mapping.map(resolvePos));
3257
+ for (let d = $after.depth; d > 0; d--) {
3258
+ if ($after.node(d).type.name === "listItem") {
3259
+ const liPos = $after.before(d);
3260
+ const liNode = $after.node(d);
3261
+ tr.setNodeMarkup(liPos, void 0, { ...liNode.attrs, value: typedNumber });
3262
+ tr.setSelection(TextSelection.near(tr.doc.resolve(tr.mapping.map(start))));
3263
+ return tr;
3264
+ }
3265
+ }
3266
+ return null;
3267
+ }
3268
+ const result = handleListInputRule(state, start, end, olType, { start: typedNumber });
3269
+ if (result === NOOP) return null;
3270
+ if (result) return result;
3271
+ }
2980
3272
  const handler = fallback.handler;
2981
3273
  return handler(state, match, start, end);
2982
3274
  })
@@ -2994,10 +3286,8 @@ function createInputRulesPlugin() {
2994
3286
  return tr;
2995
3287
  })
2996
3288
  );
2997
- rules.push(markInputRule(BOLD_STAR_RE, actionbookSchema.marks.bold, 2));
2998
- rules.push(markInputRule(BOLD_UNDER_RE, actionbookSchema.marks.bold, 2));
2999
- rules.push(markInputRule(ITALIC_STAR_RE, actionbookSchema.marks.italic, 1));
3000
- rules.push(markInputRule(ITALIC_UNDER_RE, actionbookSchema.marks.italic, 1));
3289
+ rules.push(markInputRule(BOLD_RE, actionbookSchema.marks.bold, 2));
3290
+ rules.push(markInputRule(ITALIC_RE, actionbookSchema.marks.italic, 2));
3001
3291
  rules.push(markInputRule(STRIKE_RE, actionbookSchema.marks.strikethrough, 2));
3002
3292
  rules.push(markInputRule(CODE_RE, actionbookSchema.marks.code, 1));
3003
3293
  return rules;
@@ -3020,7 +3310,7 @@ function createHistoryPlugin() {
3020
3310
 
3021
3311
  // src/ui/plugin/keymapPlugin.ts
3022
3312
  import { baseKeymap, chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock, toggleMark, setBlockType, joinBackward } from "prosemirror-commands";
3023
- import { Plugin as Plugin2, TextSelection as TextSelection2 } from "prosemirror-state";
3313
+ import { AllSelection, Plugin as Plugin2, TextSelection as TextSelection2 } from "prosemirror-state";
3024
3314
  import { keymap as keymap3 } from "prosemirror-keymap";
3025
3315
  import { liftListItem, sinkListItem, splitListItem, wrapInList } from "prosemirror-schema-list";
3026
3316
 
@@ -3417,6 +3707,19 @@ function createEmptyDocGuardPlugin() {
3417
3707
  }
3418
3708
  });
3419
3709
  }
3710
+ var selectAllCommand = (state, dispatch) => {
3711
+ const { $from, from, to } = state.selection;
3712
+ const depth = Math.min($from.depth, 1);
3713
+ const blockStart = $from.start(depth);
3714
+ const blockEnd = $from.end(depth);
3715
+ const blockFullySelected = from <= blockStart && to >= blockEnd;
3716
+ if (blockFullySelected) {
3717
+ if (dispatch) dispatch(state.tr.setSelection(new AllSelection(state.doc)));
3718
+ return true;
3719
+ }
3720
+ if (dispatch) dispatch(state.tr.setSelection(TextSelection2.create(state.doc, blockStart, blockEnd)));
3721
+ return true;
3722
+ };
3420
3723
  function createKeymapPlugin() {
3421
3724
  return {
3422
3725
  name: "keymapPlugin",
@@ -3426,10 +3729,11 @@ function createKeymapPlugin() {
3426
3729
  "Shift-Tab": shiftTabCommand,
3427
3730
  Enter: enterCommand,
3428
3731
  "Shift-Enter": shiftEnterCommand,
3732
+ "Mod-a": selectAllCommand,
3429
3733
  "Mod-b": toggleMark(boldMark),
3430
3734
  "Mod-i": toggleMark(italicMark),
3431
3735
  "Mod-u": toggleMark(underlineMark),
3432
- "Mod-Shift-x": toggleMark(strikethroughMark),
3736
+ "Mod-Shift-s": toggleMark(strikethroughMark),
3433
3737
  "Mod-e": toggleMark(codeMark),
3434
3738
  "Mod-Shift-7": wrapInList(bulletList),
3435
3739
  "Mod-Shift-8": wrapInList(orderedList)
@@ -5638,7 +5942,7 @@ function buildDocumentTree(doc2) {
5638
5942
 
5639
5943
  // src/ui/plugin/markdownClipboard.ts
5640
5944
  var key = new PluginKey2("markdownClipboard");
5641
- var MAX_PASTE_LIST_DEPTH = 3;
5945
+ var MAX_PASTE_LIST_DEPTH = 15;
5642
5946
  var MAX_FLATTEN_DEPTH = 128;
5643
5947
  function flattenDeepLists(node, listDepth = 0, _recurseDepth = 0) {
5644
5948
  if (_recurseDepth > MAX_FLATTEN_DEPTH) return node;
@@ -5716,13 +6020,47 @@ function textToSlice(text2, view) {
5716
6020
  }
5717
6021
  }
5718
6022
  var URL_RE = /^https?:\/\/[^\s]+$/i;
6023
+ var shiftHeld = false;
5719
6024
  function createPlugin() {
5720
6025
  return new Plugin3({
5721
6026
  key,
5722
6027
  props: {
6028
+ handleDOMEvents: {
6029
+ keydown(_view, event) {
6030
+ shiftHeld = event.shiftKey;
6031
+ return false;
6032
+ },
6033
+ keyup() {
6034
+ shiftHeld = false;
6035
+ return false;
6036
+ },
6037
+ paste(view, event) {
6038
+ if (shiftHeld) return false;
6039
+ const clipboardData = event.clipboardData;
6040
+ const text2 = clipboardData?.getData("text/plain");
6041
+ if (!text2 || !URL_RE.test(text2.trim())) return false;
6042
+ const { from, to } = view.state.selection;
6043
+ if (from >= to) return false;
6044
+ const selectedText = view.state.doc.textBetween(from, to);
6045
+ if (!selectedText) return false;
6046
+ event.preventDefault();
6047
+ const linkType = view.state.schema.marks.link;
6048
+ if (!linkType) return false;
6049
+ const linkMark2 = linkType.create({ href: text2.trim() });
6050
+ view.dispatch(view.state.tr.addMark(from, to, linkMark2));
6051
+ return true;
6052
+ }
6053
+ },
5723
6054
  handlePaste(view, event) {
5724
- const html = event.clipboardData?.getData("text/html");
5725
6055
  const text2 = event.clipboardData?.getData("text/plain");
6056
+ if (shiftHeld) {
6057
+ if (!text2) return false;
6058
+ const textNode = actionbookSchema.text(text2);
6059
+ const slice2 = new Slice(Fragment.from(textNode), 0, 0);
6060
+ view.dispatch(view.state.tr.replaceSelection(slice2));
6061
+ return true;
6062
+ }
6063
+ const html = event.clipboardData?.getData("text/html");
5726
6064
  if (html) {
5727
6065
  const htmlSlice = htmlToSlice(html);
5728
6066
  if (htmlSlice) {
@@ -5784,22 +6122,22 @@ function createMarkdownClipboardPlugin() {
5784
6122
 
5785
6123
  // src/ui/plugin/jumpPointPlugin.ts
5786
6124
  import { Plugin as Plugin4, PluginKey as PluginKey3, TextSelection as TextSelection3 } from "prosemirror-state";
5787
- import { Decoration, DecorationSet } from "prosemirror-view";
6125
+ import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
5788
6126
  var adjacentKey = new PluginKey3("jumpPointAdjacent");
5789
6127
  var JUMP_POINT_ADJACENT_SPEC = { jumpPointAdjacent: true };
5790
6128
  function buildDecorations(state) {
5791
6129
  const { selection } = state;
5792
- if (!selection.empty) return DecorationSet.empty;
6130
+ if (!selection.empty) return DecorationSet2.empty;
5793
6131
  const cursorPos = selection.$from.pos;
5794
6132
  const decorations = [];
5795
6133
  state.doc.descendants((node, pos) => {
5796
6134
  if (node.type.name !== "jumpPoint") return;
5797
6135
  const nodeEnd = pos + node.nodeSize;
5798
6136
  if (cursorPos === pos || cursorPos === nodeEnd) {
5799
- decorations.push(Decoration.node(pos, nodeEnd, {}, JUMP_POINT_ADJACENT_SPEC));
6137
+ decorations.push(Decoration2.node(pos, nodeEnd, {}, JUMP_POINT_ADJACENT_SPEC));
5800
6138
  }
5801
6139
  });
5802
- return DecorationSet.create(state.doc, decorations);
6140
+ return DecorationSet2.create(state.doc, decorations);
5803
6141
  }
5804
6142
  var jumpPointEditKey = new PluginKey3("jumpPointEdit");
5805
6143
  var JUMP_POINT_FULL_RE = /^\^([\p{L}\p{N}_-]+)\^$/u;
@@ -5930,7 +6268,8 @@ function createJumpPointAdjacentPlugin() {
5930
6268
  }
5931
6269
 
5932
6270
  // src/ui/plugin/jumpPointNodeViewPlugin.tsx
5933
- import { useCallback as useCallback2, useState as useState2 } from "react";
6271
+ import { useCallback as useCallback2, useRef as useRef2, useState as useState2 } from "react";
6272
+ import { createPortal } from "react-dom";
5934
6273
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
5935
6274
  function IconDiamondAlert({ size = 12, fill = "#D9352C" }) {
5936
6275
  return /* @__PURE__ */ jsxs3("svg", { width: size, height: size, viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
@@ -5958,9 +6297,7 @@ var JUMP_POINT_STYLE = {
5958
6297
  // are set via CSS class .ab-jump-point for heading-level overrides
5959
6298
  };
5960
6299
  var JUMP_POINT_ADJACENT_STYLE = {
5961
- ...JUMP_POINT_STYLE,
5962
- backgroundColor: "#FFE680",
5963
- borderColor: "#FF9500"
6300
+ ...JUMP_POINT_STYLE
5964
6301
  };
5965
6302
  var JUMP_POINT_DUPLICATE_STYLE = {
5966
6303
  ...JUMP_POINT_STYLE,
@@ -5989,64 +6326,71 @@ var CLOSE_BTN_STYLE = {
5989
6326
  lineHeight: 1
5990
6327
  };
5991
6328
  var TOOLTIP_STYLE = {
5992
- position: "absolute",
5993
- bottom: "calc(100% + 8px)",
5994
- left: "50%",
5995
- transform: "translateX(-50%)",
6329
+ position: "fixed",
6330
+ display: "flex",
6331
+ flexDirection: "column",
6332
+ alignItems: "flex-start",
6333
+ alignSelf: "stretch",
6334
+ padding: "16px 20px",
5996
6335
  backgroundColor: "#fff",
5997
6336
  border: "1px solid #CCC",
5998
6337
  borderRadius: "4px",
5999
6338
  boxShadow: "0 8px 10px rgba(13,13,13,0.12), 0 3px 14px rgba(13,13,13,0.08), 0 3px 5px rgba(13,13,13,0.04)",
6000
- padding: "16px 20px",
6001
6339
  fontSize: "14px",
6002
6340
  lineHeight: "20px",
6003
6341
  color: "#0D0D0D",
6004
6342
  maxWidth: "240px",
6005
- zIndex: 100,
6343
+ zIndex: 1100,
6006
6344
  pointerEvents: "none"
6007
6345
  };
6008
- function JumpPointNodeViewComponent({ node, view, getPos, decorations }) {
6346
+ var TOOLTIP_ARROW_STYLE = {
6347
+ position: "absolute",
6348
+ bottom: "-6px",
6349
+ left: "50%",
6350
+ transform: "translateX(-50%) rotate(45deg)",
6351
+ width: "10px",
6352
+ height: "10px",
6353
+ backgroundColor: "#fff",
6354
+ border: "1px solid #CCC",
6355
+ borderTop: "none",
6356
+ borderLeft: "none"
6357
+ };
6358
+ function JumpPointNodeViewComponent({ node, decorations }) {
6009
6359
  const id = node.attrs.id;
6010
6360
  const isAdjacent = decorations?.some((d) => d.spec?.jumpPointAdjacent === true) ?? false;
6011
6361
  const isDuplicate = decorations?.some((d) => d.spec?.jumpPointDuplicate === true) ?? false;
6012
6362
  const [showTooltip, setShowTooltip] = useState2(false);
6013
- const handleDelete2 = useCallback2(() => {
6014
- const pos = getPos();
6015
- if (pos == null) return;
6016
- const tr = view.state.tr.delete(pos, pos + node.nodeSize);
6017
- view.dispatch(tr);
6018
- view.focus();
6019
- }, [view, getPos, node.nodeSize]);
6363
+ const spanRef = useRef2(null);
6364
+ const [tooltipPos, setTooltipPos] = useState2(null);
6365
+ const handleMouseEnter = useCallback2(() => {
6366
+ if (!spanRef.current) return;
6367
+ const rect = spanRef.current.getBoundingClientRect();
6368
+ setTooltipPos({ top: rect.top - 8, left: rect.left + rect.width / 2 });
6369
+ setShowTooltip(true);
6370
+ }, []);
6020
6371
  if (isDuplicate) {
6021
6372
  return /* @__PURE__ */ jsxs3(
6022
6373
  "span",
6023
6374
  {
6375
+ ref: spanRef,
6024
6376
  className: "ab-jump-point ab-jump-point-duplicate",
6025
- style: { ...JUMP_POINT_DUPLICATE_STYLE, position: "relative" },
6377
+ style: JUMP_POINT_DUPLICATE_STYLE,
6026
6378
  id: `jp-${id}`,
6027
- onMouseEnter: () => setShowTooltip(true),
6379
+ onMouseEnter: handleMouseEnter,
6028
6380
  onMouseLeave: () => setShowTooltip(false),
6029
6381
  children: [
6030
6382
  /* @__PURE__ */ jsx4("span", { className: "ab-jp-icon", children: /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#9D091E", style: { paddingLeft: "2px", flexShrink: 0 } }) }),
6031
6383
  /* @__PURE__ */ jsx4("span", { className: "ab-jp-label", style: LABEL_STYLE, children: id }),
6032
- /* @__PURE__ */ jsx4(
6033
- "button",
6034
- {
6035
- style: CLOSE_BTN_STYLE,
6036
- onMouseDown: (e) => {
6037
- e.preventDefault();
6038
- e.stopPropagation();
6039
- handleDelete2();
6040
- },
6041
- title: "Remove duplicate anchor",
6042
- children: /* @__PURE__ */ jsx4(IconDiamondAlert, { size: 12, fill: "#D9352C" })
6043
- }
6044
- ),
6045
- showTooltip && /* @__PURE__ */ jsxs3("span", { style: TOOLTIP_STYLE, children: [
6046
- "\u201C",
6047
- id,
6048
- "\u201D is already used as an anchor"
6049
- ] })
6384
+ /* @__PURE__ */ jsx4("span", { style: { ...CLOSE_BTN_STYLE, cursor: "default" }, children: /* @__PURE__ */ jsx4(IconDiamondAlert, { size: 12, fill: "#D9352C" }) }),
6385
+ showTooltip && tooltipPos && createPortal(
6386
+ /* @__PURE__ */ jsxs3("span", { style: { ...TOOLTIP_STYLE, top: tooltipPos.top, left: tooltipPos.left, transform: "translate(-50%, -100%)" }, children: [
6387
+ "\u201C",
6388
+ id,
6389
+ "\u201D is already used as an anchor",
6390
+ /* @__PURE__ */ jsx4("span", { style: TOOLTIP_ARROW_STYLE })
6391
+ ] }),
6392
+ document.body
6393
+ )
6050
6394
  ]
6051
6395
  }
6052
6396
  );
@@ -6073,7 +6417,7 @@ function createJumpPointNodeViewPlugin() {
6073
6417
 
6074
6418
  // src/ui/plugin/jumpPointValidationPlugin.ts
6075
6419
  import { Plugin as Plugin5, PluginKey as PluginKey4 } from "prosemirror-state";
6076
- import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
6420
+ import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "prosemirror-view";
6077
6421
  var pluginKey = new PluginKey4("jumpPointValidation");
6078
6422
  function collectJumpPointIds(state) {
6079
6423
  const ids = /* @__PURE__ */ new Set();
@@ -6139,40 +6483,104 @@ function createJumpPointValidationPlugin() {
6139
6483
  },
6140
6484
  props: {
6141
6485
  decorations(state) {
6142
- return pluginKey.getState(state) ?? DecorationSet2.empty;
6486
+ return pluginKey.getState(state) ?? DecorationSet3.empty;
6143
6487
  }
6144
6488
  }
6145
6489
  })
6146
6490
  ]
6147
6491
  };
6148
6492
  }
6493
+ function createBrokenRefIcon(refId) {
6494
+ const wrapper = document.createElement("span");
6495
+ wrapper.style.cssText = "position: relative; display: inline-flex; align-items: center; cursor: help;";
6496
+ wrapper.contentEditable = "false";
6497
+ const ns = "http://www.w3.org/2000/svg";
6498
+ const svg = document.createElementNS(ns, "svg");
6499
+ svg.setAttribute("width", "14");
6500
+ svg.setAttribute("height", "14");
6501
+ svg.setAttribute("viewBox", "0 0 16 16");
6502
+ svg.setAttribute("fill", "none");
6503
+ const path = document.createElementNS(ns, "path");
6504
+ path.setAttribute("fill-rule", "evenodd");
6505
+ path.setAttribute("clip-rule", "evenodd");
6506
+ path.setAttribute("d", "M8.00002 0.666626L15.3334 7.99996L8.00002 15.3333L0.666687 7.99996L8.00002 0.666626ZM7.33335 8.66663V4.66663H8.66669V8.66663H7.33335ZM8.00002 11.3333C8.46026 11.3333 8.83335 10.9602 8.83335 10.5C8.83335 10.0397 8.46026 9.66663 8.00002 9.66663C7.53978 9.66663 7.16669 10.0397 7.16669 10.5C7.16669 10.9602 7.53978 11.3333 8.00002 11.3333Z");
6507
+ path.setAttribute("fill", "#D9352C");
6508
+ svg.appendChild(path);
6509
+ const iconSpan = document.createElement("span");
6510
+ iconSpan.style.cssText = "display: inline-flex; margin-left: 2px; vertical-align: middle;";
6511
+ iconSpan.appendChild(svg);
6512
+ const tooltip = document.createElement("span");
6513
+ tooltip.textContent = `Anchor "${refId}" no longer exists`;
6514
+ tooltip.style.cssText = [
6515
+ "display:none",
6516
+ "position:fixed",
6517
+ "z-index:1100",
6518
+ "pointer-events:none",
6519
+ "padding:16px 20px",
6520
+ "background:#fff",
6521
+ "border:1px solid #CCC",
6522
+ "border-radius:4px",
6523
+ "box-shadow:0 8px 10px rgba(13,13,13,0.12),0 3px 14px rgba(13,13,13,0.08),0 3px 5px rgba(13,13,13,0.04)",
6524
+ "font-size:14px",
6525
+ "line-height:20px",
6526
+ "color:#0D0D0D",
6527
+ "max-width:240px",
6528
+ "white-space:normal"
6529
+ ].join(";");
6530
+ const arrow = document.createElement("span");
6531
+ arrow.style.cssText = [
6532
+ "position:absolute",
6533
+ "bottom:-6px",
6534
+ "left:50%",
6535
+ "transform:translateX(-50%) rotate(45deg)",
6536
+ "width:10px",
6537
+ "height:10px",
6538
+ "background:#fff",
6539
+ "border:1px solid #CCC",
6540
+ "border-top:none",
6541
+ "border-left:none"
6542
+ ].join(";");
6543
+ tooltip.appendChild(arrow);
6544
+ wrapper.addEventListener("mouseenter", () => {
6545
+ const rect = wrapper.getBoundingClientRect();
6546
+ tooltip.style.left = `${rect.left + rect.width / 2}px`;
6547
+ tooltip.style.top = `${rect.top - 8}px`;
6548
+ tooltip.style.transform = "translate(-50%, -100%)";
6549
+ tooltip.style.display = "flex";
6550
+ tooltip.style.flexDirection = "column";
6551
+ tooltip.style.alignItems = "flex-start";
6552
+ document.body.appendChild(tooltip);
6553
+ });
6554
+ wrapper.addEventListener("mouseleave", () => {
6555
+ tooltip.style.display = "none";
6556
+ if (tooltip.parentElement === document.body) document.body.removeChild(tooltip);
6557
+ });
6558
+ wrapper.appendChild(iconSpan);
6559
+ return wrapper;
6560
+ }
6149
6561
  function buildDecorations2(state) {
6150
6562
  const decos = [];
6151
6563
  const duplicates = findDuplicatePositions(state);
6152
6564
  for (const { pos, size } of duplicates) {
6153
6565
  decos.push(
6154
- Decoration2.node(pos, pos + size, { class: "jump-point-duplicate" }, { jumpPointDuplicate: true })
6566
+ Decoration3.node(pos, pos + size, { class: "jump-point-duplicate" }, { jumpPointDuplicate: true })
6155
6567
  );
6156
6568
  }
6157
6569
  const existingIds = collectJumpPointIds(state);
6158
6570
  const brokenRefs = findBrokenAnchorRefs(state, existingIds);
6159
6571
  for (const { from, to, refId } of brokenRefs) {
6160
6572
  decos.push(
6161
- Decoration2.inline(from, to, {
6573
+ Decoration3.inline(from, to, {
6162
6574
  class: "broken-anchor-ref",
6163
6575
  "data-broken-ref": refId,
6164
- title: `Anchor "${refId}" no longer exists`,
6165
- style: "color: #D9352C; font-weight: 500; text-decoration: none;"
6576
+ spellcheck: "false",
6577
+ style: "color: #D9352C; font-weight: 500; text-decoration: none !important; text-decoration-line: none !important;"
6166
6578
  })
6167
6579
  );
6168
- const icon = document.createElement("span");
6169
- icon.textContent = " \u26A0";
6170
- icon.style.cssText = "color: #D9352C; font-size: 14px; vertical-align: middle;";
6171
- icon.setAttribute("aria-hidden", "true");
6172
- decos.push(Decoration2.widget(to, icon, { side: 1 }));
6580
+ decos.push(Decoration3.widget(to, () => createBrokenRefIcon(refId), { side: -1 }));
6173
6581
  }
6174
- if (decos.length === 0) return DecorationSet2.empty;
6175
- return DecorationSet2.create(state.doc, decos);
6582
+ if (decos.length === 0) return DecorationSet3.empty;
6583
+ return DecorationSet3.create(state.doc, decos);
6176
6584
  }
6177
6585
 
6178
6586
  // src/ui/plugin/inlineToolTagNodeViewPlugin.tsx
@@ -6246,7 +6654,7 @@ function createInlineToolTagNodeViewPlugin() {
6246
6654
 
6247
6655
  // src/ui/plugin/jinjaDecoration.ts
6248
6656
  import { Plugin as Plugin6, PluginKey as PluginKey5 } from "prosemirror-state";
6249
- import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "prosemirror-view";
6657
+ import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "prosemirror-view";
6250
6658
  var jinjaPluginKey = new PluginKey5("jinjaDecoration");
6251
6659
  var JINJA_TAG_RE = /\{%\s*(if|elif|else|endif)\s*([^%]*?)\s*%\}/g;
6252
6660
  function getBlockPositions(doc2) {
@@ -6269,7 +6677,7 @@ function addInlineChipDecorations(doc2, decorations) {
6269
6677
  const to = from + match[0].length;
6270
6678
  const keyword = match[1];
6271
6679
  decorations.push(
6272
- Decoration3.inline(from, to, {
6680
+ Decoration4.inline(from, to, {
6273
6681
  class: `jinja-chip jinja-chip-${keyword}`
6274
6682
  })
6275
6683
  );
@@ -6310,7 +6718,7 @@ function addStructureBorderDecorations(doc2, blocks, decorations) {
6310
6718
  const classes = ["jinja-bar", `jinja-bar-${branchType}`];
6311
6719
  if (i === first || prevType !== branchType) classes.push("jinja-bar-first");
6312
6720
  if (i === last || nextType !== branchType) classes.push("jinja-bar-last");
6313
- decorations.push(Decoration3.node(block.from, block.to, { class: classes.join(" ") }));
6721
+ decorations.push(Decoration4.node(block.from, block.to, { class: classes.join(" ") }));
6314
6722
  }
6315
6723
  }
6316
6724
  }
@@ -6326,13 +6734,13 @@ function hasJinjaTags(doc2) {
6326
6734
  return found;
6327
6735
  }
6328
6736
  function buildDecorations3(doc2) {
6329
- if (!hasJinjaTags(doc2)) return DecorationSet3.empty;
6737
+ if (!hasJinjaTags(doc2)) return DecorationSet4.empty;
6330
6738
  const blocks = getBlockPositions(doc2);
6331
6739
  const decorations = [];
6332
6740
  addInlineChipDecorations(doc2, decorations);
6333
6741
  addStructureBorderDecorations(doc2, blocks, decorations);
6334
- if (decorations.length === 0) return DecorationSet3.empty;
6335
- return DecorationSet3.create(doc2, decorations);
6742
+ if (decorations.length === 0) return DecorationSet4.empty;
6743
+ return DecorationSet4.create(doc2, decorations);
6336
6744
  }
6337
6745
  function createJinjaDecorationPlugin() {
6338
6746
  return {
@@ -6360,7 +6768,7 @@ function createJinjaDecorationPlugin() {
6360
6768
  }
6361
6769
 
6362
6770
  // src/ui/plugin/jinjaIfBlockPlugin.tsx
6363
- import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
6771
+ import { useEffect as useEffect2, useRef as useRef3, useState as useState3 } from "react";
6364
6772
  import { createRoot as createRoot2 } from "react-dom/client";
6365
6773
  import { Plugin as Plugin7, TextSelection as TextSelection4 } from "prosemirror-state";
6366
6774
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
@@ -6377,7 +6785,9 @@ var JINJA_STYLES = `
6377
6785
  display: grid;
6378
6786
  grid-template-areas:
6379
6787
  "header"
6788
+ "error"
6380
6789
  "body"
6790
+ "body-error"
6381
6791
  "footer";
6382
6792
  gap: 0;
6383
6793
  }
@@ -6467,6 +6877,7 @@ var JINJA_STYLES = `
6467
6877
  }
6468
6878
 
6469
6879
  .jinja-condition-error {
6880
+ grid-area: error;
6470
6881
  display: flex;
6471
6882
  align-items: center;
6472
6883
  padding: 4px 0 4px 52px;
@@ -6475,6 +6886,16 @@ var JINJA_STYLES = `
6475
6886
  line-height: 16px;
6476
6887
  }
6477
6888
 
6889
+ .jinja-body-error {
6890
+ grid-area: body-error;
6891
+ display: flex;
6892
+ align-items: center;
6893
+ padding: 0 0 4px 52px;
6894
+ color: #D9352C;
6895
+ font-size: 12px;
6896
+ line-height: 16px;
6897
+ }
6898
+
6478
6899
  .jinja-token-variable {
6479
6900
  color: #4141B2;
6480
6901
  }
@@ -6787,60 +7208,18 @@ function deleteBranch(view, nodePos, branchType) {
6787
7208
  const context = getBranchContext(view, nodePos);
6788
7209
  if (!context) return false;
6789
7210
  const { branchNode, branchPos, branchIndex, blockNode, blockPos } = context;
6790
- const branchContent = branchNode.content;
6791
7211
  if (branchType === "if") {
6792
- if (blockNode.childCount === 1) {
6793
- const tr2 = view.state.tr;
6794
- if (isBranchBodyEmpty(branchNode)) {
6795
- tr2.replaceWith(blockPos, blockPos + blockNode.nodeSize, view.state.schema.nodes.paragraph.create());
6796
- tr2.setSelection(TextSelection4.near(tr2.doc.resolve(blockPos)));
6797
- } else {
6798
- tr2.replaceWith(blockPos, blockPos + blockNode.nodeSize, branchContent);
6799
- tr2.setSelection(TextSelection4.near(tr2.doc.resolve(blockPos)));
6800
- }
6801
- view.dispatch(tr2.scrollIntoView());
6802
- view.focus();
6803
- return true;
6804
- }
6805
- const nextBranch = blockNode.child(branchIndex + 1);
6806
- const promotedCondition = typeof nextBranch.attrs.condition === "string" ? nextBranch.attrs.condition : "";
6807
- const tr = view.state.tr;
6808
- if (!isBranchBodyEmpty(branchNode)) {
6809
- const nextBranchPos = branchPos + branchNode.nodeSize;
6810
- const nextBranchContentStart = nextBranchPos + 1;
6811
- for (let i = branchContent.childCount - 1; i >= 0; i--) {
6812
- tr.insert(nextBranchContentStart, branchContent.child(i));
6813
- }
6814
- }
6815
- const mappedBranchPos = tr.mapping.map(branchPos);
6816
- const mappedBranchEnd = tr.mapping.map(branchPos + branchNode.nodeSize);
6817
- tr.delete(mappedBranchPos, mappedBranchEnd);
6818
- tr.setNodeMarkup(mappedBranchPos, void 0, {
6819
- ...nextBranch.attrs,
6820
- branchType: "if",
6821
- condition: promotedCondition
6822
- });
6823
- view.dispatch(tr.scrollIntoView());
7212
+ const tr2 = view.state.tr;
7213
+ tr2.replaceWith(blockPos, blockPos + blockNode.nodeSize, view.state.schema.nodes.paragraph.create());
7214
+ tr2.setSelection(TextSelection4.near(tr2.doc.resolve(blockPos)));
7215
+ view.dispatch(tr2.scrollIntoView());
6824
7216
  view.focus();
6825
7217
  return true;
6826
7218
  }
6827
- if (!isBranchBodyEmpty(branchNode) && branchIndex > 0) {
6828
- const prevBranch = blockNode.child(branchIndex - 1);
6829
- const prevBranchPos = branchPos - prevBranch.nodeSize;
6830
- const prevBranchContentEnd = prevBranchPos + prevBranch.nodeSize - 1;
6831
- const tr = view.state.tr;
6832
- for (let i = 0; i < branchContent.childCount; i++) {
6833
- tr.insert(prevBranchContentEnd + i, branchContent.child(i));
6834
- }
6835
- const mappedBranchPos = tr.mapping.map(branchPos);
6836
- const mappedBranchEnd = tr.mapping.map(branchPos + branchNode.nodeSize);
6837
- tr.delete(mappedBranchPos, mappedBranchEnd);
6838
- tr.setSelection(TextSelection4.near(tr.doc.resolve(tr.mapping.map(prevBranchContentEnd)), -1));
6839
- view.dispatch(tr.scrollIntoView());
6840
- view.focus();
6841
- return true;
6842
- }
6843
- view.dispatch(view.state.tr.delete(branchPos, branchPos + branchNode.nodeSize).scrollIntoView());
7219
+ const tr = view.state.tr;
7220
+ tr.delete(branchPos, branchPos + branchNode.nodeSize);
7221
+ tr.setSelection(TextSelection4.near(tr.doc.resolve(tr.mapping.map(branchPos)), -1));
7222
+ view.dispatch(tr.scrollIntoView());
6844
7223
  view.focus();
6845
7224
  return true;
6846
7225
  }
@@ -6865,8 +7244,8 @@ function ConditionDisplay({
6865
7244
  }) {
6866
7245
  const [isEditing, setIsEditing] = useState3(false);
6867
7246
  const [draftValue, setDraftValue] = useState3(condition);
6868
- const inputRef = useRef2(null);
6869
- const autoFocusedRef = useRef2(false);
7247
+ const inputRef = useRef3(null);
7248
+ const autoFocusedRef = useRef3(false);
6870
7249
  useEffect2(() => {
6871
7250
  if (!isEditing) {
6872
7251
  setDraftValue(condition);
@@ -6888,11 +7267,11 @@ function ConditionDisplay({
6888
7267
  return /* @__PURE__ */ jsx6("span", { className: "jinja-otherwise", children: "Otherwise" });
6889
7268
  }
6890
7269
  const commit = () => {
6891
- setIsEditing(false);
6892
- onConditionBlur?.();
6893
7270
  if (draftValue !== condition) {
6894
7271
  onConditionChange(draftValue);
6895
7272
  }
7273
+ setIsEditing(false);
7274
+ onConditionBlur?.();
6896
7275
  };
6897
7276
  const cancel = () => {
6898
7277
  setDraftValue(condition);
@@ -6976,6 +7355,8 @@ function JinjaBranchHeader({
6976
7355
  branchType,
6977
7356
  condition,
6978
7357
  editable = true,
7358
+ isBodyEmpty,
7359
+ isBodyTouched,
6979
7360
  onConditionChange,
6980
7361
  onDelete,
6981
7362
  onAddBranch,
@@ -6983,8 +7364,8 @@ function JinjaBranchHeader({
6983
7364
  hasElseBranch
6984
7365
  }) {
6985
7366
  const [menuSource, setMenuSource] = useState3(null);
6986
- const kebabRef = useRef2(null);
6987
- const footerRef = useRef2(null);
7367
+ const kebabRef = useRef3(null);
7368
+ const footerRef = useRef3(null);
6988
7369
  useEffect2(() => {
6989
7370
  if (!menuSource) return;
6990
7371
  const handlePointerDown = (event) => {
@@ -7008,24 +7389,9 @@ function JinjaBranchHeader({
7008
7389
  };
7009
7390
  }, [menuSource]);
7010
7391
  const menuItems = [];
7011
- const isOnlyIfBranch = branchType === "if" && isLastBranch && !hasElseBranch;
7012
- const canAddElif = editable && branchType !== "else" && (branchType === "elif" || isLastBranch);
7013
- const canAddElse = editable && branchType !== "else" && isLastBranch && !hasElseBranch;
7014
- if (canAddElif) {
7015
- menuItems.push({
7016
- label: "Else if",
7017
- onSelect: () => onAddBranch("elif")
7018
- });
7019
- }
7020
- if (canAddElse) {
7021
- menuItems.push({
7022
- label: "Else",
7023
- onSelect: () => onAddBranch("else")
7024
- });
7025
- }
7026
7392
  if (editable) {
7027
7393
  menuItems.push({
7028
- label: isOnlyIfBranch ? "Delete all" : "Delete",
7394
+ label: "Delete",
7029
7395
  onSelect: onDelete
7030
7396
  });
7031
7397
  }
@@ -7090,6 +7456,7 @@ function JinjaBranchHeader({
7090
7456
  ] }) : null
7091
7457
  ] }),
7092
7458
  conditionError && /* @__PURE__ */ jsx6("div", { className: "jinja-condition-error", children: conditionError }),
7459
+ isBodyEmpty && isBodyTouched && editable && /* @__PURE__ */ jsx6("div", { className: "jinja-body-error", children: "This field is required." }),
7093
7460
  isLastBranch && editable ? /* @__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: [
7094
7461
  /* @__PURE__ */ jsx6(
7095
7462
  "button",
@@ -7155,6 +7522,7 @@ var JinjaIfBranchView = class {
7155
7522
  node;
7156
7523
  view;
7157
7524
  getPos;
7525
+ bodyTouched = false;
7158
7526
  constructor(node, view, getPos) {
7159
7527
  this.node = node;
7160
7528
  this.view = view;
@@ -7179,6 +7547,12 @@ var JinjaIfBranchView = class {
7179
7547
  this.contentDOM.setAttribute("data-placeholder", PLACEHOLDER_TEXT);
7180
7548
  body.appendChild(dividerColumn);
7181
7549
  body.appendChild(this.contentDOM);
7550
+ this.contentDOM.addEventListener("focusout", () => {
7551
+ if (!this.bodyTouched && isBranchBodyEmpty(this.node)) {
7552
+ this.bodyTouched = true;
7553
+ this.render();
7554
+ }
7555
+ });
7182
7556
  this.root = createRoot2(this.headerContainer);
7183
7557
  this._onSiblingsChanged = () => this.render();
7184
7558
  this.dom.addEventListener("jinja-siblings-changed", this._onSiblingsChanged);
@@ -7226,6 +7600,8 @@ var JinjaIfBranchView = class {
7226
7600
  branchType,
7227
7601
  condition,
7228
7602
  editable: this.view.editable && this.view.dom.getAttribute("contenteditable") !== "false",
7603
+ isBodyEmpty: isBranchBodyEmpty(this.node),
7604
+ isBodyTouched: this.bodyTouched,
7229
7605
  isLastBranch: showAddButton,
7230
7606
  hasElseBranch: context && nodePos != null ? getElseBranch(this.view, nodePos) : false,
7231
7607
  onConditionChange: (value) => {
@@ -8326,6 +8702,7 @@ function createDragHandlePlugin() {
8326
8702
  }
8327
8703
 
8328
8704
  // src/ui/plugin/todoNodeViewPlugin.tsx
8705
+ import { Plugin as Plugin10 } from "prosemirror-state";
8329
8706
  function createCheckboxDOM(checked) {
8330
8707
  const el = document.createElement("span");
8331
8708
  el.contentEditable = "false";
@@ -8408,14 +8785,63 @@ var TodoListItemView = class {
8408
8785
  return true;
8409
8786
  }
8410
8787
  };
8788
+ function createListItemValueResetPlugin() {
8789
+ return new Plugin10({
8790
+ appendTransaction(trs, oldState, newState) {
8791
+ if (!trs.some((tr2) => tr2.docChanged)) return null;
8792
+ const oldCount = oldState.doc.childCount;
8793
+ let hasNewListItems = false;
8794
+ newState.doc.descendants((node) => {
8795
+ if (hasNewListItems) return false;
8796
+ if (node.type.name === "listItem" && node.attrs.value != null) {
8797
+ hasNewListItems = true;
8798
+ }
8799
+ });
8800
+ if (!hasNewListItems) return null;
8801
+ let tr = newState.tr;
8802
+ let changed = false;
8803
+ newState.doc.descendants((node, pos) => {
8804
+ if (node.type.name !== "orderedList") return;
8805
+ let prevValue = null;
8806
+ node.forEach((li, offset) => {
8807
+ const liValue = li.attrs.value;
8808
+ if (liValue != null && liValue === prevValue) {
8809
+ const liPos = pos + 1 + offset;
8810
+ tr = tr.setNodeMarkup(liPos, void 0, { ...li.attrs, value: null });
8811
+ changed = true;
8812
+ }
8813
+ prevValue = liValue;
8814
+ });
8815
+ });
8816
+ return changed ? tr : null;
8817
+ }
8818
+ });
8819
+ }
8411
8820
  function createTodoNodeViewPlugin() {
8412
8821
  return {
8413
8822
  name: "todoNodeView",
8823
+ plugins: () => [createListItemValueResetPlugin()],
8414
8824
  nodeViews: () => ({
8415
8825
  listItem: ((node, view, getPos) => {
8416
8826
  if (node.attrs.checked == null) {
8417
8827
  const li = document.createElement("li");
8418
- return { dom: li, contentDOM: li };
8828
+ if (node.attrs.value != null) {
8829
+ li.value = node.attrs.value;
8830
+ }
8831
+ return {
8832
+ dom: li,
8833
+ contentDOM: li,
8834
+ update(updatedNode) {
8835
+ if (updatedNode.type.name !== "listItem") return false;
8836
+ if (updatedNode.attrs.checked != null) return false;
8837
+ if (updatedNode.attrs.value != null) {
8838
+ li.value = updatedNode.attrs.value;
8839
+ } else {
8840
+ li.removeAttribute("value");
8841
+ }
8842
+ return true;
8843
+ }
8844
+ };
8419
8845
  }
8420
8846
  return new TodoListItemView(node, view, getPos);
8421
8847
  })
@@ -8424,35 +8850,36 @@ function createTodoNodeViewPlugin() {
8424
8850
  }
8425
8851
 
8426
8852
  // src/ui/plugin/placeholderPlugin.ts
8427
- import { Plugin as Plugin10, PluginKey as PluginKey8 } from "prosemirror-state";
8428
- import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "prosemirror-view";
8853
+ import { Plugin as Plugin11, PluginKey as PluginKey8 } from "prosemirror-state";
8854
+ import { Decoration as Decoration5, DecorationSet as DecorationSet5 } from "prosemirror-view";
8429
8855
  var pluginKey2 = new PluginKey8("placeholder");
8430
8856
  function buildDecorations4(state) {
8431
8857
  const { doc: doc2, selection } = state;
8432
- if (!selection.empty) return DecorationSet4.empty;
8858
+ if (!selection.empty) return DecorationSet5.empty;
8433
8859
  const $from = selection.$from;
8434
8860
  const parent = $from.parent;
8435
8861
  if (parent.type.name !== "paragraph" || parent.content.size !== 0) {
8436
- return DecorationSet4.empty;
8862
+ return DecorationSet5.empty;
8437
8863
  }
8438
8864
  const EXCLUDED_ANCESTORS = /* @__PURE__ */ new Set(["noteBlock", "blockquote", "jinjaIfBranch"]);
8439
8865
  for (let d = $from.depth - 1; d > 0; d--) {
8440
8866
  if (EXCLUDED_ANCESTORS.has($from.node(d).type.name)) {
8441
- return DecorationSet4.empty;
8867
+ return DecorationSet5.empty;
8442
8868
  }
8443
8869
  }
8444
8870
  const pos = $from.before($from.depth);
8445
- const deco = Decoration4.node(pos, pos + parent.nodeSize, {
8446
- class: "ab-empty-paragraph"
8871
+ const deco = Decoration5.node(pos, pos + parent.nodeSize, {
8872
+ class: "ab-empty-paragraph",
8873
+ "data-placeholder": "Type to start writing, or press / to insert an action."
8447
8874
  });
8448
- return DecorationSet4.create(doc2, [deco]);
8875
+ return DecorationSet5.create(doc2, [deco]);
8449
8876
  }
8450
8877
  function createPlaceholderPlugin() {
8451
8878
  let isEditable = true;
8452
8879
  return {
8453
8880
  name: "placeholder",
8454
8881
  plugins: () => [
8455
- new Plugin10({
8882
+ new Plugin11({
8456
8883
  key: pluginKey2,
8457
8884
  view(editorView) {
8458
8885
  isEditable = editorView.editable;
@@ -8475,8 +8902,8 @@ function createPlaceholderPlugin() {
8475
8902
  },
8476
8903
  props: {
8477
8904
  decorations(state) {
8478
- if (!isEditable) return DecorationSet4.empty;
8479
- return pluginKey2.getState(state) ?? DecorationSet4.empty;
8905
+ if (!isEditable) return DecorationSet5.empty;
8906
+ return pluginKey2.getState(state) ?? DecorationSet5.empty;
8480
8907
  }
8481
8908
  }
8482
8909
  })
@@ -8485,8 +8912,8 @@ function createPlaceholderPlugin() {
8485
8912
  }
8486
8913
 
8487
8914
  // src/ui/components/SlashCommandMenu.tsx
8488
- import React4, { useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState4 } from "react";
8489
- import { createPortal } from "react-dom";
8915
+ import React4, { useEffect as useEffect3, useLayoutEffect, useRef as useRef4, useState as useState4 } from "react";
8916
+ import { createPortal as createPortal2 } from "react-dom";
8490
8917
  import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
8491
8918
  function filterItems(items, query) {
8492
8919
  if (!query) return items;
@@ -8513,7 +8940,7 @@ function injectScrollbarStyle() {
8513
8940
  `;
8514
8941
  document.head.appendChild(style);
8515
8942
  }
8516
- var POPUP_SHADOW = "0 0 0 1px #CCCCCC, 0 4px 16px rgba(0, 0, 0, 0.12)";
8943
+ var POPUP_SHADOW = "0 3px 5px -3px rgba(13,13,13,0.04), 0 3px 14px 2px rgba(13,13,13,0.08), 0 8px 10px 1px rgba(13,13,13,0.12)";
8517
8944
  var BTN_RESET = {
8518
8945
  border: "none",
8519
8946
  padding: 0,
@@ -8530,7 +8957,7 @@ var MENU_WIDTH = 440;
8530
8957
  var MAX_MENU_H = 400;
8531
8958
  function SlashCommandMenu({ view, editorState, items }) {
8532
8959
  const [selectedIndex, setSelectedIndex] = useState4(0);
8533
- const listRef = useRef3(null);
8960
+ const listRef = useRef4(null);
8534
8961
  useLayoutEffect(() => {
8535
8962
  injectScrollbarStyle();
8536
8963
  }, []);
@@ -8549,7 +8976,7 @@ function SlashCommandMenu({ view, editorState, items }) {
8549
8976
  item?.scrollIntoView({ block: "nearest" });
8550
8977
  }, [selectedIndex]);
8551
8978
  useEffect3(() => {
8552
- if (!active || !view) return;
8979
+ if (!active || !view || filtered.length === 0) return;
8553
8980
  const onKeyDown = (e) => {
8554
8981
  if (e.key === "ArrowDown") {
8555
8982
  e.preventDefault();
@@ -8571,66 +8998,76 @@ function SlashCommandMenu({ view, editorState, items }) {
8571
8998
  view.focus();
8572
8999
  }
8573
9000
  }
9001
+ if (e.key === "Escape") {
9002
+ e.preventDefault();
9003
+ e.stopPropagation();
9004
+ if (range) {
9005
+ view.dispatch(view.state.tr.delete(range.from, range.to));
9006
+ }
9007
+ view.focus();
9008
+ }
8574
9009
  };
8575
9010
  document.addEventListener("keydown", onKeyDown, true);
8576
9011
  return () => document.removeEventListener("keydown", onKeyDown, true);
8577
9012
  }, [active, view, filtered, selectedIndex, range]);
8578
- if (!active || !view || !range) return null;
8579
- const coords = view.coordsAtPos(range.from);
8580
- let top = coords.bottom + 4;
8581
- let left = coords.left;
8582
- left = Math.max(VPORT_MARGIN, Math.min(left, window.innerWidth - MENU_WIDTH - VPORT_MARGIN));
8583
- if (top + MAX_MENU_H > window.innerHeight - VPORT_MARGIN) {
8584
- top = coords.top - MAX_MENU_H - 4;
8585
- }
8586
- return createPortal(
9013
+ const [menuPos, setMenuPos] = useState4(null);
9014
+ const menuRef = useRef4(null);
9015
+ useLayoutEffect(() => {
9016
+ if (!active || !view || !range || filtered.length === 0) {
9017
+ setMenuPos(null);
9018
+ return;
9019
+ }
9020
+ try {
9021
+ const coords = view.coordsAtPos(range.to);
9022
+ let left = coords.left;
9023
+ left = Math.max(VPORT_MARGIN, Math.min(left, window.innerWidth - MENU_WIDTH - VPORT_MARGIN));
9024
+ const menuHeight = menuRef.current?.offsetHeight ?? MAX_MENU_H;
9025
+ let top = coords.bottom + 4;
9026
+ if (top + menuHeight > window.innerHeight - VPORT_MARGIN) {
9027
+ top = coords.top - menuHeight - 4;
9028
+ }
9029
+ setMenuPos({ top, left });
9030
+ } catch {
9031
+ }
9032
+ });
9033
+ if (!active || !view || !range || filtered.length === 0 || !menuPos) return null;
9034
+ return createPortal2(
8587
9035
  /* @__PURE__ */ jsx7(
8588
9036
  "div",
8589
9037
  {
9038
+ ref: menuRef,
8590
9039
  className: "ab-slash-menu",
8591
9040
  style: {
8592
9041
  position: "fixed",
8593
- top,
8594
- left,
9042
+ top: menuPos.top,
9043
+ left: menuPos.left,
8595
9044
  width: MENU_WIDTH,
8596
9045
  maxHeight: MAX_MENU_H,
8597
9046
  overflowY: "auto",
8598
9047
  background: "#fff",
8599
- borderRadius: 8,
9048
+ borderRadius: 4,
8600
9049
  boxShadow: POPUP_SHADOW,
8601
- padding: "4px",
9050
+ padding: "8px 0",
8602
9051
  zIndex: 1100,
8603
9052
  animation: "ab-float-in 0.12s ease"
8604
9053
  },
8605
- children: /* @__PURE__ */ jsx7("div", { ref: listRef, children: filtered.length === 0 ? /* @__PURE__ */ jsx7(
8606
- "div",
8607
- {
8608
- style: {
8609
- padding: "8px 12px",
8610
- fontSize: 13,
8611
- color: "#9ca3af"
8612
- },
8613
- children: "No results"
8614
- }
8615
- ) : filtered.map((item, i) => {
9054
+ children: /* @__PURE__ */ jsx7("div", { ref: listRef, children: filtered.map((item, i) => {
8616
9055
  const prevGroup = i > 0 ? filtered[i - 1].group : void 0;
8617
9056
  const showGroupHeader = item.group && item.group !== prevGroup;
8618
9057
  return /* @__PURE__ */ jsxs6(React4.Fragment, { children: [
8619
- showGroupHeader && /* @__PURE__ */ jsxs6(Fragment3, { children: [
8620
- i > 0 && /* @__PURE__ */ jsx7("div", { style: { height: 1, background: "rgba(0,0,0,0.06)", margin: "4px 10px" } }),
8621
- /* @__PURE__ */ jsx7(
8622
- "div",
8623
- {
8624
- style: {
8625
- padding: "8px 16px 4px",
8626
- fontSize: 13,
8627
- fontWeight: 400,
8628
- color: "#5e5e5e"
8629
- },
8630
- children: item.group
8631
- }
8632
- )
8633
- ] }),
9058
+ showGroupHeader && /* @__PURE__ */ jsx7(Fragment3, { children: /* @__PURE__ */ jsx7(
9059
+ "div",
9060
+ {
9061
+ style: {
9062
+ padding: "16px 16px 4px",
9063
+ fontSize: 12,
9064
+ fontWeight: 600,
9065
+ lineHeight: "12px",
9066
+ color: "#858585"
9067
+ },
9068
+ children: item.group
9069
+ }
9070
+ ) }),
8634
9071
  /* @__PURE__ */ jsx7(
8635
9072
  SlashMenuItem,
8636
9073
  {
@@ -8664,20 +9101,22 @@ function SlashMenuItem({ item, index, selected, onMouseEnter, onMouseDown }) {
8664
9101
  display: "flex",
8665
9102
  alignItems: "center",
8666
9103
  gap: 12,
8667
- padding: "6px 8px",
8668
- background: selected ? "rgba(0, 0, 0, 0.04)" : "transparent",
9104
+ padding: "6px 16px",
9105
+ background: selected ? "rgba(13, 13, 13, 0.04)" : "transparent",
8669
9106
  transition: "background 0.08s"
8670
9107
  },
8671
9108
  onMouseEnter,
8672
9109
  onMouseDown,
8673
9110
  children: [
8674
- item.icon !== void 0 && /* @__PURE__ */ jsx7("span", { style: { flexShrink: 0, display: "flex", alignItems: "center" }, children: item.icon }),
8675
9111
  /* @__PURE__ */ jsx7(
8676
9112
  "span",
8677
9113
  {
8678
9114
  style: {
9115
+ flex: "1 0 0",
8679
9116
  fontSize: 14,
8680
9117
  fontWeight: 400,
9118
+ lineHeight: "20px",
9119
+ letterSpacing: "-0.1px",
8681
9120
  color: "#0d0d0d",
8682
9121
  whiteSpace: "nowrap",
8683
9122
  overflow: "hidden",
@@ -8687,17 +9126,16 @@ function SlashMenuItem({ item, index, selected, onMouseEnter, onMouseDown }) {
8687
9126
  children: item.title
8688
9127
  }
8689
9128
  ),
8690
- /* @__PURE__ */ jsx7("span", { style: { flex: 1 } }),
8691
9129
  (item.description || item.shortcut) && /* @__PURE__ */ jsx7(
8692
9130
  "span",
8693
9131
  {
8694
9132
  style: {
8695
9133
  fontSize: 12,
9134
+ lineHeight: "16px",
8696
9135
  color: "#5e5e5e",
8697
9136
  whiteSpace: "nowrap",
8698
9137
  flexShrink: 0,
8699
- textAlign: "right",
8700
- fontFamily: item.shortcut ? "monospace" : "inherit"
9138
+ textAlign: "right"
8701
9139
  },
8702
9140
  children: item.shortcut ?? item.description
8703
9141
  }
@@ -8953,8 +9391,8 @@ function DocumentTreeView({ doc: doc2, className, onNavigate }) {
8953
9391
  }
8954
9392
 
8955
9393
  // src/ui/components/FloatingMenu.tsx
8956
- import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
8957
- import { createPortal as createPortal2 } from "react-dom";
9394
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef5, useState as useState7 } from "react";
9395
+ import { createPortal as createPortal3 } from "react-dom";
8958
9396
  import { TextSelection as TextSelection6 } from "prosemirror-state";
8959
9397
  import { setBlockType as setBlockType2, toggleMark as toggleMark2, wrapIn } from "prosemirror-commands";
8960
9398
  import { wrapInList as wrapInList2 } from "prosemirror-schema-list";
@@ -9132,7 +9570,7 @@ function Divider() {
9132
9570
  }
9133
9571
  function BlockTypeDropdown({ view, state, label }) {
9134
9572
  const [open, setOpen] = useState7(false);
9135
- const btnRef = useRef4(null);
9573
+ const btnRef = useRef5(null);
9136
9574
  const [hover, setHover] = useState7(false);
9137
9575
  useEffect4(() => {
9138
9576
  if (!open) return;
@@ -9224,7 +9662,7 @@ function BlockTypeDropdown({ view, state, label }) {
9224
9662
  ]
9225
9663
  }
9226
9664
  ),
9227
- open && btnRect && createPortal2(
9665
+ open && btnRect && createPortal3(
9228
9666
  /* @__PURE__ */ jsx10(
9229
9667
  "div",
9230
9668
  {
@@ -9286,7 +9724,7 @@ function DropdownItem({ item, onRun }) {
9286
9724
  function SelectionToolbar({ view, state, selectionRect }) {
9287
9725
  const [linkMode, setLinkMode] = useState7(false);
9288
9726
  const [linkHref, setLinkHref] = useState7("");
9289
- const inputRef = useRef4(null);
9727
+ const inputRef = useRef5(null);
9290
9728
  const isBold = hasMark(state, bold2);
9291
9729
  const isItalic = hasMark(state, italic2);
9292
9730
  const isUnderline = hasMark(state, underline2);
@@ -9502,7 +9940,7 @@ function LinkHoverTooltip({ view, link: link2, onEdit }) {
9502
9940
  VPORT_MARGIN2 + tooltipHalfW,
9503
9941
  Math.min((linkLeft + linkRight) / 2, window.innerWidth - VPORT_MARGIN2 - tooltipHalfW)
9504
9942
  );
9505
- return createPortal2(
9943
+ return createPortal3(
9506
9944
  /* @__PURE__ */ jsxs9(
9507
9945
  "div",
9508
9946
  {
@@ -9616,7 +10054,7 @@ function LinkHoverTooltip({ view, link: link2, onEdit }) {
9616
10054
  function LinkEditDialog({ view, link: link2, onClose }) {
9617
10055
  const [textValue, setTextValue] = useState7(link2.text);
9618
10056
  const [hrefValue, setHrefValue] = useState7(link2.href);
9619
- const textInputRef = useRef4(null);
10057
+ const textInputRef = useRef5(null);
9620
10058
  useEffect4(() => {
9621
10059
  setTextValue(link2.text);
9622
10060
  setHrefValue(link2.href);
@@ -9714,7 +10152,7 @@ function LinkEditDialog({ view, link: link2, onClose }) {
9714
10152
  saveLink();
9715
10153
  }
9716
10154
  };
9717
- return createPortal2(
10155
+ return createPortal3(
9718
10156
  /* @__PURE__ */ jsxs9(
9719
10157
  "div",
9720
10158
  {
@@ -9903,7 +10341,7 @@ var INSERT_LINK_EVENT = "ab-insert-link";
9903
10341
  function LinkInsertDialog({ view, cursorPos, onClose }) {
9904
10342
  const [textValue, setTextValue] = useState7("");
9905
10343
  const [hrefValue, setHrefValue] = useState7("");
9906
- const textInputRef = useRef4(null);
10344
+ const textInputRef = useRef5(null);
9907
10345
  useEffect4(() => {
9908
10346
  setTimeout(() => textInputRef.current?.focus(), 0);
9909
10347
  }, []);
@@ -9954,7 +10392,7 @@ function LinkInsertDialog({ view, cursorPos, onClose }) {
9954
10392
  saveLink();
9955
10393
  }
9956
10394
  };
9957
- return createPortal2(
10395
+ return createPortal3(
9958
10396
  /* @__PURE__ */ jsxs9(
9959
10397
  "div",
9960
10398
  {
@@ -10302,8 +10740,8 @@ function FloatingMenu({ view, editorState }) {
10302
10740
  const [showEmptyHandle, setShowEmptyHandle] = useState7(false);
10303
10741
  const [editingLink, setEditingLink] = useState7(null);
10304
10742
  const [insertLinkPos, setInsertLinkPos] = useState7(null);
10305
- const dwellTimerRef = useRef4(null);
10306
- const lastEmptyPosRef = useRef4(null);
10743
+ const dwellTimerRef = useRef5(null);
10744
+ const lastEmptyPosRef = useRef5(null);
10307
10745
  useEffect4(() => {
10308
10746
  const dom = view?.dom;
10309
10747
  if (!dom) return;
@@ -10360,7 +10798,7 @@ function FloatingMenu({ view, editorState }) {
10360
10798
  }
10361
10799
  }
10362
10800
  const selectionRect = hasSelection ? getSelectionDOMRect() : null;
10363
- return createPortal2(
10801
+ return createPortal3(
10364
10802
  /* @__PURE__ */ jsxs9(Fragment4, { children: [
10365
10803
  hasSelection && selectionRect && /* @__PURE__ */ jsx10(
10366
10804
  SelectionToolbar,
@@ -10401,8 +10839,8 @@ function FloatingMenu({ view, editorState }) {
10401
10839
  }
10402
10840
 
10403
10841
  // src/ui/plugin/inlineSuggestPlugin.ts
10404
- import { Plugin as Plugin11, PluginKey as PluginKey9 } from "prosemirror-state";
10405
- import { Decoration as Decoration5, DecorationSet as DecorationSet5 } from "prosemirror-view";
10842
+ import { Plugin as Plugin12, PluginKey as PluginKey9 } from "prosemirror-state";
10843
+ import { Decoration as Decoration6, DecorationSet as DecorationSet6 } from "prosemirror-view";
10406
10844
  var inlineSuggestKey = new PluginKey9("inlineSuggest");
10407
10845
  var DEBOUNCE_MS = 600;
10408
10846
  function createInlineSuggestPlugin(provider, endpoint, options) {
@@ -10410,7 +10848,7 @@ function createInlineSuggestPlugin(provider, endpoint, options) {
10410
10848
  return {
10411
10849
  name: "inlineSuggest",
10412
10850
  plugins: () => [
10413
- new Plugin11({
10851
+ new Plugin12({
10414
10852
  key: inlineSuggestKey,
10415
10853
  state: {
10416
10854
  init: () => ({ suggestion: null, anchorPos: null }),
@@ -10426,13 +10864,13 @@ function createInlineSuggestPlugin(provider, endpoint, options) {
10426
10864
  props: {
10427
10865
  decorations(state) {
10428
10866
  const ps = inlineSuggestKey.getState(state);
10429
- if (!ps?.suggestion || !state.selection.empty) return DecorationSet5.empty;
10867
+ if (!ps?.suggestion || !state.selection.empty) return DecorationSet6.empty;
10430
10868
  const ghost = document.createElement("span");
10431
10869
  ghost.textContent = ps.suggestion;
10432
10870
  ghost.style.cssText = "color: rgba(0,0,0,0.3); pointer-events: none; user-select: none;";
10433
10871
  ghost.setAttribute("aria-hidden", "true");
10434
- return DecorationSet5.create(state.doc, [
10435
- Decoration5.widget(state.selection.from, ghost, {
10872
+ return DecorationSet6.create(state.doc, [
10873
+ Decoration6.widget(state.selection.from, ghost, {
10436
10874
  side: 1,
10437
10875
  key: "inline-suggest"
10438
10876
  })
@@ -10577,9 +11015,26 @@ function hasInvalidJinjaConditions(state) {
10577
11015
  });
10578
11016
  return found;
10579
11017
  }
11018
+ function hasEmptyJinjaBranches(state) {
11019
+ let found = false;
11020
+ state.doc.descendants((node) => {
11021
+ if (found) return false;
11022
+ if (node.type.name === "jinjaIfBranch") {
11023
+ if (node.childCount === 1) {
11024
+ const first = node.firstChild;
11025
+ if (first?.type.name === "paragraph" && first.content.size === 0) {
11026
+ found = true;
11027
+ }
11028
+ }
11029
+ return false;
11030
+ }
11031
+ });
11032
+ return found;
11033
+ }
10580
11034
  export {
10581
11035
  ActionbookRenderer,
10582
11036
  DocumentTreeView,
11037
+ EDITOR_TEXT_STYLES,
10583
11038
  EditorShell,
10584
11039
  FloatingMenu,
10585
11040
  INSERT_LINK_EVENT,
@@ -10611,6 +11066,7 @@ export {
10611
11066
  createTodoNodeViewPlugin,
10612
11067
  hasBrokenAnchorRefs,
10613
11068
  hasDuplicateJumpPoints,
11069
+ hasEmptyJinjaBranches,
10614
11070
  hasInvalidJinjaConditions,
10615
11071
  inlineSuggestKey,
10616
11072
  slashCommandKey,