@sendbird/actionbook-core 0.10.4 → 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) {
@@ -2840,10 +3043,22 @@ function handleListInputRule(state, start, end, listType, attrs) {
2840
3043
  const { depth: listDepth, node: listNode } = parentList;
2841
3044
  const paraContentSize = $from.parent.content.size;
2842
3045
  const matchedLen = end - start;
2843
- if (paraContentSize !== matchedLen) return null;
3046
+ const isMarkerOnly = paraContentSize === matchedLen;
2844
3047
  if (listNode.type === listType) {
3048
+ if (isMarkerOnly && listType === actionbookSchema.nodes.orderedList && attrs?.start != null) {
3049
+ const newStart = attrs.start;
3050
+ const currentStart = listNode.attrs.start ?? 1;
3051
+ if (newStart !== currentStart) {
3052
+ const listStart = $from.before(listDepth);
3053
+ const tr2 = state.tr.delete(start, end);
3054
+ tr2.setNodeMarkup(tr2.mapping.map(listStart), void 0, { ...listNode.attrs, start: newStart });
3055
+ tr2.setSelection(TextSelection.near(tr2.doc.resolve(tr2.mapping.map(start))));
3056
+ return tr2;
3057
+ }
3058
+ }
2845
3059
  return NOOP;
2846
3060
  }
3061
+ if (!isMarkerOnly) return NOOP;
2847
3062
  const { listItem: liType } = actionbookSchema.nodes;
2848
3063
  const tr = state.tr.delete(start, end);
2849
3064
  const $pos = tr.doc.resolve(tr.mapping.map(resolvePos));
@@ -2982,10 +3197,8 @@ function createInputRulesPlugin() {
2982
3197
  return tr;
2983
3198
  })
2984
3199
  );
2985
- rules.push(markInputRule(BOLD_STAR_RE, actionbookSchema.marks.bold, 2));
2986
- rules.push(markInputRule(BOLD_UNDER_RE, actionbookSchema.marks.bold, 2));
2987
- rules.push(markInputRule(ITALIC_STAR_RE, actionbookSchema.marks.italic, 1));
2988
- 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));
2989
3202
  rules.push(markInputRule(STRIKE_RE, actionbookSchema.marks.strikethrough, 2));
2990
3203
  rules.push(markInputRule(CODE_RE, actionbookSchema.marks.code, 1));
2991
3204
  return rules;
@@ -3008,7 +3221,7 @@ function createHistoryPlugin() {
3008
3221
 
3009
3222
  // src/ui/plugin/keymapPlugin.ts
3010
3223
  import { baseKeymap, chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock, toggleMark, setBlockType, joinBackward } from "prosemirror-commands";
3011
- import { Plugin as Plugin2, TextSelection as TextSelection2 } from "prosemirror-state";
3224
+ import { AllSelection, Plugin as Plugin2, TextSelection as TextSelection2 } from "prosemirror-state";
3012
3225
  import { keymap as keymap3 } from "prosemirror-keymap";
3013
3226
  import { liftListItem, sinkListItem, splitListItem, wrapInList } from "prosemirror-schema-list";
3014
3227
 
@@ -3405,6 +3618,19 @@ function createEmptyDocGuardPlugin() {
3405
3618
  }
3406
3619
  });
