mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 21:41:16 +00:00
added internal account code and content
This commit is contained in:
parent
6268dbac76
commit
5880e82b23
59 changed files with 5618 additions and 72 deletions
|
|
@ -1,9 +1,11 @@
|
|||
import type { AiSettings, ColorOverrides, FieldDef, ListFieldDef, ListItem, SectionData, SectionDef, TemplateContent, TemplateData, TemplateDefinition, TemplateStyle } from "./types"
|
||||
import type { AiSettings, Block, BlockDefinition, ColorOverrides, FieldDef, ListFieldDef, ListItem, SectionData, SectionDef, TemplateContent, TemplateData, TemplateDefinition, TemplateStyle } from "./types"
|
||||
|
||||
const MAX_TEXT = 4000
|
||||
const MAX_ITEMS = 40
|
||||
const MAX_BLOCKS = 40
|
||||
const HEX_COLOR = /^#[0-9a-f]{6}$/i
|
||||
const LIBRARY_KEY = /^[a-z0-9]+:[a-z0-9]+$/
|
||||
const BLOCK_ID = /^[a-z0-9-]{1,40}$/i
|
||||
export const IMAGE_STYLES: TemplateStyle["images"][] = ["original", "mono", "warm", "soft", "contrast"]
|
||||
|
||||
function libraryKey(value: unknown, own: { key: string }[]) {
|
||||
|
|
@ -51,6 +53,10 @@ const FIELD_LABELS: Record<string, string> = {
|
|||
training: "Training",
|
||||
logo: "Logo (Bild-Link; leer = Name als Text)", link: "Externer Link (z. B. mobile.de)", linkLabel: "Text für den Link",
|
||||
values: "Werte (mit Komma trennen)", steps: "Schritte (mit Komma trennen)",
|
||||
menu: "Menüpunkt (leer = nicht im Menü)", href: "Ziel des Buttons (#abschnitt, mailto:… oder https://…)",
|
||||
hrefSecondary: "Ziel des zweiten Buttons", keywords: "Schlagworte (mit Komma trennen)", word: "Schriftzug (leer = Name)",
|
||||
edition: "Kopfzeile", topics: "Themen zur Auswahl (mit Komma trennen)", secondary: "Zweite Zeile (leer = keine)",
|
||||
ribbon: "Hervorhebung (z. B. Beliebt; leer = keine)",
|
||||
items: "Einträge", plans: "Tarife", members: "Personen", questions: "Fragen", milestones: "Meilensteine", facts: "Kennzahlen",
|
||||
}
|
||||
|
||||
|
|
@ -72,25 +78,36 @@ function describeField(key: string, sample: string): FieldDef {
|
|||
|
||||
type SectionMeta = string | { label: string; optional?: boolean; items?: Record<string, string> }
|
||||
|
||||
// Felder einer Sektion aus ihren Standardinhalten; `items` benennt die Einträge von Listen
|
||||
function describeFields(section: SectionData, items?: Record<string, string>): (FieldDef | ListFieldDef)[] {
|
||||
return Object.entries(section).map(([fieldKey, value]): FieldDef | ListFieldDef => {
|
||||
if (!Array.isArray(value)) return describeField(fieldKey, value)
|
||||
const shape = value[0] ?? {}
|
||||
return {
|
||||
key: fieldKey,
|
||||
label: FIELD_LABELS[fieldKey] ?? humanize(fieldKey),
|
||||
type: "list",
|
||||
itemLabel: items?.[fieldKey] ?? "Eintrag",
|
||||
fields: Object.entries(shape).map(([itemKey, sample]) => describeField(itemKey, sample)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Editor-Beschreibung aus den Standardinhalten ableiten; nur Sektionsnamen und Listen-Bezeichnungen sind nötig
|
||||
export function describeSections<D extends TemplateData>(defaults: D, meta: Record<keyof D & string, SectionMeta>): SectionDef[] {
|
||||
return Object.entries(defaults).map(([key, section]) => {
|
||||
const info = typeof meta[key] === "string" ? { label: meta[key] as string } : (meta[key] as Exclude<SectionMeta, string>)
|
||||
const fields = Object.entries(section).map(([fieldKey, value]): FieldDef | ListFieldDef => {
|
||||
if (!Array.isArray(value)) return describeField(fieldKey, value)
|
||||
const shape = value[0] ?? {}
|
||||
return {
|
||||
key: fieldKey,
|
||||
label: FIELD_LABELS[fieldKey] ?? humanize(fieldKey),
|
||||
type: "list",
|
||||
itemLabel: info.items?.[fieldKey] ?? "Eintrag",
|
||||
fields: Object.entries(shape).map(([itemKey, sample]) => describeField(itemKey, sample)),
|
||||
}
|
||||
})
|
||||
return { key, label: info.label, optional: info.optional, fields }
|
||||
return { key, label: info.label, optional: info.optional, fields: describeFields(section, info.items) }
|
||||
})
|
||||
}
|
||||
|
||||
// Baustein für den Baukasten; Felder werden wie bei describeSections aus den Standardinhalten abgeleitet.
|
||||
// Listen brauchen mindestens einen Eintrag, er gibt die Felder neuer Einträge vor.
|
||||
export function defineBlock<D extends SectionData>(block: Omit<BlockDefinition<D>, "fields"> & { items?: Record<string, string> }) {
|
||||
const { items, ...rest } = block
|
||||
return { ...rest, fields: describeFields(block.defaults, items) } satisfies BlockDefinition<D>
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
|
@ -121,6 +138,30 @@ function mergeSection(defaults: SectionData, input: unknown): SectionData {
|
|||
return result
|
||||
}
|
||||
|
||||
// Bausteine gegen die Bibliothek auflösen: unbekannte Typen fallen weg, Inhalte wie bei Sektionen.
|
||||
// Ohne gespeicherte Liste (neue Variante) gilt die Startaufstellung der Vorlage.
|
||||
function resolveBlocks(builder: NonNullable<TemplateDefinition["builder"]>, raw: unknown): Block[] {
|
||||
const library = new Map(builder.library.map((block) => [block.type, block]))
|
||||
const source: unknown[] = Array.isArray(raw) ? raw : builder.seed.map((seed, i) => ({ id: `b${i + 1}`, ...seed }))
|
||||
const used = new Set<string>()
|
||||
const blocks: Block[] = []
|
||||
|
||||
for (const item of source.slice(0, MAX_BLOCKS)) {
|
||||
if (!isRecord(item)) continue
|
||||
const block = typeof item.type === "string" ? library.get(item.type) : undefined
|
||||
if (!block) continue
|
||||
|
||||
let id = typeof item.id === "string" && BLOCK_ID.test(item.id) && !used.has(item.id) ? item.id : ""
|
||||
for (let n = blocks.length + 1; !id; n++) {
|
||||
if (!used.has(`b${n}`)) id = `b${n}`
|
||||
}
|
||||
used.add(id)
|
||||
blocks.push({ id, type: block.type, hidden: item.hidden === true, data: mergeSection(block.defaults, item.data) })
|
||||
}
|
||||
|
||||
return blocks
|
||||
}
|
||||
|
||||
// Gespeicherte Inhalte immer gegen die aktuelle Vorlage auflösen: neue Felder bekommen ihren
|
||||
// Standardwert, entfernte oder fremde Felder fallen weg. So überstehen Varianten Änderungen an der Vorlage.
|
||||
export function resolveContent<D extends TemplateData>(def: TemplateDefinition<D>, raw: unknown): TemplateContent<D> {
|
||||
|
|
@ -165,6 +206,7 @@ export function resolveContent<D extends TemplateData>(def: TemplateDefinition<D
|
|||
data: Object.fromEntries(
|
||||
Object.entries(def.defaults).map(([key, section]) => [key, mergeSection(section, data[key])])
|
||||
) as D,
|
||||
blocks: def.builder ? resolveBlocks(def.builder, source.blocks) : [],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue