v3 : interface des objections, des coquilles validées et de la messagerie ; contrôles C28–C31
Une version retenue par une objection le dit sous l'article, à chacun selon son rôle : la personne qui vote voit qu'elle ne se vote plus (les votes faits restent) ; l'auteur, quoi faire (« Modifier l'article visé » pour une redite, « Reformuler » pour une contradiction) ; la personne qui objecte, lever ou réitérer avant l'échéance. Le panier la montre sans choix et ne l'envoie jamais. « Détails » : faire une objection (redit / contredit), les objections et où elles en sont, les coquilles proposées par d'autres avec leur diff resserré, valider / refuser / revenir, l'historique dit qui a validé. Une coquille se propose sur toute version ouverte ou en vigueur. Boîte « Messages » dans le menu, avec le nombre de non-lus (pastille sur le bouton du menu en mobile) : objet, texte, gestes, échéance ; un message traité n'a plus de geste. Répondre à une objection lit son message. Trilingue (fr, en, es). C6 suit la règle : la coquille d'un administrateur sur un texte du seed passe tout de suite ; C28–C31 couvrent le reste. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,10 +9,12 @@
|
||||
import UnitBlock from '$lib/components/UnitBlock.svelte';
|
||||
import VoteFold from '$lib/components/VoteFold.svelte';
|
||||
import WaveRule from '$lib/components/WaveRule.svelte';
|
||||
import { sections } from '$lib/basket';
|
||||
import { DocState, shown, type PanelState, type Tab, type View } from '$lib/doc.svelte';
|
||||
import { sections, votableIds } from '$lib/basket';
|
||||
import { DocState, panelSearch, shown, TABS, type PanelState, type View } from '$lib/doc.svelte';
|
||||
import { i18n, LANGS, t } from '$lib/i18n/index.svelte';
|
||||
import { answerPanel } from '$lib/objections';
|
||||
import { BasketState } from '$lib/picks.svelte';
|
||||
import { session } from '$lib/session.svelte';
|
||||
import type { Lang, Proposal, Unit } from '$lib/types';
|
||||
import type { Line } from '$lib/vote';
|
||||
import { articles, type Filter } from '$lib/versions';
|
||||
@@ -24,23 +26,30 @@
|
||||
$effect(() => {
|
||||
void doc.load(group, slug);
|
||||
});
|
||||
// The basket of this document, kept for the tab; versions no longer open to vote leave it.
|
||||
// The basket of this document, kept for the tab; versions no longer open to vote
|
||||
// (or held back by an objection) leave it.
|
||||
$effect(() => {
|
||||
const v = doc.view;
|
||||
if (!v) return;
|
||||
const known = new Set(sections(v.units).flatMap((s) => s.slots.flatMap((x) => [...x.versions, ...x.inertia].map((p) => p.id))));
|
||||
basket.load(v.doc.id, known);
|
||||
basket.load(v.doc.id, votableIds(sections(v.units)));
|
||||
});
|
||||
|
||||
// The panel and the unfolded vote live in the URL: a link (or a feedback report) reopens them.
|
||||
const TABS: Record<string, Tab> = { panier: 'basket', propositions: 'proposals', proposer: 'propose', details: 'details', reglages: 'settings' };
|
||||
const TAB_NAMES: Record<Tab, string> = { basket: 'panier', proposals: 'propositions', propose: 'proposer', details: 'details', settings: 'reglages' };
|
||||
let prefill = $state<PanelState['prefill']>(null);
|
||||
const panel = $derived.by((): PanelState | null => {
|
||||
const q = page.url.searchParams;
|
||||
const tab = TABS[q.get('onglet') ?? ''];
|
||||
if (!tab) return null;
|
||||
return { unitId: q.get('article'), proposalId: q.get('version'), tab, draftId: q.get('brouillon'), section: q.get('section'), prefill };
|
||||
return {
|
||||
unitId: q.get('article'),
|
||||
proposalId: q.get('version'),
|
||||
tab,
|
||||
draftId: q.get('brouillon'),
|
||||
section: q.get('section'),
|
||||
prefill,
|
||||
objectionId: q.get('objection'),
|
||||
edit: q.get('modifier') === '1',
|
||||
};
|
||||
});
|
||||
/** The version whose vote is unfolded under its article (`?vote=`; the old `onglet=vote` too). */
|
||||
const vote = $derived.by(() => {
|
||||
@@ -50,16 +59,7 @@
|
||||
|
||||
function go(nextPanel: PanelState | null, nextVote: string | null) {
|
||||
prefill = nextPanel?.prefill ?? null;
|
||||
const q = new URLSearchParams();
|
||||
if (nextVote) q.set('vote', nextVote);
|
||||
if (nextPanel) {
|
||||
q.set('onglet', TAB_NAMES[nextPanel.tab]);
|
||||
if (nextPanel.unitId) q.set('article', nextPanel.unitId);
|
||||
if (nextPanel.proposalId) q.set('version', nextPanel.proposalId);
|
||||
if (nextPanel.draftId) q.set('brouillon', nextPanel.draftId);
|
||||
if (nextPanel.section) q.set('section', nextPanel.section);
|
||||
}
|
||||
const search = q.toString();
|
||||
const search = panelSearch(nextPanel, nextVote);
|
||||
void goto(`${page.url.pathname}${search ? `?${search}` : ''}`, {
|
||||
replaceState: (!!panel && !!nextPanel) || (!nextPanel && !panel),
|
||||
noScroll: true,
|
||||
@@ -122,6 +122,7 @@
|
||||
onopen={(id) => go(panel, id)}
|
||||
onsettings={(id) => openPanel({ unitId: unit.id, proposalId: id, tab: 'settings' })}
|
||||
ondetails={(id) => openPanel({ unitId: unit.id, proposalId: id, tab: 'details' })}
|
||||
onanswer={(o) => openPanel(answerPanel(o, session.me?.id ?? null))}
|
||||
onsign={sign}
|
||||
/>
|
||||
{/snippet}
|
||||
|
||||
Reference in New Issue
Block a user