|
|
<!DOCTYPE html>
|
|
|
<html lang="zh_CN">
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<title>B612 Http Server {{ .IdxTitle }}</title>
|
|
|
<style>
|
|
|
* {
|
|
|
-webkit-box-sizing: border-box;
|
|
|
-moz-box-sizing: border-box;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
body {
|
|
|
background-color: #f5f5f5;
|
|
|
font-family: Arial, sans-serif;
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
display: -webkit-box;
|
|
|
display: -moz-box;
|
|
|
display: -ms-flexbox;
|
|
|
display: -webkit-flex;
|
|
|
display: flex;
|
|
|
-webkit-flex-direction: column;
|
|
|
-moz-flex-direction: column;
|
|
|
-ms-flex-direction: column;
|
|
|
flex-direction: column;
|
|
|
height: 100vh;
|
|
|
position: relative; /* 为了使背景图片固定 */
|
|
|
}
|
|
|
.background {
|
|
|
position: fixed;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
z-index: -1;
|
|
|
{{ .Photo }} /* 这里的背景图片 URL 是动态插入的 */
|
|
|
background-size: cover;
|
|
|
opacity: 0.5; /* 调整透明度 */
|
|
|
}
|
|
|
@media screen and (max-width: 768px) {
|
|
|
.background {
|
|
|
{{ .MobilePhoto }}
|
|
|
background-size: cover;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.container {
|
|
|
-webkit-flex: 1;
|
|
|
-moz-flex: 1;
|
|
|
-ms-flex: 1;
|
|
|
flex: 1;
|
|
|
width: 50%;
|
|
|
max-width: 1920px;
|
|
|
margin: 0 auto;
|
|
|
padding: 24px;
|
|
|
display: -webkit-box;
|
|
|
display: -moz-box;
|
|
|
display: -ms-flexbox;
|
|
|
display: -webkit-flex;
|
|
|
display: flex;
|
|
|
-webkit-flex-direction: column;
|
|
|
-moz-flex-direction: column;
|
|
|
-ms-flex-direction: column;
|
|
|
flex-direction: column;
|
|
|
background: rgba(245, 245, 245, 0.5); /* 添加一个半透明背景层 */
|
|
|
}
|
|
|
h1 {
|
|
|
text-align: center;
|
|
|
margin-bottom: 24px;
|
|
|
}
|
|
|
.table-container {
|
|
|
-webkit-flex: 1;
|
|
|
-moz-flex: 1;
|
|
|
-ms-flex: 1;
|
|
|
flex: 1;
|
|
|
overflow-y: auto;
|
|
|
overflow-x: hidden;
|
|
|
position: relative;
|
|
|
}
|
|
|
table {
|
|
|
width: 100%;
|
|
|
border-collapse: collapse;
|
|
|
margin-top: 24px;
|
|
|
table-layout: fixed; /* 确保表格单元格宽度固定 */
|
|
|
}
|
|
|
th, td {
|
|
|
padding: 12px;
|
|
|
text-align: left;
|
|
|
border-bottom: 1px solid #ddd;
|
|
|
white-space: normal; /* 允许换行 */
|
|
|
background: rgba(245, 245, 245, 0.5); /* 设置表格单元格背景为半透明 */
|
|
|
}
|
|
|
th[data-sort]:after {
|
|
|
content: "▲";
|
|
|
display: inline-block;
|
|
|
height: 20px;
|
|
|
width: 20px;
|
|
|
margin-left: 10px;
|
|
|
vertical-align: middle;
|
|
|
opacity: 0.3;
|
|
|
-webkit-transition: all 0.2s ease-in-out;
|
|
|
-moz-transition: all 0.2s ease-in-out;
|
|
|
-ms-transition: all 0.2s ease-in-out;
|
|
|
-o-transition: all 0.2s ease-in-out;
|
|
|
transition: all 0.2s ease-in-out;
|
|
|
}
|
|
|
th.asc:after {
|
|
|
content: "▲";
|
|
|
opacity: 1;
|
|
|
}
|
|
|
th.desc:after {
|
|
|
content: "▼";
|
|
|
opacity: 1;
|
|
|
}
|
|
|
th:hover:after {
|
|
|
opacity: 1;
|
|
|
}
|
|
|
.filename {
|
|
|
color: #007bff;
|
|
|
text-decoration: underline;
|
|
|
display: block;
|
|
|
max-width: 600px; /* 调整你想要的最大宽度 */
|
|
|
word-wrap: break-word; /* 确保文件名能够换行 */
|
|
|
}
|
|
|
.filename:hover {
|
|
|
color: #0056b3;
|
|
|
}
|
|
|
tr:hover {
|
|
|
background-color: rgba(241, 241, 241, 0.5); /* 设置鼠标悬停效果的背景为半透明 */
|
|
|
}
|
|
|
.filetype {
|
|
|
text-transform: uppercase;
|
|
|
}
|
|
|
@media screen and (max-width: 600px) {
|
|
|
table {
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
}
|
|
|
@media (orientation: portrait) and (max-width: 1024px) {
|
|
|
.container {
|
|
|
width: 60%;
|
|
|
}
|
|
|
}
|
|
|
@media (max-width: 400px) {
|
|
|
.container {
|
|
|
width: 80%;
|
|
|
}
|
|
|
}
|
|
|
thead th {
|
|
|
position: -webkit-sticky;
|
|
|
position: -moz-sticky;
|
|
|
position: sticky;
|
|
|
top: 0;
|
|
|
z-index: 1;
|
|
|
background: rgba(245, 245, 245, 0.5); /* 设置表头背景为半透明 */
|
|
|
}
|
|
|
tbody td:first-child,
|
|
|
thead th:first-child {
|
|
|
position: -webkit-sticky;
|
|
|
position: -moz-sticky;
|
|
|
position: sticky;
|
|
|
left: 0;
|
|
|
z-index: 1;
|
|
|
background: rgba(245, 245, 245, 0.5); /* 设置第一列背景为半透明 */
|
|
|
}
|
|
|
hr {
|
|
|
width: 100%;
|
|
|
}
|
|
|
|
|
|
.search-container {
|
|
|
display: -webkit-box;
|
|
|
display: -moz-box;
|
|
|
display: -ms-flexbox;
|
|
|
display: -webkit-flex;
|
|
|
display: flex;
|
|
|
-webkit-justify-content: center;
|
|
|
-moz-justify-content: center;
|
|
|
-ms-justify-content: center;
|
|
|
justify-content: center;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.search-container input {
|
|
|
padding: 10px;
|
|
|
width: 80%;
|
|
|
max-width: 400px;
|
|
|
font-size: 16px;
|
|
|
border: 1px solid #ddd;
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
<div class="background"></div>
|
|
|
<div class="container">
|
|
|
<h1>B612 Http Server - {{ .Version }}</h1>
|
|
|
<hr />
|
|
|
<h2>{{ .Idx }}</h2>
|
|
|
{{ .Upload }}
|
|
|
<div class="search-container">
|
|
|
<input type="text" id="search-box" placeholder="Search for a file..." />
|
|
|
</div>
|
|
|
<div class="table-container" id="table-container">
|
|
|
<table>
|
|
|
<thead>
|
|
|
<tr>
|
|
|
<th data-sort="name">Name</th>
|
|
|
<th data-sort="modified">Modified</th>
|
|
|
<th data-sort="size">Size</th>
|
|
|
<th data-sort="type">Type</th>
|
|
|
</tr>
|
|
|
</thead>
|
|
|
<tbody id="table-content"><!-- table content is dynamically filled by JavaScript -->
|
|
|
</tbody>
|
|
|
</table>
|
|
|
</div>
|
|
|
<hr />
|
|
|
<h2 style="text-align: center;">B612.Me © Apache 2.0 License</h2>
|
|
|
</div>
|
|
|
|
|
|
<!-- Context menu for copying file details -->
|
|
|
<div id="context-menu" style="display:none; position: absolute; z-index: 1000; background: white; border: 1px solid #ccc; padding: 5px;">
|
|
|
<ul style="list-style: none; margin: 0; padding: 0;">
|
|
|
<li id="copy-filename" style="padding: 5px; cursor: pointer;">复制文件名</li>
|
|
|
<li id="copy-link" style="padding: 5px; cursor: pointer;">复制文件链接地址</li>
|
|
|
<li id="copy-size-bytes" style="padding: 5px; cursor: pointer;">复制文件大小(按字节)</li>
|
|
|
<li id="copy-size-display" style="padding: 5px; cursor: pointer;">复制文件大小(按显示)</li>
|
|
|
</ul>
|
|
|
</div>
|
|
|
|
|
|
<script>
|
|
|
// 初始化内容
|
|
|
var dataRows = {{ .Data }};
|
|
|
|
|
|
function loadTableContent(dataRows) {
|
|
|
var tableContent = document.getElementById('table-content');
|
|
|
if (!tableContent) return;
|
|
|
|
|
|
var html = '';
|
|
|
dataRows.forEach(function(row) {
|
|
|
html += '<tr>';
|
|
|
html += '<td><a class="filename" href="' + row.attr + '">' + row.name + '</a></td>';
|
|
|
html += '<td>' + row.modified + '</td>';
|
|
|
html += '<td title="' + row.size + ' bytes">' + formatSize(row.size) + '</td>';
|
|
|
html += '<td class="filetype">' + row.type + '</td>';
|
|
|
html += '</tr>';
|
|
|
});
|
|
|
|
|
|
tableContent.innerHTML = html;
|
|
|
}
|
|
|
function renderRows(rows) {
|
|
|
var tableContent = document.getElementById('table-content');
|
|
|
var fragment = document.createDocumentFragment();
|
|
|
rows.forEach(function(row) {
|
|
|
var tr = document.createElement('tr');
|
|
|
tr.innerHTML += '<td><a class="filename" href="' + row.attr + '">' + row.name + '</a></td>';
|
|
|
tr.innerHTML += '<td>' + row.modified + '</td>';
|
|
|
var formattedSize = formatSize(row.size);
|
|
|
tr.innerHTML += '<td title="' + row.size + ' bytes">' + formattedSize + '</td>';
|
|
|
tr.innerHTML += '<td class="filetype">' + row.type + '</td>';
|
|
|
fragment.appendChild(tr);
|
|
|
});
|
|
|
tableContent.innerHTML = ''; // 清空现有内容
|
|
|
tableContent.appendChild(fragment);
|
|
|
}
|
|
|
|
|
|
function chunkedRenderRows() {
|
|
|
var chunkSize = 50;
|
|
|
var currentIndex = 0;
|
|
|
|
|
|
function renderChunk() {
|
|
|
var fragment = document.createDocumentFragment();
|
|
|
for (var i = currentIndex; i < currentIndex + chunkSize && i < dataRows.length; i++) {
|
|
|
var row = dataRows[i];
|
|
|
if (row.name === '..' && i !== 0) continue;
|
|
|
var tr = document.createElement('tr');
|
|
|
tr.innerHTML += '<td><a class="filename" href="' + row.attr + '">' + row.name + '</a></td>';
|
|
|
tr.innerHTML += '<td>' + row.modified + '</td>';
|
|
|
var formattedSize = formatSize(row.size);
|
|
|
tr.innerHTML += '<td title="' + row.size + ' bytes">' + formattedSize + '</td>';
|
|
|
tr.innerHTML += '<td class="filetype">' + row.type + '</td>';
|
|
|
fragment.appendChild(tr);
|
|
|
}
|
|
|
document.getElementById('table-content').appendChild(fragment);
|
|
|
currentIndex += chunkSize;
|
|
|
if (currentIndex < dataRows.length) {
|
|
|
requestAnimationFrame(renderChunk);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 清空现有内容
|
|
|
document.getElementById('table-content').innerHTML = '';
|
|
|
// 开始分块渲染
|
|
|
requestAnimationFrame(renderChunk);
|
|
|
}
|
|
|
|
|
|
function parseSize(size) {
|
|
|
var units = { 'KB': 1024, 'MB': 1024 * 1024, 'GB': 1024 * 1024 * 1024 };
|
|
|
var match = size.match(/(\d+\.?\d*)\s*(KB|MB|GB)/i);
|
|
|
|
|
|
if (match) {
|
|
|
return parseFloat(match[1]) * (units[match[2].toUpperCase()] || 1);
|
|
|
}
|
|
|
return parseInt(size, 10);
|
|
|
}
|
|
|
|
|
|
function formatSize(size) {
|
|
|
if (size < 0) return "-";
|
|
|
if (size < 1024) return size + ' B';
|
|
|
else if (size < 1024 * 1024) return (size / 1024).toFixed(2) + ' KB';
|
|
|
else if (size < 1024 * 1024 * 1024) return (size / (1024 * 1024)).toFixed(2) + ' MB';
|
|
|
else return (size / (1024 * 1024 * 1024)).toFixed(2) + ' GB';
|
|
|
}
|
|
|
|
|
|
function sortTable(th, n, initial) {
|
|
|
var direction = th.classList.contains('asc') && !initial ? 'desc' : 'asc';
|
|
|
dataRows.sort(function(a, b) {
|
|
|
// 检查 'name' 字段以确保 '..' 始终在第一位
|
|
|
if (a.name === '..') return -1;
|
|
|
if (b.name === '..') return 1;
|
|
|
|
|
|
var x = Object.values(a)[n];
|
|
|
var y = Object.values(b)[n];
|
|
|
|
|
|
if (n === 1) { // modified column
|
|
|
// 解析日期字符串
|
|
|
x = new Date(a.modified);
|
|
|
y = new Date(b.modified);
|
|
|
} else if (n === 2) { // size column
|
|
|
x = a.size;
|
|
|
y = b.size;
|
|
|
}
|
|
|
|
|
|
return direction === 'asc' ?
|
|
|
(x < y ? -1 : x > y ? 1 : 0) :
|
|
|
(x > y ? -1 : x < y ? 1 : 0);
|
|
|
});
|
|
|
|
|
|
th.classList.toggle('asc', direction === 'asc' && !initial);
|
|
|
th.classList.toggle('desc', direction === 'desc' && !initial);
|
|
|
updateSortIcons(th);
|
|
|
renderRows(dataRows);
|
|
|
}
|
|
|
|
|
|
|
|
|
function updateSortIcons(th) {
|
|
|
var ths = document.querySelectorAll('thead th[data-sort]');
|
|
|
ths.forEach(function(header) {
|
|
|
if (header !== th) {
|
|
|
header.classList.remove('asc');
|
|
|
header.classList.remove('desc');
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 初次加载时按 Name 列进行升序排序
|
|
|
function initialSort() {
|
|
|
var nameHeader = document.querySelector('th[data-sort="name"]');
|
|
|
if (nameHeader) {
|
|
|
nameHeader.classList.add('asc'); // 直接设置为升序状态
|
|
|
sortTable(nameHeader, 0, true); // 传递一个参数表示这是初始排序
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function isIE() {
|
|
|
return window.navigator.userAgent.indexOf("MSIE ") > -1 || navigator.userAgent.indexOf("Trident/") > -1;
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!isIE()) {
|
|
|
document.addEventListener('DOMContentLoaded', function(event) {
|
|
|
var ths = document.querySelectorAll('thead th[data-sort]');
|
|
|
ths.forEach(function(th, i) {
|
|
|
th.addEventListener('click', function() {
|
|
|
sortTable(th, i);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// 使用 chunkedRenderRows 进行初始渲染, 然后进行默认排序
|
|
|
renderRows(dataRows);
|
|
|
|
|
|
// 初次加载时按 Name 列进行升序排序
|
|
|
initialSort();
|
|
|
|
|
|
// 初始化右键菜单
|
|
|
initializeContextMenu();
|
|
|
});
|
|
|
// 处理搜索框输入事件
|
|
|
document.getElementById('search-box').addEventListener('input', function (e) {
|
|
|
var searchText = e.target.value.toLowerCase();
|
|
|
var filteredRows = dataRows.filter(function (row) {
|
|
|
return row.name.toLowerCase().includes(searchText);
|
|
|
});
|
|
|
renderRows(filteredRows);
|
|
|
});
|
|
|
}else{
|
|
|
loadTableContent(dataRows);
|
|
|
// 禁用搜索框
|
|
|
var searchBox = document.getElementById('search-box');
|
|
|
if (searchBox) {
|
|
|
searchBox.disabled = true;
|
|
|
searchBox.placeholder = "IE浏览器不受支持,大部分功能受限";
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function initializeContextMenu() {
|
|
|
var contextMenu = document.getElementById('context-menu');
|
|
|
document.addEventListener('contextmenu', function(e) {
|
|
|
if (e.target.classList.contains('filetype')) {
|
|
|
e.preventDefault();
|
|
|
var row = e.target.closest('tr');
|
|
|
var fileNameToCopy = row.querySelector('.filename').textContent;
|
|
|
var linkToCopy = row.querySelector('.filename').href;
|
|
|
var byteSizeToCopy = row.querySelector('td[title]') ? row.querySelector('td[title]').getAttribute('title') : '';
|
|
|
var displaySizeToCopy = row.querySelector('td[title]') ? row.querySelector('td[title]').textContent : '';
|
|
|
|
|
|
contextMenu.style.display = 'block';
|
|
|
contextMenu.style.top = e.pageY + 'px';
|
|
|
contextMenu.style.left = e.pageX + 'px';
|
|
|
contextMenu.setAttribute('data-filename', fileNameToCopy);
|
|
|
contextMenu.setAttribute('data-link', linkToCopy);
|
|
|
contextMenu.setAttribute('data-size-bytes', byteSizeToCopy);
|
|
|
contextMenu.setAttribute('data-size-display', displaySizeToCopy);
|
|
|
} else {
|
|
|
contextMenu.style.display = 'none';
|
|
|
}
|
|
|
});
|
|
|
|
|
|
document.addEventListener('click', function() {
|
|
|
contextMenu.style.display = 'none';
|
|
|
});
|
|
|
|
|
|
document.getElementById('copy-filename').addEventListener('click', function() {
|
|
|
var fileName = contextMenu.getAttribute('data-filename');
|
|
|
if (fileName) {
|
|
|
copyToClipboard(fileName);
|
|
|
}
|
|
|
contextMenu.style.display = 'none';
|
|
|
});
|
|
|
|
|
|
document.getElementById('copy-link').addEventListener('click', function() {
|
|
|
var fileLink = contextMenu.getAttribute('data-link');
|
|
|
if (fileLink) {
|
|
|
copyToClipboard(fileLink);
|
|
|
}
|
|
|
contextMenu.style.display = 'none';
|
|
|
});
|
|
|
|
|
|
document.getElementById('copy-size-bytes').addEventListener('click', function() {
|
|
|
var fileSizeBytes = contextMenu.getAttribute('data-size-bytes');
|
|
|
if (fileSizeBytes) {
|
|
|
copyToClipboard(fileSizeBytes);
|
|
|
}
|
|
|
contextMenu.style.display = 'none';
|
|
|
});
|
|
|
|
|
|
document.getElementById('copy-size-display').addEventListener('click', function() {
|
|
|
var fileSizeDisplay = contextMenu.getAttribute('data-size-display');
|
|
|
if (fileSizeDisplay) {
|
|
|
copyToClipboard(fileSizeDisplay);
|
|
|
}
|
|
|
contextMenu.style.display = 'none';
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function copyToClipboard(text) {
|
|
|
var textarea = document.createElement('textarea');
|
|
|
textarea.value = text;
|
|
|
document.body.appendChild(textarea);
|
|
|
textarea.select();
|
|
|
document.execCommand('copy');
|
|
|
document.body.removeChild(textarea);
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
</body>
|
|
|
</html>
|