fix: complete mobile and session interactions
This commit is contained in:
@@ -46,7 +46,7 @@ button.ghost{background:var(--surface2);color:var(--text)}button.danger{backgrou
|
||||
<div class="modal" id="modal"><div class="dialog" id="dialog"></div></div>
|
||||
<script>
|
||||
const $=id=>document.getElementById(id);let current="",me=null,currentMask="",navigationRoot="";
|
||||
async function api(url,opt={}){const r=await fetch(url,opt);if(!r.ok){let t="请求失败";try{t=(await r.json()).error||t}catch{};const e=Error(t);e.status=r.status;throw e}return r.status===204?null:r.json()}
|
||||
async function api(url,opt={}){const r=await fetch(url,{cache:"no-store",...opt});if(!r.ok){let t="请求失败";try{t=(await r.json()).error||t}catch{};const e=Error(t);e.status=r.status;throw e}return r.status===204?null:r.json()}
|
||||
function esc(s){return String(s).replace(/[&<>"']/g,c=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[c]))}
|
||||
function fmt(n){if(n<1024)return n+" B";if(n<1048576)return (n/1024).toFixed(1)+" KB";if(n<1073741824)return (n/1048576).toFixed(1)+" MB";return (n/1073741824).toFixed(1)+" GB"}
|
||||
async function init(){try{me=await api("/chfs/me")}catch{me={username:"",home:"",admin:false}};updateUser();current=me.home||"";navigationRoot=me.home||"";const h=new URLSearchParams(location.hash.slice(1));const shared=h.get("folder");if(shared!==null)current=shared;await refresh()}
|
||||
@@ -66,13 +66,14 @@ function sortBy(key){if(sortKey===key)sortDir*=-1;else{sortKey=key;sortDir=1}if(
|
||||
function renderSortState(){["name","size","modified"].forEach(k=>{const el=$("sort-"+k);if(el)el.textContent=sortKey===k?(sortDir===1?"↑":"↓"):""})}
|
||||
function emptyState(title,text,action=""){const writable=currentMask.includes("W");let buttons="";if(action==="folder"&&writable)buttons='<div class="row"><button onclick="newFolder()">新建文件夹</button><button class="ghost" onclick="newText()">新建文本</button></div>';if(action==="upload"&&writable)buttons='<div class="row"><button onclick="$(\"upload\").click()">上传文件</button><button class="ghost" onclick="newFolder()">新建文件夹</button></div>';return '<div class="empty-state"><div class="empty-icon">'+svgIcon("folder")+'</div><h3>'+esc(title)+'</h3><p>'+esc(text)+'</p>'+buttons+'</div>'}
|
||||
function render(items){lastItems=sortItems(items);selected=new Set([...selected].filter(p=>lastItems.some(f=>(current?current+"/":"")+f.name===p)));renderList(lastItems);renderGrid(lastItems);updateSelection();renderSortState()}
|
||||
function renderList(items){const body=$("files");body.innerHTML=items.length?items.map(f=>{const p=current?current+"/"+f.name:f.name,canRead=(f.mask||"").includes("R"),canWrite=(f.mask||"").includes("W"),canDelete=(f.mask||"").includes("D"),selectedNow=selected.has(p),drag=' draggable="true"';const perms='<span class="perm-dot '+(canRead?'read':'')+'"></span><span class="perm-dot '+(canWrite?'write':'')+'"></span><span class="perm-dot '+(canDelete?'delete':'')+'"></span>';return '<tr class="file-row '+(selectedNow?'selected':'')+'"'+drag+' data-file-path="'+esc(p)+'" onclick="selectItem('+JSON.stringify(p)+',true,event)" ondblclick="activateItem('+JSON.stringify(p)+')" ondragstart="fileDragStart(event,'+JSON.stringify(p)+')" ondragend="fileDragEnd(event)" ondragover="folderDragOver(event,'+JSON.stringify(p)+')" ondragleave="folderDragLeave(event)" ondrop="folderDrop(event,'+JSON.stringify(p)+')"><td><input class="selectbox" type="checkbox" '+(selectedNow?"checked":"")+' onclick="event.stopPropagation()" onchange="selectItem('+JSON.stringify(p)+',this.checked,event)"></td><td><div class="name"><span class="file-kind">'+iconFor(f)+'</span><span>'+esc(f.name)+'</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">'+perms+'</td><td><div class="actions"><button class="iconbtn" onclick="itemMenu(event,'+JSON.stringify(p)+')" 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">'+emptyState("这个文件夹还是空的","可以上传文件,或者创建一个新的文件夹。","upload")+'</td></tr>'}
|
||||
function renderGrid(items){$("grid").innerHTML=items.length?items.map(f=>{const p=current?current+"/"+f.name:f.name,canRead=(f.mask||"").includes("R"),type=fileType(f),selectedNow=selected.has(p),drag=' draggable="true"';let thumb=iconFor(f);if(type==="image"&&canRead)thumb='<img src="/chfs/preview?file='+encodeURIComponent(p)+'" loading="lazy" alt="">';const tileClass=(f.dir?'folder-tile ':'')+(['video','audio'].includes(type)?'media-tile':'');const meta=f.dir?'文件夹':fmt(f.size);return '<div class="tile '+tileClass+(selectedNow?' selected':'')+'"'+drag+' data-file-path="'+esc(p)+'" onclick="selectItem('+JSON.stringify(p)+',true,event)" ondblclick="activateItem('+JSON.stringify(p)+')" oncontextmenu="itemMenu(event,'+JSON.stringify(p)+');return false" ondragstart="fileDragStart(event,'+JSON.stringify(p)+')" ondragend="fileDragEnd(event)" ondragover="folderDragOver(event,'+JSON.stringify(p)+')" ondragleave="folderDragLeave(event)" ondrop="folderDrop(event,'+JSON.stringify(p)+')"><input class="selectbox tile-check" type="checkbox" '+(selectedNow?'checked':'')+' onclick="event.stopPropagation();selectItem('+JSON.stringify(p)+',this.checked,event)"><button class="iconbtn tile-action" onclick="event.stopPropagation();itemMenu(event,'+JSON.stringify(p)+')" 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 class="thumb">'+thumb+'</div><div class="tile-title">'+iconFor(f)+'<div class="file-name">'+esc(f.name)+'</div></div><div class="tile-meta"><span>'+meta+'</span>'+(f.modified?'<span>'+esc(f.modified)+'</span>':'')+'</div></div>'}).join(''):emptyState("这个文件夹还是空的","上传文件或创建内容后,它们会显示在这里。","upload")}
|
||||
function renderList(items){const body=$("files");body.innerHTML=items.length?items.map(f=>{const p=current?current+"/"+f.name:f.name,canRead=(f.mask||"").includes("R"),canWrite=(f.mask||"").includes("W"),canDelete=(f.mask||"").includes("D"),selectedNow=selected.has(p),drag=' draggable="true"';const perms='<span class="perm-dot '+(canRead?'read':'')+'"></span><span class="perm-dot '+(canWrite?'write':'')+'"></span><span class="perm-dot '+(canDelete?'delete':'')+'"></span>';return '<tr class="file-row '+(selectedNow?'selected':'')+'"'+drag+' data-file-path="'+esc(p)+'" onclick="handleItemClick(event,'+JSON.stringify(p)+')" ondblclick="activateItem('+JSON.stringify(p)+')" ondragstart="fileDragStart(event,'+JSON.stringify(p)+')" ondragend="fileDragEnd(event)" ondragover="folderDragOver(event,'+JSON.stringify(p)+')" ondragleave="folderDragLeave(event)" ondrop="folderDrop(event,'+JSON.stringify(p)+')"><td><input class="selectbox" type="checkbox" '+(selectedNow?"checked":"")+' onclick="event.stopPropagation()" onchange="selectItem('+JSON.stringify(p)+',this.checked,event)"></td><td><div class="name"><span class="file-kind">'+iconFor(f)+'</span><span>'+esc(f.name)+'</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">'+perms+'</td><td><div class="actions"><button class="iconbtn" onclick="event.stopPropagation();itemMenu(event,'+JSON.stringify(p)+')" 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">'+emptyState("这个文件夹还是空的","可以上传文件,或者创建一个新的文件夹。","upload")+'</td></tr>'}
|
||||
function renderGrid(items){$("grid").innerHTML=items.length?items.map(f=>{const p=current?current+"/"+f.name:f.name,canRead=(f.mask||"").includes("R"),type=fileType(f),selectedNow=selected.has(p),drag=' draggable="true"';let thumb=iconFor(f);if(type==="image"&&canRead)thumb='<img src="/chfs/preview?file='+encodeURIComponent(p)+'" loading="lazy" alt="">';const tileClass=(f.dir?'folder-tile ':'')+(['video','audio'].includes(type)?'media-tile':'');const meta=f.dir?'文件夹':fmt(f.size);return '<div class="tile '+tileClass+(selectedNow?' selected':'')+'"'+drag+' data-file-path="'+esc(p)+'" onclick="handleItemClick(event,'+JSON.stringify(p)+')" ondblclick="activateItem('+JSON.stringify(p)+')" oncontextmenu="itemMenu(event,'+JSON.stringify(p)+');return false" ondragstart="fileDragStart(event,'+JSON.stringify(p)+')" ondragend="fileDragEnd(event)" ondragover="folderDragOver(event,'+JSON.stringify(p)+')" ondragleave="folderDragLeave(event)" ondrop="folderDrop(event,'+JSON.stringify(p)+')"><input class="selectbox tile-check" type="checkbox" '+(selectedNow?'checked':'')+' onclick="event.stopPropagation();selectItem('+JSON.stringify(p)+',this.checked,event)"><button class="iconbtn tile-action" onclick="event.stopPropagation();itemMenu(event,'+JSON.stringify(p)+')" 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 class="thumb">'+thumb+'</div><div class="tile-title">'+iconFor(f)+'<div class="file-name">'+esc(f.name)+'</div></div><div class="tile-meta"><span>'+meta+'</span>'+(f.modified?'<span>'+esc(f.modified)+'</span>':'')+'</div></div>'}).join(''):emptyState("这个文件夹还是空的","上传文件或创建内容后,它们会显示在这里。","upload")}
|
||||
function fileDragStart(e,p){const paths=selected.has(p)?[...selected]:[p];e.dataTransfer.effectAllowed="move";e.dataTransfer.setData("text/chfs-paths",JSON.stringify(paths));e.currentTarget.classList.add("dragging")}
|
||||
function fileDragEnd(e){e.currentTarget.classList.remove("dragging");document.querySelectorAll('.drop-target').forEach(el=>el.classList.remove('drop-target'))}
|
||||
function folderDragOver(e,target){if(!e.dataTransfer?.types.includes('text/chfs-paths'))return;const f=lastItems.find(x=>(current?current+'/':'')+x.name===target);if(!f?.dir||!(f.mask||'').includes('W'))return;e.preventDefault();if(target===current||target===""||[...selected].some(p=>p===target||target.startsWith(p+'/')))return;e.dataTransfer.dropEffect="move";e.currentTarget.classList.add('drop-target')}
|
||||
function folderDragLeave(e){e.currentTarget.classList.remove('drop-target')}
|
||||
async function folderDrop(e,target){if(!e.dataTransfer?.types.includes('text/chfs-paths'))return;e.preventDefault();e.stopPropagation();e.currentTarget.classList.remove('drop-target');let paths=[];try{paths=JSON.parse(e.dataTransfer.getData('text/chfs-paths')||'[]')}catch{};const f=lastItems.find(x=>(current?current+'/':'')+x.name===target);if(!f?.dir||!paths.length)return;if(paths.some(p=>p===target||target.startsWith(p+'/')))return fail('不能移动到自身或子目录');const base=target.replace(/^\/+|\/+$/g,'');await runOperation('移动 '+paths.length+' 项',async()=>{let done=0,failed=[],renamed=[];for(const source of paths){const name=source.split('/').pop(),destination=base?base+'/'+name:name;try{const actual=await transferWithConflict(source,destination,false);if(actual!==destination)renamed.push(name+' → '+actual.split('/').pop());done++}catch(err){failed.push(name+': '+err.message)}}selected.clear();await refresh();if(failed.length||renamed.length)show('<h2>移动完成</h2><div class="notice">'+esc('成功 '+done+' 项,失败 '+failed.length+' 项'+(renamed.length?'。\n\n自动重命名:\n'+renamed.join('\n'):'')+(failed.length?'。\n\n失败:\n'+failed.join('\n'):''))+'</div><div class="row"><button class="ghost" onclick="closeModal()">关闭</button></div>');else toast('已移动 '+done+' 项')})}
|
||||
function handleItemClick(e,p){if(window.matchMedia&&window.matchMedia('(max-width:700px)').matches){activateItem(p);return}selectItem(p,true,e)}
|
||||
function activateItem(p){const f=lastItems.find(x=>(current?current+"/":"")+x.name===p);if(!f)return;if(f.dir&&(f.mask||"").includes("R"))openDir(p);else if((f.mask||"").includes("R"))previewFile(p)}
|
||||
function toggleTile(p){selectItem(p,!selected.has(p))}
|
||||
function openSelected(){if(selected.size!==1)return;const p=[...selected][0];if(searchMode){const f=searchItems.find(x=>x.path===p);if(f)openSearchResult(f);return}const f=lastItems.find(x=>(current?current+"/":"")+x.name===p);if(!f)return;if(f.dir&&(f.mask||"").includes("R"))openDir(p);else if((f.mask||"").includes("R"))previewFile(p)}
|
||||
@@ -99,7 +100,7 @@ async function bulkTransfer(copyOnly){if(!selected.size)return;const dstDir=awai
|
||||
async function bulkDelete(){if(!selected.size)return;const paths=[...selected];if(!await confirmBox("确认删除",paths.length===1?"确定删除 "+paths[0]+"?":"确定删除这 "+paths.length+" 项?删除文件夹时会同时删除其中内容。"))return;await runOperation("删除中",async()=>{try{const body=new URLSearchParams();paths.forEach(p=>body.append("filepath",p));await api("/chfs/rmfiles",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body});selected.clear();await refresh();toast(paths.length===1?"已删除":"已删除 "+paths.length+" 项")}catch(e){fail("删除失败:"+e.message)}})}
|
||||
|
||||
|
||||
async function loadSideRoots(){try{const d=await api("/chfs/roots"),roots=d.roots||[];$("sideRoots").innerHTML=roots.map(r=>{const writable=String(r.mask||"").includes("W");return '<button class="side-item '+(r.path===current?'active':'')+'" data-root-path="'+esc(r.path)+'" onclick="jumpRoot('+JSON.stringify(r.path)+')" ondragover="sideDragOver(event,'+JSON.stringify(r.path)+','+writable+')" ondragleave="sideDragLeave(event)" ondrop="sideDrop(event,'+JSON.stringify(r.path)+','+writable+')">'+svgIcon('folder')+' '+esc(r.name)+'</button>'}).join('')||'<div class="meta" style="padding:8px">没有可进入的位置</div>'}catch{}}
|
||||
async function loadSideRoots(){try{const d=await api("/chfs/roots"),roots=d.roots||[];$("sideRoots").innerHTML=roots.map(r=>{const writable=String(r.mask||"").includes("W");return '<button class="side-item '+(r.path===current?'active':'')+'" data-root-path="'+esc(r.path)+'" onclick="jumpRoot('+JSON.stringify(r.path)+')" ondragover="sideDragOver(event,'+JSON.stringify(r.path)+','+writable+')" ondragleave="sideDragLeave(event)" ondrop="sideDrop(event,'+JSON.stringify(r.path)+','+writable+')">'+svgIcon('folder')+' '+esc(r.name)+'</button>'}).join('')||'<div class="meta" style="padding:8px">当前账号没有其他位置</div>'}catch(e){$("sideRoots").innerHTML='<div class="meta" style="padding:8px">位置加载失败</div>'}}
|
||||
async function showRoots(){try{const d=await api("/chfs/roots");const roots=d.roots||[];let html='<h2>选择位置</h2><div>';for(const r of roots)html+='<button class="ghost" style="display:block;width:100%;text-align:left;margin:8px 0" onclick="jumpRoot('+JSON.stringify(r.path)+')"><b>'+esc(r.name)+'</b><span class="meta"> · '+esc(r.path||"/")+' · '+esc(r.mask)+'</span></button>';if(!roots.length)html+='<div class="empty">没有可进入的共享位置</div>';html+='</div><div class="row"><button class="ghost" onclick="closeModal()">关闭</button></div>';show(html)}catch(e){fail(e.message)}}
|
||||
function sideDragOver(e,target,writable){if(!e.dataTransfer?.types.includes('text/chfs-paths')||!writable)return;e.preventDefault();e.dataTransfer.dropEffect='move';e.currentTarget.classList.add('drop-target')}
|
||||
function sideDragLeave(e){e.currentTarget.classList.remove('drop-target')}
|
||||
@@ -116,7 +117,7 @@ async function renameItem(p){const name=p.split("/").pop(),n=await ask("重命
|
||||
async function removeItem(p){if(!await confirmBox("确认删除","确定删除 "+p+"?"))return;await runOperation("正在删除",async()=>{try{await api("/chfs/rmfiles",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({filepath:p})});selected.delete(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=""})
|
||||
$("uploadBtn").addEventListener("click",e=>{e.preventDefault();if($("uploadBtn").disabled){toast("当前位置没有上传权限",true);return}$("upload").click()});$("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){const tasks=entries.map((item,index)=>({id:"upload-"+Date.now()+"-"+index,file:item.file,relativePath:item.relativePath,state:"queued",xhr:null,row:null}));window._uploadTasks=tasks;show('<h2>上传文件</h2><div class="notice">共 '+tasks.length+' 个文件,最多同时上传 3 个。关闭窗口不会中断上传。</div><div id="uploadSummary" class="upload-summary"><span class="count">准备上传</span><span class="meta"></span></div><div id="uploadList"></div><div class="row"><button class="ghost" onclick="closeModal()">后台继续</button></div>');const box=$("uploadList");tasks.forEach(task=>{const row=document.createElement("div");row.className="upload-row";row.innerHTML='<div class="upload-head"><b class="upload-name" title="'+esc(task.relativePath)+'">'+esc(task.relativePath)+'</b><div class="upload-actions"><button class="ghost" data-upload-action="cancel">取消</button><button class="ghost" data-upload-action="retry" hidden>重试</button></div></div><span class="meta">'+fmt(task.file.size)+' · 排队中</span><div class="progress"><i></i></div>';row.querySelector('[data-upload-action="cancel"]').onclick=()=>cancelUploadTask(task);row.querySelector('[data-upload-action="retry"]').onclick=()=>retryUploadTask(task);task.row=row;box.appendChild(row)});await runUploadQueue(tasks,3);await refresh();const failed=tasks.filter(t=>t.state==="failed").length,cancelled=tasks.filter(t=>t.state==="cancelled").length,done=tasks.filter(t=>t.state==="done").length;if(failed||cancelled)toast("上传完成:成功 "+done+",失败 "+failed+",取消 "+cancelled,failed>0);else toast("上传完成,共 "+done+" 个文件")}
|
||||
async function runUploadQueue(tasks,limit){let cursor=0;async function worker(){while(cursor<tasks.length){const task=tasks[cursor++];if(task.state!=="queued")continue;task.state="uploading";setUploadTaskState(task,"uploading");const ok=await uploadOne(task.file,task.row,task.relativePath,task);if(ok){task.state="done";setUploadTaskState(task,"done")}else if(task.state!=="cancelled"){task.state="failed";setUploadTaskState(task,"failed")};updateUploadSummary(tasks)}}await Promise.all(Array.from({length:Math.min(limit,tasks.length)},()=>worker()));updateUploadSummary(tasks)}
|
||||
@@ -166,7 +167,7 @@ function changePassword(){show('<h2>修改密码</h2><div class="field"><input i
|
||||
async function savePassword(){try{await api("/chfs/password",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({currentPassword:$("cp").value,password:$("npw").value})});toast("密码已更新");closeModal()}catch(e){fail(e.message)}}
|
||||
function openLogin(){show('<h2>登录</h2><div class="field"><label>用户名</label><input id="u" autocomplete="username"></div><div class="field"><label>密码</label><input id="p" type="password" autocomplete="current-password"></div><div class="row"><button class="ghost" onclick="closeModal()">取消</button><button onclick="login()">登录</button></div>')}
|
||||
async function login(){try{await fetch("/chfs/session",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams({user:$("u").value,pwd:$("p").value})}).then(async r=>{if(!r.ok)throw Error((await r.json()).error||"登录失败")});closeModal();await init()}catch(e){fail(e.message)}}
|
||||
async function logout(){await fetch("/chfs/session",{method:"DELETE"});location.reload()}
|
||||
async function logout(){try{await api("/chfs/session",{method:"DELETE"})}finally{me={username:"",home:"",admin:false,managed:false};current="";navigationRoot="";selected.clear();updateUser();await refresh()}}
|
||||
function toast(message,error=false){const host=$("toastHost"),el=document.createElement("div");el.className='toast'+(error?' error':'');el.textContent=message;host.appendChild(el);setTimeout(()=>el.remove(),2800)}
|
||||
function fail(message){toast(message,true)}
|
||||
function keyboardNavigation(e){const tag=(e.target?.tagName||"").toLowerCase();if(tag==="input"||tag==="textarea"||tag==="select"||tag==="button"||tag==="a"||e.target?.isContentEditable)return;if(e.key==="Escape"){if($("modal").classList.contains("show"))closeModal();else closeContext();return}if((e.ctrlKey||e.metaKey)&&e.key.toLowerCase()==="a"){e.preventDefault();toggleAll(true);return}if(e.key==="Enter"){e.preventDefault();openSelected();return}if(e.key==="Delete"&&selected.size){e.preventDefault();bulkDelete();return}if(e.key==="Backspace"&¤t&&!searchMode){e.preventDefault();goUp();return}if((e.key==="ArrowDown"||e.key==="ArrowUp")){const source=searchMode?searchItems:lastItems;if(!source.length)return;e.preventDefault();const paths=searchMode?source.map(searchPath):selectablePaths(),anchor=selectionAnchor&&paths.includes(selectionAnchor)?selectionAnchor:[...selected][0],idx=Math.max(0,paths.indexOf(anchor)),next=Math.max(0,Math.min(paths.length-1,idx+(e.key==="ArrowDown"?1:-1)));if(searchMode)selectSearchItem(paths[next],true,e);else selectItem(paths[next],true,e);return}if(e.key==="Home"||e.key==="End"){const source=searchMode?searchItems:lastItems;if(!source.length)return;e.preventDefault();const paths=searchMode?source.map(searchPath):selectablePaths(),p=e.key==="Home"?paths[0]:paths.at(-1);if(searchMode)selectSearchItem(p,true,e);else selectItem(p,true,e)}}
|
||||
|
||||
Reference in New Issue
Block a user