diff --git a/src/app/features/content-management/components/static-pages-editor.component.html b/src/app/features/content-management/components/static-pages-editor.component.html index e6b2e58..da414fa 100644 --- a/src/app/features/content-management/components/static-pages-editor.component.html +++ b/src/app/features/content-management/components/static-pages-editor.component.html @@ -55,6 +55,9 @@ @if (!page.enabled) { {{ 'staticPages.disabledBadge' | translate }} } + @if (isModified(page)) { + {{ 'builder.unsavedChanges' | translate }} + } @if (hasDuplicateSlug(page)) { {{ 'staticPages.duplicateSlug' | translate }} } @@ -114,10 +117,16 @@
- +
+ + {{ 'adminCategories.chooseImage' | translate }} +
- +
+ + {{ 'adminCategories.chooseImage' | translate }} +
@@ -177,4 +186,5 @@ }
+ diff --git a/src/app/features/content-management/components/static-pages-editor.component.ts b/src/app/features/content-management/components/static-pages-editor.component.ts index 0dea331..bafdfed 100644 --- a/src/app/features/content-management/components/static-pages-editor.component.ts +++ b/src/app/features/content-management/components/static-pages-editor.component.ts @@ -3,6 +3,7 @@ import { FormsModule } from '@angular/forms'; import { ProjectEditorFacade } from '../../project-editor/facade/project-editor.facade'; import { ContentManagementFacade } from '../facade/content-management.facade'; import { ContentPage, ContentPageSeoConfig, ContentPageStatus } from '../models/content-page.model'; +import { BootstrapConfig } from '../../../shared/models/config'; import { TranslatePipe } from '../../../i18n/translate.pipe'; import { TranslateService } from '../../../i18n/translate.service'; import { MarketplaceHtmlEditorComponent } from '../../project-editor/components/html-editor/marketplace-html-editor.component'; @@ -14,9 +15,12 @@ import { BadgeComponent } from '../../../shared/ui/badge/badge.component'; import { EmptyStateComponent } from '../../../shared/ui/empty-state/empty-state.component'; import { SelectComponent, SelectOption } from '../../../shared/ui/select/select.component'; import { ToggleComponent } from '../../../shared/ui/toggle/toggle.component'; +import { MediaPickerComponent } from '../../../shared/media/media-picker/media-picker.component'; +import { MediaAsset } from '../../../core/media/models/media-asset.model'; type StatusFilter = 'all' | ContentPageStatus; const ALL_LOCALES_FILTER = 'all'; +type MediaField = 'heroImage' | 'thumbnail'; @Component({ selector: 'app-static-pages-editor', @@ -33,6 +37,7 @@ const ALL_LOCALES_FILTER = 'all'; EmptyStateComponent, SelectComponent, ToggleComponent, + MediaPickerComponent, ], templateUrl: './static-pages-editor.component.html', styleUrls: ['../../project-editor/sections/section.shared.scss', './static-pages-editor.component.scss'], @@ -92,6 +97,43 @@ export class StaticPagesEditorComponent { return visible.length > 0 && visible.every(page => this.selectedIds().has(page.id)); }); + /** + * The originally loaded/published pages, normalized through the same + * ContentManagementFacade.pages() path as the live editor list, keyed by + * id - reused (not reimplemented) for the per-page modified indicator. + */ + private readonly originalPagesById = computed>(() => { + const staticPages = this.projectEditor.originalStaticPages(); + if (!staticPages) { + return new Map(); + } + const normalized = this.contentFacade.pages({ staticPages } as BootstrapConfig); + return new Map(normalized.map(page => [page.id, page])); + }); + + isModified(page: ContentPage): boolean { + const original = this.originalPagesById().get(page.id); + if (!original) { + return true; // a page that didn't exist in the original snapshot is new/modified. + } + return JSON.stringify(page) !== JSON.stringify(original); + } + + protected mediaPickerOpen = false; + private mediaPickerTarget: { pageId: string; field: MediaField } | null = null; + + openMediaPicker(pageId: string, field: MediaField): void { + this.mediaPickerTarget = { pageId, field }; + this.mediaPickerOpen = true; + } + + onImagePicked(asset: MediaAsset): void { + if (this.mediaPickerTarget) { + this.updatePage(this.mediaPickerTarget.pageId, { [this.mediaPickerTarget.field]: asset.url }); + } + this.mediaPickerOpen = false; + } + createPage(): void { const slug = `custom-page-${this.allPages().length + 1}`; const page: ContentPage = { diff --git a/src/app/features/project-editor/facade/project-editor.facade.ts b/src/app/features/project-editor/facade/project-editor.facade.ts index b920ed8..2e1a87c 100644 --- a/src/app/features/project-editor/facade/project-editor.facade.ts +++ b/src/app/features/project-editor/facade/project-editor.facade.ts @@ -63,6 +63,8 @@ export class ProjectEditorFacade { readonly lastSavedAt = computed(() => this.state().lastSavedAt); readonly lastPublishedAt = computed(() => this.state().lastPublishedAt); readonly draftRestored = computed(() => this.state().draftRestored); + /** The originally loaded/published `staticPages` snapshot, for per-page modified-state comparison in the Static Pages editor. */ + readonly originalStaticPages = computed(() => this.state().originalBootstrap?.staticPages ?? null); readonly validationIssues = computed(() => { const current = this.bootstrap(); return current ? this.validator.validate(current) : [];