mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 22:04:10 +00:00
feat: add comprehensive blogging and team features
Blogging Collections: - Tags Collection with name, slug, description, color - Authors Collection with avatar, bio, social media links Posts Collection extended: - Tags and Author relationships - Co-Authors support - Automatic reading time calculation - Legacy author text field fallback New Blogging Blocks: - AuthorBioBlock: Display author info with various layouts - RelatedPostsBlock: Show related articles (auto/manual/category/tag) - ShareButtonsBlock: Social sharing (Facebook, Twitter, LinkedIn, etc.) - TableOfContentsBlock: Auto-generated TOC from headings Team Collection extended: - Slug field for profile pages (auto-generated) - Hierarchy fields (reportsTo, hierarchyLevel) for org charts - vCard export flag New Team API Endpoints: - GET /api/team - List with search and filters - GET /api/team/[slug]/vcard - vCard download (VCF) New Team Blocks: - TeamFilterBlock: Interactive team display with search/filter - OrgChartBlock: Hierarchical organization chart visualization 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
f0424a4abf
commit
2b097eefb3
18 changed files with 4858 additions and 2 deletions
187
src/app/(frontend)/api/team/[slug]/vcard/route.ts
Normal file
187
src/app/(frontend)/api/team/[slug]/vcard/route.ts
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@payload-config'
|
||||
|
||||
/**
|
||||
* vCard Export API
|
||||
*
|
||||
* GET /api/team/[slug]/vcard - Generiert vCard (VCF) für ein Team-Mitglied
|
||||
*
|
||||
* Query-Parameter:
|
||||
* - tenant (required): Tenant ID
|
||||
*/
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ slug: string }> }
|
||||
) {
|
||||
try {
|
||||
const { slug } = await params
|
||||
const { searchParams } = new URL(request.url)
|
||||
|
||||
// Required: Tenant
|
||||
const tenantId = searchParams.get('tenant')
|
||||
if (!tenantId) {
|
||||
return NextResponse.json({ error: 'tenant parameter is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
// Find team member
|
||||
const result = await payload.find({
|
||||
collection: 'team',
|
||||
where: {
|
||||
tenant: { equals: parseInt(tenantId) },
|
||||
slug: { equals: slug },
|
||||
isActive: { equals: true },
|
||||
},
|
||||
depth: 1,
|
||||
limit: 1,
|
||||
})
|
||||
|
||||
if (result.docs.length === 0) {
|
||||
return NextResponse.json({ error: 'Team member not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const member = result.docs[0]
|
||||
|
||||
// Check if vCard export is allowed
|
||||
if (member.allowVCard === false) {
|
||||
return NextResponse.json({ error: 'vCard export not allowed for this member' }, { status: 403 })
|
||||
}
|
||||
|
||||
// Generate vCard 3.0
|
||||
const vcard = generateVCard(member)
|
||||
|
||||
// Return as downloadable file
|
||||
const filename = `${member.slug || member.name?.toLowerCase().replace(/\s+/g, '-')}.vcf`
|
||||
|
||||
return new NextResponse(vcard, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'text/vcard; charset=utf-8',
|
||||
'Content-Disposition': `attachment; filename="${filename}"`,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('vCard API error:', error)
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
interface TeamMember {
|
||||
name?: string
|
||||
role?: string
|
||||
department?: string
|
||||
email?: string
|
||||
phone?: string
|
||||
showContactInfo?: boolean
|
||||
image?: { url?: string } | number
|
||||
socialLinks?: Array<{ platform?: string; url?: string }>
|
||||
qualifications?: Array<{ title?: string }>
|
||||
specializations?: Array<{ title?: string }>
|
||||
languages?: Array<{ language?: string; level?: string }>
|
||||
bioShort?: string
|
||||
}
|
||||
|
||||
function generateVCard(member: TeamMember): string {
|
||||
const lines: string[] = []
|
||||
|
||||
// vCard header
|
||||
lines.push('BEGIN:VCARD')
|
||||
lines.push('VERSION:3.0')
|
||||
|
||||
// Name
|
||||
if (member.name) {
|
||||
const nameParts = member.name.split(' ')
|
||||
const lastName = nameParts.pop() || ''
|
||||
const firstName = nameParts.join(' ')
|
||||
lines.push(`N:${escapeVCard(lastName)};${escapeVCard(firstName)};;;`)
|
||||
lines.push(`FN:${escapeVCard(member.name)}`)
|
||||
}
|
||||
|
||||
// Organization & Title
|
||||
if (member.role) {
|
||||
lines.push(`TITLE:${escapeVCard(member.role)}`)
|
||||
}
|
||||
if (member.department) {
|
||||
lines.push(`ORG:;${escapeVCard(member.department)}`)
|
||||
}
|
||||
|
||||
// Contact info (only if allowed)
|
||||
if (member.showContactInfo) {
|
||||
if (member.email) {
|
||||
lines.push(`EMAIL;TYPE=WORK:${member.email}`)
|
||||
}
|
||||
if (member.phone) {
|
||||
lines.push(`TEL;TYPE=WORK:${member.phone}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Photo URL (if available)
|
||||
if (member.image && typeof member.image === 'object' && member.image.url) {
|
||||
lines.push(`PHOTO;VALUE=URI:${member.image.url}`)
|
||||
}
|
||||
|
||||
// Social Links as URLs
|
||||
if (Array.isArray(member.socialLinks)) {
|
||||
member.socialLinks.forEach((link, index) => {
|
||||
if (link.url) {
|
||||
const label = link.platform?.toUpperCase() || `SOCIAL${index + 1}`
|
||||
lines.push(`URL;TYPE=${label}:${link.url}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Note with bio and qualifications
|
||||
const notes: string[] = []
|
||||
if (member.bioShort) {
|
||||
notes.push(member.bioShort)
|
||||
}
|
||||
if (Array.isArray(member.qualifications) && member.qualifications.length > 0) {
|
||||
const quals = member.qualifications.map((q) => q.title).filter(Boolean).join(', ')
|
||||
if (quals) {
|
||||
notes.push(`Qualifikationen: ${quals}`)
|
||||
}
|
||||
}
|
||||
if (Array.isArray(member.specializations) && member.specializations.length > 0) {
|
||||
const specs = member.specializations.map((s) => s.title).filter(Boolean).join(', ')
|
||||
if (specs) {
|
||||
notes.push(`Fachgebiete: ${specs}`)
|
||||
}
|
||||
}
|
||||
if (Array.isArray(member.languages) && member.languages.length > 0) {
|
||||
const langs = member.languages.map((l) => l.language).filter(Boolean).join(', ')
|
||||
if (langs) {
|
||||
notes.push(`Sprachen: ${langs}`)
|
||||
}
|
||||
}
|
||||
if (notes.length > 0) {
|
||||
lines.push(`NOTE:${escapeVCard(notes.join('\\n'))}`)
|
||||
}
|
||||
|
||||
// Categories (specializations as tags)
|
||||
if (Array.isArray(member.specializations) && member.specializations.length > 0) {
|
||||
const categories = member.specializations.map((s) => s.title).filter(Boolean).join(',')
|
||||
if (categories) {
|
||||
lines.push(`CATEGORIES:${escapeVCard(categories)}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Production ID and revision
|
||||
lines.push(`PRODID:-//Payload CMS//Team vCard//DE`)
|
||||
lines.push(`REV:${new Date().toISOString().replace(/[-:]/g, '').split('.')[0]}Z`)
|
||||
|
||||
// vCard footer
|
||||
lines.push('END:VCARD')
|
||||
|
||||
return lines.join('\r\n')
|
||||
}
|
||||
|
||||
function escapeVCard(text: string): string {
|
||||
if (!text) return ''
|
||||
return text
|
||||
.replace(/\\/g, '\\\\')
|
||||
.replace(/;/g, '\\;')
|
||||
.replace(/,/g, '\\,')
|
||||
.replace(/\n/g, '\\n')
|
||||
}
|
||||
214
src/app/(frontend)/api/team/route.ts
Normal file
214
src/app/(frontend)/api/team/route.ts
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getPayload } from 'payload'
|
||||
import config from '@payload-config'
|
||||
|
||||
/**
|
||||
* Team API
|
||||
*
|
||||
* GET /api/team - Liste aller Team-Mitglieder mit Filter und Suche
|
||||
*
|
||||
* Query-Parameter:
|
||||
* - tenant (required): Tenant ID
|
||||
* - slug: Einzelnes Mitglied nach Slug
|
||||
* - search: Volltextsuche in Name, Rolle, Abteilung, Bio
|
||||
* - department: Nach Abteilung filtern
|
||||
* - level: Nach Hierarchie-Ebene filtern
|
||||
* - specialization: Nach Fachgebiet filtern
|
||||
* - language: Nach Sprache filtern
|
||||
* - featured: Nur hervorgehobene (true/false)
|
||||
* - limit: Maximale Anzahl (default: 50)
|
||||
* - page: Seite für Pagination
|
||||
* - sort: Sortierung (order, name, department, startDate)
|
||||
* - locale: Sprache (de/en)
|
||||
*/
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = new URL(request.url)
|
||||
|
||||
// Required: Tenant
|
||||
const tenantId = searchParams.get('tenant')
|
||||
if (!tenantId) {
|
||||
return NextResponse.json({ error: 'tenant parameter is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
const payload = await getPayload({ config })
|
||||
|
||||
// Optional parameters
|
||||
const slug = searchParams.get('slug')
|
||||
const search = searchParams.get('search')
|
||||
const department = searchParams.get('department')
|
||||
const level = searchParams.get('level')
|
||||
const specialization = searchParams.get('specialization')
|
||||
const language = searchParams.get('language')
|
||||
const featured = searchParams.get('featured')
|
||||
const limit = Math.min(parseInt(searchParams.get('limit') || '50'), 100)
|
||||
const page = parseInt(searchParams.get('page') || '1')
|
||||
const sort = searchParams.get('sort') || 'order'
|
||||
const locale = (searchParams.get('locale') as 'de' | 'en') || 'de'
|
||||
|
||||
// Build where clause
|
||||
const where: Record<string, unknown> = {
|
||||
tenant: { equals: parseInt(tenantId) },
|
||||
isActive: { equals: true },
|
||||
}
|
||||
|
||||
// Single member by slug
|
||||
if (slug) {
|
||||
where.slug = { equals: slug }
|
||||
}
|
||||
|
||||
// Department filter
|
||||
if (department) {
|
||||
where.department = { contains: department }
|
||||
}
|
||||
|
||||
// Hierarchy level filter
|
||||
if (level) {
|
||||
where.hierarchyLevel = { equals: level }
|
||||
}
|
||||
|
||||
// Featured filter
|
||||
if (featured === 'true') {
|
||||
where.isFeatured = { equals: true }
|
||||
}
|
||||
|
||||
// Build sort string
|
||||
let sortField = 'order'
|
||||
switch (sort) {
|
||||
case 'name':
|
||||
sortField = 'name'
|
||||
break
|
||||
case 'department':
|
||||
sortField = 'department'
|
||||
break
|
||||
case 'startDate':
|
||||
sortField = '-startDate'
|
||||
break
|
||||
case '-order':
|
||||
sortField = '-order'
|
||||
break
|
||||
default:
|
||||
sortField = 'order'
|
||||
}
|
||||
|
||||
// Query team members
|
||||
const result = await payload.find({
|
||||
collection: 'team',
|
||||
where,
|
||||
sort: sortField,
|
||||
limit,
|
||||
page,
|
||||
locale,
|
||||
depth: 2, // Include image and reportsTo
|
||||
})
|
||||
|
||||
let members = result.docs
|
||||
|
||||
// Post-query filters (for array fields)
|
||||
|
||||
// Search filter (case-insensitive)
|
||||
if (search) {
|
||||
const searchLower = search.toLowerCase()
|
||||
members = members.filter((member) => {
|
||||
const nameMatch = member.name?.toLowerCase().includes(searchLower)
|
||||
const roleMatch =
|
||||
typeof member.role === 'string' && member.role.toLowerCase().includes(searchLower)
|
||||
const deptMatch =
|
||||
typeof member.department === 'string' &&
|
||||
member.department.toLowerCase().includes(searchLower)
|
||||
const bioMatch =
|
||||
typeof member.bioShort === 'string' &&
|
||||
member.bioShort.toLowerCase().includes(searchLower)
|
||||
|
||||
// Search in specializations
|
||||
const specMatch =
|
||||
Array.isArray(member.specializations) &&
|
||||
member.specializations.some(
|
||||
(s) => typeof s.title === 'string' && s.title.toLowerCase().includes(searchLower)
|
||||
)
|
||||
|
||||
return nameMatch || roleMatch || deptMatch || bioMatch || specMatch
|
||||
})
|
||||
}
|
||||
|
||||
// Specialization filter
|
||||
if (specialization) {
|
||||
const specLower = specialization.toLowerCase()
|
||||
members = members.filter(
|
||||
(member) =>
|
||||
Array.isArray(member.specializations) &&
|
||||
member.specializations.some(
|
||||
(s) => typeof s.title === 'string' && s.title.toLowerCase().includes(specLower)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Language filter
|
||||
if (language) {
|
||||
const langLower = language.toLowerCase()
|
||||
members = members.filter(
|
||||
(member) =>
|
||||
Array.isArray(member.languages) &&
|
||||
member.languages.some(
|
||||
(l) => typeof l.language === 'string' && l.language.toLowerCase().includes(langLower)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// Get unique departments for filter dropdown
|
||||
const allMembers = await payload.find({
|
||||
collection: 'team',
|
||||
where: {
|
||||
tenant: { equals: parseInt(tenantId) },
|
||||
isActive: { equals: true },
|
||||
},
|
||||
limit: 1000,
|
||||
locale,
|
||||
})
|
||||
|
||||
const departments = [
|
||||
...new Set(allMembers.docs.map((m) => m.department).filter(Boolean)),
|
||||
].sort() as string[]
|
||||
|
||||
const specializations = [
|
||||
...new Set(
|
||||
allMembers.docs.flatMap((m) =>
|
||||
Array.isArray(m.specializations) ? m.specializations.map((s) => s.title) : []
|
||||
)
|
||||
),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.sort() as string[]
|
||||
|
||||
const languages = [
|
||||
...new Set(
|
||||
allMembers.docs.flatMap((m) =>
|
||||
Array.isArray(m.languages) ? m.languages.map((l) => l.language) : []
|
||||
)
|
||||
),
|
||||
]
|
||||
.filter(Boolean)
|
||||
.sort() as string[]
|
||||
|
||||
// Single member response
|
||||
if (slug && members.length === 1) {
|
||||
return NextResponse.json({
|
||||
member: members[0],
|
||||
filters: { departments, specializations, languages },
|
||||
})
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
members,
|
||||
totalDocs: result.totalDocs,
|
||||
totalPages: result.totalPages,
|
||||
page: result.page,
|
||||
hasNextPage: result.hasNextPage,
|
||||
hasPrevPage: result.hasPrevPage,
|
||||
filters: { departments, specializations, languages },
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Team API error:', error)
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
206
src/blocks/AuthorBioBlock.ts
Normal file
206
src/blocks/AuthorBioBlock.ts
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* AuthorBioBlock
|
||||
*
|
||||
* Zeigt Autoren-Informationen am Ende eines Blog-Posts an.
|
||||
* Kann automatisch den Artikel-Autor oder manuell ausgewählte Autoren anzeigen.
|
||||
*/
|
||||
export const AuthorBioBlock: Block = {
|
||||
slug: 'author-bio-block',
|
||||
labels: {
|
||||
singular: 'Autoren-Bio',
|
||||
plural: 'Autoren-Bios',
|
||||
},
|
||||
imageURL: '/assets/blocks/author-bio.png',
|
||||
fields: [
|
||||
{
|
||||
name: 'source',
|
||||
type: 'select',
|
||||
defaultValue: 'post',
|
||||
label: 'Autoren-Quelle',
|
||||
options: [
|
||||
{ label: 'Autor des Artikels', value: 'post' },
|
||||
{ label: 'Manuell auswählen', value: 'manual' },
|
||||
],
|
||||
admin: {
|
||||
description: 'Woher sollen die Autoren-Daten kommen?',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'authors',
|
||||
type: 'relationship',
|
||||
relationTo: 'authors',
|
||||
hasMany: true,
|
||||
label: 'Autoren',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'manual',
|
||||
description: 'Wählen Sie die anzuzeigenden Autoren',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showCoAuthors',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Co-Autoren anzeigen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'post',
|
||||
description: 'Auch Co-Autoren des Artikels anzeigen',
|
||||
},
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'card',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Karte', value: 'card' },
|
||||
{ label: 'Inline', value: 'inline' },
|
||||
{ label: 'Kompakt', value: 'compact' },
|
||||
{ label: 'Feature (Groß)', value: 'feature' },
|
||||
],
|
||||
},
|
||||
// Anzuzeigende Elemente
|
||||
{
|
||||
name: 'show',
|
||||
type: 'group',
|
||||
label: 'Anzeigen',
|
||||
fields: [
|
||||
{
|
||||
name: 'avatar',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Profilbild',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Name',
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Titel/Position',
|
||||
},
|
||||
{
|
||||
name: 'bio',
|
||||
type: 'select',
|
||||
defaultValue: 'short',
|
||||
label: 'Biografie',
|
||||
options: [
|
||||
{ label: 'Keine', value: 'none' },
|
||||
{ label: 'Kurz', value: 'short' },
|
||||
{ label: 'Vollständig', value: 'full' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'social',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Social Media Links',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'E-Mail',
|
||||
},
|
||||
{
|
||||
name: 'website',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Website',
|
||||
},
|
||||
{
|
||||
name: 'postCount',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Anzahl Artikel',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'style',
|
||||
type: 'group',
|
||||
label: 'Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'avatarSize',
|
||||
type: 'select',
|
||||
defaultValue: 'md',
|
||||
label: 'Avatar-Größe',
|
||||
options: [
|
||||
{ label: 'Klein (48px)', value: 'sm' },
|
||||
{ label: 'Mittel (80px)', value: 'md' },
|
||||
{ label: 'Groß (120px)', value: 'lg' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'avatarShape',
|
||||
type: 'select',
|
||||
defaultValue: 'circle',
|
||||
label: 'Avatar-Form',
|
||||
options: [
|
||||
{ label: 'Rund', value: 'circle' },
|
||||
{ label: 'Quadratisch', value: 'square' },
|
||||
{ label: 'Abgerundet', value: 'rounded' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'bg',
|
||||
type: 'select',
|
||||
defaultValue: 'light',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Akzent', value: 'accent' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'border',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Rahmen',
|
||||
},
|
||||
{
|
||||
name: 'shadow',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Schatten',
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Trennlinie oben',
|
||||
admin: {
|
||||
description: 'Linie zur Trennung vom Artikel-Inhalt',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Label
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Über den Autor", "Geschrieben von" (optional)',
|
||||
},
|
||||
},
|
||||
// Link zur Autorenseite
|
||||
{
|
||||
name: 'linkToProfile',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Zur Autorenseite verlinken',
|
||||
},
|
||||
],
|
||||
}
|
||||
455
src/blocks/OrgChartBlock.ts
Normal file
455
src/blocks/OrgChartBlock.ts
Normal file
|
|
@ -0,0 +1,455 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* OrgChartBlock (Organigramm)
|
||||
*
|
||||
* Hierarchische Darstellung der Team-Struktur.
|
||||
* Nutzt reportsTo und hierarchyLevel aus der Team Collection.
|
||||
*/
|
||||
export const OrgChartBlock: Block = {
|
||||
slug: 'org-chart-block',
|
||||
labels: {
|
||||
singular: 'Organigramm',
|
||||
plural: 'Organigramme',
|
||||
},
|
||||
imageURL: '/assets/blocks/org-chart.png',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
defaultValue: 'Unsere Struktur',
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
// Datenquelle
|
||||
{
|
||||
name: 'source',
|
||||
type: 'select',
|
||||
defaultValue: 'auto',
|
||||
label: 'Datenquelle',
|
||||
options: [
|
||||
{ label: 'Automatisch (aus Hierarchie)', value: 'auto' },
|
||||
{ label: 'Nach Abteilung', value: 'department' },
|
||||
{ label: 'Manuell auswählen', value: 'manual' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'rootMember',
|
||||
type: 'relationship',
|
||||
relationTo: 'team',
|
||||
label: 'Wurzel-Person',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'auto',
|
||||
description: 'Oberstes Element (z.B. Geschäftsführer). Leer = automatisch ermitteln.',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'department',
|
||||
type: 'text',
|
||||
label: 'Abteilung',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'department',
|
||||
description: 'Zeigt nur diese Abteilung',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'selectedMembers',
|
||||
type: 'relationship',
|
||||
relationTo: 'team',
|
||||
hasMany: true,
|
||||
label: 'Mitglieder auswählen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'manual',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'maxDepth',
|
||||
type: 'number',
|
||||
defaultValue: 5,
|
||||
min: 1,
|
||||
max: 10,
|
||||
label: 'Maximale Tiefe',
|
||||
admin: {
|
||||
description: 'Wie viele Hierarchie-Ebenen anzeigen',
|
||||
},
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'tree',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Baum (vertikal)', value: 'tree' },
|
||||
{ label: 'Baum (horizontal)', value: 'tree-horizontal' },
|
||||
{ label: 'Organigramm (klassisch)', value: 'org' },
|
||||
{ label: 'Radial/Kreis', value: 'radial' },
|
||||
{ label: 'Ebenen (gestapelt)', value: 'layers' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'direction',
|
||||
type: 'select',
|
||||
defaultValue: 'top',
|
||||
label: 'Richtung',
|
||||
options: [
|
||||
{ label: 'Von oben nach unten', value: 'top' },
|
||||
{ label: 'Von unten nach oben', value: 'bottom' },
|
||||
{ label: 'Von links nach rechts', value: 'left' },
|
||||
{ label: 'Von rechts nach links', value: 'right' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.layout === 'tree' || siblingData?.layout === 'org',
|
||||
},
|
||||
},
|
||||
// Node-Darstellung
|
||||
{
|
||||
name: 'node',
|
||||
type: 'group',
|
||||
label: 'Knoten-Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'style',
|
||||
type: 'select',
|
||||
defaultValue: 'card',
|
||||
label: 'Stil',
|
||||
options: [
|
||||
{ label: 'Karte', value: 'card' },
|
||||
{ label: 'Kompakt', value: 'compact' },
|
||||
{ label: 'Nur Foto', value: 'avatar' },
|
||||
{ label: 'Nur Text', value: 'text' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'showImage',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Foto anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'imageSize',
|
||||
type: 'select',
|
||||
defaultValue: 'md',
|
||||
label: 'Foto-Größe',
|
||||
options: [
|
||||
{ label: 'Klein (40px)', value: 'sm' },
|
||||
{ label: 'Mittel (60px)', value: 'md' },
|
||||
{ label: 'Groß (80px)', value: 'lg' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showImage,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'imageShape',
|
||||
type: 'select',
|
||||
defaultValue: 'circle',
|
||||
label: 'Foto-Form',
|
||||
options: [
|
||||
{ label: 'Rund', value: 'circle' },
|
||||
{ label: 'Abgerundet', value: 'rounded' },
|
||||
{ label: 'Quadratisch', value: 'square' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showImage,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showName',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Name anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showRole',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Position anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showDepartment',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Abteilung anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showContact',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Kontakt anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'clickAction',
|
||||
type: 'select',
|
||||
defaultValue: 'modal',
|
||||
label: 'Klick-Aktion',
|
||||
options: [
|
||||
{ label: 'Keine', value: 'none' },
|
||||
{ label: 'Detail-Modal', value: 'modal' },
|
||||
{ label: 'Zur Profilseite', value: 'link' },
|
||||
{ label: 'Untergeordnete ein-/ausklappen', value: 'expand' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'profileBasePath',
|
||||
type: 'text',
|
||||
label: 'Profil-Basis-Pfad',
|
||||
defaultValue: '/team',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.clickAction === 'link',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Verbindungslinien
|
||||
{
|
||||
name: 'connectors',
|
||||
type: 'group',
|
||||
label: 'Verbindungslinien',
|
||||
fields: [
|
||||
{
|
||||
name: 'style',
|
||||
type: 'select',
|
||||
defaultValue: 'straight',
|
||||
label: 'Linien-Stil',
|
||||
options: [
|
||||
{ label: 'Gerade', value: 'straight' },
|
||||
{ label: 'Abgewinkelt', value: 'angular' },
|
||||
{ label: 'Gebogen', value: 'curved' },
|
||||
{ label: 'Gestrichelt', value: 'dashed' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'color',
|
||||
type: 'select',
|
||||
defaultValue: 'gray',
|
||||
label: 'Farbe',
|
||||
options: [
|
||||
{ label: 'Grau', value: 'gray' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Akzent', value: 'accent' },
|
||||
{ label: 'Gradient', value: 'gradient' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'thickness',
|
||||
type: 'select',
|
||||
defaultValue: '2',
|
||||
label: 'Dicke',
|
||||
options: [
|
||||
{ label: 'Dünn (1px)', value: '1' },
|
||||
{ label: 'Normal (2px)', value: '2' },
|
||||
{ label: 'Dick (3px)', value: '3' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'animated',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Animation',
|
||||
admin: {
|
||||
description: 'Animierte Linien beim Laden',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Hierarchie-Ebenen-Styling
|
||||
{
|
||||
name: 'levels',
|
||||
type: 'group',
|
||||
label: 'Ebenen-Styling',
|
||||
fields: [
|
||||
{
|
||||
name: 'colorByLevel',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Farblich unterscheiden',
|
||||
admin: {
|
||||
description: 'Verschiedene Farben pro Hierarchie-Ebene',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'sizeByLevel',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Größe variieren',
|
||||
admin: {
|
||||
description: 'Höhere Ebenen größer darstellen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'collapsible',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Ebenen einklappbar',
|
||||
},
|
||||
{
|
||||
name: 'initiallyExpanded',
|
||||
type: 'number',
|
||||
defaultValue: 2,
|
||||
min: 1,
|
||||
max: 5,
|
||||
label: 'Initial ausgeklappt',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.collapsible,
|
||||
description: 'Wie viele Ebenen anfangs sichtbar',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Interaktion
|
||||
{
|
||||
name: 'interaction',
|
||||
type: 'group',
|
||||
label: 'Interaktion',
|
||||
fields: [
|
||||
{
|
||||
name: 'zoomable',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Zoom erlauben',
|
||||
},
|
||||
{
|
||||
name: 'pannable',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Verschieben erlauben',
|
||||
},
|
||||
{
|
||||
name: 'minimap',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Minimap anzeigen',
|
||||
admin: {
|
||||
description: 'Kleine Übersichtskarte bei großen Organigrammen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'search',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Suche aktivieren',
|
||||
},
|
||||
{
|
||||
name: 'highlight',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Hover-Hervorhebung',
|
||||
admin: {
|
||||
description: 'Bei Hover Pfad zur Wurzel hervorheben',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fullscreen',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Vollbild-Modus',
|
||||
},
|
||||
{
|
||||
name: 'export',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Export erlauben',
|
||||
admin: {
|
||||
description: 'Als PNG oder PDF exportieren',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'style',
|
||||
type: 'group',
|
||||
label: 'Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'bg',
|
||||
type: 'select',
|
||||
defaultValue: 'light',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Transparent', value: 'none' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Gradient', value: 'gradient' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'nodeBg',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
label: 'Knoten-Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Transparent', value: 'transparent' },
|
||||
{ label: 'Akzent', value: 'accent' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'nodeShadow',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Knoten-Schatten',
|
||||
},
|
||||
{
|
||||
name: 'nodeBorder',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Knoten-Rahmen',
|
||||
},
|
||||
{
|
||||
name: 'spacing',
|
||||
type: 'select',
|
||||
defaultValue: 'md',
|
||||
label: 'Abstände',
|
||||
options: [
|
||||
{ label: 'Kompakt', value: 'sm' },
|
||||
{ label: 'Normal', value: 'md' },
|
||||
{ label: 'Großzügig', value: 'lg' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'minHeight',
|
||||
type: 'select',
|
||||
defaultValue: '400',
|
||||
label: 'Mindesthöhe',
|
||||
options: [
|
||||
{ label: 'Auto', value: 'auto' },
|
||||
{ label: '300px', value: '300' },
|
||||
{ label: '400px', value: '400' },
|
||||
{ label: '500px', value: '500' },
|
||||
{ label: '600px', value: '600' },
|
||||
{ label: 'Vollbild', value: 'full' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Legend
|
||||
{
|
||||
name: 'showLegend',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Legende anzeigen',
|
||||
admin: {
|
||||
description: 'Erklärt Farben und Symbole',
|
||||
},
|
||||
},
|
||||
// Accessibility
|
||||
{
|
||||
name: 'a11yLabel',
|
||||
type: 'text',
|
||||
label: 'ARIA Label',
|
||||
localized: true,
|
||||
defaultValue: 'Organigramm der Unternehmensstruktur',
|
||||
},
|
||||
],
|
||||
}
|
||||
263
src/blocks/RelatedPostsBlock.ts
Normal file
263
src/blocks/RelatedPostsBlock.ts
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* RelatedPostsBlock
|
||||
*
|
||||
* Zeigt verwandte Artikel an, basierend auf Kategorien, Tags
|
||||
* oder manueller Auswahl.
|
||||
*/
|
||||
export const RelatedPostsBlock: Block = {
|
||||
slug: 'related-posts-block',
|
||||
labels: {
|
||||
singular: 'Ähnliche Artikel',
|
||||
plural: 'Ähnliche Artikel',
|
||||
},
|
||||
imageURL: '/assets/blocks/related-posts.png',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
defaultValue: 'Das könnte Sie auch interessieren',
|
||||
},
|
||||
{
|
||||
name: 'source',
|
||||
type: 'select',
|
||||
defaultValue: 'auto',
|
||||
label: 'Artikel-Quelle',
|
||||
options: [
|
||||
{ label: 'Automatisch (Kategorien/Tags)', value: 'auto' },
|
||||
{ label: 'Manuell auswählen', value: 'manual' },
|
||||
{ label: 'Nach Kategorie', value: 'category' },
|
||||
{ label: 'Nach Tag', value: 'tag' },
|
||||
{ label: 'Neueste Artikel', value: 'latest' },
|
||||
{ label: 'Beliebteste Artikel', value: 'popular' },
|
||||
{ label: 'Vom gleichen Autor', value: 'author' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'posts',
|
||||
type: 'relationship',
|
||||
relationTo: 'posts',
|
||||
hasMany: true,
|
||||
label: 'Artikel',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'manual',
|
||||
description: 'Wählen Sie die anzuzeigenden Artikel',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'relationship',
|
||||
relationTo: 'categories',
|
||||
label: 'Kategorie',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'category',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'tag',
|
||||
type: 'relationship',
|
||||
relationTo: 'tags',
|
||||
label: 'Tag',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'tag',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
defaultValue: 3,
|
||||
min: 1,
|
||||
max: 12,
|
||||
label: 'Anzahl Artikel',
|
||||
},
|
||||
{
|
||||
name: 'excludeCurrent',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Aktuellen Artikel ausschließen',
|
||||
admin: {
|
||||
description: 'Den Artikel, auf dem dieser Block ist, nicht anzeigen',
|
||||
},
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'grid',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Grid', value: 'grid' },
|
||||
{ label: 'Liste', value: 'list' },
|
||||
{ label: 'Slider', value: 'slider' },
|
||||
{ label: 'Kompakt', value: 'compact' },
|
||||
{ label: 'Cards', value: 'cards' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cols',
|
||||
type: 'select',
|
||||
defaultValue: '3',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '2 Spalten', value: '2' },
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
{ label: '4 Spalten', value: '4' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.layout === 'grid' || siblingData?.layout === 'cards',
|
||||
},
|
||||
},
|
||||
// Anzuzeigende Elemente
|
||||
{
|
||||
name: 'show',
|
||||
type: 'group',
|
||||
label: 'Anzeigen',
|
||||
fields: [
|
||||
{
|
||||
name: 'image',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Beitragsbild',
|
||||
},
|
||||
{
|
||||
name: 'date',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Datum',
|
||||
},
|
||||
{
|
||||
name: 'author',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Autor',
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Kategorie',
|
||||
},
|
||||
{
|
||||
name: 'excerpt',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Kurzfassung',
|
||||
},
|
||||
{
|
||||
name: 'readingTime',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Lesezeit',
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Tags',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'style',
|
||||
type: 'group',
|
||||
label: 'Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'imgRatio',
|
||||
type: 'select',
|
||||
defaultValue: '16-9',
|
||||
label: 'Bild-Verhältnis',
|
||||
options: [
|
||||
{ label: 'Quadratisch (1:1)', value: 'square' },
|
||||
{ label: 'Querformat (4:3)', value: '4-3' },
|
||||
{ label: 'Breit (16:9)', value: '16-9' },
|
||||
{ label: 'Hochformat (3:4)', value: '3-4' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'rounded',
|
||||
type: 'select',
|
||||
defaultValue: 'md',
|
||||
label: 'Ecken',
|
||||
options: [
|
||||
{ label: 'Keine', value: 'none' },
|
||||
{ label: 'Klein', value: 'sm' },
|
||||
{ label: 'Mittel', value: 'md' },
|
||||
{ label: 'Groß', value: 'lg' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'shadow',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Schatten',
|
||||
},
|
||||
{
|
||||
name: 'hover',
|
||||
type: 'select',
|
||||
defaultValue: 'lift',
|
||||
label: 'Hover-Effekt',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Anheben', value: 'lift' },
|
||||
{ label: 'Zoom', value: 'zoom' },
|
||||
{ label: 'Overlay', value: 'overlay' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'bg',
|
||||
type: 'select',
|
||||
defaultValue: 'none',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'gap',
|
||||
type: 'select',
|
||||
defaultValue: '24',
|
||||
label: 'Abstand',
|
||||
options: [
|
||||
{ label: 'Klein (16px)', value: '16' },
|
||||
{ label: 'Normal (24px)', value: '24' },
|
||||
{ label: 'Groß (32px)', value: '32' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// CTA
|
||||
{
|
||||
name: 'showAllLink',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: '"Alle anzeigen" Link',
|
||||
},
|
||||
{
|
||||
name: 'allLinkText',
|
||||
type: 'text',
|
||||
label: 'Link-Text',
|
||||
localized: true,
|
||||
defaultValue: 'Alle Artikel anzeigen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showAllLink,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'allLinkUrl',
|
||||
type: 'text',
|
||||
label: 'Link-URL',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showAllLink,
|
||||
description: 'z.B. /blog oder /news',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
306
src/blocks/ShareButtonsBlock.ts
Normal file
306
src/blocks/ShareButtonsBlock.ts
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* ShareButtonsBlock
|
||||
*
|
||||
* Social Sharing Buttons für Blog-Posts und andere Inhalte.
|
||||
* Unterstützt verschiedene Plattformen und Layouts.
|
||||
*/
|
||||
export const ShareButtonsBlock: Block = {
|
||||
slug: 'share-buttons-block',
|
||||
labels: {
|
||||
singular: 'Teilen-Buttons',
|
||||
plural: 'Teilen-Buttons',
|
||||
},
|
||||
imageURL: '/assets/blocks/share-buttons.png',
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
label: 'Label',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Teilen:", "Artikel teilen" (optional)',
|
||||
},
|
||||
},
|
||||
// Plattformen
|
||||
{
|
||||
name: 'platforms',
|
||||
type: 'group',
|
||||
label: 'Plattformen',
|
||||
fields: [
|
||||
{
|
||||
name: 'facebook',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Facebook',
|
||||
},
|
||||
{
|
||||
name: 'twitter',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Twitter/X',
|
||||
},
|
||||
{
|
||||
name: 'linkedin',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'LinkedIn',
|
||||
},
|
||||
{
|
||||
name: 'xing',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Xing',
|
||||
},
|
||||
{
|
||||
name: 'whatsapp',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'WhatsApp',
|
||||
},
|
||||
{
|
||||
name: 'telegram',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Telegram',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'E-Mail',
|
||||
},
|
||||
{
|
||||
name: 'copy',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Link kopieren',
|
||||
},
|
||||
{
|
||||
name: 'print',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Drucken',
|
||||
},
|
||||
{
|
||||
name: 'pinterest',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Pinterest',
|
||||
},
|
||||
{
|
||||
name: 'reddit',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Reddit',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'horizontal',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Horizontal', value: 'horizontal' },
|
||||
{ label: 'Vertikal', value: 'vertical' },
|
||||
{ label: 'Floating (Seitenrand)', value: 'floating' },
|
||||
{ label: 'Sticky (unten)', value: 'sticky' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'align',
|
||||
type: 'select',
|
||||
defaultValue: 'left',
|
||||
label: 'Ausrichtung',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Mitte', value: 'center' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.layout === 'horizontal' || siblingData?.layout === 'vertical',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'floatSide',
|
||||
type: 'select',
|
||||
defaultValue: 'left',
|
||||
label: 'Seite',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.layout === 'floating',
|
||||
},
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'style',
|
||||
type: 'group',
|
||||
label: 'Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'variant',
|
||||
type: 'select',
|
||||
defaultValue: 'filled',
|
||||
label: 'Button-Stil',
|
||||
options: [
|
||||
{ label: 'Gefüllt', value: 'filled' },
|
||||
{ label: 'Outline', value: 'outline' },
|
||||
{ label: 'Nur Icon', value: 'icon' },
|
||||
{ label: 'Minimal', value: 'minimal' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
type: 'select',
|
||||
defaultValue: 'md',
|
||||
label: 'Größe',
|
||||
options: [
|
||||
{ label: 'Klein', value: 'sm' },
|
||||
{ label: 'Mittel', value: 'md' },
|
||||
{ label: 'Groß', value: 'lg' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'shape',
|
||||
type: 'select',
|
||||
defaultValue: 'rounded',
|
||||
label: 'Form',
|
||||
options: [
|
||||
{ label: 'Eckig', value: 'square' },
|
||||
{ label: 'Abgerundet', value: 'rounded' },
|
||||
{ label: 'Rund', value: 'circle' },
|
||||
{ label: 'Pill', value: 'pill' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'colorScheme',
|
||||
type: 'select',
|
||||
defaultValue: 'brand',
|
||||
label: 'Farbschema',
|
||||
options: [
|
||||
{ label: 'Plattform-Farben', value: 'brand' },
|
||||
{ label: 'Einheitlich (Grau)', value: 'gray' },
|
||||
{ label: 'Einheitlich (Dunkel)', value: 'dark' },
|
||||
{ label: 'Einheitlich (Hell)', value: 'light' },
|
||||
{ label: 'Theme-Akzent', value: 'accent' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'gap',
|
||||
type: 'select',
|
||||
defaultValue: '8',
|
||||
label: 'Abstand',
|
||||
options: [
|
||||
{ label: 'Kein Abstand', value: '0' },
|
||||
{ label: 'Klein (4px)', value: '4' },
|
||||
{ label: 'Normal (8px)', value: '8' },
|
||||
{ label: 'Groß (12px)', value: '12' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'showLabel',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Plattform-Namen anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showCount',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Share-Zähler anzeigen',
|
||||
admin: {
|
||||
description: 'Zeigt Anzahl der Shares (sofern verfügbar)',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Verhalten
|
||||
{
|
||||
name: 'behavior',
|
||||
type: 'group',
|
||||
label: 'Verhalten',
|
||||
fields: [
|
||||
{
|
||||
name: 'openInPopup',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'In Popup öffnen',
|
||||
admin: {
|
||||
description: 'Share-Dialoge in kleinem Popup statt neuem Tab',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'useNativeShare',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Native Share API nutzen',
|
||||
admin: {
|
||||
description: 'Auf mobilen Geräten den System-Share-Dialog verwenden',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'copyFeedback',
|
||||
type: 'text',
|
||||
label: 'Kopiert-Feedback',
|
||||
localized: true,
|
||||
defaultValue: 'Link kopiert!',
|
||||
admin: {
|
||||
description: 'Text der angezeigt wird nach dem Kopieren',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Share-Inhalt
|
||||
{
|
||||
name: 'content',
|
||||
type: 'group',
|
||||
label: 'Share-Inhalt',
|
||||
admin: {
|
||||
description: 'Überschreibt automatische Werte (optional)',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'customTitle',
|
||||
type: 'text',
|
||||
label: 'Titel',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Leer lassen für automatischen Seitentitel',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'customDescription',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Leer lassen für automatische Meta-Description',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'hashtags',
|
||||
type: 'text',
|
||||
label: 'Hashtags',
|
||||
admin: {
|
||||
description: 'Komma-getrennt, ohne # (z.B. "blog,news,tech")',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'via',
|
||||
type: 'text',
|
||||
label: 'Twitter Via',
|
||||
admin: {
|
||||
description: 'Twitter-Handle ohne @ (z.B. "c2sgmbh")',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
287
src/blocks/TableOfContentsBlock.ts
Normal file
287
src/blocks/TableOfContentsBlock.ts
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* TableOfContentsBlock
|
||||
*
|
||||
* Automatisches Inhaltsverzeichnis für lange Blog-Posts.
|
||||
* Extrahiert Überschriften aus dem Content und erstellt Navigation.
|
||||
*/
|
||||
export const TableOfContentsBlock: Block = {
|
||||
slug: 'toc-block',
|
||||
labels: {
|
||||
singular: 'Inhaltsverzeichnis',
|
||||
plural: 'Inhaltsverzeichnisse',
|
||||
},
|
||||
imageURL: '/assets/blocks/table-of-contents.png',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
defaultValue: 'Inhaltsverzeichnis',
|
||||
},
|
||||
// Überschriften-Level
|
||||
{
|
||||
name: 'levels',
|
||||
type: 'group',
|
||||
label: 'Überschriften-Level',
|
||||
admin: {
|
||||
description: 'Welche Überschriften-Ebenen einschließen?',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'h2',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'H2',
|
||||
},
|
||||
{
|
||||
name: 'h3',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'H3',
|
||||
},
|
||||
{
|
||||
name: 'h4',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'H4',
|
||||
},
|
||||
{
|
||||
name: 'h5',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'H5',
|
||||
},
|
||||
{
|
||||
name: 'h6',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'H6',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'list',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Liste', value: 'list' },
|
||||
{ label: 'Nummeriert', value: 'numbered' },
|
||||
{ label: 'Kompakt (Inline)', value: 'inline' },
|
||||
{ label: 'Sidebar (Sticky)', value: 'sidebar' },
|
||||
{ label: 'Dropdown/Akkordeon', value: 'dropdown' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'sidebarPos',
|
||||
type: 'select',
|
||||
defaultValue: 'right',
|
||||
label: 'Sidebar-Position',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.layout === 'sidebar',
|
||||
},
|
||||
},
|
||||
// Verhalten
|
||||
{
|
||||
name: 'behavior',
|
||||
type: 'group',
|
||||
label: 'Verhalten',
|
||||
fields: [
|
||||
{
|
||||
name: 'smoothScroll',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Sanftes Scrollen',
|
||||
},
|
||||
{
|
||||
name: 'highlightActive',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Aktiven Abschnitt markieren',
|
||||
admin: {
|
||||
description: 'Markiert den aktuell sichtbaren Abschnitt',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'scrollOffset',
|
||||
type: 'number',
|
||||
defaultValue: 80,
|
||||
label: 'Scroll-Offset (px)',
|
||||
admin: {
|
||||
description: 'Abstand zum oberen Rand nach dem Scrollen (für Fixed Headers)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'collapsible',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Einklappbar',
|
||||
admin: {
|
||||
description: 'User kann das Inhaltsverzeichnis ein-/ausklappen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'startCollapsed',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Anfangs eingeklappt',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.collapsible,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showProgress',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Lesefortschritt anzeigen',
|
||||
admin: {
|
||||
description: 'Fortschrittsbalken oder Prozent-Anzeige',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'progressStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'bar',
|
||||
label: 'Fortschritts-Stil',
|
||||
options: [
|
||||
{ label: 'Balken', value: 'bar' },
|
||||
{ label: 'Prozent', value: 'percent' },
|
||||
{ label: 'Kreis', value: 'circle' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showProgress,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'style',
|
||||
type: 'group',
|
||||
label: 'Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'bg',
|
||||
type: 'select',
|
||||
defaultValue: 'light',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Akzent', value: 'accent' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'border',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Rahmen',
|
||||
},
|
||||
{
|
||||
name: 'borderSide',
|
||||
type: 'select',
|
||||
defaultValue: 'left',
|
||||
label: 'Rahmen-Seite',
|
||||
options: [
|
||||
{ label: 'Alle', value: 'all' },
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Oben', value: 'top' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.border,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'rounded',
|
||||
type: 'select',
|
||||
defaultValue: 'md',
|
||||
label: 'Ecken',
|
||||
options: [
|
||||
{ label: 'Keine', value: 'none' },
|
||||
{ label: 'Klein', value: 'sm' },
|
||||
{ label: 'Mittel', value: 'md' },
|
||||
{ label: 'Groß', value: 'lg' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'shadow',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Schatten',
|
||||
},
|
||||
{
|
||||
name: 'indent',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Sub-Überschriften einrücken',
|
||||
},
|
||||
{
|
||||
name: 'showIcon',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Icons anzeigen',
|
||||
admin: {
|
||||
description: 'Link-Symbol neben Einträgen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fontSize',
|
||||
type: 'select',
|
||||
defaultValue: 'sm',
|
||||
label: 'Schriftgröße',
|
||||
options: [
|
||||
{ label: 'Klein', value: 'sm' },
|
||||
{ label: 'Normal', value: 'base' },
|
||||
{ label: 'Groß', value: 'lg' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'lineHeight',
|
||||
type: 'select',
|
||||
defaultValue: 'relaxed',
|
||||
label: 'Zeilenhöhe',
|
||||
options: [
|
||||
{ label: 'Eng', value: 'tight' },
|
||||
{ label: 'Normal', value: 'normal' },
|
||||
{ label: 'Locker', value: 'relaxed' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Limits
|
||||
{
|
||||
name: 'minItems',
|
||||
type: 'number',
|
||||
defaultValue: 3,
|
||||
label: 'Mindestanzahl',
|
||||
admin: {
|
||||
description: 'Inhaltsverzeichnis nur anzeigen, wenn mindestens X Einträge',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'maxItems',
|
||||
type: 'number',
|
||||
label: 'Maximalanzahl',
|
||||
admin: {
|
||||
description: 'Maximale Anzahl angezeigter Einträge (0 = unbegrenzt)',
|
||||
},
|
||||
},
|
||||
// Accessibility
|
||||
{
|
||||
name: 'a11yLabel',
|
||||
type: 'text',
|
||||
label: 'ARIA Label',
|
||||
localized: true,
|
||||
defaultValue: 'Inhaltsverzeichnis',
|
||||
},
|
||||
],
|
||||
}
|
||||
381
src/blocks/TeamFilterBlock.ts
Normal file
381
src/blocks/TeamFilterBlock.ts
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* TeamFilterBlock
|
||||
*
|
||||
* Interaktive Team-Anzeige mit Filter- und Suchfunktion.
|
||||
* Ermöglicht Filterung nach Abteilung, Fachgebiet, Sprache.
|
||||
*/
|
||||
export const TeamFilterBlock: Block = {
|
||||
slug: 'team-filter-block',
|
||||
labels: {
|
||||
singular: 'Team mit Filter',
|
||||
plural: 'Team mit Filter',
|
||||
},
|
||||
imageURL: '/assets/blocks/team-filter.png',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
defaultValue: 'Unser Team',
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
// Filter-Optionen
|
||||
{
|
||||
name: 'filters',
|
||||
type: 'group',
|
||||
label: 'Filter-Optionen',
|
||||
fields: [
|
||||
{
|
||||
name: 'showSearch',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Suchfeld anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'searchPlaceholder',
|
||||
type: 'text',
|
||||
label: 'Such-Platzhalter',
|
||||
localized: true,
|
||||
defaultValue: 'Team durchsuchen...',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showSearch,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showDepartment',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Abteilungs-Filter',
|
||||
},
|
||||
{
|
||||
name: 'showSpecialization',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Fachgebiets-Filter',
|
||||
},
|
||||
{
|
||||
name: 'showLanguage',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Sprach-Filter',
|
||||
},
|
||||
{
|
||||
name: 'showHierarchy',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Hierarchie-Filter',
|
||||
},
|
||||
{
|
||||
name: 'filterLayout',
|
||||
type: 'select',
|
||||
defaultValue: 'horizontal',
|
||||
label: 'Filter-Layout',
|
||||
options: [
|
||||
{ label: 'Horizontal (nebeneinander)', value: 'horizontal' },
|
||||
{ label: 'Vertikal (Sidebar)', value: 'sidebar' },
|
||||
{ label: 'Dropdown-Menü', value: 'dropdown' },
|
||||
{ label: 'Tabs', value: 'tabs' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'filterStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'buttons',
|
||||
label: 'Filter-Stil',
|
||||
options: [
|
||||
{ label: 'Buttons/Chips', value: 'buttons' },
|
||||
{ label: 'Dropdown', value: 'select' },
|
||||
{ label: 'Checkboxen', value: 'checkbox' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.filterLayout !== 'tabs' && siblingData?.filterLayout !== 'dropdown',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showResultCount',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Ergebnis-Anzahl anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showResetButton',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Filter-Reset-Button',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Anzeige-Optionen
|
||||
{
|
||||
name: 'display',
|
||||
type: 'group',
|
||||
label: 'Anzeige',
|
||||
fields: [
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'grid',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Grid', value: 'grid' },
|
||||
{ label: 'Liste', value: 'list' },
|
||||
{ label: 'Kompakt', value: 'compact' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
defaultValue: '3',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '2 Spalten', value: '2' },
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
{ label: '4 Spalten', value: '4' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.layout === 'grid',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'initialLimit',
|
||||
type: 'number',
|
||||
defaultValue: 12,
|
||||
min: 4,
|
||||
max: 50,
|
||||
label: 'Initiale Anzahl',
|
||||
},
|
||||
{
|
||||
name: 'loadMore',
|
||||
type: 'select',
|
||||
defaultValue: 'button',
|
||||
label: 'Mehr laden',
|
||||
options: [
|
||||
{ label: '"Mehr laden" Button', value: 'button' },
|
||||
{ label: 'Infinite Scroll', value: 'infinite' },
|
||||
{ label: 'Pagination', value: 'pagination' },
|
||||
{ label: 'Alle anzeigen', value: 'all' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'loadMoreText',
|
||||
type: 'text',
|
||||
label: 'Button-Text',
|
||||
localized: true,
|
||||
defaultValue: 'Mehr anzeigen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.loadMore === 'button',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Member-Card Optionen
|
||||
{
|
||||
name: 'card',
|
||||
type: 'group',
|
||||
label: 'Mitglieder-Karte',
|
||||
fields: [
|
||||
{
|
||||
name: 'showImage',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Foto anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'imageStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'circle',
|
||||
label: 'Foto-Stil',
|
||||
options: [
|
||||
{ label: 'Rund', value: 'circle' },
|
||||
{ label: 'Abgerundet', value: 'rounded' },
|
||||
{ label: 'Quadratisch', value: 'square' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showImage,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showRole',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Position anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showDepartment',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Abteilung anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showBio',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Kurzbiografie anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showContact',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Kontaktdaten anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showSocial',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Social Links anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showSpecializations',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Fachgebiete als Tags',
|
||||
},
|
||||
{
|
||||
name: 'showLanguages',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Sprachen anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showVCard',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'vCard-Download anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'linkToProfile',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Zur Profilseite verlinken',
|
||||
},
|
||||
{
|
||||
name: 'profileBasePath',
|
||||
type: 'text',
|
||||
label: 'Profil-Basis-Pfad',
|
||||
defaultValue: '/team',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.linkToProfile,
|
||||
description: 'z.B. "/team" ergibt "/team/max-mustermann"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'enableModal',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Detail-Modal aktivieren',
|
||||
admin: {
|
||||
description: 'Klick öffnet Modal statt Profilseite',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'style',
|
||||
type: 'group',
|
||||
label: 'Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'bg',
|
||||
type: 'select',
|
||||
defaultValue: 'none',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cardBg',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
label: 'Karten-Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Transparent', value: 'transparent' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cardShadow',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Karten-Schatten',
|
||||
},
|
||||
{
|
||||
name: 'cardHover',
|
||||
type: 'select',
|
||||
defaultValue: 'lift',
|
||||
label: 'Hover-Effekt',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Anheben', value: 'lift' },
|
||||
{ label: 'Schatten verstärken', value: 'shadow' },
|
||||
{ label: 'Rand hervorheben', value: 'border' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'gap',
|
||||
type: 'select',
|
||||
defaultValue: '24',
|
||||
label: 'Abstand',
|
||||
options: [
|
||||
{ label: 'Klein (16px)', value: '16' },
|
||||
{ label: 'Normal (24px)', value: '24' },
|
||||
{ label: 'Groß (32px)', value: '32' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'animation',
|
||||
type: 'select',
|
||||
defaultValue: 'fade',
|
||||
label: 'Filter-Animation',
|
||||
options: [
|
||||
{ label: 'Keine', value: 'none' },
|
||||
{ label: 'Fade', value: 'fade' },
|
||||
{ label: 'Slide', value: 'slide' },
|
||||
{ label: 'Scale', value: 'scale' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Empty State
|
||||
{
|
||||
name: 'emptyState',
|
||||
type: 'group',
|
||||
label: 'Keine Ergebnisse',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
defaultValue: 'Keine Mitarbeiter gefunden',
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
type: 'textarea',
|
||||
label: 'Nachricht',
|
||||
localized: true,
|
||||
defaultValue: 'Versuchen Sie andere Filterkriterien.',
|
||||
},
|
||||
{
|
||||
name: 'showResetButton',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Filter-Reset anzeigen',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -19,3 +19,13 @@ export { ProcessStepsBlock } from './ProcessStepsBlock'
|
|||
export { FAQBlock } from './FAQBlock'
|
||||
export { TeamBlock } from './TeamBlock'
|
||||
export { ServicesBlock } from './ServicesBlock'
|
||||
|
||||
// Blogging Blocks
|
||||
export { AuthorBioBlock } from './AuthorBioBlock'
|
||||
export { RelatedPostsBlock } from './RelatedPostsBlock'
|
||||
export { ShareButtonsBlock } from './ShareButtonsBlock'
|
||||
export { TableOfContentsBlock } from './TableOfContentsBlock'
|
||||
|
||||
// Team Blocks
|
||||
export { TeamFilterBlock } from './TeamFilterBlock'
|
||||
export { OrgChartBlock } from './OrgChartBlock'
|
||||
|
|
|
|||
211
src/collections/Authors.ts
Normal file
211
src/collections/Authors.ts
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
/**
|
||||
* Authors Collection
|
||||
*
|
||||
* Blog-Autoren mit Bio, Bild und Social Links.
|
||||
* Kann optional mit Team-Mitgliedern verknüpft werden.
|
||||
*/
|
||||
export const Authors: CollectionConfig = {
|
||||
slug: 'authors',
|
||||
labels: {
|
||||
singular: 'Autor',
|
||||
plural: 'Autoren',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
group: 'Content',
|
||||
defaultColumns: ['name', 'slug', 'postCount', 'isActive', 'updatedAt'],
|
||||
description: 'Blog-Autoren und Gastautoren',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
create: ({ req }) => !!req.user,
|
||||
update: ({ req }) => !!req.user,
|
||||
delete: ({ req }) => !!req.user,
|
||||
},
|
||||
fields: [
|
||||
// Basis-Informationen
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Name',
|
||||
admin: {
|
||||
description: 'Anzeigename des Autors',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
label: 'Slug',
|
||||
admin: {
|
||||
description: 'URL-freundlicher Identifier (z.B. "max-mustermann")',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'avatar',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Profilbild',
|
||||
admin: {
|
||||
description: 'Avatar/Profilbild (empfohlen: quadratisch, min. 200x200px)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bio',
|
||||
type: 'richText',
|
||||
label: 'Biografie',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Ausführliche Biografie für Autorenseite',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bioShort',
|
||||
type: 'textarea',
|
||||
label: 'Kurzbiografie',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Ein bis zwei Sätze für Anzeige unter Artikeln',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Titel/Position',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Senior Editor", "Gastautor", "Chefredakteur"',
|
||||
},
|
||||
},
|
||||
// Kontakt & Social
|
||||
{
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
label: 'E-Mail',
|
||||
admin: {
|
||||
description: 'Öffentliche Kontakt-E-Mail (optional)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'website',
|
||||
type: 'text',
|
||||
label: 'Website',
|
||||
admin: {
|
||||
description: 'Persönliche Website oder Blog',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'social',
|
||||
type: 'group',
|
||||
label: 'Social Media',
|
||||
fields: [
|
||||
{
|
||||
name: 'twitter',
|
||||
type: 'text',
|
||||
label: 'Twitter/X',
|
||||
admin: {
|
||||
description: 'Twitter-Handle ohne @ (z.B. "maxmustermann")',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkedin',
|
||||
type: 'text',
|
||||
label: 'LinkedIn',
|
||||
admin: {
|
||||
description: 'LinkedIn-Profil-URL oder Username',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'github',
|
||||
type: 'text',
|
||||
label: 'GitHub',
|
||||
admin: {
|
||||
description: 'GitHub-Username',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'instagram',
|
||||
type: 'text',
|
||||
label: 'Instagram',
|
||||
admin: {
|
||||
description: 'Instagram-Handle ohne @',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Verknüpfungen
|
||||
{
|
||||
name: 'linkedTeam',
|
||||
type: 'relationship',
|
||||
relationTo: 'team',
|
||||
label: 'Team-Mitglied',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Optional: Verknüpfung mit Team-Eintrag',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkedUser',
|
||||
type: 'relationship',
|
||||
relationTo: 'users',
|
||||
label: 'Benutzer',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Optional: Verknüpfung mit Login-User',
|
||||
},
|
||||
},
|
||||
// Status
|
||||
{
|
||||
name: 'isActive',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Aktiv',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Inaktive Autoren erscheinen nicht in Listen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isGuest',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Gastautor',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Markierung für Gastautoren',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'featured',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Hervorgehoben',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Für besondere Darstellung auf Autorenseite',
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => {
|
||||
// Auto-generate slug from name if not provided
|
||||
if (data && !data.slug && data.name) {
|
||||
data.slug = data.name
|
||||
.toLowerCase()
|
||||
.replace(/[äöüß]/g, (match: string) => {
|
||||
const map: Record<string, string> = { ä: 'ae', ö: 'oe', ü: 'ue', ß: 'ss' }
|
||||
return map[match] || match
|
||||
})
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
}
|
||||
return data
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
@ -20,6 +20,14 @@ import {
|
|||
FAQBlock,
|
||||
TeamBlock,
|
||||
ServicesBlock,
|
||||
// Blogging Blocks
|
||||
AuthorBioBlock,
|
||||
RelatedPostsBlock,
|
||||
ShareButtonsBlock,
|
||||
TableOfContentsBlock,
|
||||
// Team Blocks
|
||||
TeamFilterBlock,
|
||||
OrgChartBlock,
|
||||
} from '../blocks'
|
||||
import { pagesAccess } from '../lib/access'
|
||||
|
||||
|
|
@ -94,6 +102,14 @@ export const Pages: CollectionConfig = {
|
|||
FAQBlock,
|
||||
TeamBlock,
|
||||
ServicesBlock,
|
||||
// Blogging Blocks
|
||||
AuthorBioBlock,
|
||||
RelatedPostsBlock,
|
||||
ShareButtonsBlock,
|
||||
TableOfContentsBlock,
|
||||
// Team Blocks
|
||||
TeamFilterBlock,
|
||||
OrgChartBlock,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,36 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { authenticatedOnly, tenantScopedPublicRead } from '../lib/tenantAccess'
|
||||
|
||||
/**
|
||||
* Berechnet die geschätzte Lesezeit basierend auf Wortanzahl
|
||||
* Durchschnitt: ~200 Wörter pro Minute
|
||||
*/
|
||||
function calculateReadingTime(content: unknown): number {
|
||||
if (!content) return 1
|
||||
|
||||
// RichText zu Plain Text konvertieren (vereinfacht)
|
||||
let text = ''
|
||||
const extractText = (node: unknown): void => {
|
||||
if (!node || typeof node !== 'object') return
|
||||
const n = node as Record<string, unknown>
|
||||
if (n.text && typeof n.text === 'string') {
|
||||
text += n.text + ' '
|
||||
}
|
||||
if (Array.isArray(n.children)) {
|
||||
n.children.forEach(extractText)
|
||||
}
|
||||
if (n.root && typeof n.root === 'object') {
|
||||
extractText(n.root)
|
||||
}
|
||||
}
|
||||
|
||||
extractText(content)
|
||||
|
||||
const words = text.trim().split(/\s+/).filter(Boolean).length
|
||||
const minutes = Math.ceil(words / 200)
|
||||
return Math.max(1, minutes)
|
||||
}
|
||||
|
||||
export const Posts: CollectionConfig = {
|
||||
slug: 'posts',
|
||||
admin: {
|
||||
|
|
@ -86,11 +116,55 @@ export const Posts: CollectionConfig = {
|
|||
type: 'relationship',
|
||||
relationTo: 'categories',
|
||||
hasMany: true,
|
||||
label: 'Kategorien',
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
type: 'relationship',
|
||||
relationTo: 'tags',
|
||||
hasMany: true,
|
||||
label: 'Tags',
|
||||
admin: {
|
||||
description: 'Schlagwörter für bessere Auffindbarkeit',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'author',
|
||||
type: 'text',
|
||||
type: 'relationship',
|
||||
relationTo: 'authors',
|
||||
label: 'Autor',
|
||||
admin: {
|
||||
description: 'Hauptautor des Beitrags',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'coAuthors',
|
||||
type: 'relationship',
|
||||
relationTo: 'authors',
|
||||
hasMany: true,
|
||||
label: 'Co-Autoren',
|
||||
admin: {
|
||||
description: 'Weitere beteiligte Autoren',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'authorLegacy',
|
||||
type: 'text',
|
||||
label: 'Autor (Legacy)',
|
||||
admin: {
|
||||
description: 'Freitext-Autor für ältere Beiträge ohne Autoren-Eintrag',
|
||||
condition: (_, siblingData) => !siblingData?.author,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'readingTime',
|
||||
type: 'number',
|
||||
label: 'Lesezeit (Minuten)',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Wird automatisch berechnet',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'status',
|
||||
|
|
@ -143,4 +217,15 @@ export const Posts: CollectionConfig = {
|
|||
],
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => {
|
||||
// Automatische Lesezeit-Berechnung
|
||||
if (data?.content) {
|
||||
data.readingTime = calculateReadingTime(data.content)
|
||||
}
|
||||
return data
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
|||
81
src/collections/Tags.ts
Normal file
81
src/collections/Tags.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
|
||||
/**
|
||||
* Tags Collection
|
||||
*
|
||||
* Schlagwörter für Blog-Posts und andere Inhalte.
|
||||
* Flexibler als Kategorien, für freie Verschlagwortung.
|
||||
*/
|
||||
export const Tags: CollectionConfig = {
|
||||
slug: 'tags',
|
||||
labels: {
|
||||
singular: 'Tag',
|
||||
plural: 'Tags',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
group: 'Content',
|
||||
defaultColumns: ['name', 'slug', 'postCount', 'updatedAt'],
|
||||
description: 'Schlagwörter für Blog-Posts',
|
||||
},
|
||||
access: {
|
||||
read: () => true,
|
||||
create: ({ req }) => !!req.user,
|
||||
update: ({ req }) => !!req.user,
|
||||
delete: ({ req }) => !!req.user,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
label: 'Name',
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
label: 'Slug',
|
||||
admin: {
|
||||
description: 'URL-freundlicher Identifier (z.B. "javascript")',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
localized: true,
|
||||
label: 'Beschreibung',
|
||||
admin: {
|
||||
description: 'Optionale Beschreibung für Tag-Archivseiten',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'color',
|
||||
type: 'text',
|
||||
label: 'Farbe',
|
||||
admin: {
|
||||
description: 'Optionale Farbe für Tag-Badge (z.B. "#3B82F6" oder "blue")',
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => {
|
||||
// Auto-generate slug from name if not provided
|
||||
if (data && !data.slug && data.name) {
|
||||
data.slug = data.name
|
||||
.toLowerCase()
|
||||
.replace(/[äöüß]/g, (match: string) => {
|
||||
const map: Record<string, string> = { ä: 'ae', ö: 'oe', ü: 'ue', ß: 'ss' }
|
||||
return map[match] || match
|
||||
})
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
}
|
||||
return data
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
@ -34,6 +34,16 @@ export const Team: CollectionConfig = {
|
|||
description: 'Vollständiger Name',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
label: 'Slug',
|
||||
admin: {
|
||||
description: 'URL-freundlicher Identifier (z.B. "max-mustermann")',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
type: 'text',
|
||||
|
|
@ -273,5 +283,61 @@ export const Team: CollectionConfig = {
|
|||
description: 'Eintrittsdatum (optional)',
|
||||
},
|
||||
},
|
||||
// Hierarchie für Organigramm
|
||||
{
|
||||
name: 'reportsTo',
|
||||
type: 'relationship',
|
||||
relationTo: 'team',
|
||||
label: 'Vorgesetzter',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Direkter Vorgesetzter (für Organigramm)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'hierarchyLevel',
|
||||
type: 'select',
|
||||
label: 'Hierarchie-Ebene',
|
||||
options: [
|
||||
{ label: 'Geschäftsführung', value: 'executive' },
|
||||
{ label: 'Abteilungsleitung', value: 'department_head' },
|
||||
{ label: 'Teamleitung', value: 'team_lead' },
|
||||
{ label: 'Mitarbeiter', value: 'employee' },
|
||||
{ label: 'Praktikant/Azubi', value: 'trainee' },
|
||||
],
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Für Organigramm-Darstellung',
|
||||
},
|
||||
},
|
||||
// vCard Export erlauben
|
||||
{
|
||||
name: 'allowVCard',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'vCard-Export erlauben',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Kontaktkarte zum Download anbieten',
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => {
|
||||
// Auto-generate slug from name if not provided
|
||||
if (data && !data.slug && data.name) {
|
||||
data.slug = data.name
|
||||
.toLowerCase()
|
||||
.replace(/[äöüß]/g, (match: string) => {
|
||||
const map: Record<string, string> = { ä: 'ae', ö: 'oe', ü: 'ue', ß: 'ss' }
|
||||
return map[match] || match
|
||||
})
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
}
|
||||
return data
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
|
|||
735
src/migrations/20251213_220000_blogging_collections.ts
Normal file
735
src/migrations/20251213_220000_blogging_collections.ts
Normal file
|
|
@ -0,0 +1,735 @@
|
|||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
/**
|
||||
* Migration: Blogging Collections & Blocks
|
||||
*
|
||||
* Erstellt:
|
||||
* - Tags Collection
|
||||
* - Authors Collection
|
||||
* - Posts-Erweiterungen (Tags, Author Relations, Reading Time)
|
||||
* - AuthorBioBlock, RelatedPostsBlock, ShareButtonsBlock, TableOfContentsBlock
|
||||
*/
|
||||
|
||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||
// ========================================
|
||||
// Tags Collection
|
||||
// ========================================
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "tags" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"slug" varchar NOT NULL UNIQUE,
|
||||
"color" varchar,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "tags_locales" (
|
||||
"name" varchar NOT NULL,
|
||||
"description" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" integer NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "tags_locales"
|
||||
ADD CONSTRAINT "tags_locales_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."tags"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "tags_locales_locale_parent_id_unique"
|
||||
ON "tags_locales" USING btree ("_locale", "_parent_id");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "tags_created_at_idx"
|
||||
ON "tags" USING btree ("created_at");
|
||||
`)
|
||||
|
||||
// Tags Tenant Column (für Multi-Tenant Plugin)
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "tags" ADD COLUMN IF NOT EXISTS "tenant_id" integer;
|
||||
|
||||
ALTER TABLE "tags"
|
||||
ADD CONSTRAINT "tags_tenant_id_fk"
|
||||
FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE set null ON UPDATE no action;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "tags_tenant_idx"
|
||||
ON "tags" USING btree ("tenant_id");
|
||||
`)
|
||||
|
||||
// ========================================
|
||||
// Authors Collection
|
||||
// ========================================
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "authors" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"name" varchar NOT NULL,
|
||||
"slug" varchar NOT NULL UNIQUE,
|
||||
"avatar_id" integer,
|
||||
"email" varchar,
|
||||
"website" varchar,
|
||||
"social_twitter" varchar,
|
||||
"social_linkedin" varchar,
|
||||
"social_github" varchar,
|
||||
"social_instagram" varchar,
|
||||
"linked_team_id" integer,
|
||||
"linked_user_id" integer,
|
||||
"is_active" boolean DEFAULT true,
|
||||
"is_guest" boolean DEFAULT false,
|
||||
"featured" boolean DEFAULT false,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "authors_locales" (
|
||||
"bio" jsonb,
|
||||
"bio_short" varchar,
|
||||
"title" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" integer NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "authors"
|
||||
ADD CONSTRAINT "authors_avatar_id_fk"
|
||||
FOREIGN KEY ("avatar_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "authors"
|
||||
ADD CONSTRAINT "authors_linked_team_id_fk"
|
||||
FOREIGN KEY ("linked_team_id") REFERENCES "public"."team"("id") ON DELETE set null ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "authors"
|
||||
ADD CONSTRAINT "authors_linked_user_id_fk"
|
||||
FOREIGN KEY ("linked_user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "authors_locales"
|
||||
ADD CONSTRAINT "authors_locales_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."authors"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "authors_locales_locale_parent_id_unique"
|
||||
ON "authors_locales" USING btree ("_locale", "_parent_id");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "authors_avatar_idx"
|
||||
ON "authors" USING btree ("avatar_id");
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "authors_created_at_idx"
|
||||
ON "authors" USING btree ("created_at");
|
||||
`)
|
||||
|
||||
// Authors Tenant Column
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "authors" ADD COLUMN IF NOT EXISTS "tenant_id" integer;
|
||||
|
||||
ALTER TABLE "authors"
|
||||
ADD CONSTRAINT "authors_tenant_id_fk"
|
||||
FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE set null ON UPDATE no action;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "authors_tenant_idx"
|
||||
ON "authors" USING btree ("tenant_id");
|
||||
`)
|
||||
|
||||
// ========================================
|
||||
// Posts-Erweiterungen
|
||||
// ========================================
|
||||
|
||||
// Tags Relationship (posts_rels für tags)
|
||||
await db.execute(sql`
|
||||
-- Add tags path to posts_rels if needed
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM posts_rels WHERE path = 'tags' LIMIT 1
|
||||
) THEN
|
||||
-- tags_id column might not exist yet
|
||||
ALTER TABLE "posts_rels" ADD COLUMN IF NOT EXISTS "tags_id" integer;
|
||||
|
||||
ALTER TABLE "posts_rels"
|
||||
ADD CONSTRAINT "posts_rels_tags_fk"
|
||||
FOREIGN KEY ("tags_id") REFERENCES "public"."tags"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "posts_rels_tags_id_idx"
|
||||
ON "posts_rels" USING btree ("tags_id");
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
`)
|
||||
|
||||
// Author Relationship (author_id and co_authors via posts_rels)
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "posts" ADD COLUMN IF NOT EXISTS "author_id" integer;
|
||||
ALTER TABLE "posts" ADD COLUMN IF NOT EXISTS "author_legacy" varchar;
|
||||
ALTER TABLE "posts" ADD COLUMN IF NOT EXISTS "reading_time" integer;
|
||||
|
||||
ALTER TABLE "posts"
|
||||
ADD CONSTRAINT "posts_author_id_fk"
|
||||
FOREIGN KEY ("author_id") REFERENCES "public"."authors"("id") ON DELETE set null ON UPDATE no action;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "posts_author_idx"
|
||||
ON "posts" USING btree ("author_id");
|
||||
|
||||
-- Co-Authors via posts_rels
|
||||
ALTER TABLE "posts_rels" ADD COLUMN IF NOT EXISTS "authors_id" integer;
|
||||
|
||||
ALTER TABLE "posts_rels"
|
||||
ADD CONSTRAINT "posts_rels_authors_fk"
|
||||
FOREIGN KEY ("authors_id") REFERENCES "public"."authors"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "posts_rels_authors_id_idx"
|
||||
ON "posts_rels" USING btree ("authors_id");
|
||||
`)
|
||||
|
||||
// ========================================
|
||||
// AuthorBioBlock
|
||||
// ========================================
|
||||
await db.execute(sql`
|
||||
-- Enums for AuthorBioBlock
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_author_bio_source" AS ENUM('post', 'manual');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_author_bio_layout" AS ENUM('card', 'inline', 'compact', 'feature');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_author_bio_show_bio" AS ENUM('none', 'short', 'full');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_author_bio_avatar_size" AS ENUM('sm', 'md', 'lg');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_author_bio_avatar_shape" AS ENUM('circle', 'square', 'rounded');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_author_bio_bg" AS ENUM('none', 'light', 'dark', 'accent');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_author_bio_block" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"_path" text NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"source" "enum_pages_blocks_author_bio_source" DEFAULT 'post',
|
||||
"show_co_authors" boolean DEFAULT true,
|
||||
"layout" "enum_pages_blocks_author_bio_layout" DEFAULT 'card',
|
||||
"show_avatar" boolean DEFAULT true,
|
||||
"show_name" boolean DEFAULT true,
|
||||
"show_title" boolean DEFAULT true,
|
||||
"show_bio" "enum_pages_blocks_author_bio_show_bio" DEFAULT 'short',
|
||||
"show_social" boolean DEFAULT true,
|
||||
"show_email" boolean DEFAULT false,
|
||||
"show_website" boolean DEFAULT false,
|
||||
"show_post_count" boolean DEFAULT false,
|
||||
"style_avatar_size" "enum_pages_blocks_author_bio_avatar_size" DEFAULT 'md',
|
||||
"style_avatar_shape" "enum_pages_blocks_author_bio_avatar_shape" DEFAULT 'circle',
|
||||
"style_bg" "enum_pages_blocks_author_bio_bg" DEFAULT 'light',
|
||||
"style_border" boolean DEFAULT false,
|
||||
"style_shadow" boolean DEFAULT false,
|
||||
"style_divider" boolean DEFAULT true,
|
||||
"link_to_profile" boolean DEFAULT true,
|
||||
"block_name" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_author_bio_block_locales" (
|
||||
"label" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL
|
||||
);
|
||||
|
||||
-- Authors relationship for manual selection
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_author_bio_block_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" varchar NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"authors_id" integer
|
||||
);
|
||||
|
||||
ALTER TABLE "pages_blocks_author_bio_block"
|
||||
ADD CONSTRAINT "pages_blocks_author_bio_block_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."pages"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_author_bio_block_locales"
|
||||
ADD CONSTRAINT "pages_blocks_author_bio_block_locales_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_author_bio_block"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_author_bio_block_rels"
|
||||
ADD CONSTRAINT "pages_blocks_author_bio_block_rels_parent_fk"
|
||||
FOREIGN KEY ("parent_id") REFERENCES "public"."pages_blocks_author_bio_block"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_author_bio_block_rels"
|
||||
ADD CONSTRAINT "pages_blocks_author_bio_block_rels_authors_fk"
|
||||
FOREIGN KEY ("authors_id") REFERENCES "public"."authors"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_author_bio_block_order_idx"
|
||||
ON "pages_blocks_author_bio_block" USING btree ("_order");
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_author_bio_block_parent_id_idx"
|
||||
ON "pages_blocks_author_bio_block" USING btree ("_parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_author_bio_block_path_idx"
|
||||
ON "pages_blocks_author_bio_block" USING btree ("_path");
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "pages_blocks_author_bio_block_locales_locale_parent_id_unique"
|
||||
ON "pages_blocks_author_bio_block_locales" USING btree ("_locale", "_parent_id");
|
||||
`)
|
||||
|
||||
// ========================================
|
||||
// RelatedPostsBlock
|
||||
// ========================================
|
||||
await db.execute(sql`
|
||||
-- Enums for RelatedPostsBlock
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_related_posts_source" AS ENUM('auto', 'manual', 'category', 'tag', 'latest', 'popular', 'author');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_related_posts_layout" AS ENUM('grid', 'list', 'slider', 'compact', 'cards');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_related_posts_cols" AS ENUM('2', '3', '4');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_related_posts_img_ratio" AS ENUM('square', '4-3', '16-9', '3-4');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_related_posts_rounded" AS ENUM('none', 'sm', 'md', 'lg');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_related_posts_hover" AS ENUM('none', 'lift', 'zoom', 'overlay');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_related_posts_bg" AS ENUM('none', 'light', 'dark');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_related_posts_gap" AS ENUM('16', '24', '32');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_related_posts_block" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"_path" text NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"source" "enum_pages_blocks_related_posts_source" DEFAULT 'auto',
|
||||
"category_id" integer,
|
||||
"tag_id" integer,
|
||||
"limit" integer DEFAULT 3,
|
||||
"exclude_current" boolean DEFAULT true,
|
||||
"layout" "enum_pages_blocks_related_posts_layout" DEFAULT 'grid',
|
||||
"cols" "enum_pages_blocks_related_posts_cols" DEFAULT '3',
|
||||
"show_image" boolean DEFAULT true,
|
||||
"show_date" boolean DEFAULT true,
|
||||
"show_author" boolean DEFAULT false,
|
||||
"show_category" boolean DEFAULT true,
|
||||
"show_excerpt" boolean DEFAULT true,
|
||||
"show_reading_time" boolean DEFAULT false,
|
||||
"show_tags" boolean DEFAULT false,
|
||||
"style_img_ratio" "enum_pages_blocks_related_posts_img_ratio" DEFAULT '16-9',
|
||||
"style_rounded" "enum_pages_blocks_related_posts_rounded" DEFAULT 'md',
|
||||
"style_shadow" boolean DEFAULT false,
|
||||
"style_hover" "enum_pages_blocks_related_posts_hover" DEFAULT 'lift',
|
||||
"style_bg" "enum_pages_blocks_related_posts_bg" DEFAULT 'none',
|
||||
"style_gap" "enum_pages_blocks_related_posts_gap" DEFAULT '24',
|
||||
"show_all_link" boolean DEFAULT false,
|
||||
"all_link_url" varchar,
|
||||
"block_name" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_related_posts_block_locales" (
|
||||
"title" varchar DEFAULT 'Das könnte Sie auch interessieren',
|
||||
"all_link_text" varchar DEFAULT 'Alle Artikel anzeigen',
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL
|
||||
);
|
||||
|
||||
-- Posts relationship for manual selection
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_related_posts_block_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" varchar NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"posts_id" integer
|
||||
);
|
||||
|
||||
ALTER TABLE "pages_blocks_related_posts_block"
|
||||
ADD CONSTRAINT "pages_blocks_related_posts_block_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."pages"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_related_posts_block"
|
||||
ADD CONSTRAINT "pages_blocks_related_posts_block_category_id_fk"
|
||||
FOREIGN KEY ("category_id") REFERENCES "public"."categories"("id") ON DELETE set null ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_related_posts_block"
|
||||
ADD CONSTRAINT "pages_blocks_related_posts_block_tag_id_fk"
|
||||
FOREIGN KEY ("tag_id") REFERENCES "public"."tags"("id") ON DELETE set null ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_related_posts_block_locales"
|
||||
ADD CONSTRAINT "pages_blocks_related_posts_block_locales_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_related_posts_block"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_related_posts_block_rels"
|
||||
ADD CONSTRAINT "pages_blocks_related_posts_block_rels_parent_fk"
|
||||
FOREIGN KEY ("parent_id") REFERENCES "public"."pages_blocks_related_posts_block"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_related_posts_block_rels"
|
||||
ADD CONSTRAINT "pages_blocks_related_posts_block_rels_posts_fk"
|
||||
FOREIGN KEY ("posts_id") REFERENCES "public"."posts"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_related_posts_block_order_idx"
|
||||
ON "pages_blocks_related_posts_block" USING btree ("_order");
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_related_posts_block_parent_id_idx"
|
||||
ON "pages_blocks_related_posts_block" USING btree ("_parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_related_posts_block_path_idx"
|
||||
ON "pages_blocks_related_posts_block" USING btree ("_path");
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "pages_blocks_related_posts_block_locales_locale_parent_id_unique"
|
||||
ON "pages_blocks_related_posts_block_locales" USING btree ("_locale", "_parent_id");
|
||||
`)
|
||||
|
||||
// ========================================
|
||||
// ShareButtonsBlock
|
||||
// ========================================
|
||||
await db.execute(sql`
|
||||
-- Enums for ShareButtonsBlock
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_share_layout" AS ENUM('horizontal', 'vertical', 'floating', 'sticky');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_share_align" AS ENUM('left', 'center', 'right');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_share_float_side" AS ENUM('left', 'right');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_share_variant" AS ENUM('filled', 'outline', 'icon', 'minimal');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_share_size" AS ENUM('sm', 'md', 'lg');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_share_shape" AS ENUM('square', 'rounded', 'circle', 'pill');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_share_color" AS ENUM('brand', 'gray', 'dark', 'light', 'accent');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_share_gap" AS ENUM('0', '4', '8', '12');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_share_buttons_block" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"_path" text NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"platforms_facebook" boolean DEFAULT true,
|
||||
"platforms_twitter" boolean DEFAULT true,
|
||||
"platforms_linkedin" boolean DEFAULT true,
|
||||
"platforms_xing" boolean DEFAULT false,
|
||||
"platforms_whatsapp" boolean DEFAULT true,
|
||||
"platforms_telegram" boolean DEFAULT false,
|
||||
"platforms_email" boolean DEFAULT true,
|
||||
"platforms_copy" boolean DEFAULT true,
|
||||
"platforms_print" boolean DEFAULT false,
|
||||
"platforms_pinterest" boolean DEFAULT false,
|
||||
"platforms_reddit" boolean DEFAULT false,
|
||||
"layout" "enum_pages_blocks_share_layout" DEFAULT 'horizontal',
|
||||
"align" "enum_pages_blocks_share_align" DEFAULT 'left',
|
||||
"float_side" "enum_pages_blocks_share_float_side" DEFAULT 'left',
|
||||
"style_variant" "enum_pages_blocks_share_variant" DEFAULT 'filled',
|
||||
"style_size" "enum_pages_blocks_share_size" DEFAULT 'md',
|
||||
"style_shape" "enum_pages_blocks_share_shape" DEFAULT 'rounded',
|
||||
"style_color_scheme" "enum_pages_blocks_share_color" DEFAULT 'brand',
|
||||
"style_gap" "enum_pages_blocks_share_gap" DEFAULT '8',
|
||||
"style_show_label" boolean DEFAULT false,
|
||||
"style_show_count" boolean DEFAULT false,
|
||||
"behavior_open_in_popup" boolean DEFAULT true,
|
||||
"behavior_use_native_share" boolean DEFAULT true,
|
||||
"content_hashtags" varchar,
|
||||
"content_via" varchar,
|
||||
"block_name" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_share_buttons_block_locales" (
|
||||
"label" varchar,
|
||||
"behavior_copy_feedback" varchar DEFAULT 'Link kopiert!',
|
||||
"content_custom_title" varchar,
|
||||
"content_custom_description" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "pages_blocks_share_buttons_block"
|
||||
ADD CONSTRAINT "pages_blocks_share_buttons_block_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."pages"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_share_buttons_block_locales"
|
||||
ADD CONSTRAINT "pages_blocks_share_buttons_block_locales_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_share_buttons_block"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_share_buttons_block_order_idx"
|
||||
ON "pages_blocks_share_buttons_block" USING btree ("_order");
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_share_buttons_block_parent_id_idx"
|
||||
ON "pages_blocks_share_buttons_block" USING btree ("_parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_share_buttons_block_path_idx"
|
||||
ON "pages_blocks_share_buttons_block" USING btree ("_path");
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "pages_blocks_share_buttons_block_locales_locale_parent_id_unique"
|
||||
ON "pages_blocks_share_buttons_block_locales" USING btree ("_locale", "_parent_id");
|
||||
`)
|
||||
|
||||
// ========================================
|
||||
// TableOfContentsBlock
|
||||
// ========================================
|
||||
await db.execute(sql`
|
||||
-- Enums for TableOfContentsBlock
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_toc_layout" AS ENUM('list', 'numbered', 'inline', 'sidebar', 'dropdown');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_toc_sidebar_pos" AS ENUM('left', 'right');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_toc_progress_style" AS ENUM('bar', 'percent', 'circle');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_toc_bg" AS ENUM('none', 'light', 'dark', 'accent');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_toc_border_side" AS ENUM('all', 'left', 'top');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_toc_rounded" AS ENUM('none', 'sm', 'md', 'lg');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_toc_font_size" AS ENUM('sm', 'base', 'lg');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_toc_line_height" AS ENUM('tight', 'normal', 'relaxed');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_toc_block" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"_path" text NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"levels_h2" boolean DEFAULT true,
|
||||
"levels_h3" boolean DEFAULT true,
|
||||
"levels_h4" boolean DEFAULT false,
|
||||
"levels_h5" boolean DEFAULT false,
|
||||
"levels_h6" boolean DEFAULT false,
|
||||
"layout" "enum_pages_blocks_toc_layout" DEFAULT 'list',
|
||||
"sidebar_pos" "enum_pages_blocks_toc_sidebar_pos" DEFAULT 'right',
|
||||
"behavior_smooth_scroll" boolean DEFAULT true,
|
||||
"behavior_highlight_active" boolean DEFAULT true,
|
||||
"behavior_scroll_offset" integer DEFAULT 80,
|
||||
"behavior_collapsible" boolean DEFAULT false,
|
||||
"behavior_start_collapsed" boolean DEFAULT false,
|
||||
"behavior_show_progress" boolean DEFAULT false,
|
||||
"behavior_progress_style" "enum_pages_blocks_toc_progress_style" DEFAULT 'bar',
|
||||
"style_bg" "enum_pages_blocks_toc_bg" DEFAULT 'light',
|
||||
"style_border" boolean DEFAULT true,
|
||||
"style_border_side" "enum_pages_blocks_toc_border_side" DEFAULT 'left',
|
||||
"style_rounded" "enum_pages_blocks_toc_rounded" DEFAULT 'md',
|
||||
"style_shadow" boolean DEFAULT false,
|
||||
"style_indent" boolean DEFAULT true,
|
||||
"style_show_icon" boolean DEFAULT false,
|
||||
"style_font_size" "enum_pages_blocks_toc_font_size" DEFAULT 'sm',
|
||||
"style_line_height" "enum_pages_blocks_toc_line_height" DEFAULT 'relaxed',
|
||||
"min_items" integer DEFAULT 3,
|
||||
"max_items" integer,
|
||||
"block_name" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_toc_block_locales" (
|
||||
"title" varchar DEFAULT 'Inhaltsverzeichnis',
|
||||
"a11y_label" varchar DEFAULT 'Inhaltsverzeichnis',
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "pages_blocks_toc_block"
|
||||
ADD CONSTRAINT "pages_blocks_toc_block_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."pages"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_toc_block_locales"
|
||||
ADD CONSTRAINT "pages_blocks_toc_block_locales_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_toc_block"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_toc_block_order_idx"
|
||||
ON "pages_blocks_toc_block" USING btree ("_order");
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_toc_block_parent_id_idx"
|
||||
ON "pages_blocks_toc_block" USING btree ("_parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_toc_block_path_idx"
|
||||
ON "pages_blocks_toc_block" USING btree ("_path");
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "pages_blocks_toc_block_locales_locale_parent_id_unique"
|
||||
ON "pages_blocks_toc_block_locales" USING btree ("_locale", "_parent_id");
|
||||
`)
|
||||
}
|
||||
|
||||
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||
// Drop Blocks
|
||||
await db.execute(sql`
|
||||
DROP TABLE IF EXISTS "pages_blocks_toc_block_locales" CASCADE;
|
||||
DROP TABLE IF EXISTS "pages_blocks_toc_block" CASCADE;
|
||||
|
||||
DROP TABLE IF EXISTS "pages_blocks_share_buttons_block_locales" CASCADE;
|
||||
DROP TABLE IF EXISTS "pages_blocks_share_buttons_block" CASCADE;
|
||||
|
||||
DROP TABLE IF EXISTS "pages_blocks_related_posts_block_rels" CASCADE;
|
||||
DROP TABLE IF EXISTS "pages_blocks_related_posts_block_locales" CASCADE;
|
||||
DROP TABLE IF EXISTS "pages_blocks_related_posts_block" CASCADE;
|
||||
|
||||
DROP TABLE IF EXISTS "pages_blocks_author_bio_block_rels" CASCADE;
|
||||
DROP TABLE IF EXISTS "pages_blocks_author_bio_block_locales" CASCADE;
|
||||
DROP TABLE IF EXISTS "pages_blocks_author_bio_block" CASCADE;
|
||||
`)
|
||||
|
||||
// Drop Block Enums
|
||||
await db.execute(sql`
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_toc_line_height";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_toc_font_size";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_toc_rounded";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_toc_border_side";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_toc_bg";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_toc_progress_style";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_toc_sidebar_pos";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_toc_layout";
|
||||
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_share_gap";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_share_color";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_share_shape";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_share_size";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_share_variant";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_share_float_side";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_share_align";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_share_layout";
|
||||
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_related_posts_gap";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_related_posts_bg";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_related_posts_hover";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_related_posts_rounded";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_related_posts_img_ratio";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_related_posts_cols";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_related_posts_layout";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_related_posts_source";
|
||||
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_author_bio_bg";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_author_bio_avatar_shape";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_author_bio_avatar_size";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_author_bio_show_bio";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_author_bio_layout";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_author_bio_source";
|
||||
`)
|
||||
|
||||
// Drop Posts extensions
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "posts" DROP COLUMN IF EXISTS "author_id";
|
||||
ALTER TABLE "posts" DROP COLUMN IF EXISTS "author_legacy";
|
||||
ALTER TABLE "posts" DROP COLUMN IF EXISTS "reading_time";
|
||||
ALTER TABLE "posts_rels" DROP COLUMN IF EXISTS "authors_id";
|
||||
ALTER TABLE "posts_rels" DROP COLUMN IF EXISTS "tags_id";
|
||||
`)
|
||||
|
||||
// Drop Authors
|
||||
await db.execute(sql`
|
||||
DROP TABLE IF EXISTS "authors_locales" CASCADE;
|
||||
DROP TABLE IF EXISTS "authors" CASCADE;
|
||||
`)
|
||||
|
||||
// Drop Tags
|
||||
await db.execute(sql`
|
||||
DROP TABLE IF EXISTS "tags_locales" CASCADE;
|
||||
DROP TABLE IF EXISTS "tags" CASCADE;
|
||||
`)
|
||||
}
|
||||
451
src/migrations/20251213_230000_team_extensions.ts
Normal file
451
src/migrations/20251213_230000_team_extensions.ts
Normal file
|
|
@ -0,0 +1,451 @@
|
|||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
/**
|
||||
* Migration: Team Extensions
|
||||
*
|
||||
* Erweitert die Team Collection um:
|
||||
* - Slug für Einzelseiten
|
||||
* - Hierarchie-Felder (reportsTo, hierarchyLevel) für Organigramm
|
||||
* - vCard-Export-Flag
|
||||
*
|
||||
* Erstellt neue Blocks:
|
||||
* - TeamFilterBlock
|
||||
* - OrgChartBlock
|
||||
*/
|
||||
|
||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||
// ========================================
|
||||
// Team Collection Extensions
|
||||
// ========================================
|
||||
|
||||
// Add slug field
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "team" ADD COLUMN IF NOT EXISTS "slug" varchar UNIQUE;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "team_slug_idx"
|
||||
ON "team" USING btree ("slug");
|
||||
`)
|
||||
|
||||
// Add hierarchy fields
|
||||
await db.execute(sql`
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_team_hierarchy_level" AS ENUM('executive', 'department_head', 'team_lead', 'employee', 'trainee');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
ALTER TABLE "team" ADD COLUMN IF NOT EXISTS "reports_to_id" integer;
|
||||
ALTER TABLE "team" ADD COLUMN IF NOT EXISTS "hierarchy_level" "enum_team_hierarchy_level";
|
||||
ALTER TABLE "team" ADD COLUMN IF NOT EXISTS "allow_v_card" boolean DEFAULT true;
|
||||
|
||||
ALTER TABLE "team"
|
||||
ADD CONSTRAINT "team_reports_to_id_fk"
|
||||
FOREIGN KEY ("reports_to_id") REFERENCES "public"."team"("id") ON DELETE set null ON UPDATE no action;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "team_reports_to_idx"
|
||||
ON "team" USING btree ("reports_to_id");
|
||||
CREATE INDEX IF NOT EXISTS "team_hierarchy_level_idx"
|
||||
ON "team" USING btree ("hierarchy_level");
|
||||
`)
|
||||
|
||||
// ========================================
|
||||
// TeamFilterBlock
|
||||
// ========================================
|
||||
await db.execute(sql`
|
||||
-- Enums for TeamFilterBlock
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_team_filter_filter_layout" AS ENUM('horizontal', 'sidebar', 'dropdown', 'tabs');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_team_filter_filter_style" AS ENUM('buttons', 'select', 'checkbox');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_team_filter_display_layout" AS ENUM('grid', 'list', 'compact');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_team_filter_display_cols" AS ENUM('2', '3', '4');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_team_filter_load_more" AS ENUM('button', 'infinite', 'pagination', 'all');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_team_filter_card_img_style" AS ENUM('circle', 'rounded', 'square');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_team_filter_style_bg" AS ENUM('none', 'light', 'dark');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_team_filter_style_card_bg" AS ENUM('white', 'transparent', 'light');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_team_filter_style_hover" AS ENUM('none', 'lift', 'shadow', 'border');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_team_filter_style_gap" AS ENUM('16', '24', '32');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_team_filter_style_anim" AS ENUM('none', 'fade', 'slide', 'scale');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_team_filter_block" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"_path" text NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
-- Filters
|
||||
"filters_show_search" boolean DEFAULT true,
|
||||
"filters_show_department" boolean DEFAULT true,
|
||||
"filters_show_specialization" boolean DEFAULT false,
|
||||
"filters_show_language" boolean DEFAULT false,
|
||||
"filters_show_hierarchy" boolean DEFAULT false,
|
||||
"filters_filter_layout" "enum_pages_blocks_team_filter_filter_layout" DEFAULT 'horizontal',
|
||||
"filters_filter_style" "enum_pages_blocks_team_filter_filter_style" DEFAULT 'buttons',
|
||||
"filters_show_result_count" boolean DEFAULT true,
|
||||
"filters_show_reset_button" boolean DEFAULT true,
|
||||
-- Display
|
||||
"display_layout" "enum_pages_blocks_team_filter_display_layout" DEFAULT 'grid',
|
||||
"display_columns" "enum_pages_blocks_team_filter_display_cols" DEFAULT '3',
|
||||
"display_initial_limit" integer DEFAULT 12,
|
||||
"display_load_more" "enum_pages_blocks_team_filter_load_more" DEFAULT 'button',
|
||||
-- Card
|
||||
"card_show_image" boolean DEFAULT true,
|
||||
"card_image_style" "enum_pages_blocks_team_filter_card_img_style" DEFAULT 'circle',
|
||||
"card_show_role" boolean DEFAULT true,
|
||||
"card_show_department" boolean DEFAULT true,
|
||||
"card_show_bio" boolean DEFAULT false,
|
||||
"card_show_contact" boolean DEFAULT false,
|
||||
"card_show_social" boolean DEFAULT true,
|
||||
"card_show_specializations" boolean DEFAULT false,
|
||||
"card_show_languages" boolean DEFAULT false,
|
||||
"card_show_v_card" boolean DEFAULT false,
|
||||
"card_link_to_profile" boolean DEFAULT true,
|
||||
"card_profile_base_path" varchar DEFAULT '/team',
|
||||
"card_enable_modal" boolean DEFAULT false,
|
||||
-- Style
|
||||
"style_bg" "enum_pages_blocks_team_filter_style_bg" DEFAULT 'none',
|
||||
"style_card_bg" "enum_pages_blocks_team_filter_style_card_bg" DEFAULT 'white',
|
||||
"style_card_shadow" boolean DEFAULT true,
|
||||
"style_card_hover" "enum_pages_blocks_team_filter_style_hover" DEFAULT 'lift',
|
||||
"style_gap" "enum_pages_blocks_team_filter_style_gap" DEFAULT '24',
|
||||
"style_animation" "enum_pages_blocks_team_filter_style_anim" DEFAULT 'fade',
|
||||
-- Empty state
|
||||
"empty_state_show_reset_button" boolean DEFAULT true,
|
||||
"block_name" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_team_filter_block_locales" (
|
||||
"title" varchar DEFAULT 'Unser Team',
|
||||
"subtitle" varchar,
|
||||
"filters_search_placeholder" varchar DEFAULT 'Team durchsuchen...',
|
||||
"display_load_more_text" varchar DEFAULT 'Mehr anzeigen',
|
||||
"empty_state_title" varchar DEFAULT 'Keine Mitarbeiter gefunden',
|
||||
"empty_state_message" varchar DEFAULT 'Versuchen Sie andere Filterkriterien.',
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE "pages_blocks_team_filter_block"
|
||||
ADD CONSTRAINT "pages_blocks_team_filter_block_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."pages"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_team_filter_block_locales"
|
||||
ADD CONSTRAINT "pages_blocks_team_filter_block_locales_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_team_filter_block"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_team_filter_block_order_idx"
|
||||
ON "pages_blocks_team_filter_block" USING btree ("_order");
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_team_filter_block_parent_id_idx"
|
||||
ON "pages_blocks_team_filter_block" USING btree ("_parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_team_filter_block_path_idx"
|
||||
ON "pages_blocks_team_filter_block" USING btree ("_path");
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "pages_blocks_team_filter_block_locales_locale_parent_id_unique"
|
||||
ON "pages_blocks_team_filter_block_locales" USING btree ("_locale", "_parent_id");
|
||||
`)
|
||||
|
||||
// ========================================
|
||||
// OrgChartBlock
|
||||
// ========================================
|
||||
await db.execute(sql`
|
||||
-- Enums for OrgChartBlock
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_source" AS ENUM('auto', 'department', 'manual');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_layout" AS ENUM('tree', 'tree-horizontal', 'org', 'radial', 'layers');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_direction" AS ENUM('top', 'bottom', 'left', 'right');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_node_style" AS ENUM('card', 'compact', 'avatar', 'text');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_node_img_size" AS ENUM('sm', 'md', 'lg');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_node_img_shape" AS ENUM('circle', 'rounded', 'square');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_node_click" AS ENUM('none', 'modal', 'link', 'expand');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_conn_style" AS ENUM('straight', 'angular', 'curved', 'dashed');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_conn_color" AS ENUM('gray', 'dark', 'accent', 'gradient');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_conn_thick" AS ENUM('1', '2', '3');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_style_bg" AS ENUM('none', 'light', 'white', 'dark', 'gradient');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_style_node_bg" AS ENUM('white', 'light', 'transparent', 'accent');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_style_spacing" AS ENUM('sm', 'md', 'lg');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
CREATE TYPE "enum_pages_blocks_org_chart_style_height" AS ENUM('auto', '300', '400', '500', '600', 'full');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_org_chart_block" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"_path" text NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
-- Source
|
||||
"source" "enum_pages_blocks_org_chart_source" DEFAULT 'auto',
|
||||
"root_member_id" integer,
|
||||
"department" varchar,
|
||||
"max_depth" integer DEFAULT 5,
|
||||
-- Layout
|
||||
"layout" "enum_pages_blocks_org_chart_layout" DEFAULT 'tree',
|
||||
"direction" "enum_pages_blocks_org_chart_direction" DEFAULT 'top',
|
||||
-- Node
|
||||
"node_style" "enum_pages_blocks_org_chart_node_style" DEFAULT 'card',
|
||||
"node_show_image" boolean DEFAULT true,
|
||||
"node_image_size" "enum_pages_blocks_org_chart_node_img_size" DEFAULT 'md',
|
||||
"node_image_shape" "enum_pages_blocks_org_chart_node_img_shape" DEFAULT 'circle',
|
||||
"node_show_name" boolean DEFAULT true,
|
||||
"node_show_role" boolean DEFAULT true,
|
||||
"node_show_department" boolean DEFAULT false,
|
||||
"node_show_contact" boolean DEFAULT false,
|
||||
"node_click_action" "enum_pages_blocks_org_chart_node_click" DEFAULT 'modal',
|
||||
"node_profile_base_path" varchar DEFAULT '/team',
|
||||
-- Connectors
|
||||
"connectors_style" "enum_pages_blocks_org_chart_conn_style" DEFAULT 'straight',
|
||||
"connectors_color" "enum_pages_blocks_org_chart_conn_color" DEFAULT 'gray',
|
||||
"connectors_thickness" "enum_pages_blocks_org_chart_conn_thick" DEFAULT '2',
|
||||
"connectors_animated" boolean DEFAULT false,
|
||||
-- Levels
|
||||
"levels_color_by_level" boolean DEFAULT true,
|
||||
"levels_size_by_level" boolean DEFAULT true,
|
||||
"levels_collapsible" boolean DEFAULT true,
|
||||
"levels_initially_expanded" integer DEFAULT 2,
|
||||
-- Interaction
|
||||
"interaction_zoomable" boolean DEFAULT true,
|
||||
"interaction_pannable" boolean DEFAULT true,
|
||||
"interaction_minimap" boolean DEFAULT false,
|
||||
"interaction_search" boolean DEFAULT false,
|
||||
"interaction_highlight" boolean DEFAULT true,
|
||||
"interaction_fullscreen" boolean DEFAULT true,
|
||||
"interaction_export" boolean DEFAULT false,
|
||||
-- Style
|
||||
"style_bg" "enum_pages_blocks_org_chart_style_bg" DEFAULT 'light',
|
||||
"style_node_bg" "enum_pages_blocks_org_chart_style_node_bg" DEFAULT 'white',
|
||||
"style_node_shadow" boolean DEFAULT true,
|
||||
"style_node_border" boolean DEFAULT false,
|
||||
"style_spacing" "enum_pages_blocks_org_chart_style_spacing" DEFAULT 'md',
|
||||
"style_min_height" "enum_pages_blocks_org_chart_style_height" DEFAULT '400',
|
||||
-- Other
|
||||
"show_legend" boolean DEFAULT false,
|
||||
"block_name" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_org_chart_block_locales" (
|
||||
"title" varchar DEFAULT 'Unsere Struktur',
|
||||
"subtitle" varchar,
|
||||
"a11y_label" varchar DEFAULT 'Organigramm der Unternehmensstruktur',
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL
|
||||
);
|
||||
|
||||
-- Selected members relationship
|
||||
CREATE TABLE IF NOT EXISTS "pages_blocks_org_chart_block_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" varchar NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"team_id" integer
|
||||
);
|
||||
|
||||
ALTER TABLE "pages_blocks_org_chart_block"
|
||||
ADD CONSTRAINT "pages_blocks_org_chart_block_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."pages"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_org_chart_block"
|
||||
ADD CONSTRAINT "pages_blocks_org_chart_block_root_member_id_fk"
|
||||
FOREIGN KEY ("root_member_id") REFERENCES "public"."team"("id") ON DELETE set null ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_org_chart_block_locales"
|
||||
ADD CONSTRAINT "pages_blocks_org_chart_block_locales_parent_id_fk"
|
||||
FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_org_chart_block"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_org_chart_block_rels"
|
||||
ADD CONSTRAINT "pages_blocks_org_chart_block_rels_parent_fk"
|
||||
FOREIGN KEY ("parent_id") REFERENCES "public"."pages_blocks_org_chart_block"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
ALTER TABLE "pages_blocks_org_chart_block_rels"
|
||||
ADD CONSTRAINT "pages_blocks_org_chart_block_rels_team_fk"
|
||||
FOREIGN KEY ("team_id") REFERENCES "public"."team"("id") ON DELETE cascade ON UPDATE no action;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_org_chart_block_order_idx"
|
||||
ON "pages_blocks_org_chart_block" USING btree ("_order");
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_org_chart_block_parent_id_idx"
|
||||
ON "pages_blocks_org_chart_block" USING btree ("_parent_id");
|
||||
CREATE INDEX IF NOT EXISTS "pages_blocks_org_chart_block_path_idx"
|
||||
ON "pages_blocks_org_chart_block" USING btree ("_path");
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "pages_blocks_org_chart_block_locales_locale_parent_id_unique"
|
||||
ON "pages_blocks_org_chart_block_locales" USING btree ("_locale", "_parent_id");
|
||||
`)
|
||||
}
|
||||
|
||||
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||
// Drop OrgChartBlock
|
||||
await db.execute(sql`
|
||||
DROP TABLE IF EXISTS "pages_blocks_org_chart_block_rels" CASCADE;
|
||||
DROP TABLE IF EXISTS "pages_blocks_org_chart_block_locales" CASCADE;
|
||||
DROP TABLE IF EXISTS "pages_blocks_org_chart_block" CASCADE;
|
||||
`)
|
||||
|
||||
// Drop OrgChartBlock enums
|
||||
await db.execute(sql`
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_style_height";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_style_spacing";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_style_node_bg";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_style_bg";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_conn_thick";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_conn_color";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_conn_style";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_node_click";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_node_img_shape";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_node_img_size";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_node_style";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_direction";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_layout";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_org_chart_source";
|
||||
`)
|
||||
|
||||
// Drop TeamFilterBlock
|
||||
await db.execute(sql`
|
||||
DROP TABLE IF EXISTS "pages_blocks_team_filter_block_locales" CASCADE;
|
||||
DROP TABLE IF EXISTS "pages_blocks_team_filter_block" CASCADE;
|
||||
`)
|
||||
|
||||
// Drop TeamFilterBlock enums
|
||||
await db.execute(sql`
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_team_filter_style_anim";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_team_filter_style_gap";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_team_filter_style_hover";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_team_filter_style_card_bg";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_team_filter_style_bg";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_team_filter_card_img_style";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_team_filter_load_more";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_team_filter_display_cols";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_team_filter_display_layout";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_team_filter_filter_style";
|
||||
DROP TYPE IF EXISTS "enum_pages_blocks_team_filter_filter_layout";
|
||||
`)
|
||||
|
||||
// Drop Team extensions
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "team" DROP COLUMN IF EXISTS "slug";
|
||||
ALTER TABLE "team" DROP COLUMN IF EXISTS "reports_to_id";
|
||||
ALTER TABLE "team" DROP COLUMN IF EXISTS "hierarchy_level";
|
||||
ALTER TABLE "team" DROP COLUMN IF EXISTS "allow_v_card";
|
||||
DROP TYPE IF EXISTS "enum_team_hierarchy_level";
|
||||
`)
|
||||
}
|
||||
|
|
@ -86,6 +86,8 @@ export interface Config {
|
|||
products: Product;
|
||||
timelines: Timeline;
|
||||
workflows: Workflow;
|
||||
tags: Tag;
|
||||
authors: Author;
|
||||
'cookie-configurations': CookieConfiguration;
|
||||
'cookie-inventory': CookieInventory;
|
||||
'consent-logs': ConsentLog;
|
||||
|
|
@ -121,6 +123,8 @@ export interface Config {
|
|||
products: ProductsSelect<false> | ProductsSelect<true>;
|
||||
timelines: TimelinesSelect<false> | TimelinesSelect<true>;
|
||||
workflows: WorkflowsSelect<false> | WorkflowsSelect<true>;
|
||||
tags: TagsSelect<false> | TagsSelect<true>;
|
||||
authors: AuthorsSelect<false> | AuthorsSelect<true>;
|
||||
'cookie-configurations': CookieConfigurationsSelect<false> | CookieConfigurationsSelect<true>;
|
||||
'cookie-inventory': CookieInventorySelect<false> | CookieInventorySelect<true>;
|
||||
'consent-logs': ConsentLogsSelect<false> | ConsentLogsSelect<true>;
|
||||
|
|
@ -1101,6 +1105,377 @@ export interface Page {
|
|||
blockName?: string | null;
|
||||
blockType: 'services-block';
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* Woher sollen die Autoren-Daten kommen?
|
||||
*/
|
||||
source?: ('post' | 'manual') | null;
|
||||
/**
|
||||
* Wählen Sie die anzuzeigenden Autoren
|
||||
*/
|
||||
authors?: (number | Author)[] | null;
|
||||
/**
|
||||
* Auch Co-Autoren des Artikels anzeigen
|
||||
*/
|
||||
showCoAuthors?: boolean | null;
|
||||
layout?: ('card' | 'inline' | 'compact' | 'feature') | null;
|
||||
show?: {
|
||||
avatar?: boolean | null;
|
||||
name?: boolean | null;
|
||||
title?: boolean | null;
|
||||
bio?: ('none' | 'short' | 'full') | null;
|
||||
social?: boolean | null;
|
||||
email?: boolean | null;
|
||||
website?: boolean | null;
|
||||
postCount?: boolean | null;
|
||||
};
|
||||
style?: {
|
||||
avatarSize?: ('sm' | 'md' | 'lg') | null;
|
||||
avatarShape?: ('circle' | 'square' | 'rounded') | null;
|
||||
bg?: ('none' | 'light' | 'dark' | 'accent') | null;
|
||||
border?: boolean | null;
|
||||
shadow?: boolean | null;
|
||||
/**
|
||||
* Linie zur Trennung vom Artikel-Inhalt
|
||||
*/
|
||||
divider?: boolean | null;
|
||||
};
|
||||
/**
|
||||
* z.B. "Über den Autor", "Geschrieben von" (optional)
|
||||
*/
|
||||
label?: string | null;
|
||||
linkToProfile?: boolean | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'author-bio-block';
|
||||
}
|
||||
| {
|
||||
title?: string | null;
|
||||
source?: ('auto' | 'manual' | 'category' | 'tag' | 'latest' | 'popular' | 'author') | null;
|
||||
/**
|
||||
* Wählen Sie die anzuzeigenden Artikel
|
||||
*/
|
||||
posts?: (number | Post)[] | null;
|
||||
category?: (number | null) | Category;
|
||||
tag?: (number | null) | Tag;
|
||||
limit?: number | null;
|
||||
/**
|
||||
* Den Artikel, auf dem dieser Block ist, nicht anzeigen
|
||||
*/
|
||||
excludeCurrent?: boolean | null;
|
||||
layout?: ('grid' | 'list' | 'slider' | 'compact' | 'cards') | null;
|
||||
cols?: ('2' | '3' | '4') | null;
|
||||
show?: {
|
||||
image?: boolean | null;
|
||||
date?: boolean | null;
|
||||
author?: boolean | null;
|
||||
category?: boolean | null;
|
||||
excerpt?: boolean | null;
|
||||
readingTime?: boolean | null;
|
||||
tags?: boolean | null;
|
||||
};
|
||||
style?: {
|
||||
imgRatio?: ('square' | '4-3' | '16-9' | '3-4') | null;
|
||||
rounded?: ('none' | 'sm' | 'md' | 'lg') | null;
|
||||
shadow?: boolean | null;
|
||||
hover?: ('none' | 'lift' | 'zoom' | 'overlay') | null;
|
||||
bg?: ('none' | 'light' | 'dark') | null;
|
||||
gap?: ('16' | '24' | '32') | null;
|
||||
};
|
||||
showAllLink?: boolean | null;
|
||||
allLinkText?: string | null;
|
||||
/**
|
||||
* z.B. /blog oder /news
|
||||
*/
|
||||
allLinkUrl?: string | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'related-posts-block';
|
||||
}
|
||||
| {
|
||||
/**
|
||||
* z.B. "Teilen:", "Artikel teilen" (optional)
|
||||
*/
|
||||
label?: string | null;
|
||||
platforms?: {
|
||||
facebook?: boolean | null;
|
||||
twitter?: boolean | null;
|
||||
linkedin?: boolean | null;
|
||||
xing?: boolean | null;
|
||||
whatsapp?: boolean | null;
|
||||
telegram?: boolean | null;
|
||||
email?: boolean | null;
|
||||
copy?: boolean | null;
|
||||
print?: boolean | null;
|
||||
pinterest?: boolean | null;
|
||||
reddit?: boolean | null;
|
||||
};
|
||||
layout?: ('horizontal' | 'vertical' | 'floating' | 'sticky') | null;
|
||||
align?: ('left' | 'center' | 'right') | null;
|
||||
floatSide?: ('left' | 'right') | null;
|
||||
style?: {
|
||||
variant?: ('filled' | 'outline' | 'icon' | 'minimal') | null;
|
||||
size?: ('sm' | 'md' | 'lg') | null;
|
||||
shape?: ('square' | 'rounded' | 'circle' | 'pill') | null;
|
||||
colorScheme?: ('brand' | 'gray' | 'dark' | 'light' | 'accent') | null;
|
||||
gap?: ('0' | '4' | '8' | '12') | null;
|
||||
showLabel?: boolean | null;
|
||||
/**
|
||||
* Zeigt Anzahl der Shares (sofern verfügbar)
|
||||
*/
|
||||
showCount?: boolean | null;
|
||||
};
|
||||
behavior?: {
|
||||
/**
|
||||
* Share-Dialoge in kleinem Popup statt neuem Tab
|
||||
*/
|
||||
openInPopup?: boolean | null;
|
||||
/**
|
||||
* Auf mobilen Geräten den System-Share-Dialog verwenden
|
||||
*/
|
||||
useNativeShare?: boolean | null;
|
||||
/**
|
||||
* Text der angezeigt wird nach dem Kopieren
|
||||
*/
|
||||
copyFeedback?: string | null;
|
||||
};
|
||||
/**
|
||||
* Überschreibt automatische Werte (optional)
|
||||
*/
|
||||
content?: {
|
||||
/**
|
||||
* Leer lassen für automatischen Seitentitel
|
||||
*/
|
||||
customTitle?: string | null;
|
||||
/**
|
||||
* Leer lassen für automatische Meta-Description
|
||||
*/
|
||||
customDescription?: string | null;
|
||||
/**
|
||||
* Komma-getrennt, ohne # (z.B. "blog,news,tech")
|
||||
*/
|
||||
hashtags?: string | null;
|
||||
/**
|
||||
* Twitter-Handle ohne @ (z.B. "c2sgmbh")
|
||||
*/
|
||||
via?: string | null;
|
||||
};
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'share-buttons-block';
|
||||
}
|
||||
| {
|
||||
title?: string | null;
|
||||
/**
|
||||
* Welche Überschriften-Ebenen einschließen?
|
||||
*/
|
||||
levels?: {
|
||||
h2?: boolean | null;
|
||||
h3?: boolean | null;
|
||||
h4?: boolean | null;
|
||||
h5?: boolean | null;
|
||||
h6?: boolean | null;
|
||||
};
|
||||
layout?: ('list' | 'numbered' | 'inline' | 'sidebar' | 'dropdown') | null;
|
||||
sidebarPos?: ('left' | 'right') | null;
|
||||
behavior?: {
|
||||
smoothScroll?: boolean | null;
|
||||
/**
|
||||
* Markiert den aktuell sichtbaren Abschnitt
|
||||
*/
|
||||
highlightActive?: boolean | null;
|
||||
/**
|
||||
* Abstand zum oberen Rand nach dem Scrollen (für Fixed Headers)
|
||||
*/
|
||||
scrollOffset?: number | null;
|
||||
/**
|
||||
* User kann das Inhaltsverzeichnis ein-/ausklappen
|
||||
*/
|
||||
collapsible?: boolean | null;
|
||||
startCollapsed?: boolean | null;
|
||||
/**
|
||||
* Fortschrittsbalken oder Prozent-Anzeige
|
||||
*/
|
||||
showProgress?: boolean | null;
|
||||
progressStyle?: ('bar' | 'percent' | 'circle') | null;
|
||||
};
|
||||
style?: {
|
||||
bg?: ('none' | 'light' | 'dark' | 'accent') | null;
|
||||
border?: boolean | null;
|
||||
borderSide?: ('all' | 'left' | 'top') | null;
|
||||
rounded?: ('none' | 'sm' | 'md' | 'lg') | null;
|
||||
shadow?: boolean | null;
|
||||
indent?: boolean | null;
|
||||
/**
|
||||
* Link-Symbol neben Einträgen
|
||||
*/
|
||||
showIcon?: boolean | null;
|
||||
fontSize?: ('sm' | 'base' | 'lg') | null;
|
||||
lineHeight?: ('tight' | 'normal' | 'relaxed') | null;
|
||||
};
|
||||
/**
|
||||
* Inhaltsverzeichnis nur anzeigen, wenn mindestens X Einträge
|
||||
*/
|
||||
minItems?: number | null;
|
||||
/**
|
||||
* Maximale Anzahl angezeigter Einträge (0 = unbegrenzt)
|
||||
*/
|
||||
maxItems?: number | null;
|
||||
a11yLabel?: string | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'toc-block';
|
||||
}
|
||||
| {
|
||||
title?: string | null;
|
||||
subtitle?: string | null;
|
||||
filters?: {
|
||||
showSearch?: boolean | null;
|
||||
searchPlaceholder?: string | null;
|
||||
showDepartment?: boolean | null;
|
||||
showSpecialization?: boolean | null;
|
||||
showLanguage?: boolean | null;
|
||||
showHierarchy?: boolean | null;
|
||||
filterLayout?: ('horizontal' | 'sidebar' | 'dropdown' | 'tabs') | null;
|
||||
filterStyle?: ('buttons' | 'select' | 'checkbox') | null;
|
||||
showResultCount?: boolean | null;
|
||||
showResetButton?: boolean | null;
|
||||
};
|
||||
display?: {
|
||||
layout?: ('grid' | 'list' | 'compact') | null;
|
||||
columns?: ('2' | '3' | '4') | null;
|
||||
initialLimit?: number | null;
|
||||
loadMore?: ('button' | 'infinite' | 'pagination' | 'all') | null;
|
||||
loadMoreText?: string | null;
|
||||
};
|
||||
card?: {
|
||||
showImage?: boolean | null;
|
||||
imageStyle?: ('circle' | 'rounded' | 'square') | null;
|
||||
showRole?: boolean | null;
|
||||
showDepartment?: boolean | null;
|
||||
showBio?: boolean | null;
|
||||
showContact?: boolean | null;
|
||||
showSocial?: boolean | null;
|
||||
showSpecializations?: boolean | null;
|
||||
showLanguages?: boolean | null;
|
||||
showVCard?: boolean | null;
|
||||
linkToProfile?: boolean | null;
|
||||
/**
|
||||
* z.B. "/team" ergibt "/team/max-mustermann"
|
||||
*/
|
||||
profileBasePath?: string | null;
|
||||
/**
|
||||
* Klick öffnet Modal statt Profilseite
|
||||
*/
|
||||
enableModal?: boolean | null;
|
||||
};
|
||||
style?: {
|
||||
bg?: ('none' | 'light' | 'dark') | null;
|
||||
cardBg?: ('white' | 'transparent' | 'light') | null;
|
||||
cardShadow?: boolean | null;
|
||||
cardHover?: ('none' | 'lift' | 'shadow' | 'border') | null;
|
||||
gap?: ('16' | '24' | '32') | null;
|
||||
animation?: ('none' | 'fade' | 'slide' | 'scale') | null;
|
||||
};
|
||||
emptyState?: {
|
||||
title?: string | null;
|
||||
message?: string | null;
|
||||
showResetButton?: boolean | null;
|
||||
};
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'team-filter-block';
|
||||
}
|
||||
| {
|
||||
title?: string | null;
|
||||
subtitle?: string | null;
|
||||
source?: ('auto' | 'department' | 'manual') | null;
|
||||
/**
|
||||
* Oberstes Element (z.B. Geschäftsführer). Leer = automatisch ermitteln.
|
||||
*/
|
||||
rootMember?: (number | null) | Team;
|
||||
/**
|
||||
* Zeigt nur diese Abteilung
|
||||
*/
|
||||
department?: string | null;
|
||||
selectedMembers?: (number | Team)[] | null;
|
||||
/**
|
||||
* Wie viele Hierarchie-Ebenen anzeigen
|
||||
*/
|
||||
maxDepth?: number | null;
|
||||
layout?: ('tree' | 'tree-horizontal' | 'org' | 'radial' | 'layers') | null;
|
||||
direction?: ('top' | 'bottom' | 'left' | 'right') | null;
|
||||
node?: {
|
||||
style?: ('card' | 'compact' | 'avatar' | 'text') | null;
|
||||
showImage?: boolean | null;
|
||||
imageSize?: ('sm' | 'md' | 'lg') | null;
|
||||
imageShape?: ('circle' | 'rounded' | 'square') | null;
|
||||
showName?: boolean | null;
|
||||
showRole?: boolean | null;
|
||||
showDepartment?: boolean | null;
|
||||
showContact?: boolean | null;
|
||||
clickAction?: ('none' | 'modal' | 'link' | 'expand') | null;
|
||||
profileBasePath?: string | null;
|
||||
};
|
||||
connectors?: {
|
||||
style?: ('straight' | 'angular' | 'curved' | 'dashed') | null;
|
||||
color?: ('gray' | 'dark' | 'accent' | 'gradient') | null;
|
||||
thickness?: ('1' | '2' | '3') | null;
|
||||
/**
|
||||
* Animierte Linien beim Laden
|
||||
*/
|
||||
animated?: boolean | null;
|
||||
};
|
||||
levels?: {
|
||||
/**
|
||||
* Verschiedene Farben pro Hierarchie-Ebene
|
||||
*/
|
||||
colorByLevel?: boolean | null;
|
||||
/**
|
||||
* Höhere Ebenen größer darstellen
|
||||
*/
|
||||
sizeByLevel?: boolean | null;
|
||||
collapsible?: boolean | null;
|
||||
/**
|
||||
* Wie viele Ebenen anfangs sichtbar
|
||||
*/
|
||||
initiallyExpanded?: number | null;
|
||||
};
|
||||
interaction?: {
|
||||
zoomable?: boolean | null;
|
||||
pannable?: boolean | null;
|
||||
/**
|
||||
* Kleine Übersichtskarte bei großen Organigrammen
|
||||
*/
|
||||
minimap?: boolean | null;
|
||||
search?: boolean | null;
|
||||
/**
|
||||
* Bei Hover Pfad zur Wurzel hervorheben
|
||||
*/
|
||||
highlight?: boolean | null;
|
||||
fullscreen?: boolean | null;
|
||||
/**
|
||||
* Als PNG oder PDF exportieren
|
||||
*/
|
||||
export?: boolean | null;
|
||||
};
|
||||
style?: {
|
||||
bg?: ('none' | 'light' | 'white' | 'dark' | 'gradient') | null;
|
||||
nodeBg?: ('white' | 'light' | 'transparent' | 'accent') | null;
|
||||
nodeShadow?: boolean | null;
|
||||
nodeBorder?: boolean | null;
|
||||
spacing?: ('sm' | 'md' | 'lg') | null;
|
||||
minHeight?: ('auto' | '300' | '400' | '500' | '600' | 'full') | null;
|
||||
};
|
||||
/**
|
||||
* Erklärt Farben und Symbole
|
||||
*/
|
||||
showLegend?: boolean | null;
|
||||
a11yLabel?: string | null;
|
||||
id?: string | null;
|
||||
blockName?: string | null;
|
||||
blockType: 'org-chart-block';
|
||||
}
|
||||
)[]
|
||||
| null;
|
||||
seo?: {
|
||||
|
|
@ -1248,6 +1623,10 @@ export interface Team {
|
|||
* Vollständiger Name
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* URL-freundlicher Identifier (z.B. "max-mustermann")
|
||||
*/
|
||||
slug: string;
|
||||
/**
|
||||
* z.B. "Geschäftsführer", "Pflegedienstleitung", "Fotograf"
|
||||
*/
|
||||
|
|
@ -1342,6 +1721,18 @@ export interface Team {
|
|||
* Eintrittsdatum (optional)
|
||||
*/
|
||||
startDate?: string | null;
|
||||
/**
|
||||
* Direkter Vorgesetzter (für Organigramm)
|
||||
*/
|
||||
reportsTo?: (number | null) | Team;
|
||||
/**
|
||||
* Für Organigramm-Darstellung
|
||||
*/
|
||||
hierarchyLevel?: ('executive' | 'department_head' | 'team_lead' | 'employee' | 'trainee') | null;
|
||||
/**
|
||||
* Kontaktkarte zum Download anbieten
|
||||
*/
|
||||
allowVCard?: boolean | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
|
@ -1582,6 +1973,102 @@ export interface Service {
|
|||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* Blog-Autoren und Gastautoren
|
||||
*
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "authors".
|
||||
*/
|
||||
export interface Author {
|
||||
id: number;
|
||||
tenant?: (number | null) | Tenant;
|
||||
/**
|
||||
* Anzeigename des Autors
|
||||
*/
|
||||
name: string;
|
||||
/**
|
||||
* URL-freundlicher Identifier (z.B. "max-mustermann")
|
||||
*/
|
||||
slug: string;
|
||||
/**
|
||||
* Avatar/Profilbild (empfohlen: quadratisch, min. 200x200px)
|
||||
*/
|
||||
avatar?: (number | null) | Media;
|
||||
/**
|
||||
* Ausführliche Biografie für Autorenseite
|
||||
*/
|
||||
bio?: {
|
||||
root: {
|
||||
type: string;
|
||||
children: {
|
||||
type: any;
|
||||
version: number;
|
||||
[k: string]: unknown;
|
||||
}[];
|
||||
direction: ('ltr' | 'rtl') | null;
|
||||
format: 'left' | 'start' | 'center' | 'right' | 'end' | 'justify' | '';
|
||||
indent: number;
|
||||
version: number;
|
||||
};
|
||||
[k: string]: unknown;
|
||||
} | null;
|
||||
/**
|
||||
* Ein bis zwei Sätze für Anzeige unter Artikeln
|
||||
*/
|
||||
bioShort?: string | null;
|
||||
/**
|
||||
* z.B. "Senior Editor", "Gastautor", "Chefredakteur"
|
||||
*/
|
||||
title?: string | null;
|
||||
/**
|
||||
* Öffentliche Kontakt-E-Mail (optional)
|
||||
*/
|
||||
email?: string | null;
|
||||
/**
|
||||
* Persönliche Website oder Blog
|
||||
*/
|
||||
website?: string | null;
|
||||
social?: {
|
||||
/**
|
||||
* Twitter-Handle ohne @ (z.B. "maxmustermann")
|
||||
*/
|
||||
twitter?: string | null;
|
||||
/**
|
||||
* LinkedIn-Profil-URL oder Username
|
||||
*/
|
||||
linkedin?: string | null;
|
||||
/**
|
||||
* GitHub-Username
|
||||
*/
|
||||
github?: string | null;
|
||||
/**
|
||||
* Instagram-Handle ohne @
|
||||
*/
|
||||
instagram?: string | null;
|
||||
};
|
||||
/**
|
||||
* Optional: Verknüpfung mit Team-Eintrag
|
||||
*/
|
||||
linkedTeam?: (number | null) | Team;
|
||||
/**
|
||||
* Optional: Verknüpfung mit Login-User
|
||||
*/
|
||||
linkedUser?: (number | null) | User;
|
||||
/**
|
||||
* Inaktive Autoren erscheinen nicht in Listen
|
||||
*/
|
||||
isActive?: boolean | null;
|
||||
/**
|
||||
* Markierung für Gastautoren
|
||||
*/
|
||||
isGuest?: boolean | null;
|
||||
/**
|
||||
* Für besondere Darstellung auf Autorenseite
|
||||
*/
|
||||
featured?: boolean | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "posts".
|
||||
|
|
@ -1623,7 +2110,26 @@ export interface Post {
|
|||
[k: string]: unknown;
|
||||
};
|
||||
categories?: (number | Category)[] | null;
|
||||
author?: string | null;
|
||||
/**
|
||||
* Schlagwörter für bessere Auffindbarkeit
|
||||
*/
|
||||
tags?: (number | Tag)[] | null;
|
||||
/**
|
||||
* Hauptautor des Beitrags
|
||||
*/
|
||||
author?: (number | null) | Author;
|
||||
/**
|
||||
* Weitere beteiligte Autoren
|
||||
*/
|
||||
coAuthors?: (number | Author)[] | null;
|
||||
/**
|
||||
* Freitext-Autor für ältere Beiträge ohne Autoren-Eintrag
|
||||
*/
|
||||
authorLegacy?: string | null;
|
||||
/**
|
||||
* Wird automatisch berechnet
|
||||
*/
|
||||
readingTime?: number | null;
|
||||
status?: ('draft' | 'published' | 'archived') | null;
|
||||
publishedAt?: string | null;
|
||||
seo?: {
|
||||
|
|
@ -1634,6 +2140,31 @@ export interface Post {
|
|||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* Schlagwörter für Blog-Posts
|
||||
*
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "tags".
|
||||
*/
|
||||
export interface Tag {
|
||||
id: number;
|
||||
tenant?: (number | null) | Tenant;
|
||||
name: string;
|
||||
/**
|
||||
* URL-freundlicher Identifier (z.B. "javascript")
|
||||
*/
|
||||
slug: string;
|
||||
/**
|
||||
* Optionale Beschreibung für Tag-Archivseiten
|
||||
*/
|
||||
description?: string | null;
|
||||
/**
|
||||
* Optionale Farbe für Tag-Badge (z.B. "#3B82F6" oder "blue")
|
||||
*/
|
||||
color?: string | null;
|
||||
updatedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "social-links".
|
||||
|
|
@ -3102,6 +3633,14 @@ export interface PayloadLockedDocument {
|
|||
relationTo: 'workflows';
|
||||
value: number | Workflow;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'tags';
|
||||
value: number | Tag;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'authors';
|
||||
value: number | Author;
|
||||
} | null)
|
||||
| ({
|
||||
relationTo: 'cookie-configurations';
|
||||
value: number | CookieConfiguration;
|
||||
|
|
@ -3951,6 +4490,308 @@ export interface PagesSelect<T extends boolean = true> {
|
|||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
'author-bio-block'?:
|
||||
| T
|
||||
| {
|
||||
source?: T;
|
||||
authors?: T;
|
||||
showCoAuthors?: T;
|
||||
layout?: T;
|
||||
show?:
|
||||
| T
|
||||
| {
|
||||
avatar?: T;
|
||||
name?: T;
|
||||
title?: T;
|
||||
bio?: T;
|
||||
social?: T;
|
||||
email?: T;
|
||||
website?: T;
|
||||
postCount?: T;
|
||||
};
|
||||
style?:
|
||||
| T
|
||||
| {
|
||||
avatarSize?: T;
|
||||
avatarShape?: T;
|
||||
bg?: T;
|
||||
border?: T;
|
||||
shadow?: T;
|
||||
divider?: T;
|
||||
};
|
||||
label?: T;
|
||||
linkToProfile?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
'related-posts-block'?:
|
||||
| T
|
||||
| {
|
||||
title?: T;
|
||||
source?: T;
|
||||
posts?: T;
|
||||
category?: T;
|
||||
tag?: T;
|
||||
limit?: T;
|
||||
excludeCurrent?: T;
|
||||
layout?: T;
|
||||
cols?: T;
|
||||
show?:
|
||||
| T
|
||||
| {
|
||||
image?: T;
|
||||
date?: T;
|
||||
author?: T;
|
||||
category?: T;
|
||||
excerpt?: T;
|
||||
readingTime?: T;
|
||||
tags?: T;
|
||||
};
|
||||
style?:
|
||||
| T
|
||||
| {
|
||||
imgRatio?: T;
|
||||
rounded?: T;
|
||||
shadow?: T;
|
||||
hover?: T;
|
||||
bg?: T;
|
||||
gap?: T;
|
||||
};
|
||||
showAllLink?: T;
|
||||
allLinkText?: T;
|
||||
allLinkUrl?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
'share-buttons-block'?:
|
||||
| T
|
||||
| {
|
||||
label?: T;
|
||||
platforms?:
|
||||
| T
|
||||
| {
|
||||
facebook?: T;
|
||||
twitter?: T;
|
||||
linkedin?: T;
|
||||
xing?: T;
|
||||
whatsapp?: T;
|
||||
telegram?: T;
|
||||
email?: T;
|
||||
copy?: T;
|
||||
print?: T;
|
||||
pinterest?: T;
|
||||
reddit?: T;
|
||||
};
|
||||
layout?: T;
|
||||
align?: T;
|
||||
floatSide?: T;
|
||||
style?:
|
||||
| T
|
||||
| {
|
||||
variant?: T;
|
||||
size?: T;
|
||||
shape?: T;
|
||||
colorScheme?: T;
|
||||
gap?: T;
|
||||
showLabel?: T;
|
||||
showCount?: T;
|
||||
};
|
||||
behavior?:
|
||||
| T
|
||||
| {
|
||||
openInPopup?: T;
|
||||
useNativeShare?: T;
|
||||
copyFeedback?: T;
|
||||
};
|
||||
content?:
|
||||
| T
|
||||
| {
|
||||
customTitle?: T;
|
||||
customDescription?: T;
|
||||
hashtags?: T;
|
||||
via?: T;
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
'toc-block'?:
|
||||
| T
|
||||
| {
|
||||
title?: T;
|
||||
levels?:
|
||||
| T
|
||||
| {
|
||||
h2?: T;
|
||||
h3?: T;
|
||||
h4?: T;
|
||||
h5?: T;
|
||||
h6?: T;
|
||||
};
|
||||
layout?: T;
|
||||
sidebarPos?: T;
|
||||
behavior?:
|
||||
| T
|
||||
| {
|
||||
smoothScroll?: T;
|
||||
highlightActive?: T;
|
||||
scrollOffset?: T;
|
||||
collapsible?: T;
|
||||
startCollapsed?: T;
|
||||
showProgress?: T;
|
||||
progressStyle?: T;
|
||||
};
|
||||
style?:
|
||||
| T
|
||||
| {
|
||||
bg?: T;
|
||||
border?: T;
|
||||
borderSide?: T;
|
||||
rounded?: T;
|
||||
shadow?: T;
|
||||
indent?: T;
|
||||
showIcon?: T;
|
||||
fontSize?: T;
|
||||
lineHeight?: T;
|
||||
};
|
||||
minItems?: T;
|
||||
maxItems?: T;
|
||||
a11yLabel?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
'team-filter-block'?:
|
||||
| T
|
||||
| {
|
||||
title?: T;
|
||||
subtitle?: T;
|
||||
filters?:
|
||||
| T
|
||||
| {
|
||||
showSearch?: T;
|
||||
searchPlaceholder?: T;
|
||||
showDepartment?: T;
|
||||
showSpecialization?: T;
|
||||
showLanguage?: T;
|
||||
showHierarchy?: T;
|
||||
filterLayout?: T;
|
||||
filterStyle?: T;
|
||||
showResultCount?: T;
|
||||
showResetButton?: T;
|
||||
};
|
||||
display?:
|
||||
| T
|
||||
| {
|
||||
layout?: T;
|
||||
columns?: T;
|
||||
initialLimit?: T;
|
||||
loadMore?: T;
|
||||
loadMoreText?: T;
|
||||
};
|
||||
card?:
|
||||
| T
|
||||
| {
|
||||
showImage?: T;
|
||||
imageStyle?: T;
|
||||
showRole?: T;
|
||||
showDepartment?: T;
|
||||
showBio?: T;
|
||||
showContact?: T;
|
||||
showSocial?: T;
|
||||
showSpecializations?: T;
|
||||
showLanguages?: T;
|
||||
showVCard?: T;
|
||||
linkToProfile?: T;
|
||||
profileBasePath?: T;
|
||||
enableModal?: T;
|
||||
};
|
||||
style?:
|
||||
| T
|
||||
| {
|
||||
bg?: T;
|
||||
cardBg?: T;
|
||||
cardShadow?: T;
|
||||
cardHover?: T;
|
||||
gap?: T;
|
||||
animation?: T;
|
||||
};
|
||||
emptyState?:
|
||||
| T
|
||||
| {
|
||||
title?: T;
|
||||
message?: T;
|
||||
showResetButton?: T;
|
||||
};
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
'org-chart-block'?:
|
||||
| T
|
||||
| {
|
||||
title?: T;
|
||||
subtitle?: T;
|
||||
source?: T;
|
||||
rootMember?: T;
|
||||
department?: T;
|
||||
selectedMembers?: T;
|
||||
maxDepth?: T;
|
||||
layout?: T;
|
||||
direction?: T;
|
||||
node?:
|
||||
| T
|
||||
| {
|
||||
style?: T;
|
||||
showImage?: T;
|
||||
imageSize?: T;
|
||||
imageShape?: T;
|
||||
showName?: T;
|
||||
showRole?: T;
|
||||
showDepartment?: T;
|
||||
showContact?: T;
|
||||
clickAction?: T;
|
||||
profileBasePath?: T;
|
||||
};
|
||||
connectors?:
|
||||
| T
|
||||
| {
|
||||
style?: T;
|
||||
color?: T;
|
||||
thickness?: T;
|
||||
animated?: T;
|
||||
};
|
||||
levels?:
|
||||
| T
|
||||
| {
|
||||
colorByLevel?: T;
|
||||
sizeByLevel?: T;
|
||||
collapsible?: T;
|
||||
initiallyExpanded?: T;
|
||||
};
|
||||
interaction?:
|
||||
| T
|
||||
| {
|
||||
zoomable?: T;
|
||||
pannable?: T;
|
||||
minimap?: T;
|
||||
search?: T;
|
||||
highlight?: T;
|
||||
fullscreen?: T;
|
||||
export?: T;
|
||||
};
|
||||
style?:
|
||||
| T
|
||||
| {
|
||||
bg?: T;
|
||||
nodeBg?: T;
|
||||
nodeShadow?: T;
|
||||
nodeBorder?: T;
|
||||
spacing?: T;
|
||||
minHeight?: T;
|
||||
};
|
||||
showLegend?: T;
|
||||
a11yLabel?: T;
|
||||
id?: T;
|
||||
blockName?: T;
|
||||
};
|
||||
};
|
||||
seo?:
|
||||
| T
|
||||
|
|
@ -3978,7 +4819,11 @@ export interface PostsSelect<T extends boolean = true> {
|
|||
featuredImage?: T;
|
||||
content?: T;
|
||||
categories?: T;
|
||||
tags?: T;
|
||||
author?: T;
|
||||
coAuthors?: T;
|
||||
authorLegacy?: T;
|
||||
readingTime?: T;
|
||||
status?: T;
|
||||
publishedAt?: T;
|
||||
seo?:
|
||||
|
|
@ -4060,6 +4905,7 @@ export interface FaqsSelect<T extends boolean = true> {
|
|||
export interface TeamSelect<T extends boolean = true> {
|
||||
tenant?: T;
|
||||
name?: T;
|
||||
slug?: T;
|
||||
role?: T;
|
||||
department?: T;
|
||||
image?: T;
|
||||
|
|
@ -4101,6 +4947,9 @@ export interface TeamSelect<T extends boolean = true> {
|
|||
isFeatured?: T;
|
||||
order?: T;
|
||||
startDate?: T;
|
||||
reportsTo?: T;
|
||||
hierarchyLevel?: T;
|
||||
allowVCard?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
|
|
@ -4584,6 +5433,49 @@ export interface WorkflowsSelect<T extends boolean = true> {
|
|||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "tags_select".
|
||||
*/
|
||||
export interface TagsSelect<T extends boolean = true> {
|
||||
tenant?: T;
|
||||
name?: T;
|
||||
slug?: T;
|
||||
description?: T;
|
||||
color?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "authors_select".
|
||||
*/
|
||||
export interface AuthorsSelect<T extends boolean = true> {
|
||||
tenant?: T;
|
||||
name?: T;
|
||||
slug?: T;
|
||||
avatar?: T;
|
||||
bio?: T;
|
||||
bioShort?: T;
|
||||
title?: T;
|
||||
email?: T;
|
||||
website?: T;
|
||||
social?:
|
||||
| T
|
||||
| {
|
||||
twitter?: T;
|
||||
linkedin?: T;
|
||||
github?: T;
|
||||
instagram?: T;
|
||||
};
|
||||
linkedTeam?: T;
|
||||
linkedUser?: T;
|
||||
isActive?: T;
|
||||
isGuest?: T;
|
||||
featured?: T;
|
||||
updatedAt?: T;
|
||||
createdAt?: T;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `Config`'s JSON-Schema
|
||||
* via the `definition` "cookie-configurations_select".
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ import { Timelines } from './collections/Timelines'
|
|||
// Workflow Collection
|
||||
import { Workflows } from './collections/Workflows'
|
||||
|
||||
// Blogging Collections
|
||||
import { Tags } from './collections/Tags'
|
||||
import { Authors } from './collections/Authors'
|
||||
|
||||
// Consent Management Collections
|
||||
import { CookieConfigurations } from './collections/CookieConfigurations'
|
||||
import { CookieInventory } from './collections/CookieInventory'
|
||||
|
|
@ -161,6 +165,9 @@ export default buildConfig({
|
|||
// Timelines & Workflows
|
||||
Timelines,
|
||||
Workflows,
|
||||
// Blogging
|
||||
Tags,
|
||||
Authors,
|
||||
// Consent Management
|
||||
CookieConfigurations,
|
||||
CookieInventory,
|
||||
|
|
@ -212,6 +219,9 @@ export default buildConfig({
|
|||
// Timeline & Workflow Collections
|
||||
timelines: {},
|
||||
workflows: {},
|
||||
// Blogging Collections
|
||||
tags: {},
|
||||
authors: {},
|
||||
// Consent Management Collections - customTenantField: true weil sie bereits ein tenant-Feld haben
|
||||
'cookie-configurations': { customTenantField: true },
|
||||
'cookie-inventory': { customTenantField: true },
|
||||
|
|
|
|||
Loading…
Reference in a new issue