Files
auto-index/footer.html
2025-12-03 04:15:17 +00:00

370 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script>
document.addEventListener('DOMContentLoaded', function() {
// 添加搜索框
const searchBox = document.createElement('div');
searchBox.innerHTML = `
<input type="text" id="search" placeholder="搜索文件..."
style="width: 100%; padding: 0.6rem; margin-bottom: 1rem;
border: 2px solid #e2e8f0; border-radius: 6px;
font-size: 0.9rem; background: white; color: #1e293b;">
`;
document.body.insertBefore(searchBox, document.querySelector('pre'));
const pre = document.querySelector('pre');
if (!pre) return;
// 添加表格头部
const headerDiv = document.createElement('div');
headerDiv.innerHTML = `
<div style="display: flex; background: #f8fafc; padding: 0.3rem 1rem;
border-bottom: 1px solid #e2e8f0; font-weight: 600; font-size: 0.8rem; color: #64748b;">
<div style="flex: 1; line-height: 1.5;">📋 文件名</div>
<div style="width: 100px; text-align: right; margin-right: 1rem; line-height: 1.5;">大小</div>
<div style="width: 140px; text-align: right; margin-right: 1rem; line-height: 1.5;">修改时间</div>
<div style="width: 50px; text-align: center; line-height: 1.5;">操作</div>
</div>
`;
pre.parentNode.insertBefore(headerDiv, pre);
// 获取原始HTML并解析
const originalHTML = pre.innerHTML;
// 使用正则表达式匹配链接和后续的日期大小信息
const regex = /<a href="([^"]*)"[^>]*>([^<]*)<\/a>\s*([^<]*?)(?=<a|$)/g;
let newHTML = '';
let match;
while ((match = regex.exec(originalHTML)) !== null) {
const href = match[1];
const filename = match[2];
const afterText = match[3].trim();
// 解析日期和大小 - nginx格式是: DD-MMM-YYYY HH:MM size
const parts = afterText.split(/\s+/).filter(p => p && p !== '-');
let date = '-', size = '-';
if (parts.length >= 2) {
date = parts[0] + ' ' + parts[1]; // 日期和时间
if (parts.length >= 3 && parts[2] !== '-') {
size = parts[2]; // 文件大小
}
}
const icon = getFileIcon(filename);
// 根据是否为文件夹设置不同颜色
const isFolder = filename.endsWith('/');
const linkColor = isFolder ? '#3b82f6' : '#1e293b'; // 文件夹蓝色,文件黑色
newHTML += `
<div style="display: flex; align-items: center; padding: 0.15rem 1rem;
background: white; transition: background-color 0.2s;"
onmouseenter="this.style.backgroundColor='#f8fafc'"
onmouseleave="this.style.backgroundColor='white'">
<a href="${href}" style="flex: 1; display: flex; text-decoration: none;
color: ${linkColor}; overflow: hidden;"
data-filename="${filename.toLowerCase()}">
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.9rem; line-height: 1.2;">${filename}</span>
</a>
<div style="width: 100px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatSize(size)}</div>
<div style="width: 140px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatDate(date)}</div>
<div style="width: 50px; text-align: center;"><button onclick="copyFileUrl(event, '${href}')" title="复制链接" style="padding: 0.15rem 0.4rem; border: 1px solid #d1d5db; border-radius: 3px; background: white; color: #374151; cursor: pointer; font-size: 0.75rem; line-height: 1.3; font-weight: 500; transition: all 0.2s;" onmouseenter="this.style.backgroundColor='#eff6ff'; this.style.borderColor='#3b82f6'; this.style.color='#2563eb';" onmouseleave="this.style.backgroundColor='white'; this.style.borderColor='#d1d5db'; this.style.color='#374151';">复制</button></div>
</div>
`;
}
// 彻底清理pre元素移除所有子节点
while (pre.firstChild) {
pre.removeChild(pre.firstChild);
}
// 设置pre样式容器字体为0消除空白但子元素会有自己的字体大小
pre.style.cssText = `
background: white;
border-radius: 6px;
padding: 0;
margin: 0;
font-family: inherit;
overflow: hidden;
display: block;
font-size: 0;
line-height: 0;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
`;
// 直接设置innerHTML让浏览器自动解析
pre.innerHTML = newHTML;
// 搜索功能
// 存储所有文件(包括子目录)
let allFiles = [];
let isSearching = false;
// 递归获取所有子目录的文件
async function fetchAllFiles(path = '', depth = 0, maxDepth = 10) {
if (depth > maxDepth) return [];
try {
const response = await fetch(path || './');
const html = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const links = doc.querySelectorAll('pre a');
const files = [];
for (const link of links) {
const href = link.getAttribute('href');
const filename = link.textContent;
if (href === '../') continue; // 跳过父目录
const fullPath = path ? path + href : href;
const afterText = link.nextSibling ? link.nextSibling.textContent.trim() : '';
const parts = afterText.split(/\s+/).filter(p => p && p !== '-');
let date = '-', size = '-';
if (parts.length >= 2) {
date = parts[0] + ' ' + parts[1];
if (parts.length >= 3 && parts[2] !== '-') {
size = parts[2];
}
}
files.push({
href: fullPath,
filename: filename,
fullPath: fullPath,
date: date,
size: size,
isFolder: filename.endsWith('/')
});
// 如果是文件夹,递归获取
if (filename.endsWith('/')) {
const subFiles = await fetchAllFiles(fullPath, depth + 1, maxDepth);
files.push(...subFiles);
}
}
return files;
} catch (e) {
console.error('获取目录失败:', path, e);
return [];
}
}
// 搜索功能
const searchInput = document.getElementById('search');
let searchTimeout;
searchInput.addEventListener('input', async function(e) {
const term = e.target.value.toLowerCase().trim();
// 清除之前的延时
clearTimeout(searchTimeout);
if (!term) {
// 如果搜索框为空,显示当前目录文件
const rows = pre.querySelectorAll('div[onmouseenter]');
rows.forEach(row => row.style.display = 'flex');
return;
}
// 延时300ms再搜索避免频繁请求
searchTimeout = setTimeout(async () => {
// 如果还没有加载所有文件,先加载
if (allFiles.length === 0 && !isSearching) {
isSearching = true;
searchInput.placeholder = '正在索引所有文件...';
searchInput.disabled = true;
allFiles = await fetchAllFiles('', 0, 5); // 最多5层深度
searchInput.disabled = false;
searchInput.placeholder = '搜索所有文件...';
isSearching = false;
}
// 过滤匹配的文件
const matchedFiles = allFiles.filter(f =>
f.filename.toLowerCase().includes(term) ||
f.fullPath.toLowerCase().includes(term)
);
// 隐藏当前所有行
const rows = pre.querySelectorAll('div[onmouseenter]');
rows.forEach(row => row.style.display = 'none');
// 如果有匹配结果,显示
if (matchedFiles.length > 0) {
let searchHTML = '';
matchedFiles.forEach(file => {
const linkColor = file.isFolder ? '#3b82f6' : '#1e293b';
searchHTML += `
<div style="display: flex; align-items: center; padding: 0.15rem 1rem;
background: white; transition: background-color 0.2s;"
onmouseenter="this.style.backgroundColor='#f8fafc'"
onmouseleave="this.style.backgroundColor='white'">
<a href="${file.href}" style="flex: 1; display: flex; text-decoration: none;
color: ${linkColor}; overflow: hidden;"
data-filename="${file.filename.toLowerCase()}">
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 0.9rem; line-height: 1.2;" title="${file.fullPath}">${file.fullPath}</span>
</a>
<div style="width: 100px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatSize(file.size)}</div>
<div style="width: 140px; text-align: right; color: #64748b; font-size: 0.85rem; line-height: 1.2; margin-right: 1rem;">${formatDate(file.date)}</div>
<div style="width: 50px; text-align: center;"><button onclick="copyFileUrl(event, '${file.href}')" title="复制链接" style="padding: 0.15rem 0.4rem; border: 1px solid #d1d5db; border-radius: 3px; background: white; color: #374151; cursor: pointer; font-size: 0.75rem; line-height: 1.3; font-weight: 500; transition: all 0.2s;" onmouseenter="this.style.backgroundColor='#eff6ff'; this.style.borderColor='#3b82f6'; this.style.color='#2563eb';" onmouseleave="this.style.backgroundColor='white'; this.style.borderColor='#d1d5db'; this.style.color='#374151';">复制</button></div>
</div>
`;
});
// 添加搜索结果到页面
const tempDiv = document.createElement('div');
tempDiv.innerHTML = searchHTML;
while (tempDiv.firstChild) {
pre.appendChild(tempDiv.firstChild);
}
}
}, 300);
});
});
function getFileIcon(filename) {
if (filename.endsWith('/')) return '📁';
const ext = filename.split('.').pop().toLowerCase();
const icons = {
'pdf': '📕', 'doc': '📘', 'docx': '📘',
'jpg': '🖼️', 'jpeg': '🖼️', 'png': '🖼️', 'gif': '🖼️',
'mp4': '🎬', 'avi': '🎬', 'mov': '🎬', 'mkv': '🎬',
'mp3': '🎵', 'wav': '🎵', 'ogg': '🎵',
'zip': '📦', 'rar': '📦', '7z': '📦', 'tar': '📦',
'exe': '⚙️', 'msi': '⚙️'
};
return icons[ext] || '📄';
}
function formatSize(size) {
if (!size || size === '-') return '-';
if (size.match(/[KMGT]/i)) return size;
const num = parseFloat(size);
if (isNaN(num)) return size;
if (num < 1024) return num + 'B';
if (num < 1048576) return (num/1024).toFixed(1) + 'K';
if (num < 1073741824) return (num/1048576).toFixed(1) + 'M';
return (num/1073741824).toFixed(1) + 'G';
}
function formatDate(dateStr) {
if (!dateStr || dateStr === '-') return '-';
return dateStr.replace(/(\d{2})-(\w{3})-(\d{4}) (\d{2}):(\d{2})/, '$3-$2-$1 $4:$5');
}
// 复制文件URL到剪贴板
function copyFileUrl(event, href) {
event.preventDefault();
event.stopPropagation();
// 构建完整URL - 处理路径拼接
let basePath = window.location.pathname;
// 如果路径以/结尾,去掉末尾的/
if (basePath.endsWith('/')) {
basePath = basePath.slice(0, -1);
}
// 如果href不以/开头,添加/
if (!href.startsWith('/')) {
href = '/' + href;
}
// 解码URL中的中文字符使其更易读
let decodedHref = href;
try {
decodedHref = decodeURIComponent(href);
} catch (e) {
// 如果解码失败使用原始href
}
const fullUrl = window.location.origin + basePath + decodedHref;
console.log('复制URL:', fullUrl); // 调试信息
// 复制到剪贴板
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(fullUrl).then(() => {
// 显示复制成功提示
const button = event.target.closest('button');
if (button) {
const originalText = button.textContent;
button.textContent = '已复制';
button.style.color = '#10b981';
button.style.borderColor = '#10b981';
button.style.backgroundColor = '#f0fdf4';
setTimeout(() => {
button.textContent = originalText;
button.style.color = '#374151';
button.style.borderColor = '#d1d5db';
button.style.backgroundColor = 'white';
}, 1500);
}
}).catch(err => {
console.error('复制失败:', err);
// 降级方案:使用传统方法
const button = event.target.closest('button');
fallbackCopyTextToClipboard(fullUrl, button);
});
} else {
// 不支持clipboard API使用降级方案
const button = event.target.closest('button');
fallbackCopyTextToClipboard(fullUrl, button);
}
}
// 降级复制方案
function fallbackCopyTextToClipboard(text, button) {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.width = '2em';
textArea.style.height = '2em';
textArea.style.padding = '0';
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
textArea.style.background = 'transparent';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
const successful = document.execCommand('copy');
if (successful && button) {
// 显示复制成功不使用alert
const originalText = button.textContent;
button.textContent = '已复制';
button.style.color = '#10b981';
button.style.borderColor = '#10b981';
button.style.backgroundColor = '#f0fdf4';
setTimeout(() => {
button.textContent = originalText;
button.style.color = '#374151';
button.style.borderColor = '#d1d5db';
button.style.backgroundColor = 'white';
}, 1500);
}
} catch (err) {
console.error('复制失败:', err);
}
document.body.removeChild(textArea);
}
</script>
<div style="margin-top: 2rem; text-align: center; color: #64748b; font-size: 0.875rem;">
<p>由 nginx autoindex 提供支持</p>
</div>