- Move the guard to scripts/check-tokens.sh; regex now catches hex + rgb()/hsl() (was hex-only) across ALL src/app components (was three ui/layout dirs). `token-ok` marker suppresses justified false positives; px stays out of scope (documented in the script). - Zero exclusions: debug-state's dark code-editor palette moves to --app-devpanel-* tokens in styles.scss (the one exempt file), dropping its --exclude hole. - Tokenize remaining hits: site-footer border via color-mix; three brief border widths via --rhc-border-width-* (new --rhc-border-width-lg: 3px). Verified: planted violation fails the guard; GREEN + test-storybook:ci. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
115 lines
5.4 KiB
TypeScript
115 lines
5.4 KiB
TypeScript
import { Component, computed, input, signal } from '@angular/core';
|
|
import { NgTemplateOutlet } from '@angular/common';
|
|
import { HeadingComponent } from '@shared/ui/heading/heading.component';
|
|
import { ButtonComponent } from '@shared/ui/button/button.component';
|
|
import { PlaceholderChipComponent } from '@shared/ui/placeholder-chip/placeholder-chip.component';
|
|
import { Paragraph } from '@shared/kernel/rich-text';
|
|
import { Brief, LetterBlock } from '@brief/domain/brief';
|
|
import { Diagnostic } from '@brief/domain/placeholders';
|
|
|
|
/** A run of consecutive lines to render together: a list (bullet/number) or a single plain line. */
|
|
type PreviewSegment = { readonly list: 'bullet' | 'number' | null; readonly items: readonly Paragraph[] };
|
|
|
|
function groupParagraphs(paras: readonly Paragraph[]): PreviewSegment[] {
|
|
const out: { list: 'bullet' | 'number' | null; items: Paragraph[] }[] = [];
|
|
for (const para of paras) {
|
|
const kind = para.list ?? null;
|
|
const last = out[out.length - 1];
|
|
if (kind && last && last.list === kind) last.items.push(para);
|
|
else out.push({ list: kind, items: [para] });
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Illustrative values for the "Voorbeeld" toggle — what send resolves server-side.
|
|
const SAMPLE_VALUES: Record<string, string> = {
|
|
naam_zorgverlener: 'J. Jansen',
|
|
big_nummer: '12345678901',
|
|
};
|
|
|
|
/** Organism: read-only rendering of the letter as the approver/recipient sees it.
|
|
Placeholders show as labelled chips (values are resolved server-side only at send);
|
|
a chip flagged by the linter shows its error/warning state. The "Voorbeeld" toggle
|
|
fills auto-resolvable placeholders with sample values to preview the sent result. */
|
|
@Component({
|
|
selector: 'app-letter-preview',
|
|
imports: [NgTemplateOutlet, HeadingComponent, ButtonComponent, PlaceholderChipComponent],
|
|
styles: [`
|
|
:host{display:block}
|
|
.toolbar{display:flex;justify-content:flex-end;margin-block-end:var(--rhc-space-max-sm)}
|
|
.letter{background:var(--rhc-color-wit);border:var(--rhc-border-width-sm) solid var(--rhc-color-border-default);border-radius:var(--rhc-border-radius-md);padding:var(--rhc-space-max-2xl)}
|
|
section{margin-block-end:var(--rhc-space-max-xl)}
|
|
p{margin:0 0 var(--rhc-space-max-sm)}
|
|
ul,ol{margin:0 0 var(--rhc-space-max-sm);padding-inline-start:1.4em}
|
|
`],
|
|
template: `
|
|
<ng-template #line let-nodes>
|
|
@for (node of nodes; track $index) {
|
|
@switch (node.type) {
|
|
@case ('text') { <span>{{ node.text }}</span> }
|
|
@case ('lineBreak') { <br> }
|
|
@case ('placeholder') {
|
|
@if (showSample() && autoFor(node.key)) {
|
|
<span>{{ sampleFor(node.key) }}</span>
|
|
} @else {
|
|
<app-placeholder-chip [label]="labelFor(node.key)" [autoResolvable]="autoFor(node.key)" [state]="stateFor(node.key)" />
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</ng-template>
|
|
|
|
<div class="toolbar">
|
|
<app-button variant="subtle" (click)="showSample.set(!showSample())" [attr.aria-pressed]="showSample()">
|
|
{{ showSample() ? hideSampleLabel() : showSampleLabel() }}
|
|
</app-button>
|
|
</div>
|
|
|
|
<div class="letter">
|
|
@for (section of brief().sections; track section.sectionKey) {
|
|
<section>
|
|
<app-heading [level]="3">{{ section.title }}</app-heading>
|
|
@for (block of section.blocks; track block.blockId) {
|
|
@for (seg of segmentsOf(block); track $index) {
|
|
@if (seg.list === 'bullet') {
|
|
<ul>@for (para of seg.items; track $index) { <li><ng-container [ngTemplateOutlet]="line" [ngTemplateOutletContext]="{ $implicit: para.nodes }" /></li>}</ul>
|
|
} @else if (seg.list === 'number') {
|
|
<ol>@for (para of seg.items; track $index) { <li><ng-container [ngTemplateOutlet]="line" [ngTemplateOutletContext]="{ $implicit: para.nodes }" /></li>}</ol>
|
|
} @else {
|
|
<p><ng-container [ngTemplateOutlet]="line" [ngTemplateOutletContext]="{ $implicit: seg.items[0].nodes }" /></p>
|
|
}
|
|
}
|
|
}
|
|
</section>
|
|
}
|
|
</div>
|
|
`,
|
|
})
|
|
export class LetterPreviewComponent {
|
|
brief = input.required<Brief>();
|
|
diagnostics = input<readonly Diagnostic[]>([]);
|
|
|
|
showSampleLabel = input($localize`:@@brief.preview.showSample:Voorbeeld met testwaarden`);
|
|
hideSampleLabel = input($localize`:@@brief.preview.hideSample:Testwaarden verbergen`);
|
|
|
|
protected showSample = signal(false);
|
|
private today = new Date().toLocaleDateString('nl-NL', { day: 'numeric', month: 'long', year: 'numeric' });
|
|
|
|
private defs = computed(() => new Map(this.brief().placeholders.map((p) => [p.key, p])));
|
|
private worst = computed(() => {
|
|
const m = new Map<string, 'error' | 'warning'>();
|
|
for (const d of this.diagnostics()) {
|
|
if (!d.placeholderKey) continue;
|
|
if (d.severity === 'error') m.set(d.placeholderKey, 'error');
|
|
else if (!m.has(d.placeholderKey)) m.set(d.placeholderKey, 'warning');
|
|
}
|
|
return m;
|
|
});
|
|
|
|
protected segmentsOf = (block: LetterBlock) => groupParagraphs(block.content.paragraphs);
|
|
protected labelFor = (key: string) => this.defs().get(key)?.label ?? key;
|
|
protected autoFor = (key: string) => this.defs().get(key)?.autoResolvable ?? false;
|
|
protected stateFor = (key: string): 'ok' | 'warning' | 'error' => this.worst().get(key) ?? 'ok';
|
|
protected sampleFor = (key: string) => SAMPLE_VALUES[key] ?? (key === 'datum' ? this.today : this.labelFor(key));
|
|
}
|