3407
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
+ };
3408
3634
  function createKeymapPlugin() {
3409
3635
  return {
3410
3636
  name: "keymapPlugin",
@@ -3414,10 +3640,11 @@ function createKeymapPlugin() {
3414
3640
  "Shift-Tab": shiftTabCommand,
3415
3641
  Enter: enterCommand,
3416
3642
  "Shift-Enter": shiftEnterCommand,
3643
+ "Mod-a": selectAllCommand,
3417
3644
  "Mod-b": toggleMark(boldMark),
3418
3645
  "Mod-i": toggleMark(italicMark),
3419
3646
  "Mod-u": toggleMark(underlineMark),
3420
- "Mod-Shift-x": toggleMark(strikethroughMark),
3647
+ "Mod-Shift-s": toggleMark(strikethroughMark),
3421
3648
  "Mod-e": toggleMark(codeMark),
3422
3649
  "Mod-Shift-7": wrapInList(bulletList),
3423
3650
  "Mod-Shift-8": wrapInList(orderedList)
@@ -5704,13 +5931,31 @@ function textToSlice(text2, view) {
5704
5931
  }
5705
5932
  }
5706
5933
  var URL_RE = /^https?:\/\/[^\s]+$/i;
5934
+ var shiftHeld = false;
5707
5935
  function createPlugin() {
5708
5936
  return new Plugin3({
5709
5937
  key,
5710
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
+ },
5711
5949
  handlePaste(view, event) {
5712
- const html = event.clipboardData?.getData("text/html");
5713
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");
5714
5959
  if (html) {
5715
5960
  const htmlSlice = htmlToSlice(html);
5716
5961
  if (htmlSlice) {
@@ -5772,22 +6017,22 @@ function createMarkdownClipboardPlugin() {
5772
6017
 
5773
6018
  // src/ui/plugin/jumpPointPlugin.ts
5774
6019
  import { Plugin as Plugin4, PluginKey as PluginKey3, TextSelection as TextSelection3 } from "prosemirror-state";
5775
- import { Decoration, DecorationSet } from "prosemirror-view";
6020
+ import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
5776
6021
  var adjacentKey = new PluginKey3("jumpPointAdjacent");
5777
6022
  var JUMP_POINT_ADJACENT_SPEC = { jumpPointAdjacent: true };
5778
6023
  function buildDecorations(state) {
5779
6024
  const { selection } = state;
5780
- if (!selection.empty) return DecorationSet.empty;
6025
+ if (!selection.empty) return DecorationSet2.empty;
5781
6026
  const cursorPos = selection.$from.pos;
5782
6027
  const decorations = [];
5783
6028
  state.doc.descendants((node, pos) => {
5784
6029
  if (node.type.name !== "jumpPoint") return;
5785
6030
  const nodeEnd = pos + node.nodeSize;
5786
6031
  if (cursorPos === pos || cursorPos === nodeEnd) {
5787
- decorations.push(Decoration.node(pos, nodeEnd, {}, JUMP_POINT_ADJACENT_SPEC));
6032
+ decorations.push(Decoration2.node(pos, nodeEnd, {}, JUMP_POINT_ADJACENT_SPEC));
5788
6033
  }
5789
6034
  });
5790
- return DecorationSet.create(state.doc, decorations);
6035
+ return DecorationSet2.create(state.doc, decorations);
5791
6036
  }
5792
6037
  var jumpPointEditKey = new PluginKey3("jumpPointEdit");
5793
6038
  var JUMP_POINT_FULL_RE = /^\^([\p{L}\p{N}_-]+)\^$/u;
@@ -5918,7 +6163,8 @@ function createJumpPointAdjacentPlugin() {
5918
6163
  }
5919
6164
 
5920
6165
  // src/ui/plugin/jumpPointNodeViewPlugin.tsx
5921
- 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";
5922
6168
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
5923
6169
  function IconDiamondAlert({ size = 12, fill = "#D9352C" }) {
5924
6170
  return /* @__PURE__ */ jsxs3("svg", { width: size, height: size, viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
@@ -5946,9 +6192,7 @@ var JUMP_POINT_STYLE = {
5946
6192
  // are set via CSS class .ab-jump-point for heading-level overrides
5947
6193
  };
5948
6194
  var JUMP_POINT_ADJACENT_STYLE = {
5949
- ...JUMP_POINT_STYLE,
5950
- backgroundColor: "#FFE680",
5951
- borderColor: "#FF9500"
6195
+ ...JUMP_POINT_STYLE
5952
6196
  };
5953
6197
  var JUMP_POINT_DUPLICATE_STYLE = {
5954
6198
  ...JUMP_POINT_STYLE,
@@ -5977,64 +6221,71 @@ var CLOSE_BTN_STYLE = {
5977
6221
  lineHeight: 1
5978
6222
  };
5979
6223
  var TOOLTIP_STYLE = {
5980
- position: "absolute",
5981
- bottom: "calc(100% + 8px)",
5982
- left: "50%",
5983
- transform: "translateX(-50%)",
6224
+ position: "fixed",
6225
+ display: "flex",
6226
+ flexDirection: "column",
6227
+ alignItems: "flex-start",
6228
+ alignSelf: "stretch",
6229
+ padding: "16px 20px",
5984
6230
  backgroundColor: "#fff",
5985
6231
  border: "1px solid #CCC",
5986
6232
  borderRadius: "4px",
5987
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)",
5988
- padding: "16px 20px",
5989
6234
  fontSize: "14px",
5990
6235
  lineHeight: "20px",
5991
6236
  color: "#0D0D0D",
5992
6237
  maxWidth: "240px",
5993
- zIndex: 100,
6238
+ zIndex: 1100,
5994
6239
  pointerEvents: "none"
5995
6240
  };
5996
- 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 }) {
5997
6254
  const id = node.attrs.id;
5998
6255
  const isAdjacent = decorations?.some((d) => d.spec?.jumpPointAdjacent === true) ?? false;
5999
6256
  const isDuplicate = decorations?.some((d) => d.spec?.jumpPointDuplicate === true) ?? false;
6000
6257
  const [showTooltip, setShowTooltip] = useState2(false);
6001
- const handleDelete2 = useCallback2(() => {
6002
- const pos = getPos();
6003
- if (pos == null) return;
6004
- const tr = view.state.tr.delete(pos, pos + node.nodeSize);
6005
- view.dispatch(tr);
6006
- view.focus();
6007
- }, [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
+ }, []);
6008
6266
  if (isDuplicate) {
6009
6267
  return /* @__PURE__ */ jsxs3(
6010
6268
  "span",
6011
6269
  {
6270
+ ref: spanRef,
6012
6271
  className: "ab-jump-point ab-jump-point-duplicate",
6013
- style: { ...JUMP_POINT_DUPLICATE_STYLE, position: "relative" },
6272
+ style: JUMP_POINT_DUPLICATE_STYLE,
6014
6273
  id: `jp-${id}`,
6015
- onMouseEnter: () => setShowTooltip(true),
6274
+ onMouseEnter: handleMouseEnter,
6016
6275
  onMouseLeave: () => setShowTooltip(false),
6017
6276
  children: [
6018
6277
  /* @__PURE__ */ jsx4("span", { className: "ab-jp-icon", children: /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#9D091E", style: { paddingLeft: "2px", flexShrink: 0 } }) }),
6019
6278
  /* @__PURE__ */ jsx4("span", { className: "ab-jp-label", style: LABEL_STYLE, children: id }),
6020
- /* @__PURE__ */ jsx4(
6021
- "button",
6022
- {
6023
- style: CLOSE_BTN_STYLE,
6024
- onMouseDown: (e) => {
6025
- e.preventDefault();
6026
- e.stopPropagation();
6027
- handleDelete2();
6028
- },
6029
- title: "Remove duplicate anchor",
6030
- children: /* @__PURE__ */ jsx4(IconDiamondAlert, { size: 12, fill: "#D9352C" })
6031
- }
6032
- ),
6033
- showTooltip && /* @__PURE__ */ jsxs3("span", { style: TOOLTIP_STYLE, children: [
6034
- "\u201C",
6035
- id,
6036
- "\u201D is already used as an anchor"
6037
- ] })
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
+ )
6038
6289
  ]
6039
6290
  }
6040
6291
  );
@@ -6061,7 +6312,7 @@ function createJumpPointNodeViewPlugin() {
6061
6312
 
6062
6313
  // src/ui/plugin/jumpPointValidationPlugin.ts
6063
6314
  import { Plugin as Plugin5, PluginKey as PluginKey4 } from "prosemirror-state";
6064
- import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
6315
+ import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "prosemirror-view";
6065
6316
  var pluginKey = new PluginKey4("jumpPointValidation");
6066
6317
  function collectJumpPointIds(state) {
6067
6318
  const ids = /* @__PURE__ */ new Set();
@@ -6127,40 +6378,104 @@ function createJumpPointValidationPlugin() {
6127
6378
  },
6128
6379
  props: {
6129
6380
  decorations(state) {
6130
- return pluginKey.getState(state) ?? DecorationSet2.empty;
6381
+ return pluginKey.getState(state) ?? DecorationSet3.empty;
6131
6382
  }
6132
6383
  }
6133
6384
  })
6134
6385
  ]
6135
6386
  };
6136
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
+ }
6137
6456
  function buildDecorations2(state) {
6138
6457
  const decos = [];
6139
6458
  const duplicates = findDuplicatePositions(state);
6140
6459
  for (const { pos, size } of duplicates) {
6141
6460
  decos.push(
6142
- Decoration2.node(pos, pos + size, { class: "jump-point-duplicate" }, { jumpPointDuplicate: true })
6461
+ Decoration3.node(pos, pos + size, { class: "jump-point-duplicate" }, { jumpPointDuplicate: true })
6143
6462
  );
6144
6463
  }
6145
6464
  const existingIds = collectJumpPointIds(state);
6146
6465
  const brokenRefs = findBrokenAnchorRefs(state, existingIds);
6147
6466
  for (const { from, to, refId } of brokenRefs) {
6148
6467
  decos.push(
6149
- Decoration2.inline(from, to, {
6468
+ Decoration3.inline(from, to, {
6150
6469
  class: "broken-anchor-ref",
6151
6470
  "data-broken-ref": refId,
6152
- title: `Anchor "${refId}" no longer exists`,
6153
- 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;"
6154
6473
  })
6155
6474
  );
6156
- const icon = document.createElement("span");
6157
- icon.textContent = " \u26A0";
6158
- icon.style.cssText = "color: #D9352C; font-size: 14px; vertical-align: middle;";
6159
- icon.setAttribute("aria-hidden", "true");
6160
- decos.push(Decoration2.widget(to, icon, { side: 1 }));
6475
+ decos.push(Decoration3.widget(to, () => createBrokenRefIcon(refId), { side: -1 }));
6161
6476
  }
6162
- if (decos.length === 0) return DecorationSet2.empty;
6163
- return DecorationSet2.create(state.doc, decos);
6477
+ if (decos.length === 0) return DecorationSet3.empty;
6478
+ return DecorationSet3.create(state.doc, decos);
6164
6479
  }
6165
6480
 
6166
6481
  // src/ui/plugin/inlineToolTagNodeViewPlugin.tsx
@@ -6234,7 +6549,7 @@ function createInlineToolTagNodeViewPlugin() {
6234
6549
 
6235
6550
  // src/ui/plugin/jinjaDecoration.ts
6236
6551
  import { Plugin as Plugin6, PluginKey as PluginKey5 } from "prosemirror-state";
6237
- import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "prosemirror-view";
6552
+ import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "prosemirror-view";
6238
6553
  var jinjaPluginKey = new PluginKey5("jinjaDecoration");
6239
6554
  var JINJA_TAG_RE = /\{%\s*(if|elif|else|endif)\s*([^%]*?)\s*%\}/g;
6240
6555
  function getBlockPositions(doc2) {
@@ -6257,7 +6572,7 @@ function addInlineChipDecorations(doc2, decorations) {
6257
6572
  const to = from + match[0].length;
6258
6573
  const keyword = match[1];
6259
6574
  decorations.push(
6260
- Decoration3.inline(from, to, {
6575
+ Decoration4.inline(from, to, {
6261
6576
  class: `jinja-chip jinja-chip-${keyword}`
6262
6577
  })
6263
6578
  );
@@ -6298,7 +6613,7 @@ function addStructureBorderDecorations(doc2, blocks, decorations) {
6298
6613
  const classes = ["jinja-bar", `jinja-bar-${branchType}`];
6299
6614
  if (i === first || prevType !== branchType) classes.push("jinja-bar-first");
6300
6615
  if (i === last || nextType !== branchType) classes.push("jinja-bar-last");
6301
- decorations.push(Decoration3.node(block.from, block.to, { class: classes.join(" ") }));
6616
+ decorations.push(Decoration4.node(block.from, block.to, { class: classes.join(" ") }));
6302
6617
  }
6303
6618
  }
6304
6619
  }
@@ -6314,13 +6629,13 @@ function hasJinjaTags(doc2) {
6314
6629
  return found;
6315
6630
  }
6316
6631
  function buildDecorations3(doc2) {
6317
- if (!hasJinjaTags(doc2)) return DecorationSet3.empty;
6632
+ if (!hasJinjaTags(doc2)) return DecorationSet4.empty;
6318
6633
  const blocks = getBlockPositions(doc2);
6319
6634
  const decorations = [];
6320
6635
  addInlineChipDecorations(doc2, decorations);
6321
6636
  addStructureBorderDecorations(doc2, blocks, decorations);
6322
- if (decorations.length === 0) return DecorationSet3.empty;
6323
- return DecorationSet3.create(doc2, decorations);
6637
+ if (decorations.length === 0) return DecorationSet4.empty;
6638
+ return DecorationSet4.create(doc2, decorations);
6324
6639
  }
6325
6640
  function createJinjaDecorationPlugin() {
6326
6641
  return {
@@ -6348,7 +6663,7 @@ function createJinjaDecorationPlugin() {
6348
6663
  }
6349
6664
 
6350
6665
  // src/ui/plugin/jinjaIfBlockPlugin.tsx
6351
- 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";
6352
6667
  import { createRoot as createRoot2 } from "react-dom/client";
6353
6668
  import { Plugin as Plugin7, TextSelection as TextSelection4 } from "prosemirror-state";
6354
6669
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
@@ -6365,7 +6680,9 @@ var JINJA_STYLES = `
6365
6680
  display: grid;
6366
6681
  grid-template-areas:
6367
6682
  "header"
6683
+ "error"
6368
6684
  "body"
6685
+ "body-error"
6369
6686
  "footer";
6370
6687
  gap: 0;
6371
6688
  }
@@ -6455,6 +6772,7 @@ var JINJA_STYLES = `
6455
6772
  }
6456
6773
 
6457
6774
  .jinja-condition-error {
6775
+ grid-area: error;
6458
6776
  display: flex;
6459
6777
  align-items: center;
6460
6778
  padding: 4px 0 4px 52px;
@@ -6463,6 +6781,16 @@ var JINJA_STYLES = `
6463
6781
  line-height: 16px;
6464
6782
  }
6465
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
+
6466
6794
  .jinja-token-variable {
6467
6795
  color: #4141B2;
6468
6796
  }
@@ -6853,8 +7181,8 @@ function ConditionDisplay({
6853
7181
  }) {
6854
7182
  const [isEditing, setIsEditing] = useState3(false);
6855
7183
  const [draftValue, setDraftValue] = useState3(condition);
6856
- const inputRef = useRef2(null);
6857
- const autoFocusedRef = useRef2(false);
7184
+ const inputRef = useRef3(null);
7185
+ const autoFocusedRef = useRef3(false);
6858
7186
  useEffect2(() => {
6859
7187
  if (!isEditing) {
6860
7188
  setDraftValue(condition);
@@ -6964,6 +7292,8 @@ function JinjaBranchHeader({
6964
7292
  branchType,
6965
7293
  condition,
6966
7294
  editable = true,
7295
+ isBodyEmpty,
7296
+ isBodyTouched,
6967
7297
  onConditionChange,
6968
7298
  onDelete,
6969
7299
  onAddBranch,
@@ -6971,8 +7301,8 @@ function JinjaBranchHeader({
6971
7301
  hasElseBranch
6972
7302
  }) {
6973
7303
  const [menuSource, setMenuSource] = useState3(null);
6974
- const kebabRef = useRef2(null);
6975
- const footerRef = useRef2(null);
7304
+ const kebabRef = useRef3(null);
7305
+ const footerRef = useRef3(null);
6976
7306
  useEffect2(() => {
6977
7307
  if (!menuSource) return;
6978
7308
  const handlePointerDown = (event) => {
@@ -7078,6 +7408,7 @@ function JinjaBranchHeader({
7078
7408
  ] }) : null
7079
7409
  ] }),
7080
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." }),
7081
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: [
7082
7413
  /* @__PURE__ */ jsx6(
7083
7414
  "button",
@@ -7143,6 +7474,7 @@ var JinjaIfBranchView = class {
7143
7474
  node;
7144
7475
  view;
7145
7476
  getPos;
7477
+ bodyTouched = false;
7146
7478
  constructor(node, view, getPos) {
7147
7479
  this.node = node;
7148
7480
  this.view = view;
@@ -7167,6 +7499,12 @@ var JinjaIfBranchView = class {
7167
7499
  this.contentDOM.setAttribute("data-placeholder", PLACEHOLDER_TEXT);
7168
7500
  body.appendChild(dividerColumn);
7169
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
+ });
7170
7508
  this.root = createRoot2(this.headerContainer);
7171
7509
  this._onSiblingsChanged = () => this.render();
7172
7510
  this.dom.addEventListener("jinja-siblings-changed", this._onSiblingsChanged);
@@ -7214,6 +7552,8 @@ var JinjaIfBranchView = class {
7214
7552
  branchType,
7215
7553
  condition,
7216
7554
  editable: this.view.editable && this.view.dom.getAttribute("contenteditable") !== "false",
7555
+ isBodyEmpty: isBranchBodyEmpty(this.node),
7556
+ isBodyTouched: this.bodyTouched,
7217
7557
  isLastBranch: showAddButton,
7218
7558
  hasElseBranch: context && nodePos != null ? getElseBranch(this.view, nodePos) : false,
7219
7559
  onConditionChange: (value) => {
@@ -8413,27 +8753,27 @@ function createTodoNodeViewPlugin() {
8413
8753
 
8414
8754
  // src/ui/plugin/placeholderPlugin.ts
8415
8755
  import { Plugin as Plugin10, PluginKey as PluginKey8 } from "prosemirror-state";
8416
- import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "prosemirror-view";
8756
+ import { Decoration as Decoration5, DecorationSet as DecorationSet5 } from "prosemirror-view";
8417
8757
  var pluginKey2 = new PluginKey8("placeholder");
8418
8758
  function buildDecorations4(state) {
8419
8759
  const { doc: doc2, selection } = state;
8420
- if (!selection.empty) return DecorationSet4.empty;
8760
+ if (!selection.empty) return DecorationSet5.empty;
8421
8761
  const $from = selection.$from;
8422
8762
  const parent = $from.parent;
8423
8763
  if (parent.type.name !== "paragraph" || parent.content.size !== 0) {
8424
- return DecorationSet4.empty;
8764
+ return DecorationSet5.empty;
8425
8765
  }
8426
8766
  const EXCLUDED_ANCESTORS = /* @__PURE__ */ new Set(["noteBlock", "blockquote", "jinjaIfBranch"]);
8427
8767
  for (let d = $from.depth - 1; d > 0; d--) {
8428
8768
  if (EXCLUDED_ANCESTORS.has($from.node(d).type.name)) {
8429
- return DecorationSet4.empty;
8769
+ return DecorationSet5.empty;
8430
8770
  }
8431
8771
  }
8432
8772
  const pos = $from.before($from.depth);
8433
- const deco = Decoration4.node(pos, pos + parent.nodeSize, {
8773
+ const deco = Decoration5.node(pos, pos + parent.nodeSize, {
8434
8774
  class: "ab-empty-paragraph"
8435
8775
  });
8436
- return DecorationSet4.create(doc2, [deco]);
8776
+ return DecorationSet5.create(doc2, [deco]);
8437
8777
  }
8438
8778
  function createPlaceholderPlugin() {
8439
8779
  let isEditable = true;
@@ -8463,8 +8803,8 @@ function createPlaceholderPlugin() {
8463
8803
  },
8464
8804
  props: {
8465
8805
  decorations(state) {
8466
- if (!isEditable) return DecorationSet4.empty;
8467
- return pluginKey2.getState(state) ?? DecorationSet4.empty;
8806
+ if (!isEditable) return DecorationSet5.empty;
8807
+ return pluginKey2.getState(state) ?? DecorationSet5.empty;
8468
8808
  }
8469
8809
  }
8470
8810
  })
@@ -8473,8 +8813,8 @@ function createPlaceholderPlugin() {
8473
8813
  }
8474
8814
 
8475
8815
  // src/ui/components/SlashCommandMenu.tsx
8476
- import React4, { useEffect as useEffect3, useLayoutEffect, useRef as useRef3, useState as useState4 } from "react";
8477
- 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";
8478
8818
  import { Fragment as Fragment3, jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
8479
8819
  function filterItems(items, query) {
8480
8820
  if (!query) return items;
@@ -8501,7 +8841,7 @@ function injectScrollbarStyle() {
8501
8841
  `;
8502
8842
  document.head.appendChild(style);
8503
8843
  }
8504
- 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)";
8505
8845
  var BTN_RESET = {
8506
8846
  border: "none",
8507
8847
  padding: 0,
@@ -8518,7 +8858,7 @@ var MENU_WIDTH = 440;
8518
8858
  var MAX_MENU_H = 400;
8519
8859
  function SlashCommandMenu({ view, editorState, items }) {
8520
8860
  const [selectedIndex, setSelectedIndex] = useState4(0);
8521
- const listRef = useRef3(null);
8861
+ const listRef = useRef4(null);
8522
8862
  useLayoutEffect(() => {
8523
8863
  injectScrollbarStyle();
8524
8864
  }, []);
@@ -8537,7 +8877,7 @@ function SlashCommandMenu({ view, editorState, items }) {
8537
8877
  item?.scrollIntoView({ block: "nearest" });
8538
8878
  }, [selectedIndex]);
8539
8879
  useEffect3(() => {
8540
- if (!active || !view) return;
8880
+ if (!active || !view || filtered.length === 0) return;
8541
8881
  const onKeyDown = (e) => {
8542
8882
  if (e.key === "ArrowDown") {
8543
8883
  e.preventDefault();
@@ -8563,7 +8903,7 @@ function SlashCommandMenu({ view, editorState, items }) {
8563
8903
  document.addEventListener("keydown", onKeyDown, true);
8564
8904
  return () => document.removeEventListener("keydown", onKeyDown, true);
8565
8905
  }, [active, view, filtered, selectedIndex, range]);
8566
- if (!active || !view || !range) return null;
8906
+ if (!active || !view || !range || filtered.length === 0) return null;
8567
8907
  const coords = view.coordsAtPos(range.from);
8568
8908
  let top = coords.bottom + 4;
8569
8909
  let left = coords.left;
@@ -8571,7 +8911,7 @@ function SlashCommandMenu({ view, editorState, items }) {
8571
8911
  if (top + MAX_MENU_H > window.innerHeight - VPORT_MARGIN) {
8572
8912
  top = coords.top - MAX_MENU_H - 4;
8573
8913
  }
8574
- return createPortal(
8914
+ return createPortal2(
8575
8915
  /* @__PURE__ */ jsx7(
8576
8916
  "div",
8577
8917
  {
@@ -8584,41 +8924,29 @@ function SlashCommandMenu({ view, editorState, items }) {
8584
8924
  maxHeight: MAX_MENU_H,
8585
8925
  overflowY: "auto",
8586
8926
  background: "#fff",
8587
- borderRadius: 8,
8927
+ borderRadius: 4,
8588
8928
  boxShadow: POPUP_SHADOW,
8589
- padding: "4px",
8929
+ padding: "8px 0",
8590
8930
  zIndex: 1100,
8591
8931
  animation: "ab-float-in 0.12s ease"
8592
8932
  },
8593
- children: /* @__PURE__ */ jsx7("div", { ref: listRef, children: filtered.length === 0 ? /* @__PURE__ */ jsx7(
8594
- "div",
8595
- {
8596
- style: {
8597
- padding: "8px 12px",
8598
- fontSize: 13,
8599
- color: "#9ca3af"
8600
- },
8601
- children: "No results"
8602
- }
8603
- ) : filtered.map((item, i) => {
8933
+ children: /* @__PURE__ */ jsx7("div", { ref: listRef, children: filtered.map((item, i) => {
8604
8934
  const prevGroup = i > 0 ? filtered[i - 1].group : void 0;
8605
8935
  const showGroupHeader = item.group && item.group !== prevGroup;
8606
8936
  return /* @__PURE__ */ jsxs6(React4.Fragment, { children: [
8607
- showGroupHeader && /* @__PURE__ */ jsxs6(Fragment3, { children: [
8608
- i > 0 && /* @__PURE__ */ jsx7("div", { style: { height: 1, background: "rgba(0,0,0,0.06)", margin: "4px 10px" } }),
8609
- /* @__PURE__ */ jsx7(
8610
- "div",
8611
- {
8612
- style: {
8613
- padding: "8px 16px 4px",
8614
- fontSize: 13,
8615
- fontWeight: 400,
8616
- color: "#5e5e5e"
8617
- },
8618
- children: item.group
8619
- }
8620
- )
8621
- ] }),
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
+ ) }),
8622
8950
  /* @__PURE__ */ jsx7(
8623
8951
  SlashMenuItem,
8624
8952
  {
@@ -8652,20 +8980,22 @@ function SlashMenuItem({ item, index, selected, onMouseEnter, onMouseDown }) {
8652
8980
  display: "flex",
8653
8981
  alignItems: "center",
8654
8982
  gap: 12,
8655
- padding: "6px 8px",
8656
- background: selected ? "rgba(0, 0, 0, 0.04)" : "transparent",
8983
+ padding: "6px 16px",
8984
+ background: selected ? "rgba(13, 13, 13, 0.04)" : "transparent",
8657
8985
  transition: "background 0.08s"
8658
8986
  },
8659
8987
  onMouseEnter,
8660
8988
  onMouseDown,
8661
8989
  children: [
8662
- item.icon !== void 0 && /* @__PURE__ */ jsx7("span", { style: { flexShrink: 0, display: "flex", alignItems: "center" }, children: item.icon }),
8663
8990
  /* @__PURE__ */ jsx7(
8664
8991
  "span",
8665
8992
  {
8666
8993
  style: {
8994
+ flex: "1 0 0",
8667
8995
  fontSize: 14,
8668
8996
  fontWeight: 400,
8997
+ lineHeight: "20px",
8998
+ letterSpacing: "-0.1px",
8669
8999
  color: "#0d0d0d",
8670
9000
  whiteSpace: "nowrap",
8671
9001
  overflow: "hidden",
@@ -8675,17 +9005,16 @@ function SlashMenuItem({ item, index, selected, onMouseEnter, onMouseDown }) {
8675
9005
  children: item.title
8676
9006
  }
8677
9007
  ),
8678
- /* @__PURE__ */ jsx7("span", { style: { flex: 1 } }),
8679
9008
  (item.description || item.shortcut) && /* @__PURE__ */ jsx7(
8680
9009
  "span",
8681
9010
  {
8682
9011
  style: {
8683
9012
  fontSize: 12,
9013
+ lineHeight: "16px",
8684
9014
  color: "#5e5e5e",
8685
9015
  whiteSpace: "nowrap",
8686
9016
  flexShrink: 0,
8687
- textAlign: "right",
8688
- fontFamily: item.shortcut ? "monospace" : "inherit"
9017
+ textAlign: "right"
8689
9018
  },
8690
9019
  children: item.shortcut ?? item.description
8691
9020
  }
@@ -8941,8 +9270,8 @@ function DocumentTreeView({ doc: doc2, className, onNavigate }) {
8941
9270
  }
8942
9271
 
8943
9272
  // src/ui/components/FloatingMenu.tsx
8944
- import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
8945
- 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";
8946
9275
  import { TextSelection as TextSelection6 } from "prosemirror-state";
8947
9276
  import { setBlockType as setBlockType2, toggleMark as toggleMark2, wrapIn } from "prosemirror-commands";
8948
9277
  import { wrapInList as wrapInList2 } from "prosemirror-schema-list";
@@ -9120,7 +9449,7 @@ function Divider() {
9120
9449
  }
9121
9450
  function BlockTypeDropdown({ view, state, label }) {
9122
9451
  const [open, setOpen] = useState7(false);
9123
- const btnRef = useRef4(null);
9452
+ const btnRef = useRef5(null);
9124
9453
  const [hover, setHover] = useState7(false);
9125
9454
  useEffect4(() => {
9126
9455
  if (!open) return;
@@ -9212,7 +9541,7 @@ function BlockTypeDropdown({ view, state, label }) {
9212
9541
  ]
9213
9542
  }
9214
9543
  ),
9215
- open && btnRect && createPortal2(
9544
+ open && btnRect && createPortal3(
9216
9545
  /* @__PURE__ */ jsx10(
9217
9546
  "div",
9218
9547
  {
@@ -9274,7 +9603,7 @@ function DropdownItem({ item, onRun }) {
9274
9603
  function SelectionToolbar({ view, state, selectionRect }) {
9275
9604
  const [linkMode, setLinkMode] = useState7(false);
9276
9605
  const [linkHref, setLinkHref] = useState7("");
9277
- const inputRef = useRef4(null);
9606
+ const inputRef = useRef5(null);
9278
9607
  const isBold = hasMark(state, bold2);
9279
9608
  const isItalic = hasMark(state, italic2);
9280
9609
  const isUnderline = hasMark(state, underline2);
@@ -9490,7 +9819,7 @@ function LinkHoverTooltip({ view, link: link2, onEdit }) {
9490
9819
  VPORT_MARGIN2 + tooltipHalfW,
9491
9820
  Math.min((linkLeft + linkRight) / 2, window.innerWidth - VPORT_MARGIN2 - tooltipHalfW)
9492
9821
  );
9493
- return createPortal2(
9822
+ return createPortal3(
9494
9823
  /* @__PURE__ */ jsxs9(
9495
9824
  "div",
9496
9825
  {
@@ -9604,7 +9933,7 @@ function LinkHoverTooltip({ view, link: link2, onEdit }) {
9604
9933
  function LinkEditDialog({ view, link: link2, onClose }) {
9605
9934
  const [textValue, setTextValue] = useState7(link2.text);
9606
9935
  const [hrefValue, setHrefValue] = useState7(link2.href);
9607
- const textInputRef = useRef4(null);
9936
+ const textInputRef = useRef5(null);
9608
9937
  useEffect4(() => {
9609
9938
  setTextValue(link2.text);
9610
9939
  setHrefValue(link2.href);
@@ -9702,7 +10031,7 @@ function LinkEditDialog({ view, link: link2, onClose }) {
9702
10031
  saveLink();
9703
10032
  }
9704
10033
  };
9705
- return createPortal2(
10034
+ return createPortal3(
9706
10035
  /* @__PURE__ */ jsxs9(
9707
10036
  "div",
9708
10037
  {
@@ -9891,7 +10220,7 @@ var INSERT_LINK_EVENT = "ab-insert-link";
9891
10220
  function LinkInsertDialog({ view, cursorPos, onClose }) {
9892
10221
  const [textValue, setTextValue] = useState7("");
9893
10222
  const [hrefValue, setHrefValue] = useState7("");
9894
- const textInputRef = useRef4(null);
10223
+ const textInputRef = useRef5(null);
9895
10224
  useEffect4(() => {
9896
10225
  setTimeout(() => textInputRef.current?.focus(), 0);
9897
10226
  }, []);
@@ -9942,7 +10271,7 @@ function LinkInsertDialog({ view, cursorPos, onClose }) {
9942
10271
  saveLink();
9943
10272
  }
9944
10273
  };
9945
- return createPortal2(
10274
+ return createPortal3(
9946
10275
  /* @__PURE__ */ jsxs9(
9947
10276
  "div",
9948
10277
  {
@@ -10290,8 +10619,8 @@ function FloatingMenu({ view, editorState }) {
10290
10619
  const [showEmptyHandle, setShowEmptyHandle] = useState7(false);
10291
10620
  const [editingLink, setEditingLink] = useState7(null);
10292
10621
  const [insertLinkPos, setInsertLinkPos] = useState7(null);
10293
- const dwellTimerRef = useRef4(null);
10294
- const lastEmptyPosRef = useRef4(null);
10622
+ const dwellTimerRef = useRef5(null);
10623
+ const lastEmptyPosRef = useRef5(null);
10295
10624
  useEffect4(() => {
10296
10625
  const dom = view?.dom;
10297
10626
  if (!dom) return;
@@ -10348,7 +10677,7 @@ function FloatingMenu({ view, editorState }) {
10348
10677
  }
10349
10678
  }
10350
10679
  const selectionRect = hasSelection ? getSelectionDOMRect() : null;
10351
- return createPortal2(
10680
+ return createPortal3(
10352
10681
  /* @__PURE__ */ jsxs9(Fragment4, { children: [
10353
10682
  hasSelection && selectionRect && /* @__PURE__ */ jsx10(
10354
10683
  SelectionToolbar,
@@ -10390,7 +10719,7 @@ function FloatingMenu({ view, editorState }) {
10390
10719
 
10391
10720
  // src/ui/plugin/inlineSuggestPlugin.ts
10392
10721
  import { Plugin as Plugin11, PluginKey as PluginKey9 } from "prosemirror-state";
10393
- import { Decoration as Decoration5, DecorationSet as DecorationSet5 } from "prosemirror-view";
10722
+ import { Decoration as Decoration6, DecorationSet as DecorationSet6 } from "prosemirror-view";
10394
10723
  var inlineSuggestKey = new PluginKey9("inlineSuggest");
10395
10724
  var DEBOUNCE_MS = 600;
10396
10725
  function createInlineSuggestPlugin(provider, endpoint, options) {
@@ -10414,13 +10743,13 @@ function createInlineSuggestPlugin(provider, endpoint, options) {
10414
10743
  props: {
10415
10744
  decorations(state) {
10416
10745
  const ps = inlineSuggestKey.getState(state);
10417
- if (!ps?.suggestion || !state.selection.empty) return DecorationSet5.empty;
10746
+ if (!ps?.suggestion || !state.selection.empty) return DecorationSet6.empty;
10418
10747
  const ghost = document.createElement("span");
10419
10748
  ghost.textContent = ps.suggestion;
10420
10749
  ghost.style.cssText = "color: rgba(0,0,0,0.3); pointer-events: none; user-select: none;";
10421
10750
  ghost.setAttribute("aria-hidden", "true");
10422
- return DecorationSet5.create(state.doc, [
10423
- Decoration5.widget(state.selection.from, ghost, {
10751
+ return DecorationSet6.create(state.doc, [
10752
+ Decoration6.widget(state.selection.from, ghost, {
10424
10753
  side: 1,
10425
10754
  key: "inline-suggest"
10426
10755
  })
@@ -10565,9 +10894,26 @@ function hasInvalidJinjaConditions(state) {
10565
10894
  });
10566
10895
  return found;
10567
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
+ }
10568
10913
  export {
10569
10914
  ActionbookRenderer,
10570
10915
  DocumentTreeView,
10916
+ EDITOR_TEXT_STYLES,
10571
10917
  EditorShell,
10572
10918
  FloatingMenu,
10573
10919
  INSERT_LINK_EVENT,
@@ -10599,6 +10945,7 @@ export {
10599
10945
  createTodoNodeViewPlugin,
10600
10946
  hasBrokenAnchorRefs,
10601
10947
  hasDuplicateJumpPoints,
10948
+ hasEmptyJinjaBranches,
10602
10949
  hasInvalidJinjaConditions,
10603
10950
  inlineSuggestKey,
10604
10951
  slashCommandKey,