Five article fixes were five clicks, five confetti bursts and five undo steps. "Accept all Tidy-up (5)" makes them one of each. The single undo decided the implementation: every replacement goes into one Tiptap chain, which applies as one transaction and so undoes as one history event. That only works if the spans can't move under each other, so the plan resolves every span against the document as it stands and applies them last-first. Three outcomes rather than one, because a batch that quietly dropped a card would be reporting edits it never made: a span she already fixed herself is settled without an edit (what a single Accept does too), and a card quoting the same words as one already taken is left on screen, since findRange would resolve both to the same place. The control sits on the first card of its kind — the rail can't carry a category header, its cards are anchored to their own sentences — and only when the category has company. It is outlined rather than filled: it acts on cards she can't see from where she's standing.
41 lines
2.2 KiB
TypeScript
41 lines
2.2 KiB
TypeScript
import type { SuggestionType } from '../../api/client'
|
|
import type { Pack } from '../../i18n'
|
|
|
|
// Per-type accent color + human label, mirroring the design tokens. Shared by the
|
|
// inline hover SuggestionCard and the margin SuggestionRail so a given suggestion
|
|
// type reads the same color/name wherever it surfaces.
|
|
export const TYPE_META: Record<SuggestionType, { color: string; label: string }> = {
|
|
grammar: { color: 'var(--color-mint)', label: 'Grammar' },
|
|
phrasing: { color: 'var(--color-peach)', label: 'Phrasing' },
|
|
idiom: { color: 'var(--color-lavender)', label: 'Idiom' },
|
|
clarity: { color: 'var(--color-sky)', label: 'Clarity' },
|
|
// The label here is only the fallback (and what a screen reader gets if the
|
|
// pack hasn't arrived yet) — see typeLabel.
|
|
translate: { color: 'var(--color-jade)', label: 'Translate' },
|
|
voice: { color: 'var(--color-honey)', label: 'Voice' },
|
|
collocation: { color: 'var(--color-blossom)', label: 'Word pairing' },
|
|
mechanics: { color: 'var(--color-sage)', label: 'Tidy-up' },
|
|
}
|
|
|
|
// typeLabel is what the pill says. It exists for exactly one type: a translation
|
|
// card names itself in the writer's own language first, because that language is
|
|
// its entire subject. Every other type keeps its English name — those are the
|
|
// terms she is learning, and she is learning them in English.
|
|
//
|
|
// Taking the pack rather than reading the module singleton keeps the label
|
|
// reactive: the pair language isn't known until /api/me answers, and a card
|
|
// rendered before then must relabel itself when it does.
|
|
export function typeLabel(type: SuggestionType, pack: Pack): string {
|
|
return type === 'translate' ? pack.editor.translateLabel : TYPE_META[type].label
|
|
}
|
|
|
|
// The label on the accept-a-whole-category button. It stays English even on a
|
|
// translation card, whose pill is bilingual: the pill names the kind of advice
|
|
// she is reading, while this names an action taken over the rest of the queue,
|
|
// and every other word in the action row — Accept, Dismiss, Ask Petal — is
|
|
// English too. A control that switched languages between cards would read as a
|
|
// different control.
|
|
export function batchLabel(type: SuggestionType, count: number): string {
|
|
return `Accept all ${TYPE_META[type].label} (${count})`
|
|
}
|