2025-09-19 01:55:23 +00:00
|
|
|
<script lang="ts">
|
|
|
|
|
import { onDestroy, onMount } from 'svelte';
|
|
|
|
|
|
|
|
|
|
// Props
|
|
|
|
|
export let src: string | null = null; // URL or raw HTML (auto-detected)
|
|
|
|
|
export let title = 'Embedded Content';
|
2025-09-19 02:25:26 +00:00
|
|
|
export let initialHeight: number | null = null; // initial height in px, null = auto
|
|
|
|
|
|
2025-09-19 01:55:23 +00:00
|
|
|
export let allowScripts = true;
|
|
|
|
|
export let allowForms = false;
|
|
|
|
|
|
|
|
|
|
export let allowSameOrigin = false; // set to true only when you trust the content
|
|
|
|
|
export let allowPopups = false;
|
|
|
|
|
export let allowDownloads = true;
|
|
|
|
|
|
|
|
|
|
export let referrerPolicy: HTMLIFrameElement['referrerPolicy'] =
|
|
|
|
|
'strict-origin-when-cross-origin';
|
|
|
|
|
export let allowFullscreen = true;
|
|
|
|
|
|
|
|
|
|
let iframe: HTMLIFrameElement | null = null;
|
|
|
|
|
|
|
|
|
|
// Derived: build sandbox attribute from flags
|
|
|
|
|
$: sandbox =
|
|
|
|
|
[
|
|
|
|
|
allowScripts && 'allow-scripts',
|
|
|
|
|
allowForms && 'allow-forms',
|
|
|
|
|
allowSameOrigin && 'allow-same-origin',
|
|
|
|
|
allowPopups && 'allow-popups',
|
|
|
|
|
allowDownloads && 'allow-downloads'
|
|
|
|
|
]
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.join(' ') || undefined;
|
|
|
|
|
|
|
|
|
|
// Detect URL vs raw HTML and prep src/srcdoc
|
|
|
|
|
$: isUrl = typeof src === 'string' && /^(https?:)?\/\//i.test(src);
|
|
|
|
|
$: iframeSrc = isUrl ? (src as string) : null;
|
2025-09-19 02:25:26 +00:00
|
|
|
$: iframeDoc = !isUrl ? src : null;
|
2025-09-19 01:55:23 +00:00
|
|
|
|
|
|
|
|
// Try to measure same-origin content safely
|
|
|
|
|
function resizeSameOrigin() {
|
|
|
|
|
if (!iframe) return;
|
|
|
|
|
try {
|
|
|
|
|
const doc = iframe.contentDocument || iframe.contentWindow?.document;
|
2025-09-19 02:25:26 +00:00
|
|
|
console.log('iframe doc:', doc);
|
2025-09-19 01:55:23 +00:00
|
|
|
if (!doc) return;
|
|
|
|
|
const h = Math.max(doc.documentElement?.scrollHeight ?? 0, doc.body?.scrollHeight ?? 0);
|
|
|
|
|
if (h > 0) iframe.style.height = h + 20 + 'px';
|
|
|
|
|
} catch {
|
|
|
|
|
// Cross-origin → rely on postMessage from inside the iframe
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle height messages from the iframe (we also verify the sender)
|
|
|
|
|
function onMessage(e: MessageEvent) {
|
|
|
|
|
if (!iframe || e.source !== iframe.contentWindow) return;
|
|
|
|
|
const data = e.data as { type?: string; height?: number };
|
|
|
|
|
if (data?.type === 'iframe:height' && typeof data.height === 'number') {
|
|
|
|
|
iframe.style.height = Math.max(0, data.height) + 'px';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-19 02:25:26 +00:00
|
|
|
// When the iframe loads, try same-origin resize (cross-origin will noop)
|
|
|
|
|
function onLoad() {
|
|
|
|
|
requestAnimationFrame(resizeSameOrigin);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-19 01:55:23 +00:00
|
|
|
// Ensure event listener bound only while component lives
|
|
|
|
|
onMount(() => {
|
|
|
|
|
window.addEventListener('message', onMessage);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
onDestroy(() => {
|
|
|
|
|
window.removeEventListener('message', onMessage);
|
|
|
|
|
});
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
{#if iframeDoc}
|
|
|
|
|
<iframe
|
|
|
|
|
bind:this={iframe}
|
|
|
|
|
srcdoc={iframeDoc}
|
|
|
|
|
{title}
|
2025-09-19 02:25:26 +00:00
|
|
|
class="w-full rounded-2xl"
|
|
|
|
|
style={`${initialHeight ? `height:${initialHeight}px;` : ''}`}
|
2025-09-19 01:55:23 +00:00
|
|
|
width="100%"
|
|
|
|
|
frameborder="0"
|
|
|
|
|
{sandbox}
|
|
|
|
|
{allowFullscreen}
|
|
|
|
|
on:load={onLoad}
|
|
|
|
|
/>
|
|
|
|
|
{:else if iframeSrc}
|
|
|
|
|
<iframe
|
|
|
|
|
bind:this={iframe}
|
|
|
|
|
src={iframeSrc}
|
|
|
|
|
{title}
|
2025-09-19 02:25:26 +00:00
|
|
|
class="w-full rounded-2xl"
|
|
|
|
|
style={`${initialHeight ? `height:${initialHeight}px;` : ''}`}
|
2025-09-19 01:55:23 +00:00
|
|
|
width="100%"
|
|
|
|
|
frameborder="0"
|
|
|
|
|
{sandbox}
|
|
|
|
|
referrerpolicy={referrerPolicy}
|
|
|
|
|
{allowFullscreen}
|
|
|
|
|
on:load={onLoad}
|
|
|
|
|
/>
|
|
|
|
|
{/if}
|