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