fauna-api 1.0.682 → 1.0.692

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.
Files changed (41) hide show
  1. package/dist/src/authentification.d.ts +34 -0
  2. package/dist/src/authentification.js +135 -0
  3. package/dist/src/calendar.d.ts +5 -2
  4. package/dist/src/calendar.js +5 -2
  5. package/dist/src/cicada/contacts.d.ts +19 -25
  6. package/dist/src/cicada/contacts.js +17 -5
  7. package/dist/src/cicada/inboxes.d.ts +16 -0
  8. package/dist/src/cicada/inboxes.js +10 -0
  9. package/dist/src/extensions/web/session.d.ts +1 -3
  10. package/dist/src/extensions/web/session.js +0 -1
  11. package/dist/src/groups.d.ts +167 -20
  12. package/dist/src/groups.js +287 -31
  13. package/dist/src/marmot/web.d.ts +27 -9
  14. package/dist/src/marmot/web.js +118 -79
  15. package/dist/src/parrot/room.d.ts +6 -2
  16. package/dist/src/parrot/room.js +11 -0
  17. package/dist/src/parrot/share.d.ts +4 -1
  18. package/dist/src/parrot/share.js +4 -2
  19. package/dist/src/rooms.d.ts +37 -11
  20. package/dist/src/rooms.js +46 -14
  21. package/dist/src/shared/server.d.ts +23 -0
  22. package/dist/src/shared/server.js +41 -0
  23. package/dist/src/ui/authentification.d.ts +6 -2
  24. package/dist/src/ui/authentification.js +36 -10
  25. package/dist/src/ui/browser.js +1 -1
  26. package/dist/src/ui/chat.js +8 -6
  27. package/dist/src/ui/chatSequence.d.ts +3 -2
  28. package/dist/src/ui/chatSequence.js +7 -2
  29. package/dist/src/ui/drive.js +1 -1
  30. package/dist/src/ui/index.d.ts +2 -0
  31. package/dist/src/ui/index.js +2 -0
  32. package/dist/src/ui/integrations.js +74 -0
  33. package/dist/src/ui/maps.d.ts +1 -1
  34. package/dist/src/ui/maps.js +3 -1
  35. package/dist/src/ui/markdown.d.ts +5 -0
  36. package/dist/src/ui/markdown.js +33 -0
  37. package/dist/src/ui/toast.d.ts +2 -2
  38. package/dist/src/ui/toast.js +5 -5
  39. package/dist/src/ui/ui.css +137 -0
  40. package/dist/src/webhooks.d.ts +2 -0
  41. package/package.json +2 -2
@@ -1,3 +1,4 @@
1
+ import { Fauna } from "../../index.js";
1
2
  import { ApiKey, Credentials, Profile } from "../authentification.js";
