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 <noreply@anthropic.com>
This commit is contained in:
Luca Oelfke 2026-02-12 11:24:00 +01:00
parent 4b8f1fc814
commit 6c7d239108

209
src/management/favorites.ts Normal file
View file

@ -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<string, Favorite>();
private categories = new Map<string, FavoriteCategory>();
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<Favorite>): 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(); }
}