v3 : interface d'une version retenue — « Contre » et « Retirer mon vote », jamais « Pour »
Sous l'article, une version retenue montre « Contre » et le retrait, avec la phrase qui dit pourquoi. Dans le panier, elle se choisit en « contre » ou en retrait ; « Tout pour » la saute et le dit, « Tout contre » la prend ; un « pour » gardé d'avant l'objection quitte le panier (basket.kept). Les phrases qui disaient « ne se vote plus » disent la règle d'Yvv, en fr, en et es. C28 ajusté. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,11 @@
|
||||
// section entière, ou un article avec son titre, ou séparément »): choices
|
||||
// article by article, « tout pour » / « tout contre » for a section or the whole
|
||||
// document — never for an article whose versions compete —, then one gesture.
|
||||
// A version held back by an objection is shown, with nothing to choose.
|
||||
// A version held back by an objection takes against or a withdrawal, never a
|
||||
// « for » (Yvv, 2026-11-01: « Retirer ou "contre" »): « tout pour » skips it
|
||||
// and says so, « tout contre » takes it.
|
||||
import { onMount } from 'svelte';
|
||||
import { clearScope, counts, fill, lines, sections, votableIds, waiting, type Section, type Slot } from '$lib/basket';
|
||||
import { clearScope, counts, fill, heldIn, kept, lines, sections, votableIds, waiting, type Section, type Slot } from '$lib/basket';
|
||||
import type { DocState } from '$lib/doc.svelte';
|
||||
import { i18n, t } from '$lib/i18n/index.svelte';
|
||||
import { moduleName, relationOf, sectionLabel, titleOf } from '$lib/labels';
|
||||
@@ -38,12 +40,14 @@
|
||||
const slots = $derived(all.flatMap((s) => s.slots));
|
||||
const known = $derived(votableIds(all));
|
||||
const toSend = $derived(lines(basket.picks, known));
|
||||
const tally = $derived(counts(Object.fromEntries(Object.entries(basket.picks).filter(([id]) => known.has(id)))));
|
||||
const tally = $derived(counts(kept(basket.picks, known)));
|
||||
const left = $derived(waiting(slots, basket.picks));
|
||||
const open = $derived(!!access?.open);
|
||||
let busy = $state(false);
|
||||
let error = $state('');
|
||||
let list: HTMLElement | undefined = $state();
|
||||
/** After « tout pour »: where, and how many versions held back by an objection it left aside. */
|
||||
let skipped = $state<{ key: string | null; n: number } | null>(null);
|
||||
|
||||
onMount(() => {
|
||||
if (section) list?.querySelector(`[data-section="${CSS.escape(section)}"]`)?.scrollIntoView({ block: 'start' });
|
||||
@@ -69,6 +73,14 @@
|
||||
|
||||
const scope = (key: string | null): Slot[] => (key === '*' ? slots : (all.find((s: Section) => s.key === key)?.slots ?? []));
|
||||
|
||||
/** « Tout pour » / « tout contre » / « vider » over a scope; « tout pour » says what it skipped. */
|
||||
function gesture(key: string | null, what: 'for' | 'against' | 'clear') {
|
||||
const within = scope(key);
|
||||
basket.set(what === 'clear' ? clearScope(within, basket.picks) : fill(within, what, basket.picks));
|
||||
const n = what === 'for' ? heldIn(within).length : 0;
|
||||
skipped = n ? { key, n } : null;
|
||||
}
|
||||
|
||||
async function send() {
|
||||
error = '';
|
||||
if (!toSend.length) return;
|
||||
@@ -91,12 +103,18 @@
|
||||
|
||||
{#snippet bulk(key: string | null, label: string)}
|
||||
<div class="bulk" role="group" aria-label={label}>
|
||||
<button class="btn small" type="button" disabled={!open} onclick={() => basket.set(fill(scope(key), 'for', basket.picks))}>{t('basket.for')}</button>
|
||||
<button class="btn small" type="button" disabled={!open} onclick={() => basket.set(fill(scope(key), 'against', basket.picks))}>{t('basket.against')}</button>
|
||||
<button class="btn quiet small" type="button" onclick={() => basket.set(clearScope(scope(key), basket.picks))}>{t('basket.clear')}</button>
|
||||
<button class="btn small" type="button" disabled={!open} onclick={() => gesture(key, 'for')}>{t('basket.for')}</button>
|
||||
<button class="btn small" type="button" disabled={!open} onclick={() => gesture(key, 'against')}>{t('basket.against')}</button>
|
||||
<button class="btn quiet small" type="button" onclick={() => gesture(key, 'clear')}>{t('basket.clear')}</button>
|
||||
</div>
|
||||
{/snippet}
|
||||
|
||||
{#snippet skippedNote(key: string | null)}
|
||||
{#if skipped && skipped.key === key}
|
||||
<p class="note" role="status">{i18n.plural('basket.held.skipped', skipped.n)}</p>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<section class="basket" aria-labelledby="basket-title">
|
||||
<h3 id="basket-title">{t('basket.title')}</h3>
|
||||
<p class="muted">{t('basket.intro')}</p>
|
||||
@@ -108,6 +126,7 @@
|
||||
<span class="eyebrow">{t('basket.doc')}</span>
|
||||
{@render bulk('*', t('basket.doc'))}
|
||||
</div>
|
||||
{@render skippedNote('*')}
|
||||
{#if left.length}
|
||||
<p class="note" role="status">{i18n.plural('basket.waiting', left.length)}</p>
|
||||
{/if}
|
||||
@@ -121,6 +140,7 @@
|
||||
<h4 class="eyebrow">{label}</h4>
|
||||
{@render bulk(s.key, label)}
|
||||
</header>
|
||||
{@render skippedNote(s.key)}
|
||||
{/if}
|
||||
<ul>
|
||||
{#each s.slots as slot (slot.unit.id)}
|
||||
@@ -152,9 +172,20 @@
|
||||
<div class="version held">
|
||||
<span class="about">
|
||||
<span class="label">{labelOf(v, slot)}</span>
|
||||
<span class="faint small">{t('tally.short', { for: v.tally.for, against: v.tally.against })}</span>
|
||||
<span class="faint small">
|
||||
{t('tally.short', { for: v.tally.for, against: v.tally.against })}
|
||||
{#if v.myVote && !v.myVoteStale} · {t('basket.mine', { choice: t(`vote.choice.${v.myVote}`) })}{/if}
|
||||
</span>
|
||||
<span class="held-note">{t('basket.held')}</span>
|
||||
</span>
|
||||
<span class="held-note">{t('basket.held')}</span>
|
||||
<ChoicePicker
|
||||
held
|
||||
value={basket.picks[v.id]}
|
||||
voted={!!v.myVote}
|
||||
label={t('basket.pick', { title: slot.versions.length || slot.held.length > 1 ? `${title} — ${labelOf(v, slot)}` : title })}
|
||||
disabled={!open}
|
||||
onpick={(c) => basket.pick(v.id, c)}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</li>
|
||||
@@ -297,6 +328,8 @@
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.held-note {
|
||||
justify-self: start;
|
||||
margin-top: 0.2rem;
|
||||
padding: 0.25rem 0.7rem;
|
||||
border-radius: var(--r-pill);
|
||||
background: var(--ochre-wash);
|
||||
|
||||
Reference in New Issue
Block a user