refactor(web): ClozeCardForm + MultipleChoiceCardForm extrahieren + Import-Bug fixen
- `ClozeCardForm.svelte`: Lückentext-Formular-Sektion aus cards/new herausgezogen - `MultipleChoiceCardForm.svelte`: MC-Options-Builder (inkl. 85 Zeilen MC-CSS) aus cards/new herausgezogen — cards/new: 1010 → 856 Zeilen - Import-Bug in 9 Dateien behoben: Python-Skript hatte apiErrorMessage-Import in mehrzeilige import-Blöcke eingefügt (Syntaxfehler) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c39bacc971
commit
595f1f9cb6
12 changed files with 286 additions and 168 deletions
185
apps/web/src/lib/components/MultipleChoiceCardForm.svelte
Normal file
185
apps/web/src/lib/components/MultipleChoiceCardForm.svelte
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
<script lang="ts">
|
||||
import { t } from '$lib/i18n/index.svelte.ts';
|
||||
|
||||
let {
|
||||
front = $bindable(),
|
||||
mcOptions = $bindable(),
|
||||
mcCorrectIdx = $bindable(),
|
||||
}: {
|
||||
front: string;
|
||||
mcOptions: string[];
|
||||
mcCorrectIdx: number;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<label class="field">
|
||||
<span class="field-label">{t('card_new.front_label')}</span>
|
||||
<textarea
|
||||
bind:value={front}
|
||||
required
|
||||
rows="4"
|
||||
placeholder={t('card_new.front_placeholder')}
|
||||
class="input mono"
|
||||
></textarea>
|
||||
</label>
|
||||
<div class="field">
|
||||
<span class="field-label">Antwortoptionen</span>
|
||||
<span class="field-hint">Markiere die richtige Antwort. Leere Optionen werden ignoriert — KI ergänzt fehlende Distractors automatisch.</span>
|
||||
<div class="mc-options">
|
||||
{#each mcOptions as opt, i}
|
||||
{@const letter = ['A', 'B', 'C', 'D'][i]}
|
||||
{@const isCorrect = mcCorrectIdx === i}
|
||||
<div class="mc-option" class:mc-option-correct={isCorrect}>
|
||||
<button
|
||||
type="button"
|
||||
class="mc-radio"
|
||||
class:mc-radio-selected={isCorrect}
|
||||
onclick={() => { mcCorrectIdx = i; }}
|
||||
aria-label="Option {letter} als richtig markieren"
|
||||
aria-pressed={isCorrect}
|
||||
>
|
||||
{#if isCorrect}
|
||||
<svg width="10" height="10" viewBox="0 0 10 10" fill="currentColor">
|
||||
<circle cx="5" cy="5" r="3.5" />
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
<span class="mc-letter" class:mc-letter-correct={isCorrect}>{letter}</span>
|
||||
<input
|
||||
type="text"
|
||||
bind:value={mcOptions[i]}
|
||||
placeholder="Option {letter}"
|
||||
class="mc-input"
|
||||
/>
|
||||
{#if isCorrect && opt.trim()}
|
||||
<span class="mc-badge">✓ Richtig</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--color-foreground));
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.field-hint {
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border-radius: 0.5rem;
|
||||
background: hsl(var(--color-surface));
|
||||
color: hsl(var(--color-foreground));
|
||||
padding: 0.5rem 0.625rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
transition: border-color 0.12s, box-shadow 0.12s;
|
||||
resize: vertical;
|
||||
}
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: hsl(var(--color-primary) / 0.6);
|
||||
box-shadow: 0 0 0 3px hsl(var(--color-primary) / 0.1);
|
||||
}
|
||||
.input.mono { font-family: ui-monospace, 'Cascadia Code', monospace; }
|
||||
|
||||
.mc-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.mc-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.625rem;
|
||||
padding: 0.5rem 0.625rem;
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
border-radius: 0.5rem;
|
||||
background: hsl(var(--color-surface));
|
||||
transition: border-color 0.12s, background-color 0.12s;
|
||||
}
|
||||
.mc-option-correct {
|
||||
border-color: hsl(var(--color-success) / 0.5);
|
||||
background: hsl(var(--color-success) / 0.06);
|
||||
}
|
||||
|
||||
.mc-radio {
|
||||
flex-shrink: 0;
|
||||
width: 1.125rem;
|
||||
height: 1.125rem;
|
||||
border-radius: 50%;
|
||||
border: 2px solid hsl(var(--color-border));
|
||||
background: hsl(var(--color-surface));
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: border-color 0.12s;
|
||||
padding: 0;
|
||||
color: hsl(var(--color-success));
|
||||
}
|
||||
.mc-radio:hover { border-color: hsl(var(--color-primary) / 0.6); }
|
||||
.mc-radio-selected {
|
||||
border-color: hsl(var(--color-success));
|
||||
background: hsl(var(--color-success) / 0.12);
|
||||
}
|
||||
|
||||
.mc-letter {
|
||||
flex-shrink: 0;
|
||||
width: 1.375rem;
|
||||
height: 1.375rem;
|
||||
border-radius: 0.25rem;
|
||||
background: hsl(var(--color-surface));
|
||||
border: 1px solid hsl(var(--color-border));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 700;
|
||||
color: hsl(var(--color-muted-foreground));
|
||||
}
|
||||
.mc-letter-correct {
|
||||
background: hsl(var(--color-success) / 0.15);
|
||||
border-color: hsl(var(--color-success) / 0.4);
|
||||
color: hsl(var(--color-success));
|
||||
}
|
||||
|
||||
.mc-input {
|
||||
flex: 1;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: hsl(var(--color-foreground));
|
||||
font: inherit;
|
||||
font-size: 0.875rem;
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
.mc-input:focus { outline: none; }
|
||||
.mc-input::placeholder { color: hsl(var(--color-muted-foreground)); }
|
||||
|
||||
.mc-badge {
|
||||
flex-shrink: 0;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--color-success));
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue