247 lines
11 KiB
HTML
247 lines
11 KiB
HTML
<!doctype html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Nginx-Fancyindex-Theme · 预览</title>
|
||
<link rel="stylesheet" href="theme/css/styles.css?v=3">
|
||
</head>
|
||
<body>
|
||
<!-- ===== header.html (fragment) ===== -->
|
||
<div class="wrapper">
|
||
<header class="site-header">
|
||
<form class="search-form" role="search" onsubmit="return false" style="display:flex;justify-content:center;align-items:center;gap:10px;">
|
||
<a class="up-btn" id="up-btn" href="#" aria-label="返回上一级目录" title="返回上一级目录">
|
||
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M13.414 5.414 7.828 11l5.586 5.586a1 1 0 0 1-1.414 1.414l-6.293-6.293a1 1 0 0 1 0-1.414l6.293-6.293a1 1 0 0 1 1.414 1.414Z"/></svg>
|
||
<span>上一级</span>
|
||
</a>
|
||
<input name="filter" id="s" type="search" placeholder="输入关键字筛选文件…" aria-label="搜索文件">
|
||
<a class="home-btn" href="/" aria-label="返回根目录" title="返回根目录">
|
||
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path fill="currentColor" d="M12 3.172 2.808 11.02a1 1 0 0 0 .65 1.758H5v7.25a1 1 0 0 0 1 1h3.5a1 1 0 0 0 1-1v-4.25h3v4.25a1 1 0 0 0 1 1H18a1 1 0 0 0 1-1v-7.25h1.542a1 1 0 0 0 .65-1.758L12 3.172Z"/></svg>
|
||
<span>根目录</span>
|
||
</a>
|
||
</form>
|
||
</header>
|
||
<h2 class="dir-title">
|
||
<span class="dir-label">Directory</span>
|
||
<nav class="breadcrumb" id="dir-path" aria-label="当前路径">/</nav>
|
||
</h2>
|
||
<script>
|
||
(function () {
|
||
function renderBreadcrumb() {
|
||
var host = document.getElementById('dir-path');
|
||
if (!host) return;
|
||
var path;
|
||
if (location.protocol === 'file:') {
|
||
path = '/';
|
||
} else {
|
||
path = decodeURIComponent(location.pathname);
|
||
if (path.charAt(path.length - 1) !== '/') path += '/';
|
||
}
|
||
var parts = path.split('/').filter(function (p) { return p !== ''; });
|
||
|
||
host.innerHTML = '';
|
||
var root = document.createElement('a');
|
||
root.href = '/';
|
||
root.textContent = '/';
|
||
host.appendChild(root);
|
||
|
||
var acc = '';
|
||
parts.forEach(function (p, i) {
|
||
acc += '/' + p;
|
||
var sep = document.createElement('span');
|
||
sep.className = 'sep';
|
||
sep.textContent = (i === 0) ? ' ' : ' / ';
|
||
host.appendChild(sep);
|
||
|
||
var a = document.createElement('a');
|
||
a.href = acc + '/';
|
||
a.textContent = p;
|
||
host.appendChild(a);
|
||
});
|
||
}
|
||
renderBreadcrumb();
|
||
|
||
// 递归搜索:抓取整棵目录树(当前目录 + 子目录),构建扁平索引后按关键字过滤
|
||
function initSearch() {
|
||
var list = document.getElementById('list');
|
||
var input = document.getElementById('s');
|
||
if (!list || !input) return;
|
||
|
||
var baseDir = location.pathname;
|
||
if (baseDir.charAt(baseDir.length - 1) !== '/') baseDir += '/';
|
||
|
||
var MAX_DEPTH = 8;
|
||
var indexPromise = null;
|
||
var resultsEl = null;
|
||
|
||
function ensureResults() {
|
||
if (resultsEl) return resultsEl;
|
||
resultsEl = document.createElement('div');
|
||
resultsEl.id = 'search-results';
|
||
resultsEl.hidden = true;
|
||
list.parentNode.insertBefore(resultsEl, list.nextSibling);
|
||
return resultsEl;
|
||
}
|
||
|
||
function crawl(dir, depth, results, seen) {
|
||
if (depth > MAX_DEPTH || seen[dir]) return Promise.resolve();
|
||
seen[dir] = true;
|
||
return fetch(dir, { credentials: 'same-origin' })
|
||
.then(function (r) { return r.ok ? r.text() : ''; })
|
||
.then(function (html) {
|
||
if (!html) return;
|
||
var doc = new DOMParser().parseFromString(html, 'text/html');
|
||
var trs = doc.querySelectorAll('table#list tbody tr');
|
||
var tasks = [];
|
||
Array.prototype.forEach.call(trs, function (tr) {
|
||
var a = tr.querySelector('td a');
|
||
if (!a) return;
|
||
var name = (a.textContent || '').trim();
|
||
var href = a.getAttribute('href');
|
||
if (!href || href === '../' || name === 'Parent Directory') return;
|
||
var isDir = /\/$/.test(href);
|
||
var full = href.charAt(0) === '/' ? href : dir + href;
|
||
|
||
// 提取 Size / Last modified(与表头列保持一致)
|
||
var cells = tr.querySelectorAll('td');
|
||
var size = cells.length > 1 ? (cells[1].textContent || '').trim() : '-';
|
||
var date = cells.length > 2 ? (cells[2].textContent || '').trim() : '-';
|
||
|
||
results.push({ name: name, full: full, isDir: isDir, size: size, date: date });
|
||
if (isDir) tasks.push(crawl(full, depth + 1, results, seen));
|
||
});
|
||
return Promise.all(tasks);
|
||
})
|
||
.catch(function () { /* 忽略不可访问的目录 */ });
|
||
}
|
||
|
||
function demoIndex() {
|
||
return Promise.resolve([
|
||
{ name: 'ubuntu-24.04.iso', full: '/ubuntu-24.04.iso', isDir: false, size: '5.1 GiB', date: '2026-01-12 09:24' },
|
||
{ name: 'docs', full: '/docs/', isDir: true, size: '-', date: '2026-03-04 18:02' },
|
||
{ name: 'readme.txt', full: '/docs/readme.txt', isDir: false, size: '12 KiB', date: '2026-03-10 10:00' },
|
||
{ name: 'guide.pdf', full: '/docs/guide.pdf', isDir: false, size: '1.4 MiB', date: '2026-03-12 14:30' },
|
||
{ name: 'backup.tar.gz', full: '/backup.tar.gz', isDir: false, size: '842 MiB', date: '2026-05-21 22:47' },
|
||
{ name: 'README.md', full: '/README.md', isDir: false, size: '3.3 KiB', date: '2026-06-30 11:15' },
|
||
{ name: 'scripts', full: '/scripts/', isDir: true, size: '-', date: '2026-07-01 07:53' },
|
||
{ name: 'deploy.sh', full: '/scripts/deploy.sh', isDir: false, size: '3 KiB', date: '2026-07-02 08:11' },
|
||
{ name: 'setup.sh', full: '/scripts/setup.sh', isDir: false, size: '5 KiB', date: '2026-07-03 09:40' }
|
||
]);
|
||
}
|
||
|
||
function buildIndex() {
|
||
if (indexPromise) return indexPromise;
|
||
if (location.protocol === 'file:') {
|
||
indexPromise = demoIndex();
|
||
} else {
|
||
var results = [];
|
||
var seen = {};
|
||
indexPromise = crawl(baseDir, 0, results, seen).then(function () { return results; });
|
||
}
|
||
return indexPromise;
|
||
}
|
||
|
||
function render(entries, q) {
|
||
var el = ensureResults();
|
||
if (!entries.length) {
|
||
el.innerHTML = '<p class="search-status">未找到匹配 “' + q + '” 的文件(已包含子目录)</p>';
|
||
return;
|
||
}
|
||
var rows = entries.map(function (e) {
|
||
var displayName = e.name + (e.isDir && !/[\/]$/.test(e.name) ? '/' : '');
|
||
return '<tr>' +
|
||
'<td><a href="' + escapeHtml(e.full) + '">' + escapeHtml(displayName) + '</a></td>' +
|
||
'<td>' + escapeHtml(e.size) + '</td>' +
|
||
'<td>' + escapeHtml(e.date) + '</td>' +
|
||
'<td> </td>' +
|
||
'</tr>';
|
||
}).join('');
|
||
el.innerHTML =
|
||
'<p class="search-status">找到 ' + entries.length + ' 个匹配项(含子目录)</p>' +
|
||
'<table class="results"><thead><tr><th>Name</th><th>Size</th><th>Last modified</th><th> </th></tr></thead>' +
|
||
'<tbody>' + rows + '</tbody></table>';
|
||
}
|
||
|
||
function escapeHtml(str) {
|
||
return String(str)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"');
|
||
}
|
||
|
||
input.addEventListener('input', function () {
|
||
var q = input.value.trim().toLowerCase();
|
||
if (!q) {
|
||
list.style.display = '';
|
||
if (resultsEl) resultsEl.hidden = true;
|
||
return;
|
||
}
|
||
list.style.display = 'none';
|
||
var el = ensureResults();
|
||
el.hidden = false;
|
||
if (!indexPromise) el.innerHTML = '<p class="search-status">正在索引子目录…</p>';
|
||
buildIndex().then(function (entries) {
|
||
var matched = entries.filter(function (e) {
|
||
return e.name.toLowerCase().indexOf(q) !== -1;
|
||
});
|
||
render(matched, q);
|
||
});
|
||
});
|
||
}
|
||
// 移除 ngx-fancyindex 在表格前输出的裸文本路径(如 /bitwarden/chrome-plugins/)
|
||
function cleanupStrayText() {
|
||
var roots = [document.querySelector('.wrapper'), document.body];
|
||
roots.forEach(function (root) {
|
||
if (!root) return;
|
||
var nodes = root.childNodes;
|
||
for (var i = nodes.length - 1; i >= 0; i--) {
|
||
var n = nodes[i];
|
||
if (n.nodeType === 3 && n.textContent.trim() !== '') {
|
||
n.parentNode.removeChild(n);
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
function initUpBtn() {
|
||
var btn = document.getElementById('up-btn');
|
||
if (!btn) return;
|
||
var parent;
|
||
if (location.protocol === 'file:') {
|
||
parent = '#'; // 本地预览无真实目录层级,仅展示按钮
|
||
} else {
|
||
var path = location.pathname.replace(/\/+$/, '');
|
||
var idx = path.lastIndexOf('/');
|
||
parent = (idx <= 0) ? '/' : path.slice(0, idx + 1);
|
||
}
|
||
btn.setAttribute('href', parent);
|
||
}
|
||
|
||
function onReady() {
|
||
initSearch();
|
||
initUpBtn();
|
||
cleanupStrayText();
|
||
}
|
||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', onReady);
|
||
else onReady();
|
||
})();
|
||
</script>
|
||
<!-- ===== injected listing (simulated) ===== -->
|
||
<table id="list">
|
||
<thead><tr><th>Name</th><th>Size</th><th>Last modified</th><th> </th></tr></thead>
|
||
<tbody>
|
||
<tr class="parent"><td><a href="../">../</a></td><td>-</td><td>-</td><td>-</td></tr>
|
||
<tr><td><a href="ubuntu-24.04.iso">ubuntu-24.04.iso</a></td><td>5.1 GiB</td><td>2026-01-12 09:24</td><td> </td></tr>
|
||
<tr><td><a href="docs/">docs/</a></td><td>-</td><td>2026-03-04 18:02</td><td> </td></tr>
|
||
<tr><td><a href="backup.tar.gz">backup.tar.gz</a></td><td>842 MiB</td><td>2026-05-21 22:47</td><td> </td></tr>
|
||
<tr><td><a href="README.md">README.md</a></td><td>3.3 KiB</td><td>2026-06-30 11:15</td><td> </td></tr>
|
||
<tr><td><a href="scripts/">scripts/</a></td><td>-</td><td>2026-07-01 07:53</td><td> </td></tr>
|
||
</tbody>
|
||
</table>
|
||
<!-- ===== footer.html (fragment) ===== -->
|
||
</div><!-- /.wrapper -->
|
||
</body>
|
||
</html>
|