@sendbird/actionbook-core 0.10.2 → 0.10.4

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
@@ -3008,7 +3008,7 @@ function createHistoryPlugin() {
3008
3008
 
3009
3009
  // src/ui/plugin/keymapPlugin.ts
3010
3010
  import { baseKeymap, chainCommands, newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock, toggleMark, setBlockType, joinBackward } from "prosemirror-commands";
3011
- import { TextSelection as TextSelection2 } from "prosemirror-state";
3011
+ import { Plugin as Plugin2, TextSelection as TextSelection2 } from "prosemirror-state";
3012
3012
  import { keymap as keymap3 } from "prosemirror-keymap";
3013
3013
  import { liftListItem, sinkListItem, splitListItem, wrapInList } from "prosemirror-schema-list";
3014
3014
 
@@ -3104,6 +3104,10 @@ function createNoteBlockPlugin() {
3104
3104
  // src/ui/plugin/keymapPlugin.ts
3105
3105
  var TAB_CHAR = " ";
3106
3106
  var { listItem, bulletList, orderedList, hardBreak, heading, paragraph, horizontalRule, blockquote } = actionbookSchema.nodes;
3107
+ function isEffectivelyEmpty(node) {
3108
+ if (node.content.size === 0) return true;
3109
+ return node.textContent.trim().length === 0 && !node.firstChild?.isAtom;
3110
+ }
3107
3111
  var { bold: boldMark, italic: italicMark, underline: underlineMark, strikethrough: strikethroughMark, code: codeMark } = actionbookSchema.marks;
3108
3112
  function cursorDirectlyInListItem(state) {
3109
3113
  const { $from } = state.selection;
@@ -3112,9 +3116,8 @@ function cursorDirectlyInListItem(state) {
3112
3116
  var backspaceAfterList = (state, dispatch) => {
3113
3117
  const { $from } = state.selection;
3114
3118
  if (!state.selection.empty) return false;
3115
- if ($from.parentOffset !== 0) return false;
3116
3119
  if ($from.parent.type !== paragraph) return false;
3117
- if ($from.parent.content.size !== 0) return false;
3120
+ if (!isEffectivelyEmpty($from.parent)) return false;
3118
3121
  const parentDepth = $from.depth - 1;
3119
3122
  if (parentDepth < 0) return false;
3120
3123
  const indexInParent = $from.index(parentDepth);
@@ -3133,9 +3136,8 @@ var backspaceAfterList = (state, dispatch) => {
3133
3136
  var backspaceAfterLeafBlock = (state, dispatch) => {
3134
3137
  const { $from } = state.selection;
3135
3138
  if (!state.selection.empty) return false;
3136
- if ($from.parentOffset !== 0) return false;
3137
3139
  if ($from.parent.type !== paragraph) return false;
3138
- if ($from.parent.content.size !== 0) return false;
3140
+ if (!isEffectivelyEmpty($from.parent)) return false;
3139
3141
  const parentDepth = $from.depth - 1;
3140
3142
  if (parentDepth < 0) return false;
3141
3143
  const indexInParent = $from.index(parentDepth);
@@ -3150,8 +3152,8 @@ var backspaceAfterLeafBlock = (state, dispatch) => {
3150
3152
  };
3151
3153
  var backspaceDeleteEmptyBlock = (state, dispatch) => {
3152
3154
  const { $from } = state.selection;
3153
- if (!state.selection.empty || $from.parentOffset !== 0) return false;
3154
- if ($from.parent.type !== paragraph || $from.parent.content.size !== 0) return false;
3155
+ if (!state.selection.empty) return false;
3156
+ if ($from.parent.type !== paragraph || !isEffectivelyEmpty($from.parent)) return false;
3155
3157
  const deletableTypes = /* @__PURE__ */ new Set(["jinjaIfBlock", "jinjaIfBranch", "noteBlock", "blockquote"]);
3156
3158
  for (let d = $from.depth - 1; d > 0; d--) {
3157
3159
  const ancestor = $from.node(d);
@@ -3391,6 +3393,18 @@ var shiftEnterCommand = (state, dispatch) => {
3391
3393
  }
3392
3394
  return true;
3393
3395
  };
3396
+ function createEmptyDocGuardPlugin() {
3397
+ return new Plugin2({
3398
+ appendTransaction(_, __, newState) {
3399
+ const { doc: doc2 } = newState;
3400
+ if (doc2.childCount !== 1) return null;
3401
+ const only = doc2.child(0);
3402
+ if (only.type === paragraph) return null;
3403
+ if (only.content.size !== 0) return null;
3404
+ return newState.tr.setNodeMarkup(0, paragraph);
3405
+ }
3406
+ });
3407
+ }
3394
3408
  function createKeymapPlugin() {
3395
3409
  return {
3396
3410
  name: "keymapPlugin",
@@ -3408,13 +3422,13 @@ function createKeymapPlugin() {
3408
3422
  "Mod-Shift-7": wrapInList(bulletList),
3409
3423
  "Mod-Shift-8": wrapInList(orderedList)
3410
3424
  }),
3411
- plugins: () => [keymap3(baseKeymap)]
3425
+ plugins: () => [keymap3(baseKeymap), createEmptyDocGuardPlugin()]
3412
3426
  };
3413
3427
  }
3414
3428
 
3415
3429
  // src/ui/plugin/markdownClipboard.ts
3416
3430
  import { DOMParser as ProseMirrorDOMParser, Fragment, Slice } from "prosemirror-model";
3417
- import { Plugin as Plugin2, PluginKey as PluginKey2 } from "prosemirror-state";
3431
+ import { Plugin as Plugin3, PluginKey as PluginKey2 } from "prosemirror-state";
3418
3432
 
3419
3433
  // src/ast/traverse.ts
3420
3434
  var MAX_DEPTH3 = 128;
@@ -5691,7 +5705,7 @@ function textToSlice(text2, view) {
5691
5705
  }
5692
5706
  var URL_RE = /^https?:\/\/[^\s]+$/i;
5693
5707
  function createPlugin() {
5694
- return new Plugin2({
5708
+ return new Plugin3({
5695
5709
  key,
5696
5710
  props: {
5697
5711
  handlePaste(view, event) {
@@ -5757,7 +5771,7 @@ function createMarkdownClipboardPlugin() {
5757
5771
  }
5758
5772
 
5759
5773
  // src/ui/plugin/jumpPointPlugin.ts
5760
- import { Plugin as Plugin3, PluginKey as PluginKey3, TextSelection as TextSelection3 } from "prosemirror-state";
5774
+ import { Plugin as Plugin4, PluginKey as PluginKey3, TextSelection as TextSelection3 } from "prosemirror-state";
5761
5775
  import { Decoration, DecorationSet } from "prosemirror-view";
5762
5776
  var adjacentKey = new PluginKey3("jumpPointAdjacent");
5763
5777
  var JUMP_POINT_ADJACENT_SPEC = { jumpPointAdjacent: true };
@@ -5778,7 +5792,7 @@ function buildDecorations(state) {
5778
5792
  var jumpPointEditKey = new PluginKey3("jumpPointEdit");
5779
5793
  var JUMP_POINT_FULL_RE = /^\^([\p{L}\p{N}_-]+)\^$/u;
5780
5794
  function createJumpPointEditPlugin() {
5781
- return new Plugin3({
5795
+ return new Plugin4({
5782
5796
  key: jumpPointEditKey,
5783
5797
  state: {
5784
5798
  init: () => ({ rawRange: null }),
@@ -5880,7 +5894,7 @@ function createJumpPointAdjacentPlugin() {
5880
5894
  }),
5881
5895
  plugins: () => [
5882
5896
  createJumpPointEditPlugin(),
5883
- new Plugin3({
5897
+ new Plugin4({
5884
5898
  key: adjacentKey,
5885
5899
  state: {
5886
5900
  init(_, state) {
@@ -6046,7 +6060,7 @@ function createJumpPointNodeViewPlugin() {
6046
6060
  }
6047
6061
 
6048
6062
  // src/ui/plugin/jumpPointValidationPlugin.ts
6049
- import { Plugin as Plugin4, PluginKey as PluginKey4 } from "prosemirror-state";
6063
+ import { Plugin as Plugin5, PluginKey as PluginKey4 } from "prosemirror-state";
6050
6064
  import { Decoration as Decoration2, DecorationSet as DecorationSet2 } from "prosemirror-view";
6051
6065
  var pluginKey = new PluginKey4("jumpPointValidation");
6052
6066
  function collectJumpPointIds(state) {
@@ -6098,7 +6112,7 @@ function createJumpPointValidationPlugin() {
6098
6112
  return {
6099
6113
  name: "jumpPointValidation",
6100
6114
  plugins: () => [
6101
- new Plugin4({
6115
+ new Plugin5({
6102
6116
  key: pluginKey,
6103
6117
  state: {
6104
6118
  init(_, state) {
@@ -6136,9 +6150,14 @@ function buildDecorations2(state) {
6136
6150
  class: "broken-anchor-ref",
6137
6151
  "data-broken-ref": refId,
6138
6152
  title: `Anchor "${refId}" no longer exists`,
6139
- style: "color: #D9352C; text-decoration-color: #D9352C;"
6153
+ style: "color: #D9352C; font-weight: 500; text-decoration: none;"
6140
6154
  })
6141
6155
  );
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 }));
6142
6161
  }
6143
6162
  if (decos.length === 0) return DecorationSet2.empty;
6144
6163
  return DecorationSet2.create(state.doc, decos);
@@ -6214,7 +6233,7 @@ function createInlineToolTagNodeViewPlugin() {
6214
6233
  }
6215
6234
 
6216
6235
  // src/ui/plugin/jinjaDecoration.ts
6217
- import { Plugin as Plugin5, PluginKey as PluginKey5 } from "prosemirror-state";
6236
+ import { Plugin as Plugin6, PluginKey as PluginKey5 } from "prosemirror-state";
6218
6237
  import { Decoration as Decoration3, DecorationSet as DecorationSet3 } from "prosemirror-view";
6219
6238
  var jinjaPluginKey = new PluginKey5("jinjaDecoration");
6220
6239
  var JINJA_TAG_RE = /\{%\s*(if|elif|else|endif)\s*([^%]*?)\s*%\}/g;
@@ -6307,7 +6326,7 @@ function createJinjaDecorationPlugin() {
6307
6326
  return {
6308
6327
  name: "jinjaDecoration",
6309
6328
  plugins: () => [
6310
- new Plugin5({
6329
+ new Plugin6({
6311
6330
  key: jinjaPluginKey,
6312
6331
  state: {
6313
6332
  init(_, state) {
@@ -6331,7 +6350,7 @@ function createJinjaDecorationPlugin() {
6331
6350
  // src/ui/plugin/jinjaIfBlockPlugin.tsx
6332
6351
  import { useEffect as useEffect2, useRef as useRef2, useState as useState3 } from "react";
6333
6352
  import { createRoot as createRoot2 } from "react-dom/client";
6334
- import { Plugin as Plugin6, TextSelection as TextSelection4 } from "prosemirror-state";
6353
+ import { Plugin as Plugin7, TextSelection as TextSelection4 } from "prosemirror-state";
6335
6354
  import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
6336
6355
  var PLACEHOLDER_TEXT = "Describe what AI agent should do when this condition is met";
6337
6356
  var CONDITION_PLACEHOLDER = "Write a condition in natural language";
@@ -6430,16 +6449,18 @@ var JINJA_STYLES = `
6430
6449
  letter-spacing: -0.3px;
6431
6450
  }
6432
6451
 
6433
- .jinja-branch-condition-invalid {
6434
- text-decoration: wavy underline #D9352C;
6435
- text-underline-offset: 3px;
6452
+ .jinja-branch-header-invalid {
6453
+ border: 1px solid #D9352C;
6454
+ border-radius: 4px;
6436
6455
  }
6437
6456
 
6438
- .jinja-condition-error-icon {
6457
+ .jinja-condition-error {
6458
+ display: flex;
6459
+ align-items: center;
6460
+ padding: 4px 0 4px 52px;
6439
6461
  color: #D9352C;
6440
- margin-left: 4px;
6441
6462
  font-size: 12px;
6442
- flex-shrink: 0;
6463
+ line-height: 16px;
6443
6464
  }
6444
6465
 
6445
6466
  .jinja-token-variable {
@@ -6827,7 +6848,8 @@ function ConditionDisplay({
6827
6848
  condition,
6828
6849
  editable = true,
6829
6850
  onConditionChange,
6830
- onBackspaceEmpty
6851
+ onBackspaceEmpty,
6852
+ onConditionBlur
6831
6853
  }) {
6832
6854
  const [isEditing, setIsEditing] = useState3(false);
6833
6855
  const [draftValue, setDraftValue] = useState3(condition);
@@ -6855,6 +6877,7 @@ function ConditionDisplay({
6855
6877
  }
6856
6878
  const commit = () => {
6857
6879
  setIsEditing(false);
6880
+ onConditionBlur?.();
6858
6881
  if (draftValue !== condition) {
6859
6882
  onConditionChange(draftValue);
6860
6883
  }
@@ -6890,14 +6913,11 @@ function ConditionDisplay({
6890
6913
  }
6891
6914
  );
6892
6915
  }
6893
- const validation = condition.length > 0 ? validateCondition(condition) : null;
6894
- const isInvalid = validation != null && !validation.valid;
6895
6916
  return /* @__PURE__ */ jsx6(
6896
6917
  "button",
6897
6918
  {
6898
6919
  type: "button",
6899
- className: `jinja-branch-condition${isInvalid ? " jinja-branch-condition-invalid" : ""}`,
6900
- title: isInvalid ? validation.error : void 0,
6920
+ className: "jinja-branch-condition",
6901
6921
  onMouseDown: (event) => event.preventDefault(),
6902
6922
  onClick: () => {
6903
6923
  if (editable) {
@@ -6905,10 +6925,7 @@ function ConditionDisplay({
6905
6925
  setIsEditing(true);
6906
6926
  }
6907
6927
  },
6908
- children: condition.length > 0 ? /* @__PURE__ */ jsxs5(Fragment2, { children: [
6909
- renderCondition(condition),
6910
- isInvalid && /* @__PURE__ */ jsx6("span", { className: "jinja-condition-error-icon", children: "\u26A0" })
6911
- ] }) : /* @__PURE__ */ jsx6("span", { className: "jinja-condition-placeholder", children: CONDITION_PLACEHOLDER })
6928
+ children: condition.length > 0 ? renderCondition(condition) : /* @__PURE__ */ jsx6("span", { className: "jinja-condition-placeholder", children: CONDITION_PLACEHOLDER })
6912
6929
  }
6913
6930
  );
6914
6931
  }
@@ -7002,8 +7019,17 @@ function JinjaBranchHeader({
7002
7019
  }
7003
7020
  const canOpenFooterMenu = editable && isLastBranch && branchType !== "else";
7004
7021
  const isMenuOpen = (source) => menuSource === source && menuItems.length > 0;
7022
+ const [conditionTouched, setConditionTouched] = useState3(false);
7023
+ const conditionError = (() => {
7024
+ if (branchType === "else") return null;
7025
+ if (condition.length === 0) {
7026
+ return conditionTouched ? "This field is required." : null;
7027
+ }
7028
+ const result = validateCondition(condition);
7029
+ return result.valid ? null : result.error || "Invalid condition";
7030
+ })();
7005
7031
  return /* @__PURE__ */ jsxs5(Fragment2, { children: [
7006
- /* @__PURE__ */ jsxs5("div", { className: "jinja-branch-header", children: [
7032
+ /* @__PURE__ */ jsxs5("div", { className: `jinja-branch-header${conditionError ? " jinja-branch-header-invalid" : ""}`, children: [
7007
7033
  /* @__PURE__ */ jsx6("span", { className: `jinja-branch-badge jinja-branch-badge-${branchType}`, children: branchType === "elif" ? "ELSE IF" : branchType.toUpperCase() }),
7008
7034
  /* @__PURE__ */ jsx6(
7009
7035
  ConditionDisplay,
@@ -7012,7 +7038,8 @@ function JinjaBranchHeader({
7012
7038
  condition,
7013
7039
  editable,
7014
7040
  onConditionChange,
7015
- onBackspaceEmpty: onDelete
7041
+ onBackspaceEmpty: onDelete,
7042
+ onConditionBlur: () => setConditionTouched(true)
7016
7043
  }
7017
7044
  ),
7018
7045
  editable ? /* @__PURE__ */ jsxs5("div", { className: "jinja-branch-actions", ref: kebabRef, children: [
@@ -7050,6 +7077,7 @@ function JinjaBranchHeader({
7050
7077
  ) : null
7051
7078
  ] }) : null
7052
7079
  ] }),
7080
+ conditionError && /* @__PURE__ */ jsx6("div", { className: "jinja-condition-error", children: conditionError }),
7053
7081
  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: [
7054
7082
  /* @__PURE__ */ jsx6(
7055
7083
  "button",
@@ -7243,7 +7271,7 @@ var JinjaIfBranchView = class {
7243
7271
  };
7244
7272
  function createEditableWatcherPlugin() {
7245
7273
  let lastEditable = null;
7246
- return new Plugin6({
7274
+ return new Plugin7({
7247
7275
  view(editorView) {
7248
7276
  lastEditable = editorView.editable;
7249
7277
  return {
@@ -7274,7 +7302,7 @@ function createJinjaIfBlockPlugin() {
7274
7302
 
7275
7303
  // src/ui/plugin/linkPlugin.ts
7276
7304
  import { InputRule as InputRule2, inputRules as inputRules2 } from "prosemirror-inputrules";
7277
- import { Plugin as Plugin7, PluginKey as PluginKey6, TextSelection as TextSelection5 } from "prosemirror-state";
7305
+ import { Plugin as Plugin8, PluginKey as PluginKey6, TextSelection as TextSelection5 } from "prosemirror-state";
7278
7306
  var LINK_INPUT_RE = /\\?\[([^\]]*?)\\?\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
7279
7307
  var LINK_FULL_RE = /^\\?\[([^\]]*?)\\?\]\(([^)\s]*?)(?:\s+"([^"]*)")?\)$/;
7280
7308
  var linkEditKey = new PluginKey6("linkEdit");
@@ -7325,7 +7353,7 @@ function explodeLinkToRaw(state) {
7325
7353
  return tr;
7326
7354
  }
7327
7355
  function createLinkEditPlugin() {
7328
- return new Plugin7({
7356
+ return new Plugin8({
7329
7357
  key: linkEditKey,
7330
7358
  state: {
7331
7359
  init: () => ({ rawRange: null }),
@@ -7359,17 +7387,17 @@ function createLinkEditPlugin() {
7359
7387
  if (match) {
7360
7388
  const [, label, href, title] = match;
7361
7389
  const linkMarkType = newState.schema.marks.link;
7362
- if (linkMarkType && href) {
7390
+ if (!label) {
7391
+ reconvertTr.delete(safeFrom, safeTo);
7392
+ } else if (linkMarkType && href) {
7363
7393
  const attrs = title ? { href, title: title.trim() } : { href };
7364
7394
  reconvertTr.delete(safeFrom, safeTo);
7365
- if (label.length > 0) {
7366
- reconvertTr.insertText(label, safeFrom);
7367
- reconvertTr.addMark(
7368
- safeFrom,
7369
- safeFrom + label.length,
7370
- linkMarkType.create(attrs)
7371
- );
7372
- }
7395
+ reconvertTr.insertText(label, safeFrom);
7396
+ reconvertTr.addMark(
7397
+ safeFrom,
7398
+ safeFrom + label.length,
7399
+ linkMarkType.create(attrs)
7400
+ );
7373
7401
  }
7374
7402
  }
7375
7403
  }
@@ -7383,19 +7411,24 @@ function createLinkInputRule() {
7383
7411
  (state, match, start, end) => {
7384
7412
  const [, label, href, title] = match;
7385
7413
  const linkMarkType = state.schema.marks.link;
7386
- if (!linkMarkType || !href) return null;
7387
- const displayText = label || href;
7388
- const attrs = title ? { href, title: title.trim() } : { href };
7414
+ if (!linkMarkType) return null;
7389
7415
  const { tr } = state;
7390
- tr.replaceWith(start, end, state.schema.text(displayText));
7391
- tr.addMark(start, start + displayText.length, linkMarkType.create(attrs));
7416
+ if (!label) {
7417
+ tr.delete(start, end);
7418
+ tr.setMeta(linkEditKey, null);
7419
+ return tr;
7420
+ }
7421
+ if (!href) return null;
7422
+ const attrs = title ? { href, title: title.trim() } : { href };
7423
+ tr.replaceWith(start, end, state.schema.text(label));
7424
+ tr.addMark(start, start + label.length, linkMarkType.create(attrs));
7392
7425
  tr.setMeta(linkEditKey, null);
7393
7426
  return tr;
7394
7427
  }
7395
7428
  );
7396
7429
  }
7397
7430
  function createLinkClickPlugin() {
7398
- return new Plugin7({
7431
+ return new Plugin8({
7399
7432
  key: new PluginKey6("linkClick"),
7400
7433
  props: {
7401
7434
  handleDOMEvents: {
@@ -7448,7 +7481,7 @@ function createLinkPlugin() {
7448
7481
  }
7449
7482
 
7450
7483
  // src/ui/plugin/dragHandlePlugin.ts
7451
- import { Plugin as Plugin8, PluginKey as PluginKey7 } from "prosemirror-state";
7484
+ import { Plugin as Plugin9, PluginKey as PluginKey7 } from "prosemirror-state";
7452
7485
  var PLUGIN_KEY = new PluginKey7("dragHandle");
7453
7486
  var GRIP_SVG = `<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
7454
7487
  <circle cx="4" cy="2.5" r="1.2"/><circle cx="8" cy="2.5" r="1.2"/>
@@ -8256,7 +8289,7 @@ function createDragHandlePlugin() {
8256
8289
  plugins: () => {
8257
8290
  let controller = null;
8258
8291
  return [
8259
- new Plugin8({
8292
+ new Plugin9({
8260
8293
  key: PLUGIN_KEY,
8261
8294
  view(editorView) {
8262
8295
  controller = new DragHandleController(editorView);
@@ -8379,9 +8412,8 @@ function createTodoNodeViewPlugin() {
8379
8412
  }
8380
8413
 
8381
8414
  // src/ui/plugin/placeholderPlugin.ts
8382
- import { Plugin as Plugin9, PluginKey as PluginKey8 } from "prosemirror-state";
8415
+ import { Plugin as Plugin10, PluginKey as PluginKey8 } from "prosemirror-state";
8383
8416
  import { Decoration as Decoration4, DecorationSet as DecorationSet4 } from "prosemirror-view";
8384
- var PLACEHOLDER_TEXT2 = "Type to start writing, or press / to insert an action.";
8385
8417
  var pluginKey2 = new PluginKey8("placeholder");
8386
8418
  function buildDecorations4(state) {
8387
8419
  const { doc: doc2, selection } = state;
@@ -8399,17 +8431,25 @@ function buildDecorations4(state) {
8399
8431
  }
8400
8432
  const pos = $from.before($from.depth);
8401
8433
  const deco = Decoration4.node(pos, pos + parent.nodeSize, {
8402
- class: "ab-empty-paragraph",
8403
- "data-placeholder": PLACEHOLDER_TEXT2
8434
+ class: "ab-empty-paragraph"
8404
8435
  });
8405
8436
  return DecorationSet4.create(doc2, [deco]);
8406
8437
  }
8407
8438
  function createPlaceholderPlugin() {
8439
+ let isEditable = true;
8408
8440
  return {
8409
8441
  name: "placeholder",
8410
8442
  plugins: () => [
8411
- new Plugin9({
8443
+ new Plugin10({
8412
8444
  key: pluginKey2,
8445
+ view(editorView) {
8446
+ isEditable = editorView.editable;
8447
+ return {
8448
+ update(view) {
8449
+ isEditable = view.editable;
8450
+ }
8451
+ };
8452
+ },
8413
8453
  state: {
8414
8454
  init(_, state) {
8415
8455
  return buildDecorations4(state);
@@ -8423,6 +8463,7 @@ function createPlaceholderPlugin() {
8423
8463
  },
8424
8464
  props: {
8425
8465
  decorations(state) {
8466
+ if (!isEditable) return DecorationSet4.empty;
8426
8467
  return pluginKey2.getState(state) ?? DecorationSet4.empty;
8427
8468
  }
8428
8469
  }
@@ -8455,14 +8496,12 @@ function injectScrollbarStyle() {
8455
8496
  const style = document.createElement("style");
8456
8497
  style.id = SCROLLBAR_STYLE_ID;
8457
8498
  style.textContent = `
8458
- .ab-slash-menu::-webkit-scrollbar { width: 6px; }
8459
- .ab-slash-menu::-webkit-scrollbar-track { background: transparent; }
8460
- .ab-slash-menu::-webkit-scrollbar-thumb { background: #c4c4c4; border-radius: 3px; }
8461
- .ab-slash-menu::-webkit-scrollbar-thumb:hover { background: #999; }
8499
+ .ab-slash-menu::-webkit-scrollbar { display: none; }
8500
+ .ab-slash-menu { scrollbar-width: none; }
8462
8501
  `;
8463
8502
  document.head.appendChild(style);
8464
8503
  }
8465
- var POPUP_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)";
8504
+ var POPUP_SHADOW = "0 0 0 1px #CCCCCC, 0 4px 16px rgba(0, 0, 0, 0.12)";
8466
8505
  var BTN_RESET = {
8467
8506
  border: "none",
8468
8507
  padding: 0,
@@ -8544,12 +8583,10 @@ function SlashCommandMenu({ view, editorState, items }) {
8544
8583
  width: MENU_WIDTH,
8545
8584
  maxHeight: MAX_MENU_H,
8546
8585
  overflowY: "auto",
8547
- scrollbarWidth: "thin",
8548
- scrollbarColor: "#c4c4c4 transparent",
8549
8586
  background: "#fff",
8550
- borderRadius: 4,
8587
+ borderRadius: 8,
8551
8588
  boxShadow: POPUP_SHADOW,
8552
- padding: "8px 0",
8589
+ padding: "4px",
8553
8590
  zIndex: 1100,
8554
8591
  animation: "ab-float-in 0.12s ease"
8555
8592
  },
@@ -8615,8 +8652,8 @@ function SlashMenuItem({ item, index, selected, onMouseEnter, onMouseDown }) {
8615
8652
  display: "flex",
8616
8653
  alignItems: "center",
8617
8654
  gap: 12,
8618
- padding: "6px 16px",
8619
- background: selected ? "rgba(13,13,13,0.04)" : "transparent",
8655
+ padding: "6px 8px",
8656
+ background: selected ? "rgba(0, 0, 0, 0.04)" : "transparent",
8620
8657
  transition: "background 0.08s"
8621
8658
  },
8622
8659
  onMouseEnter,
@@ -9037,7 +9074,7 @@ function getSelectionDOMRect() {
9037
9074
  if (rect.width === 0 && rect.height === 0) return null;
9038
9075
  return rect;
9039
9076
  }
9040
- var POPUP_SHADOW2 = "0 0 0 1px rgba(0,0,0,0.07), 0 4px 20px rgba(0,0,0,0.11), 0 1px 4px rgba(0,0,0,0.05)";
9077
+ var POPUP_SHADOW2 = "0 0 0 1px #CCCCCC, 0 4px 16px rgba(0, 0, 0, 0.12)";
9041
9078
  var BTN_RESET2 = {
9042
9079
  border: "none",
9043
9080
  padding: 0,
@@ -9069,8 +9106,8 @@ function TBtn({ children, active, title, onMouseDown, style }) {
9069
9106
  borderRadius: 6,
9070
9107
  fontSize: 13,
9071
9108
  fontWeight: 600,
9072
- color: active ? "#6366f1" : "#3f3f46",
9073
- background: active ? "rgba(99,102,241,0.1)" : hover ? "rgba(0,0,0,0.05)" : "transparent",
9109
+ color: active ? "#6210CC" : "#0D0D0D",
9110
+ background: active ? "rgba(98,16,204,0.08)" : hover ? "rgba(0,0,0,0.05)" : "transparent",
9074
9111
  transition: "background 0.1s, color 0.1s",
9075
9112
  ...style
9076
9113
  },
@@ -9164,8 +9201,8 @@ function BlockTypeDropdown({ view, state, label }) {
9164
9201
  borderRadius: 6,
9165
9202
  fontSize: 11,
9166
9203
  fontWeight: 600,
9167
- color: open ? "#6366f1" : "#3f3f46",
9168
- background: open ? "rgba(99,102,241,0.1)" : hover ? "rgba(0,0,0,0.05)" : "transparent",
9204
+ color: open ? "#6210CC" : "#0D0D0D",
9205
+ background: open ? "rgba(98,16,204,0.08)" : hover ? "rgba(0,0,0,0.04)" : "transparent",
9169
9206
  transition: "background 0.1s, color 0.1s",
9170
9207
  whiteSpace: "nowrap"
9171
9208
  },
@@ -9186,9 +9223,9 @@ function BlockTypeDropdown({ view, state, label }) {
9186
9223
  left: btnRect.left,
9187
9224
  top: btnRect.bottom + 4,
9188
9225
  background: "#fff",
9189
- borderRadius: 10,
9226
+ borderRadius: 8,
9190
9227
  boxShadow: POPUP_SHADOW2,
9191
- padding: 6,
9228
+ padding: 4,
9192
9229
  minWidth: 168,
9193
9230
  animation: "ab-float-in 0.12s ease"
9194
9231
  },
@@ -9219,16 +9256,16 @@ function DropdownItem({ item, onRun }) {
9219
9256
  gap: 10,
9220
9257
  width: "100%",
9221
9258
  padding: "6px 8px",
9222
- borderRadius: 6,
9259
+ borderRadius: 4,
9223
9260
  textAlign: "left",
9224
9261
  fontSize: 13,
9225
- color: item.active ? "#6366f1" : "#3f3f46",
9262
+ color: item.active ? "#6210CC" : "#0D0D0D",
9226
9263
  fontWeight: item.active ? 600 : 400,
9227
9264
  background: hover ? "rgba(0,0,0,0.04)" : "transparent",
9228
9265
  transition: "background 0.1s"
9229
9266
  },
9230
9267
  children: [
9231
- /* @__PURE__ */ jsx10("span", { style: { width: 24, flexShrink: 0, textAlign: "center", fontWeight: 700, fontSize: 12, fontFamily: "monospace", color: "#6366f1" }, children: item.shortLabel }),
9268
+ /* @__PURE__ */ jsx10("span", { style: { width: 24, flexShrink: 0, textAlign: "center", fontWeight: 700, fontSize: 12, fontFamily: "monospace", color: "#6210CC" }, children: item.shortLabel }),
9232
9269
  item.label
9233
9270
  ]
9234
9271
  }
@@ -9305,7 +9342,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
9305
9342
  gap: 2,
9306
9343
  padding: "0 6px",
9307
9344
  background: "#fff",
9308
- borderRadius: 10,
9345
+ borderRadius: 8,
9309
9346
  boxShadow: POPUP_SHADOW2,
9310
9347
  userSelect: "none",
9311
9348
  animation: "ab-float-in 0.12s ease"
@@ -9353,7 +9390,7 @@ function SelectionToolbar({ view, state, selectionRect }) {
9353
9390
  e.preventDefault();
9354
9391
  applyLink();
9355
9392
  },
9356
- style: { color: "#6366f1", width: 24, fontSize: 14 },
9393
+ style: { color: "#6210CC", width: 24, fontSize: 14 },
9357
9394
  children: "\u21B5"
9358
9395
  }
9359
9396
  ),
@@ -9602,8 +9639,8 @@ function LinkEditDialog({ view, link: link2, onClose }) {
9602
9639
  top = Math.min(anchorBottom + 12, window.innerHeight - estimatedDialogH - VPORT_MARGIN2);
9603
9640
  }
9604
9641
  const finalHref = normalizeLinkHref(hrefValue);
9605
- const finalText = textValue.trim() ? textValue : finalHref;
9606
- const canSave = Boolean(finalHref) && (finalHref !== link2.href || finalText !== link2.text);
9642
+ const finalText = textValue.trim() ? textValue : "";
9643
+ const canSave = !textValue.trim() || Boolean(finalHref) && (finalHref !== link2.href || finalText !== link2.text);
9607
9644
  const currentLink = () => {
9608
9645
  const activeLink = getLinkAtCursor(view.state);
9609
9646
  if (activeLink && activeLink.from === link2.from && activeLink.to === link2.to) {
@@ -9614,7 +9651,14 @@ function LinkEditDialog({ view, link: link2, onClose }) {
9614
9651
  const saveLink = () => {
9615
9652
  const activeLink = currentLink();
9616
9653
  const nextHref = normalizeLinkHref(hrefValue);
9617
- const nextText = textValue.trim() ? textValue : nextHref;
9654
+ if (!textValue.trim()) {
9655
+ const tr2 = view.state.tr.delete(activeLink.from, activeLink.to);
9656
+ view.dispatch(tr2);
9657
+ onClose();
9658
+ view.focus();
9659
+ return;
9660
+ }
9661
+ const nextText = textValue;
9618
9662
  if (!nextHref) return;
9619
9663
  if (nextHref === activeLink.href && nextText === activeLink.text) return;
9620
9664
  const cursorOffset = Math.max(0, view.state.selection.from - activeLink.from);
@@ -9674,8 +9718,8 @@ function LinkEditDialog({ view, link: link2, onClose }) {
9674
9718
  gap: 16,
9675
9719
  padding: 16,
9676
9720
  background: "#FFFFFF",
9677
- borderRadius: 4,
9678
- 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)",
9721
+ borderRadius: 8,
9722
+ boxShadow: "0 0 0 1px #CCCCCC, 0 4px 16px rgba(0, 0, 0, 0.12)",
9679
9723
  boxSizing: "border-box",
9680
9724
  animation: "ab-float-in 0.12s ease"
9681
9725
  },
@@ -9843,6 +9887,189 @@ function LinkEditDialog({ view, link: link2, onClose }) {
9843
9887
  document.body
9844
9888
  );
9845
9889
  }
9890
+ var INSERT_LINK_EVENT = "ab-insert-link";
9891
+ function LinkInsertDialog({ view, cursorPos, onClose }) {
9892
+ const [textValue, setTextValue] = useState7("");
9893
+ const [hrefValue, setHrefValue] = useState7("");
9894
+ const textInputRef = useRef4(null);
9895
+ useEffect4(() => {
9896
+ setTimeout(() => textInputRef.current?.focus(), 0);
9897
+ }, []);
9898
+ useEffect4(() => {
9899
+ const onDown = (e) => {
9900
+ if (!e.target.closest(`[${FLOAT_ATTR}]`)) {
9901
+ onClose();
9902
+ }
9903
+ };
9904
+ document.addEventListener("mousedown", onDown, true);
9905
+ return () => document.removeEventListener("mousedown", onDown, true);
9906
+ }, [onClose]);
9907
+ let coords;
9908
+ try {
9909
+ coords = view.coordsAtPos(cursorPos);
9910
+ } catch {
9911
+ onClose();
9912
+ return null;
9913
+ }
9914
+ const dialogW = 280;
9915
+ const left = Math.max(
9916
+ VPORT_MARGIN2,
9917
+ Math.min(coords.left - dialogW / 2, window.innerWidth - dialogW - VPORT_MARGIN2)
9918
+ );
9919
+ const top = Math.min(coords.bottom + 8, window.innerHeight - 220 - VPORT_MARGIN2);
9920
+ const finalHref = normalizeLinkHref(hrefValue);
9921
+ const canSave = Boolean(finalHref);
9922
+ const saveLink = () => {
9923
+ if (!finalHref) return;
9924
+ const displayText = textValue.trim() || finalHref;
9925
+ const tr = view.state.tr;
9926
+ const linkMarkType = view.state.schema.marks.link;
9927
+ if (!linkMarkType) return;
9928
+ tr.insertText(displayText, cursorPos);
9929
+ tr.addMark(cursorPos, cursorPos + displayText.length, linkMarkType.create({ href: finalHref }));
9930
+ view.dispatch(tr);
9931
+ onClose();
9932
+ view.focus();
9933
+ };
9934
+ const handleKeyDown = (e) => {
9935
+ if (e.key === "Escape") {
9936
+ e.preventDefault();
9937
+ onClose();
9938
+ view.focus();
9939
+ }
9940
+ if (e.key === "Enter") {
9941
+ e.preventDefault();
9942
+ saveLink();
9943
+ }
9944
+ };
9945
+ return createPortal2(
9946
+ /* @__PURE__ */ jsxs9(
9947
+ "div",
9948
+ {
9949
+ ...{ [FLOAT_ATTR]: "" },
9950
+ style: {
9951
+ position: "fixed",
9952
+ left,
9953
+ top,
9954
+ zIndex: 1001,
9955
+ width: dialogW,
9956
+ display: "flex",
9957
+ flexDirection: "column",
9958
+ gap: 16,
9959
+ padding: 16,
9960
+ background: "#FFFFFF",
9961
+ borderRadius: 8,
9962
+ boxShadow: POPUP_SHADOW2,
9963
+ boxSizing: "border-box",
9964
+ animation: "ab-float-in 0.12s ease"
9965
+ },
9966
+ children: [
9967
+ /* @__PURE__ */ jsxs9("div", { ...{ [FLOAT_ATTR]: "" }, style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
9968
+ /* @__PURE__ */ jsxs9("div", { ...{ [FLOAT_ATTR]: "" }, style: { fontSize: 12, lineHeight: "16px", fontWeight: 600, color: "#0D0D0D" }, children: [
9969
+ "Text ",
9970
+ /* @__PURE__ */ jsx10("span", { style: { color: "#858585" }, children: "(optional)" })
9971
+ ] }),
9972
+ /* @__PURE__ */ jsx10(
9973
+ "input",
9974
+ {
9975
+ ref: textInputRef,
9976
+ ...{ [FLOAT_ATTR]: "" },
9977
+ value: textValue,
9978
+ placeholder: "",
9979
+ onChange: (e) => setTextValue(e.target.value),
9980
+ onKeyDown: handleKeyDown,
9981
+ style: {
9982
+ height: 32,
9983
+ border: "1px solid #CCCCCC",
9984
+ borderRadius: 4,
9985
+ padding: "0 16px",
9986
+ fontSize: 14,
9987
+ color: "#0D0D0D",
9988
+ outline: "none",
9989
+ boxSizing: "border-box"
9990
+ }
9991
+ }
9992
+ )
9993
+ ] }),
9994
+ /* @__PURE__ */ jsxs9("div", { ...{ [FLOAT_ATTR]: "" }, style: { display: "flex", flexDirection: "column", gap: 6 }, children: [
9995
+ /* @__PURE__ */ jsx10("div", { ...{ [FLOAT_ATTR]: "" }, style: { fontSize: 12, lineHeight: "16px", fontWeight: 600, color: "#0D0D0D" }, children: "Link" }),
9996
+ /* @__PURE__ */ jsx10(
9997
+ "input",
9998
+ {
9999
+ ...{ [FLOAT_ATTR]: "" },
10000
+ value: hrefValue,
10001
+ onChange: (e) => setHrefValue(e.target.value),
10002
+ onKeyDown: handleKeyDown,
10003
+ style: {
10004
+ height: 32,
10005
+ border: "1px solid #CCCCCC",
10006
+ borderRadius: 4,
10007
+ padding: "0 16px",
10008
+ fontSize: 14,
10009
+ color: "#0D0D0D",
10010
+ outline: "none",
10011
+ boxSizing: "border-box"
10012
+ }
10013
+ }
10014
+ )
10015
+ ] }),
10016
+ /* @__PURE__ */ jsxs9("div", { ...{ [FLOAT_ATTR]: "" }, style: { display: "flex", alignItems: "center", justifyContent: "flex-end", gap: 8 }, children: [
10017
+ /* @__PURE__ */ jsx10(
10018
+ "button",
10019
+ {
10020
+ type: "button",
10021
+ ...{ [FLOAT_ATTR]: "" },
10022
+ onMouseDown: (e) => {
10023
+ e.preventDefault();
10024
+ onClose();
10025
+ view.focus();
10026
+ },
10027
+ style: {
10028
+ ...BTN_RESET2,
10029
+ height: 32,
10030
+ padding: "0 13px",
10031
+ border: "1px solid #CCCCCC",
10032
+ borderRadius: 4,
10033
+ fontSize: 14,
10034
+ lineHeight: "20px",
10035
+ fontWeight: 600,
10036
+ color: "#0D0D0D"
10037
+ },
10038
+ children: "Cancel"
10039
+ }
10040
+ ),
10041
+ /* @__PURE__ */ jsx10(
10042
+ "button",
10043
+ {
10044
+ type: "button",
10045
+ disabled: !canSave,
10046
+ ...{ [FLOAT_ATTR]: "" },
10047
+ onMouseDown: (e) => {
10048
+ e.preventDefault();
10049
+ if (canSave) saveLink();
10050
+ },
10051
+ style: {
10052
+ ...BTN_RESET2,
10053
+ height: 32,
10054
+ padding: "0 13px",
10055
+ borderRadius: 4,
10056
+ fontSize: 14,
10057
+ lineHeight: "20px",
10058
+ fontWeight: 600,
10059
+ background: canSave ? "#6210CC" : "#ECECEC",
10060
+ color: canSave ? "#FFFFFF" : "#A6A6A6",
10061
+ cursor: canSave ? "pointer" : "default"
10062
+ },
10063
+ children: "Save"
10064
+ }
10065
+ )
10066
+ ] })
10067
+ ]
10068
+ }
10069
+ ),
10070
+ document.body
10071
+ );
10072
+ }
9846
10073
  var BLOCK_ITEMS = [
9847
10074
  {
9848
10075
  shortLabel: "\xB6",
@@ -9965,12 +10192,12 @@ function EmptyParaHandle({ view, cursorPos }) {
9965
10192
  height: btnSize,
9966
10193
  border: "1px solid rgba(0,0,0,0.1)",
9967
10194
  borderRadius: 5,
9968
- background: menuOpen ? "rgba(99,102,241,0.08)" : "#fff",
10195
+ background: menuOpen ? "rgba(98,16,204,0.08)" : "#fff",
9969
10196
  boxShadow: "0 1px 3px rgba(0,0,0,0.06)",
9970
10197
  display: "flex",
9971
10198
  alignItems: "center",
9972
10199
  justifyContent: "center",
9973
- color: menuOpen ? "#6366f1" : "#9ca3af",
10200
+ color: menuOpen ? "#6210CC" : "#9ca3af",
9974
10201
  fontSize: 16,
9975
10202
  animation: "ab-float-in 0.15s ease",
9976
10203
  transition: "color 0.1s, background 0.1s, border-color 0.1s"
@@ -9988,9 +10215,9 @@ function EmptyParaHandle({ view, cursorPos }) {
9988
10215
  left: menuLeft,
9989
10216
  top: menuTop,
9990
10217
  background: "#fff",
9991
- borderRadius: 10,
10218
+ borderRadius: 8,
9992
10219
  boxShadow: POPUP_SHADOW2,
9993
- padding: 6,
10220
+ padding: 4,
9994
10221
  minWidth: 168,
9995
10222
  animation: "ab-float-in 0.12s ease"
9996
10223
  },
@@ -10034,7 +10261,7 @@ function BlockMenuItem({
10034
10261
  borderRadius: 6,
10035
10262
  textAlign: "left",
10036
10263
  fontSize: 13,
10037
- color: "#3f3f46",
10264
+ color: "#0D0D0D",
10038
10265
  background: hover ? "rgba(0,0,0,0.04)" : "transparent",
10039
10266
  transition: "background 0.1s"
10040
10267
  },
@@ -10049,7 +10276,7 @@ function BlockMenuItem({
10049
10276
  fontWeight: 700,
10050
10277
  fontSize: 12,
10051
10278
  fontFamily: "monospace",
10052
- color: "#6366f1"
10279
+ color: "#6210CC"
10053
10280
  },
10054
10281
  children: item.shortLabel
10055
10282
  }
@@ -10062,8 +10289,19 @@ function BlockMenuItem({
10062
10289
  function FloatingMenu({ view, editorState }) {
10063
10290
  const [showEmptyHandle, setShowEmptyHandle] = useState7(false);
10064
10291
  const [editingLink, setEditingLink] = useState7(null);
10292
+ const [insertLinkPos, setInsertLinkPos] = useState7(null);
10065
10293
  const dwellTimerRef = useRef4(null);
10066
10294
  const lastEmptyPosRef = useRef4(null);
10295
+ useEffect4(() => {
10296
+ const dom = view?.dom;
10297
+ if (!dom) return;
10298
+ const handler = () => {
10299
+ const pos = view.state.selection.from;
10300
+ setInsertLinkPos(pos);
10301
+ };
10302
+ dom.addEventListener(INSERT_LINK_EVENT, handler);
10303
+ return () => dom.removeEventListener(INSERT_LINK_EVENT, handler);
10304
+ }, [view]);
10067
10305
  const [, setScrollTick] = useState7(0);
10068
10306
  useEffect4(() => {
10069
10307
  const editorDom = view?.dom;
@@ -10136,14 +10374,22 @@ function FloatingMenu({ view, editorState }) {
10136
10374
  onClose: () => setEditingLink(null)
10137
10375
  }
10138
10376
  ),
10139
- !hasSelection && !linkAtCursor && showEmptyHandle && emptyPos !== null && /* @__PURE__ */ jsx10(EmptyParaHandle, { view, cursorPos: emptyPos })
10377
+ insertLinkPos !== null && /* @__PURE__ */ jsx10(
10378
+ LinkInsertDialog,
10379
+ {
10380
+ view,
10381
+ cursorPos: insertLinkPos,
10382
+ onClose: () => setInsertLinkPos(null)
10383
+ }
10384
+ ),
10385
+ !hasSelection && !linkAtCursor && !insertLinkPos && showEmptyHandle && emptyPos !== null && /* @__PURE__ */ jsx10(EmptyParaHandle, { view, cursorPos: emptyPos })
10140
10386
  ] }),
10141
10387
  document.body
10142
10388
  );
10143
10389
  }
10144
10390
 
10145
10391
  // src/ui/plugin/inlineSuggestPlugin.ts
10146
- import { Plugin as Plugin10, PluginKey as PluginKey9 } from "prosemirror-state";
10392
+ import { Plugin as Plugin11, PluginKey as PluginKey9 } from "prosemirror-state";
10147
10393
  import { Decoration as Decoration5, DecorationSet as DecorationSet5 } from "prosemirror-view";
10148
10394
  var inlineSuggestKey = new PluginKey9("inlineSuggest");
10149
10395
  var DEBOUNCE_MS = 600;
@@ -10152,7 +10398,7 @@ function createInlineSuggestPlugin(provider, endpoint, options) {
10152
10398
  return {
10153
10399
  name: "inlineSuggest",
10154
10400
  plugins: () => [
10155
- new Plugin10({
10401
+ new Plugin11({
10156
10402
  key: inlineSuggestKey,
10157
10403
  state: {
10158
10404
  init: () => ({ suggestion: null, anchorPos: null }),
@@ -10324,6 +10570,7 @@ export {
10324
10570
  DocumentTreeView,
10325
10571
  EditorShell,
10326
10572
  FloatingMenu,
10573
+ INSERT_LINK_EVENT,
10327
10574
  JUMP_POINT_ADJACENT_SPEC,
10328
10575
  JinjaTreeView,
10329
10576
  SlashCommandMenu,