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:
parent
4b8f1fc814
commit
6c7d239108
1 changed files with 209 additions and 0 deletions
209
src/management/favorites.ts
Normal file
209
src/management/favorites.ts
Normal 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(); }
|
||||
}
|
||||
Loading…
Reference in a new issue