168 lines
4.8 KiB
TypeScript
168 lines
4.8 KiB
TypeScript
/**
|
|
* WEB-0b: CMS-Inhalte der öffentlichen Webseite (Seiten + Blöcke).
|
|
*
|
|
* Backend-Vertrag (GerbilManagerWebAPI/Endpoints/CmsEndpoints.cs):
|
|
* - GET /pages -> PageSummary[] (kein Paging!)
|
|
* - GET /pages/{slug} -> Page (inkl. Blöcke)
|
|
* - PUT /pages/{id} -> 204
|
|
* - POST /pages/{pageId}/blocks -> 201 Block
|
|
* - PUT /blocks/{id} -> 204
|
|
* - DELETE /blocks/{id} -> 204
|
|
* - PUT /pages/{pageId}/blocks/order -> 204 (Body: { blockIds })
|
|
*
|
|
* Enums kommen dank globalem JsonStringEnumConverter als STRINGS
|
|
* ("Draft"/"Published", "Heading"/"RichText"/…). Block.data ist ein
|
|
* typ-spezifisches JSON-Objekt (siehe BlockData-Typen unten).
|
|
*/
|
|
import { API_BASE_URL, api } from './client'
|
|
|
|
/**
|
|
* WEB-3-Fix: Die CMS-Endpunkte liegen unter der /api-Gruppe
|
|
* (CmsEndpoints: MapGroup("/api")) — die Aufrufe hier liefen vorher gegen
|
|
* /pages und wären gegen die ECHTE API 404 gelaufen (der e2e-Mock hat den
|
|
* Unterschied kaschiert, weil er das /api-Präfix normalisiert).
|
|
*/
|
|
const CMS = '/api'
|
|
|
|
export type PageStatus = 'Draft' | 'Published'
|
|
|
|
export type BlockType =
|
|
| 'Heading'
|
|
| 'RichText'
|
|
| 'Image'
|
|
| 'Gallery'
|
|
| 'ContactInfo'
|
|
| 'AbgabetiereList'
|
|
|
|
/** Block-Typen, die die Hand-Editier-Oberfläche neu anlegen kann (in Anzeige-Reihenfolge). */
|
|
export const ADDABLE_BLOCK_TYPES: readonly BlockType[] = [
|
|
'Heading',
|
|
'RichText',
|
|
'Image',
|
|
'Gallery',
|
|
'ContactInfo',
|
|
] as const
|
|
|
|
// ── Typ-spezifische Block-Daten ────────────────────────────────────────────
|
|
export interface HeadingData {
|
|
text: string
|
|
level: 2 | 3
|
|
}
|
|
export interface RichTextData {
|
|
/** Markdown-Quelltext (wird erst beim Veröffentlichen gerendert + bereinigt). */
|
|
markdown: string
|
|
}
|
|
export interface ImageData {
|
|
url: string
|
|
alt: string
|
|
}
|
|
export interface GalleryImage {
|
|
url: string
|
|
alt: string
|
|
}
|
|
export interface GalleryData {
|
|
images: GalleryImage[]
|
|
}
|
|
export interface ContactInfoData {
|
|
name: string
|
|
email: string
|
|
phone: string
|
|
address: string
|
|
}
|
|
export interface AbgabetiereListData {
|
|
/** auto = aus den aktuellen Abgabetieren aufgelöst; manual = von Hand gepflegt. */
|
|
mode: 'auto' | 'manual'
|
|
intro: string
|
|
}
|
|
|
|
export type BlockData = Record<string, unknown>
|
|
|
|
export interface Block {
|
|
id: string
|
|
order: number
|
|
type: BlockType
|
|
data: BlockData
|
|
}
|
|
|
|
export interface PageSummary {
|
|
id: string
|
|
slug: string
|
|
title: string
|
|
seoDescription: string | null
|
|
status: PageStatus
|
|
}
|
|
|
|
export interface Page extends PageSummary {
|
|
blocks: Block[]
|
|
}
|
|
|
|
export interface PageInput {
|
|
slug: string
|
|
title: string
|
|
seoDescription: string | null
|
|
status: PageStatus
|
|
}
|
|
|
|
export interface BlockInput {
|
|
type: BlockType
|
|
data: BlockData
|
|
order?: number
|
|
}
|
|
|
|
/** Sinnvolle Startwerte für einen neu angelegten Block. */
|
|
export function defaultBlockData(type: BlockType): BlockData {
|
|
switch (type) {
|
|
case 'Heading':
|
|
return { text: '', level: 2 }
|
|
case 'RichText':
|
|
return { markdown: '' }
|
|
case 'Image':
|
|
return { url: '', alt: '' }
|
|
case 'Gallery':
|
|
return { images: [] }
|
|
case 'ContactInfo':
|
|
return { name: '', email: '', phone: '', address: '' }
|
|
case 'AbgabetiereList':
|
|
return { mode: 'auto', intro: '' }
|
|
}
|
|
}
|
|
|
|
// ── API-Aufrufe ──────────────────────────────────────────────────────────────
|
|
export function listPages(): Promise<PageSummary[]> {
|
|
return api.get<PageSummary[]>(`${CMS}/pages`)
|
|
}
|
|
|
|
export function getPage(slug: string): Promise<Page> {
|
|
return api.get<Page>(`${CMS}/pages/${encodeURIComponent(slug)}`)
|
|
}
|
|
|
|
export function updatePage(id: string, input: PageInput): Promise<void> {
|
|
return api.put<void>(`${CMS}/pages/${id}`, input)
|
|
}
|
|
|
|
export function addBlock(pageId: string, input: BlockInput): Promise<Block> {
|
|
return api.post<Block>(`${CMS}/pages/${pageId}/blocks`, input)
|
|
}
|
|
|
|
export function updateBlock(id: string, input: BlockInput): Promise<void> {
|
|
return api.put<void>(`${CMS}/blocks/${id}`, input)
|
|
}
|
|
|
|
export function deleteBlock(id: string): Promise<void> {
|
|
return api.delete(`${CMS}/blocks/${id}`)
|
|
}
|
|
|
|
export function reorderBlocks(pageId: string, blockIds: string[]): Promise<void> {
|
|
return api.put<void>(`${CMS}/pages/${pageId}/blocks/order`, { blockIds })
|
|
}
|
|
|
|
/**
|
|
* WEB-3: Absolute URL der lokalen Vorschau (GET /api/preview/… rendert live).
|
|
* "start" liegt auf index.html, alle anderen Seiten auf {slug}/index.html —
|
|
* dieselbe Abbildung wie im SiteRenderer.
|
|
*/
|
|
export function previewUrl(slug?: string | null): string {
|
|
const path = !slug || slug === 'start' ? 'index.html' : `${encodeURIComponent(slug)}/index.html`
|
|
return `${API_BASE_URL}/api/preview/${path}`
|
|
}
|