');else toast("删除完成,共 "+done+" 项")}
async function loadSideRoots(){try{const d=await api("/chfs/roots"),roots=d.roots||[];$("sideRoots").innerHTML=roots.map(r=>'').join('')||'
没有可进入的位置
'}catch{}}
async function showRoots(){try{const d=await api("/chfs/roots");const roots=d.roots||[];let html='
选择位置
';for(const r of roots)html+='';if(!roots.length)html+='
没有可进入的共享位置
';html+='
';show(html)}catch(e){fail(e.message)}}
function jumpRoot(p){current=p;navigationRoot=p;closeModal();refresh()}
@@ -92,18 +92,18 @@ function openDir(p){current=p;refresh()}function goUp(){if(!current||current===n
function downloadFile(p){location.href="/chfs/shared/"+encodeURIComponent(p).replace(/%2F/g,"/")}
function downloadDir(p){location.href="/chfs/downloaddir/"+encodeURI(p)}
async function shareItem(p){const url=new URL("/chfs/shared/"+encodeURI(p),location.href).href;try{await navigator.clipboard.writeText(url);toast("分享链接已复制")}catch{show("
分享链接
")}}
-async function renameItem(p){const name=p.split("/").pop();const n=await ask("重命名","新名称",name);if(!n||n===name)return;const dest=(p.split("/").slice(0,-1).concat(n)).join("/");try{await api("/chfs/rename",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({old:p,new:dest})});refresh()}catch(e){fail(e.message)}}
-async function removeItem(p){if(!await confirmBox("确认删除","确定删除 "+p+"?"))return;try{await api("/chfs/rmfiles",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({filepath:p})});refresh()}catch(e){fail(e.message)}}
-async function newFolder(){const n=await ask("新建文件夹","文件夹名称");if(!n)return;const p=current?current+"/"+n:n;try{await api("/chfs/newdir",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({filepath:p})});refresh()}catch(e){fail(e.message)}}
-async function newText(){const n=await ask("新建文本","文件名","new.txt");if(!n)return;const text=await ask("新建文本","文本内容");if(text===null)return;try{await api("/chfs/newtext",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({filepath:current,title:n,text:text||""})});refresh()}catch(e){fail(e.message)}}
+async function renameItem(p){const name=p.split("/").pop();const n=await ask("重命名","新名称",name);if(!n||n===name)return;const dest=(p.split("/").slice(0,-1).concat(n)).join("/");toast("正在重命名…");try{await api("/chfs/rename",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({old:p,new:dest})});await refresh();toast("已重命名")}catch(e){fail(e.message)}}
+async function removeItem(p){if(!await confirmBox("确认删除","确定删除 "+p+"?"))return;toast("正在删除…");try{await api("/chfs/rmfiles",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({filepath:p})});await refresh();toast("已删除")}catch(e){fail(e.message)}}
+async function newFolder(){const n=await ask("新建文件夹","文件夹名称");if(!n)return;const p=current?current+"/"+n:n;toast("正在创建文件夹…");try{await api("/chfs/newdir",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({filepath:p})});await refresh();toast("文件夹已创建")}catch(e){fail(e.message)}}
+async function newText(){const n=await ask("新建文本","文件名","new.txt");if(!n)return;const text=await ask("新建文本","文本内容");if(text===null)return;toast("正在创建文本…");try{await api("/chfs/newtext",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({filepath:current,title:n,text:text||""})});await refresh();toast("文本已创建")}catch(e){fail(e.message)}}
$("upload").addEventListener("change",async e=>{await uploadFiles(e.target.files);e.target.value=""})
async function uploadFiles(files){const list=[...files].map(file=>({file,relativePath:file.webkitRelativePath||file.name}));if(!list.length)return;await uploadEntries(list)}
-async function uploadEntries(entries){show('
上传文件
正在上传 '+entries.length+' 个文件;目录结构会按原路径保留。
');const box=$("uploadList");for(const item of entries){const row=document.createElement("div");row.className="upload-row";row.innerHTML=''+esc(item.relativePath)+''+fmt(item.file.size)+' · 准备上传
';box.appendChild(row);await uploadOne(item.file,row,item.relativePath)}refresh()}
+async function uploadEntries(entries){show('
上传文件
正在上传 '+entries.length+' 个文件;目录结构会按原路径保留。
');const box=$("uploadList");let failed=0;for(const item of entries){const row=document.createElement("div");row.className="upload-row";row.innerHTML=''+esc(item.relativePath)+''+fmt(item.file.size)+' · 准备上传
';box.appendChild(row);const ok=await uploadOne(item.file,row,item.relativePath);if(!ok)failed++}await refresh();if(failed)toast(failed+" 个文件上传失败",true);else toast("上传完成,共 "+entries.length+" 个文件")}
async function collectDroppedEntries(items){const out=[];async function walk(entry,prefix=""){if(entry.isFile){await new Promise(resolve=>entry.file(file=>{out.push({file,relativePath:prefix+file.name});resolve()},()=>resolve()));return}if(entry.isDirectory){const reader=entry.createReader();const all=[];while(true){const batch=await new Promise(resolve=>reader.readEntries(resolve,()=>resolve([])));if(!batch.length)break;all.push(...batch)}for(const child of all)await walk(child,prefix+entry.name+'/')}}for(const item of items){const entry=item.webkitGetAsEntry?.();if(entry)await walk(entry);else{const file=item.getAsFile?.();if(file)out.push({file,relativePath:file.name})}}return out}
function showMobileTransfer(){const u=new URL(location.href);u.hash='folder='+encodeURIComponent(current);show('