diff --git a/packages/sdk/src/core/goview/convert.ts b/packages/sdk/src/core/goview/convert.ts index d0a1b86..a67c5c1 100644 --- a/packages/sdk/src/core/goview/convert.ts +++ b/packages/sdk/src/core/goview/convert.ts @@ -680,10 +680,75 @@ function pickNumberLikeInner(input: unknown, keys: string[], depth: number): num return undefined; } +function looksLikeHtml(input: string): boolean { + const s = input.trim(); + return s.startsWith('<') && s.includes('>'); +} + +function extractHtmlAttribute(html: string, name: string): string | undefined { + // eslint-disable-next-line no-useless-escape + const re = new RegExp(`\\b${name}\\s*=\\s*(?:\"([^\"]+)\"|'([^']+)'|([^\\s>]+))`, 'i'); + const match = re.exec(html); + return match?.[1] ?? match?.[2] ?? match?.[3]; +} + +function parseHtmlSizeAttr(value: string | undefined): number | undefined { + if (!value) return undefined; + const v = value.trim(); + // only accept plain numbers or px values; ignore %/auto/etc. + const m = /^([0-9]+(?:\.[0-9]+)?)(px)?$/i.exec(v); + if (!m) return undefined; + const n = Number(m[1]); + return Number.isFinite(n) && n > 0 ? n : undefined; +} + +function pickHtmlStringDeep(input: unknown, depth = 2): string | undefined { + if (typeof input === 'string') return looksLikeHtml(input) ? input : undefined; + if (!input || typeof input !== 'object') return undefined; + const obj = input as Record; + + for (const key of [ + 'html', + 'htmlContent', + 'htmlString', + 'iframeHtml', + 'embedHtml', + 'embedCode', + 'iframeCode', + 'iframeEmbed', + 'embed', + 'code', + 'content', + 'template', + 'srcdoc', + 'srcDoc', + ]) { + const v = obj[key]; + if (typeof v === 'string' && looksLikeHtml(v)) return v; + } + + if (depth <= 0) return undefined; + for (const nestKey of ['style', 'config', 'option', 'options', 'props', 'dataset', 'data']) { + const v = pickHtmlStringDeep(obj[nestKey], depth - 1); + if (v) return v; + } + return undefined; +} + function pickSizeLike(option: unknown): { w?: number; h?: number } { // goView-ish variants use different keys and sometimes nest them under style/config. const w = pickNumberLike(option, ['width', 'w']); const h = pickNumberLike(option, ['height', 'h']); + + // Some forks store only an embed HTML snippet (iframe/video) with width/height. + // If attr sizing is missing, this helps us pick a better default rect. + const html = pickHtmlStringDeep(option, 3); + if (html) { + const wFromHtml = parseHtmlSizeAttr(extractHtmlAttribute(html, 'width')); + const hFromHtml = parseHtmlSizeAttr(extractHtmlAttribute(html, 'height')); + return { w: w ?? wFromHtml, h: h ?? hFromHtml }; + } + return { w, h }; }