@burdenoff/website-sdk 2026.925.2 → 2026.926.1
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/dist/components/contact.js +8 -0
- package/dist/components/contact.js.map +1 -1
- package/dist/components/contact.mjs +8 -0
- package/dist/components/contact.mjs.map +1 -1
- package/dist/components/explore.d.mts +1 -1
- package/dist/components/explore.d.ts +1 -1
- package/dist/components/explore.js +1101 -518
- package/dist/components/explore.js.map +1 -1
- package/dist/components/explore.mjs +1103 -521
- package/dist/components/explore.mjs.map +1 -1
- package/dist/components/newsletter.js +8 -0
- package/dist/components/newsletter.js.map +1 -1
- package/dist/components/newsletter.mjs +8 -0
- package/dist/components/newsletter.mjs.map +1 -1
- package/dist/components/partners.js +11 -2
- package/dist/components/partners.js.map +1 -1
- package/dist/components/partners.mjs +11 -2
- package/dist/components/partners.mjs.map +1 -1
- package/dist/components/resource-links.js +8 -0
- package/dist/components/resource-links.js.map +1 -1
- package/dist/components/resource-links.mjs +8 -0
- package/dist/components/resource-links.mjs.map +1 -1
- package/dist/components/social-links.js +8 -0
- package/dist/components/social-links.js.map +1 -1
- package/dist/components/social-links.mjs +8 -0
- package/dist/components/social-links.mjs.map +1 -1
- package/dist/components/waitlist.js +8 -0
- package/dist/components/waitlist.js.map +1 -1
- package/dist/components/waitlist.mjs +8 -0
- package/dist/components/waitlist.mjs.map +1 -1
- package/dist/content/index.d.mts +1 -1
- package/dist/content/index.d.ts +1 -1
- package/dist/content/index.js +11 -2
- package/dist/content/index.js.map +1 -1
- package/dist/content/index.mjs +11 -2
- package/dist/content/index.mjs.map +1 -1
- package/dist/{explore-C0RFTKAi.d.ts → explore-Cam_ElSJ.d.ts} +70 -12
- package/dist/{explore-DzOf7n4m.d.mts → explore-hTQm-_SF.d.mts} +70 -12
- package/dist/hooks/index.js +51 -13
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +51 -13
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/{index-BMWQNZDX.d.mts → index-gm3pBCJT.d.mts} +8 -2
- package/dist/{index-BqHF-G-S.d.ts → index-ySbOLSD5.d.ts} +8 -2
- package/dist/index.d.mts +32 -11
- package/dist/index.d.ts +32 -11
- package/dist/index.js +901 -267
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +892 -269
- package/dist/index.mjs.map +1 -1
- package/dist/product/index.d.mts +16 -1
- package/dist/product/index.d.ts +16 -1
- package/dist/product/index.js +61 -17
- package/dist/product/index.js.map +1 -1
- package/dist/product/index.mjs +61 -18
- package/dist/product/index.mjs.map +1 -1
- package/dist/store/index.js +11 -2
- package/dist/store/index.js.map +1 -1
- package/dist/store/index.mjs +11 -2
- package/dist/store/index.mjs.map +1 -1
- package/dist/utils/index.d.mts +41 -1
- package/dist/utils/index.d.ts +41 -1
- package/dist/utils/index.js +42 -0
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +35 -1
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +1 -1
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
-
import { createContext, useMemo, useState, useRef, useEffect, useCallback, useContext, Children, isValidElement, cloneElement } from 'react';
|
|
3
|
-
import { Sparkles, RotateCcw, Link2, History, Trash2, Paperclip, X, Mic, Square, ArrowUp, BookOpenText, Layers, TriangleAlert, ChevronLeft, ChevronRight, ExternalLink, Check, Copy, ThumbsUp, ThumbsDown, Volume2 } from 'lucide-react';
|
|
2
|
+
import { createContext, useMemo, useState, useRef, useEffect, useCallback, useContext, Children, isValidElement, cloneElement, useId } from 'react';
|
|
3
|
+
import { Sparkles, RotateCcw, Link2, History, Trash2, Paperclip, X, Mic, Square, ArrowUp, BookOpenText, Layers, TriangleAlert, ChevronLeft, ChevronRight, ExternalLink, Check, Copy, ThumbsUp, ThumbsDown, Volume2, Film, Play, Minimize, Maximize, VideoOff } from 'lucide-react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
6
|
import ReactMarkdown from 'react-markdown';
|
|
5
7
|
import rehypeSanitize from 'rehype-sanitize';
|
|
6
8
|
import remarkGfm from 'remark-gfm';
|
|
7
|
-
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
9
|
import { clsx } from 'clsx';
|
|
9
10
|
import { twMerge } from 'tailwind-merge';
|
|
10
11
|
import { Helmet } from 'react-helmet-async';
|
|
@@ -14,478 +15,12 @@ import { cva } from 'class-variance-authority';
|
|
|
14
15
|
var __defProp = Object.defineProperty;
|
|
15
16
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
16
17
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
17
|
-
function MarkdownLink({
|
|
18
|
-
href,
|
|
19
|
-
children,
|
|
20
|
-
// react-markdown passes the hast node to every override; it is not a DOM attribute.
|
|
21
|
-
node: _node,
|
|
22
|
-
...props
|
|
23
|
-
}) {
|
|
24
|
-
const isExternal = typeof href === "string" && (href.startsWith("http://") || href.startsWith("https://"));
|
|
25
|
-
return /* @__PURE__ */ jsx(
|
|
26
|
-
"a",
|
|
27
|
-
{
|
|
28
|
-
href,
|
|
29
|
-
...props,
|
|
30
|
-
...isExternal ? { target: "_blank", rel: "noopener noreferrer" } : { rel: "noopener noreferrer" },
|
|
31
|
-
children
|
|
32
|
-
}
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
var defaultComponents = {
|
|
36
|
-
a: MarkdownLink
|
|
37
|
-
};
|
|
38
|
-
function Markdown({
|
|
39
|
-
children,
|
|
40
|
-
className,
|
|
41
|
-
inline,
|
|
42
|
-
components
|
|
43
|
-
}) {
|
|
44
|
-
const content = /* @__PURE__ */ jsx(
|
|
45
|
-
ReactMarkdown,
|
|
46
|
-
{
|
|
47
|
-
remarkPlugins: [remarkGfm],
|
|
48
|
-
rehypePlugins: [rehypeSanitize],
|
|
49
|
-
components: { ...defaultComponents, ...components },
|
|
50
|
-
...inline ? { disallowedElements: ["p"], unwrapDisallowed: true } : {},
|
|
51
|
-
children
|
|
52
|
-
}
|
|
53
|
-
);
|
|
54
|
-
if (inline) {
|
|
55
|
-
return /* @__PURE__ */ jsx("span", { className, children: content });
|
|
56
|
-
}
|
|
57
|
-
return /* @__PURE__ */ jsx("div", { className, children: content });
|
|
58
|
-
}
|
|
59
|
-
function cn(...inputs) {
|
|
60
|
-
return twMerge(clsx(inputs));
|
|
61
|
-
}
|
|
62
|
-
var EXPLORE_ANSWER_CSS = `
|
|
63
|
-
/* Nothing inside an answer may widen the page. Model output carries long URLs, code and
|
|
64
|
-
tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
|
|
65
|
-
WHOLE document horizontally scrollable, not just the bubble. */
|
|
66
|
-
.boff-explore-body,
|
|
67
|
-
.boff-explore-body p,
|
|
68
|
-
.boff-explore-body li,
|
|
69
|
-
.boff-explore-body a,
|
|
70
|
-
.boff-explore-body h1,
|
|
71
|
-
.boff-explore-body h2,
|
|
72
|
-
.boff-explore-body h3,
|
|
73
|
-
/* h4-h6 as well: a published answer page pushes the model's own headings down a level or
|
|
74
|
-
two so they nest under the page's, and a long unbroken word is no narrower for it. */
|
|
75
|
-
.boff-explore-body h4,
|
|
76
|
-
.boff-explore-body h5,
|
|
77
|
-
.boff-explore-body h6 {
|
|
78
|
-
overflow-wrap: anywhere;
|
|
79
|
-
word-break: break-word;
|
|
80
|
-
}
|
|
81
|
-
.boff-explore-body pre {
|
|
82
|
-
overflow-x: auto;
|
|
83
|
-
max-width: 100%;
|
|
84
|
-
}
|
|
85
|
-
.boff-explore-body table {
|
|
86
|
-
display: block;
|
|
87
|
-
overflow-x: auto;
|
|
88
|
-
max-width: 100%;
|
|
89
|
-
}
|
|
90
|
-
.boff-explore-body img {
|
|
91
|
-
max-width: 100%;
|
|
92
|
-
height: auto;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/* ---- Comparison and pricing tables ----------------------------------------------------
|
|
96
|
-
The harness emits a comparison, and a set of plans, as a GFM table whose FIRST column is
|
|
97
|
-
the subject \u2014 the product, or the plan (CONTRACT.md \xA77). Two things have to be true of it:
|
|
98
|
-
the page must not scroll sideways because of it, and it has to be readable on a 320px
|
|
99
|
-
phone, which a four-column grid of 1px borders is not.
|
|
100
|
-
|
|
101
|
-
Wide: a contained scroller. \`max-width: 100%\` keeps the box inside the bubble and
|
|
102
|
-
\`overflow-x: auto\` keeps the columns inside the box, so the widest possible table still
|
|
103
|
-
cannot widen the document. \`overscroll-behavior-x: contain\` stops a swipe that runs out
|
|
104
|
-
of table from chaining to the page \u2014 the same rule the transcript needed. */
|
|
105
|
-
.boff-explore-body .boff-explore-table {
|
|
106
|
-
max-width: 100%;
|
|
107
|
-
overflow-x: auto;
|
|
108
|
-
overscroll-behavior-x: contain;
|
|
109
|
-
margin: 0.75rem 0;
|
|
110
|
-
border: 1px solid hsl(var(--border));
|
|
111
|
-
border-radius: 0.75rem;
|
|
112
|
-
background-color: hsl(var(--card));
|
|
113
|
-
}
|
|
114
|
-
.boff-explore-body .boff-explore-table:focus-visible {
|
|
115
|
-
outline: 2px solid hsl(var(--ring));
|
|
116
|
-
outline-offset: 2px;
|
|
117
|
-
}
|
|
118
|
-
.boff-explore-body .boff-explore-table table {
|
|
119
|
-
/* Beats the blanket \`display: block\` above, which is the unwrapped fallback. */
|
|
120
|
-
display: table;
|
|
121
|
-
width: 100%;
|
|
122
|
-
max-width: none;
|
|
123
|
-
margin: 0;
|
|
124
|
-
border-collapse: collapse;
|
|
125
|
-
font-size: 0.8125rem;
|
|
126
|
-
}
|
|
127
|
-
.boff-explore-body .boff-explore-table th,
|
|
128
|
-
.boff-explore-body .boff-explore-table td {
|
|
129
|
-
border: 0;
|
|
130
|
-
border-top: 1px solid hsl(var(--border));
|
|
131
|
-
padding: 0.5rem 0.75rem;
|
|
132
|
-
/* \`start\`, never \`left\`: the page root carries dir="rtl" in Arabic, and a left-aligned
|
|
133
|
-
table there reads against the grain of every other line on the page. */
|
|
134
|
-
text-align: start;
|
|
135
|
-
vertical-align: top;
|
|
136
|
-
overflow-wrap: anywhere;
|
|
137
|
-
/* Prices in a column line up under each other instead of drifting by digit width. */
|
|
138
|
-
font-variant-numeric: tabular-nums;
|
|
139
|
-
min-width: 6rem;
|
|
140
|
-
}
|
|
141
|
-
.boff-explore-body .boff-explore-table thead th {
|
|
142
|
-
border-top: 0;
|
|
143
|
-
background-color: hsl(var(--muted));
|
|
144
|
-
color: hsl(var(--foreground));
|
|
145
|
-
font-weight: 600;
|
|
146
|
-
white-space: nowrap;
|
|
147
|
-
}
|
|
148
|
-
/* The first column is what the row is ABOUT \u2014 the product, or the plan name. Reading it as
|
|
149
|
-
the row's heading is what makes a price list scannable rather than a grid of strings. */
|
|
150
|
-
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
151
|
-
font-weight: 600;
|
|
152
|
-
color: hsl(var(--card-foreground));
|
|
153
|
-
}
|
|
154
18
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
.boff-explore-body .boff-explore-table {
|
|
161
|
-
overflow-x: visible;
|
|
162
|
-
}
|
|
163
|
-
.boff-explore-body .boff-explore-table table,
|
|
164
|
-
.boff-explore-body .boff-explore-table tbody,
|
|
165
|
-
.boff-explore-body .boff-explore-table tr,
|
|
166
|
-
.boff-explore-body .boff-explore-table td {
|
|
167
|
-
display: block;
|
|
168
|
-
width: 100%;
|
|
169
|
-
min-width: 0;
|
|
170
|
-
}
|
|
171
|
-
.boff-explore-body .boff-explore-table thead {
|
|
172
|
-
display: none;
|
|
173
|
-
}
|
|
174
|
-
.boff-explore-body .boff-explore-table tr {
|
|
175
|
-
border-top: 1px solid hsl(var(--border));
|
|
176
|
-
padding: 0.5rem 0;
|
|
177
|
-
}
|
|
178
|
-
.boff-explore-body .boff-explore-table tr:first-child {
|
|
179
|
-
border-top: 0;
|
|
180
|
-
}
|
|
181
|
-
.boff-explore-body .boff-explore-table td {
|
|
182
|
-
border: 0;
|
|
183
|
-
padding: 0.125rem 0.75rem;
|
|
184
|
-
}
|
|
185
|
-
.boff-explore-body .boff-explore-table td[data-label]::before {
|
|
186
|
-
content: attr(data-label);
|
|
187
|
-
display: block;
|
|
188
|
-
font-size: 0.6875rem;
|
|
189
|
-
font-weight: 600;
|
|
190
|
-
text-transform: uppercase;
|
|
191
|
-
letter-spacing: 0.04em;
|
|
192
|
-
color: hsl(var(--muted-foreground));
|
|
193
|
-
}
|
|
194
|
-
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
195
|
-
font-size: 0.9375rem;
|
|
196
|
-
padding-bottom: 0.25rem;
|
|
197
|
-
}
|
|
198
|
-
.boff-explore-body .boff-explore-table tbody td:first-child::before {
|
|
199
|
-
/* The plan's own name is the heading here; repeating "Plan" over it says nothing. */
|
|
200
|
-
display: none;
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
`;
|
|
204
|
-
function ExploreAnswerStyles() {
|
|
205
|
-
return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_ANSWER_CSS } });
|
|
206
|
-
}
|
|
207
|
-
function inlineImageOrigins(products) {
|
|
208
|
-
const origins = /* @__PURE__ */ new Set();
|
|
209
|
-
for (const product of products) {
|
|
210
|
-
let url;
|
|
211
|
-
try {
|
|
212
|
-
url = new URL(product.website);
|
|
213
|
-
} catch {
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
if (url.protocol !== "https:") continue;
|
|
217
|
-
const apex = url.hostname.replace(/^www\./, "");
|
|
218
|
-
const port = url.port ? `:${url.port}` : "";
|
|
219
|
-
origins.add(`https://${apex}${port}`);
|
|
220
|
-
origins.add(`https://www.${apex}${port}`);
|
|
221
|
-
}
|
|
222
|
-
return origins;
|
|
223
|
-
}
|
|
224
|
-
function isInlineImageAllowed(src, origins) {
|
|
225
|
-
if (typeof src !== "string" || src === "") return false;
|
|
226
|
-
let url;
|
|
227
|
-
try {
|
|
228
|
-
url = new URL(src);
|
|
229
|
-
} catch {
|
|
230
|
-
return false;
|
|
231
|
-
}
|
|
232
|
-
if (url.protocol !== "https:" || url.username || url.password) return false;
|
|
233
|
-
return origins.has(url.origin);
|
|
234
|
-
}
|
|
235
|
-
var CODE_FENCE = /^\s*(?:```|~~~)/;
|
|
236
|
-
var TABLE_ROW_LINE = /^\s{0,3}\|/;
|
|
237
|
-
var TABLE_DELIMITER_ROW = /^\s{0,3}\|[\s:|-]*-[\s:|-]*$/;
|
|
238
|
-
function readableLine(raw, mode) {
|
|
239
|
-
let line = raw;
|
|
240
|
-
line = line.replace(/!\[[^\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g, "");
|
|
241
|
-
line = mode === "speech" ? (
|
|
242
|
-
// A voice reading "h-t-t-p-s colon slash slash…" mid-sentence is noise, and the
|
|
243
|
-
// prompt asks for two to five links in every answer. The label IS the words.
|
|
244
|
-
line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1")
|
|
245
|
-
) : line.replace(/\[([^\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g, "$1 ($2)");
|
|
246
|
-
line = mode === "speech" ? line.replace(/<(https?:\/\/[^>\s]+)>/g, "") : line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
|
|
247
|
-
line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
|
|
248
|
-
line = line.replace(/^\s{0,3}>\s?/, "");
|
|
249
|
-
if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) return "";
|
|
250
|
-
line = line.replace(/`([^`]+)`/g, "$1");
|
|
251
|
-
line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
252
|
-
line = line.replace(/__([^_]+)__/g, "$1");
|
|
253
|
-
line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
|
|
254
|
-
line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
|
|
255
|
-
return line.replace(/[ \t]+$/, "");
|
|
256
|
-
}
|
|
257
|
-
function tableCells(row) {
|
|
258
|
-
const trimmed = row.trim();
|
|
259
|
-
const cells = [];
|
|
260
|
-
let cell = "";
|
|
261
|
-
for (let at = 0; at < trimmed.length; at += 1) {
|
|
262
|
-
const char = trimmed[at] ?? "";
|
|
263
|
-
if (char === "\\") {
|
|
264
|
-
cell += char + (trimmed[at + 1] ?? "");
|
|
265
|
-
at += 1;
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
if (char === "|") {
|
|
269
|
-
cells.push(cell.trim());
|
|
270
|
-
cell = "";
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
cell += char;
|
|
274
|
-
}
|
|
275
|
-
cells.push(cell.trim());
|
|
276
|
-
if (trimmed.startsWith("|") && cells[0] === "") cells.shift();
|
|
277
|
-
if (trimmed.endsWith("|") && cells[cells.length - 1] === "") cells.pop();
|
|
278
|
-
return cells;
|
|
279
|
-
}
|
|
280
|
-
function asSentence(text) {
|
|
281
|
-
return /[.!?]$/.test(text) ? text : `${text}.`;
|
|
282
|
-
}
|
|
283
|
-
function spokenTable(rows) {
|
|
284
|
-
if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? "")) return rows;
|
|
285
|
-
const labels = tableCells(rows[0] ?? "");
|
|
286
|
-
const spoken = [];
|
|
287
|
-
for (const row of rows.slice(2)) {
|
|
288
|
-
const parts = [];
|
|
289
|
-
tableCells(row).forEach((value, column) => {
|
|
290
|
-
if (value === "") return;
|
|
291
|
-
const label = column === 0 ? "" : labels[column] ?? "";
|
|
292
|
-
parts.push(label === "" ? value : `${label}: ${value}`);
|
|
293
|
-
});
|
|
294
|
-
if (parts.length > 0) spoken.push(asSentence(parts.join(". ")));
|
|
295
|
-
}
|
|
296
|
-
return spoken;
|
|
297
|
-
}
|
|
298
|
-
function readableText(markdown, mode) {
|
|
299
|
-
const lines = [];
|
|
300
|
-
let inFence = false;
|
|
301
|
-
let table = [];
|
|
302
|
-
const flushTable = () => {
|
|
303
|
-
if (table.length === 0) return;
|
|
304
|
-
for (const line of spokenTable(table)) lines.push(readableLine(line, mode));
|
|
305
|
-
table = [];
|
|
306
|
-
};
|
|
307
|
-
for (const raw of markdown.split("\n")) {
|
|
308
|
-
if (CODE_FENCE.test(raw)) {
|
|
309
|
-
flushTable();
|
|
310
|
-
inFence = !inFence;
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
if (inFence) {
|
|
314
|
-
lines.push(raw);
|
|
315
|
-
continue;
|
|
316
|
-
}
|
|
317
|
-
if (mode === "speech" && TABLE_ROW_LINE.test(raw)) {
|
|
318
|
-
table.push(raw);
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
flushTable();
|
|
322
|
-
lines.push(readableLine(raw, mode));
|
|
323
|
-
}
|
|
324
|
-
flushTable();
|
|
325
|
-
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
326
|
-
}
|
|
327
|
-
function answerPlainText(markdown) {
|
|
328
|
-
return readableText(markdown, "clipboard");
|
|
329
|
-
}
|
|
330
|
-
function answerSpeechText(markdown) {
|
|
331
|
-
return readableText(markdown, "speech");
|
|
332
|
-
}
|
|
333
|
-
var AnswerScopeContext = createContext(null);
|
|
334
|
-
var InsideLinkContext = createContext(false);
|
|
335
|
-
function AnswerLink(props) {
|
|
336
|
-
return /* @__PURE__ */ jsx(InsideLinkContext.Provider, { value: true, children: /* @__PURE__ */ jsx(MarkdownLink, { ...props }) });
|
|
337
|
-
}
|
|
338
|
-
var INLINE_IMAGE_FRAME = "relative inline-block max-w-full overflow-hidden rounded-xl border border-border bg-muted align-top shadow-sm";
|
|
339
|
-
function InlineConceptImage({ src, alt }) {
|
|
340
|
-
const scope = useContext(AnswerScopeContext);
|
|
341
|
-
const insideLink = useContext(InsideLinkContext);
|
|
342
|
-
if (!scope || !scope.enabled || !isInlineImageAllowed(src, scope.origins) || scope.failed.has(src)) {
|
|
343
|
-
return null;
|
|
344
|
-
}
|
|
345
|
-
const { strings: T, onFailed } = scope;
|
|
346
|
-
const picture = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
347
|
-
/* @__PURE__ */ jsx(
|
|
348
|
-
"img",
|
|
349
|
-
{
|
|
350
|
-
src,
|
|
351
|
-
alt: alt ?? "",
|
|
352
|
-
loading: "lazy",
|
|
353
|
-
decoding: "async",
|
|
354
|
-
onError: () => onFailed(src),
|
|
355
|
-
className: "block h-auto max-h-80 w-auto max-w-full object-contain"
|
|
356
|
-
}
|
|
357
|
-
),
|
|
358
|
-
/* @__PURE__ */ jsx(
|
|
359
|
-
"span",
|
|
360
|
-
{
|
|
361
|
-
"aria-hidden": "true",
|
|
362
|
-
className: "pointer-events-none absolute left-2 top-2 rounded-full border border-border bg-background/90 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-foreground shadow-sm",
|
|
363
|
-
children: T.conceptIllustration
|
|
364
|
-
}
|
|
365
|
-
)
|
|
366
|
-
] });
|
|
367
|
-
return /* @__PURE__ */ jsx("span", { "data-boff-explore": "inline-image", className: "my-3 block", children: insideLink ? /* @__PURE__ */ jsx("span", { className: INLINE_IMAGE_FRAME, children: picture }) : /* @__PURE__ */ jsxs(
|
|
368
|
-
"a",
|
|
369
|
-
{
|
|
370
|
-
href: src,
|
|
371
|
-
target: "_blank",
|
|
372
|
-
rel: "noopener noreferrer",
|
|
373
|
-
className: cn(
|
|
374
|
-
INLINE_IMAGE_FRAME,
|
|
375
|
-
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
376
|
-
),
|
|
377
|
-
children: [
|
|
378
|
-
picture,
|
|
379
|
-
/* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
|
|
380
|
-
"(",
|
|
381
|
-
T.opensInNewTab,
|
|
382
|
-
")"
|
|
383
|
-
] })
|
|
384
|
-
]
|
|
385
|
-
}
|
|
386
|
-
) });
|
|
387
|
-
}
|
|
388
|
-
function markdownText(node) {
|
|
389
|
-
if (!node) return "";
|
|
390
|
-
if (typeof node.value === "string") return node.value;
|
|
391
|
-
return (node.children ?? []).map(markdownText).join("");
|
|
392
|
-
}
|
|
393
|
-
function hasHeaderCells(node) {
|
|
394
|
-
return (node?.children ?? []).some((cell) => cell.tagName === "th");
|
|
395
|
-
}
|
|
396
|
-
function findHeaderRow(node) {
|
|
397
|
-
for (const child of node?.children ?? []) {
|
|
398
|
-
if (child.tagName === "tr" && hasHeaderCells(child)) return child;
|
|
399
|
-
const nested = findHeaderRow(child);
|
|
400
|
-
if (nested !== null) return nested;
|
|
401
|
-
}
|
|
402
|
-
return null;
|
|
403
|
-
}
|
|
404
|
-
function tableColumnLabels(node) {
|
|
405
|
-
const row = findHeaderRow(node);
|
|
406
|
-
if (row === null) return [];
|
|
407
|
-
return (row.children ?? []).filter((cell) => cell.tagName === "th").map((cell) => markdownText(cell).trim());
|
|
408
|
-
}
|
|
409
|
-
var TableColumnsContext = createContext([]);
|
|
410
|
-
function AnswerTable({ node, children }) {
|
|
411
|
-
const scope = useContext(AnswerScopeContext);
|
|
412
|
-
const table = /* @__PURE__ */ jsx(TableColumnsContext.Provider, { value: tableColumnLabels(node), children: /* @__PURE__ */ jsx("table", { children }) });
|
|
413
|
-
if (!scope) return table;
|
|
414
|
-
return /* @__PURE__ */ jsx(
|
|
415
|
-
"div",
|
|
416
|
-
{
|
|
417
|
-
"data-boff-explore": "table",
|
|
418
|
-
role: "region",
|
|
419
|
-
"aria-label": scope.strings.answerTable,
|
|
420
|
-
tabIndex: 0,
|
|
421
|
-
className: "boff-explore-table",
|
|
422
|
-
children: table
|
|
423
|
-
}
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
function AnswerTableRow({
|
|
427
|
-
node,
|
|
428
|
-
children,
|
|
429
|
-
...props
|
|
430
|
-
}) {
|
|
431
|
-
const labels = useContext(TableColumnsContext);
|
|
432
|
-
if (labels.length === 0 || hasHeaderCells(node)) {
|
|
433
|
-
return /* @__PURE__ */ jsx("tr", { ...props, children });
|
|
434
|
-
}
|
|
435
|
-
let column = 0;
|
|
436
|
-
const cells = Children.map(children, (cell) => {
|
|
437
|
-
if (!isValidElement(cell)) return cell;
|
|
438
|
-
const label = labels[column] ?? "";
|
|
439
|
-
column += 1;
|
|
440
|
-
if (label === "") return cell;
|
|
441
|
-
return cloneElement(cell, {
|
|
442
|
-
"data-label": label
|
|
443
|
-
});
|
|
444
|
-
});
|
|
445
|
-
return /* @__PURE__ */ jsx("tr", { ...props, children: cells });
|
|
446
|
-
}
|
|
447
|
-
var ANSWER_MARKDOWN_COMPONENTS = {
|
|
448
|
-
a: AnswerLink,
|
|
449
|
-
img: InlineConceptImage,
|
|
450
|
-
table: AnswerTable,
|
|
451
|
-
tr: AnswerTableRow
|
|
452
|
-
};
|
|
453
|
-
function ExploreAnswerBody({
|
|
454
|
-
children,
|
|
455
|
-
strings,
|
|
456
|
-
imageOrigins,
|
|
457
|
-
enabled = true,
|
|
458
|
-
components,
|
|
459
|
-
className
|
|
460
|
-
}) {
|
|
461
|
-
const [failed, setFailed] = useState(() => /* @__PURE__ */ new Set());
|
|
462
|
-
const onFailed = useCallback((src) => {
|
|
463
|
-
setFailed(
|
|
464
|
-
(current) => current.has(src) ? current : new Set(current).add(src)
|
|
465
|
-
);
|
|
466
|
-
}, []);
|
|
467
|
-
const scope = useMemo(
|
|
468
|
-
() => ({
|
|
469
|
-
strings,
|
|
470
|
-
enabled,
|
|
471
|
-
origins: imageOrigins,
|
|
472
|
-
failed,
|
|
473
|
-
onFailed
|
|
474
|
-
}),
|
|
475
|
-
[strings, enabled, imageOrigins, failed, onFailed]
|
|
476
|
-
);
|
|
477
|
-
const overrides = useMemo(
|
|
478
|
-
() => components ? { ...ANSWER_MARKDOWN_COMPONENTS, ...components } : ANSWER_MARKDOWN_COMPONENTS,
|
|
479
|
-
[components]
|
|
480
|
-
);
|
|
481
|
-
return /* @__PURE__ */ jsx("div", { className: cn("boff-prose boff-explore-body min-w-0", className), children: /* @__PURE__ */ jsx(AnswerScopeContext.Provider, { value: scope, children: /* @__PURE__ */ jsx(Markdown, { components: overrides, children }) }) });
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// src/components/explore-i18n.ts
|
|
485
|
-
var EXPLORE_LOCALES = ["en", "zh", "hi", "es", "ar", "ta"];
|
|
486
|
-
var RTL_LOCALES = /* @__PURE__ */ new Set(["ar"]);
|
|
487
|
-
function isRtlLocale(locale) {
|
|
488
|
-
return RTL_LOCALES.has(locale);
|
|
19
|
+
// src/components/explore-i18n.ts
|
|
20
|
+
var EXPLORE_LOCALES = ["en", "zh", "hi", "es", "ar", "ta"];
|
|
21
|
+
var RTL_LOCALES = /* @__PURE__ */ new Set(["ar"]);
|
|
22
|
+
function isRtlLocale(locale) {
|
|
23
|
+
return RTL_LOCALES.has(locale);
|
|
489
24
|
}
|
|
490
25
|
var EN = {
|
|
491
26
|
badge: "AI answers, grounded in our documentation",
|
|
@@ -568,6 +103,13 @@ var EN = {
|
|
|
568
103
|
readAloud: "Read aloud",
|
|
569
104
|
stopReading: "Stop reading",
|
|
570
105
|
answerTable: "Table",
|
|
106
|
+
productVideo: "Product video",
|
|
107
|
+
productVideoTitle: "{product} product video",
|
|
108
|
+
playVideo: "Play {title}",
|
|
109
|
+
closeVideo: "Close video",
|
|
110
|
+
enterFullScreen: "Full screen",
|
|
111
|
+
exitFullScreen: "Exit full screen",
|
|
112
|
+
videoUnavailable: "This video can't be played right now.",
|
|
571
113
|
groundedIn: "What this answer is based on",
|
|
572
114
|
sourceCountOne: "1 source",
|
|
573
115
|
sourceCountMany: "{count} sources",
|
|
@@ -669,6 +211,13 @@ var ZH = {
|
|
|
669
211
|
readAloud: "\u6717\u8BFB",
|
|
670
212
|
stopReading: "\u505C\u6B62\u6717\u8BFB",
|
|
671
213
|
answerTable: "\u8868\u683C",
|
|
214
|
+
productVideo: "\u4EA7\u54C1\u89C6\u9891",
|
|
215
|
+
productVideoTitle: "{product} \u4EA7\u54C1\u89C6\u9891",
|
|
216
|
+
playVideo: "\u64AD\u653E {title}",
|
|
217
|
+
closeVideo: "\u5173\u95ED\u89C6\u9891",
|
|
218
|
+
enterFullScreen: "\u5168\u5C4F",
|
|
219
|
+
exitFullScreen: "\u9000\u51FA\u5168\u5C4F",
|
|
220
|
+
videoUnavailable: "\u6682\u65F6\u65E0\u6CD5\u64AD\u653E\u6B64\u89C6\u9891\u3002",
|
|
672
221
|
groundedIn: "\u8FD9\u4E2A\u56DE\u7B54\u7684\u4F9D\u636E",
|
|
673
222
|
sourceCountOne: "1 \u4E2A\u6765\u6E90",
|
|
674
223
|
sourceCountMany: "{count} \u4E2A\u6765\u6E90",
|
|
@@ -766,6 +315,13 @@ var HI = {
|
|
|
766
315
|
readAloud: "\u091C\u093C\u094B\u0930 \u0938\u0947 \u092A\u0922\u093C\u0947\u0902",
|
|
767
316
|
stopReading: "\u092A\u0922\u093C\u0928\u093E \u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
|
|
768
317
|
answerTable: "\u0924\u093E\u0932\u093F\u0915\u093E",
|
|
318
|
+
productVideo: "\u0909\u0924\u094D\u092A\u093E\u0926 \u0935\u0940\u0921\u093F\u092F\u094B",
|
|
319
|
+
productVideoTitle: "{product} \u0909\u0924\u094D\u092A\u093E\u0926 \u0935\u0940\u0921\u093F\u092F\u094B",
|
|
320
|
+
playVideo: "{title} \u091A\u0932\u093E\u090F\u0901",
|
|
321
|
+
closeVideo: "\u0935\u0940\u0921\u093F\u092F\u094B \u092C\u0902\u0926 \u0915\u0930\u0947\u0902",
|
|
322
|
+
enterFullScreen: "\u092A\u0942\u0930\u094D\u0923 \u0938\u094D\u0915\u094D\u0930\u0940\u0928",
|
|
323
|
+
exitFullScreen: "\u092A\u0942\u0930\u094D\u0923 \u0938\u094D\u0915\u094D\u0930\u0940\u0928 \u0938\u0947 \u092C\u093E\u0939\u0930 \u0928\u093F\u0915\u0932\u0947\u0902",
|
|
324
|
+
videoUnavailable: "\u092F\u0939 \u0935\u0940\u0921\u093F\u092F\u094B \u0905\u092D\u0940 \u0928\u0939\u0940\u0902 \u091A\u0932 \u0938\u0915\u0924\u093E\u0964",
|
|
769
325
|
groundedIn: "\u092F\u0939 \u0909\u0924\u094D\u0924\u0930 \u0915\u093F\u0938 \u092A\u0930 \u0906\u0927\u093E\u0930\u093F\u0924 \u0939\u0948",
|
|
770
326
|
sourceCountOne: "1 \u0938\u094D\u0930\u094B\u0924",
|
|
771
327
|
sourceCountMany: "{count} \u0938\u094D\u0930\u094B\u0924",
|
|
@@ -867,6 +423,13 @@ var ES = {
|
|
|
867
423
|
readAloud: "Leer en voz alta",
|
|
868
424
|
stopReading: "Dejar de leer",
|
|
869
425
|
answerTable: "Tabla",
|
|
426
|
+
productVideo: "V\xEDdeo del producto",
|
|
427
|
+
productVideoTitle: "V\xEDdeo de {product}",
|
|
428
|
+
playVideo: "Reproducir {title}",
|
|
429
|
+
closeVideo: "Cerrar v\xEDdeo",
|
|
430
|
+
enterFullScreen: "Pantalla completa",
|
|
431
|
+
exitFullScreen: "Salir de pantalla completa",
|
|
432
|
+
videoUnavailable: "Ahora mismo no se puede reproducir este v\xEDdeo.",
|
|
870
433
|
groundedIn: "En qu\xE9 se basa esta respuesta",
|
|
871
434
|
sourceCountOne: "1 fuente",
|
|
872
435
|
sourceCountMany: "{count} fuentes",
|
|
@@ -968,6 +531,13 @@ var AR = {
|
|
|
968
531
|
readAloud: "\u0627\u0642\u0631\u0623 \u0628\u0635\u0648\u062A \u0639\u0627\u0644\u064D",
|
|
969
532
|
stopReading: "\u0625\u064A\u0642\u0627\u0641 \u0627\u0644\u0642\u0631\u0627\u0621\u0629",
|
|
970
533
|
answerTable: "\u062C\u062F\u0648\u0644",
|
|
534
|
+
productVideo: "\u0641\u064A\u062F\u064A\u0648 \u0627\u0644\u0645\u0646\u062A\u062C",
|
|
535
|
+
productVideoTitle: "\u0641\u064A\u062F\u064A\u0648 {product}",
|
|
536
|
+
playVideo: "\u062A\u0634\u063A\u064A\u0644 {title}",
|
|
537
|
+
closeVideo: "\u0625\u063A\u0644\u0627\u0642 \u0627\u0644\u0641\u064A\u062F\u064A\u0648",
|
|
538
|
+
enterFullScreen: "\u0645\u0644\u0621 \u0627\u0644\u0634\u0627\u0634\u0629",
|
|
539
|
+
exitFullScreen: "\u0627\u0644\u062E\u0631\u0648\u062C \u0645\u0646 \u0645\u0644\u0621 \u0627\u0644\u0634\u0627\u0634\u0629",
|
|
540
|
+
videoUnavailable: "\u062A\u0639\u0630\u0651\u0631 \u062A\u0634\u063A\u064A\u0644 \u0647\u0630\u0627 \u0627\u0644\u0641\u064A\u062F\u064A\u0648 \u0627\u0644\u0622\u0646.",
|
|
971
541
|
groundedIn: "\u0639\u0644\u0649 \u0645\u0627\u0630\u0627 \u062A\u0633\u062A\u0646\u062F \u0647\u0630\u0647 \u0627\u0644\u0625\u062C\u0627\u0628\u0629",
|
|
972
542
|
sourceCountOne: "\u0645\u0635\u062F\u0631 \u0648\u0627\u062D\u062F",
|
|
973
543
|
sourceCountMany: "{count} \u0645\u0635\u0627\u062F\u0631",
|
|
@@ -1069,6 +639,13 @@ var TA = {
|
|
|
1069
639
|
readAloud: "\u0B9A\u0BA4\u0BCD\u0BA4\u0BAE\u0BBE\u0B95\u0BAA\u0BCD \u0BAA\u0B9F\u0BBF",
|
|
1070
640
|
stopReading: "\u0BAA\u0B9F\u0BBF\u0BAA\u0BCD\u0BAA\u0BA4\u0BC8 \u0BA8\u0BBF\u0BB1\u0BC1\u0BA4\u0BCD\u0BA4\u0BC1",
|
|
1071
641
|
answerTable: "\u0B85\u0B9F\u0BCD\u0B9F\u0BB5\u0BA3\u0BC8",
|
|
642
|
+
productVideo: "\u0BA4\u0BAF\u0BBE\u0BB0\u0BBF\u0BAA\u0BCD\u0BAA\u0BC1 \u0BB5\u0BC0\u0B9F\u0BBF\u0BAF\u0BCB",
|
|
643
|
+
productVideoTitle: "{product} \u0BA4\u0BAF\u0BBE\u0BB0\u0BBF\u0BAA\u0BCD\u0BAA\u0BC1 \u0BB5\u0BC0\u0B9F\u0BBF\u0BAF\u0BCB",
|
|
644
|
+
playVideo: "{title} \u0B87\u0BAF\u0B95\u0BCD\u0B95\u0BC1",
|
|
645
|
+
closeVideo: "\u0BB5\u0BC0\u0B9F\u0BBF\u0BAF\u0BCB\u0BB5\u0BC8 \u0BAE\u0BC2\u0B9F\u0BC1",
|
|
646
|
+
enterFullScreen: "\u0BAE\u0BC1\u0BB4\u0BC1\u0BA4\u0BCD \u0BA4\u0BBF\u0BB0\u0BC8",
|
|
647
|
+
exitFullScreen: "\u0BAE\u0BC1\u0BB4\u0BC1\u0BA4\u0BCD \u0BA4\u0BBF\u0BB0\u0BC8\u0BAF\u0BBF\u0BB2\u0BBF\u0BB0\u0BC1\u0BA8\u0BCD\u0BA4\u0BC1 \u0BB5\u0BC6\u0BB3\u0BBF\u0BAF\u0BC7\u0BB1\u0BC1",
|
|
648
|
+
videoUnavailable: "\u0B87\u0BA8\u0BCD\u0BA4 \u0BB5\u0BC0\u0B9F\u0BBF\u0BAF\u0BCB\u0BB5\u0BC8 \u0B87\u0BAA\u0BCD\u0BAA\u0BCB\u0BA4\u0BC1 \u0B87\u0BAF\u0B95\u0BCD\u0B95 \u0BAE\u0BC1\u0B9F\u0BBF\u0BAF\u0BB5\u0BBF\u0BB2\u0BCD\u0BB2\u0BC8.",
|
|
1072
649
|
groundedIn: "\u0B87\u0BA8\u0BCD\u0BA4\u0BAA\u0BCD \u0BAA\u0BA4\u0BBF\u0BB2\u0BCD \u0B8E\u0BA4\u0BC8 \u0B85\u0B9F\u0BBF\u0BAA\u0BCD\u0BAA\u0B9F\u0BC8\u0BAF\u0BBE\u0B95\u0B95\u0BCD \u0B95\u0BCA\u0BA3\u0BCD\u0B9F\u0BA4\u0BC1",
|
|
1073
650
|
sourceCountOne: "1 \u0BAE\u0BC2\u0BB2\u0BAE\u0BCD",
|
|
1074
651
|
sourceCountMany: "{count} \u0BAE\u0BC2\u0BB2\u0B99\u0BCD\u0B95\u0BB3\u0BCD",
|
|
@@ -1111,25 +688,977 @@ var SPEECH_TAGS = {
|
|
|
1111
688
|
function speechLocaleOf(locale) {
|
|
1112
689
|
return SPEECH_TAGS[locale];
|
|
1113
690
|
}
|
|
1114
|
-
function resolveExploreLocale(explicit) {
|
|
1115
|
-
const candidates = [
|
|
1116
|
-
explicit,
|
|
1117
|
-
typeof document === "undefined" ? void 0 : document.documentElement.lang,
|
|
1118
|
-
typeof navigator === "undefined" ? void 0 : navigator.language
|
|
1119
|
-
];
|
|
1120
|
-
for (const candidate of candidates) {
|
|
1121
|
-
if (!candidate) continue;
|
|
1122
|
-
const primary = candidate.toLowerCase().split("-")[0];
|
|
1123
|
-
const match = EXPLORE_LOCALES.find((locale) => locale === primary);
|
|
1124
|
-
if (match) return match;
|
|
691
|
+
function resolveExploreLocale(explicit) {
|
|
692
|
+
const candidates = [
|
|
693
|
+
explicit,
|
|
694
|
+
typeof document === "undefined" ? void 0 : document.documentElement.lang,
|
|
695
|
+
typeof navigator === "undefined" ? void 0 : navigator.language
|
|
696
|
+
];
|
|
697
|
+
for (const candidate of candidates) {
|
|
698
|
+
if (!candidate) continue;
|
|
699
|
+
const primary = candidate.toLowerCase().split("-")[0];
|
|
700
|
+
const match = EXPLORE_LOCALES.find((locale) => locale === primary);
|
|
701
|
+
if (match) return match;
|
|
702
|
+
}
|
|
703
|
+
return "en";
|
|
704
|
+
}
|
|
705
|
+
function exploreStrings(locale) {
|
|
706
|
+
return STRINGS[locale] ?? EN;
|
|
707
|
+
}
|
|
708
|
+
function fill(template, values) {
|
|
709
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => values[key] ?? match);
|
|
710
|
+
}
|
|
711
|
+
function fullscreenSupport() {
|
|
712
|
+
if (typeof document === "undefined") return null;
|
|
713
|
+
if (document.fullscreenEnabled && typeof document.documentElement.requestFullscreen === "function") {
|
|
714
|
+
return "element";
|
|
715
|
+
}
|
|
716
|
+
if (typeof HTMLVideoElement !== "undefined" && "webkitEnterFullscreen" in HTMLVideoElement.prototype) {
|
|
717
|
+
return "video";
|
|
718
|
+
}
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
721
|
+
function ignoreRejection(result) {
|
|
722
|
+
result?.catch(() => void 0);
|
|
723
|
+
}
|
|
724
|
+
var VIDEO_DIALOG_CSS = `
|
|
725
|
+
.boff-explore-video-panel:fullscreen {
|
|
726
|
+
display: flex;
|
|
727
|
+
flex-direction: column;
|
|
728
|
+
gap: 0.75rem;
|
|
729
|
+
padding: 1rem;
|
|
730
|
+
background-color: hsl(var(--background));
|
|
731
|
+
color: hsl(var(--foreground));
|
|
732
|
+
}
|
|
733
|
+
.boff-explore-video-panel:fullscreen .boff-explore-video-frame {
|
|
734
|
+
display: flex;
|
|
735
|
+
flex: 1 1 auto;
|
|
736
|
+
min-height: 0;
|
|
737
|
+
align-items: center;
|
|
738
|
+
justify-content: center;
|
|
739
|
+
border: 0;
|
|
740
|
+
border-radius: 0;
|
|
741
|
+
box-shadow: none;
|
|
742
|
+
background-color: hsl(var(--background));
|
|
743
|
+
}
|
|
744
|
+
.boff-explore-video-panel:fullscreen video {
|
|
745
|
+
width: 100%;
|
|
746
|
+
height: 100%;
|
|
747
|
+
max-height: none;
|
|
748
|
+
}
|
|
749
|
+
`;
|
|
750
|
+
var DIALOG_BUTTON = "inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-full border border-border bg-card text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
|
|
751
|
+
function ExploreVideoDialog({
|
|
752
|
+
videoUrl,
|
|
753
|
+
posterUrl,
|
|
754
|
+
title,
|
|
755
|
+
strings: T,
|
|
756
|
+
dir,
|
|
757
|
+
onClose
|
|
758
|
+
}) {
|
|
759
|
+
const dialogRef = useRef(null);
|
|
760
|
+
const panelRef = useRef(null);
|
|
761
|
+
const videoRef = useRef(null);
|
|
762
|
+
const closeRef = useRef(null);
|
|
763
|
+
const nativeModalRef = useRef(false);
|
|
764
|
+
const titleId = useId();
|
|
765
|
+
const [support] = useState(fullscreenSupport);
|
|
766
|
+
const [fullscreen, setFullscreen] = useState(false);
|
|
767
|
+
const [failed, setFailed] = useState(false);
|
|
768
|
+
useEffect(() => {
|
|
769
|
+
const dialog = dialogRef.current;
|
|
770
|
+
if (!dialog) return;
|
|
771
|
+
if (typeof dialog.showModal === "function") {
|
|
772
|
+
if (!dialog.open) dialog.showModal();
|
|
773
|
+
nativeModalRef.current = true;
|
|
774
|
+
} else {
|
|
775
|
+
dialog.setAttribute("open", "");
|
|
776
|
+
nativeModalRef.current = false;
|
|
777
|
+
}
|
|
778
|
+
closeRef.current?.focus();
|
|
779
|
+
ignoreRejection(videoRef.current?.play());
|
|
780
|
+
return () => {
|
|
781
|
+
if (dialog.open && typeof dialog.close === "function") dialog.close();
|
|
782
|
+
};
|
|
783
|
+
}, []);
|
|
784
|
+
useEffect(() => {
|
|
785
|
+
const sync = () => {
|
|
786
|
+
const current = document.fullscreenElement;
|
|
787
|
+
setFullscreen(
|
|
788
|
+
current !== null && dialogRef.current?.contains(current) === true
|
|
789
|
+
);
|
|
790
|
+
};
|
|
791
|
+
const enter = () => setFullscreen(true);
|
|
792
|
+
const leave = () => setFullscreen(false);
|
|
793
|
+
const video = videoRef.current;
|
|
794
|
+
document.addEventListener("fullscreenchange", sync);
|
|
795
|
+
video?.addEventListener("webkitbeginfullscreen", enter);
|
|
796
|
+
video?.addEventListener("webkitendfullscreen", leave);
|
|
797
|
+
return () => {
|
|
798
|
+
document.removeEventListener("fullscreenchange", sync);
|
|
799
|
+
video?.removeEventListener("webkitbeginfullscreen", enter);
|
|
800
|
+
video?.removeEventListener("webkitendfullscreen", leave);
|
|
801
|
+
};
|
|
802
|
+
}, [failed]);
|
|
803
|
+
const leaveFullscreen = useCallback(() => {
|
|
804
|
+
const current = document.fullscreenElement;
|
|
805
|
+
if (current && dialogRef.current?.contains(current) && typeof document.exitFullscreen === "function") {
|
|
806
|
+
ignoreRejection(document.exitFullscreen());
|
|
807
|
+
}
|
|
808
|
+
const video = videoRef.current;
|
|
809
|
+
if (video?.webkitDisplayingFullscreen) video.webkitExitFullscreen?.();
|
|
810
|
+
}, []);
|
|
811
|
+
const requestClose = useCallback(() => {
|
|
812
|
+
videoRef.current?.pause();
|
|
813
|
+
leaveFullscreen();
|
|
814
|
+
onClose();
|
|
815
|
+
}, [leaveFullscreen, onClose]);
|
|
816
|
+
const toggleFullscreen = useCallback(() => {
|
|
817
|
+
if (fullscreen) {
|
|
818
|
+
leaveFullscreen();
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (support === "element") {
|
|
822
|
+
const panel = panelRef.current;
|
|
823
|
+
if (panel) ignoreRejection(panel.requestFullscreen());
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
const video = videoRef.current;
|
|
827
|
+
video?.webkitEnterFullscreen?.();
|
|
828
|
+
}, [fullscreen, leaveFullscreen, support]);
|
|
829
|
+
const handleClose = useCallback(
|
|
830
|
+
(event) => {
|
|
831
|
+
if (event.currentTarget.open) return;
|
|
832
|
+
requestClose();
|
|
833
|
+
},
|
|
834
|
+
[requestClose]
|
|
835
|
+
);
|
|
836
|
+
const handleKeyDown = useCallback(
|
|
837
|
+
(event) => {
|
|
838
|
+
if (event.key === "Escape" && !nativeModalRef.current) {
|
|
839
|
+
event.preventDefault();
|
|
840
|
+
requestClose();
|
|
841
|
+
}
|
|
842
|
+
},
|
|
843
|
+
[requestClose]
|
|
844
|
+
);
|
|
845
|
+
const handleStageClick = useCallback(
|
|
846
|
+
(event) => {
|
|
847
|
+
if (event.target === event.currentTarget) requestClose();
|
|
848
|
+
},
|
|
849
|
+
[requestClose]
|
|
850
|
+
);
|
|
851
|
+
if (typeof document === "undefined") return null;
|
|
852
|
+
const fullscreenLabel = fullscreen ? T.exitFullScreen : T.enterFullScreen;
|
|
853
|
+
return createPortal(
|
|
854
|
+
/* @__PURE__ */ jsxs(
|
|
855
|
+
"dialog",
|
|
856
|
+
{
|
|
857
|
+
ref: dialogRef,
|
|
858
|
+
"data-boff-explore": "video-dialog",
|
|
859
|
+
"aria-labelledby": titleId,
|
|
860
|
+
dir,
|
|
861
|
+
onClose: handleClose,
|
|
862
|
+
onKeyDown: handleKeyDown,
|
|
863
|
+
className: "fixed inset-0 z-50 m-0 h-full max-h-none w-full max-w-none overflow-y-auto overscroll-contain border-0 bg-background/95 p-0 text-foreground backdrop-blur-sm",
|
|
864
|
+
children: [
|
|
865
|
+
/* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: VIDEO_DIALOG_CSS } }),
|
|
866
|
+
/* @__PURE__ */ jsx(
|
|
867
|
+
"div",
|
|
868
|
+
{
|
|
869
|
+
onClick: handleStageClick,
|
|
870
|
+
className: "flex min-h-full items-center justify-center p-4 sm:p-6",
|
|
871
|
+
children: /* @__PURE__ */ jsxs(
|
|
872
|
+
"div",
|
|
873
|
+
{
|
|
874
|
+
ref: panelRef,
|
|
875
|
+
className: "boff-explore-video-panel flex w-full min-w-0 max-w-[960px] flex-col gap-3",
|
|
876
|
+
children: [
|
|
877
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
878
|
+
/* @__PURE__ */ jsx(
|
|
879
|
+
"h2",
|
|
880
|
+
{
|
|
881
|
+
id: titleId,
|
|
882
|
+
className: "min-w-0 flex-1 truncate text-sm font-semibold text-foreground sm:text-base",
|
|
883
|
+
children: title
|
|
884
|
+
}
|
|
885
|
+
),
|
|
886
|
+
support !== null && !failed ? (
|
|
887
|
+
// A plain button whose name says what pressing it does NOW, and no
|
|
888
|
+
// `aria-pressed`: a toggle button's name must not change with its state, or it
|
|
889
|
+
// announces "Exit full screen, pressed" — two answers to one question.
|
|
890
|
+
/* @__PURE__ */ jsx(
|
|
891
|
+
"button",
|
|
892
|
+
{
|
|
893
|
+
type: "button",
|
|
894
|
+
"data-boff-explore": "video-fullscreen",
|
|
895
|
+
"aria-label": fullscreenLabel,
|
|
896
|
+
title: fullscreenLabel,
|
|
897
|
+
onClick: toggleFullscreen,
|
|
898
|
+
className: DIALOG_BUTTON,
|
|
899
|
+
children: fullscreen ? /* @__PURE__ */ jsx(Minimize, { "aria-hidden": "true", className: "h-5 w-5" }) : /* @__PURE__ */ jsx(Maximize, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
900
|
+
}
|
|
901
|
+
)
|
|
902
|
+
) : null,
|
|
903
|
+
/* @__PURE__ */ jsx(
|
|
904
|
+
"button",
|
|
905
|
+
{
|
|
906
|
+
ref: closeRef,
|
|
907
|
+
type: "button",
|
|
908
|
+
"data-boff-explore": "video-close",
|
|
909
|
+
"aria-label": T.closeVideo,
|
|
910
|
+
title: T.closeVideo,
|
|
911
|
+
onClick: requestClose,
|
|
912
|
+
className: DIALOG_BUTTON,
|
|
913
|
+
children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "h-5 w-5" })
|
|
914
|
+
}
|
|
915
|
+
)
|
|
916
|
+
] }),
|
|
917
|
+
/* @__PURE__ */ jsx("div", { className: "boff-explore-video-frame overflow-hidden rounded-xl border border-border bg-muted shadow-lg", children: failed ? (
|
|
918
|
+
// A black box with a spinning control tells the visitor nothing; this says
|
|
919
|
+
// what happened, in their language.
|
|
920
|
+
/* @__PURE__ */ jsxs(
|
|
921
|
+
"div",
|
|
922
|
+
{
|
|
923
|
+
role: "alert",
|
|
924
|
+
className: "flex aspect-video w-full flex-col items-center justify-center gap-2 p-6 text-center text-sm text-muted-foreground",
|
|
925
|
+
children: [
|
|
926
|
+
/* @__PURE__ */ jsx(VideoOff, { "aria-hidden": "true", className: "h-6 w-6" }),
|
|
927
|
+
/* @__PURE__ */ jsx("p", { children: T.videoUnavailable })
|
|
928
|
+
]
|
|
929
|
+
}
|
|
930
|
+
)
|
|
931
|
+
) : /* @__PURE__ */ jsx(
|
|
932
|
+
"video",
|
|
933
|
+
{
|
|
934
|
+
ref: videoRef,
|
|
935
|
+
"data-boff-explore": "video-player",
|
|
936
|
+
src: videoUrl,
|
|
937
|
+
poster: posterUrl,
|
|
938
|
+
controls: true,
|
|
939
|
+
controlsList: support === "element" ? "nofullscreen" : void 0,
|
|
940
|
+
playsInline: true,
|
|
941
|
+
autoPlay: true,
|
|
942
|
+
preload: "metadata",
|
|
943
|
+
onError: () => setFailed(true),
|
|
944
|
+
className: "block max-h-[80vh] w-full bg-muted object-contain"
|
|
945
|
+
}
|
|
946
|
+
) })
|
|
947
|
+
]
|
|
948
|
+
}
|
|
949
|
+
)
|
|
950
|
+
}
|
|
951
|
+
)
|
|
952
|
+
]
|
|
953
|
+
}
|
|
954
|
+
),
|
|
955
|
+
document.body
|
|
956
|
+
);
|
|
957
|
+
}
|
|
958
|
+
function MarkdownLink({
|
|
959
|
+
href,
|
|
960
|
+
children,
|
|
961
|
+
// react-markdown passes the hast node to every override; it is not a DOM attribute.
|
|
962
|
+
node: _node,
|
|
963
|
+
...props
|
|
964
|
+
}) {
|
|
965
|
+
const isExternal = typeof href === "string" && (href.startsWith("http://") || href.startsWith("https://"));
|
|
966
|
+
return /* @__PURE__ */ jsx(
|
|
967
|
+
"a",
|
|
968
|
+
{
|
|
969
|
+
href,
|
|
970
|
+
...props,
|
|
971
|
+
...isExternal ? { target: "_blank", rel: "noopener noreferrer" } : { rel: "noopener noreferrer" },
|
|
972
|
+
children
|
|
973
|
+
}
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
var defaultComponents = {
|
|
977
|
+
a: MarkdownLink
|
|
978
|
+
};
|
|
979
|
+
function Markdown({
|
|
980
|
+
children,
|
|
981
|
+
className,
|
|
982
|
+
inline,
|
|
983
|
+
components,
|
|
984
|
+
rehypePlugins
|
|
985
|
+
}) {
|
|
986
|
+
const content = /* @__PURE__ */ jsx(
|
|
987
|
+
ReactMarkdown,
|
|
988
|
+
{
|
|
989
|
+
remarkPlugins: [remarkGfm],
|
|
990
|
+
rehypePlugins: [rehypeSanitize, ...rehypePlugins ?? []],
|
|
991
|
+
components: { ...defaultComponents, ...components },
|
|
992
|
+
...inline ? { disallowedElements: ["p"], unwrapDisallowed: true } : {},
|
|
993
|
+
children
|
|
994
|
+
}
|
|
995
|
+
);
|
|
996
|
+
if (inline) {
|
|
997
|
+
return /* @__PURE__ */ jsx("span", { className, children: content });
|
|
998
|
+
}
|
|
999
|
+
return /* @__PURE__ */ jsx("div", { className, children: content });
|
|
1000
|
+
}
|
|
1001
|
+
function cn(...inputs) {
|
|
1002
|
+
return twMerge(clsx(inputs));
|
|
1003
|
+
}
|
|
1004
|
+
var EXPLORE_ANSWER_CSS = `
|
|
1005
|
+
/* Nothing inside an answer may widen the page. Model output carries long URLs, code and
|
|
1006
|
+
tables \u2014 each of those overflows a phone by default, and one overflowing child makes the
|
|
1007
|
+
WHOLE document horizontally scrollable, not just the bubble. */
|
|
1008
|
+
.boff-explore-body,
|
|
1009
|
+
.boff-explore-body p,
|
|
1010
|
+
.boff-explore-body li,
|
|
1011
|
+
.boff-explore-body a,
|
|
1012
|
+
.boff-explore-body h1,
|
|
1013
|
+
.boff-explore-body h2,
|
|
1014
|
+
.boff-explore-body h3,
|
|
1015
|
+
/* h4-h6 as well: a published answer page pushes the model's own headings down a level or
|
|
1016
|
+
two so they nest under the page's, and a long unbroken word is no narrower for it. */
|
|
1017
|
+
.boff-explore-body h4,
|
|
1018
|
+
.boff-explore-body h5,
|
|
1019
|
+
.boff-explore-body h6 {
|
|
1020
|
+
overflow-wrap: anywhere;
|
|
1021
|
+
word-break: break-word;
|
|
1022
|
+
}
|
|
1023
|
+
.boff-explore-body pre {
|
|
1024
|
+
overflow-x: auto;
|
|
1025
|
+
max-width: 100%;
|
|
1026
|
+
}
|
|
1027
|
+
.boff-explore-body table {
|
|
1028
|
+
display: block;
|
|
1029
|
+
overflow-x: auto;
|
|
1030
|
+
max-width: 100%;
|
|
1031
|
+
}
|
|
1032
|
+
.boff-explore-body img {
|
|
1033
|
+
max-width: 100%;
|
|
1034
|
+
height: auto;
|
|
1035
|
+
}
|
|
1036
|
+
/* A product-video poster fills the card's reserved 16:9 frame and is cropped to it. The
|
|
1037
|
+
blanket \`img { height: auto }\` above \u2014 and ProseStyles' own \u2014 outrank a height utility,
|
|
1038
|
+
and would size it by its own aspect ratio instead, leaving a band of the frame showing
|
|
1039
|
+
whenever a poster is not exactly 16:9. */
|
|
1040
|
+
.boff-explore-body .boff-explore-video-poster {
|
|
1041
|
+
height: 100%;
|
|
1042
|
+
max-width: none;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/* ---- Comparison and pricing tables ----------------------------------------------------
|
|
1046
|
+
The harness emits a comparison, and a set of plans, as a GFM table whose FIRST column is
|
|
1047
|
+
the subject \u2014 the product, or the plan (CONTRACT.md \xA77). Two things have to be true of it:
|
|
1048
|
+
the page must not scroll sideways because of it, and it has to be readable on a 320px
|
|
1049
|
+
phone, which a four-column grid of 1px borders is not.
|
|
1050
|
+
|
|
1051
|
+
Wide: a contained scroller. \`max-width: 100%\` keeps the box inside the bubble and
|
|
1052
|
+
\`overflow-x: auto\` keeps the columns inside the box, so the widest possible table still
|
|
1053
|
+
cannot widen the document. \`overscroll-behavior-x: contain\` stops a swipe that runs out
|
|
1054
|
+
of table from chaining to the page \u2014 the same rule the transcript needed. */
|
|
1055
|
+
.boff-explore-body .boff-explore-table {
|
|
1056
|
+
max-width: 100%;
|
|
1057
|
+
overflow-x: auto;
|
|
1058
|
+
overscroll-behavior-x: contain;
|
|
1059
|
+
margin: 0.75rem 0;
|
|
1060
|
+
border: 1px solid hsl(var(--border));
|
|
1061
|
+
border-radius: 0.75rem;
|
|
1062
|
+
background-color: hsl(var(--card));
|
|
1063
|
+
}
|
|
1064
|
+
.boff-explore-body .boff-explore-table:focus-visible {
|
|
1065
|
+
outline: 2px solid hsl(var(--ring));
|
|
1066
|
+
outline-offset: 2px;
|
|
1067
|
+
}
|
|
1068
|
+
.boff-explore-body .boff-explore-table table {
|
|
1069
|
+
/* Beats the blanket \`display: block\` above, which is the unwrapped fallback. */
|
|
1070
|
+
display: table;
|
|
1071
|
+
width: 100%;
|
|
1072
|
+
max-width: none;
|
|
1073
|
+
margin: 0;
|
|
1074
|
+
border-collapse: collapse;
|
|
1075
|
+
font-size: 0.8125rem;
|
|
1076
|
+
}
|
|
1077
|
+
.boff-explore-body .boff-explore-table th,
|
|
1078
|
+
.boff-explore-body .boff-explore-table td {
|
|
1079
|
+
border: 0;
|
|
1080
|
+
border-top: 1px solid hsl(var(--border));
|
|
1081
|
+
padding: 0.5rem 0.75rem;
|
|
1082
|
+
/* \`start\`, never \`left\`: the page root carries dir="rtl" in Arabic, and a left-aligned
|
|
1083
|
+
table there reads against the grain of every other line on the page. */
|
|
1084
|
+
text-align: start;
|
|
1085
|
+
vertical-align: top;
|
|
1086
|
+
overflow-wrap: anywhere;
|
|
1087
|
+
/* Prices in a column line up under each other instead of drifting by digit width. */
|
|
1088
|
+
font-variant-numeric: tabular-nums;
|
|
1089
|
+
min-width: 6rem;
|
|
1090
|
+
}
|
|
1091
|
+
.boff-explore-body .boff-explore-table thead th {
|
|
1092
|
+
border-top: 0;
|
|
1093
|
+
background-color: hsl(var(--muted));
|
|
1094
|
+
color: hsl(var(--foreground));
|
|
1095
|
+
font-weight: 600;
|
|
1096
|
+
white-space: nowrap;
|
|
1097
|
+
}
|
|
1098
|
+
/* The first column is what the row is ABOUT \u2014 the product, or the plan name. Reading it as
|
|
1099
|
+
the row's heading is what makes a price list scannable rather than a grid of strings. */
|
|
1100
|
+
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
1101
|
+
font-weight: 600;
|
|
1102
|
+
color: hsl(var(--card-foreground));
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/* Narrow: one card per row. Each cell prints its own column heading, so a value is never
|
|
1106
|
+
stranded without the question it answers, and the subject cell becomes the card's title.
|
|
1107
|
+
A media query rather than a measured width \u2014 these pages are serialised post-hydration,
|
|
1108
|
+
so anything measured here would ship to every phone as static HTML. */
|
|
1109
|
+
@media (max-width: 30rem) {
|
|
1110
|
+
.boff-explore-body .boff-explore-table {
|
|
1111
|
+
overflow-x: visible;
|
|
1112
|
+
}
|
|
1113
|
+
.boff-explore-body .boff-explore-table table,
|
|
1114
|
+
.boff-explore-body .boff-explore-table tbody,
|
|
1115
|
+
.boff-explore-body .boff-explore-table tr,
|
|
1116
|
+
.boff-explore-body .boff-explore-table td {
|
|
1117
|
+
display: block;
|
|
1118
|
+
width: 100%;
|
|
1119
|
+
min-width: 0;
|
|
1120
|
+
}
|
|
1121
|
+
.boff-explore-body .boff-explore-table thead {
|
|
1122
|
+
display: none;
|
|
1123
|
+
}
|
|
1124
|
+
.boff-explore-body .boff-explore-table tr {
|
|
1125
|
+
border-top: 1px solid hsl(var(--border));
|
|
1126
|
+
padding: 0.5rem 0;
|
|
1127
|
+
}
|
|
1128
|
+
.boff-explore-body .boff-explore-table tr:first-child {
|
|
1129
|
+
border-top: 0;
|
|
1130
|
+
}
|
|
1131
|
+
.boff-explore-body .boff-explore-table td {
|
|
1132
|
+
border: 0;
|
|
1133
|
+
padding: 0.125rem 0.75rem;
|
|
1134
|
+
}
|
|
1135
|
+
.boff-explore-body .boff-explore-table td[data-label]::before {
|
|
1136
|
+
content: attr(data-label);
|
|
1137
|
+
display: block;
|
|
1138
|
+
font-size: 0.6875rem;
|
|
1139
|
+
font-weight: 600;
|
|
1140
|
+
text-transform: uppercase;
|
|
1141
|
+
letter-spacing: 0.04em;
|
|
1142
|
+
color: hsl(var(--muted-foreground));
|
|
1143
|
+
}
|
|
1144
|
+
.boff-explore-body .boff-explore-table tbody td:first-child {
|
|
1145
|
+
font-size: 0.9375rem;
|
|
1146
|
+
padding-bottom: 0.25rem;
|
|
1147
|
+
}
|
|
1148
|
+
.boff-explore-body .boff-explore-table tbody td:first-child::before {
|
|
1149
|
+
/* The plan's own name is the heading here; repeating "Plan" over it says nothing. */
|
|
1150
|
+
display: none;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
`;
|
|
1154
|
+
function ExploreAnswerStyles() {
|
|
1155
|
+
return /* @__PURE__ */ jsx("style", { dangerouslySetInnerHTML: { __html: EXPLORE_ANSWER_CSS } });
|
|
1156
|
+
}
|
|
1157
|
+
function inlineImageOrigins(products) {
|
|
1158
|
+
const origins = /* @__PURE__ */ new Set();
|
|
1159
|
+
for (const product of products) {
|
|
1160
|
+
let url;
|
|
1161
|
+
try {
|
|
1162
|
+
url = new URL(product.website);
|
|
1163
|
+
} catch {
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
if (url.protocol !== "https:") continue;
|
|
1167
|
+
const apex = url.hostname.replace(/^www\./, "");
|
|
1168
|
+
const port = url.port ? `:${url.port}` : "";
|
|
1169
|
+
origins.add(`https://${apex}${port}`);
|
|
1170
|
+
origins.add(`https://www.${apex}${port}`);
|
|
1171
|
+
}
|
|
1172
|
+
return origins;
|
|
1173
|
+
}
|
|
1174
|
+
var VIDEO_FILE_PATH = /\.(?:mp4|webm|m4v|mov|vtt)$/i;
|
|
1175
|
+
function catalogUrlOf(value, origins) {
|
|
1176
|
+
if (typeof value !== "string" || value === "") return null;
|
|
1177
|
+
let url;
|
|
1178
|
+
try {
|
|
1179
|
+
url = new URL(value);
|
|
1180
|
+
} catch {
|
|
1181
|
+
return null;
|
|
1182
|
+
}
|
|
1183
|
+
if (url.protocol !== "https:" || url.username || url.password) return null;
|
|
1184
|
+
return origins.has(url.origin) ? url : null;
|
|
1185
|
+
}
|
|
1186
|
+
function isInlineImageAllowed(src, origins) {
|
|
1187
|
+
const url = catalogUrlOf(src, origins);
|
|
1188
|
+
return url !== null && !VIDEO_FILE_PATH.test(url.pathname);
|
|
1189
|
+
}
|
|
1190
|
+
var INLINE_VIDEO_PATH = /\.(?:mp4|webm)$/i;
|
|
1191
|
+
function isInlineVideoAllowed(href, origins) {
|
|
1192
|
+
const url = catalogUrlOf(href, origins);
|
|
1193
|
+
return url !== null && INLINE_VIDEO_PATH.test(url.pathname);
|
|
1194
|
+
}
|
|
1195
|
+
function apexOriginOf(url) {
|
|
1196
|
+
const apex = url.hostname.replace(/^www\./, "");
|
|
1197
|
+
const port = url.port ? `:${url.port}` : "";
|
|
1198
|
+
return `https://${apex}${port}`;
|
|
1199
|
+
}
|
|
1200
|
+
function productSlugsByOrigin(products) {
|
|
1201
|
+
const slugs = /* @__PURE__ */ new Map();
|
|
1202
|
+
for (const product of products) {
|
|
1203
|
+
let url;
|
|
1204
|
+
try {
|
|
1205
|
+
url = new URL(product.website);
|
|
1206
|
+
} catch {
|
|
1207
|
+
continue;
|
|
1208
|
+
}
|
|
1209
|
+
if (url.protocol !== "https:") continue;
|
|
1210
|
+
const key = apexOriginOf(url);
|
|
1211
|
+
if (!slugs.has(key)) slugs.set(key, product.slug);
|
|
1212
|
+
}
|
|
1213
|
+
return slugs;
|
|
1214
|
+
}
|
|
1215
|
+
function videoProductOf(href, slugs) {
|
|
1216
|
+
const url = new URL(href);
|
|
1217
|
+
return slugs.get(apexOriginOf(url)) ?? url.hostname.replace(/^www\./, "");
|
|
1218
|
+
}
|
|
1219
|
+
function isVideoFileTarget(href) {
|
|
1220
|
+
if (typeof href !== "string") return false;
|
|
1221
|
+
try {
|
|
1222
|
+
return VIDEO_FILE_PATH.test(
|
|
1223
|
+
new URL(href, "https://answer.invalid").pathname
|
|
1224
|
+
);
|
|
1225
|
+
} catch {
|
|
1226
|
+
return false;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
var CODE_FENCE = /^\s*(?:```|~~~)/;
|
|
1230
|
+
var TABLE_ROW_LINE = /^\s{0,3}\|/;
|
|
1231
|
+
var TABLE_DELIMITER_ROW = /^\s{0,3}\|[\s:|-]*-[\s:|-]*$/;
|
|
1232
|
+
var LINKED_IMAGE = /\[!\[([^[\]]*)\]\([^)\s]*(?:\s+"[^"]*")?\)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
1233
|
+
var IMAGE = /!\[[^[\]]*\]\([^)\s]*(?:\s+"[^"]*")?\)/g;
|
|
1234
|
+
var LINK = /\[([^[\]]+)\]\(([^)\s]+)(?:\s+"[^"]*")?\)/g;
|
|
1235
|
+
function readableLine(raw, mode) {
|
|
1236
|
+
let line = raw;
|
|
1237
|
+
if (line.includes("](")) {
|
|
1238
|
+
line = line.replace(LINKED_IMAGE, (_match, alt, href) => {
|
|
1239
|
+
if (mode === "speech") return "";
|
|
1240
|
+
return alt.trim() === "" ? href : `${alt.trim()} (${href})`;
|
|
1241
|
+
});
|
|
1242
|
+
line = line.replace(IMAGE, "");
|
|
1243
|
+
line = mode === "speech" ? (
|
|
1244
|
+
// A voice reading "h-t-t-p-s colon slash slash…" mid-sentence is noise, and the
|
|
1245
|
+
// prompt asks for two to five links in every answer. The label IS the words.
|
|
1246
|
+
line.replace(LINK, "$1")
|
|
1247
|
+
) : line.replace(LINK, "$1 ($2)");
|
|
1248
|
+
}
|
|
1249
|
+
line = mode === "speech" ? line.replace(/<(https?:\/\/[^>\s]+)>/g, "") : line.replace(/<(https?:\/\/[^>\s]+)>/g, "$1");
|
|
1250
|
+
line = line.replace(/^\s{0,3}#{1,6}\s+/, "");
|
|
1251
|
+
line = line.replace(/^\s{0,3}>\s?/, "");
|
|
1252
|
+
if (/^\s{0,3}([-*_])[ \t]*(?:\1[ \t]*){2,}$/.test(line)) return "";
|
|
1253
|
+
line = line.replace(/`([^`]+)`/g, "$1");
|
|
1254
|
+
line = line.replace(/\*\*([^*]+)\*\*/g, "$1");
|
|
1255
|
+
line = line.replace(/__([^_]+)__/g, "$1");
|
|
1256
|
+
line = line.replace(/(^|[^*\w])\*([^*\n]+)\*(?![*\w])/g, "$1$2");
|
|
1257
|
+
line = line.replace(/(^|[^_\w])_([^_\n]+)_(?![_\w])/g, "$1$2");
|
|
1258
|
+
return line.replace(/[ \t]+$/, "");
|
|
1259
|
+
}
|
|
1260
|
+
function tableCells(row) {
|
|
1261
|
+
const trimmed = row.trim();
|
|
1262
|
+
const cells = [];
|
|
1263
|
+
let cell = "";
|
|
1264
|
+
for (let at = 0; at < trimmed.length; at += 1) {
|
|
1265
|
+
const char = trimmed[at] ?? "";
|
|
1266
|
+
if (char === "\\") {
|
|
1267
|
+
cell += char + (trimmed[at + 1] ?? "");
|
|
1268
|
+
at += 1;
|
|
1269
|
+
continue;
|
|
1270
|
+
}
|
|
1271
|
+
if (char === "|") {
|
|
1272
|
+
cells.push(cell.trim());
|
|
1273
|
+
cell = "";
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
cell += char;
|
|
1277
|
+
}
|
|
1278
|
+
cells.push(cell.trim());
|
|
1279
|
+
if (trimmed.startsWith("|") && cells[0] === "") cells.shift();
|
|
1280
|
+
if (trimmed.endsWith("|") && cells[cells.length - 1] === "") cells.pop();
|
|
1281
|
+
return cells;
|
|
1282
|
+
}
|
|
1283
|
+
function asSentence(text) {
|
|
1284
|
+
return /[.!?]$/.test(text) ? text : `${text}.`;
|
|
1285
|
+
}
|
|
1286
|
+
function spokenTable(rows) {
|
|
1287
|
+
if (rows.length < 2 || !TABLE_DELIMITER_ROW.test(rows[1] ?? "")) return rows;
|
|
1288
|
+
const labels = tableCells(rows[0] ?? "");
|
|
1289
|
+
const spoken = [];
|
|
1290
|
+
for (const row of rows.slice(2)) {
|
|
1291
|
+
const parts = [];
|
|
1292
|
+
tableCells(row).forEach((value, column) => {
|
|
1293
|
+
if (value === "") return;
|
|
1294
|
+
const label = column === 0 ? "" : labels[column] ?? "";
|
|
1295
|
+
parts.push(label === "" ? value : `${label}: ${value}`);
|
|
1296
|
+
});
|
|
1297
|
+
if (parts.length > 0) spoken.push(asSentence(parts.join(". ")));
|
|
1298
|
+
}
|
|
1299
|
+
return spoken;
|
|
1300
|
+
}
|
|
1301
|
+
function readableText(markdown, mode) {
|
|
1302
|
+
const lines = [];
|
|
1303
|
+
let inFence = false;
|
|
1304
|
+
let table = [];
|
|
1305
|
+
const flushTable = () => {
|
|
1306
|
+
if (table.length === 0) return;
|
|
1307
|
+
for (const line of spokenTable(table)) lines.push(readableLine(line, mode));
|
|
1308
|
+
table = [];
|
|
1309
|
+
};
|
|
1310
|
+
for (const raw of markdown.split("\n")) {
|
|
1311
|
+
if (CODE_FENCE.test(raw)) {
|
|
1312
|
+
flushTable();
|
|
1313
|
+
inFence = !inFence;
|
|
1314
|
+
continue;
|
|
1315
|
+
}
|
|
1316
|
+
if (inFence) {
|
|
1317
|
+
lines.push(raw);
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
if (mode === "speech" && TABLE_ROW_LINE.test(raw)) {
|
|
1321
|
+
table.push(raw);
|
|
1322
|
+
continue;
|
|
1323
|
+
}
|
|
1324
|
+
flushTable();
|
|
1325
|
+
lines.push(readableLine(raw, mode));
|
|
1326
|
+
}
|
|
1327
|
+
flushTable();
|
|
1328
|
+
return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
1329
|
+
}
|
|
1330
|
+
function answerPlainText(markdown) {
|
|
1331
|
+
return readableText(markdown, "clipboard");
|
|
1332
|
+
}
|
|
1333
|
+
function answerSpeechText(markdown) {
|
|
1334
|
+
return readableText(markdown, "speech");
|
|
1335
|
+
}
|
|
1336
|
+
var AnswerScopeContext = createContext(null);
|
|
1337
|
+
var InsideLinkContext = createContext(false);
|
|
1338
|
+
function AnswerLink(props) {
|
|
1339
|
+
const scope = useContext(AnswerScopeContext);
|
|
1340
|
+
const role = videoRoleOf(props.node);
|
|
1341
|
+
if (role !== void 0 && scope !== null) {
|
|
1342
|
+
const video = role === "card" && scope.enabled ? productVideoOf(props.node) : null;
|
|
1343
|
+
if (video === null) return null;
|
|
1344
|
+
return /* @__PURE__ */ jsx(
|
|
1345
|
+
InlineVideoCard,
|
|
1346
|
+
{
|
|
1347
|
+
href: video.href,
|
|
1348
|
+
poster: video.poster,
|
|
1349
|
+
alt: video.alt,
|
|
1350
|
+
scope
|
|
1351
|
+
}
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
return /* @__PURE__ */ jsx(InsideLinkContext.Provider, { value: true, children: /* @__PURE__ */ jsx(MarkdownLink, { ...props }) });
|
|
1355
|
+
}
|
|
1356
|
+
var INLINE_IMAGE_FRAME = "relative inline-block max-w-full overflow-hidden rounded-xl border border-border bg-muted align-top shadow-sm";
|
|
1357
|
+
function InlineConceptImage({ src, alt }) {
|
|
1358
|
+
const scope = useContext(AnswerScopeContext);
|
|
1359
|
+
const insideLink = useContext(InsideLinkContext);
|
|
1360
|
+
if (!scope || !scope.enabled || !isInlineImageAllowed(src, scope.origins) || scope.failed.has(src)) {
|
|
1361
|
+
return null;
|
|
1125
1362
|
}
|
|
1126
|
-
|
|
1363
|
+
const { strings: T, onFailed } = scope;
|
|
1364
|
+
const picture = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1365
|
+
/* @__PURE__ */ jsx(
|
|
1366
|
+
"img",
|
|
1367
|
+
{
|
|
1368
|
+
src,
|
|
1369
|
+
alt: alt ?? "",
|
|
1370
|
+
loading: "lazy",
|
|
1371
|
+
decoding: "async",
|
|
1372
|
+
onError: () => onFailed(src),
|
|
1373
|
+
className: "block h-auto max-h-80 w-auto max-w-full object-contain"
|
|
1374
|
+
}
|
|
1375
|
+
),
|
|
1376
|
+
/* @__PURE__ */ jsx(
|
|
1377
|
+
"span",
|
|
1378
|
+
{
|
|
1379
|
+
"aria-hidden": "true",
|
|
1380
|
+
className: "pointer-events-none absolute left-2 top-2 rounded-full border border-border bg-background/90 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-foreground shadow-sm",
|
|
1381
|
+
children: T.conceptIllustration
|
|
1382
|
+
}
|
|
1383
|
+
)
|
|
1384
|
+
] });
|
|
1385
|
+
return /* @__PURE__ */ jsx("span", { "data-boff-explore": "inline-image", className: "my-3 block", children: insideLink ? /* @__PURE__ */ jsx("span", { className: INLINE_IMAGE_FRAME, children: picture }) : /* @__PURE__ */ jsxs(
|
|
1386
|
+
"a",
|
|
1387
|
+
{
|
|
1388
|
+
href: src,
|
|
1389
|
+
target: "_blank",
|
|
1390
|
+
rel: "noopener noreferrer",
|
|
1391
|
+
className: cn(
|
|
1392
|
+
INLINE_IMAGE_FRAME,
|
|
1393
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
1394
|
+
),
|
|
1395
|
+
children: [
|
|
1396
|
+
picture,
|
|
1397
|
+
/* @__PURE__ */ jsxs("span", { className: "sr-only", children: [
|
|
1398
|
+
"(",
|
|
1399
|
+
T.opensInNewTab,
|
|
1400
|
+
")"
|
|
1401
|
+
] })
|
|
1402
|
+
]
|
|
1403
|
+
}
|
|
1404
|
+
) });
|
|
1127
1405
|
}
|
|
1128
|
-
function
|
|
1129
|
-
|
|
1406
|
+
function markdownText(node) {
|
|
1407
|
+
if (!node) return "";
|
|
1408
|
+
if (typeof node.value === "string") return node.value;
|
|
1409
|
+
return (node.children ?? []).map(markdownText).join("");
|
|
1130
1410
|
}
|
|
1131
|
-
function
|
|
1132
|
-
return
|
|
1411
|
+
function hasHeaderCells(node) {
|
|
1412
|
+
return (node?.children ?? []).some((cell) => cell.tagName === "th");
|
|
1413
|
+
}
|
|
1414
|
+
function findHeaderRow(node) {
|
|
1415
|
+
for (const child of node?.children ?? []) {
|
|
1416
|
+
if (child.tagName === "tr" && hasHeaderCells(child)) return child;
|
|
1417
|
+
const nested = findHeaderRow(child);
|
|
1418
|
+
if (nested !== null) return nested;
|
|
1419
|
+
}
|
|
1420
|
+
return null;
|
|
1421
|
+
}
|
|
1422
|
+
function tableColumnLabels(node) {
|
|
1423
|
+
const row = findHeaderRow(node);
|
|
1424
|
+
if (row === null) return [];
|
|
1425
|
+
return (row.children ?? []).filter((cell) => cell.tagName === "th").map((cell) => markdownText(cell).trim());
|
|
1426
|
+
}
|
|
1427
|
+
var TableColumnsContext = createContext([]);
|
|
1428
|
+
function AnswerTable({ node, children }) {
|
|
1429
|
+
const scope = useContext(AnswerScopeContext);
|
|
1430
|
+
const table = /* @__PURE__ */ jsx(TableColumnsContext.Provider, { value: tableColumnLabels(node), children: /* @__PURE__ */ jsx("table", { children }) });
|
|
1431
|
+
if (!scope) return table;
|
|
1432
|
+
return /* @__PURE__ */ jsx(
|
|
1433
|
+
"div",
|
|
1434
|
+
{
|
|
1435
|
+
"data-boff-explore": "table",
|
|
1436
|
+
role: "region",
|
|
1437
|
+
"aria-label": scope.strings.answerTable,
|
|
1438
|
+
tabIndex: 0,
|
|
1439
|
+
className: "boff-explore-table",
|
|
1440
|
+
children: table
|
|
1441
|
+
}
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
function AnswerTableRow({
|
|
1445
|
+
node,
|
|
1446
|
+
children,
|
|
1447
|
+
...props
|
|
1448
|
+
}) {
|
|
1449
|
+
const labels = useContext(TableColumnsContext);
|
|
1450
|
+
if (labels.length === 0 || hasHeaderCells(node)) {
|
|
1451
|
+
return /* @__PURE__ */ jsx("tr", { ...props, children });
|
|
1452
|
+
}
|
|
1453
|
+
let column = 0;
|
|
1454
|
+
const cells = Children.map(children, (cell) => {
|
|
1455
|
+
if (!isValidElement(cell)) return cell;
|
|
1456
|
+
const label = labels[column] ?? "";
|
|
1457
|
+
column += 1;
|
|
1458
|
+
if (label === "") return cell;
|
|
1459
|
+
return cloneElement(cell, {
|
|
1460
|
+
"data-label": label
|
|
1461
|
+
});
|
|
1462
|
+
});
|
|
1463
|
+
return /* @__PURE__ */ jsx("tr", { ...props, children: cells });
|
|
1464
|
+
}
|
|
1465
|
+
function linkedImageOf(node) {
|
|
1466
|
+
if (node?.tagName !== "a") return null;
|
|
1467
|
+
const content = (node.children ?? []).filter(
|
|
1468
|
+
(child) => child.tagName !== void 0 || (child.value ?? "").trim() !== ""
|
|
1469
|
+
);
|
|
1470
|
+
const image = content.length === 1 ? content[0] : void 0;
|
|
1471
|
+
if (image?.tagName !== "img") return null;
|
|
1472
|
+
const alt = image.properties?.alt;
|
|
1473
|
+
return {
|
|
1474
|
+
href: node.properties?.href,
|
|
1475
|
+
src: image.properties?.src,
|
|
1476
|
+
alt: typeof alt === "string" ? alt.trim() : ""
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
function isCardableVideo(embed, origins) {
|
|
1480
|
+
if (!isInlineVideoAllowed(embed.href, origins) || !isInlineImageAllowed(embed.src, origins)) {
|
|
1481
|
+
return false;
|
|
1482
|
+
}
|
|
1483
|
+
return apexOriginOf(new URL(embed.href)) === apexOriginOf(new URL(embed.src));
|
|
1484
|
+
}
|
|
1485
|
+
var NO_VIDEO_CONTAINERS = /* @__PURE__ */ new Set(["li", "table"]);
|
|
1486
|
+
function rehypeProductVideo(options) {
|
|
1487
|
+
return (tree) => {
|
|
1488
|
+
let claimed = false;
|
|
1489
|
+
const visit = (node, parent, contained) => {
|
|
1490
|
+
const embed = linkedImageOf(node);
|
|
1491
|
+
if (embed !== null && isVideoFileTarget(embed.href)) {
|
|
1492
|
+
const card = !claimed && !contained && parent?.tagName === "p" && isCardableVideo(embed, options.origins);
|
|
1493
|
+
if (card) claimed = true;
|
|
1494
|
+
const data = typeof node.data === "object" ? node.data : null;
|
|
1495
|
+
node.data = { ...data, boffVideo: card ? "card" : "drop" };
|
|
1496
|
+
return;
|
|
1497
|
+
}
|
|
1498
|
+
const inside = contained || node.tagName !== void 0 && NO_VIDEO_CONTAINERS.has(node.tagName);
|
|
1499
|
+
for (const child of node.children ?? []) visit(child, node, inside);
|
|
1500
|
+
};
|
|
1501
|
+
visit(tree, null, false);
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
function videoRoleOf(node) {
|
|
1505
|
+
const data = node?.data;
|
|
1506
|
+
if (typeof data !== "object" || data === null || !("boffVideo" in data)) {
|
|
1507
|
+
return void 0;
|
|
1508
|
+
}
|
|
1509
|
+
return data.boffVideo === "card" || data.boffVideo === "drop" ? data.boffVideo : void 0;
|
|
1510
|
+
}
|
|
1511
|
+
function productVideoOf(node) {
|
|
1512
|
+
const embed = linkedImageOf(node);
|
|
1513
|
+
if (embed === null || typeof embed.href !== "string" || typeof embed.src !== "string") {
|
|
1514
|
+
return null;
|
|
1515
|
+
}
|
|
1516
|
+
return { href: embed.href, poster: embed.src, alt: embed.alt };
|
|
1517
|
+
}
|
|
1518
|
+
var CANONICAL_VIDEO_ALT = /^(.+?)\s+product video$/i;
|
|
1519
|
+
function videoTitleOf(alt, T) {
|
|
1520
|
+
const product = CANONICAL_VIDEO_ALT.exec(alt)?.[1]?.trim();
|
|
1521
|
+
if (product) return fill(T.productVideoTitle, { product });
|
|
1522
|
+
return alt === "" ? T.productVideo : alt;
|
|
1523
|
+
}
|
|
1524
|
+
var INLINE_VIDEO_FRAME = "group relative block aspect-video w-full max-w-md overflow-hidden rounded-xl border border-border bg-muted shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring";
|
|
1525
|
+
function InlineVideoCard({
|
|
1526
|
+
href,
|
|
1527
|
+
poster,
|
|
1528
|
+
alt,
|
|
1529
|
+
scope
|
|
1530
|
+
}) {
|
|
1531
|
+
const { strings: T, failed, onFailed, productSlugs } = scope;
|
|
1532
|
+
const [dialogDir, setDialogDir] = useState(null);
|
|
1533
|
+
const buttonRef = useRef(null);
|
|
1534
|
+
const returnFocusRef = useRef(false);
|
|
1535
|
+
const open = dialogDir !== null;
|
|
1536
|
+
useEffect(() => {
|
|
1537
|
+
if (open || !returnFocusRef.current) return;
|
|
1538
|
+
returnFocusRef.current = false;
|
|
1539
|
+
const button = buttonRef.current;
|
|
1540
|
+
if (button?.isConnected) button.focus();
|
|
1541
|
+
}, [open]);
|
|
1542
|
+
const openPlayer = useCallback((event) => {
|
|
1543
|
+
const dir = event.currentTarget.closest("[dir]")?.getAttribute("dir");
|
|
1544
|
+
returnFocusRef.current = true;
|
|
1545
|
+
setDialogDir(dir === "rtl" ? "rtl" : "ltr");
|
|
1546
|
+
}, []);
|
|
1547
|
+
const closePlayer = useCallback(() => setDialogDir(null), []);
|
|
1548
|
+
const name = videoTitleOf(alt, T);
|
|
1549
|
+
const posterFailed = failed.has(poster);
|
|
1550
|
+
return /* @__PURE__ */ jsxs(
|
|
1551
|
+
"span",
|
|
1552
|
+
{
|
|
1553
|
+
"data-boff-explore": "inline-video",
|
|
1554
|
+
"data-product": videoProductOf(href, productSlugs),
|
|
1555
|
+
className: "my-3 block",
|
|
1556
|
+
children: [
|
|
1557
|
+
/* @__PURE__ */ jsxs(
|
|
1558
|
+
"button",
|
|
1559
|
+
{
|
|
1560
|
+
ref: buttonRef,
|
|
1561
|
+
type: "button",
|
|
1562
|
+
"aria-haspopup": "dialog",
|
|
1563
|
+
"aria-label": fill(T.playVideo, { title: name }),
|
|
1564
|
+
onClick: openPlayer,
|
|
1565
|
+
className: INLINE_VIDEO_FRAME,
|
|
1566
|
+
children: [
|
|
1567
|
+
posterFailed ? /* @__PURE__ */ jsx(
|
|
1568
|
+
"span",
|
|
1569
|
+
{
|
|
1570
|
+
"aria-hidden": "true",
|
|
1571
|
+
className: "absolute inset-0 flex items-center justify-center bg-muted text-muted-foreground",
|
|
1572
|
+
children: /* @__PURE__ */ jsx(Film, { className: "h-10 w-10" })
|
|
1573
|
+
}
|
|
1574
|
+
) : /* @__PURE__ */ jsx(
|
|
1575
|
+
"img",
|
|
1576
|
+
{
|
|
1577
|
+
src: poster,
|
|
1578
|
+
alt: "",
|
|
1579
|
+
loading: "lazy",
|
|
1580
|
+
decoding: "async",
|
|
1581
|
+
onError: () => onFailed(poster),
|
|
1582
|
+
className: "boff-explore-video-poster absolute inset-0 h-full w-full object-cover"
|
|
1583
|
+
}
|
|
1584
|
+
),
|
|
1585
|
+
/* @__PURE__ */ jsx(
|
|
1586
|
+
"span",
|
|
1587
|
+
{
|
|
1588
|
+
"aria-hidden": "true",
|
|
1589
|
+
className: "pointer-events-none absolute inset-0 flex items-center justify-center",
|
|
1590
|
+
children: /* @__PURE__ */ jsx("span", { className: "flex h-14 w-14 items-center justify-center rounded-full border border-border bg-background/90 text-foreground shadow-md transition-transform group-hover:scale-105", children: /* @__PURE__ */ jsx(Play, { className: "h-6 w-6 fill-current" }) })
|
|
1591
|
+
}
|
|
1592
|
+
),
|
|
1593
|
+
/* @__PURE__ */ jsx(
|
|
1594
|
+
"span",
|
|
1595
|
+
{
|
|
1596
|
+
"aria-hidden": "true",
|
|
1597
|
+
className: "pointer-events-none absolute start-2 top-2 rounded-full border border-border bg-background/90 px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide text-foreground shadow-sm",
|
|
1598
|
+
children: T.productVideo
|
|
1599
|
+
}
|
|
1600
|
+
)
|
|
1601
|
+
]
|
|
1602
|
+
}
|
|
1603
|
+
),
|
|
1604
|
+
open ? /* @__PURE__ */ jsx(
|
|
1605
|
+
ExploreVideoDialog,
|
|
1606
|
+
{
|
|
1607
|
+
videoUrl: href,
|
|
1608
|
+
posterUrl: poster,
|
|
1609
|
+
title: name,
|
|
1610
|
+
strings: T,
|
|
1611
|
+
dir: dialogDir,
|
|
1612
|
+
onClose: closePlayer
|
|
1613
|
+
}
|
|
1614
|
+
) : null
|
|
1615
|
+
]
|
|
1616
|
+
}
|
|
1617
|
+
);
|
|
1618
|
+
}
|
|
1619
|
+
var ANSWER_MARKDOWN_COMPONENTS = {
|
|
1620
|
+
a: AnswerLink,
|
|
1621
|
+
img: InlineConceptImage,
|
|
1622
|
+
table: AnswerTable,
|
|
1623
|
+
tr: AnswerTableRow
|
|
1624
|
+
};
|
|
1625
|
+
var NO_PRODUCT_SLUGS = /* @__PURE__ */ new Map();
|
|
1626
|
+
function ExploreAnswerBody({
|
|
1627
|
+
children,
|
|
1628
|
+
strings,
|
|
1629
|
+
imageOrigins,
|
|
1630
|
+
videoOrigins = imageOrigins,
|
|
1631
|
+
productSlugs = NO_PRODUCT_SLUGS,
|
|
1632
|
+
enabled = true,
|
|
1633
|
+
components,
|
|
1634
|
+
className
|
|
1635
|
+
}) {
|
|
1636
|
+
const [failed, setFailed] = useState(() => /* @__PURE__ */ new Set());
|
|
1637
|
+
const onFailed = useCallback((src) => {
|
|
1638
|
+
setFailed(
|
|
1639
|
+
(current) => current.has(src) ? current : new Set(current).add(src)
|
|
1640
|
+
);
|
|
1641
|
+
}, []);
|
|
1642
|
+
const scope = useMemo(
|
|
1643
|
+
() => ({
|
|
1644
|
+
strings,
|
|
1645
|
+
enabled,
|
|
1646
|
+
origins: imageOrigins,
|
|
1647
|
+
productSlugs,
|
|
1648
|
+
failed,
|
|
1649
|
+
onFailed
|
|
1650
|
+
}),
|
|
1651
|
+
[strings, enabled, imageOrigins, productSlugs, failed, onFailed]
|
|
1652
|
+
);
|
|
1653
|
+
const rehypePlugins = useMemo(
|
|
1654
|
+
() => [[rehypeProductVideo, { origins: videoOrigins }]],
|
|
1655
|
+
[videoOrigins]
|
|
1656
|
+
);
|
|
1657
|
+
const overrides = useMemo(
|
|
1658
|
+
() => components ? { ...ANSWER_MARKDOWN_COMPONENTS, ...components } : ANSWER_MARKDOWN_COMPONENTS,
|
|
1659
|
+
[components]
|
|
1660
|
+
);
|
|
1661
|
+
return /* @__PURE__ */ jsx("div", { className: cn("boff-prose boff-explore-body min-w-0", className), children: /* @__PURE__ */ jsx(AnswerScopeContext.Provider, { value: scope, children: /* @__PURE__ */ jsx(Markdown, { components: overrides, rehypePlugins, children }) }) });
|
|
1133
1662
|
}
|
|
1134
1663
|
|
|
1135
1664
|
// src/components/explore-types.ts
|
|
@@ -1742,6 +2271,35 @@ function useWebSDKConfig() {
|
|
|
1742
2271
|
return client.getConfig();
|
|
1743
2272
|
}
|
|
1744
2273
|
|
|
2274
|
+
// src/utils/product-slug.ts
|
|
2275
|
+
var PRODUCT_SLUG_RENAMES = Object.freeze(
|
|
2276
|
+
[Object.freeze({ legacy: "botlit", current: "botmanor" })]
|
|
2277
|
+
);
|
|
2278
|
+
Object.freeze(
|
|
2279
|
+
Object.fromEntries(PRODUCT_SLUG_RENAMES.map((r) => [r.legacy, r.current]))
|
|
2280
|
+
);
|
|
2281
|
+
var PRODUCT_SLUG_EMIT = Object.freeze({ productSlug: "legacy" });
|
|
2282
|
+
function findRename(slug) {
|
|
2283
|
+
const wanted = slug.toLowerCase();
|
|
2284
|
+
return PRODUCT_SLUG_RENAMES.find(
|
|
2285
|
+
(r) => r.legacy === wanted || r.current === wanted
|
|
2286
|
+
);
|
|
2287
|
+
}
|
|
2288
|
+
function normalizeProductSlug(slug) {
|
|
2289
|
+
if (slug == null) return slug;
|
|
2290
|
+
return findRename(slug)?.current ?? slug;
|
|
2291
|
+
}
|
|
2292
|
+
function legacyProductSlug(slug) {
|
|
2293
|
+
return findRename(slug)?.legacy ?? slug;
|
|
2294
|
+
}
|
|
2295
|
+
function isSameProductSlug(a, b) {
|
|
2296
|
+
if (a == null || b == null) return a === b;
|
|
2297
|
+
return a === b || normalizeProductSlug(a) === normalizeProductSlug(b);
|
|
2298
|
+
}
|
|
2299
|
+
function emitProductSlug(slug) {
|
|
2300
|
+
return PRODUCT_SLUG_EMIT.productSlug === "current" ? normalizeProductSlug(slug) : legacyProductSlug(slug);
|
|
2301
|
+
}
|
|
2302
|
+
|
|
1745
2303
|
// src/data/products.ts
|
|
1746
2304
|
var ECOSYSTEM_PRODUCTS = [
|
|
1747
2305
|
{
|
|
@@ -1764,7 +2322,12 @@ var ECOSYSTEM_PRODUCTS = [
|
|
|
1764
2322
|
},
|
|
1765
2323
|
// BotManor ships on www.botmanor.com (the apex redirects there; the old botlit.ai domain
|
|
1766
2324
|
// still serves the same site but is no longer the name we link to).
|
|
1767
|
-
|
|
2325
|
+
// The slug follows the botlit → botmanor rename switch (utils/product-slug.ts).
|
|
2326
|
+
{
|
|
2327
|
+
slug: emitProductSlug("botmanor"),
|
|
2328
|
+
name: "BotManor",
|
|
2329
|
+
websiteUrl: "https://www.botmanor.com"
|
|
2330
|
+
},
|
|
1768
2331
|
{
|
|
1769
2332
|
slug: "bigconsole",
|
|
1770
2333
|
name: "BigConsole",
|
|
@@ -3038,7 +3601,7 @@ function isHighSurrogate(code) {
|
|
|
3038
3601
|
}
|
|
3039
3602
|
function safeInlineEnd(line) {
|
|
3040
3603
|
let codeSpan = null;
|
|
3041
|
-
|
|
3604
|
+
const links = [];
|
|
3042
3605
|
const emphasis = [];
|
|
3043
3606
|
let at = 0;
|
|
3044
3607
|
while (at < line.length) {
|
|
@@ -3047,6 +3610,13 @@ function safeInlineEnd(line) {
|
|
|
3047
3610
|
at += 2;
|
|
3048
3611
|
continue;
|
|
3049
3612
|
}
|
|
3613
|
+
const innermost = links[links.length - 1];
|
|
3614
|
+
if (innermost !== void 0 && innermost.kind !== "label") {
|
|
3615
|
+
if (char === "(") links.push({ kind: "paren", start: innermost.start });
|
|
3616
|
+
else if (char === ")") links.pop();
|
|
3617
|
+
at += 1;
|
|
3618
|
+
continue;
|
|
3619
|
+
}
|
|
3050
3620
|
if (char === "`") {
|
|
3051
3621
|
let run = 1;
|
|
3052
3622
|
while (line[at + run] === "`") run += 1;
|
|
@@ -3059,17 +3629,17 @@ function safeInlineEnd(line) {
|
|
|
3059
3629
|
continue;
|
|
3060
3630
|
}
|
|
3061
3631
|
if (char === "[") {
|
|
3062
|
-
|
|
3063
|
-
at += 1;
|
|
3064
|
-
continue;
|
|
3065
|
-
}
|
|
3066
|
-
if (char === "]") {
|
|
3067
|
-
if (link !== null && line[at + 1] !== "(") link = null;
|
|
3632
|
+
links.push({ kind: "label", start: line[at - 1] === "!" ? at - 1 : at });
|
|
3068
3633
|
at += 1;
|
|
3069
3634
|
continue;
|
|
3070
3635
|
}
|
|
3071
|
-
if (char === "
|
|
3072
|
-
|
|
3636
|
+
if (char === "]" && innermost !== void 0) {
|
|
3637
|
+
links.pop();
|
|
3638
|
+
if (line[at + 1] === "(") {
|
|
3639
|
+
links.push({ kind: "destination", start: innermost.start });
|
|
3640
|
+
at += 2;
|
|
3641
|
+
continue;
|
|
3642
|
+
}
|
|
3073
3643
|
at += 1;
|
|
3074
3644
|
continue;
|
|
3075
3645
|
}
|
|
@@ -3096,9 +3666,11 @@ function safeInlineEnd(line) {
|
|
|
3096
3666
|
}
|
|
3097
3667
|
at += 1;
|
|
3098
3668
|
}
|
|
3099
|
-
const open = [
|
|
3100
|
-
|
|
3101
|
-
|
|
3669
|
+
const open = [
|
|
3670
|
+
codeSpan,
|
|
3671
|
+
links[0]?.start ?? null,
|
|
3672
|
+
emphasis[0]?.start ?? null
|
|
3673
|
+
].filter((value) => value !== null);
|
|
3102
3674
|
return open.length === 0 ? line.length : Math.min(...open);
|
|
3103
3675
|
}
|
|
3104
3676
|
function revealBoundary(text, limit) {
|
|
@@ -5085,6 +5657,7 @@ function AssistantBubble({
|
|
|
5085
5657
|
message,
|
|
5086
5658
|
activity,
|
|
5087
5659
|
imageOrigins,
|
|
5660
|
+
productSlugs,
|
|
5088
5661
|
productNames,
|
|
5089
5662
|
speechLang,
|
|
5090
5663
|
onNavigate,
|
|
@@ -5199,6 +5772,7 @@ function AssistantBubble({
|
|
|
5199
5772
|
{
|
|
5200
5773
|
strings: T,
|
|
5201
5774
|
imageOrigins,
|
|
5775
|
+
productSlugs,
|
|
5202
5776
|
enabled: completed,
|
|
5203
5777
|
className: "boff-prose-sm text-card-foreground",
|
|
5204
5778
|
children: visible
|
|
@@ -5469,21 +6043,24 @@ function ProductStrip({
|
|
|
5469
6043
|
children: T.allProducts
|
|
5470
6044
|
}
|
|
5471
6045
|
),
|
|
5472
|
-
products.map((product) =>
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
"
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
|
|
5481
|
-
|
|
5482
|
-
|
|
5483
|
-
|
|
5484
|
-
|
|
5485
|
-
|
|
5486
|
-
|
|
6046
|
+
products.map((product) => {
|
|
6047
|
+
const focused = isSameProductSlug(focusProduct, product.slug);
|
|
6048
|
+
return /* @__PURE__ */ jsx(
|
|
6049
|
+
"button",
|
|
6050
|
+
{
|
|
6051
|
+
"data-boff-explore": "chip",
|
|
6052
|
+
"data-chip-kind": "product",
|
|
6053
|
+
"data-product": product.slug,
|
|
6054
|
+
type: "button",
|
|
6055
|
+
"aria-pressed": focused,
|
|
6056
|
+
title: product.tagline ?? product.name,
|
|
6057
|
+
onClick: () => onFocus(focused ? null : product.slug),
|
|
6058
|
+
className: chipClass(focused),
|
|
6059
|
+
children: product.name
|
|
6060
|
+
},
|
|
6061
|
+
product.slug
|
|
6062
|
+
);
|
|
6063
|
+
})
|
|
5487
6064
|
]
|
|
5488
6065
|
}
|
|
5489
6066
|
),
|
|
@@ -5941,11 +6518,11 @@ function ExplorePage({
|
|
|
5941
6518
|
const resolvedBody = welcomeBody ?? catalog.welcomeBody;
|
|
5942
6519
|
const prompts = catalog.examplePrompts;
|
|
5943
6520
|
const focusedProductName = useMemo(
|
|
5944
|
-
() => catalog.products.find((p) => p.slug
|
|
6521
|
+
() => catalog.products.find((p) => isSameProductSlug(p.slug, focusProduct))?.name ?? null,
|
|
5945
6522
|
[catalog.products, focusProduct]
|
|
5946
6523
|
);
|
|
5947
6524
|
const headProductName = useMemo(
|
|
5948
|
-
() => productSlug ? catalog.products.find((p) => p.slug
|
|
6525
|
+
() => productSlug ? catalog.products.find((p) => isSameProductSlug(p.slug, productSlug))?.name ?? null : null,
|
|
5949
6526
|
[catalog.products, productSlug]
|
|
5950
6527
|
);
|
|
5951
6528
|
const imageOrigins = useMemo(
|
|
@@ -5956,6 +6533,10 @@ function ExplorePage({
|
|
|
5956
6533
|
() => productNamesByOrigin(catalog.products),
|
|
5957
6534
|
[catalog.products]
|
|
5958
6535
|
);
|
|
6536
|
+
const productSlugs = useMemo(
|
|
6537
|
+
() => productSlugsByOrigin(catalog.products),
|
|
6538
|
+
[catalog.products]
|
|
6539
|
+
);
|
|
5959
6540
|
useEffect(() => {
|
|
5960
6541
|
const el = textareaRef.current;
|
|
5961
6542
|
if (!el) return;
|
|
@@ -6518,6 +7099,7 @@ function ExplorePage({
|
|
|
6518
7099
|
message,
|
|
6519
7100
|
activity,
|
|
6520
7101
|
imageOrigins,
|
|
7102
|
+
productSlugs,
|
|
6521
7103
|
productNames,
|
|
6522
7104
|
speechLang,
|
|
6523
7105
|
onNavigate,
|
|
@@ -6841,6 +7423,6 @@ function ExplorePage({
|
|
|
6841
7423
|
);
|
|
6842
7424
|
}
|
|
6843
7425
|
|
|
6844
|
-
export { EXPLORE_PREFILL_MAX_LENGTH, ExplorePage, answerPlainText, answerSpeechText, exploreQuestionHref, inlineImageOrigins, isInlineImageAllowed, logoUrlOf, productNamesByOrigin, referenceKindOf, summariseReferences };
|
|
7426
|
+
export { EXPLORE_PREFILL_MAX_LENGTH, ExplorePage, answerPlainText, answerSpeechText, exploreQuestionHref, inlineImageOrigins, isInlineImageAllowed, isInlineVideoAllowed, logoUrlOf, productNamesByOrigin, referenceKindOf, summariseReferences };
|
|
6845
7427
|
//# sourceMappingURL=explore.mjs.map
|
|
6846
7428
|
//# sourceMappingURL=explore.mjs.map
|