(function () { var form = document.getElementById('searchForm'); var input = document.getElementById('q'); var treeEl = document.getElementById('tree'); var treeStatus = document.getElementById('treeStatus'); var resultMeta = document.getElementById('resultMeta'); var resultsEl = document.getElementById('results'); var nodeState = new Map(); var runningSearchId = 0; var HOME_SEARCH_CACHE_KEY = 'fb.home.search.q'; function parseQuery() { var params = new URLSearchParams(location.search || ''); return (params.get('q') || '').trim(); } function readCachedQuery() { try { return (sessionStorage.getItem(HOME_SEARCH_CACHE_KEY) || '').trim(); } catch (e) { return ''; } } function writeCachedQuery(q) { try { sessionStorage.setItem(HOME_SEARCH_CACHE_KEY, (q || '').trim()); } catch (e) {} } function encodePath(path) { var p = (path || '').replace(/^\/+|\/+$/g, ''); if (!p) return ''; return p.split('/').map(function (s) { return encodeURIComponent(s); }).join('/'); } function fileLink(path) { return '/files/' + encodePath(path || ''); } function apiUrl(path) { var p = encodePath(path || ''); return '/api/resources/' + p; } function childPath(base, item) { if (item && typeof item.path === 'string' && item.path.length > 0) return item.path; var name = item && typeof item.name === 'string' ? item.name : ''; if (!base) return name; return base + '/' + name; } function isDir(item) { return !!(item && (item.isDir || item.is_dir || item.dir || item.type === 'directory')); } function itemName(item) { return (item && (item.name || item.filename || item.basename || '')) || ''; } async function getResource(path) { var res = await fetch(apiUrl(path), { credentials: 'include', headers: { 'Accept': 'application/json' } }); if (res.status === 401) { location.href = '/login?redirect=' + encodeURIComponent('/search' + location.search); throw new Error('unauthorized'); } if (!res.ok) throw new Error('HTTP ' + res.status); var data = await res.json(); var items = []; if (Array.isArray(data && data.items)) items = data.items; else if (Array.isArray(data && data.children)) items = data.children; return items.map(function (it) { return { name: itemName(it), path: childPath(path || '', it), isDir: isDir(it), raw: it }; }).filter(function (it) { return !!it.name; }); } function makeNode(path, name, dir) { return { path: path || '', name: name || '/', isDir: !!dir, loaded: false, loading: false, children: [] }; } function getNode(path) { var p = path || ''; if (!nodeState.has(p)) { var name = p ? p.split('/').pop() : '/'; nodeState.set(p, makeNode(p, name, true)); } return nodeState.get(p); } async function loadNode(path) { var node = getNode(path); if (!node.isDir || node.loaded || node.loading) return node; node.loading = true; try { var children = await getResource(path); node.children = children.sort(function (a, b) { if (a.isDir !== b.isDir) return a.isDir ? -1 : 1; return a.name.localeCompare(b.name, 'zh-CN'); }); node.loaded = true; } finally { node.loading = false; } return node; } function renderTreeNode(node, parentUl) { var li = document.createElement('li'); var row = document.createElement('div'); row.className = 'tree-row'; var tg = document.createElement('span'); tg.className = 'tree-toggle'; tg.textContent = node.isDir ? '▸' : ''; var ic = document.createElement('span'); ic.className = 'tree-icon'; ic.textContent = node.isDir ? '📁' : '📄'; var nm = document.createElement('span'); nm.className = 'tree-name'; nm.textContent = node.path ? node.name : '根目录'; row.appendChild(tg); row.appendChild(ic); row.appendChild(nm); var childWrap = document.createElement('ul'); childWrap.style.display = 'none'; row.addEventListener('click', async function () { if (!node.isDir) { input.value = node.name; return; } var opening = childWrap.style.display === 'none'; childWrap.style.display = opening ? 'block' : 'none'; tg.textContent = opening ? '▾' : '▸'; if (opening && childWrap.childElementCount === 0) { var children = []; try { await loadNode(node.path); children = getNode(node.path).children; } catch (e) { var err = document.createElement('li'); err.className = 'hint'; err.textContent = '加载失败: ' + (e.message || e); childWrap.appendChild(err); return; } children.forEach(function (ch) { var childNode = makeNode(ch.path, ch.name, ch.isDir); nodeState.set(ch.path, childNode); renderTreeNode(childNode, childWrap); }); } }); li.appendChild(row); li.appendChild(childWrap); parentUl.appendChild(li); } async function initTree() { treeStatus.textContent = '加载中...'; treeEl.innerHTML = ''; var root = getNode(''); try { await loadNode(''); var ul = document.createElement('ul'); renderTreeNode(root, ul); treeEl.appendChild(ul); treeStatus.textContent = '仅显示当前账号有权限访问的目录和文件'; } catch (e) { treeStatus.textContent = '文件树加载失败: ' + (e.message || e); } } function setResultMeta(text) { resultMeta.textContent = text; } function renderResults(items) { resultsEl.innerHTML = ''; if (items.length === 0) { var empty = document.createElement('div'); empty.className = 'hint'; empty.textContent = '没有匹配结果'; resultsEl.appendChild(empty); return; } items.forEach(function (it) { var box = document.createElement('div'); box.className = 'result-item'; var title = document.createElement('a'); title.href = fileLink(it.path); title.textContent = (it.isDir ? '[目录] ' : '[文件] ') + it.name; var path = document.createElement('div'); path.className = 'result-path'; path.textContent = it.path || '/'; box.appendChild(title); box.appendChild(path); resultsEl.appendChild(box); }); } async function runSearch(q) { var id = ++runningSearchId; var query = (q || '').trim().toLowerCase(); if (!query) { setResultMeta('输入关键字后开始搜索'); resultsEl.innerHTML = ''; return; } setResultMeta('搜索中...'); var queue = ['']; var visited = new Set(); var matched = []; var scanned = 0; var maxNodes = 2500; while (queue.length > 0) { if (id !== runningSearchId) return; var current = queue.shift(); if (visited.has(current)) continue; visited.add(current); var children = []; try { var node = await loadNode(current); children = node.children || []; } catch (e) { continue; } for (var i = 0; i < children.length; i += 1) { var it = children[i]; scanned += 1; if (it.name.toLowerCase().indexOf(query) >= 0) matched.push(it); if (it.isDir) queue.push(it.path); if (scanned >= maxNodes) { setResultMeta('已扫描 ' + scanned + ' 项(达到上限),结果 ' + matched.length + ' 条'); renderResults(matched); return; } } } setResultMeta('已扫描 ' + scanned + ' 项,结果 ' + matched.length + ' 条'); renderResults(matched); } function syncQueryFromUrl() { var fromUrl = parseQuery(); var q = fromUrl || readCachedQuery(); input.value = q; if (q) { if (!fromUrl) { history.replaceState(null, '', '/search?q=' + encodeURIComponent(q)); } runSearch(q); } } form.addEventListener('submit', function (e) { e.preventDefault(); var q = (input.value || '').trim(); writeCachedQuery(q); var url = '/search' + (q ? ('?q=' + encodeURIComponent(q)) : ''); history.replaceState(null, '', url); runSearch(q); }); input.addEventListener('keydown', function (e) { if (e.key === 'Escape') { input.value = ''; writeCachedQuery(''); history.replaceState(null, '', '/search'); runSearch(''); } }); initTree(); syncQueryFromUrl(); })();