diff --git a/apps/web/src/routes/cards/new/+page.svelte b/apps/web/src/routes/cards/new/+page.svelte index 478a1e0..4ff7346 100644 --- a/apps/web/src/routes/cards/new/+page.svelte +++ b/apps/web/src/routes/cards/new/+page.svelte @@ -2,7 +2,11 @@ import { onMount } from 'svelte'; import { goto } from '$app/navigation'; import { page } from '$app/state'; - import type { CardType } from '@cards/domain'; + import { + extractClusterIds, + renderClozePrompt, + type CardType, + } from '@cards/domain'; import { createCard } from '$lib/api/cards.ts'; import { listDecks, getDeck } from '$lib/api/decks.ts'; import { devUser } from '$lib/auth/dev-stub.svelte.ts'; @@ -16,10 +20,18 @@ let cardType = $state('basic'); let front = $state(''); let back = $state(''); + let text = $state(''); + let extra = $state(''); let saving = $state(false); const frontHtml = $derived(renderMarkdown(front)); const backHtml = $derived(renderMarkdown(back)); + const clusterIds = $derived(extractClusterIds(text)); + const clozePreviewHtml = $derived.by(() => { + const firstCluster = clusterIds[0]; + if (firstCluster === undefined) return ''; + return renderMarkdown(renderClozePrompt(text, firstCluster)); + }); onMount(async () => { if (!devUser.id) { @@ -44,19 +56,33 @@ } }); + const canSave = $derived.by(() => { + if (saving || !deckId) return false; + if (cardType === 'cloze') { + return text.trim().length > 0 && clusterIds.length > 0; + } + return front.trim().length > 0 && back.trim().length > 0; + }); + async function onSubmit(e: SubmitEvent) { e.preventDefault(); - if (!deckId || !front.trim() || !back.trim()) return; + if (!canSave) return; saving = true; try { - const card = await createCard({ - deck_id: deckId, - type: cardType, - fields: { front: front.trim(), back: back.trim() }, - }); - toasts.success( - cardType === 'basic-reverse' ? '2 Reviews initialisiert (front→back, back→front)' : 'Karte angelegt' - ); + const fields: Record = + cardType === 'cloze' + ? extra.trim() + ? { text: text.trim(), extra: extra.trim() } + : { text: text.trim() } + : { front: front.trim(), back: back.trim() }; + const card = await createCard({ deck_id: deckId, type: cardType, fields }); + const msg = + cardType === 'cloze' + ? `${clusterIds.length} Reviews initialisiert (1 pro Cluster)` + : cardType === 'basic-reverse' + ? '2 Reviews initialisiert (front→back, back→front)' + : 'Karte angelegt'; + toasts.success(msg); goto(`/decks/${card.deck_id}`); } catch (e) { toasts.error(`Anlegen fehlgeschlagen: ${(e as Error).message}`); @@ -93,54 +119,103 @@ > + -
+ {#if cardType === 'cloze'}
- {#if front.trim()} -
-
Vorschau
-
{@html frontHtml}
-
+

+ {'{{c1::Antwort}}'} definiert eine Lücke. Pro Cluster-ID + (c1, c2, …) entsteht ein eigenes Review. +

+ {#if text.trim() && clusterIds.length === 0} +

+ Mindestens ein {'{{cN::…}}'}-Cluster wird gebraucht. +

+ {:else if clusterIds.length > 0} +

+ {clusterIds.length} Cluster erkannt: c{clusterIds.join(', c')} → {clusterIds.length} + Reviews. +

{/if}
-
- - {#if back.trim()} -
-
Vorschau
-
{@html backHtml}
+ {#if clozePreviewHtml} +
+
+ Vorschau (c{clusterIds[0]} maskiert)
- {/if} +
{@html clozePreviewHtml}
+
+ {/if} + + + {:else} +
+
+ + {#if front.trim()} +
+
Vorschau
+
{@html frontHtml}
+
+ {/if} +
+ +
+ + {#if back.trim()} +
+
Vorschau
+
{@html backHtml}
+
+ {/if} +
-
+ {/if}