feat(sdk): improve goView iframe/video import

This commit is contained in:
clawdbot 2026-01-28 04:32:30 +08:00
parent 97a2eccc92
commit b67d6be00f
2 changed files with 165 additions and 25 deletions

View File

@ -9,6 +9,17 @@ export interface GoViewIframeOption {
dataset?: unknown; dataset?: unknown;
src?: unknown; src?: unknown;
url?: unknown; url?: unknown;
// common alternative shapes
iframeUrl?: unknown;
iframeSrc?: unknown;
embedUrl?: unknown;
// list-ish shapes (some low-code editors model embeds as a list even for a single item)
sources?: unknown;
sourceList?: unknown;
urlList?: unknown;
borderRadius?: number; borderRadius?: number;
} }
@ -29,24 +40,6 @@ function looksLikeHtml(input: string): boolean {
return trimmed.startsWith('<') && trimmed.includes('>'); return trimmed.startsWith('<') && trimmed.includes('>');
} }
function pickSrc(option: GoViewIframeOption): string {
// Prefer the whole option first (covers iframeUrl/embedUrl variants directly on the object).
const url = pickUrlLike(option) || pickUrlLike(option.dataset) || pickUrlLike(option.src) || pickUrlLike(option.url);
if (url) return looksLikeHtml(url) ? toDataHtmlUrl(url) : url;
// Some goView / low-code exports store raw HTML instead of a URL.
const html = pickFromNested(
option,
(obj) => {
const v = obj.srcdoc ?? obj.srcDoc ?? obj.html ?? obj.htmlContent ?? obj.content ?? obj.template;
return typeof v === 'string' ? v : undefined;
},
2,
);
return html ? toDataHtmlUrl(html) : '';
}
function toMaybeNumber(v: unknown): number | undefined { function toMaybeNumber(v: unknown): number | undefined {
if (typeof v === 'number' && Number.isFinite(v)) return v; if (typeof v === 'number' && Number.isFinite(v)) return v;
if (typeof v === 'string') { if (typeof v === 'string') {
@ -56,7 +49,11 @@ function toMaybeNumber(v: unknown): number | undefined {
return undefined; return undefined;
} }
function pickFromNested<T>(input: unknown, picker: (obj: Record<string, unknown>) => T | undefined, depth: number): T | undefined { function pickFromNested<T>(
input: unknown,
picker: (obj: Record<string, unknown>) => T | undefined,
depth: number,
): T | undefined {
if (!input || typeof input !== 'object') return undefined; if (!input || typeof input !== 'object') return undefined;
const obj = input as Record<string, unknown>; const obj = input as Record<string, unknown>;
@ -72,6 +69,60 @@ function pickFromNested<T>(input: unknown, picker: (obj: Record<string, unknown>
return undefined; return undefined;
} }
function pickFirstUrlFromList(input: unknown): string {
if (!input) return '';
if (typeof input === 'string') return input;
if (Array.isArray(input)) {
for (const item of input) {
const v = pickFirstUrlFromList(item);
if (v) return v;
}
return '';
}
if (typeof input !== 'object') return '';
return pickUrlLike(input, 2);
}
function pickSrc(option: GoViewIframeOption): string {
// 1) Prefer explicit iframe-ish URL fields.
const url =
pickUrlLike({ iframeUrl: option.iframeUrl, iframeSrc: option.iframeSrc, embedUrl: option.embedUrl }) ||
pickUrlLike(option) ||
pickUrlLike(option.dataset) ||
pickUrlLike(option.src) ||
pickUrlLike(option.url);
if (url) return looksLikeHtml(url) ? toDataHtmlUrl(url) : url;
// 2) Some exports store raw HTML instead of a URL.
const html = pickFromNested(
option,
(obj) => {
const v = obj.srcdoc ?? obj.srcDoc ?? obj.html ?? obj.htmlContent ?? obj.content ?? obj.template;
return typeof v === 'string' ? v : undefined;
},
2,
);
if (html) return toDataHtmlUrl(html);
// 3) List-ish shapes.
const listUrl = pickFromNested(
option,
(obj) => {
for (const key of ['sources', 'sourceList', 'urlList']) {
const v = pickFirstUrlFromList(obj[key]);
if (v) return v;
}
return undefined;
},
2,
);
return listUrl ? (looksLikeHtml(listUrl) ? toDataHtmlUrl(listUrl) : listUrl) : '';
}
function pickBorderRadius(option: GoViewIframeOption): number | undefined { function pickBorderRadius(option: GoViewIframeOption): number | undefined {
const direct = toMaybeNumber(option.borderRadius); const direct = toMaybeNumber(option.borderRadius);
if (direct !== undefined) return direct; if (direct !== undefined) return direct;

View File

@ -10,6 +10,14 @@ export interface GoViewVideoOption {
src?: unknown; src?: unknown;
url?: unknown; url?: unknown;
// common alternative shapes
sources?: unknown;
sourceList?: unknown;
urlList?: unknown;
srcList?: unknown;
playList?: unknown;
playlist?: unknown;
autoplay?: boolean; autoplay?: boolean;
autoPlay?: boolean; autoPlay?: boolean;
isAutoPlay?: boolean; isAutoPlay?: boolean;
@ -28,11 +36,6 @@ export interface GoViewVideoOption {
*/ */
export type LegacyVideoOption = GoViewVideoOption; export type LegacyVideoOption = GoViewVideoOption;
function pickSrc(option: GoViewVideoOption): string {
// Prefer the whole option first (covers videoUrl/mp4/m3u8/flv/etc directly on the object).
return pickUrlLike(option) || pickUrlLike(option.dataset) || pickUrlLike(option.src) || pickUrlLike(option.url);
}
function asString(v: unknown): string { function asString(v: unknown): string {
if (typeof v === 'string') return v; if (typeof v === 'string') return v;
if (!v) return ''; if (!v) return '';
@ -59,7 +62,11 @@ function toMaybeNumber(v: unknown): number | undefined {
return undefined; return undefined;
} }
function pickFromNested<T>(input: unknown, picker: (obj: Record<string, unknown>) => T | undefined, depth: number): T | undefined { function pickFromNested<T>(
input: unknown,
picker: (obj: Record<string, unknown>) => T | undefined,
depth: number,
): T | undefined {
if (!input || typeof input !== 'object') return undefined; if (!input || typeof input !== 'object') return undefined;
const obj = input as Record<string, unknown>; const obj = input as Record<string, unknown>;
@ -75,6 +82,88 @@ function pickFromNested<T>(input: unknown, picker: (obj: Record<string, unknown>
return undefined; return undefined;
} }
function pickFirstUrlFromList(input: unknown): string {
if (!input) return '';
// strings, objects, or nested list wrappers are all accepted.
if (typeof input === 'string') return input;
if (Array.isArray(input)) {
for (const item of input) {
const v = pickFirstUrlFromList(item);
if (v) return v;
}
return '';
}
if (typeof input !== 'object') return '';
const obj = input as Record<string, unknown>;
// common media list item shapes
// - { src/url/value }
// - { playUrl }
// - { mp4/m3u8/flv/hls/rtsp/rtmp }
for (const key of ['mp4', 'm3u8', 'flv', 'hls', 'rtsp', 'rtmp', 'videoUrl', 'videoSrc', 'playUrl', 'src', 'url', 'value']) {
const v = obj[key];
if (typeof v === 'string' && v) return v;
}
// nested wrappers like { source: { url } } or { data: {...} }
return pickUrlLike(obj, 2);
}
function pickSrc(option: GoViewVideoOption): string {
// Prefer explicit video-ish keys in a stable order, then fall back to permissive URL picking.
const fromDirect = pickFromNested(
option,
(obj) => {
for (const key of [
// common explicit keys
'videoUrl',
'videoSrc',
// format-specific keys
'm3u8',
'hls',
'mp4',
'flv',
// streaming/camera keys
'rtsp',
'rtmp',
'stream',
'streamUrl',
// generic fallbacks
'src',
'url',
'value',
]) {
const v = obj[key];
if (typeof v === 'string' && v) return v;
}
return undefined;
},
2,
);
if (fromDirect) return fromDirect;
// Many exports use list-ish shapes: sources/sourceList/urlList/etc.
const fromList = pickFromNested(
option,
(obj) => {
for (const key of ['sources', 'sourceList', 'urlList', 'srcList', 'playList', 'playlist']) {
const v = pickFirstUrlFromList(obj[key]);
if (v) return v;
}
return undefined;
},
2,
);
if (fromList) return fromList;
// Last resort: permissive URL-like picking across option/dataset/src/url.
return pickUrlLike(option) || pickUrlLike(option.dataset) || pickUrlLike(option.src) || pickUrlLike(option.url);
}
function pickBorderRadius(option: GoViewVideoOption): number | undefined { function pickBorderRadius(option: GoViewVideoOption): number | undefined {
const direct = toMaybeNumber(option.borderRadius); const direct = toMaybeNumber(option.borderRadius);
if (direct !== undefined) return direct; if (direct !== undefined) return direct;