generated from nhcarrigan/template
feat: add clipboard history for code snippets
Implements issue #25 - Clipboard History feature that tracks copied code snippets with language detection, search, and filtering. Backend (Rust): - New clipboard.rs module with persistent storage via tauri-plugin-store - Commands: capture, list, delete, toggle pin, clear, search, update language - Auto-deduplication and max history size (100 entries) - Pinned entries stay at top and persist through clear Frontend (Svelte/TypeScript): - Clipboard store with filtering, search, and language detection - ClipboardHistoryPanel component with search, language filter, pin/delete - Clipboard button added to InputBar next to Snippets/Actions - Auto-capture from code block copy buttons - Auto-capture from manual text selection in terminal - Insert snippets directly into input field Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
import { characterState } from "$lib/stores/character";
|
||||
import { handleNewUserMessage } from "$lib/notifications/rules";
|
||||
import { setSkipNextGreeting } from "$lib/tauri";
|
||||
import { clipboardStore } from "$lib/stores/clipboard";
|
||||
import {
|
||||
setShouldRestoreHistory,
|
||||
setSavedHistory,
|
||||
@@ -27,6 +28,7 @@
|
||||
import AttachmentPreview from "$lib/components/AttachmentPreview.svelte";
|
||||
import SnippetLibraryPanel from "$lib/components/SnippetLibraryPanel.svelte";
|
||||
import QuickActionsPanel from "$lib/components/QuickActionsPanel.svelte";
|
||||
import ClipboardHistoryPanel from "$lib/components/ClipboardHistoryPanel.svelte";
|
||||
import type { Attachment } from "$lib/types/messages";
|
||||
|
||||
const INPUT_HISTORY_KEY = "hikari-input-history";
|
||||
@@ -43,6 +45,7 @@
|
||||
let isDragging = $state(false);
|
||||
let showSnippetLibrary = $state(false);
|
||||
let showQuickActions = $state(false);
|
||||
let showClipboardHistory = $state(false);
|
||||
|
||||
// Input history state
|
||||
let inputHistory = $state<string[]>([]);
|
||||
@@ -504,6 +507,15 @@ User: ${formattedMessage}`;
|
||||
const items = event.clipboardData?.items;
|
||||
let handledFile = false;
|
||||
|
||||
// Also capture text content to clipboard history
|
||||
const textContent = event.clipboardData?.getData("text/plain");
|
||||
if (textContent && textContent.trim().length > 0) {
|
||||
// Only capture multi-line or longer text (likely code snippets)
|
||||
if (textContent.includes("\n") || textContent.length > 50) {
|
||||
clipboardStore.captureClipboard(textContent, null, "Pasted into chat");
|
||||
}
|
||||
}
|
||||
|
||||
if (items && items.length > 0) {
|
||||
for (const item of items) {
|
||||
if (item.kind === "file") {
|
||||
@@ -631,6 +643,16 @@ User: ${formattedMessage}`;
|
||||
userHasTyped = true;
|
||||
}
|
||||
|
||||
function handleClipboardInsert(content: string): void {
|
||||
// Insert clipboard content at cursor position or append to input
|
||||
if (inputValue.trim()) {
|
||||
inputValue = inputValue + "\n\n" + content;
|
||||
} else {
|
||||
inputValue = content;
|
||||
}
|
||||
userHasTyped = true;
|
||||
}
|
||||
|
||||
async function handleQuickAction(prompt: string): Promise<void> {
|
||||
// Quick actions send the prompt directly
|
||||
if (!isConnected || isSubmitting) return;
|
||||
@@ -787,6 +809,27 @@ User: ${formattedMessage}`;
|
||||
</svg>
|
||||
<span>Snippets</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (showClipboardHistory = true)}
|
||||
class="control-button"
|
||||
title="Clipboard History"
|
||||
>
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2" />
|
||||
<rect x="9" y="3" width="6" height="4" rx="1" />
|
||||
</svg>
|
||||
<span>Clipboard</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="input-row">
|
||||
@@ -870,9 +913,14 @@ User: ${formattedMessage}`;
|
||||
{/if}
|
||||
|
||||
{#if showQuickActions}
|
||||
<QuickActionsPanel
|
||||
onClose={() => (showQuickActions = false)}
|
||||
onAction={handleQuickAction}
|
||||
<QuickActionsPanel onClose={() => (showQuickActions = false)} onAction={handleQuickAction} />
|
||||
{/if}
|
||||
|
||||
{#if showClipboardHistory}
|
||||
<ClipboardHistoryPanel
|
||||
isOpen={showClipboardHistory}
|
||||
onClose={() => (showClipboardHistory = false)}
|
||||
onInsert={handleClipboardInsert}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -1043,6 +1091,8 @@ User: ${formattedMessage}`;
|
||||
|
||||
.trans-gradient-button:hover:not(:disabled) {
|
||||
filter: brightness(1.1);
|
||||
box-shadow: 0 0 20px rgba(91, 206, 250, 0.4), 0 0 30px rgba(245, 169, 184, 0.3);
|
||||
box-shadow:
|
||||
0 0 20px rgba(91, 206, 250, 0.4),
|
||||
0 0 30px rgba(245, 169, 184, 0.3);
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user