@sendbird/actionbook-core 0.9.1 → 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ui/index.js CHANGED
@@ -14,7 +14,7 @@ var actionbookSchema = new Schema({
14
14
  }
15
15
  },
16
16
  heading: {
17
- content: "inline*",
17
+ content: "(text | jumpPoint | hardBreak)*",
18
18
  group: "block",
19
19
  attrs: { level: { default: 1 } },
20
20
  parseDOM: [
@@ -456,6 +456,205 @@ function createPluginArray(plugins) {
456
456
  // src/ui/components/ActionbookRenderer.tsx
457
457
  import React from "react";
458
458
 
459
+ // src/jinja/conditionHighlighter.ts
460
+ var KEYWORDS = {
461
+ and: "AND",
462
+ or: "OR",
463
+ not: "NOT",
464
+ in: "IN",
465
+ is: "IS",
466
+ True: "BOOL",
467
+ False: "BOOL",
468
+ true: "BOOL",
469
+ false: "BOOL",
470
+ None: "NONE",
471
+ null: "NONE"
472
+ };
473
+ var CATEGORY_BY_TYPE = {
474
+ STRING: "value",
475
+ NUMBER: "value",
476
+ BOOL: "value",
477
+ NONE: "value",
478
+ IDENT: "variable",
479
+ AND: "operator",
480
+ OR: "operator",
481
+ NOT: "operator",
482
+ IN: "operator",
483
+ IS: "operator",
484
+ EQ: "operator",
485
+ NEQ: "operator",
486
+ LT: "operator",
487
+ GT: "operator",
488
+ LTE: "operator",
489
+ GTE: "operator",
490
+ LPAREN: "punctuation",
491
+ RPAREN: "punctuation"
492
+ };
493
+ var NEGATIVE_NUMBER_PRECEDERS = /* @__PURE__ */ new Set([
494
+ "AND",
495
+ "OR",
496
+ "NOT",
497
+ "IN",
498
+ "IS",
499
+ "EQ",
500
+ "NEQ",
501
+ "LT",
502
+ "GT",
503
+ "LTE",
504
+ "GTE",
505
+ "LPAREN"
506
+ ]);
507
+ function canStartNegativeNumber(tokens) {
508
+ if (tokens.length === 0) {
509
+ return true;
510
+ }
511
+ const previous = tokens[tokens.length - 1];
512
+ return NEGATIVE_NUMBER_PRECEDERS.has(previous.type);
513
+ }
514
+ function finalizeTokens(input, rawTokens) {
515
+ const highlighted = [];
516
+ for (let i = 0; i < rawTokens.length; i++) {
517
+ const token = rawTokens[i];
518
+ const next = rawTokens[i + 1];
519
+ if (token.type === "IS" && next?.type === "NOT") {
520
+ highlighted.push({
521
+ text: input.slice(token.start, next.end),
522
+ start: token.start,
523
+ end: next.end,
524
+ category: "operator"
525
+ });
526
+ i++;
527
+ continue;
528
+ }
529
+ highlighted.push({
530
+ text: token.text,
531
+ start: token.start,
532
+ end: token.end,
533
+ category: CATEGORY_BY_TYPE[token.type]
534
+ });
535
+ }
536
+ return highlighted;
537
+ }
538
+ function tokenizeCondition(input) {
539
+ const rawTokens = [];
540
+ let i = 0;
541
+ while (i < input.length) {
542
+ const char = input[i];
543
+ if (/\s/.test(char)) {
544
+ i++;
545
+ continue;
546
+ }
547
+ if (char === '"' || char === "'") {
548
+ const start = i;
549
+ const quote = char;
550
+ i++;
551
+ while (i < input.length) {
552
+ if (input[i] === "\\" && i + 1 < input.length) {
553
+ i += 2;
554
+ continue;
555
+ }
556
+ if (input[i] === quote) {
557
+ i++;
558
+ rawTokens.push({
559
+ type: "STRING",
560
+ text: input.slice(start, i),
561
+ start,
562
+ end: i
563
+ });
564
+ break;
565
+ }
566
+ i++;
567
+ }
568
+ if (rawTokens[rawTokens.length - 1]?.start !== start) {
569
+ return finalizeTokens(input, rawTokens);
570
+ }
571
+ continue;
572
+ }
573
+ if (/[0-9]/.test(char) || char === "-" && i + 1 < input.length && /[0-9]/.test(input[i + 1]) && canStartNegativeNumber(rawTokens)) {
574
+ const start = i;
575
+ if (input[i] === "-") {
576
+ i++;
577
+ }
578
+ while (i < input.length && /[0-9]/.test(input[i])) {
579
+ i++;
580
+ }
581
+ if (i < input.length && input[i] === ".") {
582
+ i++;
583
+ while (i < input.length && /[0-9]/.test(input[i])) {
584
+ i++;
585
+ }
586
+ }
587
+ rawTokens.push({
588
+ type: "NUMBER",
589
+ text: input.slice(start, i),
590
+ start,
591
+ end: i
592
+ });
593
+ continue;
594
+ }
595
+ if (/[a-zA-Z_]/.test(char)) {
596
+ const start = i;
597
+ i++;
598
+ while (i < input.length && /[a-zA-Z0-9_.]/.test(input[i])) {
599
+ i++;
600
+ }
601
+ const text2 = input.slice(start, i);
602
+ rawTokens.push({
603
+ type: KEYWORDS[text2] ?? "IDENT",
604
+ text: text2,
605
+ start,
606
+ end: i
607
+ });
608
+ continue;
609
+ }
610
+ if (i + 1 < input.length) {
611
+ const twoChar = input.slice(i, i + 2);
612
+ if (twoChar === "==") {
613
+ rawTokens.push({ type: "EQ", text: twoChar, start: i, end: i + 2 });
614
+ i += 2;
615
+ continue;
616
+ }
617
+ if (twoChar === "!=") {
618
+ rawTokens.push({ type: "NEQ", text: twoChar, start: i, end: i + 2 });
619
+ i += 2;
620
+ continue;
621
+ }
622
+ if (twoChar === "<=") {
623
+ rawTokens.push({ type: "LTE", text: twoChar, start: i, end: i + 2 });
624
+ i += 2;
625
+ continue;
626
+ }
627
+ if (twoChar === ">=") {
628
+ rawTokens.push({ type: "GTE", text: twoChar, start: i, end: i + 2 });
629
+ i += 2;
630
+ continue;
631
+ }
632
+ }
633
+ if (char === "<") {
634
+ rawTokens.push({ type: "LT", text: char, start: i, end: i + 1 });
635
+ i++;
636
+ continue;
637
+ }
638
+ if (char === ">") {
639
+ rawTokens.push({ type: "GT", text: char, start: i, end: i + 1 });
640
+ i++;
641
+ continue;
642
+ }
643
+ if (char === "(") {
644
+ rawTokens.push({ type: "LPAREN", text: char, start: i, end: i + 1 });
645
+ i++;
646
+ continue;
647
+ }
648
+ if (char === ")") {
649
+ rawTokens.push({ type: "RPAREN", text: char, start: i, end: i + 1 });
650
+ i++;
651
+ continue;
652
+ }
653
+ return finalizeTokens(input, rawTokens);
654
+ }
655
+ return finalizeTokens(input, rawTokens);
656
+ }
657
+
459
658
  // src/ui/components/icons.tsx
460
659
  import { jsx, jsxs } from "react/jsx-runtime";
461
660
  function IconTool({ size = 16, fill = "currentColor", style }) {
@@ -743,6 +942,172 @@ function renderInline(node, key2, renderNode) {
743
942
  function renderInlineContent(content, keyPrefix, renderNode) {
744
943
  return content.map((node, i) => renderInline(node, `${keyPrefix}-${i}`, renderNode));
745
944
  }
945
+ var JINJA_RENDERER_STYLE_ID = "ab-renderer-jinja-styles";
946
+ var JINJA_RENDERER_STYLES = `
947
+ .ab-r-jinja-block {
948
+ margin: 8px 0;
949
+ }
950
+
951
+ .ab-r-jinja-branch {
952
+ position: relative;
953
+ }
954
+
955
+ .ab-r-jinja-header {
956
+ display: flex;
957
+ align-items: center;
958
+ gap: 12px;
959
+ padding: 8px;
960
+ border: 1px solid #E0E0E0;
961
+ border-radius: 4px;
962
+ background: #FFFFFF;
963
+ }
964
+
965
+ .ab-r-jinja-badge {
966
+ display: inline-flex;
967
+ align-items: center;
968
+ justify-content: center;
969
+ height: 32px;
970
+ padding: 0 8px;
971
+ border-radius: 4px;
972
+ font-family: "Roboto Mono", monospace;
973
+ font-size: 13px;
974
+ font-weight: 700;
975
+ line-height: 20px;
976
+ letter-spacing: -0.3px;
977
+ white-space: nowrap;
978
+ flex-shrink: 0;
979
+ }
980
+
981
+ .ab-r-jinja-badge-if,
982
+ .ab-r-jinja-badge-elif {
983
+ background: #E7F1FF;
984
+ color: #0D0D0D;
985
+ }
986
+
987
+ .ab-r-jinja-badge-else {
988
+ background: #F7F7F7;
989
+ color: #424242;
990
+ }
991
+
992
+ .ab-r-jinja-condition {
993
+ flex: 1;
994
+ min-width: 0;
995
+ display: flex;
996
+ flex-wrap: wrap;
997
+ gap: 8px;
998
+ align-items: center;
999
+ font-family: "Roboto Mono", monospace;
1000
+ font-size: 13px;
1001
+ line-height: 20px;
1002
+ letter-spacing: -0.3px;
1003
+ color: #0D0D0D;
1004
+ }
1005
+
1006
+ .ab-r-jinja-otherwise {
1007
+ flex: 1;
1008
+ color: #858585;
1009
+ font-family: "Roboto Mono", monospace;
1010
+ font-size: 13px;
1011
+ line-height: 20px;
1012
+ letter-spacing: -0.3px;
1013
+ }
1014
+
1015
+ .ab-r-jinja-body {
1016
+ display: flex;
1017
+ gap: 12px;
1018
+ padding: 0 8px;
1019
+ min-height: 32px;
1020
+ }
1021
+
1022
+ .ab-r-jinja-divider-col {
1023
+ width: 32px;
1024
+ flex-shrink: 0;
1025
+ display: flex;
1026
+ align-items: stretch;
1027
+ justify-content: center;
1028
+ }
1029
+
1030
+ .ab-r-jinja-divider {
1031
+ width: 1px;
1032
+ background: #E0E0E0;
1033
+ }
1034
+
1035
+ .ab-r-jinja-branch-last .ab-r-jinja-divider-col {
1036
+ align-items: flex-start;
1037
+ padding-left: 16px;
1038
+ }
1039
+
1040
+ .ab-r-jinja-branch-last .ab-r-jinja-divider {
1041
+ width: 16px;
1042
+ height: 28px;
1043
+ background: none;
1044
+ border-left: 1px solid #E0E0E0;
1045
+ border-bottom: 1px solid #E0E0E0;
1046
+ border-bottom-left-radius: 8px;
1047
+ }
1048
+
1049
+ .ab-r-jinja-content {
1050
+ flex: 1;
1051
+ min-width: 0;
1052
+ padding: 12px 0;
1053
+ }
1054
+
1055
+ .ab-r-jinja-token-variable { color: #4141B2; }
1056
+ .ab-r-jinja-token-operator { color: #858585; }
1057
+ .ab-r-jinja-token-value { color: #0D0D0D; }
1058
+ .ab-r-jinja-token-punctuation { color: #858585; }
1059
+ `;
1060
+ var jinjaRendererStyleRefCount = 0;
1061
+ var jinjaRendererStyleEl = null;
1062
+ function ensureJinjaRendererStyles() {
1063
+ if (jinjaRendererStyleRefCount++ > 0) return;
1064
+ if (typeof document === "undefined") return;
1065
+ const existing = document.getElementById(JINJA_RENDERER_STYLE_ID);
1066
+ if (existing instanceof HTMLStyleElement) {
1067
+ jinjaRendererStyleEl = existing;
1068
+ return;
1069
+ }
1070
+ jinjaRendererStyleEl = document.createElement("style");
1071
+ jinjaRendererStyleEl.id = JINJA_RENDERER_STYLE_ID;
1072
+ jinjaRendererStyleEl.textContent = JINJA_RENDERER_STYLES;
1073
+ document.head.appendChild(jinjaRendererStyleEl);
1074
+ }
1075
+ function renderConditionTokens(condition) {
1076
+ const tokens = tokenizeCondition(condition);
1077
+ return tokens.map((token, i) => /* @__PURE__ */ jsx2("span", { className: `ab-r-jinja-token-${token.category}`, children: token.text }, `t-${i}-${token.start}`));
1078
+ }
1079
+ function renderJinjaBranch(branch, key2, isLast, renderNode) {
1080
+ const branchType = branch.branchType;
1081
+ const badgeLabel = branchType === "elif" ? "ELSE IF" : branchType.toUpperCase();
1082
+ return /* @__PURE__ */ jsxs2(
1083
+ "div",
1084
+ {
1085
+ className: `ab-r-jinja-branch${isLast ? " ab-r-jinja-branch-last" : ""}`,
1086
+ children: [
1087
+ /* @__PURE__ */ jsxs2("div", { className: "ab-r-jinja-header", children: [
1088
+ /* @__PURE__ */ jsx2("span", { className: `ab-r-jinja-badge ab-r-jinja-badge-${branchType}`, children: badgeLabel }),
1089
+ branchType === "else" ? /* @__PURE__ */ jsx2("span", { className: "ab-r-jinja-otherwise", children: "otherwise" }) : /* @__PURE__ */ jsx2("span", { className: "ab-r-jinja-condition", children: branch.condition ? renderConditionTokens(branch.condition) : null })
1090
+ ] }),
1091
+ /* @__PURE__ */ jsxs2("div", { className: "ab-r-jinja-body", children: [
1092
+ /* @__PURE__ */ jsx2("div", { className: "ab-r-jinja-divider-col", children: /* @__PURE__ */ jsx2("div", { className: "ab-r-jinja-divider" }) }),
1093
+ /* @__PURE__ */ jsx2("div", { className: "ab-r-jinja-content", children: branch.content.map((child, i) => renderBlock(child, `${key2}-c${i}`, renderNode)) })
1094
+ ] })
1095
+ ]
1096
+ },
1097
+ key2
1098
+ );
1099
+ }
1100
+ function renderJinjaIfBlock(node, key2, renderNode) {
1101
+ ensureJinjaRendererStyles();
1102
+ return /* @__PURE__ */ jsx2("div", { className: "ab-r-jinja-block", children: node.branches.map(
1103
+ (branch, i) => renderJinjaBranch(
1104
+ branch,
1105
+ `${key2}-b${i}`,
1106
+ i === node.branches.length - 1,
1107
+ renderNode
1108
+ )
1109
+ ) }, key2);
1110
+ }
746
1111
  function renderBlock(node, key2, renderNode) {
747
1112
  const defaultRender = () => {
748
1113
  switch (node.type) {
@@ -774,6 +1139,8 @@ function renderBlock(node, key2, renderNode) {
774
1139
  return /* @__PURE__ */ jsx2("li", { children: node.content.map((child, i) => renderBlock(child, `${key2}-${i}`, renderNode)) }, key2);
775
1140
  case "blockquote":
776
1141
  return /* @__PURE__ */ jsx2("blockquote", { children: node.content.map((child, i) => renderBlock(child, `${key2}-${i}`, renderNode)) }, key2);
1142
+ case "jinjaIfBlock":
1143
+ return renderJinjaIfBlock(node, key2, renderNode);
777
1144
  case "horizontalRule":
778
1145
  return /* @__PURE__ */ jsx2("hr", {}, key2);
779
1146
  case "table":
@@ -2367,6 +2734,39 @@ var ITALIC_STAR_RE = /(?:^|[^*])\*([^*]+)\*$/;
2367
2734
  var ITALIC_UNDER_RE = /(?:^|[^_])_([^_]+)_$/;
2368
2735
  var STRIKE_RE = /(?:^|[^~])~~([^~]+)~~$/;
2369
2736
  var CODE_RE = /(?:^|[^`])`([^`]+)`$/;
2737
+ function findParentList(state, pos) {
2738
+ const $pos = state.doc.resolve(pos);
2739
+ const { bulletList: blType, orderedList: olType } = actionbookSchema.nodes;
2740
+ for (let d = $pos.depth; d > 0; d--) {
2741
+ const node = $pos.node(d);
2742
+ if (node.type === blType || node.type === olType) {
2743
+ return { depth: d, node };
2744
+ }
2745
+ }
2746
+ return null;
2747
+ }
2748
+ function handleListInputRule(state, start, end, listType, attrs) {
2749
+ const resolvePos = Math.max(start, end - 1);
2750
+ const parentList = findParentList(state, resolvePos);
2751
+ if (!parentList) return null;
2752
+ const $from = state.doc.resolve(resolvePos);
2753
+ const { depth, node: listNode } = parentList;
2754
+ const paraContentSize = $from.parent.content.size;
2755
+ const matchedLen = end - start;
2756
+ if (paraContentSize !== matchedLen) return null;
2757
+ const listStart = $from.before(depth);
2758
+ const listEnd = $from.after(depth);
2759
+ if (listNode.type === listType) {
2760
+ const tr = state.tr.replaceWith(listStart, listEnd, actionbookSchema.nodes.paragraph.create());
2761
+ tr.setSelection(TextSelection.near(tr.doc.resolve(listStart)));
2762
+ return tr;
2763
+ } else {
2764
+ const tr = state.tr.delete(start, end);
2765
+ const mappedListPos = tr.mapping.map(listStart);
2766
+ tr.setNodeMarkup(mappedListPos, listType, attrs ?? null);
2767
+ return tr;
2768
+ }
2769
+ }
2370
2770
  function markInputRule(pattern, markType, markerLen) {
2371
2771
  return new InputRule(pattern, (state, match, start, end) => {
2372
2772
  const textContent2 = match[1];
@@ -2431,12 +2831,30 @@ function createInputRulesPlugin() {
2431
2831
  return state.tr.delete(start, end).insert(start, list);
2432
2832
  })
2433
2833
  );
2434
- rules.push(wrappingInputRule(BULLET_LIST_RE, actionbookSchema.nodes.bulletList));
2435
- rules.push(
2436
- wrappingInputRule(ORDERED_LIST_RE, actionbookSchema.nodes.orderedList, (match) => ({
2437
- start: +match[1]
2438
- }))
2439
- );
2834
+ {
2835
+ const blType = actionbookSchema.nodes.bulletList;
2836
+ const fallback = wrappingInputRule(BULLET_LIST_RE, blType);
2837
+ rules.push(
2838
+ new InputRule(BULLET_LIST_RE, (state, match, start, end) => {
2839
+ const toggled = handleListInputRule(state, start, end, blType);
2840
+ if (toggled) return toggled;
2841
+ const handler = fallback.handler;
2842
+ return handler(state, match, start, end);
2843
+ })
2844
+ );
2845
+ }
2846
+ {
2847
+ const olType = actionbookSchema.nodes.orderedList;
2848
+ const fallback = wrappingInputRule(ORDERED_LIST_RE, olType, (m) => ({ start: +m[1] }));
2849
+ rules.push(
2850
+ new InputRule(ORDERED_LIST_RE, (state, match, start, end) => {
2851
+ const toggled = handleListInputRule(state, start, end, olType, { start: +match[1] });
2852
+ if (toggled) return toggled;
2853
+ const handler = fallback.handler;
2854
+ return handler(state, match, start, end);
2855
+ })
2856
+ );
2857
+ }
2440
2858
  const jumpPointType = actionbookSchema.nodes.jumpPoint;
2441
2859
  rules.push(
2442
2860
  new InputRule(JUMP_POINT_RE2, (state, match, start, end) => {
@@ -2449,7 +2867,12 @@ function createInputRulesPlugin() {
2449
2867
  }
2450
2868
  });
2451
2869
  if (exists) return null;
2452
- return state.tr.delete(start, end).insert(start, jumpPointType.create({ id }));
2870
+ const tr = state.tr.delete(start, end).insert(start, jumpPointType.create({ id }));
2871
+ const afterAtom = start + 1;
2872
+ tr.insertText(" ", afterAtom);
2873
+ const cursorPos = afterAtom + 1;
2874
+ tr.setSelection(TextSelection.near(tr.doc.resolve(Math.min(cursorPos, tr.doc.content.size))));
2875
+ return tr;
2453
2876
  })
2454
2877
  );
2455
2878
  rules.push(markInputRule(BOLD_STAR_RE, actionbookSchema.marks.bold, 2));
@@ -2468,18 +2891,110 @@ import { history, undo, redo } from "prosemirror-history";
2468
2891
  import { keymap as keymap2 } from "prosemirror-keymap";
2469
2892
  function createHistoryPlugin() {
2470
2893
  return {
2471
- name: "history",
2472
- plugins: () => [
2473
- history(),
2474
- keymap2({ "Mod-z": undo, "Mod-Shift-z": redo, "Mod-y": redo })
2475
- ]
2894
+ name: "history",
2895
+ plugins: () => [
2896
+ history(),
2897
+ keymap2({ "Mod-z": undo, "Mod-Shift-z": redo, "Mod-y": redo })
2898
+ ]
2899
+ };
2900
+ }
2901
+
2902
+ // src/ui/plugin/keymapPlugin.ts
2903
+ import { baseKeymap, chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock, toggleMark, setBlockType, joinBackward } from "prosemirror-commands";
2904
+ import { TextSelection as TextSelection2 } from "prosemirror-state";
2905
+ import { keymap as keymap3 } from "prosemirror-keymap";
2906
+ import { liftListItem, sinkListItem, splitListItem, wrapInList } from "prosemirror-schema-list";
2907
+
2908
+ // src/ui/plugin/noteBlockPlugin.tsx
2909
+ import { Selection } from "prosemirror-state";
2910
+ var NoteBlockView = class {
2911
+ dom;
2912
+ contentDOM;
2913
+ constructor(_node, _view, _getPos) {
2914
+ this.dom = document.createElement("div");
2915
+ this.dom.setAttribute("data-note-block", "");
2916
+ this.dom.className = "ab-note-block";
2917
+ this.dom.style.cssText = [
2918
+ "position: relative",
2919
+ "margin: 8px 0",
2920
+ "padding: 16px 24px",
2921
+ "background: #fff",
2922
+ "border: 1px dashed #ccc",
2923
+ "border-radius: 4px",
2924
+ "display: flex",
2925
+ "flex-direction: column",
2926
+ "gap: 4px"
2927
+ ].join(";");
2928
+ const label = document.createElement("span");
2929
+ label.contentEditable = "false";
2930
+ label.textContent = "Note";
2931
+ label.style.cssText = [
2932
+ "font-family: SF Pro Text, -apple-system, BlinkMacSystemFont, sans-serif",
2933
+ "display: inline-flex",
2934
+ "align-items: center",
2935
+ "justify-content: center",
2936
+ "align-self: flex-start",
2937
+ "font-size: 12px",
2938
+ "font-weight: 400",
2939
+ "font-style: normal",
2940
+ "line-height: 16px",
2941
+ "color: #858585",
2942
+ "background: #f7f7f7",
2943
+ "border: 1px solid #e0e0e0",
2944
+ "border-radius: 4px",
2945
+ "padding: 2px 4px",
2946
+ "user-select: none",
2947
+ "height: 20px"
2948
+ ].join(";");
2949
+ this.dom.appendChild(label);
2950
+ this.contentDOM = document.createElement("div");
2951
+ this.contentDOM.className = "ab-note-block-content";
2952
+ this.contentDOM.style.cssText = [
2953
+ "font-size: 14px",
2954
+ "font-style: italic",
2955
+ "font-weight: 400",
2956
+ "line-height: 20px",
2957
+ "letter-spacing: -0.1px",
2958
+ "color: #858585"
2959
+ ].join(";");
2960
+ this.dom.appendChild(this.contentDOM);
2961
+ }
2962
+ };
2963
+ var exitNoteBlockOnEnter = (state, dispatch) => {
2964
+ const { $from } = state.selection;
2965
+ let noteBlockDepth = -1;
2966
+ for (let d = $from.depth; d > 0; d--) {
2967
+ if ($from.node(d).type.name === "noteBlock") {
2968
+ noteBlockDepth = d;
2969
+ break;
2970
+ }
2971
+ }
2972
+ if (noteBlockDepth < 0) return false;
2973
+ const parentNode = $from.parent;
2974
+ if (parentNode.type.name !== "paragraph" || parentNode.content.size !== 0) {
2975
+ return false;
2976
+ }
2977
+ if (!dispatch) return true;
2978
+ const paragraphStart = $from.before($from.depth);
2979
+ const paragraphEnd = $from.after($from.depth);
2980
+ const tr = state.tr.delete(paragraphStart, paragraphEnd);
2981
+ const noteBlockEnd = tr.mapping.map($from.after(noteBlockDepth));
2982
+ const newParagraph = state.schema.nodes.paragraph.create();
2983
+ tr.insert(noteBlockEnd, newParagraph);
2984
+ tr.setSelection(Selection.near(tr.doc.resolve(noteBlockEnd + 1)));
2985
+ dispatch(tr.scrollIntoView());
2986
+ return true;
2987
+ };
2988
+ function createNoteBlockPlugin() {
2989
+ return {
2990
+ name: "noteBlockNodeView",
2991
+ nodeViews: () => ({
2992
+ noteBlock: (node, view, getPos) => new NoteBlockView(node, view, getPos)
2993
+ })
2476
2994
  };
2477
2995
  }
2478
2996
 
2479
2997
  // src/ui/plugin/keymapPlugin.ts
2480
- import { baseKeymap, chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock, toggleMark, setBlockType, joinBackward } from "prosemirror-commands";
2481
- import { keymap as keymap3 } from "prosemirror-keymap";
2482
- import { liftListItem, sinkListItem, splitListItem, wrapInList } from "prosemirror-schema-list";
2483
2998
  var TAB_CHAR = " ";
2484
2999
  var { listItem, bulletList, orderedList, hardBreak, heading, paragraph, horizontalRule, blockquote } = actionbookSchema.nodes;
2485
3000
  var { bold: boldMark, italic: italicMark, underline: underlineMark, strikethrough: strikethroughMark, code: codeMark } = actionbookSchema.marks;
@@ -2526,7 +3041,38 @@ var backspaceAfterLeafBlock = (state, dispatch) => {
2526
3041
  }
2527
3042
  return true;
2528
3043
  };
3044
+ var backspaceDeleteEmptyBlock = (state, dispatch) => {
3045
+ const { $from } = state.selection;
3046
+ if (!state.selection.empty || $from.parentOffset !== 0) return false;
3047
+ if ($from.parent.type !== paragraph || $from.parent.content.size !== 0) return false;
3048
+ const deletableTypes = /* @__PURE__ */ new Set(["jinjaIfBlock", "jinjaIfBranch", "noteBlock", "blockquote"]);
3049
+ for (let d = $from.depth - 1; d > 0; d--) {
3050
+ const ancestor = $from.node(d);
3051
+ if (!deletableTypes.has(ancestor.type.name)) continue;
3052
+ if (ancestor.type.name === "jinjaIfBranch" && ancestor.childCount === 1 && ancestor.child(0).content.size === 0) {
3053
+ for (let dd = d - 1; dd > 0; dd--) {
3054
+ if ($from.node(dd).type.name === "jinjaIfBlock") {
3055
+ d = dd;
3056
+ break;
3057
+ }
3058
+ }
3059
+ }
3060
+ const blockNode = $from.node(d);
3061
+ const blockText = blockNode.textContent.trim();
3062
+ if (blockText.length > 0) return false;
3063
+ if (dispatch) {
3064
+ const blockStart = $from.before(d);
3065
+ const blockEnd = $from.after(d);
3066
+ const tr = state.tr.replaceWith(blockStart, blockEnd, paragraph.create());
3067
+ tr.setSelection(TextSelection2.near(tr.doc.resolve(blockStart)));
3068
+ dispatch(tr.scrollIntoView());
3069
+ }
3070
+ return true;
3071
+ }
3072
+ return false;
3073
+ };
2529
3074
  var backspaceCommand = chainCommands(
3075
+ backspaceDeleteEmptyBlock,
2530
3076
  backspaceAfterList,
2531
3077
  backspaceAfterLeafBlock,
2532
3078
  (state, dispatch) => {
@@ -2544,8 +3090,9 @@ var backspaceCommand = chainCommands(
2544
3090
  );
2545
3091
  var tabCommand = (state, dispatch) => {
2546
3092
  const { $from } = state.selection;
2547
- if (cursorDirectlyInListItem(state) && $from.parentOffset === 0) {
3093
+ if (cursorDirectlyInListItem(state)) {
2548
3094
  if (sinkListItem(listItem)(state, dispatch)) return true;
3095
+ return true;
2549
3096
  }
2550
3097
  if (dispatch) dispatch(state.tr.insertText(TAB_CHAR));
2551
3098
  return true;
@@ -2562,7 +3109,68 @@ var shiftTabCommand = (state, dispatch) => {
2562
3109
  }
2563
3110
  return true;
2564
3111
  };
3112
+ var blockExitEnterCount = 0;
3113
+ var blockExitLastTime = 0;
3114
+ var BLOCK_EXIT_TYPES = /* @__PURE__ */ new Set(["blockquote", "jinjaIfBranch"]);
3115
+ var exitBlockOnDoubleEnter = (state, dispatch) => {
3116
+ const { $from } = state.selection;
3117
+ let blockDepth = -1;
3118
+ for (let d = $from.depth; d > 0; d--) {
3119
+ if (BLOCK_EXIT_TYPES.has($from.node(d).type.name)) {
3120
+ blockDepth = d;
3121
+ break;
3122
+ }
3123
+ }
3124
+ if (blockDepth < 0) {
3125
+ blockExitEnterCount = 0;
3126
+ return false;
3127
+ }
3128
+ if ($from.parent.type.name !== "paragraph" || $from.parent.content.size !== 0) {
3129
+ blockExitEnterCount = 0;
3130
+ return false;
3131
+ }
3132
+ const now = Date.now();
3133
+ if (now - blockExitLastTime > 2e3) blockExitEnterCount = 0;
3134
+ blockExitLastTime = now;
3135
+ blockExitEnterCount++;
3136
+ if (blockExitEnterCount < 2) return false;
3137
+ blockExitEnterCount = 0;
3138
+ if (!dispatch) return true;
3139
+ const paraStart = $from.before($from.depth);
3140
+ const paraEnd = $from.after($from.depth);
3141
+ const tr = state.tr.delete(paraStart, paraEnd);
3142
+ const mapped = tr.mapping.map($from.before($from.depth));
3143
+ const $m = tr.doc.resolve(Math.min(mapped, tr.doc.content.size));
3144
+ for (let d = $m.depth; d > 0; d--) {
3145
+ if (BLOCK_EXIT_TYPES.has($m.node(d).type.name)) {
3146
+ const last = $m.node(d).lastChild;
3147
+ if (last?.type.name === "paragraph" && last.content.size === 0) {
3148
+ const lastPos = $m.end(d) - last.nodeSize;
3149
+ tr.delete(lastPos, lastPos + last.nodeSize);
3150
+ }
3151
+ break;
3152
+ }
3153
+ }
3154
+ let exitDepth = blockDepth;
3155
+ const blockTypeName = $from.node(blockDepth).type.name;
3156
+ if (blockTypeName === "jinjaIfBranch") {
3157
+ for (let d = blockDepth - 1; d > 0; d--) {
3158
+ if ($from.node(d).type.name === "jinjaIfBlock") {
3159
+ exitDepth = d;
3160
+ break;
3161
+ }
3162
+ }
3163
+ }
3164
+ const blockEnd = tr.mapping.map($from.after(exitDepth));
3165
+ const newPara = paragraph.create();
3166
+ tr.insert(blockEnd, newPara);
3167
+ tr.setSelection(TextSelection2.near(tr.doc.resolve(blockEnd + 1)));
3168
+ dispatch(tr.scrollIntoView());
3169
+ return true;
3170
+ };
2565
3171
  var enterCommand = chainCommands(
3172
+ exitNoteBlockOnEnter,
3173
+ exitBlockOnDoubleEnter,
2566
3174
  newlineInCode,
2567
3175
  // Split list item on Enter; lift empty list item out of list
2568
3176
  splitListItem(listItem),
@@ -4336,207 +4944,19 @@ function analyzeJinjaBlocks(doc2) {
4336
4944
  });
4337
4945
  }
4338
4946
 
4339
- // src/jinja/conditionHighlighter.ts
4340
- var KEYWORDS = {
4341
- and: "AND",
4342
- or: "OR",
4343
- not: "NOT",
4344
- in: "IN",
4345
- is: "IS",
4346
- True: "BOOL",
4347
- False: "BOOL",
4348
- true: "BOOL",
4349
- false: "BOOL",
4350
- None: "NONE",
4351
- null: "NONE"
4352
- };
4353
- var CATEGORY_BY_TYPE = {
4354
- STRING: "value",
4355
- NUMBER: "value",
4356
- BOOL: "value",
4357
- NONE: "value",
4358
- IDENT: "variable",
4359
- AND: "operator",
4360
- OR: "operator",
4361
- NOT: "operator",
4362
- IN: "operator",
4363
- IS: "operator",
4364
- EQ: "operator",
4365
- NEQ: "operator",
4366
- LT: "operator",
4367
- GT: "operator",
4368
- LTE: "operator",
4369
- GTE: "operator",
4370
- LPAREN: "punctuation",
4371
- RPAREN: "punctuation"
4372
- };
4373
- var NEGATIVE_NUMBER_PRECEDERS = /* @__PURE__ */ new Set([
4374
- "AND",
4375
- "OR",
4376
- "NOT",
4377
- "IN",
4378
- "IS",
4379
- "EQ",
4380
- "NEQ",
4381
- "LT",
4382
- "GT",
4383
- "LTE",
4384
- "GTE",
4385
- "LPAREN"
4386
- ]);
4387
- function canStartNegativeNumber(tokens) {
4388
- if (tokens.length === 0) {
4389
- return true;
4390
- }
4391
- const previous = tokens[tokens.length - 1];
4392
- return NEGATIVE_NUMBER_PRECEDERS.has(previous.type);
4393
- }
4394
- function finalizeTokens(input, rawTokens) {
4395
- const highlighted = [];
4396
- for (let i = 0; i < rawTokens.length; i++) {
4397
- const token = rawTokens[i];
4398
- const next = rawTokens[i + 1];
4399
- if (token.type === "IS" && next?.type === "NOT") {
4400
- highlighted.push({
4401
- text: input.slice(token.start, next.end),
4402
- start: token.start,
4403
- end: next.end,
4404
- category: "operator"
4405
- });
4406
- i++;
4407
- continue;
4408
- }
4409
- highlighted.push({
4410
- text: token.text,
4411
- start: token.start,
4412
- end: token.end,
4413
- category: CATEGORY_BY_TYPE[token.type]
4414
- });
4415
- }
4416
- return highlighted;
4417
- }
4418
- function tokenizeCondition(input) {
4419
- const rawTokens = [];
4420
- let i = 0;
4421
- while (i < input.length) {
4422
- const char = input[i];
4423
- if (/\s/.test(char)) {
4424
- i++;
4425
- continue;
4426
- }
4427
- if (char === '"' || char === "'") {
4428
- const start = i;
4429
- const quote = char;
4430
- i++;
4431
- while (i < input.length) {
4432
- if (input[i] === "\\" && i + 1 < input.length) {
4433
- i += 2;
4434
- continue;
4435
- }
4436
- if (input[i] === quote) {
4437
- i++;
4438
- rawTokens.push({
4439
- type: "STRING",
4440
- text: input.slice(start, i),
4441
- start,
4442
- end: i
4443
- });
4444
- break;
4445
- }
4446
- i++;
4447
- }
4448
- if (rawTokens[rawTokens.length - 1]?.start !== start) {
4449
- return finalizeTokens(input, rawTokens);
4450
- }
4451
- continue;
4452
- }
4453
- if (/[0-9]/.test(char) || char === "-" && i + 1 < input.length && /[0-9]/.test(input[i + 1]) && canStartNegativeNumber(rawTokens)) {
4454
- const start = i;
4455
- if (input[i] === "-") {
4456
- i++;
4457
- }
4458
- while (i < input.length && /[0-9]/.test(input[i])) {
4459
- i++;
4460
- }
4461
- if (i < input.length && input[i] === ".") {
4462
- i++;
4463
- while (i < input.length && /[0-9]/.test(input[i])) {
4464
- i++;
4465
- }
4466
- }
4467
- rawTokens.push({
4468
- type: "NUMBER",
4469
- text: input.slice(start, i),
4470
- start,
4471
- end: i
4472
- });
4473
- continue;
4474
- }
4475
- if (/[a-zA-Z_]/.test(char)) {
4476
- const start = i;
4477
- i++;
4478
- while (i < input.length && /[a-zA-Z0-9_.]/.test(input[i])) {
4479
- i++;
4480
- }
4481
- const text2 = input.slice(start, i);
4482
- rawTokens.push({
4483
- type: KEYWORDS[text2] ?? "IDENT",
4484
- text: text2,
4485
- start,
4486
- end: i
4487
- });
4488
- continue;
4489
- }
4490
- if (i + 1 < input.length) {
4491
- const twoChar = input.slice(i, i + 2);
4492
- if (twoChar === "==") {
4493
- rawTokens.push({ type: "EQ", text: twoChar, start: i, end: i + 2 });
4494
- i += 2;
4495
- continue;
4496
- }
4497
- if (twoChar === "!=") {
4498
- rawTokens.push({ type: "NEQ", text: twoChar, start: i, end: i + 2 });
4499
- i += 2;
4500
- continue;
4501
- }
4502
- if (twoChar === "<=") {
4503
- rawTokens.push({ type: "LTE", text: twoChar, start: i, end: i + 2 });
4504
- i += 2;
4505
- continue;
4506
- }
4507
- if (twoChar === ">=") {
4508
- rawTokens.push({ type: "GTE", text: twoChar, start: i, end: i + 2 });
4509
- i += 2;
4510
- continue;
4511
- }
4512
- }
4513
- if (char === "<") {
4514
- rawTokens.push({ type: "LT", text: char, start: i, end: i + 1 });
4515
- i++;
4516
- continue;
4517
- }
4518
- if (char === ">") {
4519
- rawTokens.push({ type: "GT", text: char, start: i, end: i + 1 });
4520
- i++;
4521
- continue;
4522
- }
4523
- if (char === "(") {
4524
- rawTokens.push({ type: "LPAREN", text: char, start: i, end: i + 1 });
4525
- i++;
4526
- continue;
4527
- }
4528
- if (char === ")") {
4529
- rawTokens.push({ type: "RPAREN", text: char, start: i, end: i + 1 });
4530
- i++;
4531
- continue;
4532
- }
4533
- return finalizeTokens(input, rawTokens);
4534
- }
4535
- return finalizeTokens(input, rawTokens);
4536
- }
4537
-
4538
4947
  // src/tree/documentTree.ts
4539
4948
  var MAX_DEPTH6 = 128;
4949
+ var END_ACTION_RESOURCE_IDS = /* @__PURE__ */ new Set([
4950
+ "close-happy-tiger"
4951
+ // PredefinedToolKey.CloseConversation
4952
+ ]);
4953
+ var END_ACTION_TEXTS = /* @__PURE__ */ new Set([
4954
+ "end conversation",
4955
+ "end_conversation"
4956
+ ]);
4957
+ function isEndActionTag(tagType, resourceId, text2) {
4958
+ return tagType === "handoff" || END_ACTION_RESOURCE_IDS.has(resourceId) || END_ACTION_TEXTS.has(text2.toLowerCase());
4959
+ }
4540
4960
  function extractInlineItems(content, blockIndex) {
4541
4961
  const items = [];
4542
4962
  for (const node of content) {
@@ -4548,10 +4968,10 @@ function extractInlineItems(content, blockIndex) {
4548
4968
  children: []
4549
4969
  });
4550
4970
  } else if (node.type === "resourceTag") {
4551
- const isEndAction = node.tagType === "handoff";
4971
+ const endAction = isEndActionTag(node.tagType, node.resourceId, node.text);
4552
4972
  items.push({
4553
- type: isEndAction ? "endAction" : "resourceTag",
4554
- label: isEndAction ? `End ${node.text}` : node.text,
4973
+ type: endAction ? "endAction" : "resourceTag",
4974
+ label: endAction ? `End ${node.text}` : node.text,
4555
4975
  blockIndex,
4556
4976
  children: [],
4557
4977
  meta: { tagType: node.tagType, resourceId: node.resourceId }
@@ -4585,14 +5005,15 @@ function extractBlockItems(blocks, startIndex, depth) {
4585
5005
  break;
4586
5006
  }
4587
5007
  case "jinjaIfBlock": {
4588
- const branches = block.branches.map((branch) => {
5008
+ const branches = block.branches.map((branch, branchIdx) => {
4589
5009
  const branchLabel = branch.branchType === "else" ? "ELSE" : `${branch.branchType.toUpperCase()} ${branch.condition || ""}`.trim();
4590
5010
  const branchChildren = extractBlockItems(branch.content, blockIndex, depth + 1);
4591
5011
  return {
4592
5012
  type: "jinjaBranch",
4593
5013
  label: branchLabel,
4594
5014
  blockIndex,
4595
- children: branchChildren
5015
+ children: branchChildren,
5016
+ meta: { branchIndex: branchIdx }
4596
5017
  };
4597
5018
  });
4598
5019
  items.push({
@@ -4640,11 +5061,12 @@ function buildDocumentTree(doc2) {
4640
5061
  const hasStructuredJinja = items.some((n) => n.type === "jinjaIf");
4641
5062
  if (!hasStructuredJinja) {
4642
5063
  for (const s of structures) {
4643
- const branches = s.branches.map((b) => ({
5064
+ const branches = s.branches.map((b, branchIdx) => ({
4644
5065
  type: "jinjaBranch",
4645
5066
  label: b.type === "else" ? "ELSE" : `${b.type.toUpperCase()} ${b.condition || ""}`.trim(),
4646
5067
  blockIndex: b.tagBlockIndex,
4647
- children: []
5068
+ children: [],
5069
+ meta: { branchIndex: branchIdx }
4648
5070
  }));
4649
5071
  const jinjaNode = {
4650
5072
  type: "jinjaIf",
@@ -4748,6 +5170,7 @@ function textToSlice(text2, view) {
4748
5170
  return null;
4749
5171
  }
4750
5172
  }
5173
+ var URL_RE = /^https?:\/\/[^\s]+$/i;
4751
5174
  function createPlugin() {
4752
5175
  return new Plugin({
4753
5176
  key,
@@ -4762,6 +5185,14 @@ function createPlugin() {
4762
5185
  return true;
4763
5186
  }
4764
5187
  }
5188
+ if (text2 && URL_RE.test(text2.trim())) {
5189
+ const url = text2.trim();
5190
+ const linkMark2 = actionbookSchema.marks.link.create({ href: url });
5191
+ const textNode = actionbookSchema.text(url, [linkMark2]);
5192
+ const slice2 = new Slice(Fragment.from(textNode), 0, 0);
5193
+ view.dispatch(view.state.tr.replaceSelection(slice2));
5194
+ return true;
5195
+ }
4765
5196
  if (!text2) return false;
4766
5197
  const slice = textToSlice(text2, view);
4767
5198
  if (!slice) return false;
@@ -4807,7 +5238,7 @@ function createMarkdownClipboardPlugin() {
4807
5238
  }
4808
5239
 
4809
5240
  // src/ui/plugin/jumpPointPlugin.ts
4810
- import { Plugin as Plugin2, PluginKey as PluginKey2, TextSelection as TextSelection2 } from "prosemirror-state";
5241
+ import { Plugin as Plugin2, PluginKey as PluginKey2, TextSelection as TextSelection3 } from "prosemirror-state";
4811
5242
  import { Decoration, DecorationSet } from "prosemirror-view";
4812
5243
  var adjacentKey = new PluginKey2("jumpPointAdjacent");
4813
5244
  var JUMP_POINT_ADJACENT_SPEC = { jumpPointAdjacent: true };
@@ -4870,6 +5301,9 @@ function createJumpPointEditPlugin() {
4870
5301
  });
4871
5302
  if (!duplicate) {
4872
5303
  reconvertTr.replaceWith(safeFrom, safeTo, jumpPointType.create({ id }));
5304
+ const mappedCursor = reconvertTr.mapping.map(cursor);
5305
+ const clampedCursor = Math.min(mappedCursor, reconvertTr.doc.content.size);
5306
+ reconvertTr.setSelection(TextSelection3.near(reconvertTr.doc.resolve(clampedCursor)));
4873
5307
  }
4874
5308
  }
4875
5309
  }
@@ -4902,7 +5336,7 @@ function explodeOnBackspace(state) {
4902
5336
  const rawText = `^${id}`;
4903
5337
  const tr = state.tr.replaceWith(nodeStart, nodeEnd, state.schema.text(rawText));
4904
5338
  const cursorPos = Math.min(nodeStart + rawText.length, tr.doc.content.size);
4905
- tr.setSelection(TextSelection2.near(tr.doc.resolve(cursorPos)));
5339
+ tr.setSelection(TextSelection3.near(tr.doc.resolve(cursorPos)));
4906
5340
  return tr;
4907
5341
  }
4908
5342
  function explodeOnArrowLeft(state) {
@@ -4912,7 +5346,7 @@ function explodeOnArrowLeft(state) {
4912
5346
  const rawText = `^${id}^`;
4913
5347
  const tr = state.tr.replaceWith(nodeStart, nodeEnd, state.schema.text(rawText));
4914
5348
  const cursorPos = Math.min(nodeStart + 1 + id.length, tr.doc.content.size);
4915
- tr.setSelection(TextSelection2.near(tr.doc.resolve(cursorPos)));
5349
+ tr.setSelection(TextSelection3.near(tr.doc.resolve(cursorPos)));
4916
5350
  tr.setMeta(jumpPointEditKey, { from: nodeStart, to: nodeStart + rawText.length });
4917
5351
  return tr;
4918
5352
  }
@@ -4961,21 +5395,30 @@ function createJumpPointAdjacentPlugin() {
4961
5395
  // src/ui/plugin/jumpPointNodeViewPlugin.tsx
4962
5396
  import { useCallback as useCallback2, useState as useState2 } from "react";
4963
5397
  import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
5398
+ function IconDiamondAlert({ size = 12, fill = "#D9352C" }) {
5399
+ return /* @__PURE__ */ jsxs3("svg", { width: size, height: size, viewBox: "0 0 12 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [
5400
+ /* @__PURE__ */ jsx4(
5401
+ "path",
5402
+ {
5403
+ d: "M5.293 1.293a1 1 0 0 1 1.414 0l4 4a1 1 0 0 1 0 1.414l-4 4a1 1 0 0 1-1.414 0l-4-4a1 1 0 0 1 0-1.414l4-4Z",
5404
+ fill
5405
+ }
5406
+ ),
5407
+ /* @__PURE__ */ jsx4("path", { d: "M6 3.5v3", stroke: "#fff", strokeWidth: "1.2", strokeLinecap: "round" }),
5408
+ /* @__PURE__ */ jsx4("circle", { cx: "6", cy: "8.25", r: "0.625", fill: "#fff" })
5409
+ ] });
5410
+ }
4964
5411
  var JUMP_POINT_STYLE = {
4965
5412
  display: "inline-flex",
4966
5413
  alignItems: "center",
4967
5414
  backgroundColor: "#FFF2B6",
4968
5415
  border: "1px solid #FFC233",
4969
5416
  color: "#AA5D04",
4970
- borderRadius: "2px",
4971
- padding: "2px",
4972
- fontSize: "12px",
4973
- lineHeight: "16px",
4974
5417
  userSelect: "none",
4975
5418
  cursor: "default",
4976
- boxSizing: "border-box",
4977
- height: "20px",
4978
- overflow: "hidden"
5419
+ boxSizing: "border-box"
5420
+ // height, padding, borderRadius, fontSize, lineHeight, overflow
5421
+ // are set via CSS class .ab-jump-point for heading-level overrides
4979
5422
  };
4980
5423
  var JUMP_POINT_ADJACENT_STYLE = {
4981
5424
  ...JUMP_POINT_STYLE,
@@ -4989,8 +5432,8 @@ var JUMP_POINT_DUPLICATE_STYLE = {
4989
5432
  color: "#9D091E"
4990
5433
  };
4991
5434
  var LABEL_STYLE = {
4992
- padding: "0 4px",
4993
5435
  whiteSpace: "nowrap"
5436
+ // padding is set via CSS .ab-jp-label for heading-level overrides
4994
5437
  };
4995
5438
  var CLOSE_BTN_STYLE = {
4996
5439
  display: "inline-flex",
@@ -5021,7 +5464,7 @@ var TOOLTIP_STYLE = {
5021
5464
  fontSize: "14px",
5022
5465
  lineHeight: "20px",
5023
5466
  color: "#0D0D0D",
5024
- whiteSpace: "nowrap",
5467
+ maxWidth: "240px",
5025
5468
  zIndex: 100,
5026
5469
  pointerEvents: "none"
5027
5470
  };
@@ -5041,13 +5484,14 @@ function JumpPointNodeViewComponent({ node, view, getPos, decorations }) {
5041
5484
  return /* @__PURE__ */ jsxs3(
5042
5485
  "span",
5043
5486
  {
5487
+ className: "ab-jump-point ab-jump-point-duplicate",
5044
5488
  style: { ...JUMP_POINT_DUPLICATE_STYLE, position: "relative" },
5045
5489
  id: `jp-${id}`,
5046
5490
  onMouseEnter: () => setShowTooltip(true),
5047
5491
  onMouseLeave: () => setShowTooltip(false),
5048
5492
  children: [
5049
- /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#9D091E", style: { paddingLeft: "2px", flexShrink: 0 } }),
5050
- /* @__PURE__ */ jsx4("span", { style: LABEL_STYLE, children: id }),
5493
+ /* @__PURE__ */ jsx4("span", { className: "ab-jp-icon", children: /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#9D091E", style: { paddingLeft: "2px", flexShrink: 0 } }) }),
5494
+ /* @__PURE__ */ jsx4("span", { className: "ab-jp-label", style: LABEL_STYLE, children: id }),
5051
5495
  /* @__PURE__ */ jsx4(
5052
5496
  "button",
5053
5497
  {
@@ -5057,8 +5501,8 @@ function JumpPointNodeViewComponent({ node, view, getPos, decorations }) {
5057
5501
  e.stopPropagation();
5058
5502
  handleDelete2();
5059
5503
  },
5060
- title: "Remove anchor",
5061
- children: "\u2715"
5504
+ title: "Remove duplicate anchor",
5505
+ children: /* @__PURE__ */ jsx4(IconDiamondAlert, { size: 12, fill: "#D9352C" })
5062
5506
  }
5063
5507
  ),
5064
5508
  showTooltip && /* @__PURE__ */ jsxs3("span", { style: TOOLTIP_STYLE, children: [
@@ -5071,14 +5515,14 @@ function JumpPointNodeViewComponent({ node, view, getPos, decorations }) {
5071
5515
  );
5072
5516
  }
5073
5517
  if (isAdjacent) {
5074
- return /* @__PURE__ */ jsxs3("span", { style: JUMP_POINT_ADJACENT_STYLE, id: `jp-${id}`, children: [
5075
- /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { paddingLeft: "2px", flexShrink: 0 } }),
5076
- /* @__PURE__ */ jsx4("span", { style: LABEL_STYLE, children: `^${id}^` })
5518
+ return /* @__PURE__ */ jsxs3("span", { className: "ab-jump-point ab-jump-point-adjacent", style: JUMP_POINT_ADJACENT_STYLE, id: `jp-${id}`, children: [
5519
+ /* @__PURE__ */ jsx4("span", { className: "ab-jp-icon", children: /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { paddingLeft: "2px", flexShrink: 0 } }) }),
5520
+ /* @__PURE__ */ jsx4("span", { className: "ab-jp-label", style: LABEL_STYLE, children: `^${id}^` })
5077
5521
  ] });
5078
5522
  }
5079
- return /* @__PURE__ */ jsxs3("span", { style: JUMP_POINT_STYLE, id: `jp-${id}`, children: [
5080
- /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { paddingLeft: "2px", flexShrink: 0 } }),
5081
- /* @__PURE__ */ jsx4("span", { style: LABEL_STYLE, children: id })
5523
+ return /* @__PURE__ */ jsxs3("span", { className: "ab-jump-point", style: JUMP_POINT_STYLE, id: `jp-${id}`, children: [
5524
+ /* @__PURE__ */ jsx4("span", { className: "ab-jp-icon", children: /* @__PURE__ */ jsx4(IconAnchor, { size: 12, fill: "#AA5D04", style: { paddingLeft: "2px", flexShrink: 0 } }) }),
5525
+ /* @__PURE__ */ jsx4("span", { className: "ab-jp-label", style: LABEL_STYLE, children: id })
5082
5526
  ] });
5083
5527
  }
5084
5528
  function createJumpPointNodeViewPlugin() {
@@ -5376,6 +5820,7 @@ function createJinjaDecorationPlugin() {
5376
5820
  // src/ui/plugin/jinjaIfBlockPlugin.tsx
5377
5821
  import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
5378
5822
  import { createRoot as createRoot2 } from "react-dom/client";
5823
+ import { TextSelection as TextSelection4 } from "prosemirror-state";
5379
5824
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
5380
5825
  var PLACEHOLDER_TEXT = "Describe what AI agent should do when this condition is met";
5381
5826
  var CONDITION_PLACEHOLDER = "Write a condition in natural language";
@@ -5782,7 +6227,12 @@ function insertNewBranch(view, nodePos, branchType, condition = "") {
5782
6227
  const newBranch = createBranchNode(view.state.schema, branchType, condition);
5783
6228
  if (!newBranch) return false;
5784
6229
  const insertPos = context.branchPos + context.branchNode.nodeSize;
5785
- view.dispatch(view.state.tr.insert(insertPos, newBranch).scrollIntoView());
6230
+ const tr = view.state.tr.insert(insertPos, newBranch);
6231
+ const cursorPos = insertPos + 2;
6232
+ if (cursorPos <= tr.doc.content.size) {
6233
+ tr.setSelection(TextSelection4.near(tr.doc.resolve(cursorPos)));
6234
+ }
6235
+ view.dispatch(tr.scrollIntoView());
5786
6236
  view.focus();
5787
6237
  return true;
5788
6238
  }
@@ -5829,6 +6279,7 @@ function ConditionDisplay({
5829
6279
  const [isEditing, setIsEditing] = useState3(false);
5830
6280
  const [draftValue, setDraftValue] = useState3(condition);
5831
6281
  const inputRef = useRef2(null);
6282
+ const autoFocusedRef = useRef2(false);
5832
6283
  useEffect2(() => {
5833
6284
  if (!isEditing) {
5834
6285
  setDraftValue(condition);
@@ -5840,6 +6291,12 @@ function ConditionDisplay({
5840
6291
  inputRef.current?.select();
5841
6292
  }
5842
6293
  }, [isEditing]);
6294
+ useEffect2(() => {
6295
+ if (editable && !condition && !autoFocusedRef.current) {
6296
+ autoFocusedRef.current = true;
6297
+ setIsEditing(true);
6298
+ }
6299
+ }, []);
5843
6300
  if (branchType === "else") {
5844
6301
  return /* @__PURE__ */ jsx6("span", { className: "jinja-otherwise", children: "Otherwise" });
5845
6302
  }
@@ -6135,15 +6592,25 @@ var JinjaIfBranchView = class {
6135
6592
  }
6136
6593
  render() {
6137
6594
  const nodePos = this.getPos();
6138
- if (nodePos == null) return;
6139
- const lastBranch = getLastBranchOfBlock(this.view, nodePos);
6140
- const context = getBranchContext(this.view, nodePos);
6141
- if (!lastBranch || !context) return;
6595
+ let context = null;
6596
+ let lastBranch = null;
6597
+ if (nodePos != null) {
6598
+ context = getBranchContext(this.view, nodePos);
6599
+ lastBranch = getLastBranchOfBlock(this.view, nodePos);
6600
+ let branchIdx = context?.branchIndex ?? -1;
6601
+ if (branchIdx < 0 && this.dom.parentElement) {
6602
+ const siblings = this.dom.parentElement.querySelectorAll(":scope > [data-jinja-branch]");
6603
+ branchIdx = Array.prototype.indexOf.call(siblings, this.dom);
6604
+ }
6605
+ if (branchIdx >= 0) {
6606
+ this.dom.setAttribute("data-branch-index", String(branchIdx));
6607
+ }
6608
+ }
6142
6609
  const branchType = this.node.attrs.branchType;
6143
6610
  const condition = String(this.node.attrs.condition ?? "");
6144
- const isLast = lastBranch.index === context.branchIndex;
6145
- const lastNonElseIdx = getLastNonElseBranchIndex(this.view, nodePos);
6146
- const showAddButton = context.branchIndex === lastNonElseIdx;
6611
+ const isLast = context && lastBranch ? lastBranch.index === context.branchIndex : true;
6612
+ const lastNonElseIdx = context ? getLastNonElseBranchIndex(this.view, nodePos) : 0;
6613
+ const showAddButton = context ? context.branchIndex === lastNonElseIdx : true;
6147
6614
  this.dom.classList.toggle("jinja-branch-last", isLast);
6148
6615
  this.root.render(
6149
6616
  /* @__PURE__ */ jsx6(
@@ -6153,16 +6620,19 @@ var JinjaIfBranchView = class {
6153
6620
  condition,
6154
6621
  editable: this.view.editable,
6155
6622
  isLastBranch: showAddButton,
6156
- hasElseBranch: getElseBranch(this.view, nodePos),
6623
+ hasElseBranch: context && nodePos != null ? getElseBranch(this.view, nodePos) : false,
6157
6624
  onConditionChange: (value) => {
6158
6625
  const currentPos = this.getPos();
6159
6626
  if (currentPos == null) return;
6160
- this.view.dispatch(
6161
- this.view.state.tr.setNodeMarkup(currentPos, void 0, {
6162
- ...this.node.attrs,
6163
- condition: value
6164
- })
6165
- );
6627
+ const tr = this.view.state.tr.setNodeMarkup(currentPos, void 0, {
6628
+ ...this.node.attrs,
6629
+ condition: value
6630
+ });
6631
+ const contentPos = currentPos + 2;
6632
+ if (contentPos <= tr.doc.content.size) {
6633
+ tr.setSelection(TextSelection4.near(tr.doc.resolve(contentPos)));
6634
+ }
6635
+ this.view.dispatch(tr.scrollIntoView());
6166
6636
  this.view.focus();
6167
6637
  },
6168
6638
  onDelete: () => {
@@ -6213,9 +6683,9 @@ function createJinjaIfBlockPlugin() {
6213
6683
 
6214
6684
  // src/ui/plugin/linkPlugin.ts
6215
6685
  import { InputRule as InputRule2, inputRules as inputRules2 } from "prosemirror-inputrules";
6216
- import { Plugin as Plugin5, PluginKey as PluginKey5, TextSelection as TextSelection3 } from "prosemirror-state";
6217
- var LINK_INPUT_RE = /\[([^\]]*)\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
6218
- var LINK_FULL_RE = /^\[([^\]]*)\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
6686
+ import { Plugin as Plugin5, PluginKey as PluginKey5, TextSelection as TextSelection5 } from "prosemirror-state";
6687
+ var LINK_INPUT_RE = /\\?\[([^\]]*?)\\?\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
6688
+ var LINK_FULL_RE = /^\\?\[([^\]]*?)\\?\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
6219
6689
  var linkEditKey = new PluginKey5("linkEdit");
6220
6690
  function getMarkRange($pos, type) {
6221
6691
  const { parentOffset } = $pos;
@@ -6259,7 +6729,7 @@ function explodeLinkToRaw(state) {
6259
6729
  const tr = state.tr;
6260
6730
  tr.replaceWith(from, to, schema.text(rawText));
6261
6731
  const newPos = Math.min(from + rawCursorOffset, tr.doc.content.size);
6262
- tr.setSelection(TextSelection3.near(tr.doc.resolve(newPos)));
6732
+ tr.setSelection(TextSelection5.near(tr.doc.resolve(newPos)));
6263
6733
  tr.setMeta(linkEditKey, { from, to: from + rawText.length });
6264
6734
  return tr;
6265
6735
  }
@@ -6272,8 +6742,8 @@ function createLinkEditPlugin() {
6272
6742
  const meta = tr.getMeta(linkEditKey);
6273
6743
  if (meta !== void 0) return { rawRange: meta };
6274
6744
  if (prev.rawRange) {
6275
- const from = tr.mapping.map(prev.rawRange.from);
6276
- const to = tr.mapping.map(prev.rawRange.to);
6745
+ const from = tr.mapping.map(prev.rawRange.from, -1);
6746
+ const to = tr.mapping.map(prev.rawRange.to, -1);
6277
6747
  if (from >= to) return { rawRange: null };
6278
6748
  return { rawRange: { from, to } };
6279
6749
  }
@@ -6284,7 +6754,7 @@ function createLinkEditPlugin() {
6284
6754
  const pluginState = linkEditKey.getState(newState);
6285
6755
  if (!pluginState?.rawRange) return null;
6286
6756
  const { from, to } = pluginState.rawRange;
6287
- if (newState.selection.anchor >= from && newState.selection.anchor <= to) {
6757
+ if (newState.selection.anchor >= from && newState.selection.anchor < to) {
6288
6758
  return null;
6289
6759
  }
6290
6760
  const docSize = newState.doc.content.size;
@@ -6428,12 +6898,13 @@ var STYLES = (
6428
6898
  cursor: grab;
6429
6899
  color: transparent;
6430
6900
  border-radius: var(--ab-dh-handle-radius);
6431
- transition: color var(--ab-dh-transition-duration), background var(--ab-dh-transition-duration), opacity var(--ab-dh-transition-duration);
6901
+ transition: none;
6432
6902
  user-select: none;
6433
6903
  pointer-events: auto;
6434
6904
  touch-action: none;
6435
6905
  opacity: 0;
6436
6906
  pointer-events: none;
6907
+ overflow: visible;
6437
6908
  }
6438
6909
  .ab-drag-handle.ab-dh-active {
6439
6910
  opacity: 1;
@@ -6444,6 +6915,72 @@ var STYLES = (
6444
6915
  color: var(--ab-dh-color-visible) !important;
6445
6916
  background: var(--ab-dh-bg-hover);
6446
6917
  }
6918
+ .ab-drag-handle:hover::after {
6919
+ content: 'Drag to move';
6920
+ position: absolute;
6921
+ bottom: calc(100% + 4px);
6922
+ left: 0;
6923
+ background: #2e2e2e;
6924
+ color: #fff;
6925
+ font-family: 'SF Pro Text', -apple-system, BlinkMacSystemFont, sans-serif;
6926
+ font-size: 12px;
6927
+ font-weight: 400;
6928
+ line-height: 16px;
6929
+ padding: 4px 8px;
6930
+ border-radius: 4px;
6931
+ white-space: nowrap;
6932
+ pointer-events: none;
6933
+ z-index: 30;
6934
+ }
6935
+ .ab-drag-handle:hover::before {
6936
+ content: '';
6937
+ position: absolute;
6938
+ bottom: calc(100% + 0px);
6939
+ left: 8px;
6940
+ width: 0;
6941
+ height: 0;
6942
+ border-left: 5px solid transparent;
6943
+ border-right: 5px solid transparent;
6944
+ border-top: 5px solid #2e2e2e;
6945
+ pointer-events: none;
6946
+ z-index: 31;
6947
+ }
6948
+ .ab-drag-handle.dragging::after,
6949
+ .ab-drag-handle.dragging::before {
6950
+ display: none;
6951
+ }
6952
+ .ab-dh-menu {
6953
+ position: absolute;
6954
+ top: calc(100% + 4px);
6955
+ left: 0;
6956
+ background: #fff;
6957
+ border-radius: 4px;
6958
+ box-shadow: 0px 8px 10px 1px rgba(13,13,13,0.12), 0px 3px 14px 2px rgba(13,13,13,0.08), 0px 3px 5px -3px rgba(13,13,13,0.04);
6959
+ padding: 8px 0;
6960
+ z-index: 40;
6961
+ min-width: 120px;
6962
+ }
6963
+ .ab-dh-menu-item {
6964
+ display: flex;
6965
+ align-items: center;
6966
+ width: 100%;
6967
+ padding: 6px 16px;
6968
+ border: none;
6969
+ background: #fff;
6970
+ cursor: pointer;
6971
+ font-family: 'SF Pro Text', -apple-system, BlinkMacSystemFont, sans-serif;
6972
+ font-size: 14px;
6973
+ font-weight: 400;
6974
+ line-height: 20px;
6975
+ letter-spacing: -0.1px;
6976
+ color: #0d0d0d;
6977
+ white-space: nowrap;
6978
+ text-overflow: ellipsis;
6979
+ overflow: hidden;
6980
+ }
6981
+ .ab-dh-menu-item:hover {
6982
+ background: rgba(0,0,0,0.04);
6983
+ }
6447
6984
  .ab-drag-handle:active, .ab-drag-handle.dragging {
6448
6985
  cursor: grabbing;
6449
6986
  color: var(--ab-dh-color-accent) !important;
@@ -6753,7 +7290,6 @@ var DragHandleController = class {
6753
7290
  el.setAttribute("role", "button");
6754
7291
  el.setAttribute("aria-roledescription", "drag handle");
6755
7292
  el.setAttribute("aria-label", `\uBE14\uB85D ${idx + 1} \uC774\uB3D9`);
6756
- el.setAttribute("title", "Drag to move");
6757
7293
  el.setAttribute("tabindex", "-1");
6758
7294
  el.innerHTML = GRIP_SVG;
6759
7295
  el.style.left = `${handleLeft}px`;
@@ -6766,18 +7302,85 @@ var DragHandleController = class {
6766
7302
  this.prevBlocks = newBlocks;
6767
7303
  this.updateActiveHandle();
6768
7304
  }
7305
+ activeMenu = null;
7306
+ activeMenuCleanup = null;
7307
+ dismissMenu() {
7308
+ if (this.activeMenu) {
7309
+ this.activeMenu.remove();
7310
+ this.activeMenu = null;
7311
+ }
7312
+ if (this.activeMenuCleanup) {
7313
+ this.activeMenuCleanup();
7314
+ this.activeMenuCleanup = null;
7315
+ }
7316
+ }
7317
+ showMenu(el, blockIndex) {
7318
+ this.dismissMenu();
7319
+ const menu = document.createElement("div");
7320
+ menu.className = "ab-dh-menu";
7321
+ const deleteBtn = document.createElement("button");
7322
+ deleteBtn.className = "ab-dh-menu-item";
7323
+ deleteBtn.textContent = "Delete";
7324
+ deleteBtn.addEventListener("mousedown", (e) => {
7325
+ e.preventDefault();
7326
+ e.stopPropagation();
7327
+ this.deleteBlock(blockIndex);
7328
+ this.dismissMenu();
7329
+ });
7330
+ menu.appendChild(deleteBtn);
7331
+ el.appendChild(menu);
7332
+ this.activeMenu = menu;
7333
+ const onOutside = (e) => {
7334
+ if (!menu.contains(e.target)) {
7335
+ this.dismissMenu();
7336
+ }
7337
+ };
7338
+ setTimeout(() => document.addEventListener("mousedown", onOutside, true), 0);
7339
+ this.activeMenuCleanup = () => document.removeEventListener("mousedown", onOutside, true);
7340
+ }
7341
+ deleteBlock(blockIndex) {
7342
+ const blocks = this.collectBlocks();
7343
+ if (blockIndex >= blocks.length) return;
7344
+ const block = blocks[blockIndex];
7345
+ const from = block.offset;
7346
+ const to = block.offset + block.nodeSize;
7347
+ const tr = this.view.state.tr;
7348
+ tr.replaceWith(from, to, this.view.state.schema.nodes.paragraph.create());
7349
+ this.view.dispatch(tr);
7350
+ this.view.focus();
7351
+ }
6769
7352
  bindHandleEvents(el, index) {
6770
7353
  let currentIndex = index;
7354
+ let downX = 0;
7355
+ let downY = 0;
7356
+ let didDrag = false;
7357
+ let menuWasOpen = false;
6771
7358
  const onPointerDown = (e) => {
7359
+ if (this.activeMenu && this.activeMenu.contains(e.target)) return;
6772
7360
  e.preventDefault();
6773
7361
  if (!this.view.editable) return;
7362
+ menuWasOpen = !!this.activeMenu;
7363
+ this.dismissMenu();
7364
+ downX = e.clientX;
7365
+ downY = e.clientY;
7366
+ didDrag = false;
6774
7367
  el.setPointerCapture(e.pointerId);
6775
7368
  this.prevBlocks = this.collectBlocks();
6776
7369
  this.syncHandlePositions();
6777
7370
  this.startDrag(currentIndex, el, e);
6778
7371
  };
6779
- const onPointerMove = (e) => this.onPointerMove(e);
6780
- const onPointerUp = (e) => this.onPointerUp(e);
7372
+ const onPointerMove = (e) => {
7373
+ if (Math.abs(e.clientX - downX) > 3 || Math.abs(e.clientY - downY) > 3) {
7374
+ didDrag = true;
7375
+ }
7376
+ this.onPointerMove(e);
7377
+ };
7378
+ const onPointerUp = (e) => {
7379
+ this.onPointerUp(e);
7380
+ if (!didDrag && !menuWasOpen) {
7381
+ this.showMenu(el, currentIndex);
7382
+ }
7383
+ };
6781
7384
  const onLostCapture = () => this.cancelDrag();
6782
7385
  const onPointerCancel = () => this.cancelDrag();
6783
7386
  el.addEventListener("pointerdown", onPointerDown);
@@ -7184,81 +7787,89 @@ function createTodoNodeViewPlugin() {
7184
7787
  };
7185
7788
  }
7186
7789
 
7187
- // src/ui/plugin/noteBlockPlugin.tsx
7188
- var NoteBlockView = class {
7189
- dom;
7190
- contentDOM;
7191
- constructor(_node, _view, _getPos) {
7192
- this.dom = document.createElement("div");
7193
- this.dom.setAttribute("data-note-block", "");
7194
- this.dom.className = "ab-note-block";
7195
- this.dom.style.cssText = `
7196
- position: relative;
7197
- margin: 8px 0;
7198
- padding: 12px 16px 12px 16px;
7199
- background: rgba(0, 0, 0, 0.015);
7200
- border-top: 1px solid rgba(0, 0, 0, 0.08);
7201
- color: #999;
7202
- font-style: italic;
7203
- `;
7204
- const label = document.createElement("span");
7205
- label.contentEditable = "false";
7206
- label.textContent = "Note";
7207
- label.style.cssText = `
7208
- display: inline-block;
7209
- font-size: 11px;
7210
- font-weight: 600;
7211
- font-style: normal;
7212
- color: #aaa;
7213
- background: rgba(0, 0, 0, 0.03);
7214
- border: 1px solid rgba(0, 0, 0, 0.1);
7215
- border-radius: 3px;
7216
- padding: 1px 6px;
7217
- margin-bottom: 6px;
7218
- user-select: none;
7219
- line-height: 1.4;
7220
- `;
7221
- this.dom.appendChild(label);
7222
- this.contentDOM = document.createElement("div");
7223
- this.contentDOM.className = "ab-note-block-content";
7224
- this.dom.appendChild(this.contentDOM);
7790
+ // src/ui/plugin/placeholderPlugin.ts
7791
+ import { Plugin as Plugin7, PluginKey as PluginKey7 } from "prosemirror-state";
7792
+ import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "prosemirror-view";
7793
+ var PLACEHOLDER_TEXT2 = "Type to start writing, or press / to insert an action.";
7794
+ var pluginKey2 = new PluginKey7("placeholder");
7795
+ function buildDecorations4(state) {
7796
+ const { doc: doc2, selection } = state;
7797
+ if (!selection.empty) return DecorationSet4.empty;
7798
+ const $from = selection.$from;
7799
+ const parent = $from.parent;
7800
+ if (parent.type.name !== "paragraph" || parent.content.size !== 0) {
7801
+ return DecorationSet4.empty;
7225
7802
  }
7226
- };
7227
- function createNoteBlockPlugin() {
7803
+ const EXCLUDED_ANCESTORS = /* @__PURE__ */ new Set(["noteBlock", "blockquote", "jinjaIfBranch"]);
7804
+ for (let d = $from.depth - 1; d > 0; d--) {
7805
+ if (EXCLUDED_ANCESTORS.has($from.node(d).type.name)) {
7806
+ return DecorationSet4.empty;
7807
+ }
7808
+ }
7809
+ const pos = $from.before($from.depth);
7810
+ const deco = Decoration4.node(pos, pos + parent.nodeSize, {
7811
+ class: "ab-empty-paragraph",
7812
+ "data-placeholder": PLACEHOLDER_TEXT2
7813
+ });
7814
+ return DecorationSet4.create(doc2, [deco]);
7815
+ }
7816
+ function createPlaceholderPlugin() {
7228
7817
  return {
7229
- name: "noteBlockNodeView",
7230
- nodeViews: () => ({
7231
- noteBlock: (node, view, getPos) => new NoteBlockView(node, view, getPos)
7232
- })
7818
+ name: "placeholder",
7819
+ plugins: () => [
7820
+ new Plugin7({
7821
+ key: pluginKey2,
7822
+ state: {
7823
+ init(_, state) {
7824
+ return buildDecorations4(state);
7825
+ },
7826
+ apply(tr, oldSet, _oldState, newState) {
7827
+ if (tr.docChanged || tr.selectionSet) {
7828
+ return buildDecorations4(newState);
7829
+ }
7830
+ return oldSet;
7831
+ }
7832
+ },
7833
+ props: {
7834
+ decorations(state) {
7835
+ return pluginKey2.getState(state) ?? DecorationSet4.empty;
7836
+ }
7837
+ }
7838
+ })
7839
+ ]
7233
7840
  };
7234
7841
  }
7235
7842
 
7236
7843
  // src/ui/plugin/slashCommandPlugin.ts
7237
- import { Plugin as Plugin7, PluginKey as PluginKey7 } from "prosemirror-state";
7238
- var slashCommandKey = new PluginKey7("slashCommand");
7844
+ import { Plugin as Plugin8, PluginKey as PluginKey8 } from "prosemirror-state";
7845
+ var slashCommandKey = new PluginKey8("slashCommand");
7239
7846
  var TRIGGER_RE = /(?:^|\s)(\/[^\s]*)$/;
7240
7847
  function deriveState(state, dismissedFrom) {
7848
+ const inactive = { active: false, range: null, query: "", parentType: "" };
7241
7849
  const { selection } = state;
7242
- if (!selection.empty) return { active: false, range: null, query: "" };
7850
+ if (!selection.empty) return inactive;
7243
7851
  const { $from } = selection;
7244
7852
  const parentType = $from.parent.type.name;
7245
- if (parentType === "heading") return { active: false, range: null, query: "" };
7853
+ for (let d = $from.depth; d > 0; d--) {
7854
+ if ($from.node(d).type.name === "noteBlock") return inactive;
7855
+ }
7246
7856
  const textBefore = $from.parent.textBetween(0, $from.parentOffset, "\n", "\0");
7247
7857
  const match = TRIGGER_RE.exec(textBefore);
7248
- if (!match) return { active: false, range: null, query: "" };
7858
+ if (!match) return inactive;
7249
7859
  const slashText = match[1];
7250
7860
  const slashStartInParent = textBefore.lastIndexOf(slashText);
7251
7861
  const from = $from.start() + slashStartInParent;
7252
7862
  const to = $from.pos;
7253
- if (dismissedFrom === from) return { active: false, range: null, query: "" };
7863
+ if (dismissedFrom === from) return inactive;
7254
7864
  return {
7255
7865
  active: true,
7256
7866
  range: { from, to },
7257
- query: slashText.slice(1)
7867
+ query: slashText.slice(1),
7868
+ parentType
7258
7869
  };
7259
7870
  }
7260
7871
  function createSlashCommandPlugin() {
7261
- const plugin = new Plugin7({
7872
+ const plugin = new Plugin8({
7262
7873
  key: slashCommandKey,
7263
7874
  state: {
7264
7875
  init(_, state) {
@@ -7776,6 +8387,7 @@ function DocumentTreeView({ doc: doc2, className, onNavigate }) {
7776
8387
  // src/ui/components/FloatingMenu.tsx
7777
8388
  import { useCallback as useCallback3, useEffect as useEffect4, useRef as useRef4, useState as useState7 } from "react";
7778
8389
  import { createPortal as createPortal2 } from "react-dom";
8390
+ import { TextSelection as TextSelection6 } from "prosemirror-state";
7779
8391
  import { setBlockType as setBlockType2, toggleMark as toggleMark2, wrapIn } from "prosemirror-commands";
7780
8392
  import { wrapInList as wrapInList2 } from "prosemirror-schema-list";
7781
8393
  import { Fragment as Fragment4, jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
@@ -7798,6 +8410,56 @@ function hasMark(state, type) {
7798
8410
  if (empty) return false;
7799
8411
  return state.doc.rangeHasMark(from, to, type);
7800
8412
  }
8413
+ function getMarkRange2($pos, type) {
8414
+ const { parentOffset } = $pos;
8415
+ let child = $pos.parent.childAfter(parentOffset);
8416
+ if (parentOffset === child.offset && child.offset !== 0) {
8417
+ child = $pos.parent.childBefore(parentOffset);
8418
+ }
8419
+ if (!child.node) return null;
8420
+ const mark = child.node.marks.find((candidate) => candidate.type === type);
8421
+ if (!mark) return null;
8422
+ const parentStart = $pos.start();
8423
+ let { index: startIndex } = child;
8424
+ let startPos = parentStart + child.offset;
8425
+ let endIndex = startIndex + 1;
8426
+ let endPos = startPos + child.node.nodeSize;
8427
+ while (startIndex > 0 && mark.isInSet($pos.parent.child(startIndex - 1).marks)) {
8428
+ startIndex -= 1;
8429
+ startPos -= $pos.parent.child(startIndex).nodeSize;
8430
+ }
8431
+ while (endIndex < $pos.parent.childCount && mark.isInSet($pos.parent.child(endIndex).marks)) {
8432
+ endPos += $pos.parent.child(endIndex).nodeSize;
8433
+ endIndex += 1;
8434
+ }
8435
+ return { from: startPos, to: endPos, mark };
8436
+ }
8437
+ function getLinkAtCursor(state) {
8438
+ const { selection, doc: doc2 } = state;
8439
+ if (!selection.empty) return null;
8440
+ const range = getMarkRange2(doc2.resolve(selection.from), linkMark);
8441
+ if (!range) return null;
8442
+ return {
8443
+ href: range.mark.attrs.href || "",
8444
+ text: doc2.textBetween(range.from, range.to),
8445
+ from: range.from,
8446
+ to: range.to
8447
+ };
8448
+ }
8449
+ function normalizeLinkHref(href) {
8450
+ const trimmedHref = href.trim();
8451
+ if (!trimmedHref) return "";
8452
+ return trimmedHref.startsWith("#") || trimmedHref.startsWith("/") || /^https?:\/\//i.test(trimmedHref) ? trimmedHref : `https://${trimmedHref}`;
8453
+ }
8454
+ function getNonLinkMarksInRange(state, from, to) {
8455
+ let marks = [];
8456
+ state.doc.nodesBetween(from, to, (node) => {
8457
+ if (!node.isText) return;
8458
+ marks = node.marks.filter((mark) => mark.type !== linkMark);
8459
+ return false;
8460
+ });
8461
+ return marks;
8462
+ }
7801
8463
  function activeHeadingLevel(state) {
7802
8464
  const { $from } = state.selection;
7803
8465
  const node = $from.parent;
@@ -8260,6 +8922,408 @@ function SelectionToolbar({ view, state, selectionRect }) {
8260
8922
  }
8261
8923
  );
8262
8924
  }
8925
+ function LinkHoverTooltip({ view, link: link2, onEdit }) {
8926
+ const [hover, setHover] = useState7(false);
8927
+ const startCoords = view.coordsAtPos(link2.from);
8928
+ const endCoords = view.coordsAtPos(link2.to);
8929
+ const anchorTop = Math.min(startCoords.top, endCoords.top);
8930
+ const linkLeft = Math.min(startCoords.left, endCoords.left);
8931
+ const linkRight = Math.max(startCoords.right, endCoords.right);
8932
+ const tooltipHalfW = Math.min(174, Math.max(0, (window.innerWidth - VPORT_MARGIN2 * 2) / 2));
8933
+ const safeCenterX = Math.max(
8934
+ VPORT_MARGIN2 + tooltipHalfW,
8935
+ Math.min((linkLeft + linkRight) / 2, window.innerWidth - VPORT_MARGIN2 - tooltipHalfW)
8936
+ );
8937
+ return createPortal2(
8938
+ /* @__PURE__ */ jsxs9(
8939
+ "div",
8940
+ {
8941
+ ...{ [FLOAT_ATTR]: "" },
8942
+ style: {
8943
+ position: "fixed",
8944
+ left: safeCenterX,
8945
+ top: Math.max(64, anchorTop - 4),
8946
+ transform: "translate(-50%, -100%)",
8947
+ zIndex: 1e3,
8948
+ display: "flex",
8949
+ flexDirection: "column",
8950
+ alignItems: "center",
8951
+ animation: "ab-float-in 0.12s ease",
8952
+ pointerEvents: "auto"
8953
+ },
8954
+ children: [
8955
+ /* @__PURE__ */ jsxs9(
8956
+ "div",
8957
+ {
8958
+ ...{ [FLOAT_ATTR]: "" },
8959
+ style: {
8960
+ display: "flex",
8961
+ alignItems: "center",
8962
+ gap: 4,
8963
+ minHeight: 28,
8964
+ maxWidth: "min(348px, calc(100vw - 16px))",
8965
+ padding: "6px 8px",
8966
+ background: "#2E2E2E",
8967
+ borderRadius: 4,
8968
+ boxSizing: "border-box"
8969
+ },
8970
+ children: [
8971
+ /* @__PURE__ */ jsx10(
8972
+ "span",
8973
+ {
8974
+ ...{ [FLOAT_ATTR]: "" },
8975
+ style: {
8976
+ color: "#FFFFFF",
8977
+ fontSize: 12,
8978
+ lineHeight: "16px",
8979
+ fontFamily: '"SF Pro Text", "SF Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
8980
+ whiteSpace: "normal",
8981
+ wordBreak: "break-word",
8982
+ overflowWrap: "anywhere",
8983
+ maxWidth: 300
8984
+ },
8985
+ children: link2.href
8986
+ }
8987
+ ),
8988
+ /* @__PURE__ */ jsx10(
8989
+ "div",
8990
+ {
8991
+ ...{ [FLOAT_ATTR]: "" },
8992
+ style: {
8993
+ width: 1,
8994
+ height: 16,
8995
+ background: "#424242",
8996
+ flexShrink: 0
8997
+ }
8998
+ }
8999
+ ),
9000
+ /* @__PURE__ */ jsx10(
9001
+ "button",
9002
+ {
9003
+ type: "button",
9004
+ ...{ [FLOAT_ATTR]: "" },
9005
+ onMouseDown: (e) => {
9006
+ e.preventDefault();
9007
+ onEdit(link2);
9008
+ },
9009
+ onMouseEnter: () => setHover(true),
9010
+ onMouseLeave: () => setHover(false),
9011
+ style: {
9012
+ ...BTN_RESET2,
9013
+ padding: "0 2px",
9014
+ color: "#FFFFFF",
9015
+ fontSize: 12,
9016
+ lineHeight: "16px",
9017
+ fontWeight: 600,
9018
+ fontFamily: '"SF Pro Text", "SF Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
9019
+ opacity: hover ? 0.78 : 1,
9020
+ flexShrink: 0
9021
+ },
9022
+ children: "Edit"
9023
+ }
9024
+ )
9025
+ ]
9026
+ }
9027
+ ),
9028
+ /* @__PURE__ */ jsx10(
9029
+ "div",
9030
+ {
9031
+ ...{ [FLOAT_ATTR]: "" },
9032
+ style: {
9033
+ width: 0,
9034
+ height: 0,
9035
+ borderLeft: "6px solid transparent",
9036
+ borderRight: "6px solid transparent",
9037
+ borderTop: "6px solid #2E2E2E",
9038
+ marginTop: -1
9039
+ }
9040
+ }
9041
+ )
9042
+ ]
9043
+ }
9044
+ ),
9045
+ document.body
9046
+ );
9047
+ }
9048
+ function LinkEditDialog({ view, link: link2, onClose }) {
9049
+ const [textValue, setTextValue] = useState7(link2.text);
9050
+ const [hrefValue, setHrefValue] = useState7(link2.href);
9051
+ const textInputRef = useRef4(null);
9052
+ useEffect4(() => {
9053
+ setTextValue(link2.text);
9054
+ setHrefValue(link2.href);
9055
+ }, [link2.from, link2.href, link2.text, link2.to]);
9056
+ useEffect4(() => {
9057
+ setTimeout(() => textInputRef.current?.focus(), 0);
9058
+ }, [link2.from, link2.to]);
9059
+ useEffect4(() => {
9060
+ const onDown = (e) => {
9061
+ if (!e.target.closest(`[${FLOAT_ATTR}]`)) {
9062
+ onClose();
9063
+ }
9064
+ };
9065
+ document.addEventListener("mousedown", onDown, true);
9066
+ return () => document.removeEventListener("mousedown", onDown, true);
9067
+ }, [onClose]);
9068
+ const startCoords = view.coordsAtPos(link2.from);
9069
+ const endCoords = view.coordsAtPos(link2.to);
9070
+ const anchorTop = Math.min(startCoords.top, endCoords.top);
9071
+ const anchorBottom = Math.max(startCoords.bottom, endCoords.bottom);
9072
+ const linkLeft = Math.min(startCoords.left, endCoords.left);
9073
+ const linkRight = Math.max(startCoords.right, endCoords.right);
9074
+ const dialogW = 280;
9075
+ const estimatedDialogH = 188;
9076
+ const centerX = (linkLeft + linkRight) / 2;
9077
+ const left = Math.max(
9078
+ VPORT_MARGIN2,
9079
+ Math.min(centerX - dialogW / 2, window.innerWidth - dialogW - VPORT_MARGIN2)
9080
+ );
9081
+ let top = anchorTop - estimatedDialogH - 12;
9082
+ if (top < VPORT_MARGIN2) {
9083
+ top = Math.min(anchorBottom + 12, window.innerHeight - estimatedDialogH - VPORT_MARGIN2);
9084
+ }
9085
+ const finalHref = normalizeLinkHref(hrefValue);
9086
+ const finalText = textValue.trim() ? textValue : finalHref;
9087
+ const canSave = Boolean(finalHref) && (finalHref !== link2.href || finalText !== link2.text);
9088
+ const currentLink = () => {
9089
+ const activeLink = getLinkAtCursor(view.state);
9090
+ if (activeLink && activeLink.from === link2.from && activeLink.to === link2.to) {
9091
+ return activeLink;
9092
+ }
9093
+ return link2;
9094
+ };
9095
+ const saveLink = () => {
9096
+ const activeLink = currentLink();
9097
+ const nextHref = normalizeLinkHref(hrefValue);
9098
+ const nextText = textValue.trim() ? textValue : nextHref;
9099
+ if (!nextHref) return;
9100
+ if (nextHref === activeLink.href && nextText === activeLink.text) return;
9101
+ const cursorOffset = Math.max(0, view.state.selection.from - activeLink.from);
9102
+ const tr = view.state.tr;
9103
+ if (nextText === activeLink.text) {
9104
+ tr.removeMark(activeLink.from, activeLink.to, linkMark);
9105
+ tr.addMark(activeLink.from, activeLink.to, linkMark.create({ href: nextHref }));
9106
+ const nextCursor = Math.min(activeLink.from + cursorOffset, activeLink.to);
9107
+ tr.setSelection(TextSelection6.near(tr.doc.resolve(nextCursor)));
9108
+ } else {
9109
+ const preservedMarks = getNonLinkMarksInRange(view.state, activeLink.from, activeLink.to);
9110
+ tr.insertText(nextText, activeLink.from, activeLink.to);
9111
+ const nextTo = activeLink.from + nextText.length;
9112
+ tr.removeMark(activeLink.from, nextTo, linkMark);
9113
+ for (const mark of preservedMarks) {
9114
+ tr.addMark(activeLink.from, nextTo, mark);
9115
+ }
9116
+ tr.addMark(activeLink.from, nextTo, linkMark.create({ href: nextHref }));
9117
+ const nextCursor = Math.min(activeLink.from + cursorOffset, nextTo);
9118
+ tr.setSelection(TextSelection6.near(tr.doc.resolve(nextCursor)));
9119
+ }
9120
+ view.dispatch(tr);
9121
+ onClose();
9122
+ view.focus();
9123
+ };
9124
+ const removeLink = () => {
9125
+ const activeLink = currentLink();
9126
+ view.dispatch(view.state.tr.removeMark(activeLink.from, activeLink.to, linkMark));
9127
+ onClose();
9128
+ view.focus();
9129
+ };
9130
+ const handleKeyDown = (e) => {
9131
+ if (e.key === "Escape") {
9132
+ e.preventDefault();
9133
+ onClose();
9134
+ view.focus();
9135
+ return;
9136
+ }
9137
+ if (e.key === "Enter") {
9138
+ e.preventDefault();
9139
+ saveLink();
9140
+ }
9141
+ };
9142
+ return createPortal2(
9143
+ /* @__PURE__ */ jsxs9(
9144
+ "div",
9145
+ {
9146
+ ...{ [FLOAT_ATTR]: "" },
9147
+ style: {
9148
+ position: "fixed",
9149
+ left,
9150
+ top,
9151
+ zIndex: 1001,
9152
+ width: dialogW,
9153
+ display: "flex",
9154
+ flexDirection: "column",
9155
+ gap: 16,
9156
+ padding: 16,
9157
+ background: "#FFFFFF",
9158
+ borderRadius: 4,
9159
+ boxShadow: "0px 8px 10px rgba(13,13,13,0.12), 0px 3px 14px rgba(13,13,13,0.08), 0px 3px 5px rgba(13,13,13,0.04)",
9160
+ boxSizing: "border-box",
9161
+ animation: "ab-float-in 0.12s ease"
9162
+ },
9163
+ children: [
9164
+ /* @__PURE__ */ jsxs9("div", { ...{ [FLOAT_ATTR]: "" }, style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
9165
+ /* @__PURE__ */ jsxs9(
9166
+ "div",
9167
+ {
9168
+ ...{ [FLOAT_ATTR]: "" },
9169
+ style: {
9170
+ fontSize: 12,
9171
+ lineHeight: "16px",
9172
+ fontWeight: 600,
9173
+ color: "#0D0D0D"
9174
+ },
9175
+ children: [
9176
+ "Text ",
9177
+ /* @__PURE__ */ jsx10("span", { style: { color: "#858585" }, children: "(optional)" })
9178
+ ]
9179
+ }
9180
+ ),
9181
+ /* @__PURE__ */ jsx10(
9182
+ "input",
9183
+ {
9184
+ ref: textInputRef,
9185
+ ...{ [FLOAT_ATTR]: "" },
9186
+ value: textValue,
9187
+ onChange: (e) => setTextValue(e.target.value),
9188
+ onKeyDown: handleKeyDown,
9189
+ style: {
9190
+ height: 32,
9191
+ border: "1px solid #CCCCCC",
9192
+ borderRadius: 4,
9193
+ padding: "0 16px",
9194
+ fontSize: 14,
9195
+ color: "#0D0D0D",
9196
+ outline: "none",
9197
+ boxSizing: "border-box"
9198
+ }
9199
+ }
9200
+ )
9201
+ ] }),
9202
+ /* @__PURE__ */ jsxs9("div", { ...{ [FLOAT_ATTR]: "" }, style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
9203
+ /* @__PURE__ */ jsx10(
9204
+ "div",
9205
+ {
9206
+ ...{ [FLOAT_ATTR]: "" },
9207
+ style: {
9208
+ fontSize: 12,
9209
+ lineHeight: "16px",
9210
+ fontWeight: 600,
9211
+ color: "#0D0D0D"
9212
+ },
9213
+ children: "Link"
9214
+ }
9215
+ ),
9216
+ /* @__PURE__ */ jsx10(
9217
+ "input",
9218
+ {
9219
+ ...{ [FLOAT_ATTR]: "" },
9220
+ value: hrefValue,
9221
+ onChange: (e) => setHrefValue(e.target.value),
9222
+ onKeyDown: handleKeyDown,
9223
+ style: {
9224
+ height: 32,
9225
+ border: "1px solid #CCCCCC",
9226
+ borderRadius: 4,
9227
+ padding: "0 16px",
9228
+ fontSize: 14,
9229
+ color: "#0D0D0D",
9230
+ outline: "none",
9231
+ boxSizing: "border-box"
9232
+ }
9233
+ }
9234
+ )
9235
+ ] }),
9236
+ /* @__PURE__ */ jsxs9(
9237
+ "div",
9238
+ {
9239
+ ...{ [FLOAT_ATTR]: "" },
9240
+ style: {
9241
+ display: "flex",
9242
+ alignItems: "center",
9243
+ justifyContent: "space-between",
9244
+ gap: 12
9245
+ },
9246
+ children: [
9247
+ /* @__PURE__ */ jsx10(
9248
+ "button",
9249
+ {
9250
+ type: "button",
9251
+ ...{ [FLOAT_ATTR]: "" },
9252
+ onMouseDown: (e) => {
9253
+ e.preventDefault();
9254
+ removeLink();
9255
+ },
9256
+ style: {
9257
+ ...BTN_RESET2,
9258
+ fontSize: 14,
9259
+ lineHeight: "20px",
9260
+ fontWeight: 600,
9261
+ color: "#0D0D0D"
9262
+ },
9263
+ children: "Remove link"
9264
+ }
9265
+ ),
9266
+ /* @__PURE__ */ jsxs9("div", { ...{ [FLOAT_ATTR]: "" }, style: { display: "flex", alignItems: "center", gap: 8 }, children: [
9267
+ /* @__PURE__ */ jsx10(
9268
+ "button",
9269
+ {
9270
+ type: "button",
9271
+ ...{ [FLOAT_ATTR]: "" },
9272
+ onMouseDown: (e) => {
9273
+ e.preventDefault();
9274
+ onClose();
9275
+ view.focus();
9276
+ },
9277
+ style: {
9278
+ ...BTN_RESET2,
9279
+ height: 32,
9280
+ padding: "0 13px",
9281
+ border: "1px solid #CCCCCC",
9282
+ borderRadius: 4,
9283
+ fontSize: 14,
9284
+ lineHeight: "20px",
9285
+ fontWeight: 600,
9286
+ color: "#0D0D0D"
9287
+ },
9288
+ children: "Cancel"
9289
+ }
9290
+ ),
9291
+ /* @__PURE__ */ jsx10(
9292
+ "button",
9293
+ {
9294
+ type: "button",
9295
+ disabled: !canSave,
9296
+ ...{ [FLOAT_ATTR]: "" },
9297
+ onMouseDown: (e) => {
9298
+ e.preventDefault();
9299
+ if (!canSave) return;
9300
+ saveLink();
9301
+ },
9302
+ style: {
9303
+ ...BTN_RESET2,
9304
+ height: 32,
9305
+ padding: "0 13px",
9306
+ borderRadius: 4,
9307
+ fontSize: 14,
9308
+ lineHeight: "20px",
9309
+ fontWeight: 600,
9310
+ background: canSave ? "#6210CC" : "#ECECEC",
9311
+ color: canSave ? "#FFFFFF" : "#A6A6A6",
9312
+ cursor: canSave ? "pointer" : "default"
9313
+ },
9314
+ children: "Save"
9315
+ }
9316
+ )
9317
+ ] })
9318
+ ]
9319
+ }
9320
+ )
9321
+ ]
9322
+ }
9323
+ ),
9324
+ document.body
9325
+ );
9326
+ }
8263
9327
  var BLOCK_ITEMS = [
8264
9328
  {
8265
9329
  shortLabel: "\xB6",
@@ -8478,6 +9542,7 @@ function BlockMenuItem({
8478
9542
  }
8479
9543
  function FloatingMenu({ view, editorState }) {
8480
9544
  const [showEmptyHandle, setShowEmptyHandle] = useState7(false);
9545
+ const [editingLink, setEditingLink] = useState7(null);
8481
9546
  const dwellTimerRef = useRef4(null);
8482
9547
  const lastEmptyPosRef = useRef4(null);
8483
9548
  const [, setScrollTick] = useState7(0);
@@ -8503,9 +9568,16 @@ function FloatingMenu({ view, editorState }) {
8503
9568
  `;
8504
9569
  document.head.appendChild(style);
8505
9570
  }, []);
9571
+ const emptyPos = editorState ? emptyBlockCursorPos(editorState) : null;
9572
+ const hasSelection = editorState ? !editorState.selection.empty : false;
9573
+ const linkAtCursor = editorState && !hasSelection ? getLinkAtCursor(editorState) : null;
9574
+ useEffect4(() => {
9575
+ if (!editingLink) return;
9576
+ if (!linkAtCursor || linkAtCursor.from !== editingLink.from || linkAtCursor.to !== editingLink.to) {
9577
+ setEditingLink(null);
9578
+ }
9579
+ }, [editingLink, linkAtCursor]);
8506
9580
  if (!view || !editorState) return null;
8507
- const emptyPos = emptyBlockCursorPos(editorState);
8508
- const hasSelection = !editorState.selection.empty;
8509
9581
  if (emptyPos !== lastEmptyPosRef.current) {
8510
9582
  lastEmptyPosRef.current = emptyPos;
8511
9583
  if (dwellTimerRef.current) {
@@ -8529,23 +9601,39 @@ function FloatingMenu({ view, editorState }) {
8529
9601
  selectionRect
8530
9602
  }
8531
9603
  ),
8532
- !hasSelection && showEmptyHandle && emptyPos !== null && /* @__PURE__ */ jsx10(EmptyParaHandle, { view, cursorPos: emptyPos })
9604
+ !hasSelection && linkAtCursor && !editingLink && /* @__PURE__ */ jsx10(
9605
+ LinkHoverTooltip,
9606
+ {
9607
+ view,
9608
+ link: linkAtCursor,
9609
+ onEdit: (link2) => setEditingLink(link2)
9610
+ }
9611
+ ),
9612
+ !hasSelection && editingLink && /* @__PURE__ */ jsx10(
9613
+ LinkEditDialog,
9614
+ {
9615
+ view,
9616
+ link: editingLink,
9617
+ onClose: () => setEditingLink(null)
9618
+ }
9619
+ ),
9620
+ !hasSelection && !linkAtCursor && showEmptyHandle && emptyPos !== null && /* @__PURE__ */ jsx10(EmptyParaHandle, { view, cursorPos: emptyPos })
8533
9621
  ] }),
8534
9622
  document.body
8535
9623
  );
8536
9624
  }
8537
9625
 
8538
9626
  // src/ui/plugin/inlineSuggestPlugin.ts
8539
- import { Plugin as Plugin8, PluginKey as PluginKey8 } from "prosemirror-state";
8540
- import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "prosemirror-view";
8541
- var inlineSuggestKey = new PluginKey8("inlineSuggest");
9627
+ import { Plugin as Plugin9, PluginKey as PluginKey9 } from "prosemirror-state";
9628
+ import { Decoration as Decoration5, DecorationSet as DecorationSet5 } from "prosemirror-view";
9629
+ var inlineSuggestKey = new PluginKey9("inlineSuggest");
8542
9630
  var DEBOUNCE_MS = 600;
8543
9631
  function createInlineSuggestPlugin(provider, endpoint, options) {
8544
9632
  const { onContextChange } = options ?? {};
8545
9633
  return {
8546
9634
  name: "inlineSuggest",
8547
9635
  plugins: () => [
8548
- new Plugin8({
9636
+ new Plugin9({
8549
9637
  key: inlineSuggestKey,
8550
9638
  state: {
8551
9639
  init: () => ({ suggestion: null, anchorPos: null }),
@@ -8561,13 +9649,13 @@ function createInlineSuggestPlugin(provider, endpoint, options) {
8561
9649
  props: {
8562
9650
  decorations(state) {
8563
9651
  const ps = inlineSuggestKey.getState(state);
8564
- if (!ps?.suggestion || !state.selection.empty) return DecorationSet4.empty;
9652
+ if (!ps?.suggestion || !state.selection.empty) return DecorationSet5.empty;
8565
9653
  const ghost = document.createElement("span");
8566
9654
  ghost.textContent = ps.suggestion;
8567
9655
  ghost.style.cssText = "color: rgba(0,0,0,0.3); pointer-events: none; user-select: none;";
8568
9656
  ghost.setAttribute("aria-hidden", "true");
8569
- return DecorationSet4.create(state.doc, [
8570
- Decoration4.widget(state.selection.from, ghost, {
9657
+ return DecorationSet5.create(state.doc, [
9658
+ Decoration5.widget(state.selection.from, ghost, {
8571
9659
  side: 1,
8572
9660
  key: "inline-suggest"
8573
9661
  })
@@ -8722,6 +9810,7 @@ export {
8722
9810
  createLinkPlugin,
8723
9811
  createMarkdownClipboardPlugin,
8724
9812
  createNoteBlockPlugin,
9813
+ createPlaceholderPlugin,
8725
9814
  createPluginArray,
8726
9815
  createReactNodeView,
8727
9816
  createSlashCommandPlugin,