From 30afb5d778e73be7649e55ec3f5a9fd2765ca336 Mon Sep 17 00:00:00 2001 From: sdarbinyan Date: Wed, 15 Jul 2026 10:42:23 +0400 Subject: [PATCH] feat(media): reusable media management Sprint 22. - MediaAsset gains folder (flat) and MediaListParams gains folder/tag filters; MediaRepository.listFolders() derives the folder list from existing records - upload validation: 10MB size cap, mime allow-list (jpeg/png/webp/gif/ svg+xml/pdf), real error messages surfaced through MediaLibraryFacade instead of a generic swallowed string - SVG uploads are sanitized (script tags and on*= attributes stripped) before storage - raster images (excl. gif) are downscaled to a 2000px max dimension and re-encoded via canvas before storage - compression, not a crop UI - tag editing (window.prompt, comma-separated) via MediaLibraryFacade.updateTags() - MediaPickerComponent wired into Project Editor branding (logo, compact logo, favicon) alongside its existing category/product usage - confirmed no image fields exist on Static Pages or as a dedicated hero field to wire docs/ADMIN.md updated with the new Sprint 22 section including the storage abstraction note (MediaRepository was already the abstraction). Co-Authored-By: Claude Sonnet 5 --- docs/ADMIN.md | 83 ++++++++++++++++ docs/SPRINT-PLAN.md | 11 ++- src/app/core/media/media-repository.ts | 7 +- .../media/mock-media-repository.service.ts | 94 ++++++++++++++++--- .../core/media/models/media-asset.model.ts | 8 ++ .../media/facade/media-library.facade.ts | 30 +++++- .../media/media-library-page.component.html | 25 ++++- .../media/media-library-page.component.scss | 24 +++++ .../media/media-library-page.component.ts | 18 ++++ .../sections/branding-section.component.html | 16 +++- .../sections/branding-section.component.ts | 22 ++++- .../sections/section.shared.scss | 7 ++ .../media-picker/media-picker.component.html | 6 +- .../media-picker/media-picker.component.scss | 6 ++ 14 files changed, 324 insertions(+), 33 deletions(-) diff --git a/docs/ADMIN.md b/docs/ADMIN.md index 4d37854..9a37bdc 100644 --- a/docs/ADMIN.md +++ b/docs/ADMIN.md @@ -208,6 +208,89 @@ src/app/features/admin/categories/ - **Preview**: simple read-only line in the editor showing computed discounted price. - **Infinite scroll**: `AdminProductsFacade.infiniteScroll` toggle - when on, `loadMore()` appends the next page to `products()` instead of replacing it; pagination UI swaps for a "Load more" button. Off by default (existing paginated behavior unchanged). +## Sprint 22 - Media System hardening + +`core/media/` (`MediaRepository` abstraction, `MockMediaRepository` IndexedDB +implementation) + `features/backoffice/media/` + the shared +`shared/media/media-picker/`: + +- **Folders**: flat `folder?: string` tag on `MediaAsset` (no nesting) - + "New folder" just sets the active filter to a name typed via + `window.prompt` (mirrors the `window.confirm` pattern already used for + destructive actions elsewhere); the folder is created implicitly the next + time something uploads into it. `MediaRepository.listFolders()` derives + the folder list from existing records rather than a separate folder + entity - intentionally light, matches the flat-storage reality of an + IndexedDB mock. +- **Tags**: already existed on `MediaAsset`; added an edit affordance + (`window.prompt`, comma-separated) and `MediaLibraryFacade.updateTags()`. +- **Validation**: `MockMediaRepository.validateFile()` rejects anything over + 10MB or outside the allow-list (`jpeg/png/webp/gif/svg+xml/pdf`); errors + now propagate as real messages through `MediaLibraryFacade.error` (both + `media-library-page` and `media-picker` display it - previously upload + failures were swallowed into a generic string). +- **SVG sanitization**: `sanitizeSvg()` strips `