Keep existing attachments when adding more; clear via hold or right-click

Clicking the attach button or pasting files now appends instead of
resetting. Long-press (touch) or right-click opens a popover with
"Clear all attachments".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 16:15:48 +00:00
co-authored by Claude Opus 5.5
parent c31d01520e
commit 906333eae9
4 changed files with 15 additions and 7 deletions
+1 -1
View File
@@ -10,7 +10,7 @@ export const el = window.el = Object.fromEntries(
'suneList','newSuneBtn','userMenuBtn','userMenuAvatar','userMenu','accountSettingsOption','sunesImportOption',
'sunesExportOption','threadsImportOption','importInput','sidebarBtnRight','sidebarRight',
'sidebarOverlayRight','threadList','closeThreads','threadPopover','sunePopover','footer',
'attachBtn','attachBadge','fileInput','htmlEditor','extensionHtmlEditor',
'attachBtn','attachBadge','attachPopover','fileInput','htmlEditor','extensionHtmlEditor',
'htmlTab_index','htmlTab_extension','suneHtml','accountSettingsModal','accountSettingsForm',
'closeAccountSettings','cancelAccountSettings','set_master_prompt','set_provider',
'set_api_key_or','set_api_key_oai','set_api_key_g','set_api_key_claude','set_api_key_cf',
+10 -6
View File
@@ -119,16 +119,20 @@ $(el.sunePopover).on('click',async e=>{const act=e.target.closest('[data-action]
$(el.sunesSyncUploadBtn).on('click',()=>{hideSunesSyncPopover();performSuneUpload()});
$(el.sunesSyncDownloadBtn).on('click',()=>{hideSunesSyncPopover();performSuneDownload(false)});
function updateAttachBadge(){const n=state.attachments.length;el.attachBadge.textContent=String(n);el.attachBadge.classList.toggle('hidden',n===0)}
$(el.attachBtn).on('click',()=>{if(state.attachments.length){state.attachments=[];updateAttachBadge();el.fileInput.value=''};el.fileInput.click()})
$(el.fileInput).on('change',async()=>{const files=[...(el.fileInput.files||[])];if(!files.length)return;for(const f of files){const at=await toAttach(f).catch(()=>null);if(at)state.attachments.push(at)}updateAttachBadge()})
const addAttachments=async files=>{for(const f of files){const at=await toAttach(f).catch(()=>null);if(at)state.attachments.push(at)}updateAttachBadge()}
const hideAttachPopover=()=>el.attachPopover.classList.add('hidden')
function showAttachPopover(){if(!state.attachments.length||!el.attachPopover.classList.contains('hidden'))return;haptic();el.attachPopover.classList.remove('hidden');positionPopover(el.attachBtn,el.attachPopover);icons()}
let attachHold=null,attachHeld=false;const holdAttach=()=>{attachHeld=true;showAttachPopover()}
$(el.attachBtn).on('pointerdown',e=>{attachHeld=false;if(e.pointerType!=='mouse')attachHold=setTimeout(holdAttach,500)}).on('pointerup pointercancel pointerleave',()=>clearTimeout(attachHold)).on('contextmenu',e=>{e.preventDefault();holdAttach()}).on('click',()=>{if(attachHeld)return attachHeld=false;hideAttachPopover();el.fileInput.click()})
$(el.attachPopover).on('click',e=>{if(!e.target.closest('[data-action="clear"]'))return;state.attachments=[];updateAttachBadge();hideAttachPopover()})
$(document).on('click',e=>{if(!el.attachPopover.contains(e.target)&&!el.attachBtn.contains(e.target))hideAttachPopover()})
$(window).on('resize',()=>{if(!el.attachPopover.classList.contains('hidden'))positionPopover(el.attachBtn,el.attachPopover)})
$(el.fileInput).on('change',async()=>{const files=[...(el.fileInput.files||[])];el.fileInput.value='';await addAttachments(files)})
$(el.composer).on('paste',async e=>{
const cbd=e.clipboardData||e.originalEvent?.clipboardData,files=[...(cbd?.files||[])];
if(!files.length)return;
e.preventDefault();
state.attachments=[];
el.fileInput.value='';
for(const f of files){const at=await toAttach(f).catch(()=>null);if(at)state.attachments.push(at)}
updateAttachBadge();
await addAttachments(files);
})
$(el.composer).on('submit',async e=>{e.preventDefault();if(state.busy)return;const text=el.input.value.trim();if(!text&&!state.attachments.length)return SUNE.infer();await ensureThreadOnFirstUser(text||'(attachments)');const th=THREAD.active,shouldGenTitle=th&&!th.title;el.input.value='';const parts=[];if(text)parts.push({type:'text',text});parts.push(...state.attachments);state.attachments=[];updateAttachBadge();el.fileInput.value='';const userMsg={role:'user',content:parts.length?parts:[{type:'text',text:text||'(sent attachments)'}]};addMessage(userMsg);el.composer.dispatchEvent(new CustomEvent('user:send',{detail:{message:userMsg}}));if(shouldGenTitle)(async()=>{const title=await generateTitleWithAI(state.messages)||partsToText(state.messages.find(m=>m.role==='user')).replace(/!\[\]\(data:[^\)]+\)/g,'[Image]')||'Untitled';await THREAD.setTitle(th.id,title)})();if(!SUNE.model)return;state.busy=true;setBtnStop();const a=SUNE.active,suneMeta={sune_name:a.name,model:SUNE.model,avatar:a.avatar||''},streamId=sid(),suneBubble=addSuneBubbleStreaming(suneMeta,streamId);suneBubble.dataset.mid=streamId;suneBubble.innerHTML=SUNE_LOGO_SVG;const assistantMsg=Object.assign({id:streamId,role:'assistant',content:[{type:'text',text:''}]},suneMeta);state.messages.push(assistantMsg);THREAD.persist(false);state.stream={rid:streamId,bubble:suneBubble,meta:suneMeta,text:'',done:false};let buf='',completed=false;const onDelta=(delta,done,imgs)=>{if(imgs){if(!assistantMsg.images)assistantMsg.images=[];assistantMsg.images.push(...imgs)}buf+=delta;state.stream.text=buf;assistantMsg.content[0].text=buf;renderMarkdown(suneBubble,partsToText(assistantMsg),{enhance:false});if(done&&!completed){completed=true;setBtnSend();state.busy=false;enhanceCodeBlocks(suneBubble,true);THREAD.persist(true);el.composer.dispatchEvent(new CustomEvent('sune:newSuneResponse',{detail:{message:assistantMsg}}));state.stream={rid:null,bubble:null,meta:null,text:'',done:false}}else if(!done)THREAD.persist(false)};await streamChat(onDelta,streamId)})
let jars={html:null,extension:null};
+3
View File
@@ -17,6 +17,9 @@
<button id="sunesSyncUploadBtn" class="menu-item"><i data-lucide="upload-cloud" class="h-4 w-4"></i><span>Upload to GitHub</span></button>
<button id="sunesSyncDownloadBtn" class="menu-item"><i data-lucide="download-cloud" class="h-4 w-4"></i><span>Download from GitHub</span></button>
</div>
<div id="attachPopover" class="menu-card hidden">
<button data-action="clear" class="menu-item text-red-600"><i data-lucide="x" class="h-4 w-4"></i><span>Clear all attachments</span></button>
</div>
<div id="suneModal" class="hidden fixed inset-0 z-50">
<div class="absolute inset-0 bg-black/30"></div>
<div class="absolute inset-x-0 top-12 mx-auto w-full max-w-md px-4">
+1
View File
@@ -7,6 +7,7 @@ html,body{overscroll-behavior-y:contain;font-family:'Assistant',sans-serif}
.msg-avatar{font-size:16px}
.menu-card{position:fixed;z-index:60;min-width:12rem;border-radius:0.75rem;border:1px solid #e5e7eb;background:#fff;box-shadow:0 10px 20px rgba(0,0,0,.08)}
.menu-item{width:100%;text-align:left;padding:.5rem .75rem;font-size:0.875rem;display:flex;align-items:center;gap:.5rem}
#attachBtn{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}
#htmlEditor,#extensionHtmlEditor,#jsonSchemaEditor{outline:none;white-space:pre!important;font-size:11px;line-height:1.5;}
:not(pre)>code{font-size:85%;padding:.2em .4em;margin:0;border-radius:6px;background-color:rgba(175,184,193,0.2)}
#threadRepoInput::placeholder{font-family:sans-serif;font-weight:500;color:#9ca3af}