composeai 0.1.5 → 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/README.md +25 -1
- package/dist/index.cjs +806 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +132 -18
- package/dist/index.d.ts +132 -18
- package/dist/index.js +808 -75
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/composer.css +262 -9
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
|
|
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
|
|
820
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -1510,11 +1621,6 @@ function $isInsideCodeFence() {
|
|
|
1510
1621
|
const block = $detectBlockFor(top);
|
|
1511
1622
|
return block.kind === "code-line" || block.kind === "code-fence-open";
|
|
1512
1623
|
}
|
|
1513
|
-
function $hasMultiLineContent() {
|
|
1514
|
-
const root = lexical.$getRoot();
|
|
1515
|
-
if (root.getChildrenSize() > 1) return true;
|
|
1516
|
-
return root.getTextContent().includes("\n");
|
|
1517
|
-
}
|
|
1518
1624
|
function $offsetWithinParagraph(paragraph, point) {
|
|
1519
1625
|
if (point.type === "element") {
|
|
1520
1626
|
const children = paragraph.getChildren();
|
|
@@ -1607,10 +1713,8 @@ function KeyboardPlugin({ onSubmit }) {
|
|
|
1607
1713
|
return true;
|
|
1608
1714
|
}
|
|
1609
1715
|
let inCodeFence = false;
|
|
1610
|
-
let hasMultiLine = false;
|
|
1611
1716
|
editor.getEditorState().read(() => {
|
|
1612
1717
|
inCodeFence = $isInsideCodeFence();
|
|
1613
|
-
hasMultiLine = $hasMultiLineContent();
|
|
1614
1718
|
});
|
|
1615
1719
|
if (inCodeFence) {
|
|
1616
1720
|
if (!multiline) {
|
|
@@ -1632,7 +1736,6 @@ function KeyboardPlugin({ onSubmit }) {
|
|
|
1632
1736
|
return true;
|
|
1633
1737
|
}
|
|
1634
1738
|
}
|
|
1635
|
-
if (smartNewline && hasMultiLine) return false;
|
|
1636
1739
|
if (!submitOnEnter) return false;
|
|
1637
1740
|
return trySubmit(event);
|
|
1638
1741
|
},
|
|
@@ -1762,6 +1865,134 @@ function PasteDropPlugin() {
|
|
|
1762
1865
|
return null;
|
|
1763
1866
|
}
|
|
1764
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
|
+
|
|
1765
1996
|
// src/plugins/markdown-tokenizer.ts
|
|
1766
1997
|
var PAIRED_PATTERNS = [
|
|
1767
1998
|
{ open: "**", close: "**", format: "bold" },
|
|
@@ -1873,6 +2104,13 @@ function readCurrentChildren(paragraph) {
|
|
|
1873
2104
|
});
|
|
1874
2105
|
} else if ($isMarkdownTokenNode(child)) {
|
|
1875
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
|
+
});
|
|
1876
2114
|
} else if (lexical.$isTextNode(child)) {
|
|
1877
2115
|
out.push({
|
|
1878
2116
|
kind: "text",
|
|
@@ -1895,6 +2133,7 @@ function nodesEqual(a, b) {
|
|
|
1895
2133
|
if (aKind !== bKind) return false;
|
|
1896
2134
|
if (ai.text !== bi.text) return false;
|
|
1897
2135
|
if (ai.format !== bi.format) return false;
|
|
2136
|
+
if (ai.codeKind !== bi.codeKind) return false;
|
|
1898
2137
|
}
|
|
1899
2138
|
return true;
|
|
1900
2139
|
}
|
|
@@ -2062,7 +2301,18 @@ function $applyStyling(paragraph, block, mode) {
|
|
|
2062
2301
|
trailingDropForOffsetMap = body.length;
|
|
2063
2302
|
body = "";
|
|
2064
2303
|
} else if (body.length > 0) {
|
|
2065
|
-
|
|
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
|
+
}
|
|
2066
2316
|
}
|
|
2067
2317
|
} else if (block.kind === "hr") {
|
|
2068
2318
|
if (body.length > 0) {
|
|
@@ -2150,6 +2400,8 @@ function $applyStyling(paragraph, block, mode) {
|
|
|
2150
2400
|
for (const node of desired) {
|
|
2151
2401
|
if (node.kind === "token") {
|
|
2152
2402
|
paragraph.append($createMarkdownTokenNode(node.text));
|
|
2403
|
+
} else if (node.kind === "code") {
|
|
2404
|
+
paragraph.append($createCodeTokenNode(node.text, node.codeKind));
|
|
2153
2405
|
} else if (node.kind === "link") {
|
|
2154
2406
|
const t = $createLinkTextNode(node.text, node.url ?? "");
|
|
2155
2407
|
if (node.format !== 0) t.setFormat(node.format);
|
|
@@ -2411,10 +2663,13 @@ async function loadMermaid() {
|
|
|
2411
2663
|
function svgToDataUri(svg) {
|
|
2412
2664
|
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
|
|
2413
2665
|
}
|
|
2414
|
-
|
|
2666
|
+
var MermaidContext = react.createContext(null);
|
|
2667
|
+
function useMermaidContext() {
|
|
2668
|
+
return react.useContext(MermaidContext);
|
|
2669
|
+
}
|
|
2670
|
+
function MermaidProvider({ children }) {
|
|
2415
2671
|
const [editor] = LexicalComposerContext.useLexicalComposerContext();
|
|
2416
|
-
const { features,
|
|
2417
|
-
const { sparkle: SparkleIcon } = icons;
|
|
2672
|
+
const { features, renderDiagram } = useComposerContext();
|
|
2418
2673
|
const [diagrams, setDiagrams] = react.useState([]);
|
|
2419
2674
|
const keepSource = typeof features.mermaid === "object" ? features.mermaid.keepSource !== false : true;
|
|
2420
2675
|
react.useEffect(() => {
|
|
@@ -2422,10 +2677,10 @@ function MermaidPlugin() {
|
|
|
2422
2677
|
editor.getEditorState().read(() => {
|
|
2423
2678
|
const found = [];
|
|
2424
2679
|
const root = lexical.$getRoot();
|
|
2425
|
-
const
|
|
2680
|
+
const children2 = root.getChildren();
|
|
2426
2681
|
let i = 0;
|
|
2427
|
-
while (i <
|
|
2428
|
-
const opener =
|
|
2682
|
+
while (i < children2.length) {
|
|
2683
|
+
const opener = children2[i];
|
|
2429
2684
|
if (!lexical.$isParagraphNode(opener) || !FENCE_OPEN_MERMAID.test(opener.getTextContent())) {
|
|
2430
2685
|
i++;
|
|
2431
2686
|
continue;
|
|
@@ -2433,8 +2688,8 @@ function MermaidPlugin() {
|
|
|
2433
2688
|
const paragraphKeys = [opener.getKey()];
|
|
2434
2689
|
const codeLines = [];
|
|
2435
2690
|
let j = i + 1;
|
|
2436
|
-
while (j <
|
|
2437
|
-
const next =
|
|
2691
|
+
while (j < children2.length) {
|
|
2692
|
+
const next = children2[j];
|
|
2438
2693
|
if (!lexical.$isParagraphNode(next)) break;
|
|
2439
2694
|
const text = next.getTextContent();
|
|
2440
2695
|
paragraphKeys.push(next.getKey());
|
|
@@ -2498,26 +2753,190 @@ function MermaidPlugin() {
|
|
|
2498
2753
|
hiddenKeysRef.current.clear();
|
|
2499
2754
|
};
|
|
2500
2755
|
}, [editor]);
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
|
|
2504
|
-
"composer-mermaid",
|
|
2505
|
-
classNames,
|
|
2506
|
-
sx
|
|
2756
|
+
const value = react.useMemo(
|
|
2757
|
+
() => ({ diagrams, renderDiagram }),
|
|
2758
|
+
[diagrams, renderDiagram]
|
|
2507
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);
|
|
2508
2861
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...preview, children: [
|
|
2509
2862
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "composer-mermaid-head", children: [
|
|
2510
2863
|
/* @__PURE__ */ jsxRuntime.jsx(SparkleIcon, {}),
|
|
2511
2864
|
"Diagram preview"
|
|
2512
2865
|
] }),
|
|
2513
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "composer-mermaid-row", children: diagrams.map((d) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2514
|
-
|
|
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",
|
|
2515
2906
|
{
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
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
|
+
)
|
|
2521
2940
|
] });
|
|
2522
2941
|
}
|
|
2523
2942
|
function DiagramTile({ diagram, renderDiagram }) {
|
|
@@ -2539,12 +2958,9 @@ function ConsumerTile({
|
|
|
2539
2958
|
}
|
|
2540
2959
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "composer-mermaid-tile", children: content });
|
|
2541
2960
|
}
|
|
2542
|
-
function
|
|
2543
|
-
const { icons } = useComposerContext();
|
|
2544
|
-
const { zoom: ZoomIcon } = icons;
|
|
2961
|
+
function useDiagramSvg(diagram) {
|
|
2545
2962
|
const [svg, setSvg] = react.useState(null);
|
|
2546
2963
|
const [error, setError] = react.useState(null);
|
|
2547
|
-
const [zoom, setZoom] = react.useState(false);
|
|
2548
2964
|
const [mermaidMissing, setMermaidMissing] = react.useState(false);
|
|
2549
2965
|
const renderId = react.useMemo(
|
|
2550
2966
|
() => `mermaid-${diagram.id}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -2577,6 +2993,13 @@ function MermaidTile({ diagram }) {
|
|
|
2577
2993
|
cancelled = true;
|
|
2578
2994
|
};
|
|
2579
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);
|
|
2580
3003
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2581
3004
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2582
3005
|
"button",
|
|
@@ -2612,6 +3035,35 @@ function MermaidTile({ diagram }) {
|
|
|
2612
3035
|
)
|
|
2613
3036
|
] });
|
|
2614
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
|
+
}
|
|
2615
3067
|
function SlashMenu({
|
|
2616
3068
|
options,
|
|
2617
3069
|
selectedIndex,
|
|
@@ -3874,8 +4326,284 @@ function AttachmentTypePicker({
|
|
|
3874
4326
|
)
|
|
3875
4327
|
] });
|
|
3876
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
|
+
}
|
|
3877
4596
|
var TOOLBAR_BTN_BASE = "composer-toolbar-btn";
|
|
3878
|
-
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 }) {
|
|
3879
4607
|
const {
|
|
3880
4608
|
features,
|
|
3881
4609
|
attachmentsConfig,
|
|
@@ -3974,6 +4702,8 @@ function Toolbar({ extras }) {
|
|
|
3974
4702
|
]
|
|
3975
4703
|
}
|
|
3976
4704
|
),
|
|
4705
|
+
/* @__PURE__ */ jsxRuntime.jsx(CustomActionButtons, { submit: submit ?? (() => {
|
|
4706
|
+
}) }),
|
|
3977
4707
|
extras
|
|
3978
4708
|
] });
|
|
3979
4709
|
}
|
|
@@ -4067,7 +4797,6 @@ function HintBar({ hint }) {
|
|
|
4067
4797
|
const {
|
|
4068
4798
|
multiline,
|
|
4069
4799
|
submitOnEnter,
|
|
4070
|
-
smartNewline,
|
|
4071
4800
|
focusShortcut,
|
|
4072
4801
|
classNames,
|
|
4073
4802
|
sx
|
|
@@ -4081,16 +4810,6 @@ function HintBar({ hint }) {
|
|
|
4081
4810
|
" to send."
|
|
4082
4811
|
] });
|
|
4083
4812
|
}
|
|
4084
|
-
if (smartNewline && submitOnEnter) {
|
|
4085
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4086
|
-
"Press ",
|
|
4087
|
-
/* @__PURE__ */ jsxRuntime.jsx(Key, { children: "Enter" }),
|
|
4088
|
-
" to send a single line,",
|
|
4089
|
-
" ",
|
|
4090
|
-
/* @__PURE__ */ jsxRuntime.jsx(Key, { children: "\u2318/Ctrl + Enter" }),
|
|
4091
|
-
" to send once you've started a new line."
|
|
4092
|
-
] });
|
|
4093
|
-
}
|
|
4094
4813
|
if (!submitOnEnter) {
|
|
4095
4814
|
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4096
4815
|
"Press ",
|
|
@@ -4107,7 +4826,7 @@ function HintBar({ hint }) {
|
|
|
4107
4826
|
/* @__PURE__ */ jsxRuntime.jsx(Key, { children: "Shift + Enter" }),
|
|
4108
4827
|
" for newline."
|
|
4109
4828
|
] });
|
|
4110
|
-
}, [multiline, submitOnEnter
|
|
4829
|
+
}, [multiline, submitOnEnter]);
|
|
4111
4830
|
const focusHint = react.useMemo(() => {
|
|
4112
4831
|
if (!focusShortcut) return null;
|
|
4113
4832
|
const label = formatShortcut(focusShortcut);
|
|
@@ -4305,6 +5024,7 @@ var Composer = react.forwardRef(function Composer2(props, ref) {
|
|
|
4305
5024
|
toolbarExtras,
|
|
4306
5025
|
closeMenusOnOutsideClick = true,
|
|
4307
5026
|
mode = "markdown",
|
|
5027
|
+
variant = "compact",
|
|
4308
5028
|
multiline = true,
|
|
4309
5029
|
submitOnEnter = true,
|
|
4310
5030
|
smartNewline = true,
|
|
@@ -4333,6 +5053,7 @@ var Composer = react.forwardRef(function Composer2(props, ref) {
|
|
|
4333
5053
|
closeMenusOnOutsideClick,
|
|
4334
5054
|
attachmentOptions,
|
|
4335
5055
|
mode,
|
|
5056
|
+
variant,
|
|
4336
5057
|
multiline,
|
|
4337
5058
|
submitOnEnter,
|
|
4338
5059
|
smartNewline,
|
|
@@ -4367,6 +5088,7 @@ var Composer = react.forwardRef(function Composer2(props, ref) {
|
|
|
4367
5088
|
isStreaming: !!isStreaming,
|
|
4368
5089
|
toolbarExtras,
|
|
4369
5090
|
mode,
|
|
5091
|
+
variant,
|
|
4370
5092
|
multiline
|
|
4371
5093
|
}
|
|
4372
5094
|
),
|
|
@@ -4387,6 +5109,7 @@ var RICH_NODES = [
|
|
|
4387
5109
|
MarkdownTokenNode,
|
|
4388
5110
|
BlockParagraphNode,
|
|
4389
5111
|
LinkTextNode,
|
|
5112
|
+
CodeTokenNode,
|
|
4390
5113
|
BLOCK_PARAGRAPH_REPLACEMENT
|
|
4391
5114
|
];
|
|
4392
5115
|
var PLAIN_NODES = [MentionNode];
|
|
@@ -4402,6 +5125,7 @@ function ComposerCard({
|
|
|
4402
5125
|
isStreaming,
|
|
4403
5126
|
toolbarExtras,
|
|
4404
5127
|
mode,
|
|
5128
|
+
variant,
|
|
4405
5129
|
multiline
|
|
4406
5130
|
}) {
|
|
4407
5131
|
const { webEnabled, isDraggingFiles, classNames, sx } = useComposerContext();
|
|
@@ -4422,7 +5146,8 @@ function ComposerCard({
|
|
|
4422
5146
|
"div",
|
|
4423
5147
|
{
|
|
4424
5148
|
"data-composer-root": "",
|
|
4425
|
-
"data-composer-
|
|
5149
|
+
"data-composer-variant": variant,
|
|
5150
|
+
"data-composer-inline": variant === "full" && !multiline ? "" : void 0,
|
|
4426
5151
|
"data-composer-web": webEnabled ? "" : void 0,
|
|
4427
5152
|
"data-composer-dragging": isDraggingFiles ? "" : void 0,
|
|
4428
5153
|
...card,
|
|
@@ -4452,6 +5177,7 @@ function ComposerCard({
|
|
|
4452
5177
|
{
|
|
4453
5178
|
placeholder,
|
|
4454
5179
|
mode,
|
|
5180
|
+
variant,
|
|
4455
5181
|
multiline,
|
|
4456
5182
|
handleRef,
|
|
4457
5183
|
onSend,
|
|
@@ -4471,6 +5197,7 @@ function ComposerCard({
|
|
|
4471
5197
|
function ComposerInner({
|
|
4472
5198
|
placeholder,
|
|
4473
5199
|
mode,
|
|
5200
|
+
variant,
|
|
4474
5201
|
multiline,
|
|
4475
5202
|
handleRef,
|
|
4476
5203
|
onSend,
|
|
@@ -4498,6 +5225,9 @@ function ComposerInner({
|
|
|
4498
5225
|
const [hasText, setHasText] = react.useState(
|
|
4499
5226
|
!!initialValue && initialValue.trim().length > 0
|
|
4500
5227
|
);
|
|
5228
|
+
const [isMultiLine, setIsMultiLine] = react.useState(
|
|
5229
|
+
!!initialValue && initialValue.includes("\n")
|
|
5230
|
+
);
|
|
4501
5231
|
const onSendRef = react.useRef(onSend);
|
|
4502
5232
|
onSendRef.current = onSend;
|
|
4503
5233
|
const refocusOnSubmitRef = react.useRef(refocusOnSubmit);
|
|
@@ -4576,8 +5306,10 @@ function ComposerInner({
|
|
|
4576
5306
|
react.useEffect(() => {
|
|
4577
5307
|
return editor.registerUpdateListener(() => {
|
|
4578
5308
|
editor.getEditorState().read(() => {
|
|
4579
|
-
const
|
|
4580
|
-
|
|
5309
|
+
const root = lexical.$getRoot();
|
|
5310
|
+
const text = root.getTextContent();
|
|
5311
|
+
setHasText(text.trim().length > 0);
|
|
5312
|
+
setIsMultiLine(root.getChildrenSize() > 1 || text.includes("\n"));
|
|
4581
5313
|
});
|
|
4582
5314
|
});
|
|
4583
5315
|
}, [editor]);
|
|
@@ -4593,8 +5325,10 @@ function ComposerInner({
|
|
|
4593
5325
|
}
|
|
4594
5326
|
});
|
|
4595
5327
|
}, [editor, registerRunPrompt, submit]);
|
|
4596
|
-
const
|
|
4597
|
-
const
|
|
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(
|
|
4598
5332
|
SendButton,
|
|
4599
5333
|
{
|
|
4600
5334
|
canSend: (
|
|
@@ -4607,38 +5341,37 @@ function ComposerInner({
|
|
|
4607
5341
|
onStop
|
|
4608
5342
|
}
|
|
4609
5343
|
);
|
|
4610
|
-
|
|
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: [
|
|
4611
5349
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4612
5350
|
EditorShell,
|
|
4613
5351
|
{
|
|
4614
5352
|
placeholder,
|
|
4615
5353
|
mode,
|
|
5354
|
+
variant,
|
|
4616
5355
|
multiline,
|
|
5356
|
+
expanded: isCompact && isMultiLine,
|
|
4617
5357
|
header: /* @__PURE__ */ jsxRuntime.jsx(AttachmentTray, {}),
|
|
4618
5358
|
toolbar: toolbarSlot,
|
|
4619
5359
|
sendButton: sendButtonSlot,
|
|
4620
|
-
footer:
|
|
5360
|
+
footer: mermaidActive && !isCompact ? /* @__PURE__ */ jsxRuntime.jsx(MermaidPreview, {}) : null
|
|
4621
5361
|
}
|
|
4622
5362
|
),
|
|
4623
5363
|
/* @__PURE__ */ jsxRuntime.jsx(KeyboardPlugin, { onSubmit: submit }),
|
|
4624
5364
|
/* @__PURE__ */ jsxRuntime.jsx(AutoFocusPlugin, { enabled: !!autoFocus }),
|
|
4625
5365
|
/* @__PURE__ */ jsxRuntime.jsx(PasteDropPlugin, {}),
|
|
4626
5366
|
markdownEnabled && /* @__PURE__ */ jsxRuntime.jsx(MarkdownPlugin, {}),
|
|
4627
|
-
features.slashCommands &&
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
onSubmit: submit
|
|
4632
|
-
}
|
|
4633
|
-
),
|
|
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}`)),
|
|
4634
5371
|
features.mentions && /* @__PURE__ */ jsxRuntime.jsx(MentionPlugin, { config: features.mentions }),
|
|
4635
5372
|
features.ghostedAutoComplete && /* @__PURE__ */ jsxRuntime.jsx(GhostedAutoCompletePlugin, { config: features.ghostedAutoComplete })
|
|
4636
5373
|
] });
|
|
4637
|
-
}
|
|
4638
|
-
function MermaidSlot() {
|
|
4639
|
-
const { features, mode } = useComposerContext();
|
|
4640
|
-
if (mode !== "markdown" || !features.mermaid) return null;
|
|
4641
|
-
return /* @__PURE__ */ jsxRuntime.jsx(MermaidPlugin, {});
|
|
5374
|
+
return mermaidActive ? /* @__PURE__ */ jsxRuntime.jsx(MermaidProvider, { children: content }) : content;
|
|
4642
5375
|
}
|
|
4643
5376
|
function SuggestionRow({ items, onSelect, className }) {
|
|
4644
5377
|
const { icons } = useComposerContext();
|