This commit is contained in:
condor
2026-07-24 19:05:07 +08:00
commit b46270264b
10 changed files with 901 additions and 0 deletions
+246
View File
@@ -0,0 +1,246 @@
<!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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}
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>