Something went wrong. Try again.
Monorepo for Tangled
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133<script lang="ts"> import type { KeyboardEventHandler } from "svelte/elements"; import Eye from "$icon/eye"; import Pencil from "$icon/pencil"; import Button from "./Button.svelte"; import ButtonGroup from "./ButtonGroup.svelte"; import Textarea from "./Textarea.svelte"; import { renderMarkup, type MarkupContext } from "$lib/markup";
interface Props { value?: string; markup: MarkupContext; tab?: "write" | "preview"; id?: string; name?: string; rows?: number; placeholder?: string; disabled?: boolean; previewClass?: string; 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, previewClass = "min-h-40", autofocus = false, transparent = false }: Props = $props();
const surface = $derived(transparent ? "bg-transparent" : "bg-background-default");
let previewHtml = $state<string | null>(null); let previewing = $state(false); 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") return; const source = value; if (!source.trim()) { previewHtml = null; previewing = false; return; } let cancelled = false; previewing = true; renderMarkup(source, markup) .then((html) => { if (!cancelled) previewHtml = html; }) .catch(() => { if (!cancelled) previewHtml = null; }) .finally(() => { if (!cancelled) previewing = false; }); return () => { cancelled = true; }; }); // 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="sm" icon={Pencil} variant={tab === "write" ? "default" : "ghost"} onclick={() => (tab = "write")} > Write </Button> <Button type="button" size="sm" icon={Eye} variant={tab === "preview" ? "default" : "ghost"} 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 ? "max-h-none bg-transparent" : "max-h-none"} /> {:else if previewHtml} <div class={`markup rounded border border-border-default ${surface} px-2.5 py-2 ${previewClass}`} > <!-- eslint-disable-next-line svelte/no-at-html-tags -- sanitised in $lib/markup --> {@html previewHtml} </div> {:else} <div class={`rounded border border-border-default ${surface} px-2.5 py-2 text-sm text-foreground-subtle italic ${previewClass}`} > {previewing ? "Rendering…" : "Nothing to preview."} </div> {/if}</div>