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