Monorepo for Tangled
Something went wrong. Try again.
Svelte
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139<script lang="ts"> import type { KeyboardEventHandler } from "svelte/elements"; import Eye from "$icon/eye"; import Pencil from "$icon/pencil"; import Button from "$lib/components/ui/Button.svelte"; import ButtonGroup, { segmentProps } from "$lib/components/ui/ButtonGroup.svelte"; import EmptyState from "$lib/components/ui/EmptyState.svelte"; import Pending from "$lib/components/ui/Pending.svelte"; import Textarea, { textareaMinHeight } from "$lib/components/ui/Textarea.svelte"; import { renderMarkup, type MarkupContext } from "$lib/markup"; import { enhanceCodeblocks } from "$lib/markup/highlight";
interface Props { value?: string; markup: MarkupContext; tab?: "write" | "preview"; id?: string; name?: string; rows?: number; placeholder?: string; disabled?: boolean; autofocus?: boolean; // drop the textarea/preview background so the editor blends into its surroundings transparent?: boolean; }
let { value = $bindable(""), markup, tab = $bindable("write"), id, name, rows = 12, placeholder, disabled = false, autofocus = false, transparent = false }: Props = $props();
const surface = $derived(transparent ? "bg-transparent" : "bg-background-default"); const pane = `rounded border border-border-default px-2.5 py-2 ${textareaMinHeight}`;
let preview = $state<Promise<string> | null>(null); let textareaEl = $state<HTMLTextAreaElement>();
// focus on mount (and when returning to the write tab) if requested $effect(() => { if (autofocus) textareaEl?.focus(); });
$effect(() => { if (tab !== "preview") { preview = null; return; } const source = value; if (!source.trim()) { preview = null; return; } preview = renderMarkup(source, markup).then((html) => html ?? ""); }); // ctrl/cmd+enter submits the enclosing form, mirroring the old htmx editor const handleKeydown: KeyboardEventHandler<HTMLTextAreaElement> = (e) => { if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { e.preventDefault(); e.currentTarget.form?.requestSubmit(); } };</script>
<div class="flex flex-col gap-1.5"> <ButtonGroup class="self-start"> <Button type="button" size="small" icon={Pencil} {...segmentProps(tab === "write")} onclick={() => (tab = "write")} > Write </Button> <Button type="button" size="small" icon={Eye} {...segmentProps(tab === "preview")} onclick={() => (tab = "preview")} > Preview </Button> </ButtonGroup>
{#if tab === "write"} <Textarea {id} {name} {rows} resizeable {placeholder} bind:value bind:element={textareaEl} {disabled} onkeydown={handleKeydown} class={transparent ? "bg-transparent" : undefined} /> {:else} <Pending> {#snippet skeleton()} <div class={`${pane} ${surface} typography-paragraph-small text-foreground-subtle italic`} > Rendering… </div> {/snippet} {#snippet failed()} <div class={`${pane} ${surface} typography-paragraph-small text-foreground-subtle italic`} > Could not render preview. </div> {/snippet} {@const html = await preview} {#if html} <div class={`markup ${pane} ${surface}`} use:enhanceCodeblocks={html}> <!-- eslint-disable-next-line svelte/no-at-html-tags -- sanitised in $lib/markup --> {@html html} </div> {:else} <EmptyState size="inline" message="Nothing to preview." class={`justify-center italic ${pane} ${surface}`} /> {/if} </Pending> {/if}</div>