generated from nhcarrigan/template
feat: add native clipboard support for screenshot paste #67
@@ -37,6 +37,7 @@
|
||||
let matchingCommands = $state<SlashCommand[]>([]);
|
||||
let selectedCommandIndex = $state(0);
|
||||
let attachments = $state<Attachment[]>([]);
|
||||
let isDragging = $state(false);
|
||||
|
||||
// Input history state
|
||||
let inputHistory = $state<string[]>([]);
|
||||
@@ -301,6 +302,79 @@ User: ${formattedMessage}`;
|
||||
claudeStore.removeAttachment(id);
|
||||
}
|
||||
|
||||
function getFileTypeFromExtension(extension: string): "image" | "document" | "other" {
|
||||
const imageExtensions = ["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp"];
|
||||
const documentExtensions = ["pdf", "txt", "md", "doc", "docx", "csv", "json", "xml"];
|
||||
|
||||
if (imageExtensions.includes(extension)) {
|
||||
return "image";
|
||||
} else if (documentExtensions.includes(extension)) {
|
||||
return "document";
|
||||
}
|
||||
return "other";
|
||||
}
|
||||
|
||||
function handleDragEnter(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.dataTransfer?.types.includes("Files")) {
|
||||
isDragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragOver(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event.dataTransfer) {
|
||||
event.dataTransfer.dropEffect = "copy";
|
||||
}
|
||||
}
|
||||
|
||||
function handleDragLeave(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
// Only set isDragging to false if we're leaving the form element entirely
|
||||
const relatedTarget = event.relatedTarget as Node | null;
|
||||
const currentTarget = event.currentTarget as HTMLElement;
|
||||
if (!relatedTarget || !currentTarget.contains(relatedTarget)) {
|
||||
isDragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleDrop(event: DragEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
isDragging = false;
|
||||
|
||||
const files = event.dataTransfer?.files;
|
||||
if (!files || files.length === 0) return;
|
||||
|
||||
for (const file of files) {
|
||||
const filename = file.name;
|
||||
const extension = filename.split(".").pop()?.toLowerCase() || "";
|
||||
const fileType = getFileTypeFromExtension(extension);
|
||||
|
||||
// Create attachment from dropped file
|
||||
// Note: For dropped files, we create a preview URL for images
|
||||
let previewUrl: string | undefined;
|
||||
if (fileType === "image") {
|
||||
previewUrl = URL.createObjectURL(file);
|
||||
}
|
||||
|
||||
const attachment: Attachment = {
|
||||
id: `attachment-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
filename,
|
||||
path: (file as File & { path?: string }).path || file.name,
|
||||
size: file.size,
|
||||
type: fileType,
|
||||
mimeType: file.type,
|
||||
previewUrl,
|
||||
};
|
||||
|
||||
claudeStore.addAttachment(attachment);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFilePicker() {
|
||||
try {
|
||||
const selected = await open({
|
||||
@@ -348,29 +422,15 @@ User: ${formattedMessage}`;
|
||||
const files = Array.isArray(selected) ? selected : [selected];
|
||||
|
||||
for (const filePath of files) {
|
||||
// Get file info
|
||||
const filename = filePath.split(/[/\\]/).pop() || "unknown";
|
||||
const extension = filename.split(".").pop()?.toLowerCase() || "";
|
||||
const fileType = getFileTypeFromExtension(extension);
|
||||
|
||||
// Determine file type
|
||||
const imageExtensions = ["png", "jpg", "jpeg", "gif", "webp", "svg", "bmp"];
|
||||
const documentExtensions = ["pdf", "txt", "md", "doc", "docx", "csv", "json", "xml"];
|
||||
|
||||
let fileType: "image" | "document" | "other";
|
||||
if (imageExtensions.includes(extension)) {
|
||||
fileType = "image";
|
||||
} else if (documentExtensions.includes(extension)) {
|
||||
fileType = "document";
|
||||
} else {
|
||||
fileType = "other";
|
||||
}
|
||||
|
||||
// Create attachment
|
||||
const attachment: Attachment = {
|
||||
id: `attachment-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
filename,
|
||||
path: filePath,
|
||||
size: 0, // We don't have easy access to file size from the dialog
|
||||
size: 0,
|
||||
type: fileType,
|
||||
};
|
||||
|
||||
@@ -444,7 +504,15 @@ User: ${formattedMessage}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<form onsubmit={handleSubmit} class="input-bar">
|
||||
<form
|
||||
onsubmit={handleSubmit}
|
||||
ondragenter={handleDragEnter}
|
||||
ondragover={handleDragOver}
|
||||
ondragleave={handleDragLeave}
|
||||
ondrop={handleDrop}
|
||||
class="input-bar"
|
||||
class:is-dragging={isDragging}
|
||||
>
|
||||
<AttachmentPreview {attachments} onRemove={handleRemoveAttachment} />
|
||||
|
||||
<div class="input-controls flex gap-2 mb-2">
|
||||
@@ -532,6 +600,33 @@ User: ${formattedMessage}`;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
transition: border-color 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.input-bar.is-dragging {
|
||||
background: var(--bg-secondary);
|
||||
border: 2px dashed var(--accent-primary);
|
||||
border-radius: 12px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.input-bar.is-dragging::before {
|
||||
content: "Drop files here";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: var(--accent-primary);
|
||||
pointer-events: none;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.input-bar.is-dragging > * {
|
||||
opacity: 0.3;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.input-controls {
|
||||
|
||||
Reference in New Issue
Block a user