204 WP-NN/RB-NN comments named a closed ticket instead of the code they sit next to. git blame already records history and stays correct when code moves; the comment does not. This sweep removes the reference and keeps the sentence, across 95 files in apps/ and libs/ plus the behaviour-spec generator's header text. Eleven references stay: five story files justify an a11y disable per the README's rule, and one line in a11y.mdx documents that convention. Two sentences needed a rewrite, not a deletion, so the reference's meaning survives its removal. behaviour-spec.mdx is regenerated, not hand-edited. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
131 lines
5.2 KiB
TypeScript
131 lines
5.2 KiB
TypeScript
import { Component, computed, effect, inject } from '@angular/core';
|
|
import { PageShellComponent } from '@shared/layout/page-shell/page-shell.component';
|
|
import { AlertComponent } from '@shared/ui/alert/alert.component';
|
|
import { ButtonComponent } from '@shared/ui/button/button.component';
|
|
import { ASYNC } from '@shared/ui/async/async.component';
|
|
import { AccessStore } from '@shared/application/access.store';
|
|
import { OrgTemplateStore } from '@brief/application/org-template.store';
|
|
import { OrgTemplateEditorComponent } from '@brief/ui/org-template-editor/org-template-editor.component';
|
|
|
|
/** Page: thin container for the admin org-template editor. Deny-by-default
|
|
capability gate (`orgtemplate:edit`) — a denial alert for non-admins, the editor
|
|
for admins. Loads once the capability resolves; wires store commands to the organism. */
|
|
@Component({
|
|
selector: 'app-org-template-page',
|
|
imports: [
|
|
PageShellComponent,
|
|
AlertComponent,
|
|
ButtonComponent,
|
|
...ASYNC,
|
|
OrgTemplateEditorComponent,
|
|
],
|
|
styles: [
|
|
`
|
|
.save {
|
|
color: var(--rhc-color-foreground-subtle);
|
|
font-size: 0.9em;
|
|
}
|
|
`,
|
|
],
|
|
template: `
|
|
<app-page-shell [heading]="heading" [intro]="intro" backLink="/brief">
|
|
@if (store.lastError(); as err) {
|
|
<app-alert type="error">{{ err }}</app-alert>
|
|
}
|
|
|
|
@if (!access.ready()) {
|
|
<!-- wait for /me before deciding — avoids flashing the denial to an admin -->
|
|
} @else if (!canEdit()) {
|
|
<app-alert type="error">{{ deniedText }}</app-alert>
|
|
} @else {
|
|
<app-async [data]="store.remoteData()">
|
|
<ng-template appAsyncError>
|
|
<app-alert type="error">{{ failedText }}</app-alert>
|
|
<app-button variant="secondary" (click)="reload()">{{ retryText }}</app-button>
|
|
</ng-template>
|
|
<ng-template appAsyncLoaded>
|
|
@if (store.draft(); as draft) {
|
|
<app-org-template-editor
|
|
[draft]="draft"
|
|
[logoUrl]="store.logoUrl()"
|
|
[uploadState]="store.uploadState()"
|
|
[subOrgs]="store.subOrgs()"
|
|
[selectedSubOrgId]="store.selectedSubOrgId()"
|
|
[history]="store.history()"
|
|
[publishedVersion]="store.publishedVersion()"
|
|
[unsentBriefs]="store.unsentBriefs()"
|
|
[draftValid]="store.draftValid()"
|
|
[busy]="store.busy()"
|
|
[pendingPublish]="store.pendingPublish()"
|
|
[saveText]="saveText()"
|
|
[previewUrlFor]="previewUrlFor"
|
|
(selectSubOrg)="store.selectSubOrg($event)"
|
|
(templateEdit)="
|
|
store.edit({ tag: 'FieldEdited', field: $event.field, value: $event.value })
|
|
"
|
|
(marginEdit)="
|
|
store.edit({ tag: 'MarginEdited', edge: $event.edge, value: $event.value })
|
|
"
|
|
(logoSelected)="store.onLogoSelected($event)"
|
|
(logoRemoved)="store.onLogoRemoved($event)"
|
|
(logoRetry)="store.onLogoRetry($event)"
|
|
(requestPublish)="store.requestPublish()"
|
|
(confirmPublish)="store.confirmPublish()"
|
|
(cancelPublish)="store.cancelPublish()"
|
|
(rollback)="store.rollback($event)"
|
|
(proefbrief)="store.proefbrief()"
|
|
/>
|
|
}
|
|
</ng-template>
|
|
</app-async>
|
|
}
|
|
</app-page-shell>
|
|
`,
|
|
})
|
|
export class OrgTemplatePage {
|
|
protected store = inject(OrgTemplateStore);
|
|
protected access = inject(AccessStore);
|
|
|
|
protected canEdit = computed(() => this.access.can('orgtemplate:edit'));
|
|
protected previewUrlFor = this.store.previewUrlFor;
|
|
|
|
protected heading = $localize`:@@orgTemplate.page.heading:Huisstijl beheren`;
|
|
protected intro = $localize`:@@orgTemplate.page.intro:Beheer per organisatieonderdeel het uiterlijk van de brief: logo, afzender, ondertekening, voettekst en marges.`;
|
|
protected deniedText = $localize`:@@orgTemplate.page.denied:U hebt geen rechten om organisatiesjablonen te beheren.`;
|
|
protected failedText = $localize`:@@orgTemplate.page.failed:Het sjabloon kon niet worden geladen.`;
|
|
protected retryText = $localize`:@@orgTemplate.page.retry:Opnieuw proberen`;
|
|
|
|
private savingText = $localize`:@@orgTemplate.page.saving:Concept opslaan…`;
|
|
private savedText = $localize`:@@orgTemplate.page.saved:Concept opgeslagen`;
|
|
private saveErrorText = $localize`:@@orgTemplate.page.saveError:Opslaan mislukt`;
|
|
protected saveText = computed(() => {
|
|
switch (this.store.saveState().tag) {
|
|
case 'Saving':
|
|
return this.savingText;
|
|
case 'Saved':
|
|
return this.savedText;
|
|
case 'Error':
|
|
return this.saveErrorText;
|
|
default:
|
|
return '';
|
|
}
|
|
});
|
|
|
|
private loadRequested = false;
|
|
constructor() {
|
|
// Load once the capability resolves to `allowed` (a 403 GET would be wasted
|
|
// otherwise). Depends only on `canEdit()` + a plain flag — never on the store
|
|
// model, so dispatching `Loading` inside `load()` can't retrigger this effect.
|
|
effect(() => {
|
|
if (this.canEdit() && !this.loadRequested) {
|
|
this.loadRequested = true;
|
|
void this.store.load();
|
|
}
|
|
});
|
|
}
|
|
|
|
protected reload() {
|
|
void this.store.load();
|
|
}
|
|
}
|