From 6c7d239108392d8ebc5cfd1e9b60d025cc6df699 Mon Sep 17 00:00:00 2001 From: tolvitty Date: Thu, 12 Feb 2026 11:24:00 +0100 Subject: [PATCH] Favoriten: gespeicherte Queries mit Kategorien und Tags FavoritesManager mit Kategorien (Allgemein, Analyse, Wartung), Tags, Usage-Tracking. SaveFavoriteModal fuer Obsidian-UI. Co-Authored-By: Claude Opus 4.6 --- src/management/favorites.ts | 209 ++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 src/management/favorites.ts diff --git a/src/management/favorites.ts b/src/management/favorites.ts new file mode 100644 index 0000000..09296a4 --- /dev/null +++ b/src/management/favorites.ts @@ -0,0 +1,209 @@ +import { Modal, Setting, Notice } from 'obsidian'; +import type LogfirePlugin from '../main'; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export interface Favorite { + id: string; + name: string; + sql: string; + category: string; + tags: string[]; + createdAt: string; + lastUsedAt?: string; + usageCount: number; +} + +export interface FavoriteCategory { + id: string; + name: string; + color: string; +} + +const STORAGE_KEY = 'logfire-favorites'; + +const DEFAULT_CATEGORIES: FavoriteCategory[] = [ + { id: 'allgemein', name: 'Allgemein', color: '#4C78A8' }, + { id: 'analyse', name: 'Analyse', color: '#F58518' }, + { id: 'wartung', name: 'Wartung', color: '#E45756' }, +]; + +// --------------------------------------------------------------------------- +// FavoritesManager +// --------------------------------------------------------------------------- + +export class FavoritesManager { + private favorites = new Map(); + private categories = new Map(); + private listeners = new Set<() => void>(); + + constructor() { + this.load(); + } + + private load(): void { + const raw = localStorage.getItem(STORAGE_KEY); + if (raw) { + try { + const data = JSON.parse(raw) as { favorites: Favorite[]; categories: FavoriteCategory[] }; + for (const f of data.favorites) this.favorites.set(f.id, f); + for (const c of data.categories) this.categories.set(c.id, c); + } catch { /* ignore */ } + } + if (this.categories.size === 0) { + for (const c of DEFAULT_CATEGORIES) this.categories.set(c.id, c); + this.save(); + } + } + + private save(): void { + localStorage.setItem(STORAGE_KEY, JSON.stringify({ + favorites: Array.from(this.favorites.values()), + categories: Array.from(this.categories.values()), + })); + for (const fn of this.listeners) fn(); + } + + onChange(fn: () => void): () => void { + this.listeners.add(fn); + return () => this.listeners.delete(fn); + } + + // --------------------------------------------------------------------------- + // Favorites CRUD + // --------------------------------------------------------------------------- + + add(name: string, sql: string, category = 'allgemein', tags: string[] = []): Favorite { + const fav: Favorite = { + id: `fav-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + name, + sql, + category, + tags, + createdAt: new Date().toISOString(), + usageCount: 0, + }; + this.favorites.set(fav.id, fav); + this.save(); + return fav; + } + + getAll(): Favorite[] { + return Array.from(this.favorites.values()).sort((a, b) => { + if (b.usageCount !== a.usageCount) return b.usageCount - a.usageCount; + return a.name.localeCompare(b.name); + }); + } + + getByCategory(categoryId: string): Favorite[] { + return this.getAll().filter(f => f.category === categoryId); + } + + search(term: string): Favorite[] { + const t = term.toLowerCase(); + return this.getAll().filter( + f => f.name.toLowerCase().includes(t) || f.sql.toLowerCase().includes(t) + || f.tags.some(tag => tag.toLowerCase().includes(t)), + ); + } + + incrementUsage(id: string): void { + const f = this.favorites.get(id); + if (f) { + f.usageCount++; + f.lastUsedAt = new Date().toISOString(); + this.save(); + } + } + + update(id: string, updates: Partial): void { + const f = this.favorites.get(id); + if (f) { Object.assign(f, updates); this.save(); } + } + + delete(id: string): void { + this.favorites.delete(id); + this.save(); + } + + // --------------------------------------------------------------------------- + // Categories + // --------------------------------------------------------------------------- + + getCategories(): FavoriteCategory[] { + return Array.from(this.categories.values()); + } + + addCategory(name: string, color: string): FavoriteCategory { + const id = name.toLowerCase().replace(/\s+/g, '-'); + const cat: FavoriteCategory = { id, name, color }; + this.categories.set(id, cat); + this.save(); + return cat; + } +} + +// --------------------------------------------------------------------------- +// SaveFavoriteModal +// --------------------------------------------------------------------------- + +export class SaveFavoriteModal extends Modal { + private plugin: LogfirePlugin; + private manager: FavoritesManager; + private sql: string; + private name = ''; + private category = 'allgemein'; + private tags: string[] = []; + + constructor(plugin: LogfirePlugin, manager: FavoritesManager, sql: string) { + super(plugin.app); + this.plugin = plugin; + this.manager = manager; + this.sql = sql; + } + + onOpen(): void { + const { contentEl } = this; + contentEl.addClass('logfire-save-fav-modal'); + contentEl.createEl('h3', { text: 'Als Favorit speichern' }); + + new Setting(contentEl).setName('Name').addText(text => + text.setPlaceholder('Meine Query').onChange(v => { this.name = v; }), + ); + + new Setting(contentEl).setName('Kategorie').addDropdown(dd => { + for (const cat of this.manager.getCategories()) dd.addOption(cat.id, cat.name); + dd.setValue(this.category); + dd.onChange(v => { this.category = v; }); + }); + + new Setting(contentEl).setName('Tags').setDesc('Komma-getrennt').addText(text => + text.setPlaceholder('select, events').onChange(v => { + this.tags = v.split(',').map(s => s.trim()).filter(Boolean); + }), + ); + + contentEl.createEl('h4', { text: 'SQL-Vorschau' }); + contentEl.createEl('pre', { + cls: 'logfire-sql-preview', + text: this.sql.length > 200 ? this.sql.slice(0, 200) + '...' : this.sql, + }); + + const btns = contentEl.createDiv({ cls: 'logfire-modal-buttons' }); + const saveBtn = btns.createEl('button', { text: 'Speichern', cls: 'mod-cta' }); + saveBtn.addEventListener('click', () => this.doSave()); + const cancelBtn = btns.createEl('button', { text: 'Abbrechen' }); + cancelBtn.addEventListener('click', () => this.close()); + } + + private doSave(): void { + if (!this.name.trim()) { new Notice('Bitte einen Namen eingeben.'); return; } + this.manager.add(this.name.trim(), this.sql, this.category, this.tags); + new Notice(`Favorit "${this.name}" gespeichert.`); + this.close(); + } + + onClose(): void { this.contentEl.empty(); } +}