From b67d6be00f20d41ebde798fd64b9b71e4841fc28 Mon Sep 17 00:00:00 2001 From: clawdbot Date: Wed, 28 Jan 2026 04:32:30 +0800 Subject: [PATCH] feat(sdk): improve goView iframe/video import --- packages/sdk/src/core/widgets/iframe.ts | 89 ++++++++++++++++----- packages/sdk/src/core/widgets/video.ts | 101 ++++++++++++++++++++++-- 2 files changed, 165 insertions(+), 25 deletions(-) diff --git a/packages/sdk/src/core/widgets/iframe.ts b/packages/sdk/src/core/widgets/iframe.ts index c726bf9..90009d5 100644 --- a/packages/sdk/src/core/widgets/iframe.ts +++ b/packages/sdk/src/core/widgets/iframe.ts @@ -9,6 +9,17 @@ export interface GoViewIframeOption { dataset?: unknown; src?: 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; } @@ -29,24 +40,6 @@ function looksLikeHtml(input: string): boolean { 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 { if (typeof v === 'number' && Number.isFinite(v)) return v; if (typeof v === 'string') { @@ -56,7 +49,11 @@ function toMaybeNumber(v: unknown): number | undefined { return undefined; } -function pickFromNested(input: unknown, picker: (obj: Record) => T | undefined, depth: number): T | undefined { +function pickFromNested( + input: unknown, + picker: (obj: Record) => T | undefined, + depth: number, +): T | undefined { if (!input || typeof input !== 'object') return undefined; const obj = input as Record; @@ -72,6 +69,60 @@ function pickFromNested(input: unknown, picker: (obj: Record 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 { const direct = toMaybeNumber(option.borderRadius); if (direct !== undefined) return direct; diff --git a/packages/sdk/src/core/widgets/video.ts b/packages/sdk/src/core/widgets/video.ts index f003906..4226806 100644 --- a/packages/sdk/src/core/widgets/video.ts +++ b/packages/sdk/src/core/widgets/video.ts @@ -10,6 +10,14 @@ export interface GoViewVideoOption { src?: unknown; url?: unknown; + // common alternative shapes + sources?: unknown; + sourceList?: unknown; + urlList?: unknown; + srcList?: unknown; + playList?: unknown; + playlist?: unknown; + autoplay?: boolean; autoPlay?: boolean; isAutoPlay?: boolean; @@ -28,11 +36,6 @@ export interface 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 { if (typeof v === 'string') return v; if (!v) return ''; @@ -59,7 +62,11 @@ function toMaybeNumber(v: unknown): number | undefined { return undefined; } -function pickFromNested(input: unknown, picker: (obj: Record) => T | undefined, depth: number): T | undefined { +function pickFromNested( + input: unknown, + picker: (obj: Record) => T | undefined, + depth: number, +): T | undefined { if (!input || typeof input !== 'object') return undefined; const obj = input as Record; @@ -75,6 +82,88 @@ function pickFromNested(input: unknown, picker: (obj: Record 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; + + // 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 { const direct = toMaybeNumber(option.borderRadius); if (direct !== undefined) return direct;