2025-04-20 06:38:08 +00:00
|
|
|
<script lang="ts">
|
|
|
|
|
import DOMPurify from 'dompurify';
|
|
|
|
|
import type { Token } from 'marked';
|
|
|
|
|
|
|
|
|
|
import { WEBUI_BASE_URL } from '$lib/constants';
|
|
|
|
|
import Source from './Source.svelte';
|
2025-04-30 20:32:29 +00:00
|
|
|
import { settings } from '$lib/stores';
|
2025-04-20 06:38:08 +00:00
|
|
|
|
|
|
|
|
export let id: string;
|
|
|
|
|
export let token: Token;
|
|
|
|
|
|
|
|
|
|
export let onSourceClick: Function = () => {};
|
|
|
|
|
|
|
|
|
|
let html: string | null = null;
|
|
|
|
|
|
|
|
|
|
$: if (token.type === 'html' && token?.text) {
|
|
|
|
|
html = DOMPurify.sanitize(token.text);
|
|
|
|
|
} else {
|
|
|
|
|
html = null;
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
{#if token.type === 'html'}
|
|
|
|
|
{#if html && html.includes('<video')}
|
2025-05-05 07:25:00 +00:00
|
|
|
{@const video = html.match(/<video[^>]*>([\s\S]*?)<\/video>/)}
|
|
|
|
|
{@const videoSrc = video && video[1]}
|
|
|
|
|
{#if videoSrc}
|
|
|
|
|
<!-- svelte-ignore a11y-media-has-caption -->
|
|
|
|
|
<video
|
|
|
|
|
class="w-full my-2"
|
2025-06-04 11:43:23 +00:00
|
|
|
src={videoSrc.replaceAll('&', '&')}
|
2025-05-05 07:25:00 +00:00
|
|
|
title="Video player"
|
|
|
|
|
frameborder="0"
|
|
|
|
|
referrerpolicy="strict-origin-when-cross-origin"
|
2025-05-07 19:06:00 +00:00
|
|
|
controls
|
2025-05-05 07:25:00 +00:00
|
|
|
allowfullscreen
|
|
|
|
|
></video>
|
|
|
|
|
{:else}
|
|
|
|
|
{token.text}
|
|
|
|
|
{/if}
|
2025-06-08 10:08:07 +00:00
|
|
|
{:else if html && html.includes('<audio')}
|
|
|
|
|
{@const audio = html.match(/<audio[^>]*>([\s\S]*?)<\/audio>/)}
|
|
|
|
|
{@const audioSrc = audio && audio[1]}
|
|
|
|
|
{#if audioSrc}
|
|
|
|
|
<!-- svelte-ignore a11y-media-has-caption -->
|
|
|
|
|
<audio
|
|
|
|
|
class="w-full my-2"
|
|
|
|
|
src={audioSrc.replaceAll('&', '&')}
|
|
|
|
|
title="Audio player"
|
|
|
|
|
controls
|
|
|
|
|
></audio>
|
|
|
|
|
{:else}
|
|
|
|
|
{token.text}
|
|
|
|
|
{/if}
|
2025-04-20 06:40:31 +00:00
|
|
|
{:else if token.text && token.text.match(/<iframe\s+[^>]*src="https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})(?:\?[^"]*)?"[^>]*><\/iframe>/)}
|
2025-04-20 06:38:08 +00:00
|
|
|
{@const match = token.text.match(
|
2025-04-20 06:40:31 +00:00
|
|
|
/<iframe\s+[^>]*src="https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})(?:\?[^"]*)?"[^>]*><\/iframe>/
|
2025-04-20 06:38:08 +00:00
|
|
|
)}
|
|
|
|
|
{@const ytId = match && match[1]}
|
|
|
|
|
{#if ytId}
|
|
|
|
|
<iframe
|
|
|
|
|
class="w-full aspect-video my-2"
|
|
|
|
|
src={`https://www.youtube.com/embed/${ytId}`}
|
|
|
|
|
title="YouTube video player"
|
|
|
|
|
frameborder="0"
|
|
|
|
|
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
|
|
|
|
referrerpolicy="strict-origin-when-cross-origin"
|
|
|
|
|
allowfullscreen
|
|
|
|
|
>
|
|
|
|
|
</iframe>
|
|
|
|
|
{/if}
|
2025-06-16 13:43:17 +00:00
|
|
|
{:else if token.text && token.text.includes('<iframe')}
|
|
|
|
|
{@const match = token.text.match(/<iframe\s+[^>]*src="([^"]+)"[^>]*><\/iframe>/)}
|
|
|
|
|
{@const iframeSrc = match && match[1]}
|
|
|
|
|
{#if iframeSrc}
|
|
|
|
|
<iframe
|
|
|
|
|
class="w-full my-2"
|
|
|
|
|
src={iframeSrc}
|
|
|
|
|
title="Embedded content"
|
|
|
|
|
frameborder="0"
|
|
|
|
|
sandbox
|
2025-10-27 04:06:32 +00:00
|
|
|
on:load={(e) => {
|
|
|
|
|
try {
|
|
|
|
|
e.currentTarget.style.height =
|
|
|
|
|
e.currentTarget.contentWindow.document.body.scrollHeight + 20 + 'px';
|
|
|
|
|
} catch {}
|
|
|
|
|
}}
|
2025-06-16 13:43:17 +00:00
|
|
|
></iframe>
|
|
|
|
|
{:else}
|
|
|
|
|
{token.text}
|
|
|
|
|
{/if}
|
2025-07-08 08:31:31 +00:00
|
|
|
{:else if token.text && token.text.includes('<status')}
|
|
|
|
|
{@const match = token.text.match(/<status title="([^"]+)" done="(true|false)" ?\/?>/)}
|
|
|
|
|
{@const statusTitle = match && match[1]}
|
|
|
|
|
{@const statusDone = match && match[2] === 'true'}
|
|
|
|
|
{#if statusTitle}
|
|
|
|
|
<div class="flex flex-col justify-center -space-y-0.5">
|
|
|
|
|
<div
|
|
|
|
|
class="{statusDone === false
|
|
|
|
|
? 'shimmer'
|
|
|
|
|
: ''} text-gray-500 dark:text-gray-500 line-clamp-1 text-wrap"
|
|
|
|
|
>
|
|
|
|
|
{statusTitle}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{:else}
|
|
|
|
|
{token.text}
|
|
|
|
|
{/if}
|
2025-04-30 20:32:29 +00:00
|
|
|
{:else if token.text.includes(`<file type="html"`)}
|
|
|
|
|
{@const match = token.text.match(/<file type="html" id="([^"]+)"/)}
|
|
|
|
|
{@const fileId = match && match[1]}
|
|
|
|
|
{#if fileId}
|
|
|
|
|
<iframe
|
|
|
|
|
class="w-full my-2"
|
|
|
|
|
src={`${WEBUI_BASE_URL}/api/v1/files/${fileId}/content/html`}
|
|
|
|
|
title="Content"
|
|
|
|
|
frameborder="0"
|
2025-06-16 12:24:51 +00:00
|
|
|
sandbox="allow-scripts allow-downloads{($settings?.iframeSandboxAllowForms ?? false)
|
2025-04-30 20:32:29 +00:00
|
|
|
? ' allow-forms'
|
|
|
|
|
: ''}{($settings?.iframeSandboxAllowSameOrigin ?? false) ? ' allow-same-origin' : ''}"
|
|
|
|
|
referrerpolicy="strict-origin-when-cross-origin"
|
|
|
|
|
allowfullscreen
|
|
|
|
|
width="100%"
|
2025-10-27 04:06:32 +00:00
|
|
|
on:load={(e) => {
|
|
|
|
|
try {
|
|
|
|
|
e.currentTarget.style.height =
|
|
|
|
|
e.currentTarget.contentWindow.document.body.scrollHeight + 20 + 'px';
|
|
|
|
|
} catch {}
|
|
|
|
|
}}
|
2025-04-30 20:32:29 +00:00
|
|
|
></iframe>
|
|
|
|
|
{/if}
|
2025-04-20 06:38:08 +00:00
|
|
|
{:else if token.text.includes(`<source_id`)}
|
|
|
|
|
<Source {id} {token} onClick={onSourceClick} />
|
|
|
|
|
{:else}
|
2025-08-01 10:07:11 +00:00
|
|
|
{@const br = token.text.match(/<br\s*\/?>/)}
|
|
|
|
|
{#if br}
|
|
|
|
|
<br />
|
|
|
|
|
{:else}
|
|
|
|
|
{token.text}
|
|
|
|
|
{/if}
|
2025-04-20 06:38:08 +00:00
|
|
|
{/if}
|
|
|
|
|
{/if}
|