feat: paste box + file picker in AppShell sidebar for screenshots

- Paste box: click it, Ctrl+V a screenshot from snipping tool
- Upload icon button: opens file picker
- Only captures paste events inside the box (not globally)
- Shows "Uploading..." then "Saved!" status

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-01 21:41:26 -05:00
parent b87a3a583d
commit b09fa0b42b

View File

@@ -79,6 +79,21 @@
if (file) handleUpload(file); if (file) handleUpload(file);
} }
function onPasteBox(e: ClipboardEvent) {
const items = e.clipboardData?.items;
if (!items) return;
for (const item of items) {
if (item.type.startsWith('image/')) {
const file = item.getAsFile();
if (file) {
e.preventDefault();
handleUpload(file);
}
break;
}
}
}
// Brain sidebar sub-items // Brain sidebar sub-items
interface BrainFolder { id: string; name: string; item_count: number; } interface BrainFolder { id: string; name: string; item_count: number; }
interface BrainTag { id: string; name: string; item_count: number; } interface BrainTag { id: string; name: string; item_count: number; }
@@ -144,10 +159,15 @@
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="rail-bottom" ondragover={(e) => e.preventDefault()} ondrop={onRailDrop}> <div class="rail-bottom" ondragover={(e) => e.preventDefault()} ondrop={onRailDrop}>
<button class="rail-upload" onclick={() => uploadInput?.click()}> <div class="rail-upload-row">
<Upload size={14} strokeWidth={1.8} /> <!-- svelte-ignore a11y_no_static_element_interactions -->
<span>{uploadStatus === 'uploading' ? 'Uploading...' : uploadStatus === 'done' ? 'Saved!' : 'Upload screenshot'}</span> <div class="paste-box" contenteditable="true" onpaste={onPasteBox} role="textbox" tabindex="0">
</button> {#if uploadStatus === 'uploading'}Uploading...{:else if uploadStatus === 'done'}Saved!{:else}Paste screenshot{/if}
</div>
<button class="upload-icon-btn" onclick={() => uploadInput?.click()} title="Browse files">
<Upload size={13} strokeWidth={2} />
</button>
</div>
<input bind:this={uploadInput} type="file" accept="image/*,.pdf" onchange={onUploadInput} hidden /> <input bind:this={uploadInput} type="file" accept="image/*,.pdf" onchange={onUploadInput} hidden />
<div class="rail-date"> <div class="rail-date">
<CalendarDays size={14} strokeWidth={1.8} /> <CalendarDays size={14} strokeWidth={1.8} />
@@ -415,24 +435,49 @@
border-top: 1px solid var(--shell-line); border-top: 1px solid var(--shell-line);
} }
.rail-upload { .rail-upload-row {
display: flex; display: flex;
align-items: center; gap: 4px;
gap: 8px; align-items: stretch;
padding: 9px 12px; }
border-radius: 999px; .paste-box {
flex: 1;
padding: 7px 10px;
border-radius: 10px;
border: 1px dashed var(--shell-line); border: 1px dashed var(--shell-line);
background: none; background: none;
color: var(--shell-muted); color: var(--shell-muted);
font-size: 0.78rem; font-size: 0.72rem;
font-family: inherit; font-family: inherit;
cursor: text;
transition: all 160ms;
outline: none;
min-height: 0;
overflow: hidden;
white-space: nowrap;
}
.paste-box:focus {
border-color: var(--shell-ink);
border-style: solid;
color: var(--shell-ink);
background: rgba(255,255,255,0.2);
}
.upload-icon-btn {
width: 32px;
flex-shrink: 0;
border-radius: 10px;
border: 1px solid var(--shell-line);
background: none;
color: var(--shell-muted);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer; cursor: pointer;
transition: all 160ms; transition: all 160ms;
} }
.rail-upload:hover { .upload-icon-btn:hover {
border-color: var(--shell-ink);
color: var(--shell-ink);
background: rgba(255,255,255,0.3); background: rgba(255,255,255,0.3);
color: var(--shell-ink);
} }
.rail-date { .rail-date {