Upload 2 files
Browse files- static/app.js +22 -5
- static/index.html +2 -2
static/app.js
CHANGED
|
@@ -16,7 +16,7 @@ const STATE = {
|
|
| 16 |
selectedSources: [],
|
| 17 |
minSize: null,
|
| 18 |
maxSize: null,
|
| 19 |
-
exact:
|
| 20 |
fulltext: false,
|
| 21 |
searchPaths: true,
|
| 22 |
fulltextManifest: null,
|
|
@@ -53,6 +53,18 @@ function tokenize(text) {
|
|
| 53 |
return Array.from(new Set(String(text || "").toLowerCase().match(/[a-z0-9]+|[\u4e00-\u9fff\u3400-\u4dbf]+/g) || []));
|
| 54 |
}
|
| 55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
function formatSize(bytes) {
|
| 57 |
if (!bytes) return "";
|
| 58 |
if (bytes < 1024) return bytes + " B";
|
|
@@ -353,10 +365,11 @@ function syncUrl(replace = true) {
|
|
| 353 |
if (STATE.minSize !== null) params.set("min_size", String(STATE.minSize));
|
| 354 |
if (STATE.maxSize !== null) params.set("max_size", String(STATE.maxSize));
|
| 355 |
if (DOM.sortSelect.value !== "relevance") params.set("sort", DOM.sortSelect.value);
|
| 356 |
-
if (STATE.exact) params.set("exact", "
|
| 357 |
if (STATE.fulltext) params.set("fulltext", "1");
|
| 358 |
if (!STATE.searchPaths) params.set("search_paths", "0");
|
| 359 |
if (!STATE.historyEnabled) params.set("history", "0");
|
|
|
|
| 360 |
const basePath = STATE.source ? `/${encodeURIComponent(STATE.source)}` : "/";
|
| 361 |
const nextUrl = params.toString() ? `${basePath}?${params.toString()}` : basePath;
|
| 362 |
if (replace) history.replaceState(null, "", nextUrl);
|
|
@@ -370,7 +383,7 @@ function loadUrlState() {
|
|
| 370 |
STATE.folderSelections = params.getAll("folder");
|
| 371 |
STATE.minSize = params.get("min_size") ? parseInt(params.get("min_size"), 10) : null;
|
| 372 |
STATE.maxSize = params.get("max_size") ? parseInt(params.get("max_size"), 10) : null;
|
| 373 |
-
STATE.exact = params.get("exact") ==
|
| 374 |
STATE.fulltext = params.get("fulltext") === "1";
|
| 375 |
STATE.searchPaths = params.get("search_paths") !== "0";
|
| 376 |
STATE.historyEnabled = params.get("history") !== "0";
|
|
@@ -381,6 +394,7 @@ function loadUrlState() {
|
|
| 381 |
DOM.searchPathsToggle.checked = STATE.searchPaths;
|
| 382 |
DOM.historyToggle.checked = STATE.historyEnabled;
|
| 383 |
DOM.sortSelect.value = sort;
|
|
|
|
| 384 |
if (STATE.minSize !== null) DOM.filterMinSize.value = STATE.minSize;
|
| 385 |
if (STATE.maxSize !== null) DOM.filterMaxSize.value = STATE.maxSize;
|
| 386 |
}
|
|
@@ -425,7 +439,7 @@ async function fulltextSearch() {
|
|
| 425 |
if (STATE.exact) {
|
| 426 |
matchedIds = Object.keys(payload.docs).filter((docId) => {
|
| 427 |
const snippet = (payload.snippets[docId] || "") + " " + (payload.docs[docId]?.display_name || "");
|
| 428 |
-
return
|
| 429 |
});
|
| 430 |
} else {
|
| 431 |
for (const token of queryTokens) {
|
|
@@ -1013,15 +1027,18 @@ function attachEvents() {
|
|
| 1013 |
STATE.rightSidebarOpen = !STATE.rightSidebarOpen;
|
| 1014 |
if (STATE.isMobile && STATE.rightSidebarOpen) STATE.leftSidebarOpen = false;
|
| 1015 |
updateSidebarVisibility();
|
|
|
|
| 1016 |
});
|
| 1017 |
DOM.closeFiltersBtn.addEventListener("click", () => {
|
| 1018 |
STATE.rightSidebarOpen = false;
|
| 1019 |
updateSidebarVisibility();
|
|
|
|
| 1020 |
});
|
| 1021 |
DOM.overlay.addEventListener("click", () => {
|
| 1022 |
STATE.leftSidebarOpen = false;
|
| 1023 |
STATE.rightSidebarOpen = false;
|
| 1024 |
updateSidebarVisibility();
|
|
|
|
| 1025 |
});
|
| 1026 |
DOM.themeBtn.addEventListener("click", () => {
|
| 1027 |
STATE.isDark = !STATE.isDark;
|
|
@@ -1305,8 +1322,8 @@ async function init() {
|
|
| 1305 |
applyTheme();
|
| 1306 |
STATE.isMobile = window.innerWidth <= 768;
|
| 1307 |
syncRoute();
|
| 1308 |
-
loadUrlState();
|
| 1309 |
applyMobileMode();
|
|
|
|
| 1310 |
STATE.sources = await API.getSources();
|
| 1311 |
STATE.sourceMap = Object.fromEntries(STATE.sources.map((source) => [source.slug, source]));
|
| 1312 |
await renderSourcesFilter();
|
|
|
|
| 16 |
selectedSources: [],
|
| 17 |
minSize: null,
|
| 18 |
maxSize: null,
|
| 19 |
+
exact: true,
|
| 20 |
fulltext: false,
|
| 21 |
searchPaths: true,
|
| 22 |
fulltextManifest: null,
|
|
|
|
| 53 |
return Array.from(new Set(String(text || "").toLowerCase().match(/[a-z0-9]+|[\u4e00-\u9fff\u3400-\u4dbf]+/g) || []));
|
| 54 |
}
|
| 55 |
|
| 56 |
+
function wildcardPatternToRegExp(pattern) {
|
| 57 |
+
const escaped = String(pattern || "").replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
| 58 |
+
return new RegExp(escaped.replace(/\*/g, ".*").replace(/\?/g, "."), "i");
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
function matchesExactQuery(text, query) {
|
| 62 |
+
const value = String(text || "");
|
| 63 |
+
const pattern = String(query || "");
|
| 64 |
+
if (pattern.includes("*") || pattern.includes("?")) return wildcardPatternToRegExp(pattern).test(value);
|
| 65 |
+
return value.toLowerCase().includes(pattern.toLowerCase());
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
function formatSize(bytes) {
|
| 69 |
if (!bytes) return "";
|
| 70 |
if (bytes < 1024) return bytes + " B";
|
|
|
|
| 365 |
if (STATE.minSize !== null) params.set("min_size", String(STATE.minSize));
|
| 366 |
if (STATE.maxSize !== null) params.set("max_size", String(STATE.maxSize));
|
| 367 |
if (DOM.sortSelect.value !== "relevance") params.set("sort", DOM.sortSelect.value);
|
| 368 |
+
if (!STATE.exact) params.set("exact", "0");
|
| 369 |
if (STATE.fulltext) params.set("fulltext", "1");
|
| 370 |
if (!STATE.searchPaths) params.set("search_paths", "0");
|
| 371 |
if (!STATE.historyEnabled) params.set("history", "0");
|
| 372 |
+
if (STATE.rightSidebarOpen) params.set("filters", "1");
|
| 373 |
const basePath = STATE.source ? `/${encodeURIComponent(STATE.source)}` : "/";
|
| 374 |
const nextUrl = params.toString() ? `${basePath}?${params.toString()}` : basePath;
|
| 375 |
if (replace) history.replaceState(null, "", nextUrl);
|
|
|
|
| 383 |
STATE.folderSelections = params.getAll("folder");
|
| 384 |
STATE.minSize = params.get("min_size") ? parseInt(params.get("min_size"), 10) : null;
|
| 385 |
STATE.maxSize = params.get("max_size") ? parseInt(params.get("max_size"), 10) : null;
|
| 386 |
+
STATE.exact = params.get("exact") !== "0";
|
| 387 |
STATE.fulltext = params.get("fulltext") === "1";
|
| 388 |
STATE.searchPaths = params.get("search_paths") !== "0";
|
| 389 |
STATE.historyEnabled = params.get("history") !== "0";
|
|
|
|
| 394 |
DOM.searchPathsToggle.checked = STATE.searchPaths;
|
| 395 |
DOM.historyToggle.checked = STATE.historyEnabled;
|
| 396 |
DOM.sortSelect.value = sort;
|
| 397 |
+
STATE.rightSidebarOpen = params.get("filters") === "1";
|
| 398 |
if (STATE.minSize !== null) DOM.filterMinSize.value = STATE.minSize;
|
| 399 |
if (STATE.maxSize !== null) DOM.filterMaxSize.value = STATE.maxSize;
|
| 400 |
}
|
|
|
|
| 439 |
if (STATE.exact) {
|
| 440 |
matchedIds = Object.keys(payload.docs).filter((docId) => {
|
| 441 |
const snippet = (payload.snippets[docId] || "") + " " + (payload.docs[docId]?.display_name || "");
|
| 442 |
+
return matchesExactQuery(snippet, query);
|
| 443 |
});
|
| 444 |
} else {
|
| 445 |
for (const token of queryTokens) {
|
|
|
|
| 1027 |
STATE.rightSidebarOpen = !STATE.rightSidebarOpen;
|
| 1028 |
if (STATE.isMobile && STATE.rightSidebarOpen) STATE.leftSidebarOpen = false;
|
| 1029 |
updateSidebarVisibility();
|
| 1030 |
+
syncUrl();
|
| 1031 |
});
|
| 1032 |
DOM.closeFiltersBtn.addEventListener("click", () => {
|
| 1033 |
STATE.rightSidebarOpen = false;
|
| 1034 |
updateSidebarVisibility();
|
| 1035 |
+
syncUrl();
|
| 1036 |
});
|
| 1037 |
DOM.overlay.addEventListener("click", () => {
|
| 1038 |
STATE.leftSidebarOpen = false;
|
| 1039 |
STATE.rightSidebarOpen = false;
|
| 1040 |
updateSidebarVisibility();
|
| 1041 |
+
syncUrl();
|
| 1042 |
});
|
| 1043 |
DOM.themeBtn.addEventListener("click", () => {
|
| 1044 |
STATE.isDark = !STATE.isDark;
|
|
|
|
| 1322 |
applyTheme();
|
| 1323 |
STATE.isMobile = window.innerWidth <= 768;
|
| 1324 |
syncRoute();
|
|
|
|
| 1325 |
applyMobileMode();
|
| 1326 |
+
loadUrlState();
|
| 1327 |
STATE.sources = await API.getSources();
|
| 1328 |
STATE.sourceMap = Object.fromEntries(STATE.sources.map((source) => [source.slug, source]));
|
| 1329 |
await renderSourcesFilter();
|
static/index.html
CHANGED
|
@@ -108,8 +108,8 @@
|
|
| 108 |
</div>
|
| 109 |
<div class="filter-section" id="exact-search-section">
|
| 110 |
<label class="toggle-row">
|
| 111 |
-
<span class="toggle-label">精准搜索</span>
|
| 112 |
-
<input type="checkbox" id="exact-search-toggle">
|
| 113 |
<span class="toggle-switch"></span>
|
| 114 |
</label>
|
| 115 |
</div>
|
|
|
|
| 108 |
</div>
|
| 109 |
<div class="filter-section" id="exact-search-section">
|
| 110 |
<label class="toggle-row">
|
| 111 |
+
<span class="toggle-label">精准搜索(支持通配符*和?)</span>
|
| 112 |
+
<input type="checkbox" id="exact-search-toggle" checked>
|
| 113 |
<span class="toggle-switch"></span>
|
| 114 |
</label>
|
| 115 |
</div>
|