fauna-api 1.0.783 → 1.0.786

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.
@@ -49,6 +49,8 @@ export function createTokenizedInput(container, options = {}) {
49
49
  }
50
50
  else if (node.nodeType === Node.ELEMENT_NODE) {
51
51
  const el = node;
52
+ if (el.classList.contains('tokenized-ghost'))
53
+ return;
52
54
  if (el.classList.contains('tokenized-token')) {
53
55
  parts.push({
54
56
  kind: 'token',
@@ -328,8 +330,49 @@ export function createTokenizedInput(container, options = {}) {
328
330
  clearTimeout(suggestTimer);
329
331
  suggestTimer = setTimeout(() => runSuggest(query), 120);
330
332
  }
333
+ // ── Suggested ending ──
334
+ let ghostEl = null;
335
+ function clearGhost() {
336
+ ghostEl?.remove();
337
+ ghostEl = null;
338
+ }
339
+ function caretAtEnd() {
340
+ const sel = window.getSelection();
341
+ if (!sel || sel.rangeCount === 0 || !editor.contains(sel.anchorNode))
342
+ return false;
343
+ const after = document.createRange();
344
+ after.selectNodeContents(editor);
345
+ after.setStart(sel.getRangeAt(0).endContainer, sel.getRangeAt(0).endOffset);
346
+ const rest = after.cloneContents();
347
+ rest.querySelectorAll('.tokenized-ghost').forEach((g) => g.remove());
348
+ return (rest.textContent ?? '').replace(/\u200B/g, '') === '';
349
+ }
350
+ function placeCaretAtEnd() {
351
+ const range = document.createRange();
352
+ range.selectNodeContents(editor);
353
+ range.collapse(false);
354
+ const sel = window.getSelection();
355
+ sel?.removeAllRanges();
356
+ sel?.addRange(range);
357
+ }
358
+ function acceptGhost() {
359
+ const text = ghostEl?.textContent ?? '';
360
+ clearGhost();
361
+ if (!text)
362
+ return;
363
+ const lines = text.split('\n');
364
+ for (let i = 0; i < lines.length; i++) {
365
+ if (lines[i])
366
+ editor.appendChild(document.createTextNode(lines[i]));
367
+ if (i < lines.length - 1)
368
+ editor.appendChild(document.createElement('br'));
369
+ }
370
+ placeCaretAtEnd();
371
+ fireInput();
372
+ }
331
373
  // ── Input events ──
332
374
  editor.addEventListener('input', () => {
375
+ clearGhost();
333
376
  fireInput();
334
377
  if (activeTrigger) {
335
378
  const query = getTriggerQuery();
@@ -342,6 +385,19 @@ export function createTokenizedInput(container, options = {}) {
342
385
  }
343
386
  });
344
387
  editor.addEventListener('keydown', (e) => {
388
+ // A suggested ending: Tab takes it, Shift+Tab or Escape drops it
389
+ if (ghostEl) {
390
+ if (e.key === 'Tab' && !e.shiftKey) {
391
+ e.preventDefault();
392
+ acceptGhost();
393
+ return;
394
+ }
395
+ if (e.key === 'Escape' || e.key === 'Tab') {
396
+ e.preventDefault();
397
+ clearGhost();
398
+ return;
399
+ }
400
+ }
345
401
  // Suggestion navigation
346
402
  if (suggestionsEl.style.display !== 'none' && suggestionItems.length > 0) {
347
403
  if (e.key === 'ArrowDown') {
@@ -435,6 +491,7 @@ export function createTokenizedInput(container, options = {}) {
435
491
  }
436
492
  });
437
493
  editor.addEventListener('blur', () => {
494
+ clearGhost();
438
495
  // Delay to allow mousedown on suggestions
439
496
  setTimeout(() => {
440
497
  if (!suggestionsEl.matches(':hover')) {
@@ -457,6 +514,20 @@ export function createTokenizedInput(container, options = {}) {
457
514
  });
458
515
  // ── Public API ──
459
516
  return {
517
+ setGhost(text) {
518
+ clearGhost();
519
+ if (!text || !caretAtEnd())
520
+ return;
521
+ ghostEl = document.createElement('span');
522
+ ghostEl.className = 'tokenized-ghost';
523
+ ghostEl.contentEditable = 'false';
524
+ ghostEl.textContent = text;
525
+ ghostEl.dataset.hint = 'Tab';
526
+ editor.appendChild(ghostEl);
527
+ },
528
+ atEnd() {
529
+ return caretAtEnd();
530
+ },
460
531
  getParts() {
461
532
  return parseParts();
462
533
  },
@@ -464,6 +535,7 @@ export function createTokenizedInput(container, options = {}) {
464
535
  return serializeText(parseParts());
465
536
  },
466
537
  setParts(parts) {
538
+ clearGhost();
467
539
  editor.innerHTML = '';
468
540
  for (const part of parts) {
469
541
  if (part.kind === 'text') {
@@ -487,6 +559,7 @@ export function createTokenizedInput(container, options = {}) {
487
559
  insertTokenAtCaret(token);
488
560
  },
489
561
  clear() {
562
+ clearGhost();
490
563
  editor.innerHTML = '';
491
564
  hideSuggestions();
492
565
  fireInput();
@@ -435,6 +435,25 @@
435
435
  color: var(--chat-text, inherit);
436
436
  }
437
437
  .chat-message-text > :first-child { margin-top: 0; }
438
+
439
+ /* An HTML body (an email's), drawn as it is in a frame that runs no scripts. */
440
+ .chat-message-html {
441
+ display: block;
442
+ width: 100%;
443
+ min-width: 280px;
444
+ min-height: 48px;
445
+ max-height: 640px;
446
+ border: 0;
447
+ border-radius: 10px;
448
+ background: #fff;
449
+ }
450
+
451
+ /* A line under a message, such as why it wasn't delivered. */
452
+ .chat-message-note {
453
+ margin-top: 4px;
454
+ font-size: 12px;
455
+ color: var(--chat-note, #c0392b);
456
+ }
438
457
  .chat-message-text > :last-child { margin-bottom: 0; }
439
458
 
440
459
  .chat-message-text table {
@@ -4992,3 +5011,20 @@ button.chat-message-file {
4992
5011
  padding: 6px 12px;
4993
5012
  cursor: pointer;
4994
5013
  }
5014
+
5015
+ /* A suggested ending after the caret: Tab takes it. */
5016
+ .tokenized-ghost {
5017
+ color: var(--chat-ghost, rgba(0, 0, 0, 0.35));
5018
+ white-space: pre-wrap;
5019
+ pointer-events: none;
5020
+ user-select: none;
5021
+ }
5022
+ .tokenized-ghost::after {
5023
+ content: attr(data-hint);
5024
+ margin-left: 6px;
5025
+ padding: 0 4px;
5026
+ border: 1px solid currentColor;
5027
+ border-radius: 4px;
5028
+ font-size: 10px;
5029
+ vertical-align: middle;
5030
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fauna-api",
3
- "version": "1.0.783",
3
+ "version": "1.0.786",
4
4
  "description": "The Fauna SDK: accounts, Drive, groups, calendar, chats, payments and every Fauna app (Parrot, Marmot, Magpie, Cicada, Ant, Termite), plus React UI.",
5
5
  "homepage": "https://docs.faunakit.com",
6
6
  "repository": {
@@ -1,75 +0,0 @@
1
- import { Contact, Owner } from "./contacts.js";
2
- import { Platform } from "../integrations.js";
3
- import { Credentials, ApiKey } from "../authentification.js";
4
- import { File as FaunaFile } from "../drive.js";
5
- export type Conversation = {
6
- id: string;
7
- inboxId?: string;
8
- participants: (Contact | Owner | string)[];
9
- activeAt: Date;
10
- unread: number;
11
- };
12
- export type Reminder = {
13
- conversation: Conversation;
14
- remindAt: Date;
15
- };
16
- export type ScheduledSend = {
17
- id: string;
18
- sendAt: Date;
19
- createdAt: Date;
20
- message: string;
21
- context: {
22
- conversation: string;
23
- platform?: Platform;
24
- } | {
25
- recipients: (Contact | Owner | string)[];
26
- mode: 'broadcast' | 'group';
27
- platform: Platform;
28
- };
29
- };
30
- export type Message = {
31
- id: string;
32
- author: Contact | Owner | string;
33
- platform: Platform;
34
- createdAt: Date;
35
- message: string;
36
- raw?: string;
37
- attachments: FaunaFile[];
38
- /** Sent by the inbox agent. */
39
- agent: boolean;
40
- /** Why it never reached them, when its carrier refused it or it failed to send. */
41
- undelivered?: string;
42
- };
43
- export declare function listInbox(authorization: Credentials | ApiKey, inboxId?: string): Promise<Conversation[]>;
44
- export declare function listArchived(authorization: Credentials | ApiKey, inboxId?: string): Promise<Conversation[]>;
45
- export declare function listReminders(authorization: Credentials | ApiKey, inboxId?: string): Promise<Reminder[]>;
46
- export declare function get(authorization: Credentials | ApiKey, conversation: string): Promise<Conversation | null>;
47
- export declare function read(authorization: Credentials | ApiKey, conversation: string): Promise<Message[]>;
48
- export declare function autocomplete(authorization: Credentials | ApiKey, conversation: string, text: string): Promise<string>;
49
- export declare function markRead(authorization: Credentials | ApiKey, conversation: string): Promise<void>;
50
- export declare function remove(authorization: Credentials | ApiKey, conversation: string): Promise<void>;
51
- export declare function compose(authorization: Credentials | ApiKey, options: {
52
- recipients: string[];
53
- platform: Platform;
54
- text: string;
55
- mode: 'broadcast' | 'group';
56
- inboxId?: string;
57
- accountId?: string;
58
- }): Promise<string[]>;
59
- export declare function send(authorization: Credentials | ApiKey, conversation: string, text: string, platform?: Platform): Promise<string>;
60
- export declare function archive(authorization: Credentials | ApiKey, conversation: string, archived: boolean): Promise<void>;
61
- export declare function remind(authorization: Credentials | ApiKey, conversation: string, at: Date | null): Promise<void>;
62
- export declare function schedule(authorization: Credentials | ApiKey, options: {
63
- sendAt: Date;
64
- text: string;
65
- platform?: Platform;
66
- conversationId?: string;
67
- compose?: {
68
- recipients: string[];
69
- mode: 'broadcast' | 'group';
70
- };
71
- inboxId?: string;
72
- accountId?: string;
73
- }): Promise<string>;
74
- export declare function listScheduled(authorization: Credentials | ApiKey, inboxId?: string): Promise<ScheduledSend[]>;
75
- export declare function cancelScheduled(authorization: Credentials | ApiKey, scheduled: string): Promise<void>;
@@ -1,236 +0,0 @@
1
- import { Cicada } from "./index.js";
2
- function wrapConversation(raw) {
3
- return {
4
- ...raw,
5
- inboxId: raw.inboxId !== undefined ? String(raw.inboxId) : undefined,
6
- activeAt: new Date(raw.activeAt)
7
- };
8
- }
9
- function wrapMessage(raw) {
10
- return {
11
- ...raw,
12
- createdAt: new Date(raw.createdAt),
13
- attachments: raw.attachments ?? [],
14
- agent: raw.agent === true,
15
- };
16
- }
17
- async function listView(authorization, path, inboxId) {
18
- const response = await fetch(`${Cicada.getURL()}${path}`, {
19
- method: "POST",
20
- headers: {
21
- "Content-Type": "application/json",
22
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
23
- },
24
- body: JSON.stringify(inboxId ? { inboxId } : {})
25
- });
26
- if (!response.ok)
27
- throw new Error(await response.text());
28
- const json = await response.json();
29
- return json.map(wrapConversation);
30
- }
31
- // The inbox working set: everything except archived threads and conversations
32
- // parked for a future reminder.
33
- export async function listInbox(authorization, inboxId) {
34
- return listView(authorization, '/cicada/inbox', inboxId);
35
- }
36
- // The archive.
37
- export async function listArchived(authorization, inboxId) {
38
- return listView(authorization, '/cicada/archived', inboxId);
39
- }
40
- // Threads with a pending (future) remindAt, each paired with its due time.
41
- export async function listReminders(authorization, inboxId) {
42
- const response = await fetch(`${Cicada.getURL()}/cicada/reminders`, {
43
- method: "POST",
44
- headers: {
45
- "Content-Type": "application/json",
46
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
47
- },
48
- body: JSON.stringify(inboxId ? { inboxId } : {})
49
- });
50
- if (!response.ok)
51
- throw new Error(await response.text());
52
- const json = await response.json();
53
- return json.map((raw) => ({
54
- conversation: wrapConversation(raw.conversation),
55
- remindAt: new Date(raw.remindAt)
56
- }));
57
- }
58
- // Fetch a single conversation's metadata by id (hydrated participants), or null
59
- // if it no longer exists.
60
- export async function get(authorization, conversation) {
61
- const response = await fetch(`${Cicada.getURL()}/cicada/conversation/get`, {
62
- method: "POST",
63
- headers: {
64
- "Content-Type": "application/json",
65
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
66
- },
67
- body: JSON.stringify({ conversation })
68
- });
69
- if (!response.ok)
70
- throw new Error(await response.text());
71
- const json = await response.json();
72
- return json ? wrapConversation(json) : null;
73
- }
74
- export async function read(authorization, conversation) {
75
- const response = await fetch(`${Cicada.getURL()}/cicada/conversation`, {
76
- method: "POST",
77
- headers: {
78
- "Content-Type": "application/json",
79
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
80
- },
81
- body: JSON.stringify({ conversation })
82
- });
83
- if (!response.ok)
84
- throw new Error(await response.text());
85
- const json = await response.json();
86
- return json.map(wrapMessage);
87
- }
88
- // Suggest a continuation of an in-progress reply for a conversation. Returns
89
- // the raw completion text (the part that should follow the current draft); an
90
- // empty string means no suggestion.
91
- export async function autocomplete(authorization, conversation, text) {
92
- const response = await fetch(`${Cicada.getURL()}/cicada/autocomplete`, {
93
- method: "POST",
94
- headers: {
95
- "Content-Type": "application/json",
96
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
97
- },
98
- body: JSON.stringify({ conversation, text })
99
- });
100
- if (!response.ok)
101
- throw new Error(await response.text());
102
- return await response.text();
103
- }
104
- export async function markRead(authorization, conversation) {
105
- const response = await fetch(`${Cicada.getURL()}/cicada/conversation/mark-read`, {
106
- method: "POST",
107
- headers: {
108
- "Content-Type": "application/json",
109
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
110
- },
111
- body: JSON.stringify({ conversation })
112
- });
113
- if (!response.ok)
114
- throw new Error(await response.text());
115
- }
116
- // Permanently delete a conversation (index entry + stored messages). Backfill
117
- // won't repopulate it — it only runs on account link, and polling fetches
118
- // deltas, so old messages never re-arrive.
119
- export async function remove(authorization, conversation) {
120
- const response = await fetch(`${Cicada.getURL()}/cicada/conversation/delete`, {
121
- method: "POST",
122
- headers: {
123
- "Content-Type": "application/json",
124
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
125
- },
126
- body: JSON.stringify({ conversation })
127
- });
128
- if (!response.ok)
129
- throw new Error(await response.text());
130
- }
131
- // Start (or append to) conversations with picked contacts on a platform.
132
- // `recipients` are contact keys (email/handle/phone). `broadcast` sends a
133
- // separate 1:1 to each; `group` sends one shared thread (collapsing to
134
- // broadcast on platforms without a real group path). Returns the affected
135
- // conversation ids — open the first to show the resulting thread.
136
- export async function compose(authorization, options) {
137
- const response = await fetch(`${Cicada.getURL()}/cicada/compose`, {
138
- method: "POST",
139
- headers: {
140
- "Content-Type": "application/json",
141
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
142
- },
143
- body: JSON.stringify(options)
144
- });
145
- if (!response.ok)
146
- throw new Error(await response.text());
147
- const json = await response.json();
148
- return json.conversationIds;
149
- }
150
- export async function send(authorization, conversation, text, platform) {
151
- const response = await fetch(`${Cicada.getURL()}/cicada/send`, {
152
- method: "POST",
153
- headers: {
154
- "Content-Type": "application/json",
155
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
156
- },
157
- body: JSON.stringify({ conversation, text, platform })
158
- });
159
- if (!response.ok)
160
- throw new Error(await response.text());
161
- const json = await response.json();
162
- return json.id;
163
- }
164
- // Move a conversation in/out of the archive.
165
- export async function archive(authorization, conversation, archived) {
166
- const response = await fetch(`${Cicada.getURL()}/cicada/conversation/archive`, {
167
- method: "POST",
168
- headers: {
169
- "Content-Type": "application/json",
170
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
171
- },
172
- body: JSON.stringify({ conversation, archived })
173
- });
174
- if (!response.ok)
175
- throw new Error(await response.text());
176
- }
177
- // Set (or clear, with `at = null`) a reminder on a conversation. When the time
178
- // arrives the server re-surfaces the thread at the top of the inbox.
179
- export async function remind(authorization, conversation, at) {
180
- const response = await fetch(`${Cicada.getURL()}/cicada/conversation/remind`, {
181
- method: "POST",
182
- headers: {
183
- "Content-Type": "application/json",
184
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
185
- },
186
- body: JSON.stringify({ conversation, at: at ? at.toISOString() : null })
187
- });
188
- if (!response.ok)
189
- throw new Error(await response.text());
190
- }
191
- // Queue an outbound message for later. Provide `conversationId` to reply into an
192
- // existing thread, or `compose` to start a fresh one. Returns the queue id.
193
- export async function schedule(authorization, options) {
194
- const response = await fetch(`${Cicada.getURL()}/cicada/schedule`, {
195
- method: "POST",
196
- headers: {
197
- "Content-Type": "application/json",
198
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
199
- },
200
- body: JSON.stringify({ ...options, sendAt: options.sendAt.toISOString() })
201
- });
202
- if (!response.ok)
203
- throw new Error(await response.text());
204
- const json = await response.json();
205
- return json.id;
206
- }
207
- export async function listScheduled(authorization, inboxId) {
208
- const response = await fetch(`${Cicada.getURL()}/cicada/scheduled/list`, {
209
- method: "POST",
210
- headers: {
211
- "Content-Type": "application/json",
212
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
213
- },
214
- body: JSON.stringify(inboxId ? { inboxId } : {})
215
- });
216
- if (!response.ok)
217
- throw new Error(await response.text());
218
- const json = await response.json();
219
- return json.map((raw) => ({
220
- ...raw,
221
- sendAt: new Date(raw.sendAt),
222
- createdAt: new Date(raw.createdAt)
223
- }));
224
- }
225
- export async function cancelScheduled(authorization, scheduled) {
226
- const response = await fetch(`${Cicada.getURL()}/cicada/scheduled/cancel`, {
227
- method: "POST",
228
- headers: {
229
- "Content-Type": "application/json",
230
- "Authorization": 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`
231
- },
232
- body: JSON.stringify({ scheduled })
233
- });
234
- if (!response.ok)
235
- throw new Error(await response.text());
236
- }