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.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createContext, forwardRef, useMemo, useState, useRef, useCallback, useEffect, useContext,
|
|
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,
|
|
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
|
|
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
|
|
818
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -1508,11 +1619,6 @@ function $isInsideCodeFence() {
|
|
|
1508
1619
|
const block = $detectBlockFor(top);
|
|
1509
1620
|
return block.kind === "code-line" || block.kind === "code-fence-open";
|
|
1510
1621
|
}
|
|
1511
|
-
function $hasMultiLineContent() {
|
|
1512
|
-
const root = $getRoot();
|
|
1513
|
-
if (root.getChildrenSize() > 1) return true;
|
|
1514
|
-
return root.getTextContent().includes("\n");
|
|
1515
|
-
}
|
|
1516
1622
|
function $offsetWithinParagraph(paragraph, point) {
|
|
1517
1623
|
if (point.type === "element") {
|
|
1518
1624
|
const children = paragraph.getChildren();
|
|
@@ -1605,10 +1711,8 @@ function KeyboardPlugin({ onSubmit }) {
|
|
|
1605
1711
|
return true;
|
|
1606
1712
|
}
|
|
1607
1713
|
let inCodeFence = false;
|
|
1608
|
-
let hasMultiLine = false;
|
|
1609
1714
|
editor.getEditorState().read(() => {
|
|
1610
1715
|
inCodeFence = $isInsideCodeFence();
|
|
1611
|
-
hasMultiLine = $hasMultiLineContent();
|
|
1612
1716
|
});
|
|
1613
1717
|
if (inCodeFence) {
|
|
1614
1718
|
if (!multiline) {
|
|
@@ -1630,7 +1734,6 @@ function KeyboardPlugin({ onSubmit }) {
|
|
|
1630
1734
|
return true;
|
|
1631
1735
|
}
|
|
1632
1736
|
}
|
|
1633
|
-
if (smartNewline && hasMultiLine) return false;
|
|
1634
1737
|
if (!submitOnEnter) return false;
|
|
1635
1738
|
return trySubmit(event);
|
|
1636
1739
|
},
|
|
@@ -1760,6 +1863,134 @@ function PasteDropPlugin() {
|
|
|
1760
1863
|
return null;
|
|
1761
1864
|
}
|
|
1762
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
|
+
|
|
1763
1994
|
// src/plugins/markdown-tokenizer.ts
|
|
1764
1995
|
var PAIRED_PATTERNS = [
|
|
1765
1996
|
{ open: "**", close: "**", format: "bold" },
|
|
@@ -1871,6 +2102,13 @@ function readCurrentChildren(paragraph) {
|
|
|
1871
2102
|
});
|
|
1872
2103
|
} else if ($isMarkdownTokenNode(child)) {
|
|
1873
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
|
+
});
|
|
1874
2112
|
} else if ($isTextNode(child)) {
|
|
1875
2113
|
out.push({
|
|
1876
2114
|
kind: "text",
|
|
@@ -1893,6 +2131,7 @@ function nodesEqual(a, b) {
|
|
|
1893
2131
|
if (aKind !== bKind) return false;
|
|
1894
2132
|
if (ai.text !== bi.text) return false;
|
|
1895
2133
|
if (ai.format !== bi.format) return false;
|
|
2134
|
+
if (ai.codeKind !== bi.codeKind) return false;
|
|
1896
2135
|
}
|
|
1897
2136
|
return true;
|
|
1898
2137
|
}
|
|
@@ -2060,7 +2299,18 @@ function $applyStyling(paragraph, block, mode) {
|
|
|
2060
2299
|
trailingDropForOffsetMap = body.length;
|
|
2061
2300
|
body = "";
|
|
2062
2301
|
} else if (body.length > 0) {
|
|
2063
|
-
|
|
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
|
+
}
|
|
2064
2314
|
}
|
|
2065
2315
|
} else if (block.kind === "hr") {
|
|
2066
2316
|
if (body.length > 0) {
|
|
@@ -2148,6 +2398,8 @@ function $applyStyling(paragraph, block, mode) {
|
|
|
2148
2398
|
for (const node of desired) {
|
|
2149
2399
|
if (node.kind === "token") {
|
|
2150
2400
|
paragraph.append($createMarkdownTokenNode(node.text));
|
|
2401
|
+
} else if (node.kind === "code") {
|
|
2402
|
+
paragraph.append($createCodeTokenNode(node.text, node.codeKind));
|
|
2151
2403
|
} else if (node.kind === "link") {
|
|
2152
2404
|
const t = $createLinkTextNode(node.text, node.url ?? "");
|
|
2153
2405
|
if (node.format !== 0) t.setFormat(node.format);
|
|
@@ -2409,10 +2661,13 @@ async function loadMermaid() {
|
|
|
2409
2661
|
function svgToDataUri(svg) {
|
|
2410
2662
|
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
|
|
2411
2663
|
}
|
|
2412
|
-
|
|
2664
|
+
var MermaidContext = createContext(null);
|
|
2665
|
+
function useMermaidContext() {
|
|
2666
|
+
return useContext(MermaidContext);
|
|
2667
|
+
}
|
|
2668
|
+
function MermaidProvider({ children }) {
|
|
2413
2669
|
const [editor] = useLexicalComposerContext();
|
|
2414
|
-
const { features,
|
|
2415
|
-
const { sparkle: SparkleIcon } = icons;
|
|
2670
|
+
const { features, renderDiagram } = useComposerContext();
|
|
2416
2671
|
const [diagrams, setDiagrams] = useState([]);
|
|
2417
2672
|
const keepSource = typeof features.mermaid === "object" ? features.mermaid.keepSource !== false : true;
|
|
2418
2673
|
useEffect(() => {
|
|
@@ -2420,10 +2675,10 @@ function MermaidPlugin() {
|
|
|
2420
2675
|
editor.getEditorState().read(() => {
|
|
2421
2676
|
const found = [];
|
|
2422
2677
|
const root = $getRoot();
|
|
2423
|
-
const
|
|
2678
|
+
const children2 = root.getChildren();
|
|
2424
2679
|
let i = 0;
|
|
2425
|
-
while (i <
|
|
2426
|
-
const opener =
|
|
2680
|
+
while (i < children2.length) {
|
|
2681
|
+
const opener = children2[i];
|
|
2427
2682
|
if (!$isParagraphNode(opener) || !FENCE_OPEN_MERMAID.test(opener.getTextContent())) {
|
|
2428
2683
|
i++;
|
|
2429
2684
|
continue;
|
|
@@ -2431,8 +2686,8 @@ function MermaidPlugin() {
|
|
|
2431
2686
|
const paragraphKeys = [opener.getKey()];
|
|
2432
2687
|
const codeLines = [];
|
|
2433
2688
|
let j = i + 1;
|
|
2434
|
-
while (j <
|
|
2435
|
-
const next =
|
|
2689
|
+
while (j < children2.length) {
|
|
2690
|
+
const next = children2[j];
|
|
2436
2691
|
if (!$isParagraphNode(next)) break;
|
|
2437
2692
|
const text = next.getTextContent();
|
|
2438
2693
|
paragraphKeys.push(next.getKey());
|
|
@@ -2496,26 +2751,190 @@ function MermaidPlugin() {
|
|
|
2496
2751
|
hiddenKeysRef.current.clear();
|
|
2497
2752
|
};
|
|
2498
2753
|
}, [editor]);
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
"composer-mermaid",
|
|
2503
|
-
classNames,
|
|
2504
|
-
sx
|
|
2754
|
+
const value = useMemo(
|
|
2755
|
+
() => ({ diagrams, renderDiagram }),
|
|
2756
|
+
[diagrams, renderDiagram]
|
|
2505
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);
|
|
2506
2859
|
return /* @__PURE__ */ jsxs("div", { ...preview, children: [
|
|
2507
2860
|
/* @__PURE__ */ jsxs("div", { className: "composer-mermaid-head", children: [
|
|
2508
2861
|
/* @__PURE__ */ jsx(SparkleIcon, {}),
|
|
2509
2862
|
"Diagram preview"
|
|
2510
2863
|
] }),
|
|
2511
|
-
/* @__PURE__ */ jsx("div", { className: "composer-mermaid-row", children: diagrams.map((d) => /* @__PURE__ */ jsx(
|
|
2512
|
-
|
|
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",
|
|
2513
2904
|
{
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
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
|
+
)
|
|
2519
2938
|
] });
|
|
2520
2939
|
}
|
|
2521
2940
|
function DiagramTile({ diagram, renderDiagram }) {
|
|
@@ -2537,12 +2956,9 @@ function ConsumerTile({
|
|
|
2537
2956
|
}
|
|
2538
2957
|
return /* @__PURE__ */ jsx("div", { className: "composer-mermaid-tile", children: content });
|
|
2539
2958
|
}
|
|
2540
|
-
function
|
|
2541
|
-
const { icons } = useComposerContext();
|
|
2542
|
-
const { zoom: ZoomIcon } = icons;
|
|
2959
|
+
function useDiagramSvg(diagram) {
|
|
2543
2960
|
const [svg, setSvg] = useState(null);
|
|
2544
2961
|
const [error, setError] = useState(null);
|
|
2545
|
-
const [zoom, setZoom] = useState(false);
|
|
2546
2962
|
const [mermaidMissing, setMermaidMissing] = useState(false);
|
|
2547
2963
|
const renderId = useMemo(
|
|
2548
2964
|
() => `mermaid-${diagram.id}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -2575,6 +2991,13 @@ function MermaidTile({ diagram }) {
|
|
|
2575
2991
|
cancelled = true;
|
|
2576
2992
|
};
|
|
2577
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);
|
|
2578
3001
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2579
3002
|
/* @__PURE__ */ jsx(
|
|
2580
3003
|
"button",
|
|
@@ -2610,6 +3033,35 @@ function MermaidTile({ diagram }) {
|
|
|
2610
3033
|
)
|
|
2611
3034
|
] });
|
|
2612
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
|
+
}
|
|
2613
3065
|
function SlashMenu({
|
|
2614
3066
|
options,
|
|
2615
3067
|
selectedIndex,
|
|
@@ -3872,8 +4324,284 @@ function AttachmentTypePicker({
|
|
|
3872
4324
|
)
|
|
3873
4325
|
] });
|
|
3874
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
|
+
}
|
|
3875
4594
|
var TOOLBAR_BTN_BASE = "composer-toolbar-btn";
|
|
3876
|
-
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 }) {
|
|
3877
4605
|
const {
|
|
3878
4606
|
features,
|
|
3879
4607
|
attachmentsConfig,
|
|
@@ -3972,6 +4700,8 @@ function Toolbar({ extras }) {
|
|
|
3972
4700
|
]
|
|
3973
4701
|
}
|
|
3974
4702
|
),
|
|
4703
|
+
/* @__PURE__ */ jsx(CustomActionButtons, { submit: submit ?? (() => {
|
|
4704
|
+
}) }),
|
|
3975
4705
|
extras
|
|
3976
4706
|
] });
|
|
3977
4707
|
}
|
|
@@ -4065,7 +4795,6 @@ function HintBar({ hint }) {
|
|
|
4065
4795
|
const {
|
|
4066
4796
|
multiline,
|
|
4067
4797
|
submitOnEnter,
|
|
4068
|
-
smartNewline,
|
|
4069
4798
|
focusShortcut,
|
|
4070
4799
|
classNames,
|
|
4071
4800
|
sx
|
|
@@ -4079,16 +4808,6 @@ function HintBar({ hint }) {
|
|
|
4079
4808
|
" to send."
|
|
4080
4809
|
] });
|
|
4081
4810
|
}
|
|
4082
|
-
if (smartNewline && submitOnEnter) {
|
|
4083
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4084
|
-
"Press ",
|
|
4085
|
-
/* @__PURE__ */ jsx(Key, { children: "Enter" }),
|
|
4086
|
-
" to send a single line,",
|
|
4087
|
-
" ",
|
|
4088
|
-
/* @__PURE__ */ jsx(Key, { children: "\u2318/Ctrl + Enter" }),
|
|
4089
|
-
" to send once you've started a new line."
|
|
4090
|
-
] });
|
|
4091
|
-
}
|
|
4092
4811
|
if (!submitOnEnter) {
|
|
4093
4812
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4094
4813
|
"Press ",
|
|
@@ -4105,7 +4824,7 @@ function HintBar({ hint }) {
|
|
|
4105
4824
|
/* @__PURE__ */ jsx(Key, { children: "Shift + Enter" }),
|
|
4106
4825
|
" for newline."
|
|
4107
4826
|
] });
|
|
4108
|
-
}, [multiline, submitOnEnter
|
|
4827
|
+
}, [multiline, submitOnEnter]);
|
|
4109
4828
|
const focusHint = useMemo(() => {
|
|
4110
4829
|
if (!focusShortcut) return null;
|
|
4111
4830
|
const label = formatShortcut(focusShortcut);
|
|
@@ -4303,6 +5022,7 @@ var Composer = forwardRef(function Composer2(props, ref) {
|
|
|
4303
5022
|
toolbarExtras,
|
|
4304
5023
|
closeMenusOnOutsideClick = true,
|
|
4305
5024
|
mode = "markdown",
|
|
5025
|
+
variant = "compact",
|
|
4306
5026
|
multiline = true,
|
|
4307
5027
|
submitOnEnter = true,
|
|
4308
5028
|
smartNewline = true,
|
|
@@ -4331,6 +5051,7 @@ var Composer = forwardRef(function Composer2(props, ref) {
|
|
|
4331
5051
|
closeMenusOnOutsideClick,
|
|
4332
5052
|
attachmentOptions,
|
|
4333
5053
|
mode,
|
|
5054
|
+
variant,
|
|
4334
5055
|
multiline,
|
|
4335
5056
|
submitOnEnter,
|
|
4336
5057
|
smartNewline,
|
|
@@ -4365,6 +5086,7 @@ var Composer = forwardRef(function Composer2(props, ref) {
|
|
|
4365
5086
|
isStreaming: !!isStreaming,
|
|
4366
5087
|
toolbarExtras,
|
|
4367
5088
|
mode,
|
|
5089
|
+
variant,
|
|
4368
5090
|
multiline
|
|
4369
5091
|
}
|
|
4370
5092
|
),
|
|
@@ -4385,6 +5107,7 @@ var RICH_NODES = [
|
|
|
4385
5107
|
MarkdownTokenNode,
|
|
4386
5108
|
BlockParagraphNode,
|
|
4387
5109
|
LinkTextNode,
|
|
5110
|
+
CodeTokenNode,
|
|
4388
5111
|
BLOCK_PARAGRAPH_REPLACEMENT
|
|
4389
5112
|
];
|
|
4390
5113
|
var PLAIN_NODES = [MentionNode];
|
|
@@ -4400,6 +5123,7 @@ function ComposerCard({
|
|
|
4400
5123
|
isStreaming,
|
|
4401
5124
|
toolbarExtras,
|
|
4402
5125
|
mode,
|
|
5126
|
+
variant,
|
|
4403
5127
|
multiline
|
|
4404
5128
|
}) {
|
|
4405
5129
|
const { webEnabled, isDraggingFiles, classNames, sx } = useComposerContext();
|
|
@@ -4420,7 +5144,8 @@ function ComposerCard({
|
|
|
4420
5144
|
"div",
|
|
4421
5145
|
{
|
|
4422
5146
|
"data-composer-root": "",
|
|
4423
|
-
"data-composer-
|
|
5147
|
+
"data-composer-variant": variant,
|
|
5148
|
+
"data-composer-inline": variant === "full" && !multiline ? "" : void 0,
|
|
4424
5149
|
"data-composer-web": webEnabled ? "" : void 0,
|
|
4425
5150
|
"data-composer-dragging": isDraggingFiles ? "" : void 0,
|
|
4426
5151
|
...card,
|
|
@@ -4450,6 +5175,7 @@ function ComposerCard({
|
|
|
4450
5175
|
{
|
|
4451
5176
|
placeholder,
|
|
4452
5177
|
mode,
|
|
5178
|
+
variant,
|
|
4453
5179
|
multiline,
|
|
4454
5180
|
handleRef,
|
|
4455
5181
|
onSend,
|
|
@@ -4469,6 +5195,7 @@ function ComposerCard({
|
|
|
4469
5195
|
function ComposerInner({
|
|
4470
5196
|
placeholder,
|
|
4471
5197
|
mode,
|
|
5198
|
+
variant,
|
|
4472
5199
|
multiline,
|
|
4473
5200
|
handleRef,
|
|
4474
5201
|
onSend,
|
|
@@ -4496,6 +5223,9 @@ function ComposerInner({
|
|
|
4496
5223
|
const [hasText, setHasText] = useState(
|
|
4497
5224
|
!!initialValue && initialValue.trim().length > 0
|
|
4498
5225
|
);
|
|
5226
|
+
const [isMultiLine, setIsMultiLine] = useState(
|
|
5227
|
+
!!initialValue && initialValue.includes("\n")
|
|
5228
|
+
);
|
|
4499
5229
|
const onSendRef = useRef(onSend);
|
|
4500
5230
|
onSendRef.current = onSend;
|
|
4501
5231
|
const refocusOnSubmitRef = useRef(refocusOnSubmit);
|
|
@@ -4574,8 +5304,10 @@ function ComposerInner({
|
|
|
4574
5304
|
useEffect(() => {
|
|
4575
5305
|
return editor.registerUpdateListener(() => {
|
|
4576
5306
|
editor.getEditorState().read(() => {
|
|
4577
|
-
const
|
|
4578
|
-
|
|
5307
|
+
const root = $getRoot();
|
|
5308
|
+
const text = root.getTextContent();
|
|
5309
|
+
setHasText(text.trim().length > 0);
|
|
5310
|
+
setIsMultiLine(root.getChildrenSize() > 1 || text.includes("\n"));
|
|
4579
5311
|
});
|
|
4580
5312
|
});
|
|
4581
5313
|
}, [editor]);
|
|
@@ -4591,8 +5323,10 @@ function ComposerInner({
|
|
|
4591
5323
|
}
|
|
4592
5324
|
});
|
|
4593
5325
|
}, [editor, registerRunPrompt, submit]);
|
|
4594
|
-
const
|
|
4595
|
-
const
|
|
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(
|
|
4596
5330
|
SendButton,
|
|
4597
5331
|
{
|
|
4598
5332
|
canSend: (
|
|
@@ -4605,38 +5339,37 @@ function ComposerInner({
|
|
|
4605
5339
|
onStop
|
|
4606
5340
|
}
|
|
4607
5341
|
);
|
|
4608
|
-
|
|
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: [
|
|
4609
5347
|
/* @__PURE__ */ jsx(
|
|
4610
5348
|
EditorShell,
|
|
4611
5349
|
{
|
|
4612
5350
|
placeholder,
|
|
4613
5351
|
mode,
|
|
5352
|
+
variant,
|
|
4614
5353
|
multiline,
|
|
5354
|
+
expanded: isCompact && isMultiLine,
|
|
4615
5355
|
header: /* @__PURE__ */ jsx(AttachmentTray, {}),
|
|
4616
5356
|
toolbar: toolbarSlot,
|
|
4617
5357
|
sendButton: sendButtonSlot,
|
|
4618
|
-
footer:
|
|
5358
|
+
footer: mermaidActive && !isCompact ? /* @__PURE__ */ jsx(MermaidPreview, {}) : null
|
|
4619
5359
|
}
|
|
4620
5360
|
),
|
|
4621
5361
|
/* @__PURE__ */ jsx(KeyboardPlugin, { onSubmit: submit }),
|
|
4622
5362
|
/* @__PURE__ */ jsx(AutoFocusPlugin, { enabled: !!autoFocus }),
|
|
4623
5363
|
/* @__PURE__ */ jsx(PasteDropPlugin, {}),
|
|
4624
5364
|
markdownEnabled && /* @__PURE__ */ jsx(MarkdownPlugin, {}),
|
|
4625
|
-
features.slashCommands &&
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
onSubmit: submit
|
|
4630
|
-
}
|
|
4631
|
-
),
|
|
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}`)),
|
|
4632
5369
|
features.mentions && /* @__PURE__ */ jsx(MentionPlugin, { config: features.mentions }),
|
|
4633
5370
|
features.ghostedAutoComplete && /* @__PURE__ */ jsx(GhostedAutoCompletePlugin, { config: features.ghostedAutoComplete })
|
|
4634
5371
|
] });
|
|
4635
|
-
}
|
|
4636
|
-
function MermaidSlot() {
|
|
4637
|
-
const { features, mode } = useComposerContext();
|
|
4638
|
-
if (mode !== "markdown" || !features.mermaid) return null;
|
|
4639
|
-
return /* @__PURE__ */ jsx(MermaidPlugin, {});
|
|
5372
|
+
return mermaidActive ? /* @__PURE__ */ jsx(MermaidProvider, { children: content }) : content;
|
|
4640
5373
|
}
|
|
4641
5374
|
function SuggestionRow({ items, onSelect, className }) {
|
|
4642
5375
|
const { icons } = useComposerContext();
|