@sendbird/actionbook-core 0.10.5 → 0.10.6

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
@@ -1250,7 +1250,7 @@ var RESOURCE_TAG_TYPES = ["tool", "manual", "agent_message_template", "handoff",
1250
1250
 
1251
1251
  // src/compat/prosemirror.ts
1252
1252
  var MAX_DEPTH = 128;
1253
- var ALLOWED_URL_PROTOCOLS = /^(https?:|mailto:|tel:|#|\/)/i;
1253
+ var ALLOWED_URL_PROTOCOLS = /^(https?:|mailto:|tel:|#|\/|www\.)/i;
1254
1254
  var LIST_TYPES = /* @__PURE__ */ new Set(["bulletList", "orderedList"]);
1255
1255
  function isLooseList(items) {
1256
1256
  return items.some((li) => {
@@ -2055,7 +2055,7 @@ function jumpPointToMarkdown() {
2055
2055
 
2056
2056
  // src/markdown/mdastAdapter.ts
2057
2057
  var MAX_DEPTH2 = 128;
2058
- var ALLOWED_URL_PROTOCOLS2 = /^(https?:|mailto:|tel:|#|\/)/i;
2058
+ var ALLOWED_URL_PROTOCOLS2 = /^(https?:|mailto:|tel:|#|\/|www\.)/i;
2059
2059
  function sanitizeUrl(url) {
2060
2060
  if (ALLOWED_URL_PROTOCOLS2.test(url)) return url;
2061
2061
  return "";
@@ -2444,7 +2444,7 @@ function serializeToMarkdown(doc2) {
2444
2444
  }
2445
2445
 
2446
2446
  // src/ui/bridge/toProseMirrorJSON.ts
2447
- var ALLOWED_URL_PROTOCOLS3 = /^(https?:|mailto:|tel:|#|\/)/i;
2447
+ var ALLOWED_URL_PROTOCOLS3 = /^(https?:|mailto:|tel:|#|\/|www\.)/i;
2448
2448
  function assertNever(value) {
2449
2449
  throw new Error(`Unexpected AST node: ${JSON.stringify(value)}`);
2450
2450
  }
@@ -2627,7 +2627,184 @@ function astNodesToJSONContent(nodes) {
2627
2627
  return nodes.flatMap(convertBlockToArray);
2628
2628
  }
2629
2629
 
2630
+ // src/ui/styles/editorTextStyles.ts
2631
+ var EDITOR_TEXT_STYLES = `
2632
+ /* \u2500\u2500 CSS custom properties (override on .ProseMirror to customize) \u2500\u2500 */
2633
+ .ProseMirror {
2634
+ /* Font families */
2635
+ --ab-font-heading: 'Gellix', sans-serif;
2636
+ --ab-font-body: 'SF Pro Text', -apple-system, BlinkMacSystemFont, sans-serif;
2637
+ --ab-font-code: 'Roboto Mono', monospace;
2638
+
2639
+ /* Body text */
2640
+ --ab-body-size: 14px;
2641
+ --ab-body-weight: 400;
2642
+ --ab-body-line-height: 20px;
2643
+ --ab-body-letter-spacing: -0.1px;
2644
+ --ab-color-body: #0d0d0d;
2645
+
2646
+ /* H1 */
2647
+ --ab-h1-size: 24px;
2648
+ --ab-h1-weight: 700;
2649
+ --ab-h1-line-height: 32px;
2650
+ --ab-h1-letter-spacing: -0.25px;
2651
+
2652
+ /* H2 */
2653
+ --ab-h2-size: 20px;
2654
+ --ab-h2-weight: 700;
2655
+ --ab-h2-line-height: 24px;
2656
+ --ab-h2-letter-spacing: -0.25px;
2657
+
2658
+ /* H3 */
2659
+ --ab-h3-size: 18px;
2660
+ --ab-h3-weight: 700;
2661
+ --ab-h3-line-height: 24px;
2662
+ --ab-h3-letter-spacing: -0.25px;
2663
+
2664
+ /* Inline code */
2665
+ --ab-code-size: 13px;
2666
+ --ab-code-weight: 400;
2667
+ --ab-code-line-height: 20px;
2668
+ --ab-code-letter-spacing: -0.3px;
2669
+ --ab-code-bg: #ececec;
2670
+ --ab-code-padding: 0 8px;
2671
+ --ab-code-radius: 2px;
2672
+
2673
+ /* Code block */
2674
+ --ab-pre-bg: #ececec;
2675
+ --ab-pre-radius: 4px;
2676
+ --ab-pre-padding: 16px 8px 16px 24px;
2677
+
2678
+ /* Link */
2679
+ --ab-link-color: #6210cc;
2680
+ --ab-link-weight: 500;
2681
+
2682
+ /* Blockquote */
2683
+ --ab-blockquote-border-color: #0d0d0d;
2684
+
2685
+ /* HR */
2686
+ --ab-hr-color: #e0e0e0;
2687
+
2688
+ /* Block spacing */
2689
+ --ab-block-gap: 16px;
2690
+ }
2691
+
2692
+ /* \u2500\u2500 Base text \u2500\u2500 */
2693
+ .ProseMirror {
2694
+ font-family: var(--ab-font-body);
2695
+ font-size: var(--ab-body-size);
2696
+ font-weight: var(--ab-body-weight);
2697
+ line-height: var(--ab-body-line-height);
2698
+ letter-spacing: var(--ab-body-letter-spacing);
2699
+ color: var(--ab-color-body);
2700
+ }
2701
+
2702
+ .ProseMirror > * + * {
2703
+ margin-top: var(--ab-block-gap);
2704
+ }
2705
+
2706
+ /* \u2500\u2500 Headings \u2500\u2500 */
2707
+ .ProseMirror h1 {
2708
+ font-family: var(--ab-font-heading);
2709
+ font-size: var(--ab-h1-size);
2710
+ font-weight: var(--ab-h1-weight);
2711
+ line-height: var(--ab-h1-line-height);
2712
+ letter-spacing: var(--ab-h1-letter-spacing);
2713
+ }
2714
+
2715
+ .ProseMirror h2 {
2716
+ font-family: var(--ab-font-heading);
2717
+ font-size: var(--ab-h2-size);
2718
+ font-weight: var(--ab-h2-weight);
2719
+ line-height: var(--ab-h2-line-height);
2720
+ letter-spacing: var(--ab-h2-letter-spacing);
2721
+ }
2722
+
2723
+ .ProseMirror h3 {
2724
+ font-family: var(--ab-font-heading);
2725
+ font-size: var(--ab-h3-size);
2726
+ font-weight: var(--ab-h3-weight);
2727
+ line-height: var(--ab-h3-line-height);
2728
+ letter-spacing: var(--ab-h3-letter-spacing);
2729
+ }
2730
+
2731
+ /* \u2500\u2500 Inline code \u2500\u2500 */
2732
+ .ProseMirror code {
2733
+ font-family: var(--ab-font-code);
2734
+ font-size: var(--ab-code-size);
2735
+ font-weight: var(--ab-code-weight);
2736
+ line-height: var(--ab-code-line-height);
2737
+ letter-spacing: var(--ab-code-letter-spacing);
2738
+ background: var(--ab-code-bg);
2739
+ padding: var(--ab-code-padding);
2740
+ border-radius: var(--ab-code-radius);
2741
+ }
2742
+
2743
+ /* \u2500\u2500 Code block \u2500\u2500 */
2744
+ .ProseMirror pre {
2745
+ font-family: var(--ab-font-code);
2746
+ font-size: var(--ab-code-size);
2747
+ font-weight: var(--ab-code-weight);
2748
+ line-height: var(--ab-code-line-height);
2749
+ letter-spacing: var(--ab-code-letter-spacing);
2750
+ color: var(--ab-color-body);
2751
+ background: var(--ab-pre-bg);
2752
+ border-radius: var(--ab-pre-radius);
2753
+ padding: var(--ab-pre-padding);
2754
+ overflow-x: auto;
2755
+ white-space: pre;
2756
+ }
2757
+
2758
+ .ProseMirror pre code {
2759
+ background: transparent;
2760
+ padding: 0;
2761
+ border-radius: 0;
2762
+ font-size: inherit;
2763
+ }
2764
+
2765
+ /* \u2500\u2500 Link \u2500\u2500 */
2766
+ .ProseMirror a {
2767
+ color: var(--ab-link-color);
2768
+ font-weight: var(--ab-link-weight);
2769
+ text-decoration: none;
2770
+ }
2771
+
2772
+ .ProseMirror a:hover {
2773
+ text-decoration: none;
2774
+ }
2775
+
2776
+ /* \u2500\u2500 Blockquote \u2500\u2500 */
2777
+ .ProseMirror blockquote {
2778
+ border-left: 2px solid var(--ab-blockquote-border-color);
2779
+ border-radius: 0;
2780
+ padding-left: 20px;
2781
+ color: var(--ab-color-body);
2782
+ }
2783
+
2784
+ /* \u2500\u2500 HR \u2500\u2500 */
2785
+ .ProseMirror hr {
2786
+ border: none;
2787
+ height: 1px;
2788
+ background: var(--ab-hr-color);
2789
+ margin: 8px 0;
2790
+ }
2791
+ `;
2792
+
2630
2793
  // src/ui/hooks/useEditorView.ts
2794
+ var TEXT_STYLE_ID = "ab-editor-text-styles";
2795
+ var textStyleRefCount = 0;
2796
+ function acquireTextStyles() {
2797
+ if (textStyleRefCount++ > 0) return;
2798
+ if (document.getElementById(TEXT_STYLE_ID)) return;
2799
+ const el = document.createElement("style");
2800
+ el.id = TEXT_STYLE_ID;
2801
+ el.textContent = EDITOR_TEXT_STYLES;
2802
+ document.head.appendChild(el);
2803
+ }
2804
+ function releaseTextStyles() {
2805
+ if (--textStyleRefCount > 0) return;
2806
+ document.getElementById(TEXT_STYLE_ID)?.remove();
2807
+ }
2631
2808
  function docToState(doc2, plugins) {
2632
2809
  const pmJSON = doc2 ? toProseMirrorJSON(doc2) : { type: "doc", content: [{ type: "paragraph" }] };
2633
2810
  const pmDoc = PMNode.fromJSON(actionbookSchema, pmJSON);
@@ -2666,17 +2843,20 @@ function useEditorView(config) {
2666
2843
  if (viewInstanceRef.current) {
2667
2844
  viewInstanceRef.current.destroy();
2668
2845
  viewInstanceRef.current = null;
2846
+ releaseTextStyles();
2669
2847
  }
2670
2848
  containerRef.current = container;
2671
2849
  if (!container) {
2672
2850
  forceUpdate((n) => n + 1);
2673
2851
  return;
2674
2852
  }
2853
+ acquireTextStyles();
2675
2854
  const { pmPlugins, nodeViews } = createPluginArray(configRef.current.plugins ?? []);
2676
2855
  const state = docToState(configRef.current.content, pmPlugins);
2677
2856
  const view = new EditorView(container, {
2678
2857
  state,
2679
2858
  nodeViews,
2859
+ attributes: { spellcheck: "false" },
2680
2860
  editable: () => configRef.current.editable === true,
2681
2861
  dispatchTransaction(tr) {
2682
2862
  const newState = view.state.apply(tr);
@@ -2714,8 +2894,11 @@ function useEditorView(config) {
2714
2894
  viewInstanceRef.current?.focus();
2715
2895
  }, []);
2716
2896
  const destroy = useCallback(() => {
2717
- viewInstanceRef.current?.destroy();
2718
- viewInstanceRef.current = null;
2897
+ if (viewInstanceRef.current) {
2898
+ viewInstanceRef.current.destroy();
2899
+ viewInstanceRef.current = null;
2900
+ releaseTextStyles();
2901
+ }
2719
2902
  }, []);
2720
2903
  const getView = useCallback(() => viewInstanceRef.current, []);
2721
2904
  return {
@@ -2736,6 +2919,7 @@ import { TextSelection } from "prosemirror-state";
2736
2919
 
2737
2920
  // src/ui/plugin/slashCommandPlugin.ts
2738
2921
  import { Plugin, PluginKey } from "prosemirror-state";
2922
+ import { Decoration, DecorationSet } from "prosemirror-view";
2739
2923
  var slashCommandKey = new PluginKey("slashCommand");
2740
2924
  var TRIGGER_RE = /(?:^|\s)(\/[^\s]*)$/;
2741
2925
  function deriveState(state, dismissedFrom) {
@@ -2784,9 +2968,30 @@ function createSlashCommandPlugin() {
2784
2968
  const derived = deriveState(newState, dismissedFrom);
2785
2969
  return { ...derived, _dismissedFrom: dismissedFrom };
2786
2970
  }
2971
+ },
2972
+ props: {
2973
+ decorations(state) {
2974
+ const s = plugin.getState(state);
2975
+ if (!s?.active || !s.range) return DecorationSet.empty;
2976
+ const decos = [
2977
+ // Inline highlight for the slash command text
2978
+ Decoration.inline(s.range.from, s.range.to, {
2979
+ class: "ab-slash-trigger"
2980
+ })
2981
+ ];
2982
+ if (s.query.endsWith(":")) {
2983
+ decos.push(
2984
+ Decoration.widget(s.range.to, () => {
2985
+ const span = document.createElement("span");
2986
+ span.className = "ab-slash-trigger-placeholder";
2987
+ span.textContent = "Type a name";
2988
+ return span;
2989
+ }, { side: 1 })
2990
+ );
2991
+ }
2992
+ return DecorationSet.create(state.doc, decos);
2993
+ }
2787
2994
  }
2788
- // Expose only the public fields via the key
2789
- // (InternalState is a superset of SlashCommandState so reads work fine)
2790
2995
  });
2791
2996
  return {
2792
2997
  name: "slashCommand",
@@ -2814,10 +3019,8 @@ var HR_RE = /^([-*_])\1{2,}$/;
2814
3019
  var BULLET_LIST_RE = /^\s*([-*])\s$/;
2815
3020
  var ORDERED_LIST_RE = /^(\d+)\.\s$/;
2816
3021
  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 = /(?:^|[^_])_([^_]+)_$/;
3022
+ var BOLD_RE = /(?:^|[^*])\*\*([^*]+)\*\*$/;
3023
+ var ITALIC_RE = /(?:^|[^_])__([^_]+)__$/;
2821
3024
  var STRIKE_RE = /(?:^|[^~])~~([^~]+)~~$/;
2822
3025
  var CODE_RE = /(?:^|[^`])`([^`]+)`$/;
2823
3026
  function findParentList(state, pos) {
@@ -2994,10 +3197,8 @@ function createInputRulesPlugin() {
2994
3197
  return tr;
2995
3198
  })
2996
3199
  );
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));
3200
+ rules.push(markInputRule(BOLD_RE, actionbookSchema.marks.bold, 2));
3201
+ rules.push(markInputRule(ITALIC_RE, actionbookSchema.marks.italic, 2));
3001
3202
  rules.push(markInputRule(STRIKE_RE, actionbookSchema.marks.strikethrough, 2));
3002
3203
  rules.push(markInputRule(CODE_RE, actionbookSchema.marks.code, 1));
3003
3204
  return rules;
@@ -3020,7 +3221,7 @@ function createHistoryPlugin() {
3020
3221
 
3021
3222
  // src/ui/plugin/keymapPlugin.ts
3022
3223
  import { baseKeymap, chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock, toggleMark, setBlockType, joinBackward } from "prosemirror-commands";
3023
- import { Plugin as Plugin2, TextSelection as TextSelection2 } from "prosemirror-state";
3224
+ import { AllSelection, Plugin as Plugin2, TextSelection as TextSelection2 } from "prosemirror-state";
3024
3225
  import { keymap as keymap3 } from "prosemirror-keymap";
3025
3226
  import { liftListItem, sinkListItem, splitListItem, wrapInList } from "prosemirror-schema-list";
3026
3227
 
@@ -3417,6 +3618,19 @@ function createEmptyDocGuardPlugin() {
3417
3618
  }
3418
3619
  });
3419
3620
  }
3621
+ var selectAllCommand = (state, dispatch) => {
3622
+ const { $from, from, to } = state.selection;
3623
+ const depth = Math.min($from.depth, 1);
3624
+ const blockStart = $from.start(depth);
3625
+ const blockEnd = $from.end(depth);
3626
+ const blockFullySelected = from <= blockStart && to >= blockEnd;
3627
+ if (blockFullySelected) {
3628
+ if (dispatch) dispatch(state.tr.setSelection(new AllSelection(state.doc)));
3629
+ return true;
3630
+ }
3631
+ if (dispatch) dispatch(state.tr.setSelection(TextSelection2.create(state.doc, blockStart, blockEnd)));
3632
+ return true;
3633
+ };
3420
3634
  function createKeymapPlugin() {
3421
3635
  return {
3422
3636
  name: "keymapPlugin",
@@ -3426,10 +3640,11 @@ function createKeymapPlugin() {
3426
3640
  "Shift-Tab": shiftTabCommand,
3427
3641
  Enter: enterCommand,
3428
3642
  "Shift-Enter": shiftEnterCommand,
3643
+ "Mod-a": selectAllCommand,
3429
3644
  "Mod-b": toggleMark(boldMark),
3430
3645
  "Mod-i": toggleMark(italicMark),
3431
3646
  "Mod-u": toggleMark(underlineMark),
3432
- "Mod-Shift-x": toggleMark(strikethroughMark),
3647
+ "Mod-Shift-s": toggleMark(strikethroughMark),
3433
3648
  "Mod-e": toggleMark(codeMark),
3434
3649
  "Mod-Shift-7": wrapInList(bulletList),
3435
3650
  "Mod-Shift-8": wrapInList(orderedList)
@@ -5716,13 +5931,31 @@ function textToSlice(text2, view) {
5716
5931
  }
5717
5932
  }
5718
5933
  var URL_RE = /^https?:\/\/[^\s]+$/i;
5934
+ var shiftHeld = false;
5719
5935
  function createPlugin() {
5720
5936
  return new Plugin3({
5721
5937
  key,
5722
5938
  props: {
5939
+ handleDOMEvents: {
5940
+ keydown(_view, event) {
5941
+ shiftHeld = event.shiftKey;
5942
+ return false;
5943
+ },
5944
+ keyup() {
5945
+ shiftHeld = false;
5946
+ return false;
5947
+ }
5948
+ },
5723
5949
  handlePaste(view, event) {
5724
- const html = event.clipboardData?.getData("text/html");
5725
5950
  const text2 = event.clipboardData?.getData("text/plain");
5951
+ if (shiftHeld) {
5952
+ if (!text2) return false;
5953
+ const textNode = actionbookSchema.text(text2);
5954
+ const slice2 = new Slice(Fragment.from(textNode), 0, 0);
5955
+ view.dispatch(view.state.tr.replaceSelection(slice2));
5956
+ return true;
5957
+ }
5958
+ const html = event.clipboardData?.getData("text/html");
5726
5959
  if (html) {
5727
5960
  const htmlSlice = htmlToSlice(html);
5728
5961
  if (htmlSlice) {
@@ -5784,22 +6017,22 @@ function createMarkdownClipboardPlugin() {
5784
6017
 
5785
6018
  // src/ui/plugin/jumpPointPlugin.ts
5786
6019
  import { Plugin as Plugin4, PluginKey as PluginKey3, TextSelection as TextSelection3 } from "prosemirror-state";
5787
- import { Decoration, DecorationSet } from "prosemirror-view";
6020
+ import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
5788
6021
  var adjacentKey = new PluginKey3("jumpPointAdjacent");
5789
6022
  var JUMP_POINT_ADJACENT_SPEC = { jumpPointAdjacent: true };
5790
6023
  function buildDecorations(state) {
5791
6024
  const { selection } = state;
5792
- if (!selection.empty) return DecorationSet.empty;
6025
+ if (!selection.empty) return DecorationSet2.empty;
5793
6026
  const cursorPos = selection.$from.pos;
5794
6027
  const decorations = [];
5795
6028
  state.doc.descendants((node, pos) => {
5796
6029
  if (node.type.name !== "jumpPoint") return;
5797
6030
  const nodeEnd = pos + node.nodeSize;
5798
6031
  if (cursorPos === pos || cursorPos === nodeEnd) {
5799
- decorations.push(Decoration.node(pos, nodeEnd, {}, JUMP_POINT_ADJACENT_SPEC));
6032
+ decorations.push(Decoration2.node(pos, nodeEnd, {}, JUMP_POINT_ADJACENT_SPEC));
5800
6033
  }
5801
6034
  });
5802
- return DecorationSet.create(state.doc, decorations);
6035
+ return DecorationSet2.create(state.doc, decorations);
5803
6036
  }
5804
6037
  var jumpPointEditKey = new PluginKey3("jumpPointEdit");
5805
6038
  var JUMP_POINT_FULL_RE = /^\^([\p{L}\p{N}_-]+)\^$/u;
@@ -5930,7 +6163,8 @@ function createJumpPointAdjacentPlugin() {
5930
6163
  }
5931
6164
 
5932
6165
  // src/ui/plugin/jumpPointNodeViewPlugin.tsx
5933
- import { useCallback as useCallback2, useState as useState2 } from "react";
6166
+ import { useCallback as useCallback2, useRef as useRef2, useState as useState2 } from "react";
6167
+ import { createPortal } from "react-dom";
5934
6168
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
5935
6169
  function IconDiamondAlert({ size = 12, fill = "#D9352C" }) {
5936
6170
  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 +6192,7 @@ var JUMP_POINT_STYLE = {
5958
6192
  // are set via CSS class .ab-jump-point for heading-level overrides
5959
6193
  };
5960
6194
  var JUMP_POINT_ADJACENT_STYLE = {
5961
- ...JUMP_POINT_STYLE,
5962
- backgroundColor: "#FFE680",
5963
- borderColor: "#FF9500"
6195
+ ...JUMP_POINT_STYLE
5964
6196
  };
5965
6197
  var JUMP_POINT_DUPLICATE_STYLE = {
5966
6198
  ...JUMP_POINT_STYLE,
@@ -5989,64 +6221,71 @@ var CLOSE_BTN_STYLE = {
5989
6221
  lineHeight: 1
5990
6222
  };
5991
6223
  var TOOLTIP_STYLE = {
5992
- position: "absolute",
5993
- bottom: "calc(100% + 8px)",
5994
- left: "50%",
5995
- transform: "translateX(-50%)",
6224
+ position: "fixed",
6225
+ display: "flex",
6226
+ flexDirection: "column",
6227
+ alignItems: "flex-start",
6228
+ alignSelf: "stretch",
6229
+ padding: "16px 20px",
5996
6230
  backgroundColor: "#fff",
5997
6231
  border: "1px solid #CCC",
5998
6232
  borderRadius: "4px",
5999
6233
  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
6234
  fontSize: "14px",
6002
6235
  lineHeight: "20px",
6003
6236
  color: "#0D0D0D",
6004
6237
  maxWidth: "240px",
6005
- zIndex: 100,
6238
+ zIndex: 1100,
6006
6239
  pointerEvents: "none"
6007
6240
  };
6008
- function JumpPointNodeViewComponent({ node, view, getPos, decorations }) {
6241
+ var TOOLTIP_ARROW_STYLE = {
6242
+ position: "absolute",
6243
+ bottom: "-6px",
6244
+ left: "50%",
6245
+ transform: "translateX(-50%) rotate(45deg)",
6246
+ width: "10px",
6247
+ height: "10px",
6248
+ backgroundColor: "#fff",
6249
+ border: "1px solid #CCC",
6250
+ borderTop: "none",
6251
+ borderLeft: "none"
6252
+ };
6253
+ function JumpPointNodeViewComponent({ node, decorations }) {
6009
6254
  const id = node.attrs.id;
6010
6255
  const isAdjacent = decorations?.some((d) => d.spec?.jumpPointAdjacent === true) ?? false;
6011
6256
  const isDuplicate = decorations?.some((d) => d.spec?.jumpPointDuplicate === true) ?? false;
6012
6257
  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]);
6258
+ const spanRef = useRef2(null);
6259
+ const [tooltipPos, setTooltipPos] = useState2(null);
6260
+ const handleMouseEnter = useCallback2(() => {
6261
+ if (!spanRef.current) return;
6262
+ const rect = spanRef.current.getBoundingClientRect();
6263
+ setTooltipPos({ top: rect.top - 8, left: rect.left + rect.width / 2 });
6264
+ setShowTooltip(true);
6265
+ }, []);
6020
6266
  if (isDuplicate) {
6021
6267
  return /* @__PURE__ */ jsxs3(
6022
6268
  "span",
6023
6269
  {
6270
+ ref: spanRef,
6024
6271
  className: "ab-jump-point ab-jump-point-duplicate",
6025
- style: { ...JUMP_POINT_DUPLICATE_STYLE, position: "relative" },
6272
+ style: JUMP_POINT_DUPLICATE_STYLE,
6026
6273
  id: `jp-${id}`,
6027
- onMouseEnter: () => setShowTooltip(true),
6274
+ onMouseEnter: handleMouseEnter,
6028
6275
  onMouseLeave: () => setShowTooltip(false),
6029
6276
  children: [
6030
6277
  /* @__PURE__ */ jsx4("span", { className: "ab-jp-icon", children: /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#9D091E", style: { paddingLeft: "2px", flexShrink: 0 } }) }),
6031
6278
  /* @__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
- ] })
6279
+ /* @__PURE__ */ jsx4("span", { style: { ...CLOSE_BTN_STYLE, cursor: "default" }, children: /* @__PURE__ */ jsx4(IconDiamondAlert, { size: 12, fill: "#D9352C" }) }),
6280
+ showTooltip && tooltipPos && createPortal(
6281
+ /* @__PURE__ */ jsxs3("span", { style: { ...TOOLTIP_STYLE, top: tooltipPos.top, left: tooltipPos.left, transform: "translate(-50%, -100%)" }, children: [
6282
+ "\u201C",
6283
+ id,
6284
+ "\u201D is already used as an anchor",
6285
+ /* @__PURE__ */ jsx4("span", { style: TOOLTIP_ARROW_STYLE })
6286
+ ] }),
6287
+ document.body
6288
+ )
6050
6289
  ]
6051
6290
  }
6052
6291
  );
@@ -6073,7 +6312,7 @@ function createJumpPointNodeViewPlugin() {
6073
6312
 
6074
6313
  // src/ui/plugin/jumpPointValidationPlugin.ts
6075
6314
  import { Plugin as Plugin5, PluginKey as PluginKey4 } from "prosemirror-state";
6076
- import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
6315
+ import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "prosemirror-view";
6077
6316
  var pluginKey = new PluginKey4("jumpPointValidation");
6078
6317
  function collectJumpPointIds(state) {
6079
6318
  const ids = /* @__PURE__ */ new Set();
@@ -6139,40 +6378,104 @@ function createJumpPointValidationPlugin() {
6139
6378
  },
6140
6379
  props: {
6141
6380
  decorations(state) {
6142
- return pluginKey.getState(state) ?? DecorationSet2.empty;
6381
+ return pluginKey.getState(state) ?? DecorationSet3.empty;
6143
6382
  }
6144
6383
  }
6145
6384
  })
6146
6385
  ]
6147
6386
  };
6148
6387
  }
6388
+ function createBrokenRefIcon(refId) {
6389
+ const wrapper = document.createElement("span");
6390
+ wrapper.style.cssText = "position: relative; display: inline-flex; align-items: center; cursor: help;";
6391
+ wrapper.contentEditable = "false";
6392
+ const ns = "http://www.w3.org/2000/svg";
6393
+ const svg = document.createElementNS(ns, "svg");
6394
+ svg.setAttribute("width", "14");
6395
+ svg.setAttribute("height", "14");
6396
+ svg.setAttribute("viewBox", "0 0 16 16");
6397
+ svg.setAttribute("fill", "none");
6398
+ const path = document.createElementNS(ns, "path");
6399
+ path.setAttribute("fill-rule", "evenodd");
6400
+ path.setAttribute("clip-rule", "evenodd");
6401
+ 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");
6402
+ path.setAttribute("fill", "#D9352C");
6403
+ svg.appendChild(path);
6404
+ const iconSpan = document.createElement("span");
6405
+ iconSpan.style.cssText = "display: inline-flex; margin-left: 2px; vertical-align: middle;";
6406
+ iconSpan.appendChild(svg);
6407
+ const tooltip = document.createElement("span");
6408
+ tooltip.textContent = `Anchor "${refId}" no longer exists`;
6409
+ tooltip.style.cssText = [
6410
+ "display:none",
6411
+ "position:fixed",
6412
+ "z-index:1100",
6413
+ "pointer-events:none",
6414
+ "padding:16px 20px",
6415
+ "background:#fff",
6416
+ "border:1px solid #CCC",
6417
+ "border-radius:4px",
6418
+ "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)",
6419
+ "font-size:14px",
6420
+ "line-height:20px",
6421
+ "color:#0D0D0D",
6422
+ "max-width:240px",
6423
+ "white-space:normal"
6424
+ ].join(";");
6425
+ const arrow = document.createElement("span");
6426
+ arrow.style.cssText = [
6427
+ "position:absolute",
6428
+ "bottom:-6px",
6429
+ "left:50%",
6430
+ "transform:translateX(-50%) rotate(45deg)",
6431
+ "width:10px",
6432
+ "height:10px",
6433
+ "background:#fff",
6434
+ "border:1px solid #CCC",
6435
+ "border-top:none",
6436
+ "border-left:none"
6437
+ ].join(";");
6438
+ tooltip.appendChild(arrow);
6439
+ wrapper.addEventListener("mouseenter", () => {
6440
+ const rect = wrapper.getBoundingClientRect();
6441
+ tooltip.style.left = `${rect.left + rect.width / 2}px`;
6442
+ tooltip.style.top = `${rect.top - 8}px`;
6443
+ tooltip.style.transform = "translate(-50%, -100%)";
6444
+ tooltip.style.display = "flex";
6445
+ tooltip.style.flexDirection = "column";
6446
+ tooltip.style.alignItems = "flex-start";
6447
+ document.body.appendChild(tooltip);
6448
+ });
6449
+ wrapper.addEventListener("mouseleave", () => {
6450
+ tooltip.style.display = "none";
6451
+ if (tooltip.parentElement === document.body) document.body.removeChild(tooltip);
6452
+ });
6453
+ wrapper.appendChild(iconSpan);
6454
+ return wrapper;
6455
+ }
6149
6456
  function buildDecorations2(state) {
6150
6457
  const decos = [];
6151
6458
  const duplicates = findDuplicatePositions(state);
6152
6459
  for (const { pos, size } of duplicates) {
6153
6460
  decos.push(
6154
- Decoration2.node(pos, pos + size, { class: "jump-point-duplicate" }, { jumpPointDuplicate: true })
6461
+ Decoration3.node(pos, pos + size, { class: "jump-point-duplicate" }, { jumpPointDuplicate: true })
6155
6462
  );
6156
6463
  }
6157
6464
  const existingIds = collectJumpPointIds(state);
6158
6465
  const brokenRefs = findBrokenAnchorRefs(state, existingIds);
6159
6466
  for (const { from, to, refId } of brokenRefs) {
6160
6467
  decos.push(
6161
- Decoration2.inline(from, to, {
6468
+ Decoration3.inline(from, to, {
6162
6469
  class: "broken-anchor-ref",
6163
6470
  "data-broken-ref": refId,
6164
- title: `Anchor "${refId}" no longer exists`,
6165
- style: "color: #D9352C; font-weight: 500; text-decoration: none;"
6471
+ spellcheck: "false",
6472
+ style: "color: #D9352C; font-weight: 500; text-decoration: none !important; text-decoration-line: none !important;"
6166
6473
  })
6167
6474
  );
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 }));
6475
+ decos.push(Decoration3.widget(to, () => createBrokenRefIcon(refId), { side: -1 }));
6173
6476
  }
6174
- if (decos.length === 0) return DecorationSet2.empty;
6175
- return DecorationSet2.create(state.doc, decos);
6477
+ if (decos.length === 0) return DecorationSet3.empty;
6478
+ return DecorationSet3.create(state.doc, decos);
6176
6479
  }
6177
6480
 
6178
6481
  // src/ui/plugin/inlineToolTagNodeViewPlugin.tsx
@@ -6246,7 +6549,7 @@ function createInlineToolTagNodeViewPlugin() {
6246
6549
 
6247
6550
  // src/ui/plugin/jinjaDecoration.ts
6248
6551
  import { Plugin as Plugin6, PluginKey as PluginKey5 } from "prosemirror-state";
6249
- import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "prosemirror-view";
6552
+ import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "prosemirror-view";
6250
6553
  var jinjaPluginKey = new PluginKey5("jinjaDecoration");
6251
6554
  var JINJA_TAG_RE = /\{%\s*(if|elif|else|endif)\s*([^%]*?)\s*%\}/g;
6252
6555
  function getBlockPositions(doc2) {
@@ -6269,7 +6572,7 @@ function addInlineChipDecorations(doc2, decorations) {
6269
6572
  const to = from + match[0].length;
6270
6573
  const keyword = match[1];
6271
6574
  decorations.push(
6272
- Decoration3.inline(from, to, {
6575
+ Decoration4.inline(from, to, {
6273
6576
  class: `jinja-chip jinja-chip-${keyword}`
6274
6577
  })
6275
6578
  );
@@ -6310,7 +6613,7 @@ function addStructureBorderDecorations(doc2, blocks, decorations) {
6310
6613
  const classes = ["jinja-bar", `jinja-bar-${branchType}`];
6311
6614
  if (i === first || prevType !== branchType) classes.push("jinja-bar-first");
6312
6615
  if (i === last || nextType !== branchType) classes.push("jinja-bar-last");
6313
- decorations.push(Decoration3.node(block.from, block.to, { class: classes.join(" ") }));
6616
+ decorations.push(Decoration4.node(block.from, block.to, { class: classes.join(" ") }));
6314
6617
  }
6315
6618
  }
6316
6619
  }
@@ -6326,13 +6629,13 @@ function hasJinjaTags(doc2) {
6326
6629
  return found;
6327
6630
  }
6328
6631
  function buildDecorations3(doc2) {
6329
- if (!hasJinjaTags(doc2)) return DecorationSet3.empty;
6632
+ if (!hasJinjaTags(doc2)) return DecorationSet4.empty;
6330
6633
  const blocks = getBlockPositions(doc2);
6331
6634
  const decorations = [];
6332
6635
  addInlineChipDecorations(doc2, decorations);
6333
6636
  addStructureBorderDecorations(doc2, blocks, decorations);
6334
- if (decorations.length === 0) return DecorationSet3.empty;
6335
- return DecorationSet3.create(doc2, decorations);
6637
+ if (decorations.length === 0) return DecorationSet4.empty;
6638
+ return DecorationSet4.create(doc2, decorations);
6336
6639
  }
6337
6640
  function createJinjaDecorationPlugin() {
6338
6641
  return {
@@ -6360,7 +6663,7 @@ function createJinjaDecorationPlugin() {
6360
6663
  }
6361
6664
 
6362
6665
  // src/ui/plugin/jinjaIfBlockPlugin.tsx
6363
- import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
6666
+ import { useEffect as useEffect2, useRef as useRef3, useState as useState3 } from "react";
6364
6667
  import { createRoot as createRoot2 } from "react-dom/client";
6365
6668
  import { Plugin as Plugin7, TextSelection as TextSelection4 } from "prosemirror-state";
6366
6669
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
@@ -6377,7 +6680,9 @@ var JINJA_STYLES = `
6377
6680
  display: grid;
6378
6681
  grid-template-areas:
6379
6682
  "header"
6683
+ "error"
6380
6684
  "body"
6685
+ "body-error"
6381
6686
  "footer";
6382
6687
  gap: 0;
6383
6688
  }
@@ -6467,6 +6772,7 @@ var JINJA_STYLES = `
6467
6772
  }
6468
6773
 
6469
6774
  .jinja-condition-error {
6775
+ grid-area: error;
6470
6776
  display: flex;
6471
6777
  align-items: center;
6472
6778
  padding: 4px 0 4px 52px;
@@ -6475,6 +6781,16 @@ var JINJA_STYLES = `
6475
6781
  line-height: 16px;
6476
6782
  }
6477
6783
 
6784
+ .jinja-body-error {
6785
+ grid-area: body-error;
6786
+ display: flex;
6787
+ align-items: center;
6788
+ padding: 0 0 4px 52px;
6789
+ color: #D9352C;
6790
+ font-size: 12px;
6791
+ line-height: 16px;
6792
+ }
6793
+
6478
6794
  .jinja-token-variable {
6479
6795
  color: #4141B2;
6480
6796
  }
@@ -6865,8 +7181,8 @@ function ConditionDisplay({
6865
7181
  }) {
6866
7182
  const [isEditing, setIsEditing] = useState3(false);
6867
7183
  const [draftValue, setDraftValue] = useState3(condition);
6868
- const inputRef = useRef2(null);
6869
- const autoFocusedRef = useRef2(false);
7184
+ const inputRef = useRef3(null);
7185
+ const autoFocusedRef = useRef3(false);
6870
7186
  useEffect2(() => {
6871
7187
  if (!isEditing) {
6872
7188
  setDraftValue(condition);
@@ -6976,6 +7292,8 @@ function JinjaBranchHeader({
6976
7292
  branchType,
6977
7293
  condition,
6978
7294
  editable = true,
7295
+ isBodyEmpty,
7296
+ isBodyTouched,
6979
7297
  onConditionChange,
6980
7298
  onDelete,
6981
7299
  onAddBranch,
@@ -6983,8 +7301,8 @@ function JinjaBranchHeader({
6983
7301
  hasElseBranch
6984
7302
  }) {
6985
7303
  const [menuSource, setMenuSource] = useState3(null);
6986
- const kebabRef = useRef2(null);
6987
- const footerRef = useRef2(null);
7304
+ const kebabRef = useRef3(null);
7305
+ const footerRef = useRef3(null);
6988
7306
  useEffect2(() => {
6989
7307
  if (!menuSource) return;
6990
7308
  const handlePointerDown = (event) => {
@@ -7090,6 +7408,7 @@ function JinjaBranchHeader({
7090
7408
  ] }) : null
7091
7409
  ] }),
7092
7410
  conditionError && /* @__PURE__ */ jsx6("div", { className: "jinja-condition-error", children: conditionError }),
7411
+ isBodyEmpty && isBodyTouched && editable && /* @__PURE__ */ jsx6("div", { className: "jinja-body-error", children: "This field is required." }),
7093
7412
  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
7413
  /* @__PURE__ */ jsx6(
7095
7414
  "button",
@@ -7155,6 +7474,7 @@ var JinjaIfBranchView = class {
7155
7474
  node;
7156
7475
  view;
7157
7476
  getPos;
7477
+ bodyTouched = false;
7158
7478
  constructor(node, view, getPos) {
7159
7479
  this.node = node;
7160
7480
  this.view = view;
@@ -7179,6 +7499,12 @@ var JinjaIfBranchView = class {
7179
7499
  this.contentDOM.setAttribute("data-placeholder", PLACEHOLDER_TEXT);
7180
7500
  body.appendChild(dividerColumn);
7181
7501
  body.appendChild(this.contentDOM);
7502
+ this.contentDOM.addEventListener("focusout", () => {
7503
+ if (!this.bodyTouched && isBranchBodyEmpty(this.node)) {
7504
+ this.bodyTouched = true;
7505
+ this.render();
7506
+ }
7507
+ });
7182
7508
  this.root = createRoot2(this.headerContainer);
7183
7509
  this._onSiblingsChanged = () => this.render();
7184
7510
  this.dom.addEventListener("jinja-siblings-changed", this._onSiblingsChanged);
@@ -7226,6 +7552,8 @@ var JinjaIfBranchView = class {
7226
7552
  branchType,
7227
7553
  condition,
7228
7554
  editable: this.view.editable && this.view.dom.getAttribute("contenteditable") !== "false",
7555
+ isBodyEmpty: isBranchBodyEmpty(this.node),
7556
+ isBodyTouched: this.bodyTouched,
7229
7557
  isLastBranch: showAddButton,
7230
7558
  hasElseBranch: context && nodePos != null ? getElseBranch(this.view, nodePos) : false,
7231
7559
  onConditionChange: (value) => {
@@ -8425,27 +8753,27 @@ function createTodoNodeViewPlugin() {
8425
8753
 
8426
8754
  // src/ui/plugin/placeholderPlugin.ts
8427
8755
  import { Plugin as Plugin10, PluginKey as PluginKey8 } from "prosemirror-state";
8428
- import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "prosemirror-view";
8756
+ import { Decoration as Decoration5, DecorationSet as DecorationSet5 } from "prosemirror-view";
8429
8757
  var pluginKey2 = new PluginKey8("placeholder");
8430
8758
  function buildDecorations4(state) {
8431
8759
  const { doc: doc2, selection } = state;
8432
- if (!selection.empty) return DecorationSet4.empty;
8760
+ if (!selection.empty) return DecorationSet5.empty;
8433
8761
  const $from = selection.$from;
8434
8762
  const parent = $from.parent;
8435
8763
  if (parent.type.name !== "paragraph" || parent.content.size !== 0) {
8436
- return DecorationSet4.empty;
8764
+ return DecorationSet5.empty;
8437
8765
  }
8438
8766
  const EXCLUDED_ANCESTORS = /* @__PURE__ */ new Set(["noteBlock", "blockquote", "jinjaIfBranch"]);
8439
8767
  for (let d = $from.depth - 1; d > 0; d--) {
8440
8768
  if (EXCLUDED_ANCESTORS.has($from.node(d).type.name)) {
8441
- return DecorationSet4.empty;
8769
+ return DecorationSet5.empty;
8442
8770
  }
8443
8771
  }
8444
8772
  const pos = $from.before($from.depth);
8445
- const deco = Decoration4.node(pos, pos + parent.nodeSize, {
8773
+ const deco = Decoration5.node(pos, pos + parent.nodeSize, {
8446
8774
  class: "ab-empty-paragraph"
8447
8775
  });
8448
- return DecorationSet4.create(doc2, [deco]);
8776
+ return DecorationSet5.create(doc2, [deco]);
8449
8777
  }
8450
8778
  function createPlaceholderPlugin() {
8451
8779
  let isEditable = true;
@@ -8475,8 +8803,8 @@ function createPlaceholderPlugin() {
8475
8803
  },
8476
8804
  props: {
8477
8805
  decorations(state) {
8478
- if (!isEditable) return DecorationSet4.empty;
8479
- return pluginKey2.getState(state) ?? DecorationSet4.empty;
8806
+ if (!isEditable) return DecorationSet5.empty;
8807
+ return pluginKey2.getState(state) ?? DecorationSet5.empty;
8480
8808
  }
8481
8809
  }
8482
8810
  })
@@ -8485,8 +8813,8 @@ function createPlaceholderPlugin() {
8485
8813
  }
8486
8814
 
8487
8815
  // 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";
8816
+ import React4, { useEffect as useEffect3, useLayoutEffect, useRef as useRef4, useState as useState4 } from "react";
8817
+ import { createPortal as createPortal2 } from "react-dom";
8490
8818
  import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
8491
8819
  function filterItems(items, query) {
8492
8820
  if (!query) return items;
@@ -8513,7 +8841,7 @@ function injectScrollbarStyle() {
8513
8841
  `;
8514
8842
  document.head.appendChild(style);
8515
8843
  }
8516
- var POPUP_SHADOW = "0 0 0 1px #CCCCCC, 0 4px 16px rgba(0, 0, 0, 0.12)";
8844
+ 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
8845
  var BTN_RESET = {
8518
8846
  border: "none",
8519
8847
  padding: 0,
@@ -8530,7 +8858,7 @@ var MENU_WIDTH = 440;
8530
8858
  var MAX_MENU_H = 400;
8531
8859
  function SlashCommandMenu({ view, editorState, items }) {
8532
8860
  const [selectedIndex, setSelectedIndex] = useState4(0);
8533
- const listRef = useRef3(null);
8861
+ const listRef = useRef4(null);
8534
8862
  useLayoutEffect(() => {
8535
8863
  injectScrollbarStyle();
8536
8864
  }, []);
@@ -8549,7 +8877,7 @@ function SlashCommandMenu({ view, editorState, items }) {
8549
8877
  item?.scrollIntoView({ block: "nearest" });
8550
8878
  }, [selectedIndex]);
8551
8879
  useEffect3(() => {
8552
- if (!active || !view) return;
8880
+ if (!active || !view || filtered.length === 0) return;
8553
8881
  const onKeyDown = (e) => {
8554
8882
  if (e.key === "ArrowDown") {
8555
8883
  e.preventDefault();
@@ -8575,7 +8903,7 @@ function SlashCommandMenu({ view, editorState, items }) {
8575
8903
  document.addEventListener("keydown", onKeyDown, true);
8576
8904
  return () => document.removeEventListener("keydown", onKeyDown, true);
8577
8905
  }, [active, view, filtered, selectedIndex, range]);
8578
- if (!active || !view || !range) return null;
8906
+ if (!active || !view || !range || filtered.length === 0) return null;
8579
8907
  const coords = view.coordsAtPos(range.from);
8580
8908
  let top = coords.bottom + 4;
8581
8909
  let left = coords.left;
@@ -8583,7 +8911,7 @@ function SlashCommandMenu({ view, editorState, items }) {
8583
8911
  if (top + MAX_MENU_H > window.innerHeight - VPORT_MARGIN) {
8584
8912
  top = coords.top - MAX_MENU_H - 4;
8585
8913
  }
8586
- return createPortal(
8914
+ return createPortal2(
8587
8915
  /* @__PURE__ */ jsx7(
8588
8916
  "div",
8589
8917
  {
@@ -8596,41 +8924,29 @@ function SlashCommandMenu({ view, editorState, items }) {
8596
8924
  maxHeight: MAX_MENU_H,
8597
8925
  overflowY: "auto",
8598
8926
  background: "#fff",
8599
- borderRadius: 8,
8927
+ borderRadius: 4,
8600
8928
  boxShadow: POPUP_SHADOW,
8601
- padding: "4px",
8929
+ padding: "8px 0",
8602
8930
  zIndex: 1100,
8603
8931
  animation: "ab-float-in 0.12s ease"
8604
8932
  },
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) => {
8933
+ children: /* @__PURE__ */ jsx7("div", { ref: listRef, children: filtered.map((item, i) => {
8616
8934
  const prevGroup = i > 0 ? filtered[i - 1].group : void 0;
8617
8935
  const showGroupHeader = item.group && item.group !== prevGroup;
8618
8936
  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
- ] }),
8937
+ showGroupHeader && /* @__PURE__ */ jsx7(Fragment3, { children: /* @__PURE__ */ jsx7(
8938
+ "div",
8939
+ {
8940
+ style: {
8941
+ padding: "16px 16px 4px",
8942
+ fontSize: 12,
8943
+ fontWeight: 600,
8944
+ lineHeight: "12px",
8945
+ color: "#858585"
8946
+ },
8947
+ children: item.group
8948
+ }
8949
+ ) }),
8634
8950
  /* @__PURE__ */ jsx7(
8635
8951
  SlashMenuItem,
8636
8952
  {
@@ -8664,20 +8980,22 @@ function SlashMenuItem({ item, index, selected, onMouseEnter, onMouseDown }) {
8664
8980
  display: "flex",
8665
8981
  alignItems: "center",
8666
8982
  gap: 12,
8667
- padding: "6px 8px",
8668
- background: selected ? "rgba(0, 0, 0, 0.04)" : "transparent",
8983
+ padding: "6px 16px",
8984
+ background: selected ? "rgba(13, 13, 13, 0.04)" : "transparent",
8669
8985
  transition: "background 0.08s"
8670
8986
  },
8671
8987
  onMouseEnter,
8672
8988
  onMouseDown,
8673
8989
  children: [
8674
- item.icon !== void 0 && /* @__PURE__ */ jsx7("span", { style: { flexShrink: 0, display: "flex", alignItems: "center" }, children: item.icon }),
8675
8990
  /* @__PURE__ */ jsx7(
8676
8991
  "span",
8677
8992
  {
8678
8993
  style: {
8994
+ flex: "1 0 0",
8679
8995
  fontSize: 14,
8680
8996
  fontWeight: 400,
8997
+ lineHeight: "20px",
8998
+ letterSpacing: "-0.1px",
8681
8999
  color: "#0d0d0d",
8682
9000
  whiteSpace: "nowrap",
8683
9001
  overflow: "hidden",
@@ -8687,17 +9005,16 @@ function SlashMenuItem({ item, index, selected, onMouseEnter, onMouseDown }) {
8687
9005
  children: item.title
8688
9006
  }
8689
9007
  ),
8690
- /* @__PURE__ */ jsx7("span", { style: { flex: 1 } }),
8691
9008
  (item.description || item.shortcut) && /* @__PURE__ */ jsx7(
8692
9009
  "span",
8693
9010
  {
8694
9011
  style: {
8695
9012
  fontSize: 12,
9013
+ lineHeight: "16px",
8696
9014
  color: "#5e5e5e",
8697
9015
  whiteSpace: "nowrap",
8698
9016
  flexShrink: 0,
8699
- textAlign: "right",
8700
- fontFamily: item.shortcut ? "monospace" : "inherit"
9017
+ textAlign: "right"
8701
9018
  },
8702
9019
  children: item.shortcut ?? item.description
8703
9020
  }
@@ -8953,8 +9270,8 @@ function DocumentTreeView({ doc: doc2, className, onNavigate }) {
8953
9270
  }
8954
9271
 
8955
9272
  // 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";
9273
+ import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef5, useState as useState7 } from "react";
9274
+ import { createPortal as createPortal3 } from "react-dom";
8958
9275
  import { TextSelection as TextSelection6 } from "prosemirror-state";
8959
9276
  import { setBlockType as setBlockType2, toggleMark as toggleMark2, wrapIn } from "prosemirror-commands";
8960
9277
  import { wrapInList as wrapInList2 } from "prosemirror-schema-list";
@@ -9132,7 +9449,7 @@ function Divider() {
9132
9449
  }
9133
9450
  function BlockTypeDropdown({ view, state, label }) {
9134
9451
  const [open, setOpen] = useState7(false);
9135
- const btnRef = useRef4(null);
9452
+ const btnRef = useRef5(null);
9136
9453
  const [hover, setHover] = useState7(false);
9137
9454
  useEffect4(() => {
9138
9455
  if (!open) return;
@@ -9224,7 +9541,7 @@ function BlockTypeDropdown({ view, state, label }) {
9224
9541
  ]
9225
9542
  }
9226
9543
  ),
9227
- open && btnRect && createPortal2(
9544
+ open && btnRect && createPortal3(
9228
9545
  /* @__PURE__ */ jsx10(
9229
9546
  "div",
9230
9547
  {
@@ -9286,7 +9603,7 @@ function DropdownItem({ item, onRun }) {
9286
9603
  function SelectionToolbar({ view, state, selectionRect }) {
9287
9604
  const [linkMode, setLinkMode] = useState7(false);
9288
9605
  const [linkHref, setLinkHref] = useState7("");
9289
- const inputRef = useRef4(null);
9606
+ const inputRef = useRef5(null);
9290
9607
  const isBold = hasMark(state, bold2);
9291
9608
  const isItalic = hasMark(state, italic2);
9292
9609
  const isUnderline = hasMark(state, underline2);
@@ -9502,7 +9819,7 @@ function LinkHoverTooltip({ view, link: link2, onEdit }) {
9502
9819
  VPORT_MARGIN2 + tooltipHalfW,
9503
9820
  Math.min((linkLeft + linkRight) / 2, window.innerWidth - VPORT_MARGIN2 - tooltipHalfW)
9504
9821
  );
9505
- return createPortal2(
9822
+ return createPortal3(
9506
9823
  /* @__PURE__ */ jsxs9(
9507
9824
  "div",
9508
9825
  {
@@ -9616,7 +9933,7 @@ function LinkHoverTooltip({ view, link: link2, onEdit }) {
9616
9933
  function LinkEditDialog({ view, link: link2, onClose }) {
9617
9934
  const [textValue, setTextValue] = useState7(link2.text);
9618
9935
  const [hrefValue, setHrefValue] = useState7(link2.href);
9619
- const textInputRef = useRef4(null);
9936
+ const textInputRef = useRef5(null);
9620
9937
  useEffect4(() => {
9621
9938
  setTextValue(link2.text);
9622
9939
  setHrefValue(link2.href);
@@ -9714,7 +10031,7 @@ function LinkEditDialog({ view, link: link2, onClose }) {
9714
10031
  saveLink();
9715
10032
  }
9716
10033
  };
9717
- return createPortal2(
10034
+ return createPortal3(
9718
10035
  /* @__PURE__ */ jsxs9(
9719
10036
  "div",
9720
10037
  {
@@ -9903,7 +10220,7 @@ var INSERT_LINK_EVENT = "ab-insert-link";
9903
10220
  function LinkInsertDialog({ view, cursorPos, onClose }) {
9904
10221
  const [textValue, setTextValue] = useState7("");
9905
10222
  const [hrefValue, setHrefValue] = useState7("");
9906
- const textInputRef = useRef4(null);
10223
+ const textInputRef = useRef5(null);
9907
10224
  useEffect4(() => {
9908
10225
  setTimeout(() => textInputRef.current?.focus(), 0);
9909
10226
  }, []);
@@ -9954,7 +10271,7 @@ function LinkInsertDialog({ view, cursorPos, onClose }) {
9954
10271
  saveLink();
9955
10272
  }
9956
10273
  };
9957
- return createPortal2(
10274
+ return createPortal3(
9958
10275
  /* @__PURE__ */ jsxs9(
9959
10276
  "div",
9960
10277
  {
@@ -10302,8 +10619,8 @@ function FloatingMenu({ view, editorState }) {
10302
10619
  const [showEmptyHandle, setShowEmptyHandle] = useState7(false);
10303
10620
  const [editingLink, setEditingLink] = useState7(null);
10304
10621
  const [insertLinkPos, setInsertLinkPos] = useState7(null);
10305
- const dwellTimerRef = useRef4(null);
10306
- const lastEmptyPosRef = useRef4(null);
10622
+ const dwellTimerRef = useRef5(null);
10623
+ const lastEmptyPosRef = useRef5(null);
10307
10624
  useEffect4(() => {
10308
10625
  const dom = view?.dom;
10309
10626
  if (!dom) return;
@@ -10360,7 +10677,7 @@ function FloatingMenu({ view, editorState }) {
10360
10677
  }
10361
10678
  }
10362
10679
  const selectionRect = hasSelection ? getSelectionDOMRect() : null;
10363
- return createPortal2(
10680
+ return createPortal3(
10364
10681
  /* @__PURE__ */ jsxs9(Fragment4, { children: [
10365
10682
  hasSelection && selectionRect && /* @__PURE__ */ jsx10(
10366
10683
  SelectionToolbar,
@@ -10402,7 +10719,7 @@ function FloatingMenu({ view, editorState }) {
10402
10719
 
10403
10720
  // src/ui/plugin/inlineSuggestPlugin.ts
10404
10721
  import { Plugin as Plugin11, PluginKey as PluginKey9 } from "prosemirror-state";
10405
- import { Decoration as Decoration5, DecorationSet as DecorationSet5 } from "prosemirror-view";
10722
+ import { Decoration as Decoration6, DecorationSet as DecorationSet6 } from "prosemirror-view";
10406
10723
  var inlineSuggestKey = new PluginKey9("inlineSuggest");
10407
10724
  var DEBOUNCE_MS = 600;
10408
10725
  function createInlineSuggestPlugin(provider, endpoint, options) {
@@ -10426,13 +10743,13 @@ function createInlineSuggestPlugin(provider, endpoint, options) {
10426
10743
  props: {
10427
10744
  decorations(state) {
10428
10745
  const ps = inlineSuggestKey.getState(state);
10429
- if (!ps?.suggestion || !state.selection.empty) return DecorationSet5.empty;
10746
+ if (!ps?.suggestion || !state.selection.empty) return DecorationSet6.empty;
10430
10747
  const ghost = document.createElement("span");
10431
10748
  ghost.textContent = ps.suggestion;
10432
10749
  ghost.style.cssText = "color: rgba(0,0,0,0.3); pointer-events: none; user-select: none;";
10433
10750
  ghost.setAttribute("aria-hidden", "true");
10434
- return DecorationSet5.create(state.doc, [
10435
- Decoration5.widget(state.selection.from, ghost, {
10751
+ return DecorationSet6.create(state.doc, [
10752
+ Decoration6.widget(state.selection.from, ghost, {
10436
10753
  side: 1,
10437
10754
  key: "inline-suggest"
10438
10755
  })
@@ -10577,9 +10894,26 @@ function hasInvalidJinjaConditions(state) {
10577
10894
  });
10578
10895
  return found;
10579
10896
  }
10897
+ function hasEmptyJinjaBranches(state) {
10898
+ let found = false;
10899
+ state.doc.descendants((node) => {
10900
+ if (found) return false;
10901
+ if (node.type.name === "jinjaIfBranch") {
10902
+ if (node.childCount === 1) {
10903
+ const first = node.firstChild;
10904
+ if (first?.type.name === "paragraph" && first.content.size === 0) {
10905
+ found = true;
10906
+ }
10907
+ }
10908
+ return false;
10909
+ }
10910
+ });
10911
+ return found;
10912
+ }
10580
10913
  export {
10581
10914
  ActionbookRenderer,
10582
10915
  DocumentTreeView,
10916
+ EDITOR_TEXT_STYLES,
10583
10917
  EditorShell,
10584
10918
  FloatingMenu,
10585
10919
  INSERT_LINK_EVENT,
@@ -10611,6 +10945,7 @@ export {
10611
10945
  createTodoNodeViewPlugin,
10612
10946
  hasBrokenAnchorRefs,
10613
10947
  hasDuplicateJumpPoints,
10948
+ hasEmptyJinjaBranches,
10614
10949
  hasInvalidJinjaConditions,
10615
10950
  inlineSuggestKey,
10616
10951
  slashCommandKey,