// ==UserScript== // @name 115网盘 - 手机端适配 // @namespace https://github.com/cxf35/Video-Takeover // @version 1.38.17 // @description 手机浏览器中使用115网盘:访问115.com自动直达新版网盘,App化手机端适配(底部导航、行尾⋮菜单:移动/复制/重命名/星标/删除、云下载与回收站界面优化)。视频接管由其他脚本完成。 // @author Video Takeover // @icon https://115.com/favicon.ico // @match https://115.com/* // @match https://*.115.com/* // @run-at document-start // @grant GM_addStyle // @license GPL-3.0 // ==/UserScript== (function () { 'use strict'; // ============================================================ // 1. 手机端直达新版网盘 // 手机访问 115.com 根域时,115 会重定向到 w.115.com(手机门户宣传页)/ m.115.com, // 进不了网盘。这里把任意 115 域名下非网盘/播放页的页面直接跳转到新版网盘。 // @run-at document-start 保证先于115自身跳转脚本执行。 // /storage/ 新版网盘页面自身不触发跳转,避免循环。 // ============================================================ const isMobile = /Mobi|Android|iPhone|iPad|iPod|Mobile|UCBrowser|QQBrowser/i.test(navigator.userAgent) || (('ontouchstart' in window) && window.innerWidth < 768); if (isMobile && /(^|\.)115\.com$/.test(location.hostname) && location.pathname.indexOf('/storage/') !== 0 && location.pathname.indexOf('/players/') !== 0) { location.replace('https://115.com/storage/allfiles?cid=0&mode=wangpan'); return; } // ============================================================ // 2. 强制侧边栏"已收起"状态 // 115 用 localStorage['layout_collapsed_all_files'] 记录左侧面板收起状态 // (各 storage 页面共用此 key,用户手动收起后刷新不会展开)。 // document-start 阶段写入 true,115 初始化侧边栏时读到"已收起", // 面板原生保持收起(布局、动画、内部状态与手动收起完全一致)。 // 配合下方隐藏 CollapseButton,用户也无法再展开。 // ============================================================ try { localStorage.setItem('layout_collapsed_all_files', 'true'); } catch (_) {} // ============================================================ // 3. 新版网盘侧边栏改造 + 消除横向滚动(storage 页面全局生效) // 115 新版左侧栏(.container-leftside)含频道导航(存储/社交/我聊/生活) // 与存储功能菜单(全部文件/云下载/星标文件/回收站等)。 // 需求: // a) 左侧面板默认收起且"不能打开"(localStorage 收起状态 + 隐藏展开按钮); // b) 最左侧菜单栏整体隐藏(文件管理区占满屏幕),4 个网盘功能 // (全部文件/云下载/星标文件/回收站)移至页面底部,App 风格底部导航; // c) 消除手机端横向滚动(主容器 min-w-[800px] 适配视口 + 内容区禁横滑)。 // ============================================================ const SIDEBAR_ITEMS = [ { name: '全部文件', href: '/storage/allfiles?cid=0&mode=wangpan', icon: '/icons/sidebar_storage_gray.svg' }, { name: '云下载', href: '/storage/clouddownload', icon: '/icons/storage/channel_cloud_download.svg' }, { name: '星标文件', href: '/storage/starredfiles', icon: '/icons/storage/channel_starred_files.svg' }, { name: '回收站', href: '/storage/recyclebin', icon: '/icons/storage/channel_recycle_bin.svg' }, ]; GM_addStyle(` /* 最左侧菜单栏整体隐藏:不再占据文件管理区宽度 */ .container-leftside { display: none !important; } /* 隐藏侧边栏展开/收起按钮(面板不能再次打开) */ [class*="CollapseButton"] { display: none !important; } /* 消除横向滚动:115 主容器 min-w-[800px] 在窄屏强制 800px 宽导致页面可左右滑动, 强制主容器适配视口宽度 + 全页禁止横向溢出 */ [class*="min-w-[800px]"] { min-width: 100vw !important; } html, body { overflow-x: hidden !important; } /* 底部功能导航(App 风格) */ .vt115-tabbar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 99999; height: 56px; display: flex; align-items: stretch; background: rgba(13, 17, 23, 0.92); border-top: 1px solid rgba(255, 255, 255, 0.08); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); padding-bottom: env(safe-area-inset-bottom, 0); } .vt115-tab-item { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; color: #8A99A8; font-size: 11px; line-height: 1.2; text-decoration: none; cursor: pointer; } .vt115-tab-item img { width: 22px; height: 22px; opacity: .85; } .vt115-tab-item:hover { color: #B6C2D0; } .vt115-tab-item.active { color: #4C8DFF; font-weight: 600; } .vt115-tab-item.active img { opacity: 1; } /* 云下载页右下角"+"号添加按钮(App 风格 FAB,参考115手机App) */ .vt115-fab { position: fixed; right: 16px; bottom: 80px; z-index: 999999; width: 52px; height: 52px; border-radius: 50%; background: #1E6FFF; color: #fff; font-size: 30px; font-weight: 300; line-height: 1; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 12px rgba(30, 111, 255, 0.45); cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent; } .vt115-fab:active { transform: scale(0.94); } /* 云下载页每行文件名栏最右侧"⋮"菜单按钮 */ .vt115-rowmenu { flex-shrink: 0; width: 32px; height: 32px; margin-left: 4px; display: flex; align-items: center; justify-content: center; color: rgba(26, 39, 52, 0.85); font-size: 18px; line-height: 1; cursor: pointer; border-radius: 6px; user-select: none; -webkit-tap-highlight-color: transparent; } .vt115-rowmenu:active { background: rgba(255, 255, 255, 0.3); } /* 全部文件页行尾"⋮"按钮:fixed 钉在屏幕右缘(不参与 115 行布局,避免 DOM 结构被破坏) */ .vt115-rowmenu-fixed { position: fixed; z-index: 99999; display: none; width: 36px; height: 36px; margin: 0; background: rgba(255, 255, 255, 0.16); -webkit-backdrop-filter: blur(10px) saturate(1.5); backdrop-filter: blur(10px) saturate(1.5); border: 1px solid rgba(255, 255, 255, 0.28); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18); } /* 行菜单浮层(磨砂玻璃) */ .vt115-rowpop { position: fixed; z-index: 999999; display: none; background: rgba(255, 255, 255, 0.2); -webkit-backdrop-filter: blur(16px) saturate(1.6); backdrop-filter: blur(16px) saturate(1.6); border: 1px solid rgba(255, 255, 255, 0.3); border-radius: 10px; padding: 6px 0; min-width: 110px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.22); } .vt115-rowpop-item { padding: 10px 16px; font-size: 14px; color: rgba(26, 39, 52, 0.9); cursor: pointer; text-align: center; user-select: none; -webkit-tap-highlight-color: transparent; } .vt115-rowpop-item:active { background: rgba(255, 255, 255, 0.35); } .vt115-rowpop-item.danger { color: #E5484D; } `); // 仅处理新版网盘页面 if (location.pathname.indexOf('/storage/') !== 0) return; function setupSidebar() { function updateActive() { document.querySelectorAll('.vt115-tab-item').forEach(a => { const href = (a.getAttribute('href') || '').split('?')[0]; a.classList.toggle('active', location.pathname === href); }); } // 横向溢出兜底:若页面仍可横向滚动,把最宽的容器 min-width 限制为视口宽。 // 内部可横向滚动的容器:全部文件等列表页禁止横向滚动(内容适配视口); // 云下载页保留原生布局不压缩、允许横向滑动(手机端整页缩放显示完整可见,行内操作由"⋮"菜单承担) function fixHScroll() { const doc = document.documentElement; if (doc.scrollWidth > window.innerWidth + 1) { let worst = null, w = 0; document.querySelectorAll('div,main,section,aside,table,ul,ol,nav').forEach(el => { const r = el.getBoundingClientRect(); if (r.width > w && r.width > window.innerWidth) { w = r.width; worst = el; } }); if (worst) worst.style.minWidth = window.innerWidth + 'px'; } const isCloudDL = location.pathname.indexOf('/storage/clouddownload') === 0; document.querySelectorAll('[class*="overflow-auto"],[class*="overflow-x-auto"],[class*="overflow-scroll"]').forEach(cont => { if (cont.scrollWidth > cont.clientWidth + 2 && !isCloudDL) cont.style.overflowX = 'hidden'; }); } // 内容滚动容器底部留出导航栏高度,避免最后内容被遮挡 function padContent() { document.querySelectorAll('[class*="overflow-auto"]').forEach(el => { if (el.clientWidth > 50 && el.clientHeight > 50) el.style.paddingBottom = '64px'; }); } // 云下载行内"⋮"菜单(全局浮层单例) let cloudPop = null; let cloudPopRow = null; function findActBtn(row, txt) { let res = null; row.querySelectorAll('div,button,a,span').forEach(el => { if (!res && (el.textContent || '').trim() === txt) res = el; }); return res; } function findRow(pBtn) { let cur = pBtn; for (let d = 0; d < 8 && cur; d++) { cur = cur.parentElement; if (!cur) break; if (cur.querySelector('[class*="truncate"]')) return cur; } return null; } function findActionArea(pBtn) { let cur = pBtn; for (let d = 0; d < 6 && cur; d++) { cur = cur.parentElement; if (!cur) break; const r = cur.getBoundingClientRect(); if (r.width <= 0) continue; let hasCopy = false, hasDel = false; cur.querySelectorAll('div,button,a,span').forEach(el => { const t = (el.textContent || '').trim(); if (t === '复制' && el.getBoundingClientRect().width > 0) hasCopy = true; if (t === '删除' && el.getBoundingClientRect().width > 0) hasDel = true; }); if (hasCopy && hasDel) return cur; } return null; } function hideCloudPop() { if (cloudPop) cloudPop.style.display = 'none'; cloudPopRow = null; } // 云下载页每行:注入"⋮"菜单(复制/删除),隐藏原生操作列 function setupCloudRows() { if (location.pathname.indexOf('/storage/clouddownload') !== 0) return; if (!cloudPop) { cloudPop = document.createElement('div'); cloudPop.className = 'vt115-rowpop'; ['复制', '删除'].forEach((txt, i) => { const item = document.createElement('div'); item.className = 'vt115-rowpop-item' + (i === 1 ? ' danger' : ''); item.textContent = txt; item.addEventListener('click', () => { if (cloudPopRow) { const btn = findActBtn(cloudPopRow, txt); if (btn) btn.click(); } hideCloudPop(); }); cloudPop.appendChild(item); }); document.body.appendChild(cloudPop); document.addEventListener('click', e => { if (!e.target.closest('.vt115-rowpop') && !e.target.closest('.vt115-rowmenu')) hideCloudPop(); }, true); } // 1) 定位任务行:从文件名叶子(truncate + 长哈希文本)向上找含固定列的行容器 const seen = new Set(); document.querySelectorAll('[class*="truncate"]').forEach(leaf => { const txt = (leaf.textContent || '').trim(); if (txt.length < 2) return; let row = leaf; for (let d = 0; d < 6 && row; d++) { row = row.parentElement; if (!row) break; const cls = (typeof row.className === 'string') ? row.className : ''; if (cls.includes('px-4') && row.children.length >= 4) break; } if (!row || seen.has(row) || row.dataset.vtMenu) return; // 排除弹窗/对话框内的行(如"添加云下载"弹窗的保存路径行,避免误伤弹窗界面) if (row.closest('[role="dialog"], [class*="modal"], [class*="Modal"], [class*="dialog"]')) return; // 任务行特征校验:必须含进度列(w-24)或操作列(w-44),避免误伤其他区域 let isTaskRow = false; Array.from(row.children).forEach(k => { const ck = (typeof k.className === 'string') ? k.className : ''; if (ck.includes('w-24') || ck.includes('w-44')) isTaskRow = true; }); if (!isTaskRow) return; seen.add(row); row.dataset.vtMenu = '1'; // 提取文件夹图标(原生行首列内含 svg/img 图标) let iconEl = null; const nw0 = row.children[0]; if (nw0) { const svg = nw0.querySelector('svg'); if (svg) iconEl = svg.cloneNode(true); else { const img = nw0.querySelector('img'); if (img) iconEl = img.cloneNode(true); } } // 提取文件名与进度/大小信息 const fileName = leaf.textContent.trim(); let progTxt = ''; let sizeTxt = ''; Array.from(row.children).forEach(k => { const cls = (typeof k.className === 'string') ? k.className : ''; if (cls.includes('w-24')) progTxt = (k.textContent || '').trim(); // 进度列 else if (cls.includes('w-16')) sizeTxt = (k.textContent || '').trim(); // 大小列 else if (cls.includes('w-44')) k.dataset.vtHide = '1'; // 操作列 }); if (sizeTxt) progTxt = progTxt ? (sizeTxt + ' · ' + progTxt) : sizeTxt; // 隐藏 115 原生行内所有列(布局不可靠,重建为 App 风格行) Array.from(row.children).forEach(k => { k.style.display = 'none'; }); row.style.display = 'flex'; row.style.alignItems = 'center'; row.style.overflow = 'hidden'; const inner = document.createElement('div'); // 行内内容限制在视口宽度内,保证行尾"⋮"固定在屏幕右缘可见可点(行在900px布局中靠左显示) inner.style.cssText = 'display:flex;align-items:center;width:' + Math.max(60, window.innerWidth - 32) + 'px;min-width:0;overflow:hidden;'; if (iconEl) { iconEl.style.cssText = 'width:30px;height:30px;flex-shrink:0;margin-right:10px;'; inner.appendChild(iconEl); } const left = document.createElement('div'); left.style.cssText = 'flex:1 1 auto;min-width:0;overflow:hidden;display:flex;flex-direction:column;justify-content:center;'; const nameEl = document.createElement('div'); nameEl.style.cssText = 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#E6EBF2;font-size:14px;line-height:1.4;'; nameEl.textContent = fileName; left.appendChild(nameEl); if (progTxt) { const sub = document.createElement('div'); sub.style.cssText = 'font-size:11px;color:#8A99A8;line-height:1.3;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;'; sub.textContent = progTxt; left.appendChild(sub); } inner.appendChild(left); // 行尾"⋮"菜单按钮 const menuBtn = document.createElement('div'); menuBtn.className = 'vt115-rowmenu'; menuBtn.textContent = '⋮'; menuBtn.addEventListener('click', e => { e.stopPropagation(); cloudPopRow = row; const r = menuBtn.getBoundingClientRect(); const pw = cloudPop.offsetWidth || 110; const left2 = Math.max(8, Math.min(r.right - pw, window.innerWidth - pw - 8)); const top = Math.min(r.bottom + 4, window.innerHeight - (cloudPop.offsetHeight || 140) - 8); cloudPop.style.left = left2 + 'px'; cloudPop.style.top = Math.max(8, top) + 'px'; cloudPop.style.display = 'block'; }); inner.appendChild(menuBtn); row.appendChild(inner); }); // 2) 再隐藏原生操作区:"操作"列表头、行操作区(暂停/复制/删除)、"更多"按钮 document.querySelectorAll('div').forEach(el => { const cls = (typeof el.className === 'string') ? el.className : ''; if (!cls.includes('w-44') || !cls.includes('flex-shrink-0')) return; const t = (el.textContent || '').trim(); if ((t === '操作' || /暂停.*复制.*删除/.test(t)) && el.getBoundingClientRect().width > 0) { el.style.display = 'none'; } }); document.querySelectorAll('[data-vt-hide]').forEach(el => { el.style.display = 'none'; }); document.querySelectorAll('button,div,a,span').forEach(el => { if ((el.textContent || '').trim() === '更多' && el.getBoundingClientRect().width > 0 && el.getBoundingClientRect().width < 100) { el.style.display = 'none'; } }); // 3) 隐藏"添加云下载"弹窗中的配额信息(本月配额/购买配额),限定在弹窗遮罩内 document.querySelectorAll('div').forEach(mask => { const mc = (typeof mask.className === 'string') ? mask.className : ''; if (!/fixed inset-0/.test(mc) && !/bg-black\/20/.test(mc)) return; mask.querySelectorAll('div,span,p,button,a').forEach(el => { if (el.children.length) return; const t = (el.textContent || '').trim(); if (t === '购买配额' || t.indexOf('本月配额') === 0) el.style.display = 'none'; }); // 隐藏"最近使用"及后面的文件夹选项(保留"保存到:云下载") let recent = null; mask.querySelectorAll('div,span,p,button,a').forEach(el => { if (!el.children.length && (el.textContent || '').trim() === '最近使用') recent = el; }); if (recent) { let cur = recent; for (let d = 0; d < 5 && cur; d++) { cur = cur.parentElement; if (!cur) break; const t2 = (cur.textContent || '').trim(); if (getComputedStyle(cur).display === 'flex' && t2.includes('最近使用') && !t2.includes('保存到')) { cur.style.display = 'none'; break; } } } }); } // 全部文件页行尾"⋮"菜单(下载/移动/复制/重命名/星标/删除) let afPop = null; let afPopRow = null; // 触发操作:勾选该行后,点击顶部操作栏中对应文本的按钮 function clickTopActBtn(label, triesMax, topLimit) { return new Promise(resolve => { let tries = 0; const timer = setInterval(() => { tries++; // 选择匹配文本的最内层可见元素(外层容器无点击处理器,点击无效) let best = null; document.querySelectorAll('button,div,a,span').forEach(el => { if (el.offsetParent === null) return; if ((el.textContent || '').trim() !== label) return; const r = el.getBoundingClientRect(); if (r.width <= 0 || r.top >= (topLimit || 250)) return; if (!best || el.children.length < best.children.length) best = el; }); if (best) { clearInterval(timer); best.click(); resolve(true); } else if (tries > (triesMax || 25)) { clearInterval(timer); resolve(false); } }, 100); }); } function clickTopMoreBtn(triesMax) { // 用"已选中 N 项"定位操作栏,再点操作栏内的"更多"(避免点错顶部右侧的"更多") return new Promise(resolve => { let tries = 0; const timer = setInterval(() => { tries++; let moreBtn = null; document.querySelectorAll('div,span').forEach(el => { if (el.offsetParent === null) return; const t = (el.textContent || '').trim(); if (!/已选中\s*\d+\s*项/.test(t) || t.length > 20) return; let bar = el; for (let d = 0; d < 6 && bar; d++) { bar = bar.parentElement; if (!bar) break; if (bar.textContent.indexOf('下载') >= 0 && bar.querySelectorAll('*').length > 10) break; } if (!bar) return; let best = null; bar.querySelectorAll('button,div,a,span').forEach(k => { if (k.offsetParent === null) return; if ((k.textContent || '').trim() !== '更多') return; if (k.getBoundingClientRect().width <= 0) return; if (!best || k.children.length < best.children.length) best = k; }); if (best && !moreBtn) moreBtn = best; }); if (moreBtn) { clearInterval(timer); moreBtn.click(); resolve(true); } else if (tries > (triesMax || 25)) { clearInterval(timer); resolve(false); } }, 100); }); } async function triggerAllfilesAction(row, label) { const cb = row.querySelector('[class*="checkbox-area"]'); if (cb) { const ccls = (typeof cb.className === 'string') ? cb.className : ''; const checked = cb.getAttribute('aria-checked') === 'true' || /checked/.test(ccls) || !!cb.querySelector('[class*="checked"]'); if (!checked) cb.click(); } await new Promise(res => setTimeout(res, 300)); // 等勾选与操作栏渲染 if (label === '星标' || label === '取消星标') { // 星标/取消星标在操作栏"更多"菜单里 → 展开"更多"后点击目标项 // 菜单项在操作栏下方(y≈300),放宽高度限制 const ok = await clickTopMoreBtn(25); if (!ok) return; await new Promise(res => setTimeout(res, 700)); // 等菜单弹出 clickTopActBtn(label, 20, 600); } else { clickTopActBtn(label, 25); } } // ===== 单文件重命名(勾选行 + 派发 F2 → 115 原生"重命名文件"对话框)===== // 115 新版 hover 文件行出现单文件操作栏,其中的"重命名"打开原生单文件重命名对话框。 // 手机端无法 hover(JS 派发 MouseEvent 不触发 CSS :hover),但 115 支持 F2 快捷键: // 勾选文件后派发 F2 keydown 即弹出原生"重命名文件"对话框(输入新名 + 确认)。 function doSingleRename(row) { const cb = row.querySelector('[class*="checkbox-area"]'); if (cb) { const ccls = (typeof cb.className === 'string') ? cb.className : ''; const checked = cb.getAttribute('aria-checked') === 'true' || /checked/.test(ccls) || !!cb.querySelector('[class*="checked"]'); if (!checked) cb.click(); } setTimeout(() => { document.dispatchEvent(new KeyboardEvent('keydown', { key: 'F2', code: 'F2', keyCode: 113, which: 113, bubbles: true, cancelable: true })); document.dispatchEvent(new KeyboardEvent('keyup', { key: 'F2', code: 'F2', keyCode: 113, which: 113, bubbles: true, cancelable: true })); }, 350); } // 全部文件页/星标文件页:行尾"⋮"菜单(移动/复制/重命名/星标/删除),隐藏大小/类型/修改时间列 function setupAllfilesRows() { if (location.pathname.indexOf('/storage/allfiles') !== 0 && location.pathname.indexOf('/storage/starredfiles') !== 0) return; // 星标文件页:隐藏底部"已加载 N / M 个"文本(保留"加载更多"翻页按钮) if (location.pathname.indexOf('/storage/starredfiles') === 0) { document.querySelectorAll('span').forEach(sp => { const t = (sp.textContent || '').trim(); if (/^已加载\s*\d+\s*\/\s*\d+\s*个$/.test(t)) sp.style.display = 'none'; }); } if (!afPop) { afPop = document.createElement('div'); afPop.className = 'vt115-rowpop'; ['移动', '复制', '重命名', '星标', '删除'].forEach((txt, i) => { const item = document.createElement('div'); item.className = 'vt115-rowpop-item' + (i === 4 ? ' danger' : ''); item.textContent = txt; if (txt === '星标') item.dataset.act = 'star'; item.addEventListener('click', () => { if (afPopRow) { if (item.textContent === '重命名') { doSingleRename(afPopRow); // 单文件重命名(模拟 hover → 115 原生"重命名文件"对话框) } else { triggerAllfilesAction(afPopRow, item.textContent); } } afPop.style.display = 'none'; afPopRow = null; }); afPop.appendChild(item); }); document.body.appendChild(afPop); document.addEventListener('click', e => { if (!e.target.closest('.vt115-rowpop') && !e.target.closest('.vt115-rowmenu')) { afPop.style.display = 'none'; afPopRow = null; } }, true); } const seen = new Set(); // 表头同步隐藏"大小/类型/修改时间"列(与行内一致) document.querySelectorAll('[class*="checkbox-area"]').forEach(cbh => { const rh = cbh.getBoundingClientRect(); if (rh.width <= 0 || rh.top >= 150) return; let hrow = cbh; for (let d = 0; d < 6 && hrow; d++) { hrow = hrow.parentElement; if (!hrow) break; const hc = (typeof hrow.className === 'string') ? hrow.className : ''; if (hc.includes('px-4') || (hc.includes('flex') && hrow.children.length >= 5)) break; } if (!hrow || hrow.dataset.vtAfHead) return; if (/文件名/.test(hrow.textContent || '')) { hrow.dataset.vtAfHead = '1'; Array.from(hrow.children).forEach(k => { const ck = (typeof k.className === 'string') ? k.className : ''; if (ck.includes('w-24') || ck.includes('w-32')) k.style.display = 'none'; }); } }); document.querySelectorAll('[class*="checkbox-area"]').forEach(cb => { const r0 = cb.getBoundingClientRect(); if (r0.width <= 0 || r0.top < 150) return; // 跳过表头全选框 let row = cb; for (let d = 0; d < 7 && row; d++) { row = row.parentElement; if (!row) break; const cls = (typeof row.className === 'string') ? row.className : ''; if (cls.includes('px-4') || (cls.includes('flex') && cls.includes('items-center') && row.children.length >= 5)) break; } if (!row || seen.has(row) || row.dataset.vtAfMenu) return; if (row.closest('[role="dialog"], [class*="modal"], [class*="Modal"], [class*="dialog"]')) return; // 跳过表头行 const rt = (row.textContent || '').trim(); if (rt.includes('文件名') && (rt.includes('大小') || rt.includes('修改时间'))) return; seen.add(row); row.dataset.vtAfMenu = '1'; // 提取勾选框(保留原生点击)、图标列、文件名区——只做样式调整,【不移动子元素】 const cbEl = row.querySelector('[class*="checkbox-area"]'); const iconEl = row.children[2]; const nameEl = row.children[3]; // 隐藏行内其余子级(拖拽手柄/大小/类型/修改时间)——display:none 保留在 DOM 中,不破坏 115 自身节点结构 Array.from(row.children).forEach(k => { if (k === cbEl || k === iconEl || k === nameEl) return; k.style.display = 'none'; }); // 原位样式调整:图标列固定宽、文件名列弹性收缩 + 省略(不移动节点) if (iconEl) { iconEl.style.cssText = 'flex-shrink:0;width:34px;display:flex;align-items:center;justify-content:center;'; } if (nameEl) { nameEl.style.cssText = 'flex:1 1 auto;min-width:0;overflow:hidden;'; // 文件名内部文本强制单行省略,防止长名撑开行 const nameTxt = nameEl.querySelector('[class*="file-name"], [class*="name"]') || nameEl; nameTxt.style.whiteSpace = 'nowrap'; nameTxt.style.overflow = 'hidden'; nameTxt.style.textOverflow = 'ellipsis'; nameTxt.style.minWidth = '0'; } // 行容器:收缩适应 + 禁横向溢出(不建 inner、不移动子元素,避免破坏 115 的 DOM diff) row.style.minWidth = '0'; row.style.maxWidth = '100%'; row.style.overflow = 'hidden'; // ⋮ 菜单按钮:fixed 定位钉在屏幕右缘(不 append 到行内,不参与 115 行布局,DOM diff 兼容) const menuBtn = document.createElement('div'); menuBtn.className = 'vt115-rowmenu vt115-rowmenu-fixed'; menuBtn.textContent = '⋮'; menuBtn._vtRow = row; menuBtn.addEventListener('click', e => { e.stopPropagation(); // 再次点击已打开的菜单 → 退出菜单 if (afPop.style.display === 'block' && afPopRow === row) { afPop.style.display = 'none'; afPopRow = null; return; } afPopRow = row; // 动态更新星标菜单项:该行已星标 → 显示"取消星标" const isStarred = !!row.querySelector('[title="已星标"]'); afPop.querySelectorAll('.vt115-rowpop-item').forEach(it => { if (it.dataset.act === 'star') it.textContent = isStarred ? '取消星标' : '星标'; }); const r = menuBtn.getBoundingClientRect(); const pw = afPop.offsetWidth || 110; const left = Math.max(8, Math.min(r.right - pw, window.innerWidth - pw - 8)); const top = Math.min(r.bottom + 4, window.innerHeight - (afPop.offsetHeight || 280) - 8); afPop.style.left = left + 'px'; afPop.style.top = Math.max(8, top) + 'px'; afPop.style.display = 'block'; }); document.body.appendChild(menuBtn); }); positionMenus(); } // 定位所有 fixed"⋮"按钮到屏幕右缘、对应行的垂直位置;行不可见/已移除则隐藏 function positionMenus() { if (location.pathname.indexOf('/storage/allfiles') !== 0 && location.pathname.indexOf('/storage/starredfiles') !== 0) return; const rightX = window.innerWidth - 44; document.querySelectorAll('.vt115-rowmenu-fixed').forEach(mb => { const row = mb._vtRow; if (!row || !row.isConnected) { mb.style.display = 'none'; return; } const rr = row.getBoundingClientRect(); if (rr.width <= 0 || rr.height <= 0 || rr.top > window.innerHeight || rr.bottom < 0) { mb.style.display = 'none'; return; } mb.style.display = 'flex'; mb.style.left = rightX + 'px'; mb.style.top = Math.round(rr.top + rr.height / 2 - 18) + 'px'; }); } function apply() { // 隐藏右上角"切换旧版"按钮(fixed top-0 right-0 链接) document.querySelectorAll('a').forEach(a => { if ((a.textContent || '').trim() === '切换旧版') a.style.display = 'none'; }); // 回收站页:隐藏"全选""清空回收站"按钮与"列表/网格"视图切换(保留"还原""删除") if (location.pathname.indexOf('/storage/recyclebin') === 0) { document.querySelectorAll('button').forEach(btn => { const t = (btn.textContent || '').trim(); if (t === '全选' || t === '清空回收站') btn.style.display = 'none'; }); document.querySelectorAll('[title="列表视图"], [title="网格视图"]').forEach(v => { let c = v; for (let d = 0; d < 3 && c; d++) { const cls = (typeof c.className === 'string') ? c.className : ''; if (cls.indexOf('bg-gray-100') >= 0) { c.style.display = 'none'; break; } c = c.parentElement; } }); } // 云下载页:App 风格改造——隐藏原"添加云下载"按钮, // 右下角注入"+"号浮动按钮(点击触发原按钮,保持原生添加流程) const isCloudDL = location.pathname.indexOf('/storage/clouddownload') === 0; if (isCloudDL) { let round = 0; while (round < 3) { round++; let hit = false; document.querySelectorAll('div,button,a,span').forEach(el => { if ((el.textContent || '').trim() === '添加云下载' && el.getBoundingClientRect().width > 0) { el.style.display = 'none'; hit = true; } }); if (!hit) break; } if (!document.querySelector('.vt115-fab')) { const fab = document.createElement('div'); fab.className = 'vt115-fab'; fab.textContent = '+'; fab.addEventListener('click', () => { let btn = null; document.querySelectorAll('div,button,a,span').forEach(el => { if (!btn && (el.textContent || '').trim() === '添加云下载') btn = el; }); if (btn) btn.click(); }); document.body.appendChild(fab); } } else if (document.querySelector('.vt115-fab')) { document.querySelector('.vt115-fab').remove(); } setupCloudRows(); setupAllfilesRows(); fitDialogs(); // 注入底部功能导航(幂等:已注入则跳过) if (!document.querySelector('.vt115-tabbar')) { const bar = document.createElement('div'); bar.className = 'vt115-tabbar'; SIDEBAR_ITEMS.forEach(it => { const a = document.createElement('a'); a.href = it.href; a.className = 'vt115-tab-item'; a.innerHTML = `${it.name}`; bar.appendChild(a); }); document.body.appendChild(bar); } updateActive(); fixHScroll(); padContent(); } // 移动/复制弹窗对话框移动端适配:CSS vw 在无 viewport meta 的手机上等于布局视口宽(900px),限宽无效, // 改用 JS 按真实屏幕宽强制限宽(取 innerWidth/screen.width/clientWidth 的最小值,兼容整页缩放与溢出两种模式)。 // 根因:目录内出现长文件名会把对话框/页面横向撑大(与目录层级无关)——故需识别对话框容器并限宽, // 同时把内部所有超宽后代(尤其长文件名文本)限宽+省略,防止撑开容器。 function fitDialogs() { const realW = Math.min(window.innerWidth, window.screen.width || window.innerWidth, document.documentElement.clientWidth); const targetW = Math.max(240, realW - 16); const seen = new Set(); // 对话框特征容器:固定宽 420px 的旧版容器 + rounded-lg shadow-xl 真实容器 + max-h-[90vh] const sel = '[class*="w-[420px]"][class*="rounded-xl"], [class*="rounded-lg"][class*="shadow-xl"], [class*="max-h-[90vh]"]'; document.querySelectorAll(sel).forEach(dlg => { if (seen.has(dlg)) return; const r = dlg.getBoundingClientRect(); if (r.width <= 0 || r.width < 200) return; // 只处理移动/复制类对话框(含"新建文件夹"按钮或"移动到/复制到"标题),避免误伤普通卡片 const t = dlg.textContent || ''; if (t.indexOf('新建文件夹') < 0 && t.indexOf('移动到') < 0 && t.indexOf('复制到') < 0 && t.indexOf('移动文件') < 0) return; seen.add(dlg); if (dlg.dataset.vtFit !== String(targetW)) { dlg.dataset.vtFit = String(targetW); dlg.style.width = targetW + 'px'; dlg.style.maxWidth = targetW + 'px'; dlg.style.overflowX = 'hidden'; } // 内部防撑大:每次执行(幂等,处理新目录出现的长文件名) fitDialogContent(dlg, targetW); }); // 已展开的嵌套目录面板(left-full):仅限宽可见/可交互的,未展开(opacity-0/pointer-events-none)不动 document.querySelectorAll('[class*="left-full"]').forEach(p => { const cs = getComputedStyle(p); const r = p.getBoundingClientRect(); if (r.width <= 0 || r.width <= realW) return; if (cs.opacity === '0' || cs.pointerEvents === 'none' || cs.display === 'none') return; if (p.dataset.vtFit !== String(targetW)) { p.dataset.vtFit = String(targetW); p.style.width = targetW + 'px'; p.style.maxWidth = targetW + 'px'; p.style.overflowX = 'hidden'; } fitDialogContent(p, targetW); }); } // 对话框内部防撑大:所有宽度超过 targetW 的可见后代限宽;长文件名文本省略;flex 伸缩项允许收缩 function fitDialogContent(dlg, targetW) { const all = dlg.querySelectorAll('*'); for (let i = 0; i < all.length; i++) { const inner = all[i]; const ir = inner.getBoundingClientRect(); if (ir.width <= 0) continue; if (ir.width > targetW) { if (inner.children.length === 0) { inner.style.maxWidth = targetW + 'px'; inner.style.whiteSpace = 'nowrap'; inner.style.overflow = 'hidden'; inner.style.textOverflow = 'ellipsis'; } else { inner.style.maxWidth = '100%'; inner.style.overflowX = 'hidden'; } } const cs = getComputedStyle(inner); if (ir.width > 60 && (cs.flexGrow === '1' || cs.flexShrink === '1' || cs.flexBasis === '0%' || cs.flexBasis === '0px')) { inner.style.minWidth = '0'; inner.style.maxWidth = '100%'; } } // 文件名/文本类元素补省略(min-width:0 + ellipsis),防止 flex 行内长名撑开 dlg.querySelectorAll('[class*="file-name"],[class*="name"],[class*="title"]').forEach(n => { const cs2 = getComputedStyle(n); if (cs2.whiteSpace !== 'nowrap') { n.style.whiteSpace = 'nowrap'; n.style.overflow = 'hidden'; n.style.textOverflow = 'ellipsis'; n.style.minWidth = '0'; } }); } const mo = new MutationObserver(() => { apply(); }); mo.observe(document.documentElement, { childList: true, subtree: true }); apply(); // 滚动时即时重定位行尾"⋮"按钮 let scrollTick = false; window.addEventListener('scroll', () => { if (scrollTick) return; scrollTick = true; requestAnimationFrame(() => { positionMenus(); scrollTick = false; }); }, { passive: true }); // SPA 路由跳转后刷新高亮 + 兜底修复 setInterval(() => { updateActive(); fixHScroll(); padContent(); positionMenus(); }, 800); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', setupSidebar, { once: true }); } else { setupSidebar(); } })();