mirror of
https://github.com/KevinMidboe/seasoned.git
synced 2026-03-11 03:49:07 +00:00
Feat: Command palette (#105)
* Add command palette with smart navigation and usage tracking - Add CommandPalette.vue component with keyboard shortcut (Cmd+K/Ctrl+K) - Implement smart route navigation with parameter input support - Add content search integration via Elasticsearch - Create commandTracking.ts utility for usage analytics - Track command frequency and recency with scoring algorithm - Support for all application routes with metadata and icons - Includes badge system for auth requirements and shortcuts * Integrate CommandPalette into main application - Add CommandPalette component to App.vue - Enable global keyboard shortcut (Cmd+K/Ctrl+K) - Command palette is now accessible from anywhere in the app
This commit is contained in:
101
src/utils/commandTracking.ts
Normal file
101
src/utils/commandTracking.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
interface CommandData {
|
||||
count: number;
|
||||
lastUsed: string; // ISO timestamp
|
||||
routePath?: string;
|
||||
type: "route" | "content";
|
||||
}
|
||||
|
||||
interface CommandStats {
|
||||
commands: Record<string, CommandData>;
|
||||
version: number;
|
||||
}
|
||||
|
||||
const STORAGE_KEY = "commandPalette_stats";
|
||||
const CURRENT_VERSION = 1;
|
||||
|
||||
function getStats(): CommandStats {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (!stored) {
|
||||
return { commands: {}, version: CURRENT_VERSION };
|
||||
}
|
||||
const parsed = JSON.parse(stored) as CommandStats;
|
||||
return parsed;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to parse command stats:", error);
|
||||
return { commands: {}, version: CURRENT_VERSION };
|
||||
}
|
||||
}
|
||||
|
||||
function saveStats(stats: CommandStats): void {
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(stats));
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Failed to save command stats:", error);
|
||||
}
|
||||
}
|
||||
|
||||
export function trackCommand(
|
||||
id: string,
|
||||
type: "route" | "content",
|
||||
metadata?: { routePath?: string }
|
||||
): void {
|
||||
const stats = getStats();
|
||||
|
||||
if (!stats.commands[id]) {
|
||||
stats.commands[id] = {
|
||||
count: 0,
|
||||
lastUsed: new Date().toISOString(),
|
||||
type,
|
||||
routePath: metadata?.routePath
|
||||
};
|
||||
}
|
||||
|
||||
stats.commands[id].count += 1;
|
||||
stats.commands[id].lastUsed = new Date().toISOString();
|
||||
|
||||
if (metadata?.routePath) {
|
||||
stats.commands[id].routePath = metadata.routePath;
|
||||
}
|
||||
|
||||
saveStats(stats);
|
||||
}
|
||||
|
||||
export function getCommandScore(commandId: string): number {
|
||||
const stats = getStats();
|
||||
const command = stats.commands[commandId];
|
||||
|
||||
if (!command) return 0;
|
||||
|
||||
const now = new Date().getTime();
|
||||
const lastUsed = new Date(command.lastUsed).getTime();
|
||||
const daysSinceLastUse = (now - lastUsed) / (1000 * 60 * 60 * 24);
|
||||
|
||||
// Recency bonus: 10 points for today, decreasing to 0 after 10 days
|
||||
const recencyBonus = Math.max(0, 10 - daysSinceLastUse);
|
||||
|
||||
// Combined score: 70% frequency, 30% recency
|
||||
return command.count * 0.7 + recencyBonus * 0.3;
|
||||
}
|
||||
|
||||
export function getTopCommands(limit = 10): { id: string; score: number }[] {
|
||||
const stats = getStats();
|
||||
|
||||
const scored = Object.keys(stats.commands).map(id => ({
|
||||
id,
|
||||
score: getCommandScore(id)
|
||||
}));
|
||||
|
||||
return scored.sort((a, b) => b.score - a.score).slice(0, limit);
|
||||
}
|
||||
|
||||
export function clearCommandHistory(): void {
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}
|
||||
|
||||
export function getCommandStats(commandId: string): CommandData | null {
|
||||
const stats = getStats();
|
||||
return stats.commands[commandId] || null;
|
||||
}
|
||||
Reference in New Issue
Block a user