2
3
  export type Authorization = {
3
4
  authorization: Credentials | ApiKey;
@@ -63,3 +64,25 @@ export declare class Room {
63
64
  generateAccessCode(): Promise<string>;
64
65
  }
65
66
  export declare function connect(authorization: Authorization, endpoint: string, hooks?: Hooks): Promise<Presence>;
67
+ /** A code you own: a room you shared, or a file link. */
68
+ export type AccessCode = {
69
+ code: string;
70
+ /** What it opens, e.g. `parrot:chat:{id}` or `file:{path}`. */
71
+ reference: string;
72
+ /** True when people who join with it may act in the room, not only watch. */
73
+ write: boolean;
74
+ expires: Date;
75
+ };
76
+ export declare const AccessCode: {
77
+ fromJSON(json: any): AccessCode;
78
+ };
79
+ /**
80
+ * The access codes you own, the one that lasts longest first: your room shares
81
+ * and file links, and the ones you made for your groups.
82
+ */
83
+ export declare function listAccessCodes(authorization: Credentials | ApiKey, query: {
84
+ cursor: number;
85
+ max: number;
86
+ }): Promise<AccessCode[]>;
87
+ /** Revoke one of your access codes. It stops working at once; anyone in a room with it is disconnected. */
88
+ export declare function revokeAccessCode(authorization: Credentials | ApiKey, code: string): Promise<void>;
@@ -1,3 +1,4 @@
1
+ import { Fauna } from "../../index.js";
1
2
  export class Presence {
2
3
  constructor(room, id, profile) {
3
4
  Object.defineProperty(this, "room", {
@@ -300,3 +301,43 @@ export async function connect(authorization, endpoint, hooks = {}) {
300
301
  };
301
302
  });
302
303
  }
304
+ export const AccessCode = {
305
+ fromJSON(json) {
306
+ return {
307
+ code: String(json.code ?? ""),
308
+ reference: String(json.reference ?? ""),
309
+ write: json.write === true,
310
+ expires: new Date(json.expires),
311
+ };
312
+ },
313
+ };
314
+ /**
315
+ * The access codes you own, the one that lasts longest first: your room shares
316
+ * and file links, and the ones you made for your groups.
317
+ */
318
+ export async function listAccessCodes(authorization, query) {
319
+ const params = new URLSearchParams({ cursor: String(query.cursor), max: String(query.max) });
320
+ const response = await fetch(`${Fauna.getURL()}/access-codes?${params}`, {
321
+ method: "GET",
322
+ headers: {
323
+ Authorization: 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`,
324
+ },
325
+ });
326
+ if (!response.ok) {
327
+ throw new Error(await response.text());
328
+ }
329
+ const json = await response.json();
330
+ return (Array.isArray(json) ? json : []).map((row) => AccessCode.fromJSON(row));
331
+ }
332
+ /** Revoke one of your access codes. It stops working at once; anyone in a room with it is disconnected. */
333
+ export async function revokeAccessCode(authorization, code) {
334
+ const response = await fetch(`${Fauna.getURL()}/access-codes/${encodeURIComponent(code)}`, {
335
+ method: "DELETE",
336
+ headers: {
337
+ Authorization: 'email' in authorization ? `Token ${authorization.token}` : `ApiKey ${authorization.id}`,
338
+ },
339
+ });
340
+ if (!response.ok) {
341
+ throw new Error(await response.text());
342
+ }
343
+ }
@@ -1,13 +1,17 @@
1
+ import { Fauna } from "fauna-api";
1
2
  export declare function getToken(): string | undefined;
2
3
  export declare function setToken(token: string): void;
3
4
  export declare function clearToken(): void;
5
+ /** Signs this browser out: ends the session on the server, then forgets the login cookie. */
6
+ export declare function signOut(credentials: Fauna.Authentification.Credentials): Promise<void>;
4
7
  /** Whether this tab is a support session opened from Fauna's ticket board. */
5
8
  export declare function isSupportView(): boolean;
6
9
  /**
7
- * Takes `token` and `support` off a page's query. A support token stays in this tab; any other token
10
+ * Takes `handoff` and `support` off a page's query (and the address bar, before anything else loads),
11
+ * then redeems the handoff code for its session. A support session stays in this tab; any other
8
12
  * becomes the login cookie and ends the tab's support session. Returns whether the query changed.
9
13
  */
10
- export declare function takeTokenFromURL(params: URLSearchParams): boolean;
14
+ export declare function takeHandoffFromURL(params: URLSearchParams): Promise<boolean>;
11
15
  /** The token this tab signs in with: the support session's in a support view, else the login cookie. */
12
16
  export declare function sessionToken(): string | undefined;
13
17
  export type OAuthProvider = 'google' | 'microsoft';
@@ -44,8 +44,17 @@ export function clearToken() {
44
44
  return;
45
45
  document.cookie = `${CREDENTIALS_COOKIE}=; ${cookieSuffix(0)}`;
46
46
  }
47
- // Support sessions: Fauna's ticket board opens a reporter's page with `?token=...&support=1`.
48
- // That token belongs to the reporter, so it lives in this tab only and never replaces the login cookie.
47
+ /** Signs this browser out: ends the session on the server, then forgets the login cookie. */
48
+ export async function signOut(credentials) {
49
+ try {
50
+ await Fauna.Authentification.signOut(credentials);
51
+ }
52
+ finally {
53
+ clearToken();
54
+ }
55
+ }
56
+ // Support sessions: Fauna's ticket board opens a reporter's page with `?handoff=...&support=1`.
57
+ // That session belongs to the reporter, so it lives in this tab only and never replaces the login cookie.
49
58
  const SUPPORT_TOKEN_KEY = 'fauna-support-token';
50
59
  function readSupportToken() {
51
60
  try {
@@ -60,13 +69,32 @@ export function isSupportView() {
60
69
  return Boolean(readSupportToken());
61
70
  }
62
71
  /**
63
- * Takes `token` and `support` off a page's query. A support token stays in this tab; any other token
72
+ * Takes `handoff` and `support` off a page's query (and the address bar, before anything else loads),
73
+ * then redeems the handoff code for its session. A support session stays in this tab; any other
64
74
  * becomes the login cookie and ends the tab's support session. Returns whether the query changed.
65
75
  */
66
- export function takeTokenFromURL(params) {
67
- const token = params.get('token');
68
- const changed = Boolean(token) || params.has('support');
69
- if (token && params.get('support') === '1') {
76
+ export async function takeHandoffFromURL(params) {
77
+ const code = params.get('handoff');
78
+ const support = params.get('support') === '1';
79
+ const changed = Boolean(code) || params.has('support');
80
+ params.delete('handoff');
81
+ params.delete('support');
82
+ if (changed && typeof location !== 'undefined') {
83
+ const url = new URL(location.href);
84
+ url.searchParams.delete('handoff');
85
+ url.searchParams.delete('support');
86
+ history.replaceState(history.state, '', url);
87
+ }
88
+ if (!code)
89
+ return changed;
90
+ let token;
91
+ try {
92
+ token = await Fauna.Authentification.redeemHandoff(code);
93
+ }
94
+ catch {
95
+ return changed;
96
+ }
97
+ if (support) {
70
98
  try {
71
99
  sessionStorage.setItem(SUPPORT_TOKEN_KEY, token);
72
100
  }
@@ -74,7 +102,7 @@ export function takeTokenFromURL(params) {
74
102
  /* no tab storage: drop it rather than sign in as the reporter */
75
103
  }
76
104
  }
77
- else if (token) {
105
+ else {
78
106
  try {
79
107
  sessionStorage.removeItem(SUPPORT_TOKEN_KEY);
80
108
  }
@@ -83,8 +111,6 @@ export function takeTokenFromURL(params) {
83
111
  }
84
112
  setToken(token);
85
113
  }
86
- params.delete('token');
87
- params.delete('support');
88
114
  return changed;
89
115
  }
90
116
  /** The token this tab signs in with: the support session's in a support view, else the login cookie. */
@@ -388,7 +388,7 @@ export function createBrowser(container, options) {
388
388
  gsap.set(cursorHost, { x: 0, y: 0, scale: 1 });
389
389
  const replayer = new DomReplayer(frame);
390
390
  let toolTl = null;
391
- // Only a full document replays in its own frame; a fragment shares this page's document.
391
+ // Until the first replay's frame loads, the replayer's document is still this page's.
392
392
  const caret = caretPainter(() => {
393
393
  const doc = replayer.getDocument();
394
394
  return doc === document ? null : doc;
@@ -3,7 +3,6 @@ import { useLayoutEffect, useImperativeHandle, useMemo, useRef, useState, forwar
3
3
  import { createRoot } from "react-dom/client";
4
4
  import { flushSync } from "react-dom";
5
5
  import gsap from "gsap";
6
- import { marked } from "marked";
7
6
  import { Fauna } from "fauna-api";
8
7
  import { mimeFromFileName } from "../marmot/media.js";
9
8
  import { createTokenizedInput, parseRichText, toPlainText, hasTokens } from "./tokenized.js";
@@ -14,6 +13,7 @@ import { createTooltip } from "./hover.js";
14
13
  import { sendInformaticToast } from "./toast.js";
15
14
  import { buildAvatar, createSelector, stampModelAvatar, stampUserAvatar } from "./generative.js";
16
15
  import { authorColorSeed, colorFromSeed } from "./cursor.js";
16
+ import { render as renderMarkdown } from "./markdown.js";
17
17
  // Re-export so apps can flatten serialized token markers (e.g. before sending
18
18
  // history to the LLM) without reaching into the tokenized-input module.
19
19
  export { toPlainText } from "./tokenized.js";
@@ -56,7 +56,8 @@ function webResults(call, query) {
56
56
  for (const result of JSON.parse(String(call.result))) {
57
57
  const link = document.createElement('a');
58
58
  link.className = 'chat-tool-web-source';
59
- link.href = result.url;
59
+ if (/^https?:/i.test(result.url))
60
+ link.href = result.url;
60
61
  link.target = '_blank';
61
62
  link.rel = 'noopener';
62
63
  const title = document.createElement('span');
@@ -153,7 +154,7 @@ function buildMessageToken(token, opts) {
153
154
  // render text segments as plain text and hydrate each marker into a chip.
154
155
  function renderRichText(textEl, text, opts) {
155
156
  if (!hasTokens(text)) {
156
- textEl.innerHTML = marked.parse(text);
157
+ textEl.innerHTML = renderMarkdown(text);
157
158
  return;
158
159
  }
159
160
  for (const part of parseRichText(text)) {
@@ -369,7 +370,7 @@ function createStreamRenderer(modelEl, messagesEl, renderToolCall) {
369
370
  // just behind the incoming text.
370
371
  function renderShown() {
371
372
  if (currentTextEl)
372
- currentTextEl.innerHTML = marked.parse(currentBuffer.slice(0, shown));
373
+ currentTextEl.innerHTML = renderMarkdown(currentBuffer.slice(0, shown));
373
374
  }
374
375
  function stopReveal() {
375
376
  if (rafId !== null) {
@@ -418,6 +419,7 @@ function createStreamRenderer(modelEl, messagesEl, renderToolCall) {
418
419
  const stored = toolCalls.find((item) => item.id === id);
419
420
  const changed = stored.status !== call.status
420
421
  || stored.result !== call.result
422
+ || stored.session?.reference !== call.session?.reference
421
423
  || stored.session?.code !== call.session?.code
422
424
  || (call.status === 'running' && stored.progress !== call.progress);
423
425
  if (!changed)
@@ -464,7 +466,7 @@ function createStreamRenderer(modelEl, messagesEl, renderToolCall) {
464
466
  if (currentBuffer) {
465
467
  completedSegments.push(currentBuffer);
466
468
  if (currentTextEl) {
467
- currentTextEl.innerHTML = await marked.parse(currentBuffer);
469
+ currentTextEl.innerHTML = renderMarkdown(currentBuffer);
468
470
  }
469
471
  }
470
472
  autoScroll(messagesEl);
@@ -499,7 +501,7 @@ function createStreamRenderer(modelEl, messagesEl, renderToolCall) {
499
501
  shown = trailing.length;
500
502
  ensureTextEl();
501
503
  if (currentTextEl)
502
- currentTextEl.innerHTML = marked.parse(trailing);
504
+ currentTextEl.innerHTML = renderMarkdown(trailing);
503
505
  },
504
506
  };
505
507
  }
@@ -1,7 +1,8 @@
1
- /** The live room a tool opened, to watch it: a browser, or a machine's desktop, by access code. */
1
+ /** The live room a tool opened, to watch it: a browser, joined with its access code, or a machine's desktop, joined by reference by the people who may use the machine. */
2
2
  export type ToolSession = {
3
3
  kind: 'browser' | 'desktop';
4
- code: string;
4
+ reference: string;
5
+ code?: string;
5
6
  url?: string;
6
7
  };
7
8
  export type ChatSequenceToolCall = {
@@ -1,7 +1,12 @@
1
1
  function toolSession(raw) {
2
- if (!raw || typeof raw.code !== 'string' || !raw.code)
2
+ if (!raw || typeof raw.reference !== 'string' || !raw.reference)
3
3
  return undefined;
4
- return { kind: raw.kind === 'desktop' ? 'desktop' : 'browser', code: raw.code, ...(typeof raw.url === 'string' && raw.url ? { url: raw.url } : {}) };
4
+ return {
5
+ kind: raw.kind === 'desktop' ? 'desktop' : 'browser',
6
+ reference: raw.reference,
7
+ ...(typeof raw.code === 'string' && raw.code ? { code: raw.code } : {}),
8
+ ...(typeof raw.url === 'string' && raw.url ? { url: raw.url } : {}),
9
+ };
5
10
  }
6
11
  export function roomPartsToChatFields(parts) {
7
12
  const texts = [];
@@ -233,7 +233,7 @@ export function createFileElement(file, options = {}) {
233
233
  name.readOnly = true;
234
234
  label.appendChild(name);
235
235
  const type = document.createElement('span');
236
- type.innerHTML = extensionFor(file.contentType, file.name).toUpperCase();
236
+ type.textContent = extensionFor(file.contentType, file.name).toUpperCase();
237
237
  label.appendChild(type);
238
238
  const opts = document.createElement('div');
239
239
  opts.className = `file-${view}-options`;
@@ -15,6 +15,7 @@ import * as BrowserUI from "./browser.js";
15
15
  import * as RosterUI from "./roster.js";
16
16
  import * as MapsUI from "./maps.js";
17
17
  import * as ViewUI from "./view.js";
18
+ import * as MarkdownUI from "./markdown.js";
18
19
  export declare namespace UI {
19
20
  function setRoute(route: string): void;
20
21
  function getIP(): string;
@@ -39,4 +40,5 @@ export declare namespace UI {
39
40
  export import Roster = RosterUI;
40
41
  export import Maps = MapsUI;
41
42
  export import View = ViewUI;
43
+ export import Markdown = MarkdownUI;
42
44
  }
@@ -15,6 +15,7 @@ import * as BrowserUI from "./browser.js";
15
15
  import * as RosterUI from "./roster.js";
16
16
  import * as MapsUI from "./maps.js";
17
17
  import * as ViewUI from "./view.js";
18
+ import * as MarkdownUI from "./markdown.js";
18
19
  export var UI;
19
20
  (function (UI) {
20
21
  var PREFIX = 'https://';
@@ -84,4 +85,5 @@ export var UI;
84
85
  UI.Roster = RosterUI;
85
86
  UI.Maps = MapsUI;
86
87
  UI.View = ViewUI;
88
+ UI.Markdown = MarkdownUI;
87
89
  })(UI || (UI = {}));
@@ -75,6 +75,77 @@ export function createConnections(credentials, options = {}) {
75
75
  fail(err);
76
76
  }
77
77
  }
78
+ // Twilio delivers new texts only to the number's own "A message comes in" webhook,
79
+ // which Fauna doesn't set, so a linked number shows where to point it.
80
+ function buildTwilioWebhookNote() {
81
+ const url = `${new URL(Fauna.getURL()).origin}/integrations/twilio/webhook`;
82
+ const note = document.createElement('div');
83
+ note.className = 'connection-card-note';
84
+ const head = document.createElement('div');
85
+ head.className = 'connection-card-note-head';
86
+ head.innerHTML = '<span class="material-symbols-outlined connection-card-note-icon">error</span>';
87
+ const title = document.createElement('span');
88
+ title.className = 'connection-card-note-title';
89
+ title.textContent = 'Set the webhook in Twilio';
90
+ title.title = 'New texts only reach Fauna once Twilio sends them to this webhook.';
91
+ head.appendChild(title);
92
+ const open = document.createElement('a');
93
+ open.className = 'connection-card-note-link';
94
+ open.href = 'https://console.twilio.com/us1/develop/phone-numbers/manage/incoming';
95
+ open.target = '_blank';
96
+ open.rel = 'noopener';
97
+ open.innerHTML = 'Open<span class="material-symbols-outlined">open_in_new</span>';
98
+ head.appendChild(open);
99
+ note.appendChild(head);
100
+ const steps = document.createElement('ol');
101
+ steps.className = 'connection-card-note-steps';
102
+ for (const path of [
103
+ ['Active numbers', 'your number'],
104
+ ['Messaging', 'A message comes in'],
105
+ ['Webhook, HTTP POST, this URL'],
106
+ ]) {
107
+ const step = document.createElement('li');
108
+ path.forEach((part, i) => {
109
+ if (i) {
110
+ const arrow = document.createElement('span');
111
+ arrow.className = 'material-symbols-outlined connection-card-note-arrow';
112
+ arrow.textContent = 'arrow_forward';
113
+ step.appendChild(arrow);
114
+ }
115
+ const text = document.createElement('span');
116
+ text.textContent = part;
117
+ step.appendChild(text);
118
+ });
119
+ steps.appendChild(step);
120
+ }
121
+ note.appendChild(steps);
122
+ const copy = document.createElement('button');
123
+ copy.type = 'button';
124
+ copy.className = 'connection-card-note-url';
125
+ copy.title = `Copy ${url}`;
126
+ const code = document.createElement('code');
127
+ code.textContent = url;
128
+ const icon = document.createElement('span');
129
+ icon.className = 'material-symbols-outlined';
130
+ icon.textContent = 'content_copy';
131
+ copy.append(code, icon);
132
+ copy.onclick = async () => {
133
+ try {
134
+ await navigator.clipboard.writeText(url);
135
+ icon.textContent = 'check';
136
+ copy.classList.add('copied');
137
+ setTimeout(() => {
138
+ icon.textContent = 'content_copy';
139
+ copy.classList.remove('copied');
140
+ }, 1600);
141
+ }
142
+ catch (err) {
143
+ fail(err);
144
+ }
145
+ };
146
+ note.appendChild(copy);
147
+ return note;
148
+ }
78
149
  // One card per platform. `accounts` are the already-linked accounts for this
79
150
  // platform (possibly several); each gets a labeled row with an unlink action.
80
151
  function buildCard(platform, accounts) {
@@ -141,6 +212,9 @@ export function createConnections(credentials, options = {}) {
141
212
  }
142
213
  card.appendChild(accountsBox);
143
214
  }
215
+ if (platform === Fauna.Integrations.Platform.TWILIO && accounts.length) {
216
+ card.appendChild(buildTwilioWebhookNote());
217
+ }
144
218
  // Connect / add-another action.
145
219
  const connectBtn = document.createElement('button');
146
220
  connectBtn.type = 'button';
@@ -1,5 +1,5 @@
1
1
  import { type CSSProperties } from "react";
2
- import maplibregl from "maplibre-gl";
2
+ import * as maplibregl from "maplibre-gl";
3
3
  import type { ApiKey, Credentials } from "../authentification.js";
4
4
  import type { Group } from "../groups.js";
5
5
  import type { Coordinates, MapTheme, Place, Route, TravelMode, Units } from "../maps.js";
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useLayoutEffect, useRef, useState } from "react";
3
3
  import { createRoot } from "react-dom/client";
4
- import maplibregl from "maplibre-gl";
4
+ import * as maplibregl from "maplibre-gl";
5
5
  import { gsap } from "gsap";
6
6
  import { Fauna } from "fauna-api";
7
7
  function reducedMotion() {
@@ -368,6 +368,8 @@ function formatTime(value) {
368
368
  }
369
369
  /** Open where to book: a link, or a form posted in a new tab when the offer carries one. */
370
370
  export function openOffer(offer) {
371
+ if (!/^https?:/i.test(offer.url))
372
+ return;
371
373
  if (!offer.form) {
372
374
  window.open(offer.url, "_blank", "noopener");
373
375
  return;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Markdown as HTML that is safe to put in the page, for text anyone else wrote: people,
3
+ * models, mail and the web.
4
+ */
5
+ export declare function render(text: string): string;
@@ -0,0 +1,33 @@
1
+ import { Marked } from "marked";
2
+ function escapeHtml(text) {
3
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
4
+ }
5
+ const LINK = /^(https:|mailto:)/i;
6
+ const IMAGE = /^https:/i;
7
+ /** Raw HTML shows as text, links go only to https or mail, and images only load over https. */
8
+ const markdown = new Marked({
9
+ renderer: {
10
+ html({ text }) {
11
+ return escapeHtml(text);
12
+ },
13
+ link({ href, title, tokens }) {
14
+ const text = this.parser.parseInline(tokens);
15
+ if (!LINK.test(href))
16
+ return text;
17
+ return `<a href="${escapeHtml(href)}"${title ? ` title="${escapeHtml(title)}"` : ""} target="_blank" rel="noopener noreferrer">${text}</a>`;
18
+ },
19
+ image({ href, title, text, tokens }) {
20
+ const alt = escapeHtml(tokens ? this.parser.parseInline(tokens, this.parser.textRenderer) : text);
21
+ if (!IMAGE.test(href))
22
+ return alt;
23
+ return `<img src="${escapeHtml(href)}" alt="${alt}"${title ? ` title="${escapeHtml(title)}"` : ""}>`;
24
+ },
25
+ },
26
+ });
27
+ /**
28
+ * Markdown as HTML that is safe to put in the page, for text anyone else wrote: people,
29
+ * models, mail and the web.
30
+ */
31
+ export function render(text) {
32
+ return markdown.parse(text, { async: false });
33
+ }
@@ -1,6 +1,6 @@
1
1
  type ToastType = 'success' | 'error' | 'warning' | 'info';
2
- export declare function sendInformaticToast(html: string, type: ToastType, duration?: number): Promise<boolean>;
3
- export declare function sendProgressToast(html: string): {
2
+ export declare function sendInformaticToast(text: string, type: ToastType, duration?: number): Promise<boolean>;
3
+ export declare function sendProgressToast(text: string): {
4
4
  show: Promise<boolean>;
5
5
  setProgress: (progress: number) => void;
6
6
  };
@@ -41,13 +41,13 @@ async function drain() {
41
41
  PROCESSING = false;
42
42
  }
43
43
  // ─── Public API (imperative, unchanged signatures) ────────────────────────────
44
- export async function sendInformaticToast(html, type, duration = 5000) {
44
+ export async function sendInformaticToast(text, type, duration = 5000) {
45
45
  return new Promise((resolveShow) => {
46
46
  QUEUE.push(() => {
47
47
  let resolveDone;
48
48
  const done = new Promise((res) => { resolveDone = res; });
49
49
  const toast = {
50
- state: { html, type, progress: null },
50
+ state: { text, type, progress: null },
51
51
  done,
52
52
  resolveDone,
53
53
  resolveShow,
@@ -61,7 +61,7 @@ export async function sendInformaticToast(html, type, duration = 5000) {
61
61
  void drain();
62
62
  });
63
63
  }
64
- export function sendProgressToast(html) {
64
+ export function sendProgressToast(text) {
65
65
  let setter = () => { };
66
66
  let current = 0;
67
67
  const show = new Promise((resolveShow) => {
@@ -73,7 +73,7 @@ export function sendProgressToast(html) {
73
73
  let resolveDone;
74
74
  const done = new Promise((res) => { resolveDone = res; });
75
75
  const toast = {
76
- state: { html, type: 'info', progress: current },
76
+ state: { text, type: 'info', progress: current },
77
77
  done,
78
78
  resolveDone,
79
79
  resolveShow,
@@ -246,7 +246,7 @@ function ToastRoot() {
246
246
  borderRadius: 999,
247
247
  background: `${p.accent}18`,
248
248
  color: p.accent,
249
- }, children: _jsx("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.4, strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: p.icon }) }) }), _jsx("span", { style: { flex: 1, minWidth: 0, overflowWrap: 'anywhere' }, dangerouslySetInnerHTML: { __html: state.html } }), _jsx("span", { "aria-hidden": "true", style: { flex: '0 0 auto', color: '#8d8981', fontSize: 16, lineHeight: 1 }, children: "\u00D7" }), state.progress !== null && (_jsx("span", { style: {
249
+ }, children: _jsx("svg", { width: "15", height: "15", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2.4, strokeLinecap: "round", strokeLinejoin: "round", children: _jsx("path", { d: p.icon }) }) }), _jsx("span", { style: { flex: 1, minWidth: 0, overflowWrap: 'anywhere' }, children: state.text }), _jsx("span", { "aria-hidden": "true", style: { flex: '0 0 auto', color: '#8d8981', fontSize: 16, lineHeight: 1 }, children: "\u00D7" }), state.progress !== null && (_jsx("span", { style: {
250
250
  position: 'absolute',
251
251
  left: 0,
252
252
  bottom: 0,