Compare commits

..

2 Commits

Author SHA1 Message Date
clawdbot
62b1d60ff2 fix(goview): extract src from embed html for iframe/video 2026-01-28 12:30:11 +08:00
clawdbot
c4c50b9e89 fix(editor): make editor layout full-viewport 2026-01-28 11:41:44 +08:00
4 changed files with 74 additions and 11 deletions

View File

@ -1,8 +1,10 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
width: 100vw;
height: 100vh;
max-width: none;
margin: 0;
padding: 0;
text-align: initial;
}
.logo {

View File

@ -24,12 +24,16 @@ a:hover {
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
/* Editor app should occupy the full viewport; Vite template centering breaks layout */
#root {
width: 100vw;
height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;

View File

@ -40,6 +40,21 @@ function looksLikeHtml(input: string): boolean {
return trimmed.startsWith('<') && trimmed.includes('>');
}
function extractSrcFromEmbedHtml(html: string): string {
// Many low-code editors store iframe widgets as an embed code string.
// Prefer extracting the actual src to keep the resulting screen portable.
// Examples:
// - <iframe src="https://example.com" ...></iframe>
// - <iframe src='//example.com' ...>
// - <embed src="...">
const s = html.trim();
if (!s) return '';
const match = /\b(?:iframe|embed)\b[^>]*\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+))/i.exec(s);
const src = match?.[1] ?? match?.[2] ?? match?.[3] ?? '';
return typeof src === 'string' ? src.trim() : '';
}
function toMaybeNumber(v: unknown): number | undefined {
if (typeof v === 'number' && Number.isFinite(v)) return v;
if (typeof v === 'string') {
@ -94,7 +109,13 @@ function pickSrc(option: GoViewIframeOption): string {
pickUrlLike(option.src) ||
pickUrlLike(option.url);
if (url) return looksLikeHtml(url) ? toDataHtmlUrl(url) : url;
if (url) {
if (looksLikeHtml(url)) {
const extracted = extractSrcFromEmbedHtml(url);
return extracted || toDataHtmlUrl(url);
}
return url;
}
// 2) Some exports store raw HTML instead of a URL.
const html = pickFromNested(
@ -120,7 +141,12 @@ function pickSrc(option: GoViewIframeOption): string {
2,
);
return listUrl ? (looksLikeHtml(listUrl) ? toDataHtmlUrl(listUrl) : listUrl) : '';
if (!listUrl) return '';
if (looksLikeHtml(listUrl)) {
const extracted = extractSrcFromEmbedHtml(listUrl);
return extracted || toDataHtmlUrl(listUrl);
}
return listUrl;
}
function pickBorderRadius(option: GoViewIframeOption): number | undefined {

View File

@ -42,6 +42,29 @@ function asString(v: unknown): string {
return '';
}
function looksLikeHtml(input: string): boolean {
const trimmed = input.trim();
return trimmed.startsWith('<') && trimmed.includes('>');
}
function extractSrcFromVideoHtml(html: string): string {
// Some low-code exports store a whole <video> or <source> tag string.
// Examples:
// - <video src="..." />
// - <video><source src="..." /></video>
const s = html.trim();
if (!s) return '';
// Prefer <source src="..."> if present.
const sourceMatch = /\bsource\b[^>]*\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+))/i.exec(s);
const sourceSrc = sourceMatch?.[1] ?? sourceMatch?.[2] ?? sourceMatch?.[3] ?? '';
if (sourceSrc) return sourceSrc.trim();
const videoMatch = /\bvideo\b[^>]*\bsrc\s*=\s*(?:"([^"]+)"|'([^']+)'|([^\s>]+))/i.exec(s);
const videoSrc = videoMatch?.[1] ?? videoMatch?.[2] ?? videoMatch?.[3] ?? '';
return typeof videoSrc === 'string' ? videoSrc.trim() : '';
}
function toMaybeBoolean(v: unknown): boolean | undefined {
if (typeof v === 'boolean') return v;
if (typeof v === 'number') return v !== 0;
@ -113,6 +136,13 @@ function pickFirstUrlFromList(input: unknown): string {
return pickUrlLike(obj, 2);
}
function normalizeSrcMaybeFromHtml(raw: string): string {
if (!raw) return '';
if (!looksLikeHtml(raw)) return raw;
const extracted = extractSrcFromVideoHtml(raw);
return extracted || raw;
}
function pickSrc(option: GoViewVideoOption): string {
// Prefer explicit video-ish keys in a stable order, then fall back to permissive URL picking.
const fromDirect = pickFromNested(
@ -144,7 +174,7 @@ function pickSrc(option: GoViewVideoOption): string {
},
2,
);
if (fromDirect) return fromDirect;
if (fromDirect) return normalizeSrcMaybeFromHtml(fromDirect);
// Many exports use list-ish shapes: sources/sourceList/urlList/etc.
const fromList = pickFromNested(
@ -158,10 +188,11 @@ function pickSrc(option: GoViewVideoOption): string {
},
2,
);
if (fromList) return fromList;
if (fromList) return normalizeSrcMaybeFromHtml(fromList);
// Last resort: permissive URL-like picking across option/dataset/src/url.
return pickUrlLike(option) || pickUrlLike(option.dataset) || pickUrlLike(option.src) || pickUrlLike(option.url);
const raw = pickUrlLike(option) || pickUrlLike(option.dataset) || pickUrlLike(option.src) || pickUrlLike(option.url);
return normalizeSrcMaybeFromHtml(raw);
}
function pickBorderRadius(option: GoViewVideoOption): number | undefined {