composeai 0.1.4 → 0.1.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/index.cjs CHANGED
@@ -395,6 +395,13 @@ var IconAttach = makeIcon(
395
395
  /* @__PURE__ */ jsxRuntime.jsx("path", { d: "m16 6-8.414 8.586a2 2 0 0 0 0 2.828 2 2 0 0 0 2.828 0l8.414-8.586a4 4 0 0 0 0-5.656 4 4 0 0 0-5.656 0l-8.415 8.585a6 6 0 1 0 8.486 8.486" })
396
396
  ] })
397
397
  );
398
+ var IconPlus = makeIcon(
399
+ "IconPlus",
400
+ /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
401
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M12 5v14" }),
402
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M5 12h14" })
403
+ ] })
404
+ );
398
405
  var IconImage = makeIcon(
399
406
  "IconImage",
400
407
  /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
@@ -483,6 +490,7 @@ var DEFAULT_ICONS = {
483
490
  send: IconSend,
484
491
  stop: IconStop,
485
492
  attach: IconAttach,
493
+ plus: IconPlus,
486
494
  image: IconImage,
487
495
  voice: IconVoice,
488
496
  voiceRecording: IconVoiceRecording,
@@ -508,6 +516,8 @@ var DEFAULT_FEATURES = {
508
516
  voice: true,
509
517
  mermaid: true,
510
518
  web: true,
519
+ // No consumer-defined actions by default.
520
+ custom: [],
511
521
  // Off by default — ghost autocomplete is an opt-in input affordance that
512
522
  // only makes sense when the consumer has a curated list of completions.
513
523
  ghostedAutoComplete: false
@@ -539,6 +549,7 @@ function normalizeFeatures(features) {
539
549
  voice: features.voice ?? DEFAULT_FEATURES.voice,
540
550
  mermaid: features.mermaid ?? DEFAULT_FEATURES.mermaid,
541
551
  web: features.web ?? DEFAULT_FEATURES.web,
552
+ custom: features.custom ?? DEFAULT_FEATURES.custom,
542
553
  ghostedAutoComplete: features.ghostedAutoComplete ?? DEFAULT_FEATURES.ghostedAutoComplete
543
554
  };
544
555
  }
@@ -554,6 +565,7 @@ function ComposerProvider({
554
565
  closeMenusOnOutsideClick = true,
555
566
  attachmentOptions,
556
567
  mode = "markdown",
568
+ variant = "compact",
557
569
  multiline = true,
558
570
  submitOnEnter = true,
559
571
  smartNewline = true,
@@ -732,6 +744,7 @@ function ComposerProvider({
732
744
  attachmentOptions: normalizedAttachmentOptions,
733
745
  markdownMode,
734
746
  mode,
747
+ variant,
735
748
  multiline,
736
749
  submitOnEnter,
737
750
  smartNewline,
@@ -764,6 +777,7 @@ function ComposerProvider({
764
777
  normalizedAttachmentOptions,
765
778
  markdownMode,
766
779
  mode,
780
+ variant,
767
781
  multiline,
768
782
  submitOnEnter,
769
783
  smartNewline,
@@ -789,7 +803,9 @@ function useComposerContext() {
789
803
  function EditorShell({
790
804
  placeholder,
791
805
  mode,
806
+ variant,
792
807
  multiline,
808
+ expanded,
793
809
  header,
794
810
  toolbar,
795
811
  sendButton,
@@ -797,11 +813,14 @@ function EditorShell({
797
813
  }) {
798
814
  const { classNames, sx, dir } = useComposerContext();
799
815
  const isMarkdown = mode === "markdown";
800
- const editorClass = multiline ? "composer-editor composer-editor--multiline" : "composer-editor composer-editor--inline";
816
+ const isCompact = variant === "compact";
817
+ const compactInline = isCompact && !expanded;
818
+ const fillEditor = compactInline || !isCompact && !multiline;
819
+ const editorClass = isCompact ? "composer-editor composer-editor--compact" : multiline ? "composer-editor composer-editor--multiline" : "composer-editor composer-editor--inline";
801
820
  const editor = slotProps("editor", editorClass, classNames, sx);
802
821
  const editorResolved = resolveSx(sx?.editor);
803
822
  const placeholderBase = mirrorEditorPadding(editorResolved);
804
- const placeholderClass = multiline ? "composer-placeholder composer-placeholder--multiline" : "composer-placeholder composer-placeholder--inline";
823
+ const placeholderClass = isCompact ? "composer-placeholder composer-placeholder--compact" : multiline ? "composer-placeholder composer-placeholder--multiline" : "composer-placeholder composer-placeholder--inline";
805
824
  const placeholderProps = slotProps(
806
825
  "placeholder",
807
826
  placeholderClass,
@@ -816,8 +835,9 @@ function EditorShell({
816
835
  {
817
836
  className: cn(
818
837
  "composer-editor-block",
819
- // Inline: the editor block is the flex child that fills the row.
820
- !multiline && "composer-editor-block--inline"
838
+ // Inline + compact: the editor block is the flex child that fills the
839
+ // row between the leading actions and the trailing send cluster.
840
+ fillEditor && "composer-editor-block--inline"
821
841
  ),
822
842
  children: isMarkdown ? /* @__PURE__ */ jsxRuntime.jsx(
823
843
  LexicalRichTextPlugin.RichTextPlugin,
@@ -836,6 +856,32 @@ function EditorShell({
836
856
  )
837
857
  }
838
858
  );
859
+ if (isCompact) {
860
+ const actions = toolbar && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "composer-compact-actions", children: toolbar });
861
+ const sendCluster = sendButton && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "composer-compact-send", children: sendButton });
862
+ if (expanded) {
863
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
864
+ header,
865
+ editorBlock,
866
+ (actions || sendCluster) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "composer-compact-footer", children: [
867
+ actions ?? /* @__PURE__ */ jsxRuntime.jsx("span", {}),
868
+ sendCluster
869
+ ] }),
870
+ /* @__PURE__ */ jsxRuntime.jsx(LexicalHistoryPlugin.HistoryPlugin, {}),
871
+ footer
872
+ ] });
873
+ }
874
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
875
+ header,
876
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "composer-compact-row", children: [
877
+ actions,
878
+ editorBlock,
879
+ sendCluster
880
+ ] }),
881
+ /* @__PURE__ */ jsxRuntime.jsx(LexicalHistoryPlugin.HistoryPlugin, {}),
882
+ footer
883
+ ] });
884
+ }
839
885
  if (!multiline) {
840
886
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
841
887
  header,
@@ -1226,6 +1272,63 @@ function $createLinkTextNode(text = "", url = "") {
1226
1272
  function $isLinkTextNode(node) {
1227
1273
  return node instanceof LinkTextNode;
1228
1274
  }
1275
+ var BASE_CLASS = "composer-code-tok";
1276
+ var CodeTokenNode = class _CodeTokenNode extends lexical.TextNode {
1277
+ __codeKind;
1278
+ static getType() {
1279
+ return "composeai-code-token";
1280
+ }
1281
+ static clone(node) {
1282
+ return new _CodeTokenNode(node.__text, node.__codeKind, node.__key);
1283
+ }
1284
+ constructor(text = "", codeKind = "text", key) {
1285
+ super(text, key);
1286
+ this.__codeKind = codeKind;
1287
+ }
1288
+ getCodeKind() {
1289
+ return this.getLatest().__codeKind;
1290
+ }
1291
+ setCodeKind(kind) {
1292
+ const self = this.getWritable();
1293
+ self.__codeKind = kind;
1294
+ return self;
1295
+ }
1296
+ createDOM(config) {
1297
+ const dom = super.createDOM(config);
1298
+ lexical.addClassNamesToElement(dom, BASE_CLASS, `${BASE_CLASS}--${this.__codeKind}`);
1299
+ return dom;
1300
+ }
1301
+ updateDOM(prevNode, dom, config) {
1302
+ const updated = super.updateDOM(prevNode, dom, config);
1303
+ if (prevNode.__codeKind !== this.__codeKind) {
1304
+ dom.classList.remove(`${BASE_CLASS}--${prevNode.__codeKind}`);
1305
+ dom.classList.add(`${BASE_CLASS}--${this.__codeKind}`);
1306
+ }
1307
+ return updated;
1308
+ }
1309
+ static importJSON(serializedNode) {
1310
+ return $createCodeTokenNode("", serializedNode.codeKind).updateFromJSON(
1311
+ serializedNode
1312
+ );
1313
+ }
1314
+ updateFromJSON(serializedNode) {
1315
+ return super.updateFromJSON(serializedNode);
1316
+ }
1317
+ exportJSON() {
1318
+ return {
1319
+ ...super.exportJSON(),
1320
+ type: _CodeTokenNode.getType(),
1321
+ version: 1,
1322
+ codeKind: this.__codeKind
1323
+ };
1324
+ }
1325
+ };
1326
+ function $createCodeTokenNode(text = "", codeKind = "text") {
1327
+ return lexical.$applyNodeReplacement(new CodeTokenNode(text, codeKind));
1328
+ }
1329
+ function $isCodeTokenNode(node) {
1330
+ return node instanceof CodeTokenNode;
1331
+ }
1229
1332
  var HEADING_RE = /^(#{1,6}) /;
1230
1333
  var QUOTE_RE = /^> /;
1231
1334
  var BULLET_RE = /^[-*+] /;
@@ -1299,12 +1402,20 @@ function $computeBlockMap() {
1299
1402
  const map = /* @__PURE__ */ new Map();
1300
1403
  const root = lexical.$getRoot();
1301
1404
  let insideCode = false;
1405
+ let codeLang;
1302
1406
  for (const child of root.getChildren()) {
1303
1407
  if (!lexical.$isParagraphNode(child)) continue;
1304
- const info = $resolveBlockFor(child, insideCode);
1408
+ let info = $resolveBlockFor(child, insideCode);
1409
+ if (info.kind === "code-fence-open") {
1410
+ insideCode = true;
1411
+ codeLang = info.lang;
1412
+ } else if (info.kind === "code-fence-close") {
1413
+ insideCode = false;
1414
+ codeLang = void 0;
1415
+ } else if (info.kind === "code-line" && codeLang) {
1416
+ info = { ...info, lang: codeLang };
1417
+ }
1305
1418
  map.set(child.getKey(), info);
1306
- if (info.kind === "code-fence-open") insideCode = true;
1307
- else if (info.kind === "code-fence-close") insideCode = false;
1308
1419
  }
1309
1420
  return map;
1310
1421
  }
@@ -1399,7 +1510,8 @@ function wrapByFormat(text, format) {
1399
1510
  if (format & FORMAT_BIT.strike) out = `~~${out}~~`;
1400
1511
  return out;
1401
1512
  }
1402
- function toMarkdown(editor) {
1513
+ function toMarkdown(editor, opts) {
1514
+ const linkedMention = opts?.linkedMention === true;
1403
1515
  return editor.getEditorState().read(() => {
1404
1516
  const root = lexical.$getRoot();
1405
1517
  let usingLive = true;
@@ -1424,7 +1536,13 @@ function toMarkdown(editor) {
1424
1536
  let out = "";
1425
1537
  for (const child of paragraph.getChildren()) {
1426
1538
  if ($isMentionNode(child)) {
1427
- out += `${child.getMentionPrefix()}${child.getMentionLabel()}`;
1539
+ const prefix = child.getMentionPrefix();
1540
+ const label = child.getMentionLabel();
1541
+ if (linkedMention) {
1542
+ out += `[${prefix}${label}](mention:${child.getMentionId()})`;
1543
+ } else {
1544
+ out += `${prefix}${label}`;
1545
+ }
1428
1546
  continue;
1429
1547
  }
1430
1548
  if (lexical.$isLineBreakNode(child)) {
@@ -1503,11 +1621,6 @@ function $isInsideCodeFence() {
1503
1621
  const block = $detectBlockFor(top);
1504
1622
  return block.kind === "code-line" || block.kind === "code-fence-open";
1505
1623
  }
1506
- function $hasMultiLineContent() {
1507
- const root = lexical.$getRoot();
1508
- if (root.getChildrenSize() > 1) return true;
1509
- return root.getTextContent().includes("\n");
1510
- }
1511
1624
  function $offsetWithinParagraph(paragraph, point) {
1512
1625
  if (point.type === "element") {
1513
1626
  const children = paragraph.getChildren();
@@ -1600,10 +1713,8 @@ function KeyboardPlugin({ onSubmit }) {
1600
1713
  return true;
1601
1714
  }
1602
1715
  let inCodeFence = false;
1603
- let hasMultiLine = false;
1604
1716
  editor.getEditorState().read(() => {
1605
1717
  inCodeFence = $isInsideCodeFence();
1606
- hasMultiLine = $hasMultiLineContent();
1607
1718
  });
1608
1719
  if (inCodeFence) {
1609
1720
  if (!multiline) {
@@ -1625,7 +1736,6 @@ function KeyboardPlugin({ onSubmit }) {
1625
1736
  return true;
1626
1737
  }
1627
1738
  }
1628
- if (smartNewline && hasMultiLine) return false;
1629
1739
  if (!submitOnEnter) return false;
1630
1740
  return trySubmit(event);
1631
1741
  },
@@ -1755,6 +1865,134 @@ function PasteDropPlugin() {
1755
1865
  return null;
1756
1866
  }
1757
1867
 
1868
+ // src/plugins/mermaid-highlight.ts
1869
+ var KEYWORDS = /* @__PURE__ */ new Set([
1870
+ "flowchart",
1871
+ "graph",
1872
+ "sequenceDiagram",
1873
+ "classDiagram",
1874
+ "stateDiagram",
1875
+ "stateDiagram-v2",
1876
+ "erDiagram",
1877
+ "journey",
1878
+ "gantt",
1879
+ "pie",
1880
+ "gitGraph",
1881
+ "mindmap",
1882
+ "timeline",
1883
+ "quadrantChart",
1884
+ "requirementDiagram",
1885
+ "C4Context",
1886
+ "subgraph",
1887
+ "end",
1888
+ "direction",
1889
+ "participant",
1890
+ "actor",
1891
+ "note",
1892
+ "loop",
1893
+ "alt",
1894
+ "opt",
1895
+ "par",
1896
+ "and",
1897
+ "else",
1898
+ "rect",
1899
+ "activate",
1900
+ "deactivate",
1901
+ "class",
1902
+ "state",
1903
+ "click",
1904
+ "call",
1905
+ "href",
1906
+ "style",
1907
+ "linkStyle",
1908
+ "classDef",
1909
+ "section",
1910
+ "title",
1911
+ "accTitle",
1912
+ "accDescr",
1913
+ "over",
1914
+ "as",
1915
+ // flowchart directions
1916
+ "TB",
1917
+ "TD",
1918
+ "BT",
1919
+ "RL",
1920
+ "LR"
1921
+ ]);
1922
+ var ARROW_CHARS = /* @__PURE__ */ new Set(["-", "=", ".", "<", ">"]);
1923
+ var WS_RE = /\s/;
1924
+ var WORD_RE = /[A-Za-z0-9_]/;
1925
+ var DIGIT_RE = /[0-9]/;
1926
+ function tokenizeMermaidLine(line) {
1927
+ const out = [];
1928
+ const push = (text, kind) => {
1929
+ if (text) out.push({ text, kind });
1930
+ };
1931
+ let i = 0;
1932
+ const n = line.length;
1933
+ while (i < n) {
1934
+ const c = line[i];
1935
+ if (WS_RE.test(c)) {
1936
+ let j = i + 1;
1937
+ while (j < n && WS_RE.test(line[j])) j++;
1938
+ push(line.slice(i, j), "text");
1939
+ i = j;
1940
+ continue;
1941
+ }
1942
+ if (c === "%" && line[i + 1] === "%") {
1943
+ push(line.slice(i), "comment");
1944
+ i = n;
1945
+ continue;
1946
+ }
1947
+ if (c === '"') {
1948
+ let j = i + 1;
1949
+ while (j < n && line[j] !== '"') j++;
1950
+ j = Math.min(j + 1, n);
1951
+ push(line.slice(i, j), "string");
1952
+ i = j;
1953
+ continue;
1954
+ }
1955
+ if ("[](){}|".includes(c)) {
1956
+ push(c, "punct");
1957
+ i++;
1958
+ continue;
1959
+ }
1960
+ if (ARROW_CHARS.has(c)) {
1961
+ let j = i + 1;
1962
+ while (j < n && ARROW_CHARS.has(line[j])) j++;
1963
+ if (j < n && (line[j] === "x" || line[j] === "o") && j - i >= 2) j++;
1964
+ const run = line.slice(i, j);
1965
+ const isArrow = run.length >= 2 || run.includes(">") || run.includes("<");
1966
+ push(run, isArrow ? "arrow" : "punct");
1967
+ i = j;
1968
+ continue;
1969
+ }
1970
+ if (":;,&+*".includes(c)) {
1971
+ push(c, "punct");
1972
+ i++;
1973
+ continue;
1974
+ }
1975
+ if (DIGIT_RE.test(c)) {
1976
+ let j = i + 1;
1977
+ while (j < n && /[0-9.]/.test(line[j])) j++;
1978
+ push(line.slice(i, j), "number");
1979
+ i = j;
1980
+ continue;
1981
+ }
1982
+ if (WORD_RE.test(c)) {
1983
+ let j = i + 1;
1984
+ while (j < n && WORD_RE.test(line[j])) j++;
1985
+ const word = line.slice(i, j);
1986
+ push(word, KEYWORDS.has(word) ? "keyword" : "ident");
1987
+ i = j;
1988
+ continue;
1989
+ }
1990
+ push(c, "text");
1991
+ i++;
1992
+ }
1993
+ return out;
1994
+ }
1995
+
1758
1996
  // src/plugins/markdown-tokenizer.ts
1759
1997
  var PAIRED_PATTERNS = [
1760
1998
  { open: "**", close: "**", format: "bold" },
@@ -1866,6 +2104,13 @@ function readCurrentChildren(paragraph) {
1866
2104
  });
1867
2105
  } else if ($isMarkdownTokenNode(child)) {
1868
2106
  out.push({ kind: "token", text: child.getTextContent(), format: 0 });
2107
+ } else if ($isCodeTokenNode(child)) {
2108
+ out.push({
2109
+ kind: "code",
2110
+ text: child.getTextContent(),
2111
+ format: 0,
2112
+ codeKind: child.getCodeKind()
2113
+ });
1869
2114
  } else if (lexical.$isTextNode(child)) {
1870
2115
  out.push({
1871
2116
  kind: "text",
@@ -1888,6 +2133,7 @@ function nodesEqual(a, b) {
1888
2133
  if (aKind !== bKind) return false;
1889
2134
  if (ai.text !== bi.text) return false;
1890
2135
  if (ai.format !== bi.format) return false;
2136
+ if (ai.codeKind !== bi.codeKind) return false;
1891
2137
  }
1892
2138
  return true;
1893
2139
  }
@@ -2055,7 +2301,18 @@ function $applyStyling(paragraph, block, mode) {
2055
2301
  trailingDropForOffsetMap = body.length;
2056
2302
  body = "";
2057
2303
  } else if (body.length > 0) {
2058
- desired.push({ kind: "text", text: body, format: 0 });
2304
+ if (block.kind === "code-line" && block.lang === "mermaid") {
2305
+ for (const tok of tokenizeMermaidLine(body)) {
2306
+ desired.push({
2307
+ kind: "code",
2308
+ text: tok.text,
2309
+ format: 0,
2310
+ codeKind: tok.kind
2311
+ });
2312
+ }
2313
+ } else {
2314
+ desired.push({ kind: "text", text: body, format: 0 });
2315
+ }
2059
2316
  }
2060
2317
  } else if (block.kind === "hr") {
2061
2318
  if (body.length > 0) {
@@ -2143,6 +2400,8 @@ function $applyStyling(paragraph, block, mode) {
2143
2400
  for (const node of desired) {
2144
2401
  if (node.kind === "token") {
2145
2402
  paragraph.append($createMarkdownTokenNode(node.text));
2403
+ } else if (node.kind === "code") {
2404
+ paragraph.append($createCodeTokenNode(node.text, node.codeKind));
2146
2405
  } else if (node.kind === "link") {
2147
2406
  const t = $createLinkTextNode(node.text, node.url ?? "");
2148
2407
  if (node.format !== 0) t.setFormat(node.format);
@@ -2404,10 +2663,13 @@ async function loadMermaid() {
2404
2663
  function svgToDataUri(svg) {
2405
2664
  return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
2406
2665
  }
2407
- function MermaidPlugin() {
2666
+ var MermaidContext = react.createContext(null);
2667
+ function useMermaidContext() {
2668
+ return react.useContext(MermaidContext);
2669
+ }
2670
+ function MermaidProvider({ children }) {
2408
2671
  const [editor] = LexicalComposerContext.useLexicalComposerContext();
2409
- const { features, icons, renderDiagram, classNames, sx } = useComposerContext();
2410
- const { sparkle: SparkleIcon } = icons;
2672
+ const { features, renderDiagram } = useComposerContext();
2411
2673
  const [diagrams, setDiagrams] = react.useState([]);
2412
2674
  const keepSource = typeof features.mermaid === "object" ? features.mermaid.keepSource !== false : true;
2413
2675
  react.useEffect(() => {
@@ -2415,10 +2677,10 @@ function MermaidPlugin() {
2415
2677
  editor.getEditorState().read(() => {
2416
2678
  const found = [];
2417
2679
  const root = lexical.$getRoot();
2418
- const children = root.getChildren();
2680
+ const children2 = root.getChildren();
2419
2681
  let i = 0;
2420
- while (i < children.length) {
2421
- const opener = children[i];
2682
+ while (i < children2.length) {
2683
+ const opener = children2[i];
2422
2684
  if (!lexical.$isParagraphNode(opener) || !FENCE_OPEN_MERMAID.test(opener.getTextContent())) {
2423
2685
  i++;
2424
2686
  continue;
@@ -2426,8 +2688,8 @@ function MermaidPlugin() {
2426
2688
  const paragraphKeys = [opener.getKey()];
2427
2689
  const codeLines = [];
2428
2690
  let j = i + 1;
2429
- while (j < children.length) {
2430
- const next = children[j];
2691
+ while (j < children2.length) {
2692
+ const next = children2[j];
2431
2693
  if (!lexical.$isParagraphNode(next)) break;
2432
2694
  const text = next.getTextContent();
2433
2695
  paragraphKeys.push(next.getKey());
@@ -2491,26 +2753,190 @@ function MermaidPlugin() {
2491
2753
  hiddenKeysRef.current.clear();
2492
2754
  };
2493
2755
  }, [editor]);
2494
- if (diagrams.length === 0) return null;
2495
- const preview = slotProps(
2496
- "mermaidPreview",
2497
- "composer-mermaid",
2498
- classNames,
2499
- sx
2756
+ const value = react.useMemo(
2757
+ () => ({ diagrams, renderDiagram }),
2758
+ [diagrams, renderDiagram]
2500
2759
  );
2760
+ return /* @__PURE__ */ jsxRuntime.jsxs(MermaidContext.Provider, { value, children: [
2761
+ children,
2762
+ /* @__PURE__ */ jsxRuntime.jsx(MermaidBackground, {})
2763
+ ] });
2764
+ }
2765
+ function MermaidBackground() {
2766
+ const ctx = useMermaidContext();
2767
+ if (!ctx || ctx.renderDiagram) return null;
2768
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: ctx.diagrams.map((d) => /* @__PURE__ */ jsxRuntime.jsx(DiagramBackdrop, { diagram: d }, d.id)) });
2769
+ }
2770
+ function DiagramBackdrop({ diagram }) {
2771
+ const [editor] = LexicalComposerContext.useLexicalComposerContext();
2772
+ const { svg } = useDiagramSvg(diagram);
2773
+ const [rect, setRect] = react.useState(null);
2774
+ const dataUri = react.useMemo(() => svg ? svgToDataUri(svg) : null, [svg]);
2775
+ react.useEffect(() => {
2776
+ if (!svg) {
2777
+ setRect(null);
2778
+ return;
2779
+ }
2780
+ const root = editor.getRootElement();
2781
+ const block2 = root?.parentElement;
2782
+ if (!root || !block2) return;
2783
+ let raf = 0;
2784
+ const measure = () => {
2785
+ raf = 0;
2786
+ const blockRect = block2.getBoundingClientRect();
2787
+ let top = Infinity;
2788
+ let left = Infinity;
2789
+ let right = -Infinity;
2790
+ let bottom = -Infinity;
2791
+ let any = false;
2792
+ for (const key of diagram.paragraphKeys) {
2793
+ const el = editor.getElementByKey(key);
2794
+ if (!el) continue;
2795
+ any = true;
2796
+ const r = el.getBoundingClientRect();
2797
+ top = Math.min(top, r.top);
2798
+ left = Math.min(left, r.left);
2799
+ right = Math.max(right, r.right);
2800
+ bottom = Math.max(bottom, r.bottom);
2801
+ }
2802
+ if (!any) {
2803
+ setRect(null);
2804
+ return;
2805
+ }
2806
+ const relTop = top - blockRect.top;
2807
+ const visTop = Math.max(0, relTop);
2808
+ const visBottom = Math.min(blockRect.height, relTop + (bottom - top));
2809
+ if (visBottom <= visTop) {
2810
+ setRect(null);
2811
+ return;
2812
+ }
2813
+ setRect({
2814
+ top: visTop,
2815
+ left: left - blockRect.left,
2816
+ width: right - left,
2817
+ height: visBottom - visTop
2818
+ });
2819
+ };
2820
+ const schedule = () => {
2821
+ if (!raf) raf = requestAnimationFrame(measure);
2822
+ };
2823
+ schedule();
2824
+ const unregister = editor.registerUpdateListener(schedule);
2825
+ root.addEventListener("scroll", schedule, { passive: true });
2826
+ const ro = new ResizeObserver(schedule);
2827
+ ro.observe(root);
2828
+ return () => {
2829
+ if (raf) cancelAnimationFrame(raf);
2830
+ unregister();
2831
+ root.removeEventListener("scroll", schedule);
2832
+ ro.disconnect();
2833
+ };
2834
+ }, [editor, svg, diagram.paragraphKeys]);
2835
+ const block = editor.getRootElement()?.parentElement;
2836
+ if (!dataUri || !rect || !block) return null;
2837
+ return reactDom.createPortal(
2838
+ /* @__PURE__ */ jsxRuntime.jsx(
2839
+ "div",
2840
+ {
2841
+ "aria-hidden": true,
2842
+ className: "composer-mermaid-backdrop",
2843
+ style: {
2844
+ top: rect.top,
2845
+ left: rect.left,
2846
+ width: rect.width,
2847
+ height: rect.height,
2848
+ backgroundImage: `url("${dataUri}")`
2849
+ }
2850
+ }
2851
+ ),
2852
+ block
2853
+ );
2854
+ }
2855
+ function MermaidPreview() {
2856
+ const ctx = useMermaidContext();
2857
+ const { icons, classNames, sx } = useComposerContext();
2858
+ const { sparkle: SparkleIcon } = icons;
2859
+ if (!ctx || ctx.diagrams.length === 0) return null;
2860
+ const preview = slotProps("mermaidPreview", "composer-mermaid", classNames, sx);
2501
2861
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...preview, children: [
2502
2862
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "composer-mermaid-head", children: [
2503
2863
  /* @__PURE__ */ jsxRuntime.jsx(SparkleIcon, {}),
2504
2864
  "Diagram preview"
2505
2865
  ] }),
2506
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "composer-mermaid-row", children: diagrams.map((d) => /* @__PURE__ */ jsxRuntime.jsx(
2507
- DiagramTile,
2866
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "composer-mermaid-row", children: ctx.diagrams.map((d) => /* @__PURE__ */ jsxRuntime.jsx(DiagramTile, { diagram: d, renderDiagram: ctx.renderDiagram }, d.id)) })
2867
+ ] });
2868
+ }
2869
+ function MermaidQuickAction() {
2870
+ const ctx = useMermaidContext();
2871
+ const { icons, closeMenusOnOutsideClick } = useComposerContext();
2872
+ const { sparkle: SparkleIcon } = icons;
2873
+ const [open, setOpen] = react.useState(false);
2874
+ const triggerRef = react.useRef(null);
2875
+ const popoverRef = react.useRef(null);
2876
+ const menuId = react.useId();
2877
+ react.useEffect(() => {
2878
+ if (!open) return;
2879
+ const onPointerDown = (event) => {
2880
+ if (!closeMenusOnOutsideClick) return;
2881
+ const target = event.target;
2882
+ if (!target) return;
2883
+ if (popoverRef.current?.contains(target)) return;
2884
+ if (triggerRef.current?.contains(target)) return;
2885
+ setOpen(false);
2886
+ };
2887
+ const onKeyDown = (event) => {
2888
+ if (event.key === "Escape") {
2889
+ event.preventDefault();
2890
+ setOpen(false);
2891
+ triggerRef.current?.focus();
2892
+ }
2893
+ };
2894
+ document.addEventListener("pointerdown", onPointerDown, true);
2895
+ document.addEventListener("keydown", onKeyDown);
2896
+ return () => {
2897
+ document.removeEventListener("pointerdown", onPointerDown, true);
2898
+ document.removeEventListener("keydown", onKeyDown);
2899
+ };
2900
+ }, [open, closeMenusOnOutsideClick]);
2901
+ if (!ctx || ctx.diagrams.length === 0) return null;
2902
+ const count = ctx.diagrams.length;
2903
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "composer-quick-actions", children: [
2904
+ /* @__PURE__ */ jsxRuntime.jsxs(
2905
+ "button",
2508
2906
  {
2509
- diagram: d,
2510
- renderDiagram
2511
- },
2512
- d.id
2513
- )) })
2907
+ ref: triggerRef,
2908
+ type: "button",
2909
+ "aria-label": `Diagram preview (${count})`,
2910
+ "aria-haspopup": "dialog",
2911
+ "aria-expanded": open,
2912
+ "aria-controls": open ? menuId : void 0,
2913
+ "data-active": open ? "" : void 0,
2914
+ onClick: () => setOpen((o) => !o),
2915
+ className: "composer-mermaid-trigger",
2916
+ children: [
2917
+ /* @__PURE__ */ jsxRuntime.jsx(DiagramThumb, { diagram: ctx.diagrams[0], renderDiagram: ctx.renderDiagram }),
2918
+ count > 1 && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "composer-mermaid-count", children: count })
2919
+ ]
2920
+ }
2921
+ ),
2922
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
2923
+ "div",
2924
+ {
2925
+ ref: popoverRef,
2926
+ id: menuId,
2927
+ role: "dialog",
2928
+ "aria-label": "Diagram preview",
2929
+ "data-composer-popover": "open",
2930
+ className: "composer-popover-in composer-mermaid-pop",
2931
+ children: [
2932
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "composer-mermaid-head", children: [
2933
+ /* @__PURE__ */ jsxRuntime.jsx(SparkleIcon, {}),
2934
+ "Diagram preview"
2935
+ ] }),
2936
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "composer-mermaid-row", children: ctx.diagrams.map((d) => /* @__PURE__ */ jsxRuntime.jsx(DiagramTile, { diagram: d, renderDiagram: ctx.renderDiagram }, d.id)) })
2937
+ ]
2938
+ }
2939
+ )
2514
2940
  ] });
2515
2941
  }
2516
2942
  function DiagramTile({ diagram, renderDiagram }) {
@@ -2532,12 +2958,9 @@ function ConsumerTile({
2532
2958
  }
2533
2959
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "composer-mermaid-tile", children: content });
2534
2960
  }
2535
- function MermaidTile({ diagram }) {
2536
- const { icons } = useComposerContext();
2537
- const { zoom: ZoomIcon } = icons;
2961
+ function useDiagramSvg(diagram) {
2538
2962
  const [svg, setSvg] = react.useState(null);
2539
2963
  const [error, setError] = react.useState(null);
2540
- const [zoom, setZoom] = react.useState(false);
2541
2964
  const [mermaidMissing, setMermaidMissing] = react.useState(false);
2542
2965
  const renderId = react.useMemo(
2543
2966
  () => `mermaid-${diagram.id}-${Math.random().toString(36).slice(2, 8)}`,
@@ -2570,6 +2993,13 @@ function MermaidTile({ diagram }) {
2570
2993
  cancelled = true;
2571
2994
  };
2572
2995
  }, [diagram.code, renderId]);
2996
+ return { svg, error, mermaidMissing };
2997
+ }
2998
+ function MermaidTile({ diagram }) {
2999
+ const { icons } = useComposerContext();
3000
+ const { zoom: ZoomIcon } = icons;
3001
+ const [zoom, setZoom] = react.useState(false);
3002
+ const { svg, error, mermaidMissing } = useDiagramSvg(diagram);
2573
3003
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2574
3004
  /* @__PURE__ */ jsxRuntime.jsx(
2575
3005
  "button",
@@ -2605,6 +3035,35 @@ function MermaidTile({ diagram }) {
2605
3035
  )
2606
3036
  ] });
2607
3037
  }
3038
+ function DiagramThumb({ diagram, renderDiagram }) {
3039
+ if (renderDiagram) {
3040
+ return /* @__PURE__ */ jsxRuntime.jsx(ConsumerThumb, { diagram, renderDiagram });
3041
+ }
3042
+ return /* @__PURE__ */ jsxRuntime.jsx(MermaidThumb, { diagram });
3043
+ }
3044
+ function ConsumerThumb({
3045
+ diagram,
3046
+ renderDiagram
3047
+ }) {
3048
+ let content = null;
3049
+ try {
3050
+ content = renderDiagram({ code: diagram.code, language: "mermaid" });
3051
+ } catch {
3052
+ content = null;
3053
+ }
3054
+ return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "composer-mermaid-thumb", children: content });
3055
+ }
3056
+ function MermaidThumb({ diagram }) {
3057
+ const { svg } = useDiagramSvg(diagram);
3058
+ if (!svg) return /* @__PURE__ */ jsxRuntime.jsx("span", { className: "composer-mermaid-thumb" });
3059
+ return /* @__PURE__ */ jsxRuntime.jsx(
3060
+ "span",
3061
+ {
3062
+ className: "composer-mermaid-thumb",
3063
+ dangerouslySetInnerHTML: { __html: svg }
3064
+ }
3065
+ );
3066
+ }
2608
3067
  function SlashMenu({
2609
3068
  options,
2610
3069
  selectedIndex,
@@ -3867,8 +4326,284 @@ function AttachmentTypePicker({
3867
4326
  )
3868
4327
  ] });
3869
4328
  }
4329
+ function useCustomActionContext(submit) {
4330
+ const [editor] = LexicalComposerContext.useLexicalComposerContext();
4331
+ return react.useMemo(
4332
+ () => ({
4333
+ insertText: (text) => {
4334
+ editor.update(() => {
4335
+ const sel = lexical.$getSelection();
4336
+ if (lexical.$isRangeSelection(sel)) sel.insertText(text);
4337
+ });
4338
+ focusEditor(editor);
4339
+ },
4340
+ insertMarkdown: (md) => {
4341
+ editor.update(() => {
4342
+ $insertTextWithParagraphBreaks(md);
4343
+ });
4344
+ focusEditor(editor);
4345
+ },
4346
+ submit
4347
+ }),
4348
+ [editor, submit]
4349
+ );
4350
+ }
4351
+ function CustomActionButtons({ submit }) {
4352
+ const { features, classNames, sx } = useComposerContext();
4353
+ const ctx = useCustomActionContext(submit);
4354
+ const actions = features.custom;
4355
+ const toolbarBtn = slotProps(
4356
+ "toolbarButton",
4357
+ "composer-toolbar-btn",
4358
+ classNames,
4359
+ sx
4360
+ );
4361
+ if (actions.length === 0) return null;
4362
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: actions.map((action, i) => /* @__PURE__ */ jsxRuntime.jsx(Tooltip, { content: action.title, side: "top", children: /* @__PURE__ */ jsxRuntime.jsx(
4363
+ "button",
4364
+ {
4365
+ type: "button",
4366
+ "aria-label": action.title,
4367
+ "aria-pressed": action.active,
4368
+ "data-active": action.active ? "" : void 0,
4369
+ disabled: action.disabled,
4370
+ onClick: () => action.onClick(ctx),
4371
+ ...toolbarBtn,
4372
+ children: action.icon
4373
+ }
4374
+ ) }, action.id ?? i)) });
4375
+ }
4376
+ function QuickActionsMenu({ extras, submit }) {
4377
+ const {
4378
+ features,
4379
+ attachmentsConfig,
4380
+ addFiles,
4381
+ webEnabled,
4382
+ toggleWeb,
4383
+ icons,
4384
+ classNames,
4385
+ sx,
4386
+ closeMenusOnOutsideClick
4387
+ } = useComposerContext();
4388
+ const {
4389
+ plus: PlusIcon,
4390
+ attach: AttachIcon,
4391
+ image: ImageIcon,
4392
+ web: WebIcon
4393
+ } = icons;
4394
+ const [open, setOpen] = react.useState(false);
4395
+ const triggerRef = react.useRef(null);
4396
+ const popoverRef = react.useRef(null);
4397
+ const fileInputRef = react.useRef(null);
4398
+ const imageInputRef = react.useRef(null);
4399
+ const menuId = react.useId();
4400
+ const attachmentsEnabled = !!features.attachments;
4401
+ const showFileBtn = attachmentsEnabled && attachmentsConfig.file !== false;
4402
+ const showImageBtn = attachmentsEnabled && attachmentsConfig.image !== false;
4403
+ const types = showFileBtn && Array.isArray(attachmentsConfig.types) ? attachmentsConfig.types : [];
4404
+ const hasTypePicker = types.length > 0;
4405
+ const customActions = features.custom;
4406
+ const actionCtx = useCustomActionContext(submit ?? (() => {
4407
+ }));
4408
+ const hasAnyAction = showFileBtn || showImageBtn || features.web || customActions.length > 0 || !!extras;
4409
+ const close = react.useCallback(() => setOpen(false), []);
4410
+ const openFilePicker = react.useCallback((accept) => {
4411
+ const input = fileInputRef.current;
4412
+ if (!input) return;
4413
+ input.accept = accept ?? attachmentsConfig.accept ?? "";
4414
+ input.click();
4415
+ }, [attachmentsConfig.accept]);
4416
+ react.useEffect(() => {
4417
+ if (!open) return;
4418
+ const onPointerDown = (event) => {
4419
+ if (!closeMenusOnOutsideClick) return;
4420
+ const target = event.target;
4421
+ if (!target) return;
4422
+ if (popoverRef.current?.contains(target)) return;
4423
+ if (triggerRef.current?.contains(target)) return;
4424
+ close();
4425
+ };
4426
+ const onKeyDown = (event) => {
4427
+ if (event.key === "Escape") {
4428
+ event.preventDefault();
4429
+ close();
4430
+ triggerRef.current?.focus();
4431
+ }
4432
+ };
4433
+ document.addEventListener("pointerdown", onPointerDown, true);
4434
+ document.addEventListener("keydown", onKeyDown);
4435
+ return () => {
4436
+ document.removeEventListener("pointerdown", onPointerDown, true);
4437
+ document.removeEventListener("keydown", onKeyDown);
4438
+ };
4439
+ }, [open, closeMenusOnOutsideClick, close]);
4440
+ if (!hasAnyAction) return null;
4441
+ const triggerBtn = slotProps(
4442
+ "toolbarButton",
4443
+ "composer-toolbar-btn",
4444
+ classNames,
4445
+ sx
4446
+ );
4447
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "composer-quick-actions", children: [
4448
+ showFileBtn && /* @__PURE__ */ jsxRuntime.jsx(
4449
+ "input",
4450
+ {
4451
+ ref: fileInputRef,
4452
+ type: "file",
4453
+ multiple: true,
4454
+ accept: attachmentsConfig.accept,
4455
+ hidden: true,
4456
+ onChange: (e) => {
4457
+ const files = e.target.files;
4458
+ if (files) addFiles(Array.from(files));
4459
+ if (fileInputRef.current) fileInputRef.current.value = "";
4460
+ }
4461
+ }
4462
+ ),
4463
+ showImageBtn && /* @__PURE__ */ jsxRuntime.jsx(
4464
+ "input",
4465
+ {
4466
+ ref: imageInputRef,
4467
+ type: "file",
4468
+ multiple: true,
4469
+ accept: "image/*",
4470
+ hidden: true,
4471
+ onChange: (e) => {
4472
+ const files = e.target.files;
4473
+ if (files) addFiles(Array.from(files));
4474
+ if (imageInputRef.current) imageInputRef.current.value = "";
4475
+ }
4476
+ }
4477
+ ),
4478
+ /* @__PURE__ */ jsxRuntime.jsx(
4479
+ "button",
4480
+ {
4481
+ ref: triggerRef,
4482
+ type: "button",
4483
+ "aria-label": "Quick actions",
4484
+ "aria-haspopup": "menu",
4485
+ "aria-expanded": open,
4486
+ "aria-controls": open ? menuId : void 0,
4487
+ "data-active": open ? "" : void 0,
4488
+ onClick: () => setOpen((o) => !o),
4489
+ ...triggerBtn,
4490
+ children: /* @__PURE__ */ jsxRuntime.jsx(PlusIcon, {})
4491
+ }
4492
+ ),
4493
+ open && /* @__PURE__ */ jsxRuntime.jsxs(
4494
+ "div",
4495
+ {
4496
+ ref: popoverRef,
4497
+ id: menuId,
4498
+ role: "menu",
4499
+ "aria-label": "Quick actions",
4500
+ "data-composer-popover": "open",
4501
+ className: "composer-popover-in composer-attach-menu composer-quick-menu",
4502
+ children: [
4503
+ showFileBtn && !hasTypePicker && /* @__PURE__ */ jsxRuntime.jsx(
4504
+ ActionItem,
4505
+ {
4506
+ icon: /* @__PURE__ */ jsxRuntime.jsx(AttachIcon, {}),
4507
+ label: "Attach file",
4508
+ onClick: () => {
4509
+ close();
4510
+ openFilePicker();
4511
+ }
4512
+ }
4513
+ ),
4514
+ hasTypePicker && types.map((type) => /* @__PURE__ */ jsxRuntime.jsx(
4515
+ ActionItem,
4516
+ {
4517
+ icon: type.icon ?? /* @__PURE__ */ jsxRuntime.jsx(AttachIcon, {}),
4518
+ label: type.label,
4519
+ description: type.description,
4520
+ onClick: () => {
4521
+ close();
4522
+ openFilePicker(type.accept);
4523
+ }
4524
+ },
4525
+ type.id
4526
+ )),
4527
+ showImageBtn && /* @__PURE__ */ jsxRuntime.jsx(
4528
+ ActionItem,
4529
+ {
4530
+ icon: /* @__PURE__ */ jsxRuntime.jsx(ImageIcon, {}),
4531
+ label: "Add image",
4532
+ onClick: () => {
4533
+ close();
4534
+ imageInputRef.current?.click();
4535
+ }
4536
+ }
4537
+ ),
4538
+ features.web && /* @__PURE__ */ jsxRuntime.jsx(
4539
+ ActionItem,
4540
+ {
4541
+ icon: /* @__PURE__ */ jsxRuntime.jsx(WebIcon, {}),
4542
+ label: "Search the web",
4543
+ active: webEnabled,
4544
+ onClick: () => {
4545
+ toggleWeb();
4546
+ close();
4547
+ }
4548
+ }
4549
+ ),
4550
+ customActions.map((action, i) => /* @__PURE__ */ jsxRuntime.jsx(
4551
+ ActionItem,
4552
+ {
4553
+ icon: action.icon,
4554
+ label: action.title,
4555
+ active: action.active,
4556
+ disabled: action.disabled,
4557
+ onClick: () => {
4558
+ action.onClick(actionCtx);
4559
+ close();
4560
+ }
4561
+ },
4562
+ action.id ?? i
4563
+ )),
4564
+ extras && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "composer-quick-extras", children: extras })
4565
+ ]
4566
+ }
4567
+ )
4568
+ ] });
4569
+ }
4570
+ function ActionItem({
4571
+ icon,
4572
+ label,
4573
+ description,
4574
+ active,
4575
+ disabled,
4576
+ onClick
4577
+ }) {
4578
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4579
+ "button",
4580
+ {
4581
+ role: "menuitem",
4582
+ type: "button",
4583
+ "aria-pressed": active,
4584
+ "data-active": active ? "" : void 0,
4585
+ disabled,
4586
+ onClick,
4587
+ className: cn("composer-attach-item"),
4588
+ children: [
4589
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "composer-attach-item-icon", children: icon }),
4590
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "composer-attach-item-label", children: label }),
4591
+ description ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "composer-attach-item-desc", children: description }) : null
4592
+ ]
4593
+ }
4594
+ );
4595
+ }
3870
4596
  var TOOLBAR_BTN_BASE = "composer-toolbar-btn";
3871
- function Toolbar({ extras }) {
4597
+ function Toolbar({ extras, variant = "full", submit }) {
4598
+ if (variant === "compact") {
4599
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4600
+ /* @__PURE__ */ jsxRuntime.jsx(QuickActionsMenu, { extras, submit }),
4601
+ /* @__PURE__ */ jsxRuntime.jsx(MermaidQuickAction, {})
4602
+ ] });
4603
+ }
4604
+ return /* @__PURE__ */ jsxRuntime.jsx(FullToolbar, { extras, submit });
4605
+ }
4606
+ function FullToolbar({ extras, submit }) {
3872
4607
  const {
3873
4608
  features,
3874
4609
  attachmentsConfig,
@@ -3967,6 +4702,8 @@ function Toolbar({ extras }) {
3967
4702
  ]
3968
4703
  }
3969
4704
  ),
4705
+ /* @__PURE__ */ jsxRuntime.jsx(CustomActionButtons, { submit: submit ?? (() => {
4706
+ }) }),
3970
4707
  extras
3971
4708
  ] });
3972
4709
  }
@@ -4060,7 +4797,6 @@ function HintBar({ hint }) {
4060
4797
  const {
4061
4798
  multiline,
4062
4799
  submitOnEnter,
4063
- smartNewline,
4064
4800
  focusShortcut,
4065
4801
  classNames,
4066
4802
  sx
@@ -4074,16 +4810,6 @@ function HintBar({ hint }) {
4074
4810
  " to send."
4075
4811
  ] });
4076
4812
  }
4077
- if (smartNewline && submitOnEnter) {
4078
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4079
- "Press ",
4080
- /* @__PURE__ */ jsxRuntime.jsx(Key, { children: "Enter" }),
4081
- " to send a single line,",
4082
- " ",
4083
- /* @__PURE__ */ jsxRuntime.jsx(Key, { children: "\u2318/Ctrl + Enter" }),
4084
- " to send once you've started a new line."
4085
- ] });
4086
- }
4087
4813
  if (!submitOnEnter) {
4088
4814
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4089
4815
  "Press ",
@@ -4100,7 +4826,7 @@ function HintBar({ hint }) {
4100
4826
  /* @__PURE__ */ jsxRuntime.jsx(Key, { children: "Shift + Enter" }),
4101
4827
  " for newline."
4102
4828
  ] });
4103
- }, [multiline, submitOnEnter, smartNewline]);
4829
+ }, [multiline, submitOnEnter]);
4104
4830
  const focusHint = react.useMemo(() => {
4105
4831
  if (!focusShortcut) return null;
4106
4832
  const label = formatShortcut(focusShortcut);
@@ -4298,6 +5024,7 @@ var Composer = react.forwardRef(function Composer2(props, ref) {
4298
5024
  toolbarExtras,
4299
5025
  closeMenusOnOutsideClick = true,
4300
5026
  mode = "markdown",
5027
+ variant = "compact",
4301
5028
  multiline = true,
4302
5029
  submitOnEnter = true,
4303
5030
  smartNewline = true,
@@ -4326,6 +5053,7 @@ var Composer = react.forwardRef(function Composer2(props, ref) {
4326
5053
  closeMenusOnOutsideClick,
4327
5054
  attachmentOptions,
4328
5055
  mode,
5056
+ variant,
4329
5057
  multiline,
4330
5058
  submitOnEnter,
4331
5059
  smartNewline,
@@ -4360,6 +5088,7 @@ var Composer = react.forwardRef(function Composer2(props, ref) {
4360
5088
  isStreaming: !!isStreaming,
4361
5089
  toolbarExtras,
4362
5090
  mode,
5091
+ variant,
4363
5092
  multiline
4364
5093
  }
4365
5094
  ),
@@ -4380,6 +5109,7 @@ var RICH_NODES = [
4380
5109
  MarkdownTokenNode,
4381
5110
  BlockParagraphNode,
4382
5111
  LinkTextNode,
5112
+ CodeTokenNode,
4383
5113
  BLOCK_PARAGRAPH_REPLACEMENT
4384
5114
  ];
4385
5115
  var PLAIN_NODES = [MentionNode];
@@ -4395,6 +5125,7 @@ function ComposerCard({
4395
5125
  isStreaming,
4396
5126
  toolbarExtras,
4397
5127
  mode,
5128
+ variant,
4398
5129
  multiline
4399
5130
  }) {
4400
5131
  const { webEnabled, isDraggingFiles, classNames, sx } = useComposerContext();
@@ -4415,7 +5146,8 @@ function ComposerCard({
4415
5146
  "div",
4416
5147
  {
4417
5148
  "data-composer-root": "",
4418
- "data-composer-inline": multiline ? void 0 : "",
5149
+ "data-composer-variant": variant,
5150
+ "data-composer-inline": variant === "full" && !multiline ? "" : void 0,
4419
5151
  "data-composer-web": webEnabled ? "" : void 0,
4420
5152
  "data-composer-dragging": isDraggingFiles ? "" : void 0,
4421
5153
  ...card,
@@ -4445,6 +5177,7 @@ function ComposerCard({
4445
5177
  {
4446
5178
  placeholder,
4447
5179
  mode,
5180
+ variant,
4448
5181
  multiline,
4449
5182
  handleRef,
4450
5183
  onSend,
@@ -4464,6 +5197,7 @@ function ComposerCard({
4464
5197
  function ComposerInner({
4465
5198
  placeholder,
4466
5199
  mode,
5200
+ variant,
4467
5201
  multiline,
4468
5202
  handleRef,
4469
5203
  onSend,
@@ -4491,6 +5225,9 @@ function ComposerInner({
4491
5225
  const [hasText, setHasText] = react.useState(
4492
5226
  !!initialValue && initialValue.trim().length > 0
4493
5227
  );
5228
+ const [isMultiLine, setIsMultiLine] = react.useState(
5229
+ !!initialValue && initialValue.includes("\n")
5230
+ );
4494
5231
  const onSendRef = react.useRef(onSend);
4495
5232
  onSendRef.current = onSend;
4496
5233
  const refocusOnSubmitRef = react.useRef(refocusOnSubmit);
@@ -4499,9 +5236,10 @@ function ComposerInner({
4499
5236
  if (isStreaming) return;
4500
5237
  if (uploadsBlocking) return;
4501
5238
  let payload = null;
5239
+ const linkedMention = typeof features.mentions === "object" && !!features.mentions.linkedMention;
4502
5240
  editor.getEditorState().read(() => {
4503
5241
  const { text, mentions } = collectPlainAndMentions(editor);
4504
- const markdown = toMarkdown(editor);
5242
+ const markdown = toMarkdown(editor, { linkedMention });
4505
5243
  const trimmed = text.trim();
4506
5244
  if (!trimmed) {
4507
5245
  if (attachments.length === 0) return;
@@ -4568,8 +5306,10 @@ function ComposerInner({
4568
5306
  react.useEffect(() => {
4569
5307
  return editor.registerUpdateListener(() => {
4570
5308
  editor.getEditorState().read(() => {
4571
- const text = lexical.$getRoot().getTextContent().trim();
4572
- setHasText(text.length > 0);
5309
+ const root = lexical.$getRoot();
5310
+ const text = root.getTextContent();
5311
+ setHasText(text.trim().length > 0);
5312
+ setIsMultiLine(root.getChildrenSize() > 1 || text.includes("\n"));
4573
5313
  });
4574
5314
  });
4575
5315
  }, [editor]);
@@ -4585,8 +5325,10 @@ function ComposerInner({
4585
5325
  }
4586
5326
  });
4587
5327
  }, [editor, registerRunPrompt, submit]);
4588
- const toolbarSlot = /* @__PURE__ */ jsxRuntime.jsx(Toolbar, { extras: toolbarExtras });
4589
- const sendButtonSlot = /* @__PURE__ */ jsxRuntime.jsx(
5328
+ const isCompact = variant === "compact";
5329
+ const mermaidActive = multiline && mode === "markdown" && !!features.mermaid;
5330
+ const toolbarSlot = /* @__PURE__ */ jsxRuntime.jsx(Toolbar, { extras: toolbarExtras, variant, submit });
5331
+ const sendButton = /* @__PURE__ */ jsxRuntime.jsx(
4590
5332
  SendButton,
4591
5333
  {
4592
5334
  canSend: (
@@ -4599,38 +5341,37 @@ function ComposerInner({
4599
5341
  onStop
4600
5342
  }
4601
5343
  );
4602
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5344
+ const sendButtonSlot = isCompact && features.voice ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5345
+ /* @__PURE__ */ jsxRuntime.jsx(VoiceButton, {}),
5346
+ sendButton
5347
+ ] }) : sendButton;
5348
+ const content = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4603
5349
  /* @__PURE__ */ jsxRuntime.jsx(
4604
5350
  EditorShell,
4605
5351
  {
4606
5352
  placeholder,
4607
5353
  mode,
5354
+ variant,
4608
5355
  multiline,
5356
+ expanded: isCompact && isMultiLine,
4609
5357
  header: /* @__PURE__ */ jsxRuntime.jsx(AttachmentTray, {}),
4610
5358
  toolbar: toolbarSlot,
4611
5359
  sendButton: sendButtonSlot,
4612
- footer: multiline ? /* @__PURE__ */ jsxRuntime.jsx(MermaidSlot, {}) : null
5360
+ footer: mermaidActive && !isCompact ? /* @__PURE__ */ jsxRuntime.jsx(MermaidPreview, {}) : null
4613
5361
  }
4614
5362
  ),
4615
5363
  /* @__PURE__ */ jsxRuntime.jsx(KeyboardPlugin, { onSubmit: submit }),
4616
5364
  /* @__PURE__ */ jsxRuntime.jsx(AutoFocusPlugin, { enabled: !!autoFocus }),
4617
5365
  /* @__PURE__ */ jsxRuntime.jsx(PasteDropPlugin, {}),
4618
5366
  markdownEnabled && /* @__PURE__ */ jsxRuntime.jsx(MarkdownPlugin, {}),
4619
- features.slashCommands && /* @__PURE__ */ jsxRuntime.jsx(
4620
- SlashCommandPlugin,
4621
- {
4622
- config: features.slashCommands,
4623
- onSubmit: submit
4624
- }
4625
- ),
5367
+ features.slashCommands && // One typeahead per config — a single config or an array of them, so
5368
+ // consumers can register several trigger symbols (each with its own
5369
+ // action menu) at once. Keyed by trigger; give each a distinct symbol.
5370
+ (Array.isArray(features.slashCommands) ? features.slashCommands : [features.slashCommands]).map((cfg, i) => /* @__PURE__ */ jsxRuntime.jsx(SlashCommandPlugin, { config: cfg, onSubmit: submit }, cfg.trigger ?? `slash-${i}`)),
4626
5371
  features.mentions && /* @__PURE__ */ jsxRuntime.jsx(MentionPlugin, { config: features.mentions }),
4627
5372
  features.ghostedAutoComplete && /* @__PURE__ */ jsxRuntime.jsx(GhostedAutoCompletePlugin, { config: features.ghostedAutoComplete })
4628
5373
  ] });
4629
- }
4630
- function MermaidSlot() {
4631
- const { features, mode } = useComposerContext();
4632
- if (mode !== "markdown" || !features.mermaid) return null;
4633
- return /* @__PURE__ */ jsxRuntime.jsx(MermaidPlugin, {});
5374
+ return mermaidActive ? /* @__PURE__ */ jsxRuntime.jsx(MermaidProvider, { children: content }) : content;
4634
5375
  }
4635
5376
  function SuggestionRow({ items, onSelect, className }) {
4636
5377
  const { icons } = useComposerContext();