Files
hikari-desktop/src/lib/stores/snippets.ts
T
hikari 4c46d4c8fd
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 54s
CI / Lint & Test (push) Successful in 14m42s
CI / Build Linux (push) Successful in 19m4s
CI / Build Windows (cross-compile) (push) Successful in 28m37s
feat: add multiple productivity features and UI enhancements (#68)
## Summary

This PR adds a collection of productivity features and UI enhancements to improve the Hikari Desktop experience:

### New Features
- **Clipboard History** (#25) - Track and manage copied code snippets with language detection, search, filtering, and pinning
- **Quick Actions Panel** (#15) - Buttons for common quick actions like "Review PR", "Run tests", "Explain file", with customizable actions
- **Git Integration Panel** (#24) - View current branch, changed/staged files, quick git actions (commit, push, pull), and branch management
- **Session Import/Export** (#8) - Export conversations to JSON and import previously saved sessions
- **Snippet Library** (#22) - Save and reuse common prompts with categories and quick insert
- **Session History** (#14) - Auto-save conversations with browsable history and search
- **High Contrast Mode** (#20) - Accessibility theme with improved visibility
- **Minimize to System Tray** (#11) - System tray support with right-click menu

### UI Enhancements
- Trans-pride gradient theme applied across UI elements
- Copy button added to code blocks
- Linter formatting and eslint-disable comments for cleaner code

## Closes

Closes #8
Closes #11
Closes #14
Closes #15
Closes #20
Closes #22
Closes #24
Closes #25
Closes #34
Closes #35
Closes #36
Closes #37
Closes #69
Closes #70

## Test Plan

- [ ] Verify clipboard history captures code from code block copy buttons
- [ ] Verify clipboard history captures manually selected text from terminal
- [ ] Test snippet library CRUD operations and insertion
- [ ] Test quick actions panel with default and custom actions
- [ ] Test git panel shows correct status, branch, and performs git operations
- [ ] Test session history auto-save and restore
- [ ] Test session import/export roundtrip
- [ ] Verify high contrast mode provides adequate contrast
- [ ] Test minimize to tray functionality and tray menu
- [ ] Verify trans-pride gradient theme displays correctly in all themes

---
* This PR was created with help from Hikari~ 🌸*

Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Reviewed-on: #68
Co-authored-by: Hikari <hikari@nhcarrigan.com>
Co-committed-by: Hikari <hikari@nhcarrigan.com>
2026-01-25 22:19:00 -08:00

139 lines
3.5 KiB
TypeScript

import { writable, derived } from "svelte/store";
import { invoke } from "@tauri-apps/api/core";
export interface Snippet {
id: string;
name: string;
content: string;
category: string;
is_default: boolean;
created_at: string;
updated_at: string;
}
function createSnippetsStore() {
const snippets = writable<Snippet[]>([]);
const categories = writable<string[]>([]);
const isLoading = writable(false);
const selectedCategory = writable<string | null>(null);
const filteredSnippets = derived(
[snippets, selectedCategory],
([$snippets, $selectedCategory]) => {
if (!$selectedCategory) {
return $snippets;
}
return $snippets.filter((s) => s.category === $selectedCategory);
}
);
async function loadSnippets(): Promise<void> {
isLoading.set(true);
try {
const [snippetList, categoryList] = await Promise.all([
invoke<Snippet[]>("list_snippets"),
invoke<string[]>("get_snippet_categories"),
]);
snippets.set(snippetList);
categories.set(categoryList);
} catch (error) {
console.error("Failed to load snippets:", error);
} finally {
isLoading.set(false);
}
}
async function saveSnippet(snippet: Snippet): Promise<boolean> {
try {
await invoke("save_snippet", { snippet });
await loadSnippets();
return true;
} catch (error) {
console.error("Failed to save snippet:", error);
return false;
}
}
async function createSnippet(name: string, content: string, category: string): Promise<boolean> {
const now = new Date().toISOString();
const snippet: Snippet = {
id: `custom-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
name,
content,
category,
is_default: false,
created_at: now,
updated_at: now,
};
return saveSnippet(snippet);
}
async function updateSnippet(
id: string,
name: string,
content: string,
category: string
): Promise<boolean> {
const currentSnippets = await invoke<Snippet[]>("list_snippets");
const existing = currentSnippets.find((s) => s.id === id);
if (!existing) {
console.error("Snippet not found for update");
return false;
}
const updated: Snippet = {
...existing,
name,
content,
category,
updated_at: new Date().toISOString(),
};
return saveSnippet(updated);
}
async function deleteSnippet(snippetId: string): Promise<boolean> {
try {
await invoke("delete_snippet", { snippetId });
await loadSnippets();
return true;
} catch (error) {
console.error("Failed to delete snippet:", error);
return false;
}
}
async function resetDefaults(): Promise<boolean> {
try {
await invoke("reset_default_snippets");
await loadSnippets();
return true;
} catch (error) {
console.error("Failed to reset default snippets:", error);
return false;
}
}
function setSelectedCategory(category: string | null): void {
selectedCategory.set(category);
}
return {
snippets: { subscribe: snippets.subscribe },
categories: { subscribe: categories.subscribe },
filteredSnippets: { subscribe: filteredSnippets.subscribe },
isLoading: { subscribe: isLoading.subscribe },
selectedCategory: { subscribe: selectedCategory.subscribe },
loadSnippets,
saveSnippet,
createSnippet,
updateSnippet,
deleteSnippet,
resetDefaults,
setSelectedCategory,
};
}
export const snippetsStore = createSnippetsStore();