diff --git a/chfs-ng/internal/api/web/index.html b/chfs-ng/internal/api/web/index.html
index 3e9b2e6..4140f96 100644
--- a/chfs-ng/internal/api/web/index.html
+++ b/chfs-ng/internal/api/web/index.html
@@ -101,9 +101,13 @@ async function collectDroppedEntries(items){const out=[];async function walk(ent
function showMobileTransfer(){const u=new URL(location.href);u.hash='folder='+encodeURIComponent(current);show('
手机传输
用手机扫描二维码打开当前文件夹。登录后即可按你的权限上传、下载和管理文件。
')}
function setupDropZone(){const z=$("dropZone"),main=$("fileCard");let depth=0;[document,main].forEach(el=>{el.addEventListener('dragenter',e=>{if(!e.dataTransfer?.types.includes('Files'))return;e.preventDefault();depth++;z.style.display='block';z.classList.add('drop-active')});el.addEventListener('dragover',e=>{if(e.dataTransfer?.types.includes('Files'))e.preventDefault()});el.addEventListener('dragleave',e=>{if(!e.dataTransfer?.types.includes('Files'))return;depth--;if(depth<=0){depth=0;z.classList.remove('drop-active')}});el.addEventListener('drop',async e=>{if(!e.dataTransfer?.types.includes('Files'))return;e.preventDefault();depth=0;z.classList.remove('drop-active');z.style.display='none';const entries=await collectDroppedEntries(e.dataTransfer.items||[]);if(entries.length)await uploadEntries(entries);else if(e.dataTransfer.files?.length)await uploadFiles(e.dataTransfer.files)})})}
function uploadOne(file,row,relativePath=file.name){return new Promise(resolve=>{const xhr=new XMLHttpRequest(),fd=new FormData();const clean=String(relativePath||file.name).replace(/\\/g,'/').replace(/^\/+|\/+$/g,'');const parts=clean.split('/');const name=parts.pop()||file.name;const folder=parts.length?[current,...parts].filter(Boolean).join('/') : current;fd.append("folder",folder);fd.append("file",file,name);const meta=row.querySelector('.meta'),bar=row.querySelector('i');xhr.open('POST','/chfs/upload');xhr.upload.onprogress=e=>{if(e.lengthComputable){const pct=Math.round(e.loaded/e.total*100);bar.style.width=pct+'%';meta.textContent=fmt(file.size)+' · '+pct+'%'}};xhr.onload=()=>{if(xhr.status>=200&&xhr.status<300){bar.style.width='100%';meta.textContent=fmt(file.size)+' · 完成'}else{meta.textContent='上传失败';bar.style.width='0'}};xhr.onerror=()=>{meta.textContent='网络错误';bar.style.width='0'};xhr.onloadend=resolve;xhr.send(fd)})}
-$("search").addEventListener("keydown",async e=>{if(e.key!=="Enter")return;const s=e.target.value.trim();if(!s){refresh();return}try{const d=await api("/chfs/search?str="+encodeURIComponent(s));renderSearch(d.files||[])}catch(x){fail(x.message)}})
-function renderSearch(items){$("crumb").textContent="搜索结果";$("files").parentElement.hidden=false;$("grid").hidden=true;const body=$("files");body.innerHTML=items.length?items.map(f=>{const p=f.path,canRead=true;return ' | | '+(f.dir?"—":fmt(f.size))+' | '+esc(f.modified||"")+' | |
'}).join(""): '| 没有找到匹配文件 |
'}
-function openSearchResult(f){if(f.dir){current=f.path;navigationRoot=me.home||"";refresh()}else previewFile(f.path)}
+let searchMode=false,searchItems=[];
+$("search").addEventListener("keydown",async e=>{if(e.key!=="Enter")return;const s=e.target.value.trim();if(!s){searchMode=false;searchItems=[];refresh();return}try{const d=await api("/chfs/search?str="+encodeURIComponent(s));renderSearch(d.files||[])}catch(x){fail(x.message)}})
+function renderSearch(items){searchMode=true;searchItems=items;selected.clear();$("crumb").textContent="搜索结果";$("files").parentElement.hidden=false;$("grid").hidden=true;$("bulk").classList.remove("show");const body=$("files");body.innerHTML=items.length?items.map(f=>{const p=f.path;return ' | '+iconFor(f)+''+esc(f.path)+' | '+(f.dir?"—":fmt(f.size))+' | '+esc(f.modified||"")+' | 搜索结果 | |
'}).join(""): '| 没有找到匹配文件 |
'}
+function selectSearchItem(p){document.querySelectorAll("#files .file-row").forEach(row=>row.classList.toggle("selected",row.dataset.searchPath===p));selected.clear();selected.add(p)}
+function toggleSearchItem(p,checked){if(checked){selected.clear();selected.add(p)}else selected.delete(p);selectSearchItem(checked?p:"")}
+function searchMenu(e,f){e.preventDefault();selectSearchItem(f.path);let html='';html+='';if(f.dir)html+='';html+='';$("context").innerHTML=html;$("context").style.left=Math.min(e.clientX,innerWidth-190)+'px';$("context").style.top=Math.min(e.clientY,innerHeight-160)+'px';$("context").style.display='block'}
+function openSearchResult(f){if(f.dir){current=f.path;navigationRoot=me.home||"";searchMode=false;$("search").value="";refresh()}else previewFile(f.path)}
async function previewFile(p){const n=p.split('/').pop(),ext=n.toLowerCase().split('.').pop(),url='/chfs/preview?file='+encodeURIComponent(p),media=['mp4','webm','mov','mkv'].includes(ext),audio=['mp3','wav','flac','m4a','ogg'].includes(ext),image=['png','jpg','jpeg','gif','webp','bmp','svg'].includes(ext),pdf=ext==='pdf',text=['txt','md','markdown','json','yaml','yml','xml','csv','log','js','ts','jsx','tsx','css','scss','html','htm','go','rs','py','java','c','cpp','h','hpp','sh','sql','toml','ini','conf'].includes(ext);let body='';if(image)body='
';else if(media)body='';else if(audio)body='';else if(pdf)body='';else if(text){try{const r=await fetch(url);if(!r.ok)throw Error();body=''+esc(await r.text())+'
'}catch{body='无法读取文本内容
'}}else body='暂不支持该文件类型的在线预览
';show(''+esc(n)+' '+body+'
',true)}
function changePassword(){show('修改密码
')}