@bitbaum/chatkit 0.8.2 → 0.10.0
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 +55 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/markdown.d.ts +39 -5
- package/dist/markdown.d.ts.map +1 -1
- package/dist/markdown.js +108 -11
- package/dist/markdown.js.map +1 -1
- package/dist/react/AnswerActions.d.ts +39 -0
- package/dist/react/AnswerActions.d.ts.map +1 -0
- package/dist/react/AnswerActions.js +49 -0
- package/dist/react/AnswerActions.js.map +1 -0
- package/dist/react/AttachMenu.d.ts +11 -2
- package/dist/react/AttachMenu.d.ts.map +1 -1
- package/dist/react/AttachMenu.js +21 -24
- package/dist/react/AttachMenu.js.map +1 -1
- package/dist/react/Markdown.d.ts +53 -4
- package/dist/react/Markdown.d.ts.map +1 -1
- package/dist/react/Markdown.js +92 -28
- package/dist/react/Markdown.js.map +1 -1
- package/dist/react/Sheet.d.ts +20 -0
- package/dist/react/Sheet.d.ts.map +1 -0
- package/dist/react/Sheet.js +37 -0
- package/dist/react/Sheet.js.map +1 -0
- package/dist/react/Thread.d.ts +19 -2
- package/dist/react/Thread.d.ts.map +1 -1
- package/dist/react/Thread.js +10 -8
- package/dist/react/Thread.js.map +1 -1
- package/dist/react/icons.d.ts +3 -0
- package/dist/react/icons.d.ts.map +1 -1
- package/dist/react/icons.js +3 -0
- package/dist/react/icons.js.map +1 -1
- package/dist/react/index.d.ts +7 -2
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +4 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/use-speech.d.ts +25 -0
- package/dist/react/use-speech.d.ts.map +1 -0
- package/dist/react/use-speech.js +66 -0
- package/dist/react/use-speech.js.map +1 -0
- package/dist/replies.d.ts +6 -3
- package/dist/replies.d.ts.map +1 -1
- package/dist/replies.js +8 -5
- package/dist/replies.js.map +1 -1
- package/dist/speech.d.ts +17 -0
- package/dist/speech.d.ts.map +1 -0
- package/dist/speech.js +81 -0
- package/dist/speech.js.map +1 -0
- package/package.json +1 -1
- package/styles.css +288 -0
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ interface that calls them.)
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
pnpm add @bitbaum/chatkit@^0.
|
|
18
|
+
pnpm add @bitbaum/chatkit@^0.9.0
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
**Install releases from npm, never a commit or a branch.** A semver range is
|
|
@@ -121,6 +121,31 @@ while a turn runs. Pass `replies` on a message to supply your own instead.
|
|
|
121
121
|
An app with its own thread uses `extractReplies(text)` and `<ChatReplies>`, so
|
|
122
122
|
the buttons look and behave the same everywhere.
|
|
123
123
|
|
|
124
|
+
The model leads. The instruction asks for replies on EVERY answer, the
|
|
125
|
+
recommended next step first, and the first button is drawn as the lead
|
|
126
|
+
(`ck-reply-lead`): on a phone the answer is read and the next move is one
|
|
127
|
+
tap, the way Grok does it. Only an answer that ends the conversation has no
|
|
128
|
+
buttons.
|
|
129
|
+
|
|
130
|
+
### Under every answer — Copy, Listen, Share
|
|
131
|
+
|
|
132
|
+
`AnswerActions` is the row under an answer, the same in every product:
|
|
133
|
+
|
|
134
|
+
```tsx
|
|
135
|
+
<AnswerActions text={answer} onRetry={retry} speaker={studioVoice} share={{ url }}>
|
|
136
|
+
<YourOwnButton />
|
|
137
|
+
</AnswerActions>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Copy copies the markdown. Listen reads the answer aloud as prose (headings,
|
|
141
|
+
emphasis, citations and code fences stripped — `plainTextForSpeech`) with the
|
|
142
|
+
browser's own voice, or with the `speaker` you pass (`{ speak(text), cancel() }`
|
|
143
|
+
— loki hands in its studio voice); while it reads, the row becomes a small
|
|
144
|
+
player: Stop and the time so far. Share opens the system share sheet with the
|
|
145
|
+
answer's prose (or what you pass), only where one exists. A button that does
|
|
146
|
+
nothing is never shown. `ChatThread` renders it under every answer; pass
|
|
147
|
+
`speaker` and `share` through.
|
|
148
|
+
|
|
124
149
|
### The next message while a turn runs
|
|
125
150
|
|
|
126
151
|
`queue` keeps Send live while `sending`, beside Stop, and the placeholder
|
|
@@ -154,7 +179,11 @@ Because Files takes anything, `isTextFile` decides afterwards what can be read
|
|
|
154
179
|
inline; a PDF gets a sentence, not a prompt full of bytes. With a mouse the
|
|
155
180
|
paperclip opens the file dialog directly — on a desktop all three are the
|
|
156
181
|
same dialog. A box that is not chatkit's composer renders `<AttachMenu
|
|
157
|
-
onFiles={…} />` rather than a bare `<input type="file">`.
|
|
182
|
+
onFiles={…} />` rather than a bare `<input type="file">`. An app with more to
|
|
183
|
+
bring into a message than files — one of the person's own things, a note the
|
|
184
|
+
model reads every time — passes `extra={(close) => <YourRows />}`: the rows
|
|
185
|
+
sit under the three sources in the same sheet, and the sheet then opens on a
|
|
186
|
+
mouse too, since a file dialog cannot hold them. Labels:
|
|
158
187
|
`labels={{ attachMenu: { title, close, sources: { camera, photos, files } } }}`.
|
|
159
188
|
|
|
160
189
|
### Picking files anywhere — `@bitbaum/chatkit/attach`
|
|
@@ -200,6 +229,30 @@ Every reason a file did not attach is a label — `labels={{ attachNotes: {
|
|
|
200
229
|
imageTooLarge: (name, mb) => … } }}` — so it reaches the reader in their
|
|
201
230
|
language.
|
|
202
231
|
|
|
232
|
+
### The look of an answer
|
|
233
|
+
|
|
234
|
+
`Markdown` renders what a model writes, never as HTML: paragraphs, headings
|
|
235
|
+
with their level, bullet, numbered, nested and task lists, tables (they scroll
|
|
236
|
+
sideways on a phone instead of breaking the page), rules, bold, italic, code,
|
|
237
|
+
links, web images and `[F1]` citation handles. Three things make an answer
|
|
238
|
+
read like the ChatGPT app's rather than a wall of text, and a model does all
|
|
239
|
+
three unprompted:
|
|
240
|
+
|
|
241
|
+
- **A list of named things becomes cards.** `1. **Option.** why` — every item
|
|
242
|
+
opening with a bold lead — renders as a badge (the number, or the emoji the
|
|
243
|
+
lead opens with: `**🤖 Robot.** …`), the name, and the body under it.
|
|
244
|
+
- **A table is a table**, with the header row kept.
|
|
245
|
+
- **Sources are listed once**, under the answer, from the handles the text
|
|
246
|
+
cites and the `citations` map you pass (`{ F1: { label, detail, href? } }`).
|
|
247
|
+
On a mouse they are a row of chips; on a touch screen ONE "Sources · n"
|
|
248
|
+
button opens a sheet with every record — label, detail, link — because a
|
|
249
|
+
row of chips on a phone was a row of cut-off words. `renderCitation`
|
|
250
|
+
replaces the in-text superscript with your own; `renderSources` replaces
|
|
251
|
+
the row with your own; `sources={false}` drops it. `SourcesRow` and `Sheet`
|
|
252
|
+
are exported for an app that lists sources from elsewhere.
|
|
253
|
+
|
|
254
|
+
`demo/screen.html` shows all of it.
|
|
255
|
+
|
|
203
256
|
### Look
|
|
204
257
|
|
|
205
258
|
Every colour, radius and face is a `--ck-*` variable. They default to
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
|
package/dist/markdown.d.ts
CHANGED
|
@@ -5,22 +5,32 @@
|
|
|
5
5
|
*
|
|
6
6
|
* From loki's `MarkdownText`, the one renderer in the fleet that four repos
|
|
7
7
|
* did NOT have: those rendered model output with the literal `**` still in it.
|
|
8
|
-
* Supports what answers use — paragraphs, bullet
|
|
9
|
-
* **bold**, *italic*, `code`,
|
|
10
|
-
*
|
|
8
|
+
* Supports what answers use — paragraphs, bullet, numbered, nested and task
|
|
9
|
+
* lists, headings with their level, tables, rules, **bold**, *italic*, `code`,
|
|
10
|
+
* fenced code, [links](url),  and `[F1]` citation handles — and
|
|
11
|
+
* deliberately nothing else.
|
|
11
12
|
*/
|
|
13
|
+
export type ListItem = {
|
|
14
|
+
text: string;
|
|
15
|
+
/** `- [ ]` / `- [x]`: a task, and whether it is done. */
|
|
16
|
+
task?: boolean;
|
|
17
|
+
/** One level of nesting: the items indented under this one. */
|
|
18
|
+
children?: ListItem[];
|
|
19
|
+
};
|
|
12
20
|
export type MarkdownBlock = {
|
|
13
21
|
kind: "p";
|
|
14
22
|
text: string;
|
|
15
23
|
} | {
|
|
16
24
|
kind: "h";
|
|
17
25
|
text: string;
|
|
26
|
+
level: 1 | 2 | 3;
|
|
18
27
|
} | {
|
|
19
28
|
kind: "ul";
|
|
20
|
-
items:
|
|
29
|
+
items: ListItem[];
|
|
21
30
|
} | {
|
|
22
31
|
kind: "ol";
|
|
23
|
-
items:
|
|
32
|
+
items: ListItem[];
|
|
33
|
+
start: number;
|
|
24
34
|
} | {
|
|
25
35
|
kind: "code";
|
|
26
36
|
lang: string;
|
|
@@ -28,8 +38,26 @@ export type MarkdownBlock = {
|
|
|
28
38
|
} | {
|
|
29
39
|
kind: "quote";
|
|
30
40
|
text: string;
|
|
41
|
+
} | {
|
|
42
|
+
kind: "table";
|
|
43
|
+
head: string[];
|
|
44
|
+
rows: string[][];
|
|
45
|
+
} | {
|
|
46
|
+
kind: "hr";
|
|
31
47
|
};
|
|
32
48
|
export declare function parseBlocks(text: string): MarkdownBlock[];
|
|
49
|
+
/**
|
|
50
|
+
* A list where every item opens with a bold lead — `**Title.** body` — is a
|
|
51
|
+
* list of named things, and reads as cards: a numbered or bulleted badge, the
|
|
52
|
+
* name, the body under it. That is how the ChatGPT app lays out "three
|
|
53
|
+
* options" or "two levels"; a model writes it that way unprompted.
|
|
54
|
+
*/
|
|
55
|
+
export declare function listLead(text: string): {
|
|
56
|
+
lead: string;
|
|
57
|
+
rest: string;
|
|
58
|
+
icon?: string;
|
|
59
|
+
} | null;
|
|
60
|
+
export declare function isCardList(items: ListItem[]): boolean;
|
|
33
61
|
export type InlineSpan = {
|
|
34
62
|
kind: "text";
|
|
35
63
|
text: string;
|
|
@@ -48,11 +76,17 @@ export type InlineSpan = {
|
|
|
48
76
|
kind: "link";
|
|
49
77
|
text: string;
|
|
50
78
|
href: string;
|
|
79
|
+
} | {
|
|
80
|
+
kind: "image";
|
|
81
|
+
alt: string;
|
|
82
|
+
src: string;
|
|
51
83
|
} | {
|
|
52
84
|
kind: "cite";
|
|
53
85
|
ids: string[];
|
|
54
86
|
};
|
|
55
87
|
export declare function parseInline(text: string): InlineSpan[];
|
|
88
|
+
/** Every citation handle an answer uses, in order of first use. */
|
|
89
|
+
export declare function citedIds(text: string): string[];
|
|
56
90
|
/**
|
|
57
91
|
* Only http(s), mailto and same-site paths become links. A `javascript:` URL in
|
|
58
92
|
* model output is text, never a link — the answer is written by a model that
|
package/dist/markdown.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,yDAAyD;IACzD,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,QAAQ,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,aAAa,GACrB;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC3B;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;CAAE,GAC7C;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,QAAQ,EAAE,CAAA;CAAE,GACjC;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC;AAmBnB,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CA2FzD;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAS3F;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,OAAO,CAErD;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC9B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAI9B;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,UAAU,EAAE,CAAA;CAAE,GACxD;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,UAAU,EAAE,CAAA;CAAE,GACpD;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC5C;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC3C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAKpC,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,CAmCtD;AAED,mEAAmE;AACnE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAM/C;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYpD;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEpD"}
|
package/dist/markdown.js
CHANGED
|
@@ -5,10 +5,26 @@
|
|
|
5
5
|
*
|
|
6
6
|
* From loki's `MarkdownText`, the one renderer in the fleet that four repos
|
|
7
7
|
* did NOT have: those rendered model output with the literal `**` still in it.
|
|
8
|
-
* Supports what answers use — paragraphs, bullet
|
|
9
|
-
* **bold**, *italic*, `code`,
|
|
10
|
-
*
|
|
8
|
+
* Supports what answers use — paragraphs, bullet, numbered, nested and task
|
|
9
|
+
* lists, headings with their level, tables, rules, **bold**, *italic*, `code`,
|
|
10
|
+
* fenced code, [links](url),  and `[F1]` citation handles — and
|
|
11
|
+
* deliberately nothing else.
|
|
11
12
|
*/
|
|
13
|
+
const BULLET_RE = /^([-*•])\s+(.*)$/;
|
|
14
|
+
const NUMBERED_RE = /^(\d+)[.)]\s+(.*)$/;
|
|
15
|
+
const TASK_RE = /^\[([ xX])\]\s+(.*)$/;
|
|
16
|
+
function listItem(text) {
|
|
17
|
+
const task = TASK_RE.exec(text);
|
|
18
|
+
if (task)
|
|
19
|
+
return { text: task[2] ?? "", task: task[1] !== " " };
|
|
20
|
+
return { text };
|
|
21
|
+
}
|
|
22
|
+
/** `| a | b |` → ["a", "b"]; a row may omit its outer pipes. */
|
|
23
|
+
function tableCells(line) {
|
|
24
|
+
const inner = line.replace(/^\|/, "").replace(/\|$/, "");
|
|
25
|
+
return inner.split(/(?<!\\)\|/).map((c) => c.replace(/\\\|/g, "|").trim());
|
|
26
|
+
}
|
|
27
|
+
const TABLE_RULE_RE = /^\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?$/;
|
|
12
28
|
export function parseBlocks(text) {
|
|
13
29
|
const blocks = [];
|
|
14
30
|
let list = null;
|
|
@@ -34,24 +50,64 @@ export function parseBlocks(text) {
|
|
|
34
50
|
list = null;
|
|
35
51
|
continue;
|
|
36
52
|
}
|
|
37
|
-
|
|
38
|
-
|
|
53
|
+
// A table: a header row, a rule of dashes, then rows. The rule is what
|
|
54
|
+
// tells a table from a sentence with a pipe in it.
|
|
55
|
+
if (line.includes("|") && TABLE_RULE_RE.test((lines[i + 1] ?? "").trim())) {
|
|
56
|
+
list = null;
|
|
57
|
+
const head = tableCells(line);
|
|
58
|
+
const rows = [];
|
|
59
|
+
i += 2;
|
|
60
|
+
while (i < lines.length && (lines[i] ?? "").trim().includes("|")) {
|
|
61
|
+
rows.push(tableCells((lines[i] ?? "").trim()));
|
|
62
|
+
i++;
|
|
63
|
+
}
|
|
64
|
+
i--;
|
|
65
|
+
blocks.push({ kind: "table", head, rows });
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (/^(-{3,}|\*{3,}|_{3,})$/.test(line)) {
|
|
69
|
+
list = null;
|
|
70
|
+
blocks.push({ kind: "hr" });
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
const bullet = BULLET_RE.exec(line);
|
|
74
|
+
const numbered = NUMBERED_RE.exec(line);
|
|
39
75
|
if (bullet || numbered) {
|
|
40
76
|
const kind = bullet ? "ul" : "ol";
|
|
77
|
+
const indent = raw.length - raw.trimStart().length;
|
|
78
|
+
const item = listItem((bullet ? bullet[2] : numbered[2]) ?? "");
|
|
79
|
+
// Indented under the previous item: one level of nesting, whichever
|
|
80
|
+
// marker it uses.
|
|
81
|
+
if (list && indent >= 2 && list.items.length > 0) {
|
|
82
|
+
const parent = list.items[list.items.length - 1];
|
|
83
|
+
(parent.children ??= []).push(item);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
41
86
|
if (!list || list.kind !== kind) {
|
|
42
|
-
list =
|
|
87
|
+
list =
|
|
88
|
+
kind === "ol"
|
|
89
|
+
? { kind: "ol", items: [], start: Number(numbered[1]) || 1 }
|
|
90
|
+
: { kind: "ul", items: [] };
|
|
43
91
|
blocks.push(list);
|
|
44
92
|
}
|
|
45
|
-
list.items.push(
|
|
93
|
+
list.items.push(item);
|
|
46
94
|
continue;
|
|
47
95
|
}
|
|
48
96
|
list = null;
|
|
49
|
-
|
|
50
|
-
|
|
97
|
+
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
98
|
+
if (heading) {
|
|
99
|
+
const level = Math.min(heading[1].length, 3);
|
|
100
|
+
blocks.push({ kind: "h", text: heading[2] ?? "", level });
|
|
51
101
|
}
|
|
52
102
|
else if (line.startsWith(">")) {
|
|
53
103
|
// A model's `>` leaked into prose was one of orangecat's fixed bugs.
|
|
54
|
-
|
|
104
|
+
// Consecutive quoted lines are one quote.
|
|
105
|
+
const prev = blocks[blocks.length - 1];
|
|
106
|
+
const quoted = line.replace(/^>\s?/, "");
|
|
107
|
+
if (prev && prev.kind === "quote" && (lines[i - 1] ?? "").trim().startsWith(">"))
|
|
108
|
+
prev.text += `\n${quoted}`;
|
|
109
|
+
else
|
|
110
|
+
blocks.push({ kind: "quote", text: quoted });
|
|
55
111
|
}
|
|
56
112
|
else {
|
|
57
113
|
const prev = blocks[blocks.length - 1];
|
|
@@ -64,7 +120,28 @@ export function parseBlocks(text) {
|
|
|
64
120
|
}
|
|
65
121
|
return blocks;
|
|
66
122
|
}
|
|
67
|
-
|
|
123
|
+
/**
|
|
124
|
+
* A list where every item opens with a bold lead — `**Title.** body` — is a
|
|
125
|
+
* list of named things, and reads as cards: a numbered or bulleted badge, the
|
|
126
|
+
* name, the body under it. That is how the ChatGPT app lays out "three
|
|
127
|
+
* options" or "two levels"; a model writes it that way unprompted.
|
|
128
|
+
*/
|
|
129
|
+
export function listLead(text) {
|
|
130
|
+
const m = /^\*\*([^*]+?)\*\*\s*(?:[—–:-]\s*)?([\s\S]*)$/.exec(text);
|
|
131
|
+
if (!m)
|
|
132
|
+
return null;
|
|
133
|
+
let lead = (m[1] ?? "").trim().replace(/[.:]$/, "");
|
|
134
|
+
// A lead that opens with an emoji — `**🤖 Robot.** …` — hands it to the
|
|
135
|
+
// badge: the icon tile the ChatGPT app draws, from the model's own choice.
|
|
136
|
+
const icon = /^(\p{Extended_Pictographic}\uFE0F?)\s+(.+)$/u.exec(lead);
|
|
137
|
+
if (icon)
|
|
138
|
+
lead = icon[2] ?? lead;
|
|
139
|
+
return { lead, rest: (m[2] ?? "").trim(), ...(icon ? { icon: icon[1] } : {}) };
|
|
140
|
+
}
|
|
141
|
+
export function isCardList(items) {
|
|
142
|
+
return items.length >= 2 && items.every((it) => listLead(it.text) !== null);
|
|
143
|
+
}
|
|
144
|
+
const INLINE_RE = /(`[^`]+`|!\[[^\]]*\]\([^)\s]+\)|\*\*[^*]+\*\*|\*[^*\s][^*]*\*|\[[^\]]+\]\([^)\s]+\)|\[[FD]\d+(?:,\s*[FD]\d+)*\]|https?:\/\/[^\s<>()]+[^\s<>().,;:!?'"])/g;
|
|
68
145
|
export function parseInline(text) {
|
|
69
146
|
const out = [];
|
|
70
147
|
for (const part of text.split(INLINE_RE)) {
|
|
@@ -85,6 +162,16 @@ export function parseInline(text) {
|
|
|
85
162
|
out.push({ kind: "em", text: inner, children: parseInline(inner) });
|
|
86
163
|
}
|
|
87
164
|
else {
|
|
165
|
+
const image = /^!\[([^\]]*)\]\(([^)\s]+)\)$/.exec(part);
|
|
166
|
+
if (image) {
|
|
167
|
+
// Only a web image: a data: or javascript: "image" from a model is text.
|
|
168
|
+
const src = safeHref(image[2] ?? "");
|
|
169
|
+
if (src && /^https?:/.test(src))
|
|
170
|
+
out.push({ kind: "image", alt: image[1] ?? "", src });
|
|
171
|
+
else
|
|
172
|
+
out.push({ kind: "text", text: image[1] ?? part });
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
88
175
|
const link = /^\[([^\]]+)\]\(([^)\s]+)\)$/.exec(part);
|
|
89
176
|
const href = link
|
|
90
177
|
? safeHref(link[2] ?? "")
|
|
@@ -101,6 +188,16 @@ export function parseInline(text) {
|
|
|
101
188
|
}
|
|
102
189
|
return out;
|
|
103
190
|
}
|
|
191
|
+
/** Every citation handle an answer uses, in order of first use. */
|
|
192
|
+
export function citedIds(text) {
|
|
193
|
+
const ids = [];
|
|
194
|
+
for (const m of text.matchAll(/\[([FD]\d+(?:,\s*[FD]\d+)*)\]/g)) {
|
|
195
|
+
for (const id of (m[1] ?? "").split(/,\s*/))
|
|
196
|
+
if (!ids.includes(id))
|
|
197
|
+
ids.push(id);
|
|
198
|
+
}
|
|
199
|
+
return ids;
|
|
200
|
+
}
|
|
104
201
|
/**
|
|
105
202
|
* Only http(s), mailto and same-site paths become links. A `javascript:` URL in
|
|
106
203
|
* model output is text, never a link — the answer is written by a model that
|
package/dist/markdown.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"markdown.js","sourceRoot":"","sources":["../src/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAoBH,MAAM,SAAS,GAAG,kBAAkB,CAAC;AACrC,MAAM,WAAW,GAAG,oBAAoB,CAAC;AACzC,MAAM,OAAO,GAAG,sBAAsB,CAAC;AAEvC,SAAS,QAAQ,CAAC,IAAY;IAC5B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;IAChE,OAAO,EAAE,IAAI,EAAE,CAAC;AAClB,CAAC;AAED,gEAAgE;AAChE,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzD,OAAO,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AAC7E,CAAC;AACD,MAAM,aAAa,GAAG,6CAA6C,CAAC;AAEpE,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,IAAI,IAAI,GAEN,IAAI,CAAC;IACP,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACxB,wEAAwE;QACxE,6BAA6B;QAC7B,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,IAAI,GAAG,IAAI,CAAC;YACZ,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;YAClC,MAAM,IAAI,GAAa,EAAE,CAAC;YAC1B,CAAC,EAAE,CAAC;YACJ,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;gBACtE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;gBAC1B,CAAC,EAAE,CAAC;YACN,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC3D,SAAS;QACX,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,GAAG,IAAI,CAAC;YACZ,SAAS;QACX,CAAC;QACD,uEAAuE;QACvE,mDAAmD;QACnD,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YAC1E,IAAI,GAAG,IAAI,CAAC;YACZ,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;YAC9B,MAAM,IAAI,GAAe,EAAE,CAAC;YAC5B,CAAC,IAAI,CAAC,CAAC;YACP,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;gBACjE,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;gBAC/C,CAAC,EAAE,CAAC;YACN,CAAC;YACD,CAAC,EAAE,CAAC;YACJ,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC3C,SAAS;QACX,CAAC;QACD,IAAI,wBAAwB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACxC,IAAI,GAAG,IAAI,CAAC;YACZ,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5B,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,MAAM,IAAI,QAAQ,EAAE,CAAC;YACvB,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;YAClC,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC,MAAM,CAAC;YACnD,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;YACjE,oEAAoE;YACpE,kBAAkB;YAClB,IAAI,IAAI,IAAI,MAAM,IAAI,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;gBAClD,CAAC,MAAM,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACpC,SAAS;YACX,CAAC;YACD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;gBAChC,IAAI;oBACF,IAAI,KAAK,IAAI;wBACX,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,QAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE;wBAC7D,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;gBAChC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpB,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtB,SAAS;QACX,CAAC;QACD,IAAI,GAAG,IAAI,CAAC;QACZ,MAAM,OAAO,GAAG,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/C,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAE,CAAC,MAAM,EAAE,CAAC,CAAc,CAAC;YAC3D,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5D,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAChC,qEAAqE;YACrE,0CAA0C;YAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACvC,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACzC,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC;gBAC9E,IAAI,CAAC,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;;gBACxB,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QACpD,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACvC,qDAAqD;YACrD,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE;gBAAE,IAAI,CAAC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;;gBAClF,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,CAAC,GAAG,8CAA8C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACpE,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IACpD,wEAAwE;IACxE,2EAA2E;IAC3E,MAAM,IAAI,GAAG,8CAA8C,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACvE,IAAI,IAAI;QAAE,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IACjC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;AACjF,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAiB;IAC1C,OAAO,KAAK,CAAC,MAAM,IAAI,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,CAAC;AAC9E,CAAC;AAcD,MAAM,SAAS,GACb,0JAA0J,CAAC;AAE7J,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,GAAG,GAAiB,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,IAAI,+BAA+B,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC/C,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACnE,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACtD,CAAC;aAAM,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3E,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAChC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1E,CAAC;aAAM,IAAI,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;YAChC,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtE,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACxD,IAAI,KAAK,EAAE,CAAC;gBACV,yEAAyE;gBACzE,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;gBACrC,IAAI,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;oBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;;oBAClF,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;gBACxD,SAAS;YACX,CAAC;YACD,MAAM,IAAI,GAAG,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACtD,MAAM,IAAI,GAAG,IAAI;gBACf,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;gBACzB,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;oBACzB,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;oBAChB,CAAC,CAAC,IAAI,CAAC;YACX,IAAI,IAAI,IAAI,IAAI;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;iBACrE,IAAI,CAAC,IAAI,IAAI,IAAI;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;;gBAChE,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,gCAAgC,CAAC,EAAE,CAAC;QAChE,KAAK,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACtB,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,CAAC;IACvD,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IAChC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;QACrB,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,CAAC,QAAQ,KAAK,SAAS;YAClF,CAAC,CAAC,CAAC,CAAC,IAAI;YACR,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import { type Speaker } from "../speech.js";
|
|
3
|
+
export type AnswerActionsLabels = {
|
|
4
|
+
copy: string;
|
|
5
|
+
copied: string;
|
|
6
|
+
listen: string;
|
|
7
|
+
stop: string;
|
|
8
|
+
share: string;
|
|
9
|
+
retry: string;
|
|
10
|
+
};
|
|
11
|
+
export declare const DEFAULT_ANSWER_ACTIONS_LABELS: AnswerActionsLabels;
|
|
12
|
+
/**
|
|
13
|
+
* The row under an answer — Copy, Listen, Share, Try again — the same in every
|
|
14
|
+
* product, so an answer is handled the same way wherever it was written.
|
|
15
|
+
*
|
|
16
|
+
* Every button is one that works here: Listen only where something can
|
|
17
|
+
* speak, Share only where the system share sheet exists (phones), Try again
|
|
18
|
+
* only when the caller can re-ask. A button that does nothing is worse than
|
|
19
|
+
* a missing one. While an answer is being read, a small player takes the
|
|
20
|
+
* row's place: a stop and the time read so far.
|
|
21
|
+
*
|
|
22
|
+
* `children` are the app's own buttons after these (loki's thumbs-down).
|
|
23
|
+
*/
|
|
24
|
+
export declare function AnswerActions({ text, onRetry, speaker, share, labels: overrides, children, }: {
|
|
25
|
+
/** The answer's markdown. Copied as written; read as prose. */
|
|
26
|
+
text: string;
|
|
27
|
+
onRetry?: () => void;
|
|
28
|
+
/** The app's voice. Without it, the browser's; without either, no Listen. */
|
|
29
|
+
speaker?: Speaker | null;
|
|
30
|
+
/** What the share sheet gets. Defaults to the answer's prose. `false` hides Share. */
|
|
31
|
+
share?: {
|
|
32
|
+
title?: string;
|
|
33
|
+
text?: string;
|
|
34
|
+
url?: string;
|
|
35
|
+
} | false;
|
|
36
|
+
labels?: Partial<AnswerActionsLabels>;
|
|
37
|
+
children?: ReactNode;
|
|
38
|
+
}): import("react").JSX.Element;
|
|
39
|
+
//# sourceMappingURL=AnswerActions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnswerActions.d.ts","sourceRoot":"","sources":["../../src/react/AnswerActions.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE7D,OAAO,EAAsB,KAAK,OAAO,EAAE,MAAM,cAAc,CAAC;AAOhE,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,eAAO,MAAM,6BAA6B,EAAE,mBAO3C,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,EAC5B,IAAI,EACJ,OAAO,EACP,OAAO,EACP,KAAK,EACL,MAAM,EAAE,SAAS,EACjB,QAAQ,GACT,EAAE;IACD,+DAA+D;IAC/D,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,sFAAsF;IACtF,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,CAAC;IAChE,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACtC,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,+BAoFA"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useSyncExternalStore } from "react";
|
|
4
|
+
import { formatElapsed } from "../composer.js";
|
|
5
|
+
import { plainTextForSpeech } from "../speech.js";
|
|
6
|
+
import { useClipboard } from "./hooks.js";
|
|
7
|
+
import { useSpeech } from "./use-speech.js";
|
|
8
|
+
import { IconCheck, IconCopy, IconRetry, IconShare, IconSpeaker, IconStop } from "./icons.js";
|
|
9
|
+
const noSubscribe = () => () => { };
|
|
10
|
+
export const DEFAULT_ANSWER_ACTIONS_LABELS = {
|
|
11
|
+
copy: "Copy",
|
|
12
|
+
copied: "Copied",
|
|
13
|
+
listen: "Listen",
|
|
14
|
+
stop: "Stop",
|
|
15
|
+
share: "Share",
|
|
16
|
+
retry: "Try again",
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* The row under an answer — Copy, Listen, Share, Try again — the same in every
|
|
20
|
+
* product, so an answer is handled the same way wherever it was written.
|
|
21
|
+
*
|
|
22
|
+
* Every button is one that works here: Listen only where something can
|
|
23
|
+
* speak, Share only where the system share sheet exists (phones), Try again
|
|
24
|
+
* only when the caller can re-ask. A button that does nothing is worse than
|
|
25
|
+
* a missing one. While an answer is being read, a small player takes the
|
|
26
|
+
* row's place: a stop and the time read so far.
|
|
27
|
+
*
|
|
28
|
+
* `children` are the app's own buttons after these (loki's thumbs-down).
|
|
29
|
+
*/
|
|
30
|
+
export function AnswerActions({ text, onRetry, speaker, share, labels: overrides, children, }) {
|
|
31
|
+
const labels = { ...DEFAULT_ANSWER_ACTIONS_LABELS, ...overrides };
|
|
32
|
+
const { copied, copy } = useClipboard();
|
|
33
|
+
const speech = useSpeech(speaker);
|
|
34
|
+
const canShare = useSyncExternalStore(noSubscribe, () => typeof navigator !== "undefined" && typeof navigator.share === "function", () => false);
|
|
35
|
+
if (speech.speaking) {
|
|
36
|
+
return (_jsxs("div", { className: "ck-turn-actions ck-player", role: "status", "aria-live": "polite", children: [_jsxs("button", { type: "button", className: "ck-action ck-action-stop", onClick: speech.stop, "aria-label": labels.stop, children: [_jsx(IconStop, {}), _jsx("span", { children: labels.stop })] }), _jsx("span", { className: "ck-player-wave", "aria-hidden": true, children: Array.from({ length: 7 }).map((_, i) => (_jsx("span", {}, i))) }), _jsx("span", { className: "ck-player-time", children: formatElapsed(speech.elapsed) })] }));
|
|
37
|
+
}
|
|
38
|
+
return (_jsxs("div", { className: "ck-turn-actions", children: [_jsxs("button", { type: "button", className: "ck-action", onClick: () => copy(text), "aria-label": copied ? labels.copied : labels.copy, children: [copied ? _jsx(IconCheck, {}) : _jsx(IconCopy, {}), _jsx("span", { children: copied ? labels.copied : labels.copy })] }), speech.supported && (_jsxs("button", { type: "button", className: "ck-action", onClick: () => speech.speak(text), "aria-label": labels.listen, children: [_jsx(IconSpeaker, {}), _jsx("span", { children: labels.listen })] })), canShare && share !== false && (_jsxs("button", { type: "button", className: "ck-action",
|
|
39
|
+
// A dismissed share sheet rejects; that is the person changing their
|
|
40
|
+
// mind, not an error to report.
|
|
41
|
+
onClick: () => void navigator
|
|
42
|
+
.share({
|
|
43
|
+
title: share?.title,
|
|
44
|
+
text: share?.text ?? plainTextForSpeech(text),
|
|
45
|
+
url: share?.url,
|
|
46
|
+
})
|
|
47
|
+
.catch(() => { }), "aria-label": labels.share, children: [_jsx(IconShare, {}), _jsx("span", { children: labels.share })] })), onRetry && (_jsxs("button", { type: "button", className: "ck-action", onClick: onRetry, children: [_jsx(IconRetry, {}), _jsx("span", { children: labels.retry })] })), children] }));
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=AnswerActions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AnswerActions.js","sourceRoot":"","sources":["../../src/react/AnswerActions.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,oBAAoB,EAAkB,MAAM,OAAO,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,kBAAkB,EAAgB,MAAM,cAAc,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE9F,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC;AAWnC,MAAM,CAAC,MAAM,6BAA6B,GAAwB;IAChE,IAAI,EAAE,MAAM;IACZ,MAAM,EAAE,QAAQ;IAChB,MAAM,EAAE,QAAQ;IAChB,IAAI,EAAE,MAAM;IACZ,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,WAAW;CACnB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,IAAI,EACJ,OAAO,EACP,OAAO,EACP,KAAK,EACL,MAAM,EAAE,SAAS,EACjB,QAAQ,GAWT;IACC,MAAM,MAAM,GAAG,EAAE,GAAG,6BAA6B,EAAE,GAAG,SAAS,EAAE,CAAC;IAClE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,YAAY,EAAE,CAAC;IACxC,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,QAAQ,GAAG,oBAAoB,CACnC,WAAW,EACX,GAAG,EAAE,CAAC,OAAO,SAAS,KAAK,WAAW,IAAI,OAAO,SAAS,CAAC,KAAK,KAAK,UAAU,EAC/E,GAAG,EAAE,CAAC,KAAK,CACZ,CAAC;IAEF,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,OAAO,CACL,eAAK,SAAS,EAAC,2BAA2B,EAAC,IAAI,EAAC,QAAQ,eAAW,QAAQ,aACzE,kBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,0BAA0B,EACpC,OAAO,EAAE,MAAM,CAAC,IAAI,gBACR,MAAM,CAAC,IAAI,aAEvB,KAAC,QAAQ,KAAG,EACZ,yBAAO,MAAM,CAAC,IAAI,GAAQ,IACnB,EACT,eAAM,SAAS,EAAC,gBAAgB,iCAC7B,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CACvC,iBAAW,CAAC,CAAI,CACjB,CAAC,GACG,EACP,eAAM,SAAS,EAAC,gBAAgB,YAAE,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC,GAAQ,IACnE,CACP,CAAC;IACJ,CAAC;IAED,OAAO,CACL,eAAK,SAAS,EAAC,iBAAiB,aAC9B,kBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,WAAW,EACrB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,gBACb,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,aAE/C,MAAM,CAAC,CAAC,CAAC,KAAC,SAAS,KAAG,CAAC,CAAC,CAAC,KAAC,QAAQ,KAAG,EACtC,yBAAO,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,GAAQ,IAC5C,EACR,MAAM,CAAC,SAAS,IAAI,CACnB,kBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,WAAW,EACrB,OAAO,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,gBACrB,MAAM,CAAC,MAAM,aAEzB,KAAC,WAAW,KAAG,EACf,yBAAO,MAAM,CAAC,MAAM,GAAQ,IACrB,CACV,EACA,QAAQ,IAAI,KAAK,KAAK,KAAK,IAAI,CAC9B,kBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,WAAW;gBACrB,qEAAqE;gBACrE,gCAAgC;gBAChC,OAAO,EAAE,GAAG,EAAE,CACZ,KAAK,SAAS;qBACX,KAAK,CAAC;oBACL,KAAK,EAAE,KAAK,EAAE,KAAK;oBACnB,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,kBAAkB,CAAC,IAAI,CAAC;oBAC7C,GAAG,EAAE,KAAK,EAAE,GAAG;iBAChB,CAAC;qBACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,gBAER,MAAM,CAAC,KAAK,aAExB,KAAC,SAAS,KAAG,EACb,yBAAO,MAAM,CAAC,KAAK,GAAQ,IACpB,CACV,EACA,OAAO,IAAI,CACV,kBAAQ,IAAI,EAAC,QAAQ,EAAC,SAAS,EAAC,WAAW,EAAC,OAAO,EAAE,OAAO,aAC1D,KAAC,SAAS,KAAG,EACb,yBAAO,MAAM,CAAC,KAAK,GAAQ,IACpB,CACV,EACA,QAAQ,IACL,CACP,CAAC;AACJ,CAAC"}
|
|
@@ -21,9 +21,13 @@ export declare const DEFAULT_ATTACH_MENU_LABELS: AttachMenuLabels;
|
|
|
21
21
|
* browser supplies Escape, the focus trap and focus restore.
|
|
22
22
|
*
|
|
23
23
|
* Every app that attaches anything gets this from the composer. An app with a
|
|
24
|
-
* custom box renders `<AttachMenu>` itself rather than a bare file input
|
|
24
|
+
* custom box renders `<AttachMenu>` itself rather than a bare file input, and
|
|
25
|
+
* an app with more to bring into a message than files — one of the person's
|
|
26
|
+
* own things, a note the model reads every time — puts those rows in `extra`:
|
|
27
|
+
* they sit under Camera · Photos · Files in the same sheet, on every pointer,
|
|
28
|
+
* since a mouse cannot reach them through a file dialog.
|
|
25
29
|
*/
|
|
26
|
-
export declare function AttachMenu({ onFiles, disabled, labels, sources, trigger, }: {
|
|
30
|
+
export declare function AttachMenu({ onFiles, disabled, labels, sources, trigger, extra, onOpenChange, }: {
|
|
27
31
|
onFiles: (files: FileList | null) => void;
|
|
28
32
|
disabled?: boolean;
|
|
29
33
|
labels?: AttachMenuLabels;
|
|
@@ -33,5 +37,10 @@ export declare function AttachMenu({ onFiles, disabled, labels, sources, trigger
|
|
|
33
37
|
/** The button's content. Defaults to a paperclip icon; an uploader passes
|
|
34
38
|
* its own ("Add photo"). The button keeps chatkit's 44px target. */
|
|
35
39
|
trigger?: ReactNode;
|
|
40
|
+
/** The app's own rows under the three sources (see above). With it the
|
|
41
|
+
* sheet opens on every pointer. A row that should close the sheet calls
|
|
42
|
+
* the `close` it is given. */
|
|
43
|
+
extra?: (close: () => void) => ReactNode;
|
|
44
|
+
onOpenChange?: (open: boolean) => void;
|
|
36
45
|
}): import("react").JSX.Element;
|
|
37
46
|
//# sourceMappingURL=AttachMenu.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AttachMenu.d.ts","sourceRoot":"","sources":["../../src/react/AttachMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"AttachMenu.d.ts","sourceRoot":"","sources":["../../src/react/AttachMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAoB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAI3E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;CACvC,CAAC;AAEF,eAAO,MAAM,0BAA0B,EAAE,gBAKxC,CAAC;AASF;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,UAAU,CAAC,EACzB,OAAO,EACP,QAAgB,EAChB,MAAmC,EACnC,OAAiB,EACjB,OAAO,EACP,KAAK,EACL,YAAY,GACb,EAAE;IACD,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,KAAK,IAAI,CAAC;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B;8EAC0E;IAC1E,OAAO,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAClC;yEACqE;IACrE,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;mCAE+B;IAC/B,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,IAAI,KAAK,SAAS,CAAC;IACzC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC,+BAuEA"}
|
package/dist/react/AttachMenu.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useRef } from "react";
|
|
3
|
+
import { useRef, useState } from "react";
|
|
4
4
|
import { ATTACH_SOURCE_INPUT } from "../attachments.js";
|
|
5
|
-
import { IconCamera, IconFileUp, IconImage, IconPaperclip
|
|
5
|
+
import { IconCamera, IconFileUp, IconImage, IconPaperclip } from "./icons.js";
|
|
6
|
+
import { Sheet, prefersSheet } from "./Sheet.js";
|
|
6
7
|
export const DEFAULT_ATTACH_MENU_LABELS = {
|
|
7
8
|
attach: "Attach a screenshot or file",
|
|
8
9
|
title: "Add to message",
|
|
@@ -15,11 +16,6 @@ const ICONS = {
|
|
|
15
16
|
photos: IconImage,
|
|
16
17
|
files: IconFileUp,
|
|
17
18
|
};
|
|
18
|
-
/** A touch screen gets the sheet; a mouse gets the file dialog in one click,
|
|
19
|
-
* because on a desktop Camera / Photos / Files are all the same dialog. */
|
|
20
|
-
function prefersSheet() {
|
|
21
|
-
return typeof window !== "undefined" && window.matchMedia?.("(pointer: coarse)").matches;
|
|
22
|
-
}
|
|
23
19
|
/**
|
|
24
20
|
* The paperclip, and what it opens on a phone: a sheet with Camera, Photos and
|
|
25
21
|
* Files — one input each. See `ATTACH_SOURCE_INPUT` for why three inputs and
|
|
@@ -32,20 +28,27 @@ function prefersSheet() {
|
|
|
32
28
|
* browser supplies Escape, the focus trap and focus restore.
|
|
33
29
|
*
|
|
34
30
|
* Every app that attaches anything gets this from the composer. An app with a
|
|
35
|
-
* custom box renders `<AttachMenu>` itself rather than a bare file input
|
|
31
|
+
* custom box renders `<AttachMenu>` itself rather than a bare file input, and
|
|
32
|
+
* an app with more to bring into a message than files — one of the person's
|
|
33
|
+
* own things, a note the model reads every time — puts those rows in `extra`:
|
|
34
|
+
* they sit under Camera · Photos · Files in the same sheet, on every pointer,
|
|
35
|
+
* since a mouse cannot reach them through a file dialog.
|
|
36
36
|
*/
|
|
37
|
-
export function AttachMenu({ onFiles, disabled = false, labels = DEFAULT_ATTACH_MENU_LABELS, sources = SOURCES, trigger, }) {
|
|
37
|
+
export function AttachMenu({ onFiles, disabled = false, labels = DEFAULT_ATTACH_MENU_LABELS, sources = SOURCES, trigger, extra, onOpenChange, }) {
|
|
38
38
|
const inputs = useRef({});
|
|
39
|
-
const
|
|
40
|
-
const
|
|
39
|
+
const [sheetOpen, setSheetOpen] = useState(false);
|
|
40
|
+
const setOpen = (next) => {
|
|
41
|
+
setSheetOpen(next);
|
|
42
|
+
onOpenChange?.(next);
|
|
43
|
+
};
|
|
44
|
+
const close = () => setOpen(false);
|
|
41
45
|
const pick = (source) => {
|
|
42
46
|
close();
|
|
43
47
|
inputs.current[source]?.click();
|
|
44
48
|
};
|
|
45
49
|
const open = () => {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
d.showModal();
|
|
50
|
+
if (extra || (sources.length > 1 && prefersSheet()))
|
|
51
|
+
setOpen(true);
|
|
49
52
|
// A mouse gets the broadest picker offered: Files when it is one of the
|
|
50
53
|
// sources, else the last one (Photos, for an image uploader).
|
|
51
54
|
else
|
|
@@ -56,15 +59,9 @@ export function AttachMenu({ onFiles, disabled = false, labels = DEFAULT_ATTACH_
|
|
|
56
59
|
}, type: "file", hidden: true, multiple: source !== "camera", "data-ck-source": source, ...ATTACH_SOURCE_INPUT[source], onChange: (e) => {
|
|
57
60
|
onFiles(e.target.files);
|
|
58
61
|
e.target.value = "";
|
|
59
|
-
} }, source))), _jsx("button", { type: "button", className: trigger ? "ck-attach-trigger" : "ck-icon-btn", onClick: open, disabled: disabled, "aria-label": labels.attach, "aria-haspopup": "dialog", title: labels.attach, children: trigger ?? _jsx(IconPaperclip, {}) }),
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (e.target === e.currentTarget)
|
|
64
|
-
close();
|
|
65
|
-
}, children: _jsxs("div", { className: "ck-sheet-panel", children: [_jsxs("div", { className: "ck-sheet-head", children: [_jsx("span", { className: "ck-sheet-title", children: labels.title }), _jsx("button", { type: "button", className: "ck-icon-btn ck-sheet-close", onClick: close, "aria-label": labels.close, title: labels.close, children: _jsx(IconX, {}) })] }), _jsx("div", { className: "ck-attach-sources", children: sources.map((source) => {
|
|
66
|
-
const Icon = ICONS[source];
|
|
67
|
-
return (_jsxs("button", { type: "button", className: "ck-attach-source", "data-ck-source": source, onClick: () => pick(source), children: [_jsx(Icon, {}), _jsx("span", { children: labels.sources[source] })] }, source));
|
|
68
|
-
}) })] }) })] }));
|
|
62
|
+
} }, source))), _jsx("button", { type: "button", className: trigger ? "ck-attach-trigger" : "ck-icon-btn", onClick: open, disabled: disabled, "aria-label": labels.attach, "aria-haspopup": "dialog", title: labels.attach, children: trigger ?? _jsx(IconPaperclip, {}) }), _jsxs(Sheet, { open: sheetOpen, onClose: close, title: labels.title, closeLabel: labels.close, children: [_jsx("div", { className: "ck-attach-sources", children: sources.map((source) => {
|
|
63
|
+
const Icon = ICONS[source];
|
|
64
|
+
return (_jsxs("button", { type: "button", className: "ck-attach-source", "data-ck-source": source, onClick: () => pick(source), children: [_jsx(Icon, {}), _jsx("span", { children: labels.sources[source] })] }, source));
|
|
65
|
+
}) }), extra && _jsx("div", { className: "ck-sheet-extra", children: extra(close) })] })] }));
|
|
69
66
|
}
|
|
70
67
|
//# sourceMappingURL=AttachMenu.js.map
|