@agent-native/core 0.84.22 → 0.84.24
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/model-config.ts +1 -1
- package/corpus/core/src/cli/skills.ts +17 -3
- package/corpus/core/src/client/AssistantChat.tsx +45 -12
- package/corpus/core/src/client/active-run-state.ts +22 -0
- package/corpus/core/src/client/chat/message-components.tsx +2 -1
- package/corpus/core/src/client/chat/tool-call-display.tsx +18 -2
- package/corpus/core/src/client/sse-event-processor.ts +157 -40
- package/corpus/core/src/notifications/index.ts +1 -0
- package/corpus/core/src/notifications/registry.ts +13 -1
- package/corpus/core/src/templates/default/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +15 -13
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +18 -1
- package/corpus/templates/analytics/server/db/schema.ts +1 -1
- package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +4 -1
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +76 -7
- package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +30 -1
- package/corpus/templates/clips/actions/finalize-recording.ts +84 -4
- package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +256 -0
- package/corpus/templates/clips/actions/reprocess-recording.ts +160 -0
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +2 -2
- package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
- package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
- package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
- package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
- package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
- package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
- package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
- package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
- package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
- package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +31 -3
- package/corpus/templates/clips/changelog/2026-07-01-clip-embeds-now-render-as-a-clean-video-only-player-without-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-01-shared-clips-now-start-playing-and-scrub-instantly-instead-o.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/github-preview-content.ts +2 -2
- package/corpus/templates/clips/chrome-extension/src/github-preview.ts +41 -31
- package/corpus/templates/clips/server/lib/video-remux.ts +222 -0
- package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +75 -19
- package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +11 -1
- package/corpus/templates/design/changelog/2026-07-01-design-chat-no-longer-shows-an-empty-state-selection-chip-in-the-composer.md +6 -0
- package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +2 -0
- package/corpus/templates/videos/.agents/skills/frontend-design/SKILL.md +2 -0
- package/dist/agent/model-config.js +1 -1
- package/dist/agent/model-config.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +17 -3
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +2 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +37 -15
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/active-run-state.d.ts +2 -0
- package/dist/client/active-run-state.d.ts.map +1 -1
- package/dist/client/active-run-state.js +17 -0
- package/dist/client/active-run-state.js.map +1 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +3 -1
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +3 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +8 -8
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +1 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +136 -35
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/notifications/index.d.ts +1 -1
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +1 -1
- package/dist/notifications/index.js.map +1 -1
- package/dist/notifications/registry.d.ts +5 -0
- package/dist/notifications/registry.d.ts.map +1 -1
- package/dist/notifications/registry.js +4 -1
- package/dist/notifications/registry.js.map +1 -1
- package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +2 -0
- package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +2 -0
- package/package.json +1 -1
- package/src/templates/default/.agents/skills/frontend-design/SKILL.md +2 -0
- package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +2 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repair the playback of already-stored recordings.
|
|
3
|
+
*
|
|
4
|
+
* Clips uploaded via the streaming path (or before the seekable rewrite
|
|
5
|
+
* existed) can ship raw MediaRecorder output: an MP4 with a trailing `moov`
|
|
6
|
+
* atom or a WebM with no Cues index. Those load slowly and re-buffer on every
|
|
7
|
+
* seek even though the file downloads fine. This action re-fetches the stored
|
|
8
|
+
* media, rewrites it to be start-playable and seekable (MP4 faststart / WebM
|
|
9
|
+
* Cues remux), re-uploads the fixed file, and repoints the recording at it.
|
|
10
|
+
*
|
|
11
|
+
* It's idempotent and non-destructive: clips that are already seekable are
|
|
12
|
+
* skipped, and any clip we can't improve or re-upload is left untouched.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* pnpm action reprocess-recording --id=<recordingId>
|
|
16
|
+
* pnpm action reprocess-recording --ids='["id1","id2"]'
|
|
17
|
+
* pnpm action reprocess-recording --all --limit=20
|
|
18
|
+
* pnpm action reprocess-recording --id=<recordingId> --force
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { defineAction } from "@agent-native/core";
|
|
22
|
+
import { and, desc, eq, isNotNull } from "drizzle-orm";
|
|
23
|
+
import { z } from "zod";
|
|
24
|
+
|
|
25
|
+
import { getDb, schema } from "../server/db/index.js";
|
|
26
|
+
import {
|
|
27
|
+
getCurrentOwnerEmail,
|
|
28
|
+
ownerEmailMatches,
|
|
29
|
+
} from "../server/lib/recordings.js";
|
|
30
|
+
import {
|
|
31
|
+
ensureRecordingSeekable,
|
|
32
|
+
type EnsureSeekableResult,
|
|
33
|
+
} from "./lib/ensure-seekable-video.js";
|
|
34
|
+
|
|
35
|
+
const MAX_TARGETS_PER_CALL = 100;
|
|
36
|
+
const DEFAULT_ALL_LIMIT = 20;
|
|
37
|
+
|
|
38
|
+
function parseIds(value: unknown): string[] {
|
|
39
|
+
if (Array.isArray(value)) {
|
|
40
|
+
return value.filter((v): v is string => typeof v === "string" && !!v);
|
|
41
|
+
}
|
|
42
|
+
if (typeof value === "string" && value.trim()) {
|
|
43
|
+
try {
|
|
44
|
+
const parsed = JSON.parse(value);
|
|
45
|
+
if (Array.isArray(parsed)) {
|
|
46
|
+
return parsed.filter((v): v is string => typeof v === "string" && !!v);
|
|
47
|
+
}
|
|
48
|
+
} catch {
|
|
49
|
+
// Fall back to a single comma-separated / plain id string.
|
|
50
|
+
return value
|
|
51
|
+
.split(",")
|
|
52
|
+
.map((s) => s.trim())
|
|
53
|
+
.filter(Boolean);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export default defineAction({
|
|
60
|
+
description:
|
|
61
|
+
"Repair slow-loading or buffering recordings by rewriting their stored video to be start-playable and seekable (MP4 faststart / WebM Cues remux) and re-uploading it. Use for clips that take a long time to load or re-buffer when scrubbing. Pass `id` for one clip, `ids` for several, or `all: true` to sweep the caller's ready clips (bounded by `limit`). Already-seekable clips are skipped unless `force` is true.",
|
|
62
|
+
schema: z.object({
|
|
63
|
+
id: z.string().optional().describe("A single recording id to repair."),
|
|
64
|
+
ids: z
|
|
65
|
+
.union([z.array(z.string()), z.string()])
|
|
66
|
+
.optional()
|
|
67
|
+
.describe(
|
|
68
|
+
"Multiple recording ids (array, or JSON/comma string for CLI).",
|
|
69
|
+
),
|
|
70
|
+
all: z.coerce
|
|
71
|
+
.boolean()
|
|
72
|
+
.optional()
|
|
73
|
+
.describe(
|
|
74
|
+
"Repair the caller's own ready clips, most recent first, up to `limit`.",
|
|
75
|
+
),
|
|
76
|
+
limit: z.coerce
|
|
77
|
+
.number()
|
|
78
|
+
.int()
|
|
79
|
+
.min(1)
|
|
80
|
+
.max(MAX_TARGETS_PER_CALL)
|
|
81
|
+
.optional()
|
|
82
|
+
.describe(
|
|
83
|
+
`Max clips to process when using \`all\` (default ${DEFAULT_ALL_LIMIT}).`,
|
|
84
|
+
),
|
|
85
|
+
force: z.coerce
|
|
86
|
+
.boolean()
|
|
87
|
+
.optional()
|
|
88
|
+
.describe("Re-run even on clips already marked seekable."),
|
|
89
|
+
}),
|
|
90
|
+
run: async (args) => {
|
|
91
|
+
const db = getDb();
|
|
92
|
+
const ownerEmail = getCurrentOwnerEmail();
|
|
93
|
+
const force = Boolean(args.force);
|
|
94
|
+
|
|
95
|
+
let targetIds: string[] = [];
|
|
96
|
+
if (args.id) targetIds.push(args.id);
|
|
97
|
+
targetIds.push(...parseIds(args.ids));
|
|
98
|
+
|
|
99
|
+
if (args.all) {
|
|
100
|
+
const limit = args.limit ?? DEFAULT_ALL_LIMIT;
|
|
101
|
+
const rows = await db
|
|
102
|
+
.select({ id: schema.recordings.id })
|
|
103
|
+
.from(schema.recordings)
|
|
104
|
+
.where(
|
|
105
|
+
and(
|
|
106
|
+
ownerEmailMatches(schema.recordings.ownerEmail, ownerEmail),
|
|
107
|
+
eq(schema.recordings.status, "ready"),
|
|
108
|
+
isNotNull(schema.recordings.videoUrl),
|
|
109
|
+
),
|
|
110
|
+
)
|
|
111
|
+
.orderBy(desc(schema.recordings.createdAt))
|
|
112
|
+
.limit(limit);
|
|
113
|
+
targetIds.push(...rows.map((r) => r.id));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// De-dupe while preserving order, and bound the batch so a single call
|
|
117
|
+
// can't run unboundedly under the hosted foreground budget.
|
|
118
|
+
targetIds = Array.from(new Set(targetIds)).slice(0, MAX_TARGETS_PER_CALL);
|
|
119
|
+
|
|
120
|
+
if (targetIds.length === 0) {
|
|
121
|
+
return {
|
|
122
|
+
ok: true,
|
|
123
|
+
processed: 0,
|
|
124
|
+
changed: 0,
|
|
125
|
+
results: [] as EnsureSeekableResult[],
|
|
126
|
+
message:
|
|
127
|
+
"No recordings to reprocess. Pass id / ids, or all: true to sweep your clips.",
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const results: EnsureSeekableResult[] = [];
|
|
132
|
+
for (const recordingId of targetIds) {
|
|
133
|
+
try {
|
|
134
|
+
results.push(
|
|
135
|
+
await ensureRecordingSeekable({ recordingId, ownerEmail, force }),
|
|
136
|
+
);
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.warn("[reprocess-recording] failed for", recordingId, err);
|
|
139
|
+
results.push({
|
|
140
|
+
recordingId,
|
|
141
|
+
status: "skipped-fetch-failed",
|
|
142
|
+
changed: false,
|
|
143
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const changed = results.filter((r) => r.changed).length;
|
|
149
|
+
console.log(
|
|
150
|
+
`Reprocessed ${results.length} recording(s); ${changed} rewritten for smoother playback.`,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
ok: true,
|
|
155
|
+
processed: results.length,
|
|
156
|
+
changed,
|
|
157
|
+
results,
|
|
158
|
+
};
|
|
159
|
+
},
|
|
160
|
+
});
|
|
@@ -452,8 +452,8 @@ function ClipsEmbedConfigurator({
|
|
|
452
452
|
|
|
453
453
|
const code =
|
|
454
454
|
mode === "responsive"
|
|
455
|
-
? `<div style="position:relative;padding-bottom:56.25%;height:0"><iframe src="${src}" frameborder="0" allowfullscreen allow="autoplay; picture-in-picture" style="position:absolute;inset:0;width:100%;height:100
|
|
456
|
-
: `<iframe src="${src}" width="${width}" height="${height}" frameborder="0" allowfullscreen allow="autoplay; picture-in-picture"></iframe>`;
|
|
455
|
+
? `<div style="position:relative;padding-bottom:56.25%;height:0;background:#000;overflow:hidden"><iframe src="${src}" title="${t("shareDialog.embedIframeTitle")}" frameborder="0" scrolling="no" allowfullscreen allow="autoplay; fullscreen; picture-in-picture" style="position:absolute;inset:0;width:100%;height:100%;border:0;background:#000;overflow:hidden"></iframe></div>`
|
|
456
|
+
: `<iframe src="${src}" title="${t("shareDialog.embedIframeTitle")}" width="${width}" height="${height}" frameborder="0" scrolling="no" allowfullscreen allow="autoplay; fullscreen; picture-in-picture" style="display:block;max-width:100%;border:0;background:#000;overflow:hidden"></iframe>`;
|
|
457
457
|
|
|
458
458
|
return (
|
|
459
459
|
<div className="space-y-3">
|
|
@@ -445,6 +445,7 @@ const messages = {
|
|
|
445
445
|
askOwnerPublic: "Bitten Sie den Eigentümer, es öffentlich zu machen.",
|
|
446
446
|
responsive: "Responsiv (16:9)",
|
|
447
447
|
fixedSize: "Feste Größe",
|
|
448
|
+
embedIframeTitle: "Clips-Video",
|
|
448
449
|
width: "Breite",
|
|
449
450
|
height: "Höhe",
|
|
450
451
|
autoplay: "Übersetzt: Autoplay",
|
|
@@ -440,6 +440,7 @@ const messages = {
|
|
|
440
440
|
askOwnerPublic: "Pídele al propietario que lo haga público.",
|
|
441
441
|
responsive: "Responsivo (16:9)",
|
|
442
442
|
fixedSize: "Tamaño fijo",
|
|
443
|
+
embedIframeTitle: "Video de Clips",
|
|
443
444
|
width: "Ancho",
|
|
444
445
|
height: "Altura",
|
|
445
446
|
autoplay: "Reproducción automática",
|
|
@@ -440,6 +440,7 @@ const messages = {
|
|
|
440
440
|
askOwnerPublic: "Demandez au propriétaire de le rendre public.",
|
|
441
441
|
responsive: "Réactif (16:9)",
|
|
442
442
|
fixedSize: "Taille fixe",
|
|
443
|
+
embedIframeTitle: "Video Clips",
|
|
443
444
|
width: "Largeur",
|
|
444
445
|
height: "Hauteur",
|
|
445
446
|
autoplay: "Lecture automatique",
|
|
@@ -425,6 +425,7 @@ const messages = {
|
|
|
425
425
|
askOwnerPublic: "मालिक से इसे सार्वजनिक करने के लिए कहें.",
|
|
426
426
|
responsive: "प्रतिक्रियाशील (16:9)",
|
|
427
427
|
fixedSize: "निश्चित आकार",
|
|
428
|
+
embedIframeTitle: "Clips वीडियो",
|
|
428
429
|
width: "चौड़ाई",
|
|
429
430
|
height: "ऊंचाई",
|
|
430
431
|
autoplay: "स्वत: प्ले",
|
|
@@ -438,6 +438,7 @@ const messages = {
|
|
|
438
438
|
askOwnerPublic: "Peça ao proprietário para torná-lo público.",
|
|
439
439
|
responsive: "Responsivo (16:9)",
|
|
440
440
|
fixedSize: "Tamanho fixo",
|
|
441
|
+
embedIframeTitle: "Video do Clips",
|
|
441
442
|
width: "Largura",
|
|
442
443
|
height: "Altura",
|
|
443
444
|
autoplay: "Reprodução automática",
|
|
@@ -78,6 +78,34 @@ export default function EmbedRoute() {
|
|
|
78
78
|
});
|
|
79
79
|
const [pwError, setPwError] = useState<string | null>(null);
|
|
80
80
|
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (typeof document === "undefined") return;
|
|
83
|
+
|
|
84
|
+
const html = document.documentElement;
|
|
85
|
+
const body = document.body;
|
|
86
|
+
const previous = {
|
|
87
|
+
htmlOverflow: html.style.overflow,
|
|
88
|
+
htmlHeight: html.style.height,
|
|
89
|
+
bodyOverflow: body.style.overflow,
|
|
90
|
+
bodyHeight: body.style.height,
|
|
91
|
+
bodyBackground: body.style.background,
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
html.style.overflow = "hidden";
|
|
95
|
+
html.style.height = "100%";
|
|
96
|
+
body.style.overflow = "hidden";
|
|
97
|
+
body.style.height = "100%";
|
|
98
|
+
body.style.background = "#000";
|
|
99
|
+
|
|
100
|
+
return () => {
|
|
101
|
+
html.style.overflow = previous.htmlOverflow;
|
|
102
|
+
html.style.height = previous.htmlHeight;
|
|
103
|
+
body.style.overflow = previous.bodyOverflow;
|
|
104
|
+
body.style.height = previous.bodyHeight;
|
|
105
|
+
body.style.background = previous.bodyBackground;
|
|
106
|
+
};
|
|
107
|
+
}, []);
|
|
108
|
+
|
|
81
109
|
const dataQ = useQuery({
|
|
82
110
|
queryKey: ["public-recording", shareId, password],
|
|
83
111
|
queryFn: async () => {
|
|
@@ -135,7 +163,7 @@ export default function EmbedRoute() {
|
|
|
135
163
|
|
|
136
164
|
if (dataQ.isLoading) {
|
|
137
165
|
return (
|
|
138
|
-
<div className="flex items-center justify-center
|
|
166
|
+
<div className="fixed inset-0 flex h-dvh w-dvw items-center justify-center overflow-hidden bg-black">
|
|
139
167
|
<Spinner className="h-8 w-8 text-white/70" />
|
|
140
168
|
</div>
|
|
141
169
|
);
|
|
@@ -153,14 +181,14 @@ export default function EmbedRoute() {
|
|
|
153
181
|
|
|
154
182
|
if (!recording) {
|
|
155
183
|
return (
|
|
156
|
-
<div className="flex items-center justify-center
|
|
184
|
+
<div className="fixed inset-0 flex h-dvh w-dvw items-center justify-center overflow-hidden bg-black text-white">
|
|
157
185
|
<p className="text-sm">{t("embedRoute.unavailable")}</p>
|
|
158
186
|
</div>
|
|
159
187
|
);
|
|
160
188
|
}
|
|
161
189
|
|
|
162
190
|
return (
|
|
163
|
-
<div className="h-
|
|
191
|
+
<div className="fixed inset-0 h-dvh w-dvw overflow-hidden bg-black">
|
|
164
192
|
<VideoPlayer
|
|
165
193
|
ref={playerRef}
|
|
166
194
|
recordingId={recording.id}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Agent-Native Clips",
|
|
4
4
|
"short_name": "Clips",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.12",
|
|
6
6
|
"description": "Start Clips recordings from Chrome, capture optional diagnostics, and preview Clips links on GitHub.",
|
|
7
7
|
"minimum_chrome_version": "116",
|
|
8
8
|
"action": {
|
|
@@ -168,10 +168,10 @@
|
|
|
168
168
|
Object.assign(frame.style, {
|
|
169
169
|
display: "block",
|
|
170
170
|
width: "100%",
|
|
171
|
-
height: "
|
|
171
|
+
height: "405px",
|
|
172
172
|
border: "0",
|
|
173
173
|
borderRadius: "8px",
|
|
174
|
-
background: "
|
|
174
|
+
background: "#000",
|
|
175
175
|
overflow: "hidden",
|
|
176
176
|
});
|
|
177
177
|
|
|
@@ -121,36 +121,39 @@ function renderPreview(
|
|
|
121
121
|
root.textContent = "";
|
|
122
122
|
|
|
123
123
|
const card = document.createElement("section");
|
|
124
|
-
card.className =
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
124
|
+
card.className =
|
|
125
|
+
state.kind === "playable"
|
|
126
|
+
? "clips-preview-card clips-preview-card--player"
|
|
127
|
+
: "clips-preview-card";
|
|
128
|
+
|
|
129
|
+
if (state.kind !== "playable") {
|
|
130
|
+
const header = document.createElement("header");
|
|
131
|
+
header.className = "clips-preview-header";
|
|
132
|
+
|
|
133
|
+
const titleWrap = document.createElement("div");
|
|
134
|
+
titleWrap.className = "clips-preview-title-wrap";
|
|
135
|
+
|
|
136
|
+
const title = document.createElement("div");
|
|
137
|
+
title.className = "clips-preview-title";
|
|
138
|
+
title.textContent =
|
|
139
|
+
state.kind === "unavailable" ? state.title : "Clips preview";
|
|
140
|
+
|
|
141
|
+
const subtitle = document.createElement("div");
|
|
142
|
+
subtitle.className = "clips-preview-subtitle";
|
|
143
|
+
subtitle.textContent = preview.host;
|
|
144
|
+
|
|
145
|
+
titleWrap.append(title, subtitle);
|
|
146
|
+
|
|
147
|
+
const open = document.createElement("a");
|
|
148
|
+
open.className = "clips-preview-open";
|
|
149
|
+
open.href = preview.sourceUrl.toString();
|
|
150
|
+
open.target = "_blank";
|
|
151
|
+
open.rel = "noreferrer noopener";
|
|
152
|
+
open.textContent = "Open clip";
|
|
153
|
+
|
|
154
|
+
header.append(titleWrap, open);
|
|
155
|
+
card.append(header);
|
|
156
|
+
}
|
|
154
157
|
|
|
155
158
|
if (state.kind === "playable") {
|
|
156
159
|
const player = document.createElement("div");
|
|
@@ -161,6 +164,7 @@ function renderPreview(
|
|
|
161
164
|
frame.src = preview.embedUrl.toString();
|
|
162
165
|
frame.allow = "autoplay; fullscreen; picture-in-picture";
|
|
163
166
|
frame.allowFullscreen = true;
|
|
167
|
+
frame.scrolling = "no";
|
|
164
168
|
frame.referrerPolicy = "no-referrer";
|
|
165
169
|
|
|
166
170
|
player.append(frame);
|
|
@@ -273,6 +277,7 @@ function installStyles(): void {
|
|
|
273
277
|
body {
|
|
274
278
|
margin: 0;
|
|
275
279
|
min-width: 0;
|
|
280
|
+
width: 100%;
|
|
276
281
|
background: transparent;
|
|
277
282
|
}
|
|
278
283
|
|
|
@@ -292,6 +297,12 @@ function installStyles(): void {
|
|
|
292
297
|
box-shadow: var(--clips-shadow);
|
|
293
298
|
}
|
|
294
299
|
|
|
300
|
+
.clips-preview-card--player {
|
|
301
|
+
border: 0;
|
|
302
|
+
background: #000000;
|
|
303
|
+
box-shadow: none;
|
|
304
|
+
}
|
|
305
|
+
|
|
295
306
|
.clips-preview-header {
|
|
296
307
|
display: flex;
|
|
297
308
|
align-items: center;
|
|
@@ -348,7 +359,6 @@ function installStyles(): void {
|
|
|
348
359
|
position: relative;
|
|
349
360
|
width: 100%;
|
|
350
361
|
aspect-ratio: 16 / 9;
|
|
351
|
-
min-height: 240px;
|
|
352
362
|
background: #000000;
|
|
353
363
|
}
|
|
354
364
|
|