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
87
apps/web/src/lib/components/ClozeCardForm.svelte
Normal file
87
apps/web/src/lib/components/ClozeCardForm.svelte
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { t } from '$lib/i18n/index.svelte.ts';
|
||||||
|
|
||||||
|
let {
|
||||||
|
text = $bindable(),
|
||||||
|
extra = $bindable(),
|
||||||
|
clusterIds,
|
||||||
|
}: {
|
||||||
|
text: string;
|
||||||
|
extra: string;
|
||||||
|
clusterIds: string[];
|
||||||
|
} = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<label class="field">
|
||||||
|
<span class="field-label">{t('card_new.cloze_text_label')}</span>
|
||||||
|
<textarea
|
||||||
|
bind:value={text}
|
||||||
|
required
|
||||||
|
rows="6"
|
||||||
|
placeholder={t('card_new.cloze_text_placeholder')}
|
||||||
|
class="input mono"
|
||||||
|
></textarea>
|
||||||
|
<span class="field-hint">{t('card_new.cloze_help')}</span>
|
||||||
|
{#if text.trim() && clusterIds.length === 0}
|
||||||
|
<span class="field-error">{t('card_new.cloze_no_clusters')}</span>
|
||||||
|
{:else if clusterIds.length > 0}
|
||||||
|
<span class="field-hint">
|
||||||
|
{t('card_new.cloze_clusters_detected', { n: clusterIds.length, ids: clusterIds.join(', c') })}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</label>
|
||||||
|
<label class="field">
|
||||||
|
<span class="field-label">{t('card_new.cloze_extra_label')}</span>
|
||||||
|
<textarea
|
||||||
|
bind:value={extra}
|
||||||
|
rows="3"
|
||||||
|
placeholder={t('card_new.cloze_extra_placeholder')}
|
||||||
|
class="input mono"
|
||||||
|
></textarea>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field-error {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: hsl(var(--color-error));
|
||||||
|
}
|
||||||
|
|
||||||
|
.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; }
|
||||||
|
</style>
|
||||||
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>
|
||||||
|
|
@ -2,12 +2,12 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
import { apiErrorMessage } from '$lib/api/error.ts';
|
|
||||||
hideDiscussion,
|
hideDiscussion,
|
||||||
listDiscussions,
|
listDiscussions,
|
||||||
postDiscussion,
|
postDiscussion,
|
||||||
type Discussion,
|
type Discussion,
|
||||||
} from '$lib/api/marketplace.ts';
|
} from '$lib/api/marketplace.ts';
|
||||||
|
import { apiErrorMessage } from '$lib/api/error.ts';
|
||||||
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
||||||
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import type { Card, Deck } from '@cards/domain';
|
import type { Card, Deck } from '@cards/domain';
|
||||||
import {
|
import {
|
||||||
import { apiErrorMessage } from '$lib/api/error.ts';
|
|
||||||
getMyAuthorProfile,
|
getMyAuthorProfile,
|
||||||
getMarketplaceDeck,
|
getMarketplaceDeck,
|
||||||
initMarketplaceDeck,
|
initMarketplaceDeck,
|
||||||
publishMarketplaceVersion,
|
publishMarketplaceVersion,
|
||||||
type MarketplaceAuthor,
|
type MarketplaceAuthor,
|
||||||
} from '$lib/api/marketplace.ts';
|
} from '$lib/api/marketplace.ts';
|
||||||
|
import { apiErrorMessage } from '$lib/api/error.ts';
|
||||||
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
import { apiErrorMessage } from '$lib/api/error.ts';
|
|
||||||
closePullRequest,
|
closePullRequest,
|
||||||
listPullRequests,
|
listPullRequests,
|
||||||
mergePullRequest,
|
mergePullRequest,
|
||||||
rejectPullRequest,
|
rejectPullRequest,
|
||||||
type PullRequest,
|
type PullRequest,
|
||||||
} from '$lib/api/marketplace.ts';
|
} from '$lib/api/marketplace.ts';
|
||||||
|
import { apiErrorMessage } from '$lib/api/error.ts';
|
||||||
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
||||||
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import {
|
import {
|
||||||
import { apiErrorMessage } from '$lib/api/error.ts';
|
|
||||||
extractClusterIds,
|
extractClusterIds,
|
||||||
maskRegionCount,
|
maskRegionCount,
|
||||||
renderClozePrompt,
|
renderClozePrompt,
|
||||||
type Card,
|
type Card,
|
||||||
type CardType,
|
type CardType,
|
||||||
} from '@cards/domain';
|
} from '@cards/domain';
|
||||||
|
import { apiErrorMessage } from '$lib/api/error.ts';
|
||||||
import { getCard, updateCard, deleteCard } from '$lib/api/cards.ts';
|
import { getCard, updateCard, deleteCard } from '$lib/api/cards.ts';
|
||||||
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
||||||
import { renderMarkdown } from '$lib/markdown.ts';
|
import { renderMarkdown } from '$lib/markdown.ts';
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import {
|
import {
|
||||||
import { apiErrorMessage } from '$lib/api/error.ts';
|
|
||||||
extractClusterIds,
|
extractClusterIds,
|
||||||
maskRegionCount,
|
maskRegionCount,
|
||||||
renderClozePrompt,
|
renderClozePrompt,
|
||||||
type CardType,
|
type CardType,
|
||||||
} from '@cards/domain';
|
} from '@cards/domain';
|
||||||
import { createCard } from '$lib/api/cards.ts';
|
import { createCard } from '$lib/api/cards.ts';
|
||||||
|
import { apiErrorMessage } from '$lib/api/error.ts';
|
||||||
import { listDecks, getDeck } from '$lib/api/decks.ts';
|
import { listDecks, getDeck } from '$lib/api/decks.ts';
|
||||||
import { API_BASE } from '$lib/api/client.ts';
|
import { API_BASE } from '$lib/api/client.ts';
|
||||||
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
||||||
|
|
@ -17,6 +17,8 @@
|
||||||
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
||||||
import { t } from '$lib/i18n/index.svelte.ts';
|
import { t } from '$lib/i18n/index.svelte.ts';
|
||||||
import ImageOcclusionEditor from '$lib/components/ImageOcclusionEditor.svelte';
|
import ImageOcclusionEditor from '$lib/components/ImageOcclusionEditor.svelte';
|
||||||
|
import ClozeCardForm from '$lib/components/ClozeCardForm.svelte';
|
||||||
|
import MultipleChoiceCardForm from '$lib/components/MultipleChoiceCardForm.svelte';
|
||||||
import CardSurface from '$lib/components/CardSurface.svelte';
|
import CardSurface from '$lib/components/CardSurface.svelte';
|
||||||
|
|
||||||
type DeckLite = { id: string; name: string };
|
type DeckLite = { id: string; name: string };
|
||||||
|
|
@ -193,81 +195,10 @@
|
||||||
<ImageOcclusionEditor bind:imageRef bind:maskRegionsJson />
|
<ImageOcclusionEditor bind:imageRef bind:maskRegionsJson />
|
||||||
|
|
||||||
{:else if cardType === 'cloze'}
|
{:else if cardType === 'cloze'}
|
||||||
<label class="field">
|
<ClozeCardForm bind:text bind:extra {clusterIds} />
|
||||||
<span class="field-label">{t('card_new.cloze_text_label')}</span>
|
|
||||||
<textarea
|
|
||||||
bind:value={text}
|
|
||||||
required
|
|
||||||
rows="6"
|
|
||||||
placeholder={t('card_new.cloze_text_placeholder')}
|
|
||||||
class="input mono"
|
|
||||||
></textarea>
|
|
||||||
<span class="field-hint">{t('card_new.cloze_help')}</span>
|
|
||||||
{#if text.trim() && clusterIds.length === 0}
|
|
||||||
<span class="field-error">{t('card_new.cloze_no_clusters')}</span>
|
|
||||||
{:else if clusterIds.length > 0}
|
|
||||||
<span class="field-hint">
|
|
||||||
{t('card_new.cloze_clusters_detected', { n: clusterIds.length, ids: clusterIds.join(', c') })}
|
|
||||||
</span>
|
|
||||||
{/if}
|
|
||||||
</label>
|
|
||||||
<label class="field">
|
|
||||||
<span class="field-label">{t('card_new.cloze_extra_label')}</span>
|
|
||||||
<textarea
|
|
||||||
bind:value={extra}
|
|
||||||
rows="3"
|
|
||||||
placeholder={t('card_new.cloze_extra_placeholder')}
|
|
||||||
class="input mono"
|
|
||||||
></textarea>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
{:else if cardType === 'multiple-choice'}
|
{:else if cardType === 'multiple-choice'}
|
||||||
<label class="field">
|
<MultipleChoiceCardForm bind:front bind:mcOptions bind:mcCorrectIdx />
|
||||||
<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>
|
|
||||||
|
|
||||||
{:else if cardType === 'typing'}
|
{:else if cardType === 'typing'}
|
||||||
<div class="grid-2">
|
<div class="grid-2">
|
||||||
|
|
@ -629,91 +560,6 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* MC option builder */
|
|
||||||
.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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
.actions {
|
.actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
import { apiErrorMessage } from '$lib/api/error.ts';
|
|
||||||
forkDeck,
|
forkDeck,
|
||||||
getMarketplaceDeck,
|
getMarketplaceDeck,
|
||||||
getMarketplaceVersion,
|
getMarketplaceVersion,
|
||||||
|
|
@ -20,6 +19,7 @@
|
||||||
type MarketplaceVersion,
|
type MarketplaceVersion,
|
||||||
type MarketplaceVersionCard,
|
type MarketplaceVersionCard,
|
||||||
} from '$lib/api/marketplace.ts';
|
} from '$lib/api/marketplace.ts';
|
||||||
|
import { apiErrorMessage } from '$lib/api/error.ts';
|
||||||
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
||||||
import AuthorBadge from '$lib/components/marketplace/AuthorBadge.svelte';
|
import AuthorBadge from '$lib/components/marketplace/AuthorBadge.svelte';
|
||||||
import DiscussionThread from '$lib/components/marketplace/DiscussionThread.svelte';
|
import DiscussionThread from '$lib/components/marketplace/DiscussionThread.svelte';
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
import { apiErrorMessage } from '$lib/api/error.ts';
|
|
||||||
browseDecks,
|
browseDecks,
|
||||||
getExplore,
|
getExplore,
|
||||||
type DeckListEntry,
|
type DeckListEntry,
|
||||||
} from '$lib/api/marketplace.ts';
|
} from '$lib/api/marketplace.ts';
|
||||||
|
import { apiErrorMessage } from '$lib/api/error.ts';
|
||||||
import DeckListGrid from '$lib/components/marketplace/DeckListGrid.svelte';
|
import DeckListGrid from '$lib/components/marketplace/DeckListGrid.svelte';
|
||||||
import EmptyState from '$lib/components/marketplace/EmptyState.svelte';
|
import EmptyState from '$lib/components/marketplace/EmptyState.svelte';
|
||||||
import SkeletonGrid from '$lib/components/marketplace/SkeletonGrid.svelte';
|
import SkeletonGrid from '$lib/components/marketplace/SkeletonGrid.svelte';
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import {
|
import {
|
||||||
import { apiErrorMessage } from '$lib/api/error.ts';
|
|
||||||
browseDecks,
|
browseDecks,
|
||||||
getMyAuthorProfile,
|
getMyAuthorProfile,
|
||||||
upsertMyAuthorProfile,
|
upsertMyAuthorProfile,
|
||||||
type DeckListEntry,
|
type DeckListEntry,
|
||||||
type MarketplaceAuthor,
|
type MarketplaceAuthor,
|
||||||
} from '$lib/api/marketplace.ts';
|
} from '$lib/api/marketplace.ts';
|
||||||
|
import { apiErrorMessage } from '$lib/api/error.ts';
|
||||||
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
||||||
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import {
|
import {
|
||||||
import { apiErrorMessage } from '$lib/api/error.ts';
|
|
||||||
clusterIdForSubIndex,
|
clusterIdForSubIndex,
|
||||||
maskForSubIndex,
|
maskForSubIndex,
|
||||||
renderClozePrompt,
|
renderClozePrompt,
|
||||||
renderClozeAnswer,
|
renderClozeAnswer,
|
||||||
type Rating,
|
type Rating,
|
||||||
} from '@cards/domain';
|
} from '@cards/domain';
|
||||||
|
import { apiErrorMessage } from '$lib/api/error.ts';
|
||||||
import { getDeck } from '$lib/api/decks.ts';
|
import { getDeck } from '$lib/api/decks.ts';
|
||||||
import { listDueReviews, gradeReview, type DueReview } from '$lib/api/reviews.ts';
|
import { listDueReviews, gradeReview, type DueReview } from '$lib/api/reviews.ts';
|
||||||
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
import { apiErrorMessage } from '$lib/api/error.ts';
|
|
||||||
browseDecks,
|
browseDecks,
|
||||||
followAuthor,
|
followAuthor,
|
||||||
getAuthor,
|
getAuthor,
|
||||||
|
|
@ -13,6 +12,7 @@
|
||||||
type DeckListEntry,
|
type DeckListEntry,
|
||||||
type MarketplaceAuthor,
|
type MarketplaceAuthor,
|
||||||
} from '$lib/api/marketplace.ts';
|
} from '$lib/api/marketplace.ts';
|
||||||
|
import { apiErrorMessage } from '$lib/api/error.ts';
|
||||||
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
import { devUser } from '$lib/auth/dev-stub.svelte.ts';
|
||||||
import DeckListGrid from '$lib/components/marketplace/DeckListGrid.svelte';
|
import DeckListGrid from '$lib/components/marketplace/DeckListGrid.svelte';
|
||||||
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
import { toasts } from '$lib/stores/toasts.svelte.ts';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue