mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
feat: major update with network graphs, themes, todo extensions, and more
## New Features ### Network Graph Visualization (Contacts, Calendar, Todo) - D3.js force simulation for physics-based layout - Zoom & pan with mouse/touchpad - Keyboard shortcuts: +/- zoom, 0 reset, Esc deselect, / search, F focus - Filtering by tags, company/location/project, connection strength - Shared components in @manacore/shared-ui ### Central Tags API (mana-core-auth) - CRUD endpoints for tags - Schema: tags table with userId, name, color, app - Shared tag components in @manacore/shared-ui ### Custom Themes System - Theme editor with live preview and color picker - Community theme gallery - Theme sharing (public, unlisted, private) - Backend API in mana-core-auth ### Todo App Extensions - Glass-pill design for task input and items - Settings page with 20+ preferences - Task edit modal with inline editing - Statistics page with visualizations - PWA support with offline capabilities - Multiple kanban boards ### Contacts App Features - Duplicate detection - Photo upload - Batch operations - Enhanced favorites page with multiple view modes - Alphabet view improvements - Search modal ### Help System - @manacore/shared-help-content - @manacore/shared-help-ui - @manacore/shared-help-types ### Other Features - Themes page for all apps - Referral system frontend - CommandBar (global search) - Skeleton loaders - Settings page improvements ## Bug Fixes - Network graph simulation initialization - Database schema TEXT for user_id columns (Better Auth compatibility) - Various styling fixes ## Documentation - Daily report for 2025-12-10 - CI/CD deployment guide 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e84371aa94
commit
ee42b6cc76
381 changed files with 39284 additions and 6275 deletions
383
docs/central-services/COMMAND-BAR.md
Normal file
383
docs/central-services/COMMAND-BAR.md
Normal file
|
|
@ -0,0 +1,383 @@
|
|||
# Central Command Bar
|
||||
|
||||
Die zentrale Command Bar bietet eine einheitliche Schnellsuche und Navigation über alle Manacore-Apps hinweg. Sie wird mit `Cmd/Ctrl+K` aktiviert und bietet Suche, Quick Actions und Tastatur-Navigation.
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ @manacore/shared-ui │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ CommandBar.svelte │ │
|
||||
│ │ - Suche mit Debounce (150ms) │ │
|
||||
│ │ - Quick Actions │ │
|
||||
│ │ - Tastatur-Navigation │ │
|
||||
│ │ - Ergebnis-Anzeige mit Avataren │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────────────┼────────────────────┐
|
||||
│ │ │
|
||||
┌────▼────┐ ┌─────▼────┐ ┌─────▼────┐
|
||||
│ Todo │ │ Calendar │ │ Contacts │
|
||||
│ │ │ │ │ │
|
||||
│ Sucht │ │ Sucht │ │ Sucht │
|
||||
│ Tasks │ │ Events │ │ Kontakte │
|
||||
└─────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
## Package
|
||||
|
||||
| Package | Beschreibung |
|
||||
|---------|--------------|
|
||||
| `@manacore/shared-ui` | CommandBar Svelte-Komponente + TypeScript-Typen |
|
||||
|
||||
## Keyboard Shortcut
|
||||
|
||||
| Shortcut | Aktion |
|
||||
|----------|--------|
|
||||
| `Cmd/Ctrl+K` | Command Bar öffnen |
|
||||
| `↑↓` | Navigation durch Ergebnisse |
|
||||
| `Enter` | Auswahl bestätigen |
|
||||
| `Escape` | Schließen |
|
||||
|
||||
## TypeScript Interfaces
|
||||
|
||||
### CommandBarItem
|
||||
|
||||
Ein Suchergebnis:
|
||||
|
||||
```typescript
|
||||
interface CommandBarItem {
|
||||
id: string; // Eindeutige ID
|
||||
title: string; // Haupttext (z.B. Name, Titel)
|
||||
subtitle?: string; // Untertitel (z.B. Datum, E-Mail)
|
||||
icon?: string; // Icon-Name
|
||||
imageUrl?: string; // Avatar/Bild URL
|
||||
isFavorite?: boolean; // Favorit-Markierung (zeigt Herz-Icon)
|
||||
}
|
||||
```
|
||||
|
||||
### QuickAction
|
||||
|
||||
Eine Schnellaktion (ohne Suche):
|
||||
|
||||
```typescript
|
||||
interface QuickAction {
|
||||
id: string; // Eindeutige ID
|
||||
label: string; // Anzeigetext
|
||||
icon: string; // Icon-Name
|
||||
href?: string; // Link-Ziel (optional)
|
||||
shortcut?: string; // Tastenkürzel-Anzeige
|
||||
onclick?: () => void; // Click-Handler (alternativ zu href)
|
||||
}
|
||||
```
|
||||
|
||||
### Unterstützte Icons
|
||||
|
||||
Die CommandBar unterstützt folgende eingebaute Icons:
|
||||
|
||||
| Icon-Name | Beschreibung |
|
||||
|-----------|--------------|
|
||||
| `plus` | Plus-Zeichen (Neu erstellen) |
|
||||
| `heart` | Herz (Favoriten) |
|
||||
| `tag` | Tag/Label |
|
||||
| `upload` | Upload (Import) |
|
||||
| `calendar` | Kalender |
|
||||
| `clock` | Uhr |
|
||||
| `check` | Häkchen |
|
||||
| `settings` | Zahnrad (Einstellungen) |
|
||||
| `list` | Liste |
|
||||
|
||||
## Komponenten-Props
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
open: boolean; // Sichtbarkeit
|
||||
onClose: () => void; // Schließen-Handler
|
||||
onSearch: (query: string) => Promise<CommandBarItem[]>; // Such-Funktion
|
||||
onSelect: (item: CommandBarItem) => void; // Auswahl-Handler
|
||||
quickActions?: QuickAction[]; // Schnellaktionen
|
||||
placeholder?: string; // Suchfeld-Placeholder
|
||||
emptyText?: string; // Text bei leeren Ergebnissen
|
||||
searchingText?: string; // Text während Suche
|
||||
}
|
||||
```
|
||||
|
||||
## Nutzung
|
||||
|
||||
### Basis-Beispiel
|
||||
|
||||
```svelte
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { CommandBar } from '@manacore/shared-ui';
|
||||
import type { CommandBarItem, QuickAction } from '@manacore/shared-ui';
|
||||
|
||||
let commandBarOpen = $state(false);
|
||||
|
||||
// Quick Actions definieren
|
||||
const quickActions: QuickAction[] = [
|
||||
{ id: 'new', label: 'Neu erstellen', icon: 'plus', href: '/new', shortcut: 'N' },
|
||||
{ id: 'settings', label: 'Einstellungen', icon: 'settings', href: '/settings' },
|
||||
];
|
||||
|
||||
// Such-Funktion implementieren
|
||||
async function handleSearch(query: string): Promise<CommandBarItem[]> {
|
||||
const results = await api.search(query);
|
||||
return results.map((item) => ({
|
||||
id: item.id,
|
||||
title: item.name,
|
||||
subtitle: item.description,
|
||||
}));
|
||||
}
|
||||
|
||||
// Auswahl verarbeiten
|
||||
function handleSelect(item: CommandBarItem) {
|
||||
goto(`/item/${item.id}`);
|
||||
}
|
||||
|
||||
// Keyboard Shortcut registrieren
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
|
||||
event.preventDefault();
|
||||
commandBarOpen = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
<CommandBar
|
||||
bind:open={commandBarOpen}
|
||||
onClose={() => (commandBarOpen = false)}
|
||||
onSearch={handleSearch}
|
||||
onSelect={handleSelect}
|
||||
{quickActions}
|
||||
placeholder="Suchen..."
|
||||
emptyText="Keine Ergebnisse"
|
||||
searchingText="Suche..."
|
||||
/>
|
||||
```
|
||||
|
||||
## App-spezifische Implementierungen
|
||||
|
||||
### Todo App
|
||||
|
||||
```typescript
|
||||
// Quick Actions
|
||||
const quickActions: QuickAction[] = [
|
||||
{ id: 'new', label: 'Neue Aufgabe erstellen', icon: 'plus', href: '/task/new', shortcut: 'N' },
|
||||
{ id: 'kanban', label: 'Kanban-Board', icon: 'list', href: '/kanban' },
|
||||
{ id: 'stats', label: 'Statistiken', icon: 'chart', href: '/statistics' },
|
||||
{ id: 'settings', label: 'Einstellungen', icon: 'settings', href: '/settings' },
|
||||
];
|
||||
|
||||
// Suche: Tasks durchsuchen
|
||||
async function handleSearch(query: string): Promise<CommandBarItem[]> {
|
||||
const tasks = await getTasks({ search: query });
|
||||
return tasks.slice(0, 10).map((task) => ({
|
||||
id: task.id,
|
||||
title: task.title,
|
||||
subtitle: task.isCompleted
|
||||
? '✓ Erledigt'
|
||||
: task.dueDate
|
||||
? new Date(task.dueDate).toLocaleDateString('de-DE')
|
||||
: 'Kein Datum',
|
||||
}));
|
||||
}
|
||||
|
||||
// Auswahl: Zu Task navigieren
|
||||
function handleSelect(item: CommandBarItem) {
|
||||
goto(`/task/${item.id}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Calendar App
|
||||
|
||||
```typescript
|
||||
// Quick Actions
|
||||
const quickActions: QuickAction[] = [
|
||||
{ id: 'new', label: 'Neuen Termin erstellen', icon: 'plus', href: '/event/new', shortcut: 'N' },
|
||||
{ id: 'today', label: 'Zu Heute springen', icon: 'calendar', onclick: () => viewStore.goToToday() },
|
||||
{ id: 'agenda', label: 'Agenda anzeigen', icon: 'list', href: '/agenda' },
|
||||
{ id: 'settings', label: 'Einstellungen', icon: 'settings', href: '/settings' },
|
||||
];
|
||||
|
||||
// Suche: Events durchsuchen
|
||||
async function handleSearch(query: string): Promise<CommandBarItem[]> {
|
||||
const result = await searchEvents(query);
|
||||
if (result.error || !result.data) return [];
|
||||
|
||||
return result.data.slice(0, 10).map((event) => ({
|
||||
id: event.id,
|
||||
title: event.title,
|
||||
subtitle: format(new Date(event.startTime), 'dd. MMM yyyy, HH:mm', { locale: de }),
|
||||
}));
|
||||
}
|
||||
|
||||
// Auswahl: Zu Event navigieren
|
||||
function handleSelect(item: CommandBarItem) {
|
||||
goto(`/event/${item.id}`);
|
||||
}
|
||||
```
|
||||
|
||||
### Contacts App
|
||||
|
||||
```typescript
|
||||
// Quick Actions
|
||||
const quickActions: QuickAction[] = [
|
||||
{ id: 'new', label: 'Neuen Kontakt erstellen', icon: 'plus', href: '/contacts/new', shortcut: 'N' },
|
||||
{ id: 'favorites', label: 'Favoriten anzeigen', icon: 'heart', href: '/favorites' },
|
||||
{ id: 'tags', label: 'Tags verwalten', icon: 'tag', href: '/tags' },
|
||||
{ id: 'import', label: 'Kontakte importieren', icon: 'upload', href: '/data?tab=import' },
|
||||
];
|
||||
|
||||
// Suche: Kontakte durchsuchen
|
||||
async function handleSearch(query: string): Promise<CommandBarItem[]> {
|
||||
const response = await contactsApi.list({ search: query, limit: 10 });
|
||||
return (response.contacts || []).map((contact) => ({
|
||||
id: contact.id,
|
||||
title: contact.displayName ||
|
||||
[contact.firstName, contact.lastName].filter(Boolean).join(' ') ||
|
||||
contact.email || 'Unbekannt',
|
||||
subtitle: contact.company || contact.email,
|
||||
imageUrl: contact.photoUrl,
|
||||
isFavorite: contact.isFavorite,
|
||||
}));
|
||||
}
|
||||
|
||||
// Auswahl: Kontakt-Modal öffnen
|
||||
function handleSelect(item: CommandBarItem) {
|
||||
goto(`/contacts/${item.id}`);
|
||||
}
|
||||
```
|
||||
|
||||
## Funktionen
|
||||
|
||||
### Suche
|
||||
|
||||
- **Debounce:** 150ms Verzögerung für Performance
|
||||
- **Loading-State:** Spinner während der Suche
|
||||
- **Empty State:** Konfigurierbare Meldung bei keinen Ergebnissen
|
||||
- **Limit:** Ergebnisse werden typischerweise auf 10 begrenzt
|
||||
|
||||
### Quick Actions
|
||||
|
||||
Wenn kein Suchtext eingegeben ist, werden Quick Actions angezeigt:
|
||||
|
||||
- Navigation mit Pfeiltasten
|
||||
- Ausführung mit Enter
|
||||
- Keyboard-Shortcuts werden rechts angezeigt
|
||||
|
||||
### Ergebnis-Anzeige
|
||||
|
||||
- **Avatar:** Bild oder Initialen
|
||||
- **Titel:** Haupttext
|
||||
- **Untertitel:** Zusatzinfo (grau)
|
||||
- **Favorit:** Herz-Icon wenn `isFavorite: true`
|
||||
- **Hover:** Visuelles Feedback bei Maus-Over
|
||||
|
||||
### Keyboard Navigation
|
||||
|
||||
| Taste | Aktion |
|
||||
|-------|--------|
|
||||
| `↑` / `↓` | Durch Ergebnisse navigieren |
|
||||
| `Enter` | Ausgewähltes Element öffnen |
|
||||
| `Escape` | Command Bar schließen |
|
||||
|
||||
## Styling
|
||||
|
||||
Die Command Bar verwendet ein dunkles Theme mit CSS-Variablen:
|
||||
|
||||
```css
|
||||
.command-modal {
|
||||
background: #1a1a1a;
|
||||
border: 1px solid #333;
|
||||
border-radius: 12px;
|
||||
max-width: 560px;
|
||||
}
|
||||
|
||||
.command-result.selected {
|
||||
background: #2a2a2a;
|
||||
}
|
||||
|
||||
.result-avatar {
|
||||
background: #3b82f6; /* Primary Color */
|
||||
}
|
||||
|
||||
.result-favorite {
|
||||
color: #ef4444; /* Rot für Herz */
|
||||
}
|
||||
```
|
||||
|
||||
### Animationen
|
||||
|
||||
- **Fade In:** Backdrop erscheint mit 0.15s
|
||||
- **Slide In:** Modal gleitet von oben mit 0.2s
|
||||
- **Loading Spinner:** Rotation Animation
|
||||
|
||||
## Integration in Layout
|
||||
|
||||
Typischerweise wird die CommandBar im App-Layout integriert:
|
||||
|
||||
```svelte
|
||||
<!-- src/routes/(app)/+layout.svelte -->
|
||||
<script lang="ts">
|
||||
import { CommandBar } from '@manacore/shared-ui';
|
||||
|
||||
let commandBarOpen = $state(false);
|
||||
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
// Cmd/Ctrl+K funktioniert auch in Input-Feldern
|
||||
if ((event.ctrlKey || event.metaKey) && event.key === 'k') {
|
||||
event.preventDefault();
|
||||
commandBarOpen = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window onkeydown={handleKeydown} />
|
||||
|
||||
<!-- Haupt-Layout -->
|
||||
<PillNavigation ... />
|
||||
|
||||
<main>
|
||||
{@render children()}
|
||||
</main>
|
||||
|
||||
<!-- Command Bar (global) -->
|
||||
<CommandBar
|
||||
bind:open={commandBarOpen}
|
||||
onClose={() => (commandBarOpen = false)}
|
||||
onSearch={handleSearch}
|
||||
onSelect={handleSelect}
|
||||
{quickActions}
|
||||
/>
|
||||
```
|
||||
|
||||
## Dateien
|
||||
|
||||
### @manacore/shared-ui
|
||||
|
||||
| Datei | Beschreibung |
|
||||
|-------|--------------|
|
||||
| `src/command-bar/CommandBar.svelte` | Hauptkomponente |
|
||||
| `src/command-bar/index.ts` | Exports |
|
||||
| `src/index.ts` | Package-Export |
|
||||
|
||||
## Vorteile
|
||||
|
||||
- **Einheitliche UX:** Gleiche Interaktion in allen Apps
|
||||
- **Schnelle Navigation:** Sofortiger Zugriff auf häufige Aktionen
|
||||
- **Tastatur-fokussiert:** Optimiert für Power-User
|
||||
- **Flexibel:** App-spezifische Such-Logik und Quick Actions
|
||||
- **Responsive:** Funktioniert auf Desktop und Mobile
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Such-Performance:** Limit auf 10 Ergebnisse setzen
|
||||
2. **Quick Actions:** Maximal 4-5 Aktionen für Übersichtlichkeit
|
||||
3. **Sinnvolle Shortcuts:** Mnemonische Kürzel (N=Neu, S=Settings)
|
||||
4. **Gute Subtitles:** Zusätzliche Info für eindeutige Identifikation
|
||||
5. **Keyboard First:** Cmd+K sollte immer funktionieren, auch in Input-Feldern
|
||||
603
docs/central-services/HELP.md
Normal file
603
docs/central-services/HELP.md
Normal file
|
|
@ -0,0 +1,603 @@
|
|||
# Central Help System
|
||||
|
||||
Das zentrale Help-System bietet eine einheitliche Hilfeseite für alle Manacore-Apps. Es unterstützt mehrsprachige Inhalte, Volltextsuche, und die Kombination von zentralen und app-spezifischen Inhalten.
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Zentrale Help-Inhalte │
|
||||
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
|
||||
│ │ FAQ (allgemein)│ │ Features │ │ Changelog │ │
|
||||
│ │ - Account │ │ - Theming │ │ - v1.5.0 │ │
|
||||
│ │ - Billing │ │ - Tags │ │ - v1.4.0 │ │
|
||||
│ │ - Privacy │ │ - Sync │ │ ... │ │
|
||||
│ └───────────────┘ └───────────────┘ └───────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
mergeContent()
|
||||
│
|
||||
┌─────────────────────┼─────────────────────┐
|
||||
│ │ │
|
||||
┌────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
|
||||
│ Todo │ │ Calendar │ │ Contacts │
|
||||
│ │ │ │ │ │
|
||||
│ + App- │ │ + App- │ │ + App- │
|
||||
│ spezif. │ │ spezif. │ │ spezif. │
|
||||
│ FAQ │ │ Shortcuts │ │ Features │
|
||||
└──────────┘ └───────────┘ └───────────┘
|
||||
```
|
||||
|
||||
## Packages
|
||||
|
||||
| Package | Beschreibung |
|
||||
|---------|--------------|
|
||||
| `@manacore/shared-help-types` | TypeScript-Typen und Zod-Schemas |
|
||||
| `@manacore/shared-help-content` | Content-Loader, Parser, Merger, Suche |
|
||||
| `@manacore/shared-help-ui` | Svelte 5 UI-Komponenten |
|
||||
| `@manacore/shared-help-mobile` | React Native Komponenten |
|
||||
|
||||
## Content-Typen
|
||||
|
||||
### FAQ (Frequently Asked Questions)
|
||||
|
||||
```typescript
|
||||
interface FAQItem {
|
||||
id: string;
|
||||
language: 'de' | 'en' | 'fr' | 'it' | 'es';
|
||||
question: string;
|
||||
answer: string;
|
||||
category: 'general' | 'account' | 'billing' | 'features' | 'technical' | 'privacy';
|
||||
featured?: boolean;
|
||||
tags?: string[];
|
||||
relatedFaqs?: string[];
|
||||
order?: number;
|
||||
appSpecific?: boolean;
|
||||
apps?: string[];
|
||||
}
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
```typescript
|
||||
interface FeatureItem {
|
||||
id: string;
|
||||
language: SupportedLanguage;
|
||||
title: string;
|
||||
description: string;
|
||||
content: string;
|
||||
icon?: string;
|
||||
category: 'getting-started' | 'core' | 'advanced' | 'integration';
|
||||
available?: boolean;
|
||||
comingSoon?: boolean;
|
||||
highlights?: string[];
|
||||
learnMoreUrl?: string;
|
||||
}
|
||||
```
|
||||
|
||||
### Keyboard Shortcuts
|
||||
|
||||
```typescript
|
||||
interface ShortcutsItem {
|
||||
id: string;
|
||||
language: SupportedLanguage;
|
||||
category: 'navigation' | 'editing' | 'general' | 'app-specific';
|
||||
title?: string;
|
||||
shortcuts: Array<{
|
||||
shortcut: string; // z.B. "Ctrl+S", "⌘+K"
|
||||
action: string;
|
||||
description?: string;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
### Getting Started Guides
|
||||
|
||||
```typescript
|
||||
interface GettingStartedItem {
|
||||
id: string;
|
||||
language: SupportedLanguage;
|
||||
title: string;
|
||||
description: string;
|
||||
content: string;
|
||||
difficulty: 'beginner' | 'intermediate' | 'advanced';
|
||||
estimatedTime?: string;
|
||||
prerequisites?: string[];
|
||||
steps?: Array<{
|
||||
title: string;
|
||||
content: string;
|
||||
duration?: string;
|
||||
}>;
|
||||
}
|
||||
```
|
||||
|
||||
### Changelog
|
||||
|
||||
```typescript
|
||||
interface ChangelogItem {
|
||||
id: string;
|
||||
language: SupportedLanguage;
|
||||
version: string;
|
||||
title: string;
|
||||
releaseDate: Date;
|
||||
type: 'major' | 'minor' | 'patch' | 'beta';
|
||||
summary?: string;
|
||||
content: string;
|
||||
highlighted?: boolean;
|
||||
changes?: {
|
||||
features?: Array<{ title: string; description?: string }>;
|
||||
improvements?: Array<{ title: string; description?: string }>;
|
||||
bugfixes?: Array<{ title: string; description?: string }>;
|
||||
};
|
||||
platforms?: string[];
|
||||
}
|
||||
```
|
||||
|
||||
### Contact Info
|
||||
|
||||
```typescript
|
||||
interface ContactInfo {
|
||||
id: string;
|
||||
language: SupportedLanguage;
|
||||
title: string;
|
||||
content: string;
|
||||
supportEmail?: string;
|
||||
supportUrl?: string;
|
||||
discordUrl?: string;
|
||||
twitterUrl?: string;
|
||||
documentationUrl?: string;
|
||||
responseTime?: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Unterstützte Sprachen
|
||||
|
||||
```typescript
|
||||
type SupportedLanguage = 'en' | 'de' | 'fr' | 'it' | 'es';
|
||||
```
|
||||
|
||||
## Content-Struktur
|
||||
|
||||
### HelpContent Objekt
|
||||
|
||||
```typescript
|
||||
interface HelpContent {
|
||||
faq: FAQItem[];
|
||||
features: FeatureItem[];
|
||||
shortcuts: ShortcutsItem[];
|
||||
gettingStarted: GettingStartedItem[];
|
||||
changelog: ChangelogItem[];
|
||||
contact: ContactInfo | null;
|
||||
}
|
||||
```
|
||||
|
||||
## Content Laden und Mergen
|
||||
|
||||
### Content Merger
|
||||
|
||||
Der Merger kombiniert zentrale und app-spezifische Inhalte:
|
||||
|
||||
```typescript
|
||||
import { mergeContent, createEmptyContent } from '@manacore/shared-help-content';
|
||||
|
||||
// Zentrale Inhalte (für alle Apps)
|
||||
const centralContent: HelpContent = {
|
||||
faq: [
|
||||
{ id: 'account-login', question: 'Wie melde ich mich an?', ... },
|
||||
{ id: 'billing-cancel', question: 'Wie kündige ich?', ... },
|
||||
],
|
||||
features: [...],
|
||||
// ...
|
||||
};
|
||||
|
||||
// App-spezifische Inhalte
|
||||
const appContent: Partial<HelpContent> = {
|
||||
faq: [
|
||||
{ id: 'todo-recurring', question: 'Wie erstelle ich wiederkehrende Tasks?', ... },
|
||||
],
|
||||
shortcuts: [
|
||||
{ category: 'app-specific', shortcuts: [...] },
|
||||
],
|
||||
};
|
||||
|
||||
// Zusammenführen
|
||||
const content = mergeContent(centralContent, appContent, {
|
||||
appId: 'todo',
|
||||
locale: 'de',
|
||||
overrideById: true, // App-Content ersetzt zentralen mit gleicher ID
|
||||
});
|
||||
```
|
||||
|
||||
### Filterung
|
||||
|
||||
Inhalte werden automatisch gefiltert nach:
|
||||
- **Sprache:** Nur Inhalte der aktuellen Locale
|
||||
- **App:** `appSpecific: true` Inhalte nur wenn `apps` die aktuelle App enthält
|
||||
- **Reihenfolge:** Sortiert nach `order` Property
|
||||
|
||||
## Suche
|
||||
|
||||
### Such-Index erstellen
|
||||
|
||||
```typescript
|
||||
import { buildSearchIndex, search, createSearcher } from '@manacore/shared-help-content';
|
||||
|
||||
// Index erstellen
|
||||
const index = buildSearchIndex(content, {
|
||||
titleWeight: 2.0,
|
||||
contentWeight: 1.0,
|
||||
tagsWeight: 1.5,
|
||||
threshold: 0.3,
|
||||
minMatchCharLength: 2,
|
||||
});
|
||||
|
||||
// Suchen
|
||||
const results = search(index, 'wiederkehrend', {
|
||||
limit: 10,
|
||||
threshold: 0.4,
|
||||
types: ['faq', 'guide'], // Optional: Nur bestimmte Typen
|
||||
});
|
||||
```
|
||||
|
||||
### SearchResult
|
||||
|
||||
```typescript
|
||||
interface SearchResult {
|
||||
id: string;
|
||||
type: 'faq' | 'feature' | 'guide' | 'changelog';
|
||||
title: string;
|
||||
excerpt: string;
|
||||
score: number;
|
||||
highlight?: string;
|
||||
item: FAQItem | FeatureItem | GettingStartedItem | ChangelogItem;
|
||||
}
|
||||
```
|
||||
|
||||
## UI-Komponenten
|
||||
|
||||
### HelpPage (Hauptkomponente)
|
||||
|
||||
Vollständige Hilfeseite mit allen Sektionen:
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { HelpPage } from '@manacore/shared-help-ui';
|
||||
import { helpContent, translations } from '$lib/help';
|
||||
</script>
|
||||
|
||||
<HelpPage
|
||||
content={helpContent}
|
||||
appName="Todo"
|
||||
appId="todo"
|
||||
translations={translations}
|
||||
searchEnabled={true}
|
||||
showFAQ={true}
|
||||
showFeatures={true}
|
||||
showShortcuts={true}
|
||||
showGettingStarted={true}
|
||||
showChangelog={true}
|
||||
showContact={true}
|
||||
defaultSection="faq"
|
||||
showBackButton={true}
|
||||
onBack={() => goto('/')}
|
||||
onSectionChange={(section) => console.log(section)}
|
||||
/>
|
||||
```
|
||||
|
||||
### Einzelne Sektionen
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import {
|
||||
FAQSection,
|
||||
FeaturesOverview,
|
||||
KeyboardShortcuts,
|
||||
GettingStartedGuide,
|
||||
ChangelogSection,
|
||||
ContactSection,
|
||||
HelpSearch,
|
||||
} from '@manacore/shared-help-ui';
|
||||
</script>
|
||||
|
||||
<!-- FAQ mit Kategorien -->
|
||||
<FAQSection
|
||||
items={content.faq}
|
||||
translations={translations}
|
||||
showCategories={true}
|
||||
maxItems={10}
|
||||
expandFirst={true}
|
||||
/>
|
||||
|
||||
<!-- Features-Übersicht -->
|
||||
<FeaturesOverview
|
||||
items={content.features}
|
||||
translations={translations}
|
||||
/>
|
||||
|
||||
<!-- Keyboard Shortcuts -->
|
||||
<KeyboardShortcuts
|
||||
items={content.shortcuts}
|
||||
translations={translations}
|
||||
/>
|
||||
|
||||
<!-- Getting Started Guides -->
|
||||
<GettingStartedGuide
|
||||
items={content.gettingStarted}
|
||||
translations={translations}
|
||||
/>
|
||||
|
||||
<!-- Changelog -->
|
||||
<ChangelogSection
|
||||
items={content.changelog}
|
||||
translations={translations}
|
||||
maxItems={5}
|
||||
/>
|
||||
|
||||
<!-- Kontakt -->
|
||||
<ContactSection
|
||||
contact={content.contact}
|
||||
translations={translations}
|
||||
/>
|
||||
|
||||
<!-- Suche -->
|
||||
<HelpSearch
|
||||
content={content}
|
||||
translations={translations}
|
||||
placeholder="Hilfe durchsuchen..."
|
||||
onResultSelect={(result) => navigateToResult(result)}
|
||||
/>
|
||||
```
|
||||
|
||||
## Übersetzungen
|
||||
|
||||
### HelpPageTranslations Interface
|
||||
|
||||
```typescript
|
||||
interface HelpPageTranslations {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
searchPlaceholder: string;
|
||||
sections: {
|
||||
faq: string;
|
||||
features: string;
|
||||
shortcuts: string;
|
||||
gettingStarted: string;
|
||||
changelog: string;
|
||||
contact: string;
|
||||
};
|
||||
search: {
|
||||
noResults: string;
|
||||
resultsCount: string;
|
||||
searching: string;
|
||||
};
|
||||
faq: {
|
||||
noItems: string;
|
||||
categories: {
|
||||
general: string;
|
||||
account: string;
|
||||
billing: string;
|
||||
features: string;
|
||||
technical: string;
|
||||
privacy: string;
|
||||
};
|
||||
};
|
||||
features: {
|
||||
noItems: string;
|
||||
comingSoon: string;
|
||||
learnMore: string;
|
||||
};
|
||||
shortcuts: {
|
||||
noItems: string;
|
||||
};
|
||||
gettingStarted: {
|
||||
noItems: string;
|
||||
estimatedTime: string;
|
||||
difficulty: {
|
||||
beginner: string;
|
||||
intermediate: string;
|
||||
advanced: string;
|
||||
};
|
||||
};
|
||||
changelog: {
|
||||
noItems: string;
|
||||
types: {
|
||||
major: string;
|
||||
minor: string;
|
||||
patch: string;
|
||||
beta: string;
|
||||
};
|
||||
};
|
||||
contact: {
|
||||
noInfo: string;
|
||||
email: string;
|
||||
responseTime: string;
|
||||
};
|
||||
common: {
|
||||
back: string;
|
||||
showMore: string;
|
||||
showLess: string;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Beispiel (Deutsch)
|
||||
|
||||
```typescript
|
||||
const translations: HelpPageTranslations = {
|
||||
title: 'Hilfe',
|
||||
subtitle: 'Finde Antworten und lerne die App kennen',
|
||||
searchPlaceholder: 'Suche in der Hilfe...',
|
||||
sections: {
|
||||
faq: 'Häufige Fragen',
|
||||
features: 'Funktionen',
|
||||
shortcuts: 'Tastaturkürzel',
|
||||
gettingStarted: 'Erste Schritte',
|
||||
changelog: 'Neuigkeiten',
|
||||
contact: 'Kontakt',
|
||||
},
|
||||
search: {
|
||||
noResults: 'Keine Ergebnisse gefunden',
|
||||
resultsCount: '{count} Ergebnisse',
|
||||
searching: 'Suche...',
|
||||
},
|
||||
faq: {
|
||||
noItems: 'Keine FAQ-Einträge vorhanden',
|
||||
categories: {
|
||||
general: 'Allgemein',
|
||||
account: 'Konto',
|
||||
billing: 'Abrechnung',
|
||||
features: 'Funktionen',
|
||||
technical: 'Technisch',
|
||||
privacy: 'Datenschutz',
|
||||
},
|
||||
},
|
||||
// ... weitere
|
||||
};
|
||||
```
|
||||
|
||||
## Dateien
|
||||
|
||||
### @manacore/shared-help-types
|
||||
|
||||
| Datei | Beschreibung |
|
||||
|-------|--------------|
|
||||
| `src/content.ts` | Content-Typ Definitionen |
|
||||
| `src/schemas.ts` | Zod-Validierungsschemas |
|
||||
| `src/search.ts` | Such-bezogene Typen |
|
||||
|
||||
### @manacore/shared-help-content
|
||||
|
||||
| Datei | Beschreibung |
|
||||
|-------|--------------|
|
||||
| `src/parser.ts` | Markdown-Parser |
|
||||
| `src/loader.ts` | Content-Loader für verschiedene Formate |
|
||||
| `src/merger.ts` | Content-Merger (zentral + app-spezifisch) |
|
||||
| `src/search.ts` | Volltextsuche mit Fuse.js |
|
||||
|
||||
### @manacore/shared-help-ui
|
||||
|
||||
| Datei | Beschreibung |
|
||||
|-------|--------------|
|
||||
| `src/pages/HelpPage.svelte` | Vollständige Hilfeseite |
|
||||
| `src/components/FAQSection.svelte` | FAQ-Bereich |
|
||||
| `src/components/FAQItem.svelte` | Einzelner FAQ-Eintrag |
|
||||
| `src/components/FeaturesOverview.svelte` | Feature-Übersicht |
|
||||
| `src/components/FeatureCard.svelte` | Feature-Karte |
|
||||
| `src/components/KeyboardShortcuts.svelte` | Tastaturkürzel |
|
||||
| `src/components/GettingStartedGuide.svelte` | Erste-Schritte-Guide |
|
||||
| `src/components/ChangelogSection.svelte` | Changelog-Bereich |
|
||||
| `src/components/ChangelogEntry.svelte` | Einzelner Changelog-Eintrag |
|
||||
| `src/components/ContactSection.svelte` | Kontakt-Bereich |
|
||||
| `src/components/HelpSearch.svelte` | Suchkomponente |
|
||||
|
||||
## Integration in eine App
|
||||
|
||||
### 1. Dependencies
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@manacore/shared-help-types": "workspace:*",
|
||||
"@manacore/shared-help-content": "workspace:*",
|
||||
"@manacore/shared-help-ui": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Content erstellen
|
||||
|
||||
```typescript
|
||||
// src/lib/help/content.ts
|
||||
import type { HelpContent } from '@manacore/shared-help-types';
|
||||
|
||||
export const appHelpContent: Partial<HelpContent> = {
|
||||
faq: [
|
||||
{
|
||||
id: 'calendar-recurring',
|
||||
language: 'de',
|
||||
question: 'Wie erstelle ich wiederkehrende Termine?',
|
||||
answer: 'Öffne einen Termin und aktiviere die Wiederholung...',
|
||||
category: 'features',
|
||||
appSpecific: true,
|
||||
apps: ['calendar'],
|
||||
},
|
||||
],
|
||||
shortcuts: [
|
||||
{
|
||||
id: 'calendar-shortcuts',
|
||||
language: 'de',
|
||||
category: 'app-specific',
|
||||
title: 'Kalender-Shortcuts',
|
||||
shortcuts: [
|
||||
{ shortcut: 'N', action: 'Neuer Termin' },
|
||||
{ shortcut: 'T', action: 'Zu Heute springen' },
|
||||
{ shortcut: 'W', action: 'Wochenansicht' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Hilfeseite erstellen
|
||||
|
||||
```svelte
|
||||
<!-- src/routes/(app)/help/+page.svelte -->
|
||||
<script lang="ts">
|
||||
import { HelpPage } from '@manacore/shared-help-ui';
|
||||
import { mergeContent } from '@manacore/shared-help-content';
|
||||
import { centralContent } from '$lib/help/central';
|
||||
import { appHelpContent } from '$lib/help/content';
|
||||
import { translations } from '$lib/help/translations';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
const content = mergeContent(centralContent, appHelpContent, {
|
||||
appId: 'calendar',
|
||||
locale: 'de',
|
||||
});
|
||||
</script>
|
||||
|
||||
<HelpPage
|
||||
{content}
|
||||
appName="Calendar"
|
||||
appId="calendar"
|
||||
{translations}
|
||||
searchEnabled={true}
|
||||
onBack={() => goto('/')}
|
||||
/>
|
||||
```
|
||||
|
||||
## App-spezifische Inhalte
|
||||
|
||||
### Markierung
|
||||
|
||||
Inhalte können als app-spezifisch markiert werden:
|
||||
|
||||
```typescript
|
||||
{
|
||||
id: 'todo-labels',
|
||||
appSpecific: true,
|
||||
apps: ['todo', 'calendar'], // Nur in diesen Apps sichtbar
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Override by ID
|
||||
|
||||
Wenn `overrideById: true` (Standard), ersetzt app-spezifischer Content zentralen Content mit gleicher ID:
|
||||
|
||||
```typescript
|
||||
// Zentral
|
||||
{ id: 'feature-tags', title: 'Tags allgemein', ... }
|
||||
|
||||
// App-spezifisch
|
||||
{ id: 'feature-tags', title: 'Tags in Todo', ... } // Ersetzt zentralen Content
|
||||
```
|
||||
|
||||
## Vorteile
|
||||
|
||||
- **Wiederverwendbarkeit:** Zentrale FAQs (Account, Billing) nur einmal schreiben
|
||||
- **Konsistenz:** Einheitliches Look & Feel der Hilfeseite
|
||||
- **Mehrsprachigkeit:** 5 Sprachen unterstützt
|
||||
- **Suche:** Integrierte Volltextsuche
|
||||
- **Flexibilität:** App-spezifische Inhalte können hinzugefügt werden
|
||||
- **Typ-Sicherheit:** Vollständige TypeScript-Typen und Zod-Validierung
|
||||
90
docs/central-services/README.md
Normal file
90
docs/central-services/README.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# Central Services
|
||||
|
||||
Dieses Verzeichnis dokumentiert zentrale Services, die von allen Manacore-Apps gemeinsam genutzt werden. Diese Services laufen in `mana-core-auth` und bieten einheitliche APIs.
|
||||
|
||||
## Übersicht
|
||||
|
||||
| Service | Beschreibung | Dokumentation |
|
||||
|---------|--------------|---------------|
|
||||
| **Tags** | Einheitliche Tags/Labels für Todo, Calendar, Contacts | [TAGS.md](./TAGS.md) |
|
||||
| **Theming** | Theme-Varianten, Dark Mode, Accessibility, Custom Themes | [THEMING.md](./THEMING.md) |
|
||||
| **Help** | Zentrale Hilfeseite mit FAQ, Features, Shortcuts, Changelog | [HELP.md](./HELP.md) |
|
||||
| **Command Bar** | Globale Schnellsuche und Navigation (Cmd/Ctrl+K) | [COMMAND-BAR.md](./COMMAND-BAR.md) |
|
||||
|
||||
## Architektur-Prinzipien
|
||||
|
||||
### Zentralisierung
|
||||
|
||||
Bestimmte Daten und Funktionen werden zentral in `mana-core-auth` verwaltet:
|
||||
|
||||
- **User-bezogen:** Jeder Service speichert Daten pro User (`userId`)
|
||||
- **App-übergreifend:** Daten sind in allen Apps verfügbar
|
||||
- **API-basiert:** Zugriff erfolgt über REST-APIs
|
||||
|
||||
### Soft References
|
||||
|
||||
Da die Apps ihre eigenen Datenbanken haben, können keine Foreign Keys zu mana-core-auth erstellt werden:
|
||||
|
||||
```
|
||||
Todo-DB mana-core-auth-DB
|
||||
┌─────────────────┐ ┌─────────────────┐
|
||||
│ task_to_tags │ │ tags │
|
||||
│ │ │ │
|
||||
│ tag_id ─ ─ ─ ─ ─│─ ─ ─ ─ ▶│ id │
|
||||
│ (keine FK) │ │ │
|
||||
└─────────────────┘ └─────────────────┘
|
||||
```
|
||||
|
||||
**Konsequenz:** Apps müssen ungültige IDs beim Laden ignorieren.
|
||||
|
||||
### Shared Packages
|
||||
|
||||
Für jeden zentralen Service gibt es ein entsprechendes Client-Package:
|
||||
|
||||
| Service | Package |
|
||||
|---------|---------|
|
||||
| Tags | `@manacore/shared-tags` |
|
||||
|
||||
Diese Packages enthalten:
|
||||
- TypeScript Types
|
||||
- API Client Klasse
|
||||
- Helper-Funktionen
|
||||
|
||||
## Lokale Entwicklung
|
||||
|
||||
### Alle zentralen Services starten
|
||||
|
||||
```bash
|
||||
# Infrastruktur
|
||||
pnpm docker:up
|
||||
|
||||
# Auth-Service (enthält alle zentralen APIs)
|
||||
pnpm dev:auth
|
||||
```
|
||||
|
||||
### Datenbank-Schema pushen
|
||||
|
||||
```bash
|
||||
cd services/mana-core-auth
|
||||
pnpm db:push
|
||||
```
|
||||
|
||||
## Shared Packages
|
||||
|
||||
| Package | Beschreibung |
|
||||
|---------|--------------|
|
||||
| `@manacore/shared-tags` | Tags Client für zentrale Tags API |
|
||||
| `@manacore/shared-theme` | Theme Store, A11y Store, User Settings |
|
||||
| `@manacore/shared-theme-ui` | Svelte UI-Komponenten für Theming |
|
||||
| `@manacore/shared-help-types` | TypeScript-Typen für Help-Inhalte |
|
||||
| `@manacore/shared-help-content` | Content-Loader, Parser, Merger, Suche |
|
||||
| `@manacore/shared-help-ui` | Svelte UI-Komponenten für Hilfeseite |
|
||||
| `@manacore/shared-help-mobile` | React Native Komponenten für Hilfe |
|
||||
|
||||
## Hinzufügen neuer zentraler Services
|
||||
|
||||
1. **Schema erstellen:** `services/mana-core-auth/src/db/schema/<name>.schema.ts`
|
||||
2. **Module erstellen:** `services/mana-core-auth/src/<name>/`
|
||||
3. **In app.module.ts registrieren**
|
||||
4. **Shared Package erstellen:** `packages/shared-<name>/`
|
||||
5. **Dokumentation schreiben:** `docs/central-services/<NAME>.md`
|
||||
248
docs/central-services/TAGS.md
Normal file
248
docs/central-services/TAGS.md
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
# Central Tags API
|
||||
|
||||
Das zentrale Tags-System ermöglicht einheitliche Tags/Labels über alle Manacore-Apps hinweg. Ein Tag, der in Todo erstellt wird, ist automatisch auch in Calendar und Contacts verfügbar.
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ mana-core-auth │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ tags Tabelle (zentral) │ │
|
||||
│ │ - id, userId, name, color, icon, createdAt │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ GET /api/v1/tags │ POST/PUT/DELETE │
|
||||
└────────────────────────────┼────────────────────────────────┘
|
||||
│
|
||||
┌────────────────────┼────────────────────┐
|
||||
│ │ │
|
||||
┌────▼────┐ ┌─────▼────┐ ┌─────▼────┐
|
||||
│ Todo │ │ Calendar │ │ Contacts │
|
||||
│ │ │ │ │ │
|
||||
│ task_ │ │ event_ │ │ contact_ │
|
||||
│ to_tags │ │ to_tags │ │ to_tags │
|
||||
│ (tagId) │ │ (tagId) │ │ (tagId) │
|
||||
└─────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
Alle Endpoints sind unter `http://localhost:3001/api/v1/tags` verfügbar und erfordern einen Bearer Token.
|
||||
|
||||
| Methode | Endpoint | Beschreibung |
|
||||
|---------|----------|--------------|
|
||||
| `GET` | `/tags` | Alle Tags des Users abrufen |
|
||||
| `GET` | `/tags/:id` | Einzelnes Tag abrufen |
|
||||
| `GET` | `/tags/by-ids?ids=id1,id2` | Mehrere Tags per ID abrufen |
|
||||
| `POST` | `/tags` | Neues Tag erstellen |
|
||||
| `POST` | `/tags/defaults` | Default-Tags erstellen |
|
||||
| `PUT` | `/tags/:id` | Tag aktualisieren |
|
||||
| `DELETE` | `/tags/:id` | Tag löschen |
|
||||
|
||||
## Tag-Objekt
|
||||
|
||||
```typescript
|
||||
interface Tag {
|
||||
id: string; // UUID
|
||||
userId: string; // User-ID (aus JWT)
|
||||
name: string; // Tag-Name (max 100 Zeichen)
|
||||
color: string; // Hex-Farbe (#3B82F6)
|
||||
icon?: string | null; // Optionales Phosphor-Icon
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
```
|
||||
|
||||
## Default-Tags
|
||||
|
||||
Beim Aufruf von `POST /tags/defaults` werden folgende Standard-Tags erstellt:
|
||||
|
||||
| Name | Farbe | Hex |
|
||||
|------|-------|-----|
|
||||
| Arbeit | Blau | `#3B82F6` |
|
||||
| Persönlich | Grün | `#10B981` |
|
||||
| Familie | Pink | `#EC4899` |
|
||||
| Wichtig | Rot | `#EF4444` |
|
||||
|
||||
## Client-Nutzung
|
||||
|
||||
### Shared Package
|
||||
|
||||
Das `@manacore/shared-tags` Package stellt einen Client bereit:
|
||||
|
||||
```typescript
|
||||
import { createTagsClient } from '@manacore/shared-tags';
|
||||
|
||||
const tagsClient = createTagsClient({
|
||||
authUrl: 'http://localhost:3001',
|
||||
getToken: async () => authStore.getAccessToken(),
|
||||
});
|
||||
|
||||
// Alle Tags laden
|
||||
const tags = await tagsClient.getAll();
|
||||
|
||||
// Tag erstellen
|
||||
const newTag = await tagsClient.create({
|
||||
name: 'Meeting',
|
||||
color: '#8B5CF6',
|
||||
});
|
||||
|
||||
// Tags per IDs laden
|
||||
const selectedTags = await tagsClient.getByIds(['id1', 'id2']);
|
||||
|
||||
// Tag aktualisieren
|
||||
await tagsClient.update('tag-id', { color: '#22C55E' });
|
||||
|
||||
// Tag löschen
|
||||
await tagsClient.delete('tag-id');
|
||||
|
||||
// Default-Tags erstellen
|
||||
await tagsClient.createDefaults();
|
||||
```
|
||||
|
||||
### In App-Stores
|
||||
|
||||
Die Apps nutzen den Client in ihren Stores:
|
||||
|
||||
**Todo (labels.svelte.ts):**
|
||||
```typescript
|
||||
import { createTagsClient, type Tag } from '@manacore/shared-tags';
|
||||
|
||||
// Label = Tag (Alias für Abwärtskompatibilität)
|
||||
export type Label = Tag;
|
||||
|
||||
// Client lazy initialisieren
|
||||
const client = createTagsClient({ ... });
|
||||
|
||||
export const labelsStore = {
|
||||
async fetchLabels() {
|
||||
const labels = await client.getAll();
|
||||
// ...
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**Calendar (event-tags.ts):**
|
||||
```typescript
|
||||
export type EventTag = Tag;
|
||||
```
|
||||
|
||||
**Contacts (contacts.ts):**
|
||||
```typescript
|
||||
export type ContactTag = Tag;
|
||||
```
|
||||
|
||||
## Junction Tables
|
||||
|
||||
Jede App behält ihre eigene Junction-Table für die Zuordnung:
|
||||
|
||||
### Todo: `task_to_tags`
|
||||
```sql
|
||||
CREATE TABLE task_to_tags (
|
||||
task_id UUID REFERENCES tasks(id) ON DELETE CASCADE,
|
||||
tag_id UUID NOT NULL, -- Soft reference zu mana-core-auth.tags
|
||||
PRIMARY KEY (task_id, tag_id)
|
||||
);
|
||||
```
|
||||
|
||||
### Calendar: `event_to_tags`
|
||||
```sql
|
||||
CREATE TABLE event_to_tags (
|
||||
event_id UUID REFERENCES events(id) ON DELETE CASCADE,
|
||||
tag_id UUID NOT NULL, -- Soft reference zu mana-core-auth.tags
|
||||
PRIMARY KEY (event_id, tag_id)
|
||||
);
|
||||
```
|
||||
|
||||
### Contacts: `contact_to_tags`
|
||||
```sql
|
||||
CREATE TABLE contact_to_tags (
|
||||
contact_id UUID REFERENCES contacts(id) ON DELETE CASCADE,
|
||||
tag_id UUID NOT NULL, -- Soft reference zu mana-core-auth.tags
|
||||
PRIMARY KEY (contact_id, tag_id)
|
||||
);
|
||||
```
|
||||
|
||||
**Hinweis:** Da die Tags in einer anderen Datenbank liegen, sind keine Foreign Key Constraints möglich. Die Apps validieren Tag-IDs beim Laden und ignorieren ungültige IDs.
|
||||
|
||||
## Entwicklung & Testing
|
||||
|
||||
### Alle drei Apps gleichzeitig starten
|
||||
|
||||
```bash
|
||||
pnpm dev:tags-test
|
||||
```
|
||||
|
||||
Dieser Befehl:
|
||||
1. Richtet alle Datenbanken ein (todo, calendar, contacts, auth)
|
||||
2. Startet alle Services mit farbcodierten Logs
|
||||
|
||||
### Manuelles API-Testing
|
||||
|
||||
```bash
|
||||
# Token holen
|
||||
TOKEN=$(curl -s -X POST http://localhost:3001/api/v1/auth/login \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"email": "test@example.com", "password": "password"}' | jq -r '.accessToken')
|
||||
|
||||
# Tags abrufen
|
||||
curl http://localhost:3001/api/v1/tags \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
|
||||
# Tag erstellen
|
||||
curl -X POST http://localhost:3001/api/v1/tags \
|
||||
-H "Authorization: Bearer $TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "Projekt X", "color": "#F59E0B"}'
|
||||
|
||||
# Default-Tags erstellen
|
||||
curl -X POST http://localhost:3001/api/v1/tags/defaults \
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
```
|
||||
|
||||
## Dateien
|
||||
|
||||
### Backend (mana-core-auth)
|
||||
|
||||
| Datei | Beschreibung |
|
||||
|-------|--------------|
|
||||
| `src/db/schema/tags.schema.ts` | Drizzle-Schema für tags-Tabelle |
|
||||
| `src/tags/tags.module.ts` | NestJS Module |
|
||||
| `src/tags/tags.controller.ts` | REST Controller |
|
||||
| `src/tags/tags.service.ts` | Business Logic |
|
||||
| `src/tags/dto/create-tag.dto.ts` | DTO für Tag-Erstellung |
|
||||
| `src/tags/dto/update-tag.dto.ts` | DTO für Tag-Update |
|
||||
|
||||
### Shared Package
|
||||
|
||||
| Datei | Beschreibung |
|
||||
|-------|--------------|
|
||||
| `packages/shared-tags/src/types.ts` | TypeScript Interfaces |
|
||||
| `packages/shared-tags/src/client.ts` | TagsClient Klasse |
|
||||
| `packages/shared-tags/src/index.ts` | Exports |
|
||||
|
||||
### Frontend-Integrationen
|
||||
|
||||
| App | API-Client | Store |
|
||||
|-----|------------|-------|
|
||||
| Todo | `src/lib/api/labels.ts` | `src/lib/stores/labels.svelte.ts` |
|
||||
| Calendar | `src/lib/api/event-tags.ts` | `src/lib/stores/event-tags.svelte.ts` |
|
||||
| Contacts | `src/lib/api/contacts.ts` (tagsApi) | - |
|
||||
|
||||
## Migration von lokalen Tags
|
||||
|
||||
Wenn eine App vorher eigene Tags hatte:
|
||||
|
||||
1. **Daten exportieren:** Bestehende Tags aus der lokalen Tabelle exportieren
|
||||
2. **Tags erstellen:** Per API in mana-core-auth erstellen
|
||||
3. **IDs mappen:** Alte Tag-IDs auf neue IDs mappen
|
||||
4. **Junction Tables aktualisieren:** Tag-IDs in Junction-Tables ersetzen
|
||||
5. **Lokale Tabelle löschen:** Alte Tags-Tabelle entfernen
|
||||
|
||||
## Vorteile
|
||||
|
||||
- **Konsistenz:** Ein Tag "Arbeit" ist überall gleich
|
||||
- **Einheitliche Farben:** Tags sehen in allen Apps identisch aus
|
||||
- **Weniger Duplikation:** Code und Daten werden geteilt
|
||||
- **Cross-App Features:** Möglich (z.B. "Zeige alles mit Tag X")
|
||||
497
docs/central-services/THEMING.md
Normal file
497
docs/central-services/THEMING.md
Normal file
|
|
@ -0,0 +1,497 @@
|
|||
# Central Theming System
|
||||
|
||||
Das zentrale Theming-System ermöglicht einheitliches Aussehen und Benutzereinstellungen über alle Manacore-Apps hinweg. Es besteht aus mehreren Schichten: Theme-Varianten, Light/Dark-Modus, Accessibility-Einstellungen und Custom Themes.
|
||||
|
||||
## Architektur
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ mana-core-auth │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ user_settings (JSON-Feld) │ │
|
||||
│ │ - theme: { mode, colorScheme, pinnedThemes } │ │
|
||||
│ │ - nav: { desktopPosition, sidebarCollapsed } │ │
|
||||
│ │ - locale: "de" │ │
|
||||
│ │ - general: { startPages, sounds, etc. } │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ custom_themes Tabelle (Community Themes) │ │
|
||||
│ │ - lightColors, darkColors, author, downloads, etc. │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
┌────────────────────┼────────────────────┐
|
||||
│ │ │
|
||||
┌────▼────┐ ┌─────▼────┐ ┌─────▼────┐
|
||||
│ Todo │ │ Calendar │ │ Contacts │
|
||||
│ │ │ │ │ │
|
||||
│ shared- │ │ shared- │ │ shared- │
|
||||
│ theme │ │ theme │ │ theme │
|
||||
└─────────┘ └──────────┘ └──────────┘
|
||||
```
|
||||
|
||||
## Packages
|
||||
|
||||
| Package | Beschreibung |
|
||||
|---------|--------------|
|
||||
| `@manacore/shared-theme` | Theme Store, Types, Utilities, Konstanten |
|
||||
| `@manacore/shared-theme-ui` | Svelte UI-Komponenten (ThemeSelector, ThemePage, etc.) |
|
||||
|
||||
## Theme-Varianten
|
||||
|
||||
Es gibt 8 vordefinierte Theme-Varianten:
|
||||
|
||||
### Standard-Varianten (PillNav)
|
||||
| Name | Farbe | Icon | Hue |
|
||||
|------|-------|------|-----|
|
||||
| `lume` | Gold ✨ | sparkle | 47 |
|
||||
| `nature` | Grün 🌿 | leaf | 122 |
|
||||
| `stone` | Blau-Grau 🪨 | hexagon | 200 |
|
||||
| `ocean` | Blau 🌊 | waves | 199 |
|
||||
|
||||
### Erweiterte Varianten (Themes-Seite)
|
||||
| Name | Farbe | Icon | Hue |
|
||||
|------|-------|------|-----|
|
||||
| `sunset` | Coral/Orange 🌅 | sun | 15 |
|
||||
| `midnight` | Violett 🌙 | moon | 260 |
|
||||
| `rose` | Pink 🌹 | flower | 340 |
|
||||
| `lavender` | Lavendel 💜 | sparkle | 270 |
|
||||
|
||||
## Theme-Modus
|
||||
|
||||
```typescript
|
||||
type ThemeMode = 'light' | 'dark' | 'system';
|
||||
```
|
||||
|
||||
- **light**: Heller Modus
|
||||
- **dark**: Dunkler Modus
|
||||
- **system**: Folgt der System-Einstellung
|
||||
|
||||
## Color Tokens
|
||||
|
||||
Jede Theme-Variante definiert diese HSL-Farbwerte für Light und Dark:
|
||||
|
||||
```typescript
|
||||
interface ThemeColors {
|
||||
primary: string; // Hauptfarbe
|
||||
primaryForeground: string; // Text auf Primary
|
||||
secondary: string; // Sekundärfarbe
|
||||
secondaryForeground: string;
|
||||
background: string; // Seitenhintergrund
|
||||
foreground: string; // Haupttext
|
||||
surface: string; // Karten-Hintergrund
|
||||
surfaceHover: string; // Hover-Zustand
|
||||
surfaceElevated: string; // Modals, Dropdowns
|
||||
muted: string; // Deaktivierte Elemente
|
||||
mutedForeground: string;
|
||||
border: string; // Rahmen
|
||||
borderStrong: string; // Starke Rahmen
|
||||
error: string; // Fehler-Rot
|
||||
success: string; // Erfolg-Grün
|
||||
warning: string; // Warnung-Orange
|
||||
input: string; // Input-Hintergrund
|
||||
ring: string; // Focus-Ring
|
||||
}
|
||||
```
|
||||
|
||||
### HSL-Format
|
||||
|
||||
Farben werden als HSL-Strings ohne `hsl()` Wrapper gespeichert:
|
||||
|
||||
```typescript
|
||||
// Format: "H S% L%"
|
||||
const gold = '47 95% 58%';
|
||||
const darkBlue = '199 100% 18%';
|
||||
|
||||
// CSS-Verwendung
|
||||
--color-primary: 47 95% 58%;
|
||||
background-color: hsl(var(--color-primary));
|
||||
```
|
||||
|
||||
## Store-Nutzung
|
||||
|
||||
### Theme Store
|
||||
|
||||
```typescript
|
||||
import { createThemeStore } from '@manacore/shared-theme';
|
||||
|
||||
// Store erstellen
|
||||
export const theme = createThemeStore({
|
||||
appId: 'calendar',
|
||||
defaultMode: 'system',
|
||||
defaultVariant: 'ocean',
|
||||
});
|
||||
|
||||
// In Komponente initialisieren
|
||||
onMount(() => {
|
||||
const cleanup = theme.initialize();
|
||||
return cleanup;
|
||||
});
|
||||
|
||||
// Zugriff auf State
|
||||
theme.mode // 'light' | 'dark' | 'system'
|
||||
theme.variant // 'ocean' | 'nature' | ...
|
||||
theme.effectiveMode // 'light' | 'dark' (aufgelöst)
|
||||
theme.isDark // boolean
|
||||
|
||||
// Aktionen
|
||||
theme.setMode('dark');
|
||||
theme.setVariant('nature');
|
||||
theme.toggleMode(); // Light ↔ Dark
|
||||
theme.cycleMode(); // Light → Dark → System → Light
|
||||
```
|
||||
|
||||
### App-spezifische Primary Color
|
||||
|
||||
```typescript
|
||||
export const theme = createThemeStore({
|
||||
appId: 'memoro',
|
||||
primaryColor: {
|
||||
light: '47 95% 58%', // Gold
|
||||
dark: '47 95% 58%',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### User Settings Store (Server-Sync)
|
||||
|
||||
```typescript
|
||||
import { createUserSettingsStore } from '@manacore/shared-theme';
|
||||
|
||||
export const userSettings = createUserSettingsStore({
|
||||
appId: 'calendar',
|
||||
authUrl: 'http://localhost:3001',
|
||||
getAccessToken: () => authStore.getAccessToken(),
|
||||
});
|
||||
|
||||
// Laden
|
||||
await userSettings.load();
|
||||
|
||||
// Zugriff
|
||||
userSettings.theme.mode // Theme-Modus
|
||||
userSettings.theme.colorScheme // Variante
|
||||
userSettings.nav.desktopPosition // 'top' | 'bottom'
|
||||
userSettings.locale // 'de'
|
||||
userSettings.general.soundsEnabled
|
||||
|
||||
// Aktualisieren (speichert auf Server)
|
||||
await userSettings.updateGlobal({
|
||||
theme: { mode: 'dark' }
|
||||
});
|
||||
|
||||
// App-spezifische Überschreibung
|
||||
await userSettings.updateAppOverride({
|
||||
theme: { colorScheme: 'nature' }
|
||||
});
|
||||
```
|
||||
|
||||
## Accessibility (A11y)
|
||||
|
||||
### A11y Store
|
||||
|
||||
```typescript
|
||||
import { createA11yStore } from '@manacore/shared-theme';
|
||||
|
||||
export const a11y = createA11yStore({ appId: 'calendar' });
|
||||
|
||||
// State
|
||||
a11y.contrast // 'normal' | 'high'
|
||||
a11y.colorblind // 'none' | 'deuteranopia' | 'protanopia' | 'monochrome'
|
||||
a11y.reduceMotion // boolean
|
||||
|
||||
// Aktionen
|
||||
a11y.setContrast('high');
|
||||
a11y.setColorblind('deuteranopia');
|
||||
a11y.setReduceMotion(true);
|
||||
a11y.resetAll();
|
||||
```
|
||||
|
||||
### A11y-Optionen
|
||||
|
||||
**Kontrast:**
|
||||
- `normal`: Standard (WCAG AA 4.5:1)
|
||||
- `high`: Erhöhter Kontrast (WCAG AAA 7:1)
|
||||
|
||||
**Farbenblindheit:**
|
||||
- `none`: Keine Anpassung
|
||||
- `deuteranopia`: Grün-Blindheit (~6% der Männer)
|
||||
- `protanopia`: Rot-Blindheit (~1% der Männer)
|
||||
- `monochrome`: Graustufen
|
||||
|
||||
**Reduzierte Bewegung:**
|
||||
- Respektiert `prefers-reduced-motion`
|
||||
- Kann manuell überschrieben werden
|
||||
|
||||
## Custom Themes
|
||||
|
||||
### Custom Themes Store
|
||||
|
||||
```typescript
|
||||
import { createCustomThemesStore } from '@manacore/shared-theme';
|
||||
|
||||
export const customThemes = createCustomThemesStore({
|
||||
authUrl: 'http://localhost:3001',
|
||||
getAccessToken: () => authStore.getAccessToken(),
|
||||
});
|
||||
|
||||
// Eigene Themes laden
|
||||
await customThemes.loadCustomThemes();
|
||||
|
||||
// Theme erstellen
|
||||
const newTheme = await customThemes.createTheme({
|
||||
name: 'Mein Theme',
|
||||
emoji: '🎨',
|
||||
lightColors: { primary: '200 80% 50%', ... },
|
||||
darkColors: { primary: '200 70% 60%', ... },
|
||||
});
|
||||
|
||||
// Community Themes durchsuchen
|
||||
await customThemes.browseCommunity({
|
||||
sort: 'popular',
|
||||
search: 'dark',
|
||||
});
|
||||
|
||||
// Theme herunterladen
|
||||
await customThemes.downloadTheme(themeId);
|
||||
|
||||
// Theme anwenden
|
||||
customThemes.applyCustomTheme(theme);
|
||||
```
|
||||
|
||||
### Theme Editor
|
||||
|
||||
Der Theme Editor erlaubt das visuelle Erstellen von Themes:
|
||||
|
||||
**Hauptfarben (immer sichtbar):**
|
||||
- Primary, Background, Surface, Foreground
|
||||
- Error, Success, Warning
|
||||
|
||||
**Erweiterte Farben (zugeklappt):**
|
||||
- PrimaryForeground, Secondary, SecondaryForeground
|
||||
- SurfaceHover, SurfaceElevated, Muted, MutedForeground
|
||||
- Border, BorderStrong, Input, Ring
|
||||
|
||||
## UI-Komponenten
|
||||
|
||||
### ThemePage
|
||||
|
||||
Vollständige Themes-Seite mit allen Optionen:
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { ThemePage } from '@manacore/shared-theme-ui';
|
||||
import { theme, a11y } from '$lib/stores';
|
||||
</script>
|
||||
|
||||
<ThemePage
|
||||
themeStore={theme}
|
||||
a11yStore={a11y}
|
||||
showAccessibility={true}
|
||||
showPinnedThemes={true}
|
||||
/>
|
||||
```
|
||||
|
||||
### ThemeSelector
|
||||
|
||||
Dropdown zur Theme-Auswahl:
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { ThemeSelector } from '@manacore/shared-theme-ui';
|
||||
import { theme } from '$lib/stores';
|
||||
</script>
|
||||
|
||||
<ThemeSelector themeStore={theme} />
|
||||
```
|
||||
|
||||
### ThemeModeSelector
|
||||
|
||||
Umschalter für Light/Dark/System:
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { ThemeModeSelector } from '@manacore/shared-theme-ui';
|
||||
import { theme } from '$lib/stores';
|
||||
</script>
|
||||
|
||||
<ThemeModeSelector themeStore={theme} />
|
||||
```
|
||||
|
||||
### ThemeToggle
|
||||
|
||||
Einfacher Dark/Light Toggle:
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { ThemeToggle } from '@manacore/shared-theme-ui';
|
||||
import { theme } from '$lib/stores';
|
||||
</script>
|
||||
|
||||
<ThemeToggle themeStore={theme} />
|
||||
```
|
||||
|
||||
### A11ySettings
|
||||
|
||||
Vollständige Accessibility-Einstellungen:
|
||||
|
||||
```svelte
|
||||
<script>
|
||||
import { A11ySettings } from '@manacore/shared-theme-ui';
|
||||
import { a11y } from '$lib/stores';
|
||||
</script>
|
||||
|
||||
<A11ySettings store={a11y} />
|
||||
```
|
||||
|
||||
## CSS-Integration
|
||||
|
||||
### Tailwind CSS
|
||||
|
||||
Die Themes werden als CSS-Variablen angewendet:
|
||||
|
||||
```css
|
||||
:root {
|
||||
--color-primary: 199 98% 45%;
|
||||
--color-background: 199 100% 97%;
|
||||
--color-foreground: 199 100% 18%;
|
||||
/* ... weitere Tokens */
|
||||
}
|
||||
|
||||
.dark {
|
||||
--color-primary: 199 98% 48%;
|
||||
--color-background: 200 25% 7%;
|
||||
--color-foreground: 0 0% 100%;
|
||||
}
|
||||
```
|
||||
|
||||
In Tailwind:
|
||||
|
||||
```html
|
||||
<div class="bg-background text-foreground">
|
||||
<button class="bg-primary text-primary-foreground">
|
||||
Klick mich
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### tailwind.config.js
|
||||
|
||||
```javascript
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
background: 'hsl(var(--color-background))',
|
||||
foreground: 'hsl(var(--color-foreground))',
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--color-primary))',
|
||||
foreground: 'hsl(var(--color-primary-foreground))',
|
||||
},
|
||||
// ... weitere
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dateien
|
||||
|
||||
### @manacore/shared-theme
|
||||
|
||||
| Datei | Beschreibung |
|
||||
|-------|--------------|
|
||||
| `src/types.ts` | Alle TypeScript Interfaces |
|
||||
| `src/constants.ts` | Theme-Definitionen (8 Varianten) |
|
||||
| `src/store.svelte.ts` | Theme Store Factory |
|
||||
| `src/a11y-store.svelte.ts` | Accessibility Store |
|
||||
| `src/a11y-constants.ts` | A11y Konstanten |
|
||||
| `src/a11y-utils.ts` | A11y Helper |
|
||||
| `src/user-settings-store.svelte.ts` | Server-Sync Store |
|
||||
| `src/custom-themes-store.svelte.ts` | Custom Themes Store |
|
||||
| `src/utils.ts` | Theme Utilities |
|
||||
| `src/app-routes.ts` | Start-Page Konfiguration |
|
||||
|
||||
### @manacore/shared-theme-ui
|
||||
|
||||
| Datei | Beschreibung |
|
||||
|-------|--------------|
|
||||
| `src/ThemeSelector.svelte` | Theme-Dropdown |
|
||||
| `src/ThemeModeSelector.svelte` | Light/Dark/System Selector |
|
||||
| `src/ThemeToggle.svelte` | Einfacher Toggle |
|
||||
| `src/components/ThemeCard.svelte` | Theme-Vorschau Karte |
|
||||
| `src/components/ThemeGrid.svelte` | Grid von Theme-Karten |
|
||||
| `src/components/A11ySettings.svelte` | A11y Einstellungen |
|
||||
| `src/components/editor/` | Theme Editor Komponenten |
|
||||
| `src/components/community/` | Community Themes Komponenten |
|
||||
| `src/pages/ThemePage.svelte` | Vollständige Themes-Seite |
|
||||
| `src/pages/ThemeEditorPage.svelte` | Theme Editor Seite |
|
||||
| `src/pages/CommunityThemesPage.svelte` | Community Themes |
|
||||
|
||||
## Integration in eine App
|
||||
|
||||
### 1. Dependencies installieren
|
||||
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"@manacore/shared-theme": "workspace:*",
|
||||
"@manacore/shared-theme-ui": "workspace:*"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Store erstellen
|
||||
|
||||
```typescript
|
||||
// src/lib/stores/theme.ts
|
||||
import { createThemeStore, createA11yStore } from '@manacore/shared-theme';
|
||||
|
||||
export const theme = createThemeStore({
|
||||
appId: 'myapp',
|
||||
defaultVariant: 'ocean',
|
||||
});
|
||||
|
||||
export const a11y = createA11yStore({
|
||||
appId: 'myapp',
|
||||
});
|
||||
```
|
||||
|
||||
### 3. Im Root-Layout initialisieren
|
||||
|
||||
```svelte
|
||||
<!-- src/routes/+layout.svelte -->
|
||||
<script>
|
||||
import { onMount } from 'svelte';
|
||||
import { theme, a11y } from '$lib/stores/theme';
|
||||
|
||||
onMount(() => {
|
||||
const cleanupTheme = theme.initialize();
|
||||
const cleanupA11y = a11y.initialize();
|
||||
return () => {
|
||||
cleanupTheme();
|
||||
cleanupA11y();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
```
|
||||
|
||||
### 4. Themes-Seite hinzufügen
|
||||
|
||||
```svelte
|
||||
<!-- src/routes/(app)/themes/+page.svelte -->
|
||||
<script>
|
||||
import { ThemePage } from '@manacore/shared-theme-ui';
|
||||
import { theme, a11y } from '$lib/stores/theme';
|
||||
</script>
|
||||
|
||||
<ThemePage themeStore={theme} a11yStore={a11y} />
|
||||
```
|
||||
|
||||
## Vorteile
|
||||
|
||||
- **Konsistenz:** Alle Apps sehen einheitlich aus
|
||||
- **User Experience:** Theme-Einstellungen werden gespeichert
|
||||
- **Accessibility:** Barrierefreiheit ist eingebaut
|
||||
- **Anpassbarkeit:** Nutzer können eigene Themes erstellen
|
||||
- **Community:** Themes können geteilt werden
|
||||
Loading…
Add table
Add a link
Reference in a new issue