Spaces:
Running
Running
| <script lang="ts"> | |
| import Button from '$lib/components/ui/button/button.svelte'; | |
| import { Check, Copy, Eye, X } from '@lucide/svelte'; | |
| import { HighlightAuto } from 'svelte-highlight'; | |
| import { mode } from 'mode-watcher'; | |
| import githubDarkUrl from 'svelte-highlight/styles/github-dark.css?url'; | |
| import githubUrl from 'svelte-highlight/styles/github.css?url'; | |
| import Switch from '$lib/components/ui/switch/switch.svelte'; | |
| import * as Dialog from '$lib/components/ui/dialog/index.js'; | |
| let { | |
| lang, | |
| text, | |
| className = 'my-3 relative rounded-lg border border-border/60 bg-muted/50' | |
| }: { lang?: string; text: string; className?: string } = $props(); | |
| let copiedCode = $state(false); | |
| let open = $state.raw(false); | |
| async function copy(text: string) { | |
| await navigator.clipboard.writeText(text); | |
| copiedCode = true; | |
| setTimeout(() => (copiedCode = false), 2000); | |
| } | |
| function hasStrictHtml5Doctype(input: string): boolean { | |
| if (!input) return false; | |
| const withoutBOM = input.replace(/^\uFEFF/, ''); | |
| const trimmed = withoutBOM.trimStart(); | |
| // Strict HTML5 doctype: <!doctype html> with optional whitespace before > | |
| return /^<!doctype\s+html\s*>/i.test(trimmed); | |
| } | |
| function isSvgDocument(input: string): boolean { | |
| const trimmed = input.trimStart(); | |
| return /^(?:<\?xml[^>]*>\s*)?(?:<!doctype\s+svg[^>]*>\s*)?<svg[\s>]/i.test(trimmed); | |
| } | |
| $effect(() => { | |
| const themeUrl = mode.current === 'dark' ? githubDarkUrl : githubUrl; | |
| let link = document.getElementById('highlight-theme') as HTMLLinkElement | null; | |
| if (!link) { | |
| link = document.createElement('link'); | |
| link.id = 'highlight-theme'; | |
| link.rel = 'stylesheet'; | |
| document.head.appendChild(link); | |
| } | |
| link.href = themeUrl; | |
| }); | |
| let canShowPreview = $derived( | |
| hasStrictHtml5Doctype(text) || isSvgDocument(text) || lang == 'svg' || lang == 'xml' | |
| ); | |
| // svelte-ignore state_referenced_locally | |
| let showPreview = $state.raw(canShowPreview); | |
| </script> | |
| <div class="overflow-hidden {className}"> | |
| {#if lang} | |
| <div | |
| class="flex items-center justify-between border-b border-border/60 bg-muted px-3 py-1.5 dark:bg-accent/30" | |
| > | |
| <span class="font-mono text-xs text-muted-foreground"> | |
| {#if showPreview} | |
| Live Preview | |
| {:else} | |
| {lang} | |
| {/if} | |
| </span> | |
| {#if canShowPreview} | |
| <div class="flex items-center gap-1.5"> | |
| {#if showPreview} | |
| <Button variant="outline" size="2xs" onclick={() => (open = true)}> | |
| <Eye class="size-3.5" /> | |
| Open | |
| </Button> | |
| {/if} | |
| <p class="font-mono text-[10px] text-muted-foreground"> | |
| Show {showPreview ? 'Preview' : 'Code'} | |
| </p> | |
| <Switch bind:checked={showPreview} /> | |
| </div> | |
| {/if} | |
| </div> | |
| {/if} | |
| {#if showPreview} | |
| <div class="group relative"> | |
| <iframe | |
| srcDoc={text} | |
| class="h-[500px] w-full" | |
| title="Preview" | |
| sandbox="allow-scripts allow-popups" | |
| referrerpolicy="no-referrer" | |
| ></iframe> | |
| </div> | |
| {:else} | |
| <div class="group relative"> | |
| <HighlightAuto code={text} class="font-mono text-xs! leading-relaxed" /> | |
| <Button | |
| variant="outline" | |
| class="absolute top-2 right-2 opacity-0 group-hover:opacity-100" | |
| size="icon-xs" | |
| onclick={() => copy(text)} | |
| > | |
| {#if copiedCode} | |
| <Check class="size-3.5 text-green-500" /> | |
| {:else} | |
| <Copy class="size-3.5" /> | |
| {/if} | |
| </Button> | |
| </div> | |
| {/if} | |
| </div> | |
| <Dialog.Root bind:open> | |
| <Dialog.Content | |
| class="max-w-[90dvw]! gap-0! space-y-0! border-none! p-0!" | |
| showCloseButton={false} | |
| > | |
| <Dialog.Description class="relative mt-0 pt-0!"> | |
| <Button | |
| variant="outline" | |
| class="absolute top-2 right-2" | |
| size="icon-xs" | |
| onclick={() => (open = false)} | |
| > | |
| <X class="size-3.5" /> | |
| </Button> | |
| <iframe | |
| srcDoc={text} | |
| class="h-[90dvh] w-full" | |
| title="Preview" | |
| sandbox="allow-scripts allow-popups" | |
| referrerpolicy="no-referrer" | |
| ></iframe> | |
| </Dialog.Description> | |
| </Dialog.Content> | |
| </Dialog.Root> | |