mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-18 18:41:24 +02:00
refactor: restructure
monorepo with apps/ and services/ directories
This commit is contained in:
parent
25824ed0ac
commit
ff80aeec1f
4062 changed files with 2592 additions and 1278 deletions
91
apps/uload/docs/cards/README.md
Normal file
91
apps/uload/docs/cards/README.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# Card System Dokumentation
|
||||
|
||||
## Übersicht
|
||||
|
||||
Das Card System in uload ist eine flexible, modulare Architektur für die Erstellung und Verwaltung von wiederverwendbaren UI-Karten. Es ermöglicht die Erstellung von dynamischen, themebaren und vollständig konfigurierbaren Karten für verschiedene Anwendungsfälle.
|
||||
|
||||
## Inhaltsverzeichnis
|
||||
|
||||
1. [Architektur](./architecture.md)
|
||||
2. [Komponenten](./components.md)
|
||||
3. [Module](./modules.md)
|
||||
4. [Themes](./themes.md)
|
||||
5. [Templates](./templates.md)
|
||||
6. [API Reference](./api-reference.md)
|
||||
7. [Beispiele](./examples.md)
|
||||
|
||||
## Schnellstart
|
||||
|
||||
### Basis-Verwendung
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import BaseCard from '$lib/components/cards/BaseCard.svelte';
|
||||
|
||||
const cardConfig = {
|
||||
variant: 'default',
|
||||
modules: [
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Meine Karte',
|
||||
subtitle: 'Eine Beispielkarte'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
</script>
|
||||
|
||||
<BaseCard {...cardConfig} />
|
||||
```
|
||||
|
||||
## Hauptkonzepte
|
||||
|
||||
### 1. BaseCard
|
||||
|
||||
Die zentrale Komponente, die als Container für alle Kartentypen dient.
|
||||
|
||||
### 2. Module
|
||||
|
||||
Wiederverwendbare Bausteine, aus denen Karten zusammengesetzt werden:
|
||||
|
||||
- HeaderModule
|
||||
- ContentModule
|
||||
- LinksModule
|
||||
- MediaModule
|
||||
- StatsModule
|
||||
- ActionsModule
|
||||
- FooterModule
|
||||
|
||||
### 3. Themes
|
||||
|
||||
Anpassbare Designsysteme mit Farben, Typografie, Abständen und Animationen.
|
||||
|
||||
### 4. Templates
|
||||
|
||||
Vordefinierte Kartenkonfigurationen für häufige Anwendungsfälle.
|
||||
|
||||
## Features
|
||||
|
||||
- 🎨 **Vollständig themebare Komponenten**
|
||||
- 📦 **Modularer Aufbau**
|
||||
- 💾 **Datenbankgestützte Konfiguration**
|
||||
- 🎭 **Mehrere Varianten** (default, compact, hero, minimal, glass, gradient)
|
||||
- 📱 **Responsive Design**
|
||||
- ⚡ **Optimierte Performance**
|
||||
- ♿ **Barrierefreiheit**
|
||||
|
||||
## Verwendung in der App
|
||||
|
||||
Das Card System wird in verschiedenen Bereichen der uload-App verwendet:
|
||||
|
||||
1. **Profilseiten** - Anzeige von Benutzerinformationen
|
||||
2. **Link-Verwaltung** - Darstellung von Link-Sammlungen
|
||||
3. **Dashboard** - Statistiken und Übersichten
|
||||
4. **Template Store** - Marktplatz für Kartenvorlagen
|
||||
|
||||
## Nächste Schritte
|
||||
|
||||
- [Architektur verstehen](./architecture.md)
|
||||
- [Erste eigene Karte erstellen](./examples.md#erste-karte)
|
||||
- [Template Store erkunden](./templates.md#template-store)
|
||||
539
apps/uload/docs/cards/api-reference.md
Normal file
539
apps/uload/docs/cards/api-reference.md
Normal file
|
|
@ -0,0 +1,539 @@
|
|||
# Card System API Reference
|
||||
|
||||
## CardTemplateService
|
||||
|
||||
Der zentrale Service für die Verwaltung von Cards, Templates und Themes.
|
||||
|
||||
### Import
|
||||
|
||||
```javascript
|
||||
import { cardTemplateService } from '$lib/services/cardTemplates';
|
||||
```
|
||||
|
||||
## Methoden
|
||||
|
||||
### Theme-Verwaltung
|
||||
|
||||
#### `getPublicThemes()`
|
||||
|
||||
Lädt alle öffentlichen Themes.
|
||||
|
||||
**Returns:** `Promise<DBTheme[]>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
const themes = await cardTemplateService.getPublicThemes();
|
||||
```
|
||||
|
||||
#### `getTheme(id)`
|
||||
|
||||
Lädt ein spezifisches Theme.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `id: string` - Theme-ID
|
||||
|
||||
**Returns:** `Promise<DBTheme | null>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
const theme = await cardTemplateService.getTheme('theme_123');
|
||||
```
|
||||
|
||||
#### `createTheme(theme)`
|
||||
|
||||
Erstellt ein neues Theme.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `theme: Partial<DBTheme>` - Theme-Daten
|
||||
|
||||
**Returns:** `Promise<DBTheme | null>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
const newTheme = await cardTemplateService.createTheme({
|
||||
name: 'My Theme',
|
||||
colors: {
|
||||
primary: '#ff0000'
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### `updateTheme(id, updates)`
|
||||
|
||||
Aktualisiert ein existierendes Theme.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `id: string` - Theme-ID
|
||||
- `updates: Partial<DBTheme>` - Zu aktualisierende Felder
|
||||
|
||||
**Returns:** `Promise<DBTheme | null>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
await cardTemplateService.updateTheme('theme_123', {
|
||||
colors: { primary: '#00ff00' }
|
||||
});
|
||||
```
|
||||
|
||||
### Template-Verwaltung
|
||||
|
||||
#### `getPublicTemplates(category?)`
|
||||
|
||||
Lädt öffentliche Templates, optional gefiltert nach Kategorie.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `category?: string` - Optional: Kategorie-Filter
|
||||
|
||||
**Returns:** `Promise<DBCardTemplate[]>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
// Alle Templates
|
||||
const allTemplates = await cardTemplateService.getPublicTemplates();
|
||||
|
||||
// Nur Profile-Templates
|
||||
const profileTemplates = await cardTemplateService.getPublicTemplates('profile');
|
||||
```
|
||||
|
||||
#### `getTemplate(id)`
|
||||
|
||||
Lädt ein spezifisches Template.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `id: string` - Template-ID
|
||||
|
||||
**Returns:** `Promise<DBCardTemplate | null>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
const template = await cardTemplateService.getTemplate('template_123');
|
||||
```
|
||||
|
||||
#### `createTemplate(template)`
|
||||
|
||||
Erstellt ein neues Template.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `template: Partial<DBCardTemplate>` - Template-Daten
|
||||
|
||||
**Returns:** `Promise<DBCardTemplate | null>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
const newTemplate = await cardTemplateService.createTemplate({
|
||||
name: 'My Template',
|
||||
slug: 'my-template',
|
||||
modules: [
|
||||
/* ... */
|
||||
]
|
||||
});
|
||||
```
|
||||
|
||||
#### `updateTemplate(id, updates)`
|
||||
|
||||
Aktualisiert ein Template.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `id: string` - Template-ID
|
||||
- `updates: Partial<DBCardTemplate>` - Updates
|
||||
|
||||
**Returns:** `Promise<DBCardTemplate | null>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
await cardTemplateService.updateTemplate('template_123', {
|
||||
name: 'Updated Name'
|
||||
});
|
||||
```
|
||||
|
||||
### User Cards
|
||||
|
||||
#### `getUserCards(page)`
|
||||
|
||||
Lädt die Karten eines Benutzers für eine bestimmte Seite.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `page: string` - Seitenname (z.B. 'profile', 'dashboard')
|
||||
|
||||
**Returns:** `Promise<DBUserCard[]>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
const profileCards = await cardTemplateService.getUserCards('profile');
|
||||
```
|
||||
|
||||
#### `saveUserCard(card)`
|
||||
|
||||
Speichert oder aktualisiert eine Benutzerkarte.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `card: Partial<DBUserCard>` - Kartendaten
|
||||
|
||||
**Returns:** `Promise<DBUserCard | null>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
const savedCard = await cardTemplateService.saveUserCard({
|
||||
template_id: 'template_123',
|
||||
page: 'profile',
|
||||
position: 0,
|
||||
is_active: true
|
||||
});
|
||||
```
|
||||
|
||||
#### `deleteUserCard(id)`
|
||||
|
||||
Löscht eine Benutzerkarte.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `id: string` - Karten-ID
|
||||
|
||||
**Returns:** `Promise<boolean>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
const success = await cardTemplateService.deleteUserCard('card_123');
|
||||
```
|
||||
|
||||
### Konvertierungsmethoden
|
||||
|
||||
#### `templateToCardConfig(template)`
|
||||
|
||||
Konvertiert ein Datenbank-Template zu einer CardConfig.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `template: DBCardTemplate` - Template aus Datenbank
|
||||
|
||||
**Returns:** `CardConfig`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
const config = cardTemplateService.templateToCardConfig(dbTemplate);
|
||||
```
|
||||
|
||||
#### `dbThemeToThemeConfig(dbTheme)`
|
||||
|
||||
Konvertiert ein Datenbank-Theme zu einer ThemeConfig.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `dbTheme: DBTheme` - Theme aus Datenbank
|
||||
|
||||
**Returns:** `ThemeConfig`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
const themeConfig = cardTemplateService.dbThemeToThemeConfig(dbTheme);
|
||||
```
|
||||
|
||||
#### `userCardToCardConfig(userCard)`
|
||||
|
||||
Konvertiert eine Benutzerkarte zu einer CardConfig.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `userCard: DBUserCard` - Benutzerkarte
|
||||
|
||||
**Returns:** `CardConfig`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
const config = cardTemplateService.userCardToCardConfig(userCard);
|
||||
```
|
||||
|
||||
### Spezielle Methoden
|
||||
|
||||
#### `createStandardProfileCardTemplate(userData)`
|
||||
|
||||
Erstellt eine Standard-Profilkarten-Konfiguration.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
```typescript
|
||||
userData: {
|
||||
username?: string;
|
||||
email?: string;
|
||||
bio?: string;
|
||||
avatar?: string;
|
||||
websiteUrl?: string;
|
||||
socialLinks?: any[];
|
||||
totalLinks?: number;
|
||||
totalClicks?: number;
|
||||
memberSince?: string;
|
||||
showEmail?: boolean;
|
||||
showStats?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**Returns:** `CardConfig`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
const profileConfig = cardTemplateService.createStandardProfileCardTemplate({
|
||||
username: 'johndoe',
|
||||
bio: 'Software Developer',
|
||||
totalLinks: 42
|
||||
});
|
||||
```
|
||||
|
||||
#### `createOrUpdateStandardProfileCard(userData)`
|
||||
|
||||
Erstellt oder aktualisiert die Standard-Profilkarte eines Benutzers.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `userData: any` - Benutzerdaten
|
||||
|
||||
**Returns:** `Promise<DBUserCard | null>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
const card = await cardTemplateService.createOrUpdateStandardProfileCard({
|
||||
username: 'johndoe',
|
||||
bio: 'Updated bio'
|
||||
});
|
||||
```
|
||||
|
||||
#### `incrementDownloads(templateId)`
|
||||
|
||||
Erhöht den Download-Zähler eines Templates.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `templateId: string` - Template-ID
|
||||
|
||||
**Returns:** `Promise<void>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
await cardTemplateService.incrementDownloads('template_123');
|
||||
```
|
||||
|
||||
#### `rateTemplate(templateId, rating)`
|
||||
|
||||
Bewertet ein Template.
|
||||
|
||||
**Parameters:**
|
||||
|
||||
- `templateId: string` - Template-ID
|
||||
- `rating: number` - Bewertung (1-5)
|
||||
|
||||
**Returns:** `Promise<void>`
|
||||
|
||||
**Beispiel:**
|
||||
|
||||
```javascript
|
||||
await cardTemplateService.rateTemplate('template_123', 5);
|
||||
```
|
||||
|
||||
## Datentypen
|
||||
|
||||
### DBTheme
|
||||
|
||||
```typescript
|
||||
interface DBTheme {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
description?: string;
|
||||
author?: string;
|
||||
version?: string;
|
||||
is_public?: boolean;
|
||||
is_premium?: boolean;
|
||||
price?: number;
|
||||
colors?: any;
|
||||
typography?: any;
|
||||
spacing?: any;
|
||||
borderRadius?: any;
|
||||
shadows?: any;
|
||||
animations?: any;
|
||||
created: string;
|
||||
updated: string;
|
||||
}
|
||||
```
|
||||
|
||||
### DBCardTemplate
|
||||
|
||||
```typescript
|
||||
interface DBCardTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
description?: string;
|
||||
category?: string;
|
||||
theme_id?: string;
|
||||
is_public?: boolean;
|
||||
modules?: any;
|
||||
layout?: any;
|
||||
responsive?: any;
|
||||
preview_image?: string;
|
||||
downloads?: number;
|
||||
rating?: number;
|
||||
author_id?: string;
|
||||
created: string;
|
||||
updated: string;
|
||||
expand?: {
|
||||
theme_id?: DBTheme;
|
||||
author_id?: any;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### DBUserCard
|
||||
|
||||
```typescript
|
||||
interface DBUserCard {
|
||||
id: string;
|
||||
user_id: string;
|
||||
template_id?: string;
|
||||
page?: string;
|
||||
position?: number;
|
||||
custom_config?: any;
|
||||
is_active?: boolean;
|
||||
created: string;
|
||||
updated: string;
|
||||
expand?: {
|
||||
template_id?: DBCardTemplate;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### CardConfig
|
||||
|
||||
```typescript
|
||||
interface CardConfig {
|
||||
id?: string;
|
||||
variant?: 'default' | 'compact' | 'hero' | 'minimal' | 'glass' | 'gradient';
|
||||
theme?: ThemeConfig;
|
||||
modules?: ModuleConfig[];
|
||||
layout?: LayoutConfig;
|
||||
animations?: AnimationConfig;
|
||||
responsive?: ResponsiveConfig;
|
||||
className?: string;
|
||||
style?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### ModuleConfig
|
||||
|
||||
```typescript
|
||||
interface ModuleConfig {
|
||||
type: 'header' | 'content' | 'footer' | 'media' | 'stats' | 'actions' | 'links' | 'custom';
|
||||
component?: string;
|
||||
props?: Record<string, any>;
|
||||
order?: number;
|
||||
visibility?: 'always' | 'desktop' | 'mobile' | 'conditional';
|
||||
grid?: {
|
||||
col?: number;
|
||||
row?: number;
|
||||
colSpan?: number;
|
||||
rowSpan?: number;
|
||||
};
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### ThemeConfig
|
||||
|
||||
```typescript
|
||||
interface ThemeConfig {
|
||||
id?: string;
|
||||
name?: string;
|
||||
colors?: {
|
||||
primary?: string;
|
||||
secondary?: string;
|
||||
accent?: string;
|
||||
background?: string;
|
||||
surface?: string;
|
||||
text?: string;
|
||||
textMuted?: string;
|
||||
border?: string;
|
||||
hover?: string;
|
||||
[key: string]: string | undefined;
|
||||
};
|
||||
typography?: {
|
||||
fontFamily?: string;
|
||||
fontSize?: Record<string, string>;
|
||||
fontWeight?: Record<string, number>;
|
||||
lineHeight?: Record<string, string>;
|
||||
};
|
||||
spacing?: Record<string, string>;
|
||||
borderRadius?: Record<string, string>;
|
||||
shadows?: Record<string, string>;
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
Alle Service-Methoden geben `null` zurück oder werfen Fehler, die abgefangen werden sollten:
|
||||
|
||||
```javascript
|
||||
try {
|
||||
const template = await cardTemplateService.getTemplate('invalid_id');
|
||||
if (!template) {
|
||||
console.log('Template nicht gefunden');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Laden des Templates:', error);
|
||||
}
|
||||
```
|
||||
|
||||
## Authentifizierung
|
||||
|
||||
Einige Methoden erfordern eine aktive Authentifizierung über PocketBase:
|
||||
|
||||
```javascript
|
||||
import { pb } from '$lib/pocketbase';
|
||||
|
||||
// Prüfen ob Benutzer eingeloggt ist
|
||||
if (pb.authStore.model) {
|
||||
// Authentifizierte Aktionen
|
||||
const userCards = await cardTemplateService.getUserCards('profile');
|
||||
} else {
|
||||
// Redirect zu Login
|
||||
goto('/login');
|
||||
}
|
||||
```
|
||||
|
||||
## Performance-Tipps
|
||||
|
||||
1. **Caching**: Templates und Themes können gecached werden
|
||||
2. **Lazy Loading**: Lade nur benötigte Daten
|
||||
3. **Batch Operations**: Nutze Promise.all für parallele Anfragen
|
||||
4. **Pagination**: Nutze Pagination für große Listen
|
||||
|
||||
```javascript
|
||||
// Parallel laden
|
||||
const [themes, templates] = await Promise.all([
|
||||
cardTemplateService.getPublicThemes(),
|
||||
cardTemplateService.getPublicTemplates()
|
||||
]);
|
||||
```
|
||||
276
apps/uload/docs/cards/architecture.md
Normal file
276
apps/uload/docs/cards/architecture.md
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
# Card System Architektur
|
||||
|
||||
## Überblick
|
||||
|
||||
Das Card System basiert auf einer modularen, komponentenbasierten Architektur, die maximale Flexibilität und Wiederverwendbarkeit bietet.
|
||||
|
||||
## Architektur-Diagramm
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────┐
|
||||
│ ThemeProvider │
|
||||
│ (Globale Theme-Konfiguration) │
|
||||
└────────────────┬────────────────────────┘
|
||||
│
|
||||
┌────────────────▼────────────────────────┐
|
||||
│ BaseCard │
|
||||
│ (Container-Komponente) │
|
||||
│ │
|
||||
│ Props: │
|
||||
│ - variant │
|
||||
│ - theme │
|
||||
│ - modules[] │
|
||||
│ - layout │
|
||||
│ - animations │
|
||||
└────────────────┬────────────────────────┘
|
||||
│
|
||||
┌────────────┼────────────┐
|
||||
│ │ │
|
||||
┌───▼───┐ ┌───▼───┐ ┌───▼───┐
|
||||
│Module1│ │Module2│ │Module3│
|
||||
└───────┘ └───────┘ └───────┘
|
||||
```
|
||||
|
||||
## Komponenten-Hierarchie
|
||||
|
||||
### 1. BaseCard (`/src/lib/components/cards/BaseCard.svelte`)
|
||||
|
||||
Die Hauptkomponente, die als Container für alle Module dient.
|
||||
|
||||
**Eigenschaften:**
|
||||
|
||||
- **variant**: Visuelle Variante der Karte
|
||||
- **theme**: Theme-Konfiguration
|
||||
- **modules**: Array von Modul-Konfigurationen
|
||||
- **layout**: Layout-Einstellungen
|
||||
- **animations**: Animations-Konfiguration
|
||||
- **responsive**: Responsive-Verhalten
|
||||
|
||||
**Verantwortlichkeiten:**
|
||||
|
||||
- Modul-Rendering
|
||||
- Theme-Anwendung
|
||||
- Layout-Management
|
||||
- Animations-Steuerung
|
||||
|
||||
### 2. Module System
|
||||
|
||||
Module sind die Bausteine einer Karte. Jedes Modul ist eine eigenständige Komponente mit spezifischer Funktionalität.
|
||||
|
||||
**Verfügbare Module:**
|
||||
|
||||
#### HeaderModule
|
||||
|
||||
```typescript
|
||||
interface HeaderModuleProps {
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
avatar?: string;
|
||||
badge?: string;
|
||||
icon?: string;
|
||||
actions?: Array<{
|
||||
label: string;
|
||||
action: () => void;
|
||||
icon?: string;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
#### ContentModule
|
||||
|
||||
```typescript
|
||||
interface ContentModuleProps {
|
||||
text?: string;
|
||||
html?: string;
|
||||
items?: Array<{
|
||||
label: string;
|
||||
value: string | number;
|
||||
icon?: string;
|
||||
}>;
|
||||
truncate?: boolean;
|
||||
maxLines?: number;
|
||||
}
|
||||
```
|
||||
|
||||
#### LinksModule
|
||||
|
||||
```typescript
|
||||
interface LinksModuleProps {
|
||||
links: Array<{
|
||||
label: string;
|
||||
href: string;
|
||||
icon?: string;
|
||||
description?: string;
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
style?: 'button' | 'list' | 'card';
|
||||
columns?: 1 | 2;
|
||||
showDescription?: boolean;
|
||||
showIcon?: boolean;
|
||||
target?: '_blank' | '_self';
|
||||
buttonVariant?: 'primary' | 'secondary' | 'ghost' | 'outline';
|
||||
gap?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
```
|
||||
|
||||
## Datenfluss
|
||||
|
||||
```
|
||||
Datenbank (PocketBase)
|
||||
│
|
||||
▼
|
||||
Service Layer
|
||||
(cardTemplates.ts)
|
||||
│
|
||||
▼
|
||||
Store/State
|
||||
│
|
||||
▼
|
||||
UI Components
|
||||
(BaseCard + Module)
|
||||
│
|
||||
▼
|
||||
Rendering
|
||||
```
|
||||
|
||||
## Service Layer
|
||||
|
||||
### CardTemplateService (`/src/lib/services/cardTemplates.ts`)
|
||||
|
||||
Der Service verwaltet die Kommunikation mit der Datenbank und die Transformation von Daten.
|
||||
|
||||
**Hauptmethoden:**
|
||||
|
||||
- `getPublicTemplates()` - Lädt öffentliche Templates
|
||||
- `getTemplate(id)` - Lädt ein spezifisches Template
|
||||
- `createTemplate(template)` - Erstellt neues Template
|
||||
- `updateTemplate(id, updates)` - Aktualisiert Template
|
||||
- `getUserCards(page)` - Lädt Benutzerkarten
|
||||
- `saveUserCard(card)` - Speichert Benutzerkarte
|
||||
- `templateToCardConfig(template)` - Konvertiert DB-Template zu CardConfig
|
||||
- `dbThemeToThemeConfig(dbTheme)` - Konvertiert DB-Theme zu ThemeConfig
|
||||
|
||||
## Datenbank-Schema
|
||||
|
||||
### Collections in PocketBase
|
||||
|
||||
#### `themes`
|
||||
|
||||
Speichert Theme-Konfigurationen
|
||||
|
||||
- Farben, Typografie, Abstände
|
||||
- Öffentlich/Privat
|
||||
- Premium/Free
|
||||
|
||||
#### `card_templates`
|
||||
|
||||
Vordefinierte Kartenkonfigurationen
|
||||
|
||||
- Module-Array
|
||||
- Layout-Einstellungen
|
||||
- Kategorie und Tags
|
||||
|
||||
#### `user_cards`
|
||||
|
||||
Benutzerspezifische Karteninstanzen
|
||||
|
||||
- Verknüpfung zu Templates
|
||||
- Custom-Konfiguration
|
||||
- Position und Sichtbarkeit
|
||||
|
||||
## Konfigurationsstruktur
|
||||
|
||||
### CardConfig
|
||||
|
||||
```typescript
|
||||
interface CardConfig {
|
||||
id?: string;
|
||||
variant?: 'default' | 'compact' | 'hero' | 'minimal' | 'glass' | 'gradient';
|
||||
theme?: ThemeConfig;
|
||||
modules?: ModuleConfig[];
|
||||
layout?: LayoutConfig;
|
||||
animations?: AnimationConfig;
|
||||
responsive?: ResponsiveConfig;
|
||||
className?: string;
|
||||
style?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### ModuleConfig
|
||||
|
||||
```typescript
|
||||
interface ModuleConfig {
|
||||
type: 'header' | 'content' | 'footer' | 'media' | 'stats' | 'actions' | 'links' | 'custom';
|
||||
component?: string;
|
||||
props?: Record<string, any>;
|
||||
order?: number;
|
||||
visibility?: 'always' | 'desktop' | 'mobile' | 'conditional';
|
||||
grid?: {
|
||||
col?: number;
|
||||
row?: number;
|
||||
colSpan?: number;
|
||||
rowSpan?: number;
|
||||
};
|
||||
className?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### ThemeConfig
|
||||
|
||||
```typescript
|
||||
interface ThemeConfig {
|
||||
id?: string;
|
||||
name?: string;
|
||||
colors?: {
|
||||
primary?: string;
|
||||
secondary?: string;
|
||||
accent?: string;
|
||||
background?: string;
|
||||
surface?: string;
|
||||
text?: string;
|
||||
textMuted?: string;
|
||||
border?: string;
|
||||
hover?: string;
|
||||
};
|
||||
typography?: {
|
||||
fontFamily?: string;
|
||||
fontSize?: Record<string, string>;
|
||||
fontWeight?: Record<string, number>;
|
||||
lineHeight?: Record<string, string>;
|
||||
};
|
||||
spacing?: Record<string, string>;
|
||||
borderRadius?: Record<string, string>;
|
||||
shadows?: Record<string, string>;
|
||||
}
|
||||
```
|
||||
|
||||
## Performance-Optimierungen
|
||||
|
||||
1. **Lazy Loading**: Module werden nur bei Bedarf geladen
|
||||
2. **Memoization**: Berechnete Werte werden gecached
|
||||
3. **Virtual Scrolling**: Bei langen Listen
|
||||
4. **Bundle Splitting**: Separate Bundles für Module
|
||||
5. **CSS-in-JS**: Nur benötigte Styles werden geladen
|
||||
|
||||
## Erweiterbarkeit
|
||||
|
||||
### Neue Module hinzufügen
|
||||
|
||||
1. Komponente in `/src/lib/components/cards/modules/` erstellen
|
||||
2. Props-Interface in `types.ts` definieren
|
||||
3. Module in `BaseCard.svelte` registrieren
|
||||
4. Dokumentation aktualisieren
|
||||
|
||||
### Custom Themes erstellen
|
||||
|
||||
1. Theme-Objekt nach ThemeConfig-Interface erstellen
|
||||
2. In Datenbank speichern oder lokal verwenden
|
||||
3. Mit ThemeProvider anwenden
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Modularität**: Halte Module klein und fokussiert
|
||||
2. **Typsicherheit**: Verwende TypeScript-Interfaces
|
||||
3. **Responsive**: Teste auf verschiedenen Bildschirmgrößen
|
||||
4. **Performance**: Minimiere Re-Renders
|
||||
5. **Barrierefreiheit**: ARIA-Labels und Keyboard-Navigation
|
||||
350
apps/uload/docs/cards/components.md
Normal file
350
apps/uload/docs/cards/components.md
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
# Card System Komponenten
|
||||
|
||||
## BaseCard
|
||||
|
||||
Die zentrale Komponente des Card Systems.
|
||||
|
||||
### Import
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import BaseCard from '$lib/components/cards/BaseCard.svelte';
|
||||
</script>
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
| Prop | Type | Default | Beschreibung |
|
||||
| ------------ | ------------------ | ----------- | ------------------------------------------------------------------------------- |
|
||||
| `variant` | `string` | `'default'` | Visuelle Variante: `default`, `compact`, `hero`, `minimal`, `glass`, `gradient` |
|
||||
| `theme` | `ThemeConfig` | `{}` | Theme-Konfiguration |
|
||||
| `modules` | `ModuleConfig[]` | `[]` | Array von Modul-Konfigurationen |
|
||||
| `layout` | `LayoutConfig` | `{}` | Layout-Einstellungen |
|
||||
| `animations` | `AnimationConfig` | `{}` | Animations-Konfiguration |
|
||||
| `responsive` | `ResponsiveConfig` | `{}` | Responsive-Einstellungen |
|
||||
| `className` | `string` | `''` | Zusätzliche CSS-Klassen |
|
||||
| `style` | `string` | `''` | Inline-Styles |
|
||||
|
||||
### Varianten
|
||||
|
||||
#### Default
|
||||
|
||||
```svelte
|
||||
<BaseCard variant="default">
|
||||
<!-- Standard-Karte mit Border und Shadow -->
|
||||
</BaseCard>
|
||||
```
|
||||
|
||||
#### Compact
|
||||
|
||||
```svelte
|
||||
<BaseCard variant="compact">
|
||||
<!-- Kompakte Karte mit reduziertem Padding -->
|
||||
</BaseCard>
|
||||
```
|
||||
|
||||
#### Hero
|
||||
|
||||
```svelte
|
||||
<BaseCard variant="hero">
|
||||
<!-- Große Karte mit Gradient-Hintergrund -->
|
||||
</BaseCard>
|
||||
```
|
||||
|
||||
#### Minimal
|
||||
|
||||
```svelte
|
||||
<BaseCard variant="minimal">
|
||||
<!-- Minimalistische Karte ohne Border -->
|
||||
</BaseCard>
|
||||
```
|
||||
|
||||
#### Glass
|
||||
|
||||
```svelte
|
||||
<BaseCard variant="glass">
|
||||
<!-- Glasmorphismus-Effekt -->
|
||||
</BaseCard>
|
||||
```
|
||||
|
||||
#### Gradient
|
||||
|
||||
```svelte
|
||||
<BaseCard variant="gradient">
|
||||
<!-- Gradient-Hintergrund -->
|
||||
</BaseCard>
|
||||
```
|
||||
|
||||
## CardBuilder
|
||||
|
||||
Interaktiver Builder zum Erstellen von Karten.
|
||||
|
||||
### Import
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import CardBuilder from '$lib/components/builder/CardBuilder.svelte';
|
||||
</script>
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
| Prop | Type | Default | Beschreibung |
|
||||
| --------------- | ------------- | ------- | ----------------------- |
|
||||
| `initialConfig` | `CardConfig` | `{}` | Initiale Konfiguration |
|
||||
| `theme` | `ThemeConfig` | `{}` | Theme für Preview |
|
||||
| `onSave` | `Function` | - | Callback beim Speichern |
|
||||
| `onCancel` | `Function` | - | Callback beim Abbrechen |
|
||||
|
||||
### Beispiel
|
||||
|
||||
```svelte
|
||||
<CardBuilder
|
||||
initialConfig={myCardConfig}
|
||||
theme={myTheme}
|
||||
onSave={(config) => saveCard(config)}
|
||||
onCancel={() => goto('/cards')}
|
||||
/>
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- Drag & Drop für Module
|
||||
- Live-Preview
|
||||
- Modul-Editor
|
||||
- Export als JSON oder Svelte-Code
|
||||
- Theme-Auswahl
|
||||
|
||||
## ThemeProvider
|
||||
|
||||
Stellt Theme-Kontext für Kinder-Komponenten bereit.
|
||||
|
||||
### Import
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import ThemeProvider from '$lib/components/cards/ThemeProvider.svelte';
|
||||
</script>
|
||||
```
|
||||
|
||||
### Verwendung
|
||||
|
||||
```svelte
|
||||
<ThemeProvider theme={myTheme}>
|
||||
<BaseCard {...cardConfig} />
|
||||
</ThemeProvider>
|
||||
```
|
||||
|
||||
### Theme-Struktur
|
||||
|
||||
```javascript
|
||||
const theme = {
|
||||
colors: {
|
||||
primary: '#3b82f6',
|
||||
secondary: '#8b5cf6',
|
||||
accent: '#ec4899',
|
||||
background: '#ffffff',
|
||||
surface: '#f9fafb',
|
||||
text: '#111827',
|
||||
textMuted: '#6b7280',
|
||||
border: '#e5e7eb',
|
||||
hover: '#f3f4f6'
|
||||
},
|
||||
typography: {
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
fontSize: {
|
||||
xs: '0.75rem',
|
||||
sm: '0.875rem',
|
||||
md: '1rem',
|
||||
lg: '1.125rem',
|
||||
xl: '1.25rem'
|
||||
}
|
||||
},
|
||||
spacing: {
|
||||
xs: '0.25rem',
|
||||
sm: '0.5rem',
|
||||
md: '1rem',
|
||||
lg: '1.5rem',
|
||||
xl: '2rem'
|
||||
},
|
||||
borderRadius: {
|
||||
sm: '0.25rem',
|
||||
md: '0.5rem',
|
||||
lg: '0.75rem',
|
||||
xl: '1rem',
|
||||
full: '9999px'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## ModuleEditor
|
||||
|
||||
Editor für einzelne Module innerhalb des CardBuilders.
|
||||
|
||||
### Features
|
||||
|
||||
- Prop-Editor für jedes Modul
|
||||
- Visuelle Konfiguration
|
||||
- Echtzeit-Vorschau
|
||||
- Validierung
|
||||
|
||||
## ProfileInfoCard
|
||||
|
||||
Spezialisierte Karte für Benutzerprofile.
|
||||
|
||||
### Import
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import ProfileInfoCard from '$lib/components/profile/ProfileInfoCard.svelte';
|
||||
</script>
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
```typescript
|
||||
interface ProfileInfoCardProps {
|
||||
user: {
|
||||
username?: string;
|
||||
name?: string;
|
||||
avatar?: string;
|
||||
bio?: string;
|
||||
location?: string;
|
||||
website?: string;
|
||||
github?: string;
|
||||
twitter?: string;
|
||||
linkedin?: string;
|
||||
instagram?: string;
|
||||
showClickStats?: boolean;
|
||||
created?: string;
|
||||
};
|
||||
totalLinks?: number;
|
||||
totalFolders?: number;
|
||||
totalClicks?: number;
|
||||
memberSince?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Beispiel
|
||||
|
||||
```svelte
|
||||
<ProfileInfoCard
|
||||
user={userData}
|
||||
totalLinks={25}
|
||||
totalFolders={5}
|
||||
totalClicks={1337}
|
||||
memberSince="January 2024"
|
||||
/>
|
||||
```
|
||||
|
||||
## LinksCard
|
||||
|
||||
Karte zur Anzeige von Link-Sammlungen.
|
||||
|
||||
### Import
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import LinksCard from '$lib/components/profile/LinksCard.svelte';
|
||||
</script>
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
```typescript
|
||||
interface LinksCardProps {
|
||||
links: Link[];
|
||||
folders: Folder[];
|
||||
username: string;
|
||||
showClickStats?: boolean;
|
||||
oneLinkQR?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- Suche und Filter
|
||||
- Sortierung (Recent, Clicks, Title)
|
||||
- Ordner-Filter
|
||||
- QR-Code-Anzeige
|
||||
- Click-Statistiken
|
||||
|
||||
## Komposition
|
||||
|
||||
### Beispiel: Dashboard-Karte
|
||||
|
||||
```svelte
|
||||
<BaseCard
|
||||
variant="default"
|
||||
layout={{
|
||||
padding: '1.5rem',
|
||||
columns: 2,
|
||||
gap: '1rem'
|
||||
}}
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Dashboard',
|
||||
subtitle: 'Übersicht deiner Aktivitäten',
|
||||
icon: '📊'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'stats',
|
||||
props: {
|
||||
stats: [
|
||||
{ label: 'Links', value: 42, icon: '🔗' },
|
||||
{ label: 'Clicks', value: '1.2k', icon: '👆' },
|
||||
{ label: 'Conversion', value: '24%', icon: '📈' }
|
||||
],
|
||||
layout: 'grid'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'actions',
|
||||
props: {
|
||||
actions: [
|
||||
{ label: 'Neuer Link', variant: 'primary' },
|
||||
{ label: 'Statistiken', variant: 'secondary' }
|
||||
],
|
||||
layout: 'horizontal'
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
### CSS-Variablen
|
||||
|
||||
Alle Komponenten nutzen CSS-Variablen für Theming:
|
||||
|
||||
```css
|
||||
--card-primary: #3b82f6;
|
||||
--card-secondary: #8b5cf6;
|
||||
--card-accent: #ec4899;
|
||||
--card-background: #ffffff;
|
||||
--card-surface: #f9fafb;
|
||||
--card-text: #111827;
|
||||
--card-text-muted: #6b7280;
|
||||
--card-border: #e5e7eb;
|
||||
```
|
||||
|
||||
### Tailwind-Integration
|
||||
|
||||
Die Komponenten verwenden Tailwind-Klassen mit Theme-Präfix:
|
||||
|
||||
- `bg-theme-primary`
|
||||
- `text-theme-text`
|
||||
- `border-theme-border`
|
||||
- etc.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Verwende die richtige Variante** für deinen Use-Case
|
||||
2. **Halte Module fokussiert** - Ein Modul, eine Aufgabe
|
||||
3. **Nutze Theme-Provider** für konsistentes Styling
|
||||
4. **Teste Responsive-Verhalten** auf verschiedenen Geräten
|
||||
5. **Optimiere Performance** durch lazy loading großer Module
|
||||
579
apps/uload/docs/cards/examples.md
Normal file
579
apps/uload/docs/cards/examples.md
Normal file
|
|
@ -0,0 +1,579 @@
|
|||
# Card System Beispiele
|
||||
|
||||
## Erste Karte
|
||||
|
||||
### Einfache Karte mit Header
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import BaseCard from '$lib/components/cards/BaseCard.svelte';
|
||||
</script>
|
||||
|
||||
<BaseCard
|
||||
variant="default"
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Meine erste Karte',
|
||||
subtitle: 'Ein einfaches Beispiel',
|
||||
icon: '🎉'
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### Karte mit mehreren Modulen
|
||||
|
||||
```svelte
|
||||
<BaseCard
|
||||
variant="default"
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
order: 0,
|
||||
props: {
|
||||
title: 'Komplexere Karte',
|
||||
subtitle: 'Mit mehreren Modulen'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'content',
|
||||
order: 1,
|
||||
props: {
|
||||
text: 'Dies ist der Hauptinhalt der Karte. Hier kann beliebiger Text stehen.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'actions',
|
||||
order: 2,
|
||||
props: {
|
||||
actions: [
|
||||
{ label: 'Mehr erfahren', variant: 'primary' },
|
||||
{ label: 'Schließen', variant: 'ghost' }
|
||||
]
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
## Profil-Karten
|
||||
|
||||
### Basis-Profil
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import BaseCard from '$lib/components/cards/BaseCard.svelte';
|
||||
|
||||
const userData = {
|
||||
name: 'Max Mustermann',
|
||||
role: 'Software Developer',
|
||||
avatar: '/avatars/max.jpg',
|
||||
bio: 'Passionate about creating amazing web experiences.'
|
||||
};
|
||||
</script>
|
||||
|
||||
<BaseCard
|
||||
variant="default"
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: userData.name,
|
||||
subtitle: userData.role,
|
||||
avatar: userData.avatar
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'content',
|
||||
props: {
|
||||
text: userData.bio
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### Erweitertes Profil mit Stats
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
const profileConfig = {
|
||||
variant: 'hero',
|
||||
modules: [
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Jane Doe',
|
||||
subtitle: 'UX Designer',
|
||||
avatar: '/avatars/jane.jpg',
|
||||
badge: 'PRO'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'content',
|
||||
props: {
|
||||
text: 'Creating beautiful and functional user experiences since 2015.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'stats',
|
||||
props: {
|
||||
stats: [
|
||||
{ label: 'Projects', value: 127, icon: '📁' },
|
||||
{ label: 'Clients', value: 45, icon: '👥' },
|
||||
{ label: 'Awards', value: 8, icon: '🏆' }
|
||||
],
|
||||
layout: 'grid'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'links',
|
||||
props: {
|
||||
links: [
|
||||
{ label: 'Portfolio', href: 'https://portfolio.com', icon: '🎨' },
|
||||
{ label: 'LinkedIn', href: 'https://linkedin.com', icon: '💼' },
|
||||
{ label: 'GitHub', href: 'https://github.com', icon: '💻' }
|
||||
],
|
||||
style: 'button',
|
||||
showIcon: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
</script>
|
||||
|
||||
<BaseCard {...profileConfig} />
|
||||
```
|
||||
|
||||
## Dashboard-Karten
|
||||
|
||||
### Statistik-Karte
|
||||
|
||||
```svelte
|
||||
<BaseCard
|
||||
variant="compact"
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Verkaufsstatistik',
|
||||
subtitle: 'Letzten 30 Tage'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'stats',
|
||||
props: {
|
||||
stats: [
|
||||
{ label: 'Umsatz', value: '€12.4k', change: 12, color: 'green' },
|
||||
{ label: 'Bestellungen', value: 234, change: -5, color: 'red' },
|
||||
{ label: 'Conversion', value: '3.2%', change: 8, color: 'blue' }
|
||||
],
|
||||
layout: 'list'
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### Activity Feed
|
||||
|
||||
```svelte
|
||||
<BaseCard
|
||||
variant="default"
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Letzte Aktivitäten',
|
||||
icon: '📋'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'content',
|
||||
props: {
|
||||
items: [
|
||||
{ label: 'Neuer Benutzer', value: 'vor 5 Min.', icon: '👤' },
|
||||
{ label: 'Bestellung #1234', value: 'vor 15 Min.', icon: '🛒' },
|
||||
{ label: 'Kommentar erhalten', value: 'vor 1 Std.', icon: '💬' },
|
||||
{ label: 'System-Update', value: 'vor 2 Std.', icon: '🔄' }
|
||||
]
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
## Link-Sammlungen
|
||||
|
||||
### Social Media Links
|
||||
|
||||
```svelte
|
||||
<BaseCard
|
||||
variant="minimal"
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Folge mir',
|
||||
subtitle: 'auf Social Media'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'links',
|
||||
props: {
|
||||
links: [
|
||||
{ label: 'Instagram', href: 'https://instagram.com/user', icon: '📷' },
|
||||
{ label: 'Twitter', href: 'https://twitter.com/user', icon: '🐦' },
|
||||
{ label: 'YouTube', href: 'https://youtube.com/user', icon: '📺' },
|
||||
{ label: 'TikTok', href: 'https://tiktok.com/@user', icon: '🎵' }
|
||||
],
|
||||
style: 'button',
|
||||
columns: 2,
|
||||
buttonVariant: 'secondary',
|
||||
showIcon: true
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### Ressourcen-Liste
|
||||
|
||||
```svelte
|
||||
<BaseCard
|
||||
variant="default"
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Nützliche Ressourcen'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'links',
|
||||
props: {
|
||||
links: [
|
||||
{
|
||||
label: 'Dokumentation',
|
||||
href: '/docs',
|
||||
icon: '📚',
|
||||
description: 'Vollständige API-Dokumentation'
|
||||
},
|
||||
{
|
||||
label: 'Tutorials',
|
||||
href: '/tutorials',
|
||||
icon: '🎓',
|
||||
description: 'Schritt-für-Schritt Anleitungen'
|
||||
},
|
||||
{
|
||||
label: 'Community Forum',
|
||||
href: '/forum',
|
||||
icon: '💬',
|
||||
description: 'Hilfe von der Community'
|
||||
}
|
||||
],
|
||||
style: 'card',
|
||||
showDescription: true,
|
||||
showIcon: true
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
## Media-Karten
|
||||
|
||||
### Bild-Galerie
|
||||
|
||||
```svelte
|
||||
<BaseCard
|
||||
variant="default"
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Projekt Screenshots'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'media',
|
||||
props: {
|
||||
type: 'image',
|
||||
src: '/screenshots/dashboard.png',
|
||||
alt: 'Dashboard Screenshot',
|
||||
aspectRatio: '16/9'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'content',
|
||||
props: {
|
||||
text: 'Das neue Dashboard-Design mit verbesserter Benutzerführung.'
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
### QR-Code Karte
|
||||
|
||||
```svelte
|
||||
<BaseCard
|
||||
variant="compact"
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Mein QR-Code',
|
||||
subtitle: 'Scanne für Kontaktdaten'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'media',
|
||||
props: {
|
||||
type: 'qr',
|
||||
qrData: 'https://example.com/contact',
|
||||
qrSize: 200,
|
||||
qrColor: '#000000'
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
## Mit Themes
|
||||
|
||||
### Dark Theme Karte
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import ThemeProvider from '$lib/components/cards/ThemeProvider.svelte';
|
||||
|
||||
const darkTheme = {
|
||||
colors: {
|
||||
primary: '#60a5fa',
|
||||
secondary: '#a78bfa',
|
||||
accent: '#f472b6',
|
||||
background: '#111827',
|
||||
surface: '#1f2937',
|
||||
text: '#f9fafb',
|
||||
textMuted: '#9ca3af',
|
||||
border: '#374151',
|
||||
hover: '#374151'
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<ThemeProvider theme={darkTheme}>
|
||||
<BaseCard
|
||||
variant="default"
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Dark Mode Karte',
|
||||
subtitle: 'Mit custom Theme'
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
```
|
||||
|
||||
### Gradient Theme
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
const gradientTheme = {
|
||||
colors: {
|
||||
primary: '#ff6b6b',
|
||||
secondary: '#4ecdc4',
|
||||
accent: '#45b7d1',
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
||||
text: '#ffffff'
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<BaseCard
|
||||
variant="gradient"
|
||||
theme={gradientTheme}
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Gradient Card',
|
||||
subtitle: 'Mit Farbverlauf'
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
## Dynamische Karten
|
||||
|
||||
### Karte aus Datenbank laden
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { cardTemplateService } from '$lib/services/cardTemplates';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
let cardConfig = null;
|
||||
|
||||
onMount(async () => {
|
||||
const template = await cardTemplateService.getTemplate('template_123');
|
||||
if (template) {
|
||||
cardConfig = cardTemplateService.templateToCardConfig(template);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if cardConfig}
|
||||
<BaseCard {...cardConfig} />
|
||||
{/if}
|
||||
```
|
||||
|
||||
### Benutzer-spezifische Karte
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { pb } from '$lib/pocketbase';
|
||||
|
||||
let userCards = [];
|
||||
|
||||
async function loadUserCards() {
|
||||
userCards = await cardTemplateService.getUserCards('profile');
|
||||
}
|
||||
|
||||
onMount(loadUserCards);
|
||||
</script>
|
||||
|
||||
{#each userCards as userCard}
|
||||
<BaseCard {...cardTemplateService.userCardToCardConfig(userCard)} />
|
||||
{/each}
|
||||
```
|
||||
|
||||
## Card Builder Integration
|
||||
|
||||
### Karte bearbeiten
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import CardBuilder from '$lib/components/builder/CardBuilder.svelte';
|
||||
|
||||
let editMode = false;
|
||||
let cardConfig = {
|
||||
/* ... */
|
||||
};
|
||||
|
||||
function handleSave(newConfig) {
|
||||
cardConfig = newConfig;
|
||||
editMode = false;
|
||||
// Speichern in Datenbank
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if editMode}
|
||||
<CardBuilder initialConfig={cardConfig} onSave={handleSave} onCancel={() => (editMode = false)} />
|
||||
{:else}
|
||||
<BaseCard {...cardConfig} />
|
||||
<button onclick={() => (editMode = true)}>Bearbeiten</button>
|
||||
{/if}
|
||||
```
|
||||
|
||||
## Responsive Karten
|
||||
|
||||
### Mobile-optimierte Karte
|
||||
|
||||
```svelte
|
||||
<BaseCard
|
||||
variant="default"
|
||||
responsive={{
|
||||
breakpoints: {
|
||||
sm: '640px',
|
||||
md: '768px',
|
||||
lg: '1024px'
|
||||
},
|
||||
mobileLayout: 'stack'
|
||||
}}
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
visibility: 'always',
|
||||
props: { title: 'Responsive Karte' }
|
||||
},
|
||||
{
|
||||
type: 'content',
|
||||
visibility: 'desktop', // Nur auf Desktop
|
||||
props: { text: 'Dieser Text ist nur auf Desktop sichtbar.' }
|
||||
},
|
||||
{
|
||||
type: 'actions',
|
||||
visibility: 'mobile', // Nur auf Mobile
|
||||
props: {
|
||||
actions: [{ label: 'Mobile Action', variant: 'primary' }]
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
## Animierte Karten
|
||||
|
||||
### Mit Eingangs-Animation
|
||||
|
||||
```svelte
|
||||
<BaseCard
|
||||
variant="default"
|
||||
animations={{
|
||||
hover: true,
|
||||
entrance: 'slide',
|
||||
duration: 300,
|
||||
delay: 100
|
||||
}}
|
||||
modules={[
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Animierte Karte',
|
||||
subtitle: 'Mit Slide-In Effekt'
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
```
|
||||
|
||||
## Fehlerbehandlung
|
||||
|
||||
### Mit Fallback
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
let cardConfig = null;
|
||||
let error = null;
|
||||
|
||||
async function loadCard() {
|
||||
try {
|
||||
const template = await cardTemplateService.getTemplate('id');
|
||||
cardConfig = cardTemplateService.templateToCardConfig(template);
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
<BaseCard variant="minimal">
|
||||
<p>Fehler: {error}</p>
|
||||
</BaseCard>
|
||||
{:else if cardConfig}
|
||||
<BaseCard {...cardConfig} />
|
||||
{:else}
|
||||
<BaseCard variant="minimal">
|
||||
<p>Lädt...</p>
|
||||
</BaseCard>
|
||||
{/if}
|
||||
```
|
||||
304
apps/uload/docs/cards/implementation-comparison.md
Normal file
304
apps/uload/docs/cards/implementation-comparison.md
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
# Implementierungs-Vergleich: Module vs. HTML Cards
|
||||
|
||||
## Zusammenfassung für Entscheidungsfindung
|
||||
|
||||
### 🎯 Die Kernfrage
|
||||
|
||||
Soll das Card-System komplett auf HTML/CSS umgestellt werden oder ein Hybrid-Ansatz verfolgt werden?
|
||||
|
||||
## Option 1: Vollständige HTML/CSS Migration
|
||||
|
||||
### Wie es funktioniert
|
||||
|
||||
```html
|
||||
<!-- Nutzer schreibt direkt HTML/CSS -->
|
||||
<div class="my-custom-card">
|
||||
<style>
|
||||
.my-custom-card {
|
||||
background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
|
||||
padding: 2rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
</style>
|
||||
<h1>{{username}}</h1>
|
||||
<p>{{bio}}</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
### ✅ Vorteile
|
||||
|
||||
- **Maximale Freiheit** für kreative Nutzer
|
||||
- **Einfachere Datenbank** (nur HTML/CSS Text speichern)
|
||||
- **Universell portabel** (funktioniert überall)
|
||||
- **Keine Framework-Abhängigkeit**
|
||||
|
||||
### ❌ Nachteile
|
||||
|
||||
- **Sicherheitsrisiko** (XSS, CSS-Injection)
|
||||
- **Keine Garantie für Responsive Design**
|
||||
- **Schwer für Anfänger**
|
||||
- **Performance nicht kontrollierbar**
|
||||
- **Wartbarkeit leidet**
|
||||
|
||||
### Datenbank-Schema
|
||||
|
||||
```sql
|
||||
CREATE TABLE cards (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT,
|
||||
html TEXT, -- Einfacher HTML String
|
||||
css TEXT, -- Einfacher CSS String
|
||||
variables JSON -- Template-Variablen
|
||||
);
|
||||
```
|
||||
|
||||
## Option 2: Beibehaltung Modulares System
|
||||
|
||||
### Wie es funktioniert
|
||||
|
||||
```javascript
|
||||
// Nutzer konfiguriert Module
|
||||
{
|
||||
modules: [
|
||||
{ type: 'header', props: { title: 'Titel' } },
|
||||
{ type: 'content', props: { text: 'Inhalt' } }
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
### ✅ Vorteile
|
||||
|
||||
- **Konsistente Qualität**
|
||||
- **Automatisch responsive**
|
||||
- **Sicher** (kein Code-Injection)
|
||||
- **Einfach für Anfänger**
|
||||
- **Optimale Performance**
|
||||
|
||||
### ❌ Nachteile
|
||||
|
||||
- **Begrenzte Kreativität**
|
||||
- **Komplexere Datenbank**
|
||||
- **Framework-abhängig**
|
||||
- **Mehr Entwicklungsaufwand**
|
||||
|
||||
## Option 3: Hybrid-Ansatz (EMPFEHLUNG) 🏆
|
||||
|
||||
### Die beste Lösung aus beiden Welten
|
||||
|
||||
```typescript
|
||||
interface UnifiedCard {
|
||||
renderMode: 'beginner' | 'advanced' | 'expert';
|
||||
|
||||
// Beginner: Visual Builder
|
||||
modules?: ModuleConfig[];
|
||||
|
||||
// Advanced: Template mit Variablen
|
||||
template?: string;
|
||||
|
||||
// Expert: Raw HTML/CSS
|
||||
customHTML?: string;
|
||||
customCSS?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Drei Stufen für verschiedene Nutzergruppen
|
||||
|
||||
#### 🟢 Stufe 1: Visual Builder (80% der Nutzer)
|
||||
|
||||
```javascript
|
||||
// Einfach, sicher, schnell
|
||||
{
|
||||
renderMode: 'beginner',
|
||||
modules: [
|
||||
{ type: 'header', props: { title: 'Meine Karte' } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### 🟡 Stufe 2: Template Editor (15% der Nutzer)
|
||||
|
||||
```handlebars
|
||||
// Flexibler, aber noch kontrolliert { renderMode: 'advanced', template: `
|
||||
<div class='card'>
|
||||
<h2>{{title}}</h2>
|
||||
<p>{{description}}</p>
|
||||
</div>
|
||||
` }
|
||||
```
|
||||
|
||||
#### 🔴 Stufe 3: Code Editor (5% der Nutzer)
|
||||
|
||||
```html
|
||||
// Volle Kontrolle für Power-User { renderMode: 'expert', customHTML: '
|
||||
<div>Komplett custom...</div>
|
||||
', customCSS: '.custom { ... }' }
|
||||
```
|
||||
|
||||
## 📊 Entscheidungsmatrix
|
||||
|
||||
| Kriterium | Nur Module | Nur HTML | Hybrid |
|
||||
| ----------------------------------- | ---------- | ---------- | ---------- |
|
||||
| **Nutzerfreundlichkeit (Anfänger)** | ⭐⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| **Flexibilität (Experten)** | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
| **Sicherheit** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ |
|
||||
| **Performance** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
|
||||
| **Wartbarkeit** | ⭐⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐ |
|
||||
| **Entwicklungsaufwand** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
|
||||
| **Datenbank-Komplexität** | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
|
||||
| **Zukunftssicherheit** | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
||||
|
||||
## 🚀 Implementierungs-Roadmap für Hybrid-Ansatz
|
||||
|
||||
### Phase 1: Vorbereitung (Woche 1)
|
||||
|
||||
```typescript
|
||||
// 1. Unified Card Interface definieren
|
||||
interface UnifiedCard {
|
||||
id: string;
|
||||
renderMode: RenderMode;
|
||||
config: ModularConfig | TemplateConfig | CustomConfig;
|
||||
constraints: CardConstraints;
|
||||
}
|
||||
|
||||
// 2. Renderer abstrahieren
|
||||
class CardRenderer {
|
||||
render(card: UnifiedCard): HTMLElement {
|
||||
switch (card.renderMode) {
|
||||
case 'beginner':
|
||||
return this.renderModular(card);
|
||||
case 'advanced':
|
||||
return this.renderTemplate(card);
|
||||
case 'expert':
|
||||
return this.renderCustom(card);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 2: HTML/CSS Renderer (Woche 2-3)
|
||||
|
||||
```typescript
|
||||
// Sicherer HTML Renderer mit Sandboxing
|
||||
class SafeHTMLRenderer {
|
||||
private sanitizer = new DOMPurify();
|
||||
|
||||
render(html: string, css: string): SafeHTML {
|
||||
const safeHTML = this.sanitizer.sanitize(html);
|
||||
const safeCSS = this.sanitizeCSS(css);
|
||||
|
||||
return this.wrapInIframe(safeHTML, safeCSS);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Migrations-Tools (Woche 4)
|
||||
|
||||
```typescript
|
||||
// Konverter zwischen Formaten
|
||||
class CardConverter {
|
||||
// Module → HTML
|
||||
modulesToHTML(modules: ModuleConfig[]): string {
|
||||
return modules.map((m) => this.moduleToHTML(m)).join('');
|
||||
}
|
||||
|
||||
// HTML → Module (Best Effort)
|
||||
htmlToModules(html: string): ModuleConfig[] {
|
||||
// AI-unterstützte Konvertierung
|
||||
return this.parseHTMLStructure(html);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: UI Integration (Woche 5-6)
|
||||
|
||||
```svelte
|
||||
<!-- Unified Card Builder -->
|
||||
<script>
|
||||
let mode = 'beginner';
|
||||
let card = createEmptyCard();
|
||||
</script>
|
||||
|
||||
<div class="builder">
|
||||
<!-- Mode Selector -->
|
||||
<ModeSelector bind:mode />
|
||||
|
||||
<!-- Conditional Editors -->
|
||||
{#if mode === 'beginner'}
|
||||
<VisualBuilder bind:card />
|
||||
{:else if mode === 'advanced'}
|
||||
<TemplateEditor bind:card />
|
||||
{:else}
|
||||
<CodeEditor bind:card />
|
||||
{/if}
|
||||
|
||||
<!-- Universal Preview -->
|
||||
<CardPreview {card} />
|
||||
</div>
|
||||
```
|
||||
|
||||
## 💰 Kosten-Nutzen-Analyse
|
||||
|
||||
### Entwicklungskosten
|
||||
|
||||
- **Nur Module**: 2 Wochen (bereits fertig)
|
||||
- **Nur HTML**: 4 Wochen (Neuimplementierung)
|
||||
- **Hybrid**: 6 Wochen (beide Systeme)
|
||||
|
||||
### Langfristiger Nutzen
|
||||
|
||||
- **Nur Module**: Begrenzte Zielgruppe
|
||||
- **Nur HTML**: Sicherheitsrisiken, Support-Aufwand
|
||||
- **Hybrid**: Maximale Reichweite, zukunftssicher
|
||||
|
||||
## 🎯 Finale Empfehlung
|
||||
|
||||
### Implementiere den Hybrid-Ansatz mit folgender Priorität:
|
||||
|
||||
1. **Behalte das modulare System** als Hauptfeature
|
||||
2. **Füge Template-Editor hinzu** für fortgeschrittene Nutzer
|
||||
3. **HTML/CSS als "Beta-Feature"** für Power-User
|
||||
4. **Schrittweise Migration** basierend auf Nutzer-Feedback
|
||||
|
||||
### Warum Hybrid?
|
||||
|
||||
- ✅ **Keine Breaking Changes** - Existierende Cards funktionieren weiter
|
||||
- ✅ **Progressive Enhancement** - Nutzer können wachsen
|
||||
- ✅ **Marktdifferenzierung** - Einzigartige Features für alle Nutzergruppen
|
||||
- ✅ **Risikominimierung** - Sicherheitsprobleme nur bei Opt-in
|
||||
- ✅ **Lernkurve** - Anfänger werden nicht überfordert
|
||||
|
||||
### Datenbank bleibt flexibel:
|
||||
|
||||
```sql
|
||||
-- Einheitliche Struktur für alle Modi
|
||||
CREATE TABLE unified_cards (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT,
|
||||
render_mode TEXT CHECK(render_mode IN ('beginner', 'advanced', 'expert')),
|
||||
|
||||
-- Für modulare Cards
|
||||
modules JSON,
|
||||
|
||||
-- Für Template Cards
|
||||
template TEXT,
|
||||
template_vars JSON,
|
||||
|
||||
-- Für Custom HTML
|
||||
custom_html TEXT,
|
||||
custom_css TEXT,
|
||||
|
||||
-- Gemeinsam
|
||||
constraints JSON,
|
||||
theme_id TEXT,
|
||||
created_at TIMESTAMP,
|
||||
updated_at TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
## 🏁 Nächste Schritte
|
||||
|
||||
1. **User Research**: Umfrage welche Features gewünscht sind
|
||||
2. **Proof of Concept**: HTML-Renderer mit Sandbox
|
||||
3. **Security Audit**: Externe Überprüfung
|
||||
4. **Incremental Rollout**: Erst für Premium-User
|
||||
5. **Monitoring**: Welcher Modus wird wie genutzt?
|
||||
408
apps/uload/docs/cards/modules.md
Normal file
408
apps/uload/docs/cards/modules.md
Normal file
|
|
@ -0,0 +1,408 @@
|
|||
# Card System Module
|
||||
|
||||
Module sind die Bausteine des Card Systems. Jedes Modul hat eine spezifische Funktion und kann in verschiedenen Kombinationen verwendet werden.
|
||||
|
||||
## Verfügbare Module
|
||||
|
||||
### HeaderModule
|
||||
|
||||
Zeigt Titel, Untertitel, Avatar und optionale Actions an.
|
||||
|
||||
#### Props
|
||||
|
||||
```typescript
|
||||
interface HeaderModuleProps {
|
||||
title?: string; // Haupttitel
|
||||
subtitle?: string; // Untertitel
|
||||
avatar?: string; // Avatar-URL
|
||||
avatarAlt?: string; // Alt-Text für Avatar
|
||||
badge?: string; // Badge-Text
|
||||
icon?: string; // Icon (Emoji oder Symbol)
|
||||
actions?: Array<{
|
||||
// Action-Buttons
|
||||
label: string;
|
||||
action: () => void;
|
||||
icon?: string;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
#### Beispiel
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'John Doe',
|
||||
subtitle: 'Software Developer',
|
||||
avatar: '/avatars/john.jpg',
|
||||
badge: 'PRO',
|
||||
actions: [
|
||||
{ label: 'Edit', action: () => editProfile(), icon: '✏️' }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ContentModule
|
||||
|
||||
Zeigt Text, HTML oder Listen an.
|
||||
|
||||
#### Props
|
||||
|
||||
```typescript
|
||||
interface ContentModuleProps {
|
||||
text?: string; // Plaintext
|
||||
html?: string; // HTML-Inhalt
|
||||
items?: Array<{
|
||||
// Liste von Items
|
||||
label: string;
|
||||
value: string | number;
|
||||
icon?: string;
|
||||
}>;
|
||||
truncate?: boolean; // Text abschneiden
|
||||
maxLines?: number; // Max. Zeilen
|
||||
}
|
||||
```
|
||||
|
||||
#### Beispiel
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'content',
|
||||
props: {
|
||||
text: 'Dies ist eine Beschreibung...',
|
||||
truncate: true,
|
||||
maxLines: 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### LinksModule
|
||||
|
||||
Zeigt eine Liste von Links in verschiedenen Stilen an.
|
||||
|
||||
#### Props
|
||||
|
||||
```typescript
|
||||
interface LinksModuleProps {
|
||||
links: Array<{
|
||||
label: string;
|
||||
href: string;
|
||||
icon?: string;
|
||||
description?: string;
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
style?: 'button' | 'list' | 'card'; // Darstellungsstil
|
||||
columns?: 1 | 2; // Anzahl Spalten
|
||||
showDescription?: boolean; // Beschreibung anzeigen
|
||||
showIcon?: boolean; // Icons anzeigen
|
||||
target?: '_blank' | '_self'; // Link-Target
|
||||
buttonVariant?: 'primary' | 'secondary' | 'ghost' | 'outline';
|
||||
gap?: 'sm' | 'md' | 'lg'; // Abstand zwischen Links
|
||||
}
|
||||
```
|
||||
|
||||
#### Beispiel
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'links',
|
||||
props: {
|
||||
links: [
|
||||
{ label: 'Website', href: 'https://example.com', icon: '🌐' },
|
||||
{ label: 'GitHub', href: 'https://github.com/user', icon: '💻' },
|
||||
{ label: 'LinkedIn', href: 'https://linkedin.com/in/user', icon: '💼' }
|
||||
],
|
||||
style: 'button',
|
||||
columns: 1,
|
||||
showIcon: true,
|
||||
buttonVariant: 'secondary'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### MediaModule
|
||||
|
||||
Zeigt Bilder, Videos, QR-Codes oder Icons an.
|
||||
|
||||
#### Props
|
||||
|
||||
```typescript
|
||||
interface MediaModuleProps {
|
||||
type: 'image' | 'video' | 'qr' | 'icon'; // Medientyp
|
||||
src?: string; // Source-URL
|
||||
alt?: string; // Alt-Text
|
||||
aspectRatio?: string; // Seitenverhältnis (z.B. '16/9')
|
||||
objectFit?: 'cover' | 'contain' | 'fill'; // Objekt-Anpassung
|
||||
qrData?: string; // Daten für QR-Code
|
||||
qrSize?: number; // QR-Code Größe
|
||||
qrColor?: string; // QR-Code Farbe
|
||||
icon?: string; // Icon (für type='icon')
|
||||
iconSize?: string; // Icon-Größe
|
||||
}
|
||||
```
|
||||
|
||||
#### Beispiel
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'media',
|
||||
props: {
|
||||
type: 'image',
|
||||
src: '/images/hero.jpg',
|
||||
alt: 'Hero Image',
|
||||
aspectRatio: '16/9',
|
||||
objectFit: 'cover'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### StatsModule
|
||||
|
||||
Zeigt Statistiken in verschiedenen Layouts an.
|
||||
|
||||
#### Props
|
||||
|
||||
```typescript
|
||||
interface StatsModuleProps {
|
||||
stats: Array<{
|
||||
label: string;
|
||||
value: string | number;
|
||||
change?: number; // Prozentuale Änderung
|
||||
icon?: string;
|
||||
color?: string; // Custom Farbe
|
||||
}>;
|
||||
layout?: 'grid' | 'list' | 'compact'; // Layout-Stil
|
||||
}
|
||||
```
|
||||
|
||||
#### Beispiel
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'stats',
|
||||
props: {
|
||||
stats: [
|
||||
{ label: 'Besucher', value: '1.2k', change: 12, icon: '👥' },
|
||||
{ label: 'Umsatz', value: '€5.4k', change: -3, icon: '💰' },
|
||||
{ label: 'Conversion', value: '24%', icon: '📈' }
|
||||
],
|
||||
layout: 'grid'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ActionsModule
|
||||
|
||||
Zeigt Action-Buttons oder Links an.
|
||||
|
||||
#### Props
|
||||
|
||||
```typescript
|
||||
interface ActionsModuleProps {
|
||||
actions: Array<{
|
||||
label: string;
|
||||
action?: () => void; // Callback-Funktion
|
||||
href?: string; // Alternativ: Link
|
||||
variant?: 'primary' | 'secondary' | 'ghost' | 'link';
|
||||
icon?: string;
|
||||
disabled?: boolean;
|
||||
}>;
|
||||
layout?: 'horizontal' | 'vertical' | 'grid';
|
||||
alignment?: 'left' | 'center' | 'right' | 'between';
|
||||
}
|
||||
```
|
||||
|
||||
#### Beispiel
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'actions',
|
||||
props: {
|
||||
actions: [
|
||||
{ label: 'Speichern', variant: 'primary', action: () => save() },
|
||||
{ label: 'Abbrechen', variant: 'ghost', action: () => cancel() }
|
||||
],
|
||||
layout: 'horizontal',
|
||||
alignment: 'right'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### FooterModule
|
||||
|
||||
Zeigt Footer-Informationen mit Links und Social Media an.
|
||||
|
||||
#### Props
|
||||
|
||||
```typescript
|
||||
interface FooterModuleProps {
|
||||
text?: string; // Footer-Text
|
||||
links?: Array<{
|
||||
// Footer-Links
|
||||
label: string;
|
||||
href: string;
|
||||
icon?: string;
|
||||
}>;
|
||||
copyright?: string; // Copyright-Text
|
||||
socialLinks?: Array<{
|
||||
// Social Media Links
|
||||
platform: string;
|
||||
url: string;
|
||||
icon?: string;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
#### Beispiel
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'footer',
|
||||
props: {
|
||||
text: 'Powered by uload',
|
||||
copyright: '© 2024 Company Name',
|
||||
socialLinks: [
|
||||
{ platform: 'Twitter', url: 'https://twitter.com/company', icon: '🐦' },
|
||||
{ platform: 'Facebook', url: 'https://facebook.com/company', icon: '📘' }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Module kombinieren
|
||||
|
||||
### Beispiel: Profil-Karte
|
||||
|
||||
```javascript
|
||||
const profileCard = {
|
||||
variant: 'default',
|
||||
modules: [
|
||||
{
|
||||
type: 'header',
|
||||
order: 0,
|
||||
props: {
|
||||
title: 'Jane Smith',
|
||||
subtitle: 'UX Designer',
|
||||
avatar: '/avatars/jane.jpg'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'content',
|
||||
order: 1,
|
||||
props: {
|
||||
text: 'Passionate about creating beautiful and functional user experiences.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'stats',
|
||||
order: 2,
|
||||
props: {
|
||||
stats: [
|
||||
{ label: 'Projects', value: 42 },
|
||||
{ label: 'Clients', value: 18 },
|
||||
{ label: 'Awards', value: 3 }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'links',
|
||||
order: 3,
|
||||
props: {
|
||||
links: [
|
||||
{ label: 'Portfolio', href: 'https://portfolio.com' },
|
||||
{ label: 'Contact', href: 'mailto:jane@example.com' }
|
||||
],
|
||||
style: 'button'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## Eigene Module erstellen
|
||||
|
||||
### 1. Komponente erstellen
|
||||
|
||||
```svelte
|
||||
<!-- src/lib/components/cards/modules/CustomModule.svelte -->
|
||||
<script lang="ts">
|
||||
import type { CustomModuleProps } from '../types';
|
||||
|
||||
let { customProp = 'default' }: CustomModuleProps = $props();
|
||||
</script>
|
||||
|
||||
<div class="custom-module">
|
||||
<!-- Dein Module-Inhalt -->
|
||||
</div>
|
||||
```
|
||||
|
||||
### 2. Types definieren
|
||||
|
||||
```typescript
|
||||
// In types.ts hinzufügen
|
||||
export interface CustomModuleProps {
|
||||
customProp?: string;
|
||||
// Weitere Props...
|
||||
}
|
||||
```
|
||||
|
||||
### 3. In BaseCard registrieren
|
||||
|
||||
```svelte
|
||||
// In BaseCard.svelte
|
||||
import CustomModule from './modules/CustomModule.svelte';
|
||||
|
||||
const moduleComponents = {
|
||||
// ... andere Module
|
||||
custom: CustomModule
|
||||
};
|
||||
```
|
||||
|
||||
## Module-Konfiguration
|
||||
|
||||
### Sichtbarkeit
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'content',
|
||||
visibility: 'desktop', // Nur auf Desktop anzeigen
|
||||
props: { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
### Grid-Layout
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'media',
|
||||
grid: {
|
||||
col: 1, // Spalte 1
|
||||
row: 1, // Zeile 1
|
||||
colSpan: 2, // Über 2 Spalten
|
||||
rowSpan: 1 // Über 1 Zeile
|
||||
},
|
||||
props: { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Styling
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'header',
|
||||
className: 'custom-header-class',
|
||||
props: { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Halte Module klein und fokussiert** - Ein Modul sollte nur eine Aufgabe erfüllen
|
||||
2. **Verwende TypeScript** für Type-Safety
|
||||
3. **Nutze Props sparsam** - Zu viele Optionen machen Module komplex
|
||||
4. **Dokumentiere neue Module** ausführlich
|
||||
5. **Teste Module isoliert** bevor du sie in Karten verwendest
|
||||
6. **Beachte Barrierefreiheit** - ARIA-Labels, Keyboard-Navigation
|
||||
7. **Optimiere Performance** - Lazy Loading für schwere Module
|
||||
551
apps/uload/docs/cards/server-side-html-cards.md
Normal file
551
apps/uload/docs/cards/server-side-html-cards.md
Normal file
|
|
@ -0,0 +1,551 @@
|
|||
# Server-Side HTML Cards - Konzept & Analyse
|
||||
|
||||
## Executive Summary
|
||||
|
||||
Die Idee, Cards als reinen HTML/CSS-Code serverseitig zu rendern, bietet maximale Flexibilität für Nutzer und könnte das bestehende modulare System ergänzen oder ersetzen. Dieser Ansatz würde es ermöglichen, dass Nutzer komplett eigene Designs erstellen können, während gleichzeitig die Aspect Ratio und Container-Constraints eingehalten werden.
|
||||
|
||||
## 🎯 Konzept-Übersicht
|
||||
|
||||
### Grundidee
|
||||
|
||||
```html
|
||||
<!-- Nutzer definiert HTML/CSS -->
|
||||
<div class="custom-card">
|
||||
<style>
|
||||
.custom-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
padding: 2rem;
|
||||
color: white;
|
||||
}
|
||||
.custom-card h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
</style>
|
||||
<h2>{{username}}</h2>
|
||||
<p>{{bio}}</p>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Server rendert zu:
|
||||
|
||||
```html
|
||||
<div class="card-container" style="aspect-ratio: 16/9;">
|
||||
<iframe srcdoc="..." sandbox="allow-same-origin" style="width: 100%; height: 100%; border: none;">
|
||||
<!-- Nutzer HTML/CSS isoliert in iframe -->
|
||||
</iframe>
|
||||
</div>
|
||||
```
|
||||
|
||||
## 📊 Vergleich der Ansätze
|
||||
|
||||
| Aspekt | Modulares System (aktuell) | HTML/CSS System | Hybrid-Ansatz |
|
||||
| ---------------------- | -------------------------- | --------------- | ------------- |
|
||||
| **Flexibilität** | Mittel | Sehr hoch | Hoch |
|
||||
| **Sicherheit** | Hoch | Niedrig-Mittel | Mittel-Hoch |
|
||||
| **Performance** | Sehr gut | Gut | Gut |
|
||||
| **Nutzer-Komplexität** | Niedrig | Hoch | Variabel |
|
||||
| **Wartbarkeit** | Sehr gut | Schlecht | Mittel |
|
||||
| **Datenbank** | Komplex | Einfach | Mittel |
|
||||
|
||||
## ✅ Vorteile Server-Side HTML Cards
|
||||
|
||||
### 1. **Maximale Kreativität**
|
||||
|
||||
- Nutzer können JEDES Design umsetzen
|
||||
- Keine Einschränkungen durch vordefinierte Module
|
||||
- Custom Animationen und Effekte möglich
|
||||
- Einzigartige Layouts
|
||||
|
||||
### 2. **Einfachere Datenbank**
|
||||
|
||||
```sql
|
||||
-- Statt komplexer JSON-Strukturen
|
||||
CREATE TABLE cards (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT,
|
||||
html_content TEXT,
|
||||
css_content TEXT,
|
||||
variables JSON, -- Für Template-Variablen
|
||||
created_at TIMESTAMP
|
||||
);
|
||||
```
|
||||
|
||||
### 3. **Portabilität**
|
||||
|
||||
- HTML/CSS ist universell
|
||||
- Kann in jede Plattform exportiert werden
|
||||
- Keine Framework-Abhängigkeiten
|
||||
|
||||
### 4. **Learning Opportunity**
|
||||
|
||||
- Nutzer lernen HTML/CSS
|
||||
- Community kann Code teilen
|
||||
- Inspiration durch andere Designs
|
||||
|
||||
## ❌ Nachteile & Risiken
|
||||
|
||||
### 1. **Sicherheitsrisiken**
|
||||
|
||||
```javascript
|
||||
// XSS-Gefahr
|
||||
<script>alert('XSS')</script>
|
||||
|
||||
// CSS-Injection
|
||||
<style>
|
||||
body { display: none !important; }
|
||||
</style>
|
||||
|
||||
// Clickjacking
|
||||
<a href="javascript:void(0)" onclick="stealData()">
|
||||
```
|
||||
|
||||
### 2. **Performance-Probleme**
|
||||
|
||||
- Unkontrollierte CSS-Animationen
|
||||
- Große Bilder/Assets
|
||||
- Ineffiziente Selektoren
|
||||
- Memory Leaks durch JavaScript
|
||||
|
||||
### 3. **Responsive Design**
|
||||
|
||||
- Nutzer müssen selbst Media Queries schreiben
|
||||
- Inkonsistente Mobile-Ansichten
|
||||
- Aspect Ratio schwer zu garantieren
|
||||
|
||||
### 4. **Wartbarkeit**
|
||||
|
||||
- Kein Type-Checking
|
||||
- Schwer zu debuggen
|
||||
- Updates kompliziert
|
||||
- Keine einheitliche Code-Qualität
|
||||
|
||||
## 🔒 Sicherheitskonzept
|
||||
|
||||
### 1. **Sandboxing mit iframes**
|
||||
|
||||
```html
|
||||
<iframe
|
||||
srcdoc="{{sanitized_html}}"
|
||||
sandbox="allow-same-origin"
|
||||
csp="default-src 'self'; script-src 'none';"
|
||||
loading="lazy"
|
||||
/>
|
||||
```
|
||||
|
||||
### 2. **HTML/CSS Sanitization**
|
||||
|
||||
```javascript
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
import { sanitizeCSS } from 'css-tree';
|
||||
|
||||
function sanitizeCardContent(html, css) {
|
||||
// HTML säubern
|
||||
const cleanHTML = DOMPurify.sanitize(html, {
|
||||
ALLOWED_TAGS: ['div', 'span', 'p', 'h1', 'h2', 'h3', 'img', 'a'],
|
||||
ALLOWED_ATTR: ['class', 'id', 'href', 'src', 'alt', 'style'],
|
||||
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form'],
|
||||
FORBID_ATTR: ['onclick', 'onload', 'onerror']
|
||||
});
|
||||
|
||||
// CSS säubern
|
||||
const cleanCSS = sanitizeCSS(css, {
|
||||
removeImports: true,
|
||||
removeJavaScript: true,
|
||||
limitSelectors: true,
|
||||
maxNesting: 3
|
||||
});
|
||||
|
||||
return { html: cleanHTML, css: cleanCSS };
|
||||
}
|
||||
```
|
||||
|
||||
### 3. **Content Security Policy**
|
||||
|
||||
```javascript
|
||||
// Server-Header
|
||||
response.headers.set(
|
||||
'Content-Security-Policy',
|
||||
"default-src 'self'; " +
|
||||
"style-src 'self' 'unsafe-inline'; " +
|
||||
"script-src 'none'; " +
|
||||
"img-src 'self' data: https:;"
|
||||
);
|
||||
```
|
||||
|
||||
## 🎨 Hybrid-Ansatz (EMPFEHLUNG)
|
||||
|
||||
### Konzept: "Progressive Enhancement"
|
||||
|
||||
```typescript
|
||||
interface CardDefinition {
|
||||
type: 'modular' | 'template' | 'custom-html';
|
||||
|
||||
// Für modulare Cards (wie bisher)
|
||||
modules?: ModuleConfig[];
|
||||
|
||||
// Für Template-basierte Cards
|
||||
template?: string;
|
||||
templateVars?: Record<string, any>;
|
||||
|
||||
// Für Custom HTML
|
||||
customHTML?: string;
|
||||
customCSS?: string;
|
||||
|
||||
// Gemeinsame Properties
|
||||
constraints?: {
|
||||
aspectRatio?: string;
|
||||
maxWidth?: string;
|
||||
minHeight?: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Drei Ebenen der Anpassung:
|
||||
|
||||
#### Level 1: Module Builder (Anfänger)
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'modular',
|
||||
modules: [
|
||||
{ type: 'header', props: { title: 'Meine Karte' } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Level 2: Template Editor (Fortgeschritten)
|
||||
|
||||
```handlebars
|
||||
{ type: 'template', template: `
|
||||
<div class='card-template'>
|
||||
<h2>{{title}}</h2>
|
||||
<p>{{description}}</p>
|
||||
</div>
|
||||
`, templateVars: { title: 'Titel', description: 'Text' } }
|
||||
```
|
||||
|
||||
#### Level 3: Custom HTML/CSS (Experten)
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'custom-html',
|
||||
customHTML: '<div class="my-card">...</div>',
|
||||
customCSS: '.my-card { ... }'
|
||||
}
|
||||
```
|
||||
|
||||
## 💻 Implementierungsplan
|
||||
|
||||
### Phase 1: Basis-Infrastruktur (Woche 1-2)
|
||||
|
||||
#### 1.1 Sanitization Layer
|
||||
|
||||
```typescript
|
||||
// src/lib/services/cardSanitizer.ts
|
||||
export class CardSanitizer {
|
||||
sanitizeHTML(html: string): string;
|
||||
sanitizeCSS(css: string): string;
|
||||
validateConstraints(html: string, constraints: Constraints): boolean;
|
||||
extractVariables(html: string): string[];
|
||||
}
|
||||
```
|
||||
|
||||
#### 1.2 Rendering Engine
|
||||
|
||||
```svelte
|
||||
<!-- src/lib/components/cards/CustomHTMLCard.svelte -->
|
||||
<script lang="ts">
|
||||
import { sanitizeCard } from '$lib/services/cardSanitizer';
|
||||
|
||||
export let html: string;
|
||||
export let css: string;
|
||||
export let variables: Record<string, any> = {};
|
||||
export let aspectRatio: string = '16/9';
|
||||
|
||||
$: sanitized = sanitizeCard(html, css);
|
||||
$: rendered = replaceVariables(sanitized.html, variables);
|
||||
</script>
|
||||
|
||||
<div class="custom-card-container" style="aspect-ratio: {aspectRatio}">
|
||||
<iframe
|
||||
srcdoc={`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>${sanitized.css}</style>
|
||||
<style>
|
||||
body { margin: 0; padding: 0; height: 100vh; display: flex; }
|
||||
.card-content { flex: 1; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card-content">${rendered}</div>
|
||||
</body>
|
||||
</html>
|
||||
`}
|
||||
sandbox="allow-same-origin"
|
||||
loading="lazy"
|
||||
title="Custom Card"
|
||||
/>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Phase 2: Editor & Preview (Woche 3-4)
|
||||
|
||||
#### 2.1 HTML/CSS Editor
|
||||
|
||||
```svelte
|
||||
<!-- src/lib/components/builder/HTMLCardEditor.svelte -->
|
||||
<script>
|
||||
import CodeMirror from 'codemirror';
|
||||
import 'codemirror/mode/htmlmixed/htmlmixed';
|
||||
import 'codemirror/mode/css/css';
|
||||
|
||||
export let html = '';
|
||||
export let css = '';
|
||||
export let onUpdate;
|
||||
|
||||
// Live Preview
|
||||
$: preview = generatePreview(html, css);
|
||||
</script>
|
||||
|
||||
<div class="editor-container">
|
||||
<div class="editors">
|
||||
<div class="html-editor">
|
||||
<CodeMirror bind:value={html} mode="htmlmixed" />
|
||||
</div>
|
||||
<div class="css-editor">
|
||||
<CodeMirror bind:value={css} mode="css" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview">
|
||||
<CustomHTMLCard {html} {css} />
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
#### 2.2 Template Variables
|
||||
|
||||
```typescript
|
||||
interface TemplateVariable {
|
||||
name: string;
|
||||
type: 'text' | 'number' | 'image' | 'link' | 'list';
|
||||
default?: any;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
// Variable extraction
|
||||
function extractVariables(html: string): TemplateVariable[] {
|
||||
const regex = /\{\{(\w+)(?::(\w+))?\}\}/g;
|
||||
const variables: TemplateVariable[] = [];
|
||||
|
||||
let match;
|
||||
while ((match = regex.exec(html)) !== null) {
|
||||
variables.push({
|
||||
name: match[1],
|
||||
type: match[2] || 'text',
|
||||
required: true
|
||||
});
|
||||
}
|
||||
|
||||
return variables;
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 3: Datenbank-Migration (Woche 5)
|
||||
|
||||
#### Neue Datenbank-Struktur
|
||||
|
||||
```sql
|
||||
-- Erweiterte cards Tabelle
|
||||
ALTER TABLE cards ADD COLUMN render_type TEXT DEFAULT 'modular';
|
||||
ALTER TABLE cards ADD COLUMN html_content TEXT;
|
||||
ALTER TABLE cards ADD COLUMN css_content TEXT;
|
||||
ALTER TABLE cards ADD COLUMN template_variables JSON;
|
||||
ALTER TABLE cards ADD COLUMN constraints JSON;
|
||||
|
||||
-- Versionierung für Custom Cards
|
||||
CREATE TABLE card_versions (
|
||||
id TEXT PRIMARY KEY,
|
||||
card_id TEXT REFERENCES cards(id),
|
||||
version INTEGER,
|
||||
html_content TEXT,
|
||||
css_content TEXT,
|
||||
created_at TIMESTAMP,
|
||||
change_note TEXT
|
||||
);
|
||||
```
|
||||
|
||||
### Phase 4: Builder Integration (Woche 6)
|
||||
|
||||
```svelte
|
||||
<!-- Erweiterter Card Builder -->
|
||||
<script>
|
||||
let builderMode: 'visual' | 'template' | 'code' = 'visual';
|
||||
</script>
|
||||
|
||||
<div class="builder-modes">
|
||||
<button class:active={builderMode === 'visual'} on:click={() => (builderMode = 'visual')}>
|
||||
Visual Builder (Module)
|
||||
</button>
|
||||
<button class:active={builderMode === 'template'} on:click={() => (builderMode = 'template')}>
|
||||
Template Editor
|
||||
</button>
|
||||
<button class:active={builderMode === 'code'} on:click={() => (builderMode = 'code')}>
|
||||
HTML/CSS Code
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{#if builderMode === 'visual'}
|
||||
<CardBuilder />
|
||||
{:else if builderMode === 'template'}
|
||||
<TemplateEditor />
|
||||
{:else}
|
||||
<HTMLCardEditor />
|
||||
{/if}
|
||||
```
|
||||
|
||||
## 🏗️ Technische Architektur
|
||||
|
||||
### Rendering Pipeline
|
||||
|
||||
```
|
||||
User Input (HTML/CSS)
|
||||
↓
|
||||
Sanitization Layer
|
||||
↓
|
||||
Variable Replacement
|
||||
↓
|
||||
Constraint Validation
|
||||
↓
|
||||
iframe Sandboxing
|
||||
↓
|
||||
Final Render
|
||||
```
|
||||
|
||||
### Aspect Ratio Enforcement
|
||||
|
||||
```css
|
||||
/* Container styles */
|
||||
.card-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: var(--card-aspect-ratio, 16/9);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card-container iframe {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* Inside iframe */
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
}
|
||||
```
|
||||
|
||||
## 📈 Migration Strategy
|
||||
|
||||
### Schritt 1: Parallel-Betrieb
|
||||
|
||||
- Bestehendes System bleibt
|
||||
- HTML-Cards als "Advanced Mode"
|
||||
- Opt-in für Power-User
|
||||
|
||||
### Schritt 2: Feature Parity
|
||||
|
||||
- Card Builder unterstützt beide Modi
|
||||
- Import/Export zwischen Formaten
|
||||
- Template Converter
|
||||
|
||||
### Schritt 3: Unified System
|
||||
|
||||
```typescript
|
||||
// Universeller Card Renderer
|
||||
function renderCard(card: UnifiedCard): HTMLElement {
|
||||
switch (card.type) {
|
||||
case 'modular':
|
||||
return renderModularCard(card);
|
||||
case 'template':
|
||||
return renderTemplateCard(card);
|
||||
case 'custom-html':
|
||||
return renderCustomHTMLCard(card);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🎯 Empfehlung
|
||||
|
||||
### Hybrid-Ansatz implementieren mit:
|
||||
|
||||
1. **Beibehaltung des modularen Systems** für 80% der Nutzer
|
||||
2. **Template-System** für fortgeschrittene Anpassungen
|
||||
3. **HTML/CSS-Editor** für Power-User und Entwickler
|
||||
4. **Strikte Sandboxing** für Sicherheit
|
||||
5. **Progressive Enhancement** - Nutzer können zwischen Modi wechseln
|
||||
|
||||
### Vorteile dieses Ansatzes:
|
||||
|
||||
- ✅ Backward Compatibility
|
||||
- ✅ Verschiedene Skill-Level bedient
|
||||
- ✅ Sicherheit gewährleistet
|
||||
- ✅ Performance optimiert
|
||||
- ✅ Wartbarkeit erhalten
|
||||
|
||||
### Timeline:
|
||||
|
||||
- **Woche 1-2**: Proof of Concept
|
||||
- **Woche 3-4**: Editor & Tools
|
||||
- **Woche 5**: Datenbank-Migration
|
||||
- **Woche 6**: Integration
|
||||
- **Woche 7-8**: Testing & Rollout
|
||||
|
||||
## 🚀 Nächste Schritte
|
||||
|
||||
1. **Proof of Concept** mit einfachem HTML-Renderer
|
||||
2. **Sicherheitsaudit** der Sanitization
|
||||
3. **Performance-Tests** mit komplexen Cards
|
||||
4. **User Research** - Welcher Ansatz wird bevorzugt?
|
||||
5. **Schrittweise Migration** beginnen
|
||||
|
||||
## 💡 Zusätzliche Überlegungen
|
||||
|
||||
### AI-Unterstützung
|
||||
|
||||
```typescript
|
||||
// KI generiert HTML/CSS basierend auf Beschreibung
|
||||
async function generateCardFromPrompt(prompt: string): Promise<CardHTML> {
|
||||
const response = await ai.generate({
|
||||
prompt: `Create a card with: ${prompt}`,
|
||||
constraints: ['responsive', 'aspect-ratio: 16/9', 'no-javascript']
|
||||
});
|
||||
return sanitizeCard(response);
|
||||
}
|
||||
```
|
||||
|
||||
### Marketplace Evolution
|
||||
|
||||
- Verkauf von HTML/CSS Templates
|
||||
- Code-Snippets Library
|
||||
- Community Challenges
|
||||
- Template Converter Tools
|
||||
|
||||
### Monitoring & Analytics
|
||||
|
||||
```typescript
|
||||
// Track welcher Modus am meisten genutzt wird
|
||||
analytics.track('card_created', {
|
||||
type: 'modular' | 'template' | 'custom-html',
|
||||
complexity: calculateComplexity(card),
|
||||
render_time: measureRenderTime(card)
|
||||
});
|
||||
```
|
||||
458
apps/uload/docs/cards/templates.md
Normal file
458
apps/uload/docs/cards/templates.md
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
# Card Templates
|
||||
|
||||
## Übersicht
|
||||
|
||||
Templates sind vordefinierte Kartenkonfigurationen, die als Ausgangspunkt für eigene Karten dienen. Sie kombinieren Module, Layouts und Themes zu wiederverwendbaren Vorlagen.
|
||||
|
||||
## Template-Struktur
|
||||
|
||||
### CardTemplateConfig Interface
|
||||
|
||||
```typescript
|
||||
interface CardTemplateConfig extends CardConfig {
|
||||
name: string; // Template-Name
|
||||
slug: string; // URL-freundlicher Name
|
||||
description?: string; // Beschreibung
|
||||
category?: string; // Kategorie
|
||||
isPublic?: boolean; // Öffentlich verfügbar
|
||||
previewImage?: string; // Vorschaubild-URL
|
||||
}
|
||||
```
|
||||
|
||||
## Vordefinierte Templates
|
||||
|
||||
### Profile Card Template
|
||||
|
||||
```javascript
|
||||
const profileTemplate = {
|
||||
name: 'Standard Profile',
|
||||
slug: 'standard-profile',
|
||||
description: 'Vollständige Profilkarte mit Avatar, Bio und Social Links',
|
||||
category: 'profile',
|
||||
variant: 'default',
|
||||
modules: [
|
||||
{
|
||||
type: 'header',
|
||||
order: 0,
|
||||
props: {
|
||||
title: '{{username}}',
|
||||
subtitle: '{{bio}}',
|
||||
avatar: '{{avatar}}'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'stats',
|
||||
order: 1,
|
||||
props: {
|
||||
stats: [
|
||||
{ label: 'Links', value: '{{totalLinks}}', icon: '🔗' },
|
||||
{ label: 'Clicks', value: '{{totalClicks}}', icon: '👆' }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'links',
|
||||
order: 2,
|
||||
props: {
|
||||
links: '{{socialLinks}}',
|
||||
style: 'button',
|
||||
showIcon: true
|
||||
}
|
||||
}
|
||||
],
|
||||
layout: {
|
||||
padding: '1.5rem'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Dashboard Stats Template
|
||||
|
||||
```javascript
|
||||
const dashboardTemplate = {
|
||||
name: 'Dashboard Stats',
|
||||
slug: 'dashboard-stats',
|
||||
description: 'Übersichtskarte mit wichtigen Metriken',
|
||||
category: 'dashboard',
|
||||
variant: 'compact',
|
||||
modules: [
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: 'Übersicht',
|
||||
icon: '📊'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'stats',
|
||||
props: {
|
||||
stats: [
|
||||
{ label: 'Besucher', value: '{{visitors}}', change: '{{visitorChange}}' },
|
||||
{ label: 'Umsatz', value: '{{revenue}}', change: '{{revenueChange}}' },
|
||||
{ label: 'Conversion', value: '{{conversion}}', change: '{{conversionChange}}' }
|
||||
],
|
||||
layout: 'grid'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
### Link Collection Template
|
||||
|
||||
```javascript
|
||||
const linkCollectionTemplate = {
|
||||
name: 'Link Collection',
|
||||
slug: 'link-collection',
|
||||
description: 'Sammlung von Links mit Beschreibungen',
|
||||
category: 'links',
|
||||
variant: 'default',
|
||||
modules: [
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: '{{collectionName}}',
|
||||
subtitle: '{{collectionDescription}}'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'links',
|
||||
props: {
|
||||
links: '{{links}}',
|
||||
style: 'card',
|
||||
columns: 2,
|
||||
showDescription: true,
|
||||
showIcon: true
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
### Media Gallery Template
|
||||
|
||||
```javascript
|
||||
const mediaGalleryTemplate = {
|
||||
name: 'Media Gallery',
|
||||
slug: 'media-gallery',
|
||||
description: 'Bildergalerie mit Titel und Beschreibung',
|
||||
category: 'media',
|
||||
variant: 'minimal',
|
||||
modules: [
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: '{{galleryTitle}}'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'media',
|
||||
props: {
|
||||
type: 'image',
|
||||
src: '{{featuredImage}}',
|
||||
aspectRatio: '16/9'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'content',
|
||||
props: {
|
||||
text: '{{description}}'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
## Template Store
|
||||
|
||||
Der Template Store ist ein Marktplatz für Card Templates.
|
||||
|
||||
### Features
|
||||
|
||||
- **Browse & Filter**: Nach Kategorie, Tags, Beliebtheit
|
||||
- **Preview**: Live-Vorschau mit eigenen Daten
|
||||
- **Download**: Templates herunterladen und anpassen
|
||||
- **Rating**: Bewertung durch Community
|
||||
- **Sharing**: Eigene Templates teilen
|
||||
|
||||
### Template Store verwenden
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { cardTemplateService } from '$lib/services/cardTemplates';
|
||||
|
||||
// Öffentliche Templates laden
|
||||
const templates = await cardTemplateService.getPublicTemplates();
|
||||
|
||||
// Nach Kategorie filtern
|
||||
const profileTemplates = await cardTemplateService.getPublicTemplates('profile');
|
||||
|
||||
// Template verwenden
|
||||
async function useTemplate(template) {
|
||||
await cardTemplateService.incrementDownloads(template.id);
|
||||
// Template in Card Builder öffnen
|
||||
goto(`/card-builder?template=${template.id}`);
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## Templates verwalten
|
||||
|
||||
### Template erstellen
|
||||
|
||||
```javascript
|
||||
const newTemplate = await cardTemplateService.createTemplate({
|
||||
name: 'Mein Template',
|
||||
slug: 'mein-template',
|
||||
description: 'Beschreibung',
|
||||
category: 'custom',
|
||||
is_public: false,
|
||||
modules: [
|
||||
/* ... */
|
||||
],
|
||||
layout: {
|
||||
/* ... */
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Template aktualisieren
|
||||
|
||||
```javascript
|
||||
await cardTemplateService.updateTemplate(templateId, {
|
||||
name: 'Neuer Name',
|
||||
modules: updatedModules
|
||||
});
|
||||
```
|
||||
|
||||
### Template löschen
|
||||
|
||||
```javascript
|
||||
await cardTemplateService.deleteTemplate(templateId);
|
||||
```
|
||||
|
||||
## Template-Variablen
|
||||
|
||||
Templates können Platzhalter verwenden, die bei der Verwendung ersetzt werden:
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'header',
|
||||
props: {
|
||||
title: '{{username}}', // Wird durch tatsächlichen Username ersetzt
|
||||
subtitle: '{{bio}}' // Wird durch Bio ersetzt
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Verfügbare Variablen
|
||||
|
||||
- `{{username}}` - Benutzername
|
||||
- `{{email}}` - E-Mail-Adresse
|
||||
- `{{bio}}` - Biografie
|
||||
- `{{avatar}}` - Avatar-URL
|
||||
- `{{totalLinks}}` - Anzahl Links
|
||||
- `{{totalClicks}}` - Anzahl Klicks
|
||||
- `{{socialLinks}}` - Array von Social Links
|
||||
- Custom-Variablen je nach Kontext
|
||||
|
||||
## Template zu Card konvertieren
|
||||
|
||||
```javascript
|
||||
// Template laden
|
||||
const template = await cardTemplateService.getTemplate(templateId);
|
||||
|
||||
// Zu CardConfig konvertieren
|
||||
const cardConfig = cardTemplateService.templateToCardConfig(template);
|
||||
|
||||
// Mit eigenen Daten füllen
|
||||
const filledConfig = fillTemplateVariables(cardConfig, {
|
||||
username: 'John Doe',
|
||||
bio: 'Software Developer'
|
||||
// ...
|
||||
});
|
||||
|
||||
// Card rendern
|
||||
<BaseCard {...filledConfig} />;
|
||||
```
|
||||
|
||||
## Template-Kategorien
|
||||
|
||||
### Profile
|
||||
|
||||
- Benutzerprofile
|
||||
- Team-Mitglieder
|
||||
- Autoren-Karten
|
||||
|
||||
### Dashboard
|
||||
|
||||
- Statistik-Übersichten
|
||||
- KPI-Karten
|
||||
- Activity-Feeds
|
||||
|
||||
### Links
|
||||
|
||||
- Link-Sammlungen
|
||||
- Social Media Links
|
||||
- Ressourcen-Listen
|
||||
|
||||
### Media
|
||||
|
||||
- Bildergalerien
|
||||
- Video-Player
|
||||
- QR-Code-Karten
|
||||
|
||||
### Content
|
||||
|
||||
- Blog-Post-Karten
|
||||
- Produkt-Karten
|
||||
- Service-Karten
|
||||
|
||||
### Custom
|
||||
|
||||
- Benutzerdefinierte Templates
|
||||
- Spezial-Layouts
|
||||
|
||||
## Template-Service
|
||||
|
||||
### Methoden
|
||||
|
||||
```typescript
|
||||
class CardTemplateService {
|
||||
// Templates abrufen
|
||||
async getPublicTemplates(category?: string): Promise<DBCardTemplate[]>;
|
||||
async getTemplate(id: string): Promise<DBCardTemplate | null>;
|
||||
|
||||
// Templates verwalten
|
||||
async createTemplate(template: Partial<DBCardTemplate>): Promise<DBCardTemplate | null>;
|
||||
async updateTemplate(
|
||||
id: string,
|
||||
updates: Partial<DBCardTemplate>
|
||||
): Promise<DBCardTemplate | null>;
|
||||
async deleteTemplate(id: string): Promise<boolean>;
|
||||
|
||||
// Konvertierung
|
||||
templateToCardConfig(template: DBCardTemplate): CardConfig;
|
||||
|
||||
// Statistiken
|
||||
async incrementDownloads(templateId: string): Promise<void>;
|
||||
async rateTemplate(templateId: string, rating: number): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
## Eigene Templates erstellen
|
||||
|
||||
### 1. Template definieren
|
||||
|
||||
```javascript
|
||||
const myTemplate = {
|
||||
name: 'Mein Custom Template',
|
||||
slug: 'mein-custom-template',
|
||||
description: 'Ein spezielles Template für meinen Use Case',
|
||||
category: 'custom',
|
||||
modules: [
|
||||
// Module hier definieren
|
||||
],
|
||||
layout: {
|
||||
columns: 2,
|
||||
gap: '1rem',
|
||||
padding: '1.5rem'
|
||||
},
|
||||
theme: {
|
||||
// Optional: Custom Theme
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Template speichern
|
||||
|
||||
```javascript
|
||||
// In Datenbank
|
||||
const saved = await cardTemplateService.createTemplate(myTemplate);
|
||||
|
||||
// Oder lokal verwenden
|
||||
localStorage.setItem('myTemplate', JSON.stringify(myTemplate));
|
||||
```
|
||||
|
||||
### 3. Template teilen
|
||||
|
||||
```javascript
|
||||
// Öffentlich machen
|
||||
await cardTemplateService.updateTemplate(templateId, {
|
||||
is_public: true
|
||||
});
|
||||
|
||||
// Export als JSON
|
||||
const json = JSON.stringify(template, null, 2);
|
||||
downloadAsFile('template.json', json);
|
||||
```
|
||||
|
||||
## Template-Migration
|
||||
|
||||
### Von statischen Komponenten
|
||||
|
||||
```svelte
|
||||
<!-- Alt: Statische Komponente -->
|
||||
<div class="profile-card">
|
||||
<h2>{user.name}</h2>
|
||||
<p>{user.bio}</p>
|
||||
</div>
|
||||
|
||||
<!-- Neu: Template-basiert -->
|
||||
<BaseCard template="profile-basic" data={user} />
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Modularität**: Erstelle kleine, wiederverwendbare Templates
|
||||
2. **Flexibilität**: Verwende Variablen für dynamische Inhalte
|
||||
3. **Kategorisierung**: Ordne Templates sinnvollen Kategorien zu
|
||||
4. **Dokumentation**: Beschreibe Templates ausführlich
|
||||
5. **Vorschau**: Stelle Vorschaubilder bereit
|
||||
6. **Versionierung**: Versioniere Templates bei Änderungen
|
||||
7. **Testing**: Teste Templates mit verschiedenen Datensätzen
|
||||
|
||||
## Template-Beispiele
|
||||
|
||||
### Minimal Profile
|
||||
|
||||
```javascript
|
||||
{
|
||||
name: 'Minimal Profile',
|
||||
modules: [{
|
||||
type: 'header',
|
||||
props: { title: '{{name}}' }
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### Social Media Hub
|
||||
|
||||
```javascript
|
||||
{
|
||||
name: 'Social Media Hub',
|
||||
modules: [{
|
||||
type: 'links',
|
||||
props: {
|
||||
links: '{{socialLinks}}',
|
||||
style: 'button',
|
||||
columns: 2
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
### Stats Dashboard
|
||||
|
||||
```javascript
|
||||
{
|
||||
name: 'Stats Dashboard',
|
||||
modules: [{
|
||||
type: 'stats',
|
||||
props: {
|
||||
stats: '{{metrics}}',
|
||||
layout: 'grid'
|
||||
}
|
||||
}]
|
||||
}
|
||||
```
|
||||
420
apps/uload/docs/cards/themes.md
Normal file
420
apps/uload/docs/cards/themes.md
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
# Card System Themes
|
||||
|
||||
## Übersicht
|
||||
|
||||
Themes definieren das visuelle Erscheinungsbild von Karten. Sie umfassen Farben, Typografie, Abstände, Schatten und Animationen.
|
||||
|
||||
## Theme-Struktur
|
||||
|
||||
### ThemeConfig Interface
|
||||
|
||||
```typescript
|
||||
interface ThemeConfig {
|
||||
id?: string;
|
||||
name?: string;
|
||||
colors?: {
|
||||
primary?: string; // Primärfarbe
|
||||
secondary?: string; // Sekundärfarbe
|
||||
accent?: string; // Akzentfarbe
|
||||
background?: string; // Hintergrundfarbe
|
||||
surface?: string; // Oberflächenfarbe
|
||||
text?: string; // Textfarbe
|
||||
textMuted?: string; // Gedämpfte Textfarbe
|
||||
border?: string; // Rahmenfarbe
|
||||
hover?: string; // Hover-Farbe
|
||||
// Weitere custom Farben möglich
|
||||
};
|
||||
typography?: {
|
||||
fontFamily?: string;
|
||||
fontSize?: {
|
||||
xs?: string;
|
||||
sm?: string;
|
||||
md?: string;
|
||||
lg?: string;
|
||||
xl?: string;
|
||||
};
|
||||
fontWeight?: {
|
||||
light?: number;
|
||||
normal?: number;
|
||||
medium?: number;
|
||||
semibold?: number;
|
||||
bold?: number;
|
||||
};
|
||||
lineHeight?: {
|
||||
tight?: string;
|
||||
normal?: string;
|
||||
relaxed?: string;
|
||||
};
|
||||
};
|
||||
spacing?: {
|
||||
xs?: string;
|
||||
sm?: string;
|
||||
md?: string;
|
||||
lg?: string;
|
||||
xl?: string;
|
||||
};
|
||||
borderRadius?: {
|
||||
none?: string;
|
||||
sm?: string;
|
||||
md?: string;
|
||||
lg?: string;
|
||||
xl?: string;
|
||||
full?: string;
|
||||
};
|
||||
shadows?: {
|
||||
none?: string;
|
||||
sm?: string;
|
||||
md?: string;
|
||||
lg?: string;
|
||||
xl?: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
## Vordefinierte Themes
|
||||
|
||||
### Default Theme
|
||||
|
||||
```javascript
|
||||
const defaultTheme = {
|
||||
name: 'Default',
|
||||
colors: {
|
||||
primary: '#3b82f6',
|
||||
secondary: '#8b5cf6',
|
||||
accent: '#ec4899',
|
||||
background: '#ffffff',
|
||||
surface: '#f9fafb',
|
||||
text: '#111827',
|
||||
textMuted: '#6b7280',
|
||||
border: '#e5e7eb',
|
||||
hover: '#f3f4f6'
|
||||
},
|
||||
typography: {
|
||||
fontFamily: 'Inter, sans-serif',
|
||||
fontSize: {
|
||||
xs: '0.75rem',
|
||||
sm: '0.875rem',
|
||||
md: '1rem',
|
||||
lg: '1.125rem',
|
||||
xl: '1.25rem'
|
||||
}
|
||||
},
|
||||
spacing: {
|
||||
xs: '0.25rem',
|
||||
sm: '0.5rem',
|
||||
md: '1rem',
|
||||
lg: '1.5rem',
|
||||
xl: '2rem'
|
||||
},
|
||||
borderRadius: {
|
||||
sm: '0.25rem',
|
||||
md: '0.5rem',
|
||||
lg: '0.75rem',
|
||||
xl: '1rem',
|
||||
full: '9999px'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Dark Theme
|
||||
|
||||
```javascript
|
||||
const darkTheme = {
|
||||
name: 'Dark',
|
||||
colors: {
|
||||
primary: '#60a5fa',
|
||||
secondary: '#a78bfa',
|
||||
accent: '#f472b6',
|
||||
background: '#111827',
|
||||
surface: '#1f2937',
|
||||
text: '#f9fafb',
|
||||
textMuted: '#9ca3af',
|
||||
border: '#374151',
|
||||
hover: '#374151'
|
||||
}
|
||||
// ... weitere Eigenschaften
|
||||
};
|
||||
```
|
||||
|
||||
### Minimal Theme
|
||||
|
||||
```javascript
|
||||
const minimalTheme = {
|
||||
name: 'Minimal',
|
||||
colors: {
|
||||
primary: '#000000',
|
||||
secondary: '#666666',
|
||||
accent: '#000000',
|
||||
background: '#ffffff',
|
||||
surface: '#ffffff',
|
||||
text: '#000000',
|
||||
textMuted: '#666666',
|
||||
border: '#e0e0e0',
|
||||
hover: '#f5f5f5'
|
||||
},
|
||||
typography: {
|
||||
fontFamily: 'Helvetica, Arial, sans-serif'
|
||||
},
|
||||
borderRadius: {
|
||||
sm: '0',
|
||||
md: '0',
|
||||
lg: '0',
|
||||
xl: '0'
|
||||
},
|
||||
shadows: {
|
||||
sm: 'none',
|
||||
md: 'none',
|
||||
lg: 'none'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## Theme verwenden
|
||||
|
||||
### Mit ThemeProvider
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import ThemeProvider from '$lib/components/cards/ThemeProvider.svelte';
|
||||
import BaseCard from '$lib/components/cards/BaseCard.svelte';
|
||||
|
||||
const myTheme = {
|
||||
colors: {
|
||||
primary: '#ff6b6b'
|
||||
// ...
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<ThemeProvider theme={myTheme}>
|
||||
<BaseCard {...cardConfig} />
|
||||
</ThemeProvider>
|
||||
```
|
||||
|
||||
### Direkt an BaseCard
|
||||
|
||||
```svelte
|
||||
<BaseCard theme={myTheme} variant="default" {modules} />
|
||||
```
|
||||
|
||||
## Theme-Editor
|
||||
|
||||
Der Theme-Editor ermöglicht die visuelle Erstellung und Anpassung von Themes.
|
||||
|
||||
### Features
|
||||
|
||||
- **Live-Preview**: Änderungen sofort sichtbar
|
||||
- **Color-Picker**: Farben visuell auswählen
|
||||
- **Typography-Controls**: Schriftarten und Größen anpassen
|
||||
- **Export/Import**: Themes als JSON ex-/importieren
|
||||
- **Speichern**: In Datenbank speichern
|
||||
|
||||
### Verwendung
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import ThemeEditor from '$lib/components/theme/ThemeEditor.svelte';
|
||||
</script>
|
||||
|
||||
<ThemeEditor initialTheme={currentTheme} onSave={(theme) => saveTheme(theme)} showPreview={true} />
|
||||
```
|
||||
|
||||
## CSS-Variablen
|
||||
|
||||
Themes werden als CSS-Variablen im DOM gesetzt:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--theme-primary: #3b82f6;
|
||||
--theme-secondary: #8b5cf6;
|
||||
--theme-accent: #ec4899;
|
||||
--theme-background: #ffffff;
|
||||
--theme-surface: #f9fafb;
|
||||
--theme-text: #111827;
|
||||
--theme-text-muted: #6b7280;
|
||||
--theme-border: #e5e7eb;
|
||||
--theme-hover: #f3f4f6;
|
||||
}
|
||||
```
|
||||
|
||||
### In Komponenten verwenden
|
||||
|
||||
```svelte
|
||||
<style>
|
||||
.my-component {
|
||||
color: var(--theme-text);
|
||||
background: var(--theme-background);
|
||||
border: 1px solid var(--theme-border);
|
||||
}
|
||||
|
||||
.my-component:hover {
|
||||
background: var(--theme-hover);
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## Theme Store
|
||||
|
||||
### Datenbank-Schema
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: string,
|
||||
name: string,
|
||||
slug: string,
|
||||
description: string,
|
||||
author: string,
|
||||
is_public: boolean,
|
||||
is_premium: boolean,
|
||||
price: number,
|
||||
colors: object,
|
||||
typography: object,
|
||||
spacing: object,
|
||||
borderRadius: object,
|
||||
shadows: object,
|
||||
animations: object,
|
||||
downloads: number,
|
||||
rating: number,
|
||||
created: datetime,
|
||||
updated: datetime
|
||||
}
|
||||
```
|
||||
|
||||
### Theme-Service Methoden
|
||||
|
||||
```typescript
|
||||
// Öffentliche Themes laden
|
||||
const themes = await cardTemplateService.getPublicThemes();
|
||||
|
||||
// Spezifisches Theme laden
|
||||
const theme = await cardTemplateService.getTheme(themeId);
|
||||
|
||||
// Neues Theme erstellen
|
||||
const newTheme = await cardTemplateService.createTheme({
|
||||
name: 'My Theme',
|
||||
colors: {
|
||||
/* ... */
|
||||
}
|
||||
});
|
||||
|
||||
// Theme aktualisieren
|
||||
const updated = await cardTemplateService.updateTheme(themeId, {
|
||||
colors: { primary: '#ff0000' }
|
||||
});
|
||||
```
|
||||
|
||||
## Theme-Kategorien
|
||||
|
||||
### Business
|
||||
|
||||
- Professionell
|
||||
- Seriös
|
||||
- Klare Linien
|
||||
- Gedeckte Farben
|
||||
|
||||
### Creative
|
||||
|
||||
- Bunt
|
||||
- Verspielt
|
||||
- Gradients
|
||||
- Animationen
|
||||
|
||||
### Minimal
|
||||
|
||||
- Reduziert
|
||||
- Viel Weißraum
|
||||
- Keine Schatten
|
||||
- Monochrom
|
||||
|
||||
### Dark Mode
|
||||
|
||||
- Dunkle Hintergründe
|
||||
- Helle Texte
|
||||
- Reduzierte Kontraste
|
||||
- Augenschonend
|
||||
|
||||
## Eigene Themes erstellen
|
||||
|
||||
### 1. Theme-Objekt definieren
|
||||
|
||||
```javascript
|
||||
const myCustomTheme = {
|
||||
name: 'Ocean Blue',
|
||||
colors: {
|
||||
primary: '#006994',
|
||||
secondary: '#00a8cc',
|
||||
accent: '#fafafa',
|
||||
background: '#f0f9ff',
|
||||
surface: '#e0f2fe',
|
||||
text: '#0c4a6e',
|
||||
textMuted: '#0284c7',
|
||||
border: '#bae6fd',
|
||||
hover: '#dbeafe'
|
||||
},
|
||||
typography: {
|
||||
fontFamily: '"Segoe UI", Tahoma, Geneva, Verdana, sans-serif'
|
||||
},
|
||||
borderRadius: {
|
||||
sm: '0.5rem',
|
||||
md: '0.75rem',
|
||||
lg: '1rem',
|
||||
xl: '1.5rem'
|
||||
},
|
||||
shadows: {
|
||||
sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
|
||||
md: '0 4px 6px -1px rgb(0 0 0 / 0.1)',
|
||||
lg: '0 10px 15px -3px rgb(0 0 0 / 0.1)'
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Theme registrieren
|
||||
|
||||
```javascript
|
||||
// In Datenbank speichern
|
||||
const saved = await cardTemplateService.createTheme(myCustomTheme);
|
||||
|
||||
// Oder lokal verwenden
|
||||
<ThemeProvider theme={myCustomTheme}>
|
||||
<!-- Karten hier -->
|
||||
</ThemeProvider>
|
||||
```
|
||||
|
||||
### 3. Theme testen
|
||||
|
||||
- Verschiedene Kartenvarianten
|
||||
- Alle Module-Typen
|
||||
- Light/Dark Umgebungen
|
||||
- Verschiedene Bildschirmgrößen
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Konsistenz**: Verwende ein einheitliches Farbschema
|
||||
2. **Kontrast**: Stelle sicher, dass Text gut lesbar ist
|
||||
3. **Hierarchie**: Nutze Farben zur visuellen Hierarchie
|
||||
4. **Zugänglichkeit**: Beachte WCAG-Richtlinien für Kontraste
|
||||
5. **Performance**: Vermeide zu viele CSS-Variablen
|
||||
6. **Responsive**: Teste auf verschiedenen Geräten
|
||||
7. **Dokumentation**: Dokumentiere Custom-Themes ausführlich
|
||||
|
||||
## Theme-Migration
|
||||
|
||||
### Von Tailwind zu Theme
|
||||
|
||||
```javascript
|
||||
// Tailwind-Klassen
|
||||
<div class="bg-blue-500 text-white p-4">
|
||||
|
||||
// Theme-System
|
||||
<div style="background: var(--theme-primary); color: var(--theme-text); padding: var(--theme-spacing-md);">
|
||||
```
|
||||
|
||||
### Theme-Versionierung
|
||||
|
||||
```javascript
|
||||
const theme = {
|
||||
name: 'My Theme',
|
||||
version: '1.2.0' // Semantic Versioning
|
||||
// ...
|
||||
};
|
||||
```
|
||||
278
apps/uload/docs/cards/unified-cards-migration.md
Normal file
278
apps/uload/docs/cards/unified-cards-migration.md
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
# Unified Cards - Datenbank Migration Guide
|
||||
|
||||
## PocketBase Collection Schema
|
||||
|
||||
### Neue Collection: `unified_cards`
|
||||
|
||||
```javascript
|
||||
// Collection Konfiguration
|
||||
{
|
||||
name: 'unified_cards',
|
||||
type: 'base',
|
||||
schema: [
|
||||
{
|
||||
name: 'user_id',
|
||||
type: 'relation',
|
||||
required: true,
|
||||
options: {
|
||||
collectionId: 'users',
|
||||
cascadeDelete: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'render_mode',
|
||||
type: 'select',
|
||||
required: true,
|
||||
options: {
|
||||
values: ['beginner', 'advanced', 'expert']
|
||||
}
|
||||
},
|
||||
// Modular Mode Fields
|
||||
{
|
||||
name: 'modules',
|
||||
type: 'json',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'theme_id',
|
||||
type: 'relation',
|
||||
required: false,
|
||||
options: {
|
||||
collectionId: 'themes'
|
||||
}
|
||||
},
|
||||
// Template Mode Fields
|
||||
{
|
||||
name: 'template',
|
||||
type: 'text',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'template_css',
|
||||
type: 'text',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'template_variables',
|
||||
type: 'json',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'template_values',
|
||||
type: 'json',
|
||||
required: false
|
||||
},
|
||||
// Custom HTML Mode Fields
|
||||
{
|
||||
name: 'custom_html',
|
||||
type: 'text',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'custom_css',
|
||||
type: 'text',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'custom_js',
|
||||
type: 'text',
|
||||
required: false
|
||||
},
|
||||
// Common Fields
|
||||
{
|
||||
name: 'variant',
|
||||
type: 'select',
|
||||
required: false,
|
||||
options: {
|
||||
values: ['default', 'compact', 'hero', 'minimal', 'glass', 'gradient']
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'constraints',
|
||||
type: 'json',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'metadata',
|
||||
type: 'json',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'page',
|
||||
type: 'text',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
type: 'number',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'is_active',
|
||||
type: 'bool',
|
||||
required: false,
|
||||
options: {
|
||||
default: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'is_public',
|
||||
type: 'bool',
|
||||
required: false,
|
||||
options: {
|
||||
default: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Migration Script
|
||||
|
||||
### 1. Erstelle die Collection in PocketBase Admin
|
||||
|
||||
1. Gehe zu PocketBase Admin UI
|
||||
2. Klicke auf "New Collection"
|
||||
3. Füge die Felder wie oben beschrieben hinzu
|
||||
4. Setze die API Rules:
|
||||
- List/View: `@request.auth.id = user_id || is_public = true`
|
||||
- Create: `@request.auth.id != ""`
|
||||
- Update: `@request.auth.id = user_id`
|
||||
- Delete: `@request.auth.id = user_id`
|
||||
|
||||
### 2. Migriere bestehende Cards
|
||||
|
||||
```javascript
|
||||
// Migration Script (in PocketBase Admin Console ausführen)
|
||||
// ODER als separates Node.js Script
|
||||
|
||||
async function migrateExistingCards() {
|
||||
// Hole alle existierenden user_cards
|
||||
const oldCards = await pb.collection('user_cards').getFullList();
|
||||
|
||||
for (const oldCard of oldCards) {
|
||||
const unifiedCard = {
|
||||
user_id: oldCard.user_id,
|
||||
render_mode: 'beginner', // Alle alten Cards sind modular
|
||||
modules: oldCard.custom_config?.modules || [],
|
||||
theme_id: oldCard.theme_id,
|
||||
variant: oldCard.custom_config?.variant || 'default',
|
||||
constraints: {
|
||||
aspectRatio: '16/9'
|
||||
},
|
||||
metadata: {
|
||||
name: oldCard.template_id || 'Migrated Card',
|
||||
created: oldCard.created,
|
||||
updated: oldCard.updated,
|
||||
migrated_from: oldCard.id
|
||||
},
|
||||
page: oldCard.page,
|
||||
position: oldCard.position,
|
||||
is_active: oldCard.is_active
|
||||
};
|
||||
|
||||
try {
|
||||
await pb.collection('unified_cards').create(unifiedCard);
|
||||
console.log(`Migrated card ${oldCard.id}`);
|
||||
} catch (error) {
|
||||
console.error(`Failed to migrate card ${oldCard.id}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Verwendung im Code
|
||||
|
||||
### Card erstellen/laden
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { unifiedCardService } from '$lib/services/unifiedCardService';
|
||||
import UnifiedCard from '$lib/components/cards/UnifiedCard.svelte';
|
||||
|
||||
// Neue Card erstellen
|
||||
const newCard = {
|
||||
renderMode: 'beginner',
|
||||
modularConfig: {
|
||||
modules: [{ type: 'header', props: { title: 'My Card' } }]
|
||||
}
|
||||
};
|
||||
|
||||
const cardId = await unifiedCardService.saveCard(newCard);
|
||||
|
||||
// Card laden
|
||||
const loadedCard = await unifiedCardService.loadCard(cardId);
|
||||
</script>
|
||||
|
||||
<!-- Card anzeigen -->
|
||||
<UnifiedCard card={loadedCard} />
|
||||
```
|
||||
|
||||
### Builder verwenden
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import UnifiedCardBuilder from '$lib/components/builder/UnifiedCardBuilder.svelte';
|
||||
|
||||
async function handleSave(card) {
|
||||
const id = await unifiedCardService.saveCard(card);
|
||||
if (id) {
|
||||
console.log('Card saved:', id);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<UnifiedCardBuilder onSave={handleSave} onCancel={() => history.back()} />
|
||||
```
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
Die alten `user_cards` und Module funktionieren weiterhin:
|
||||
|
||||
```svelte
|
||||
<!-- Alt (funktioniert weiter) -->
|
||||
<BaseCard {modules} />
|
||||
|
||||
<!-- Neu (unified) -->
|
||||
<UnifiedCard card={unifiedCard} />
|
||||
```
|
||||
|
||||
## Rollout Plan
|
||||
|
||||
### Phase 1: Parallel-Betrieb (Woche 1-2)
|
||||
|
||||
- Neue unified_cards Collection erstellen
|
||||
- UnifiedCard Komponenten deployen
|
||||
- Keine Breaking Changes
|
||||
|
||||
### Phase 2: Soft Migration (Woche 3-4)
|
||||
|
||||
- Neuer Builder als "Beta Feature"
|
||||
- User können zwischen altem und neuem Builder wählen
|
||||
- Automatische Migration bei erstem Speichern
|
||||
|
||||
### Phase 3: Full Migration (Woche 5-6)
|
||||
|
||||
- Alle Cards auf unified_cards migrieren
|
||||
- Alter Builder deprecated
|
||||
- user_cards Collection als Read-Only
|
||||
|
||||
### Phase 4: Cleanup (Woche 7-8)
|
||||
|
||||
- Alte Collections entfernen
|
||||
- Code Cleanup
|
||||
- Dokumentation finalisieren
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Modular Cards (Beginner Mode) funktionieren
|
||||
- [ ] Template Cards (Advanced Mode) funktionieren
|
||||
- [ ] Custom HTML Cards (Expert Mode) funktionieren
|
||||
- [ ] Migration von alten Cards
|
||||
- [ ] Sicherheit: XSS Prevention
|
||||
- [ ] Sicherheit: CSS Injection Prevention
|
||||
- [ ] Performance: Große Cards
|
||||
- [ ] Responsive Design
|
||||
- [ ] Browser Kompatibilität
|
||||
- [ ] Import/Export
|
||||
- [ ] Converter zwischen Modi
|
||||
Loading…
Add table
Add a link
Reference in a new issue