feat: modernize search results

This commit is contained in:
OpenClaw Agent
2026-09-22 02:46:09 +08:00
parent 75c51d7d94
commit 8dc6aa64a1
+7 -3
View File
@@ -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('<h2>手机传输</h2><div class="notice">用手机扫描二维码打开当前文件夹。登录后即可按你的权限上传、下载和管理文件。</div><img class="qr" src="/chfs/qrcode?url='+encodeURIComponent(u.href)+'" alt="QR Code"><div class="field"><input value="'+esc(u.href)+'" readonly></div><div class="row"><button class="ghost" onclick="closeModal()">关闭</button></div>')}
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 '<tr><td><input class="selectbox" type="checkbox" disabled></td><td><div class="name">'+iconFor(f)+' <a href="#" onclick="openSearchResult('+JSON.stringify(f)+');return false">'+esc(f.path)+'</a></div></td><td class="meta">'+(f.dir?"—":fmt(f.size))+'</td><td class="meta">'+esc(f.modified||"")+'</td><td></td></tr>'}).join(""): '<tr><td colspan="5" class="empty">没有找到匹配文件</td></tr>'}
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 '<tr class="file-row" data-search-path="'+esc(p)+'" onclick="selectSearchItem('+JSON.stringify(p)+')" ondblclick="openSearchResult('+JSON.stringify(f)+')" oncontextmenu="searchMenu(event,'+JSON.stringify(f)+');return false"><td><input class="selectbox" type="checkbox" onclick="event.stopPropagation()" onchange="toggleSearchItem('+JSON.stringify(p)+',this.checked)"></td><td><div class="name"><span class="file-kind">'+iconFor(f)+'</span><span>'+esc(f.path)+'</span></div></td><td class="size-cell">'+(f.dir?"—":fmt(f.size))+'</td><td class="time-cell">'+esc(f.modified||"")+'</td><td class="perm-cell"><span class="meta">搜索结果</span></td><td><div class="actions"><button class="iconbtn" onclick="searchMenu(event,'+JSON.stringify(f)+')" title="更多操作"><svg class="ui-icon" viewBox="0 0 24 24"><circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/></svg></button></div></td></tr>'}).join(""): '<tr><td colspan="6" class="empty">没有找到匹配文件</td></tr>'}
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+='<button onclick="downloadFile('+JSON.stringify(f.path)+');closeContext()">下载</button>';if(f.dir)html+='<button onclick="downloadDir('+JSON.stringify(f.path)+');closeContext()">下载 ZIP</button>';html+='<button onclick="openSearchResult('+JSON.stringify(f)+');closeContext()">打开</button>';$("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='<img src="'+url+'" alt="'+esc(n)+'">';else if(media)body='<video controls autoplay><source src="'+url+'"></video>';else if(audio)body='<audio controls autoplay><source src="'+url+'"></audio>';else if(pdf)body='<iframe src="'+url+'"></iframe>';else if(text){try{const r=await fetch(url);if(!r.ok)throw Error();body='<pre class="preview-text">'+esc(await r.text())+'</pre>'}catch{body='<div class="preview-empty">无法读取文本内容</div>'}}else body='<div class="preview-empty">暂不支持该文件类型的在线预览</div>';show('<div class="preview-head"><span class="preview-title">'+esc(n)+'</span><button class="iconbtn" onclick="location.href='+JSON.stringify('/chfs/shared/'+encodeURI(p))+'" title="下载">'+svgIcon('file')+'</button><button class="iconbtn" onclick="closeModal()" title="关闭"><svg class="ui-icon" viewBox="0 0 24 24"><path d="m6 6 12 12M18 6 6 18"/></svg></button></div><div class="preview-stage">'+body+'</div><div class="preview-foot"><button class="ghost" onclick="closeModal()">关闭</button></div>',true)}
function changePassword(){show('<h2>修改密码</h2><div class="field"><input id="cp" type="password" placeholder="当前密码"><input id="npw" type="password" placeholder="新密码"></div><div class="row"><button class="ghost" onclick="closeModal()">取消</button><button onclick="savePassword()">保存</button></div>')}