feat(ui): sort global sources and keep breadcrumb on one line
Browse files- static/app.js +8 -2
- static/style.css +7 -1
- tests/test_browser.py +35 -0
- tests/test_search.py +9 -0
static/app.js
CHANGED
|
@@ -1323,7 +1323,10 @@ function renderCheckboxList(container, items, selected, onChange) {
|
|
| 1323 |
}
|
| 1324 |
|
| 1325 |
async function renderSourcesFilter() {
|
| 1326 |
-
|
|
|
|
|
|
|
|
|
|
| 1327 |
STATE.selectedSources = values;
|
| 1328 |
STATE.page = 1;
|
| 1329 |
updateFilterCancelButtons();
|
|
@@ -1724,7 +1727,10 @@ function retrySidebar(source, path, routeId, requestSequence) {
|
|
| 1724 |
|
| 1725 |
function renderSourceListSidebar(sources) {
|
| 1726 |
DOM.sidebarTitle.textContent = "数据包";
|
| 1727 |
-
|
|
|
|
|
|
|
|
|
|
| 1728 |
}
|
| 1729 |
|
| 1730 |
function browserContentHTML(data, path, includeBreadcrumb) {
|
|
|
|
| 1323 |
}
|
| 1324 |
|
| 1325 |
async function renderSourcesFilter() {
|
| 1326 |
+
const ordered = [...(STATE.sources || [])].sort((a, b) =>
|
| 1327 |
+
(a.count || 0) - (b.count || 0) ||
|
| 1328 |
+
String(a.name || "").localeCompare(String(b.name || ""), "zh-CN"));
|
| 1329 |
+
renderCheckboxList(DOM.filterSourceList, ordered.map((source) => ({ key: source.slug, label: source.name, count: source.count })), STATE.selectedSources, (values) => {
|
| 1330 |
STATE.selectedSources = values;
|
| 1331 |
STATE.page = 1;
|
| 1332 |
updateFilterCancelButtons();
|
|
|
|
| 1727 |
|
| 1728 |
function renderSourceListSidebar(sources) {
|
| 1729 |
DOM.sidebarTitle.textContent = "数据包";
|
| 1730 |
+
const ordered = [...(sources || [])].sort((a, b) =>
|
| 1731 |
+
(a.count || 0) - (b.count || 0) ||
|
| 1732 |
+
String(a.name || "").localeCompare(String(b.name || ""), "zh-CN"));
|
| 1733 |
+
DOM.sidebarContent.innerHTML = ordered.map((source) => `<button type="button" class="repo-list-item" data-source="${escapeHTML(source.slug)}" aria-label="打开数据包:${escapeHTML(source.name)}">${ICON_HTML.source}<span class="repo-name">${escapeHTML(source.name)}</span><span class="repo-count">${(source.count || 0).toLocaleString()}</span></button>`).join("");
|
| 1734 |
}
|
| 1735 |
|
| 1736 |
function browserContentHTML(data, path, includeBreadcrumb) {
|
static/style.css
CHANGED
|
@@ -774,12 +774,18 @@ body:not(.mobile) .preview-fullscreen .preview-body {
|
|
| 774 |
gap: 2px;
|
| 775 |
padding: 6px 8px;
|
| 776 |
border-bottom: 1px solid var(--outline-variant);
|
| 777 |
-
flex-wrap:
|
| 778 |
font-size: 12px;
|
| 779 |
flex-shrink: 0;
|
| 780 |
min-height: 32px;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 781 |
}
|
| 782 |
|
|
|
|
|
|
|
| 783 |
.sidebar-breadcrumb .crumb-item {
|
| 784 |
border: 0;
|
| 785 |
background: none;
|
|
|
|
| 774 |
gap: 2px;
|
| 775 |
padding: 6px 8px;
|
| 776 |
border-bottom: 1px solid var(--outline-variant);
|
| 777 |
+
flex-wrap: nowrap;
|
| 778 |
font-size: 12px;
|
| 779 |
flex-shrink: 0;
|
| 780 |
min-height: 32px;
|
| 781 |
+
overflow-x: auto;
|
| 782 |
+
overflow-y: hidden;
|
| 783 |
+
white-space: nowrap;
|
| 784 |
+
scrollbar-width: none;
|
| 785 |
}
|
| 786 |
|
| 787 |
+
.sidebar-breadcrumb::-webkit-scrollbar { display: none; }
|
| 788 |
+
|
| 789 |
.sidebar-breadcrumb .crumb-item {
|
| 790 |
border: 0;
|
| 791 |
background: none;
|
tests/test_browser.py
CHANGED
|
@@ -610,6 +610,41 @@ class BrowserInteractionTests(unittest.TestCase):
|
|
| 610 |
finally:
|
| 611 |
context.close()
|
| 612 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 613 |
def test_individual_batch_and_zip_downloads_are_browser_valid(self):
|
| 614 |
context, page, errors = self.page()
|
| 615 |
try:
|
|
|
|
| 610 |
finally:
|
| 611 |
context.close()
|
| 612 |
|
| 613 |
+
def test_global_source_order_and_breadcrumb_single_line(self):
|
| 614 |
+
context, page, errors = self.page()
|
| 615 |
+
try:
|
| 616 |
+
page.goto(self.base_url + "/CCRD", wait_until="domcontentloaded")
|
| 617 |
+
page.locator(".result-item").first.wait_for(timeout=30000)
|
| 618 |
+
result = page.evaluate("""() => {
|
| 619 |
+
STATE.sources = [
|
| 620 |
+
{slug: 'large', name: 'Large', count: 30},
|
| 621 |
+
{slug: 'small', name: 'Small', count: 2},
|
| 622 |
+
{slug: 'medium', name: 'Medium', count: 10},
|
| 623 |
+
];
|
| 624 |
+
renderSourceListSidebar(STATE.sources);
|
| 625 |
+
renderSourcesFilter();
|
| 626 |
+
const sidebar = Array.from(DOM.sidebarContent.querySelectorAll('.repo-list-item'), node => node.dataset.source);
|
| 627 |
+
const filter = Array.from(DOM.filterSourceList.querySelectorAll('input'), node => node.value);
|
| 628 |
+
DOM.sidebarContent.innerHTML = browserContentHTML({folders: [], files: []}, 'a/b', true);
|
| 629 |
+
const crumb = document.querySelector('.sidebar-breadcrumb');
|
| 630 |
+
const style = getComputedStyle(crumb);
|
| 631 |
+
return {
|
| 632 |
+
sidebar,
|
| 633 |
+
filter,
|
| 634 |
+
wrap: style.flexWrap,
|
| 635 |
+
overflowX: style.overflowX,
|
| 636 |
+
whiteSpace: style.whiteSpace,
|
| 637 |
+
};
|
| 638 |
+
}""")
|
| 639 |
+
self.assertEqual(result["sidebar"], ["small", "medium", "large"])
|
| 640 |
+
self.assertEqual(result["filter"], ["small", "medium", "large"])
|
| 641 |
+
self.assertEqual(result["wrap"], "nowrap")
|
| 642 |
+
self.assertEqual(result["overflowX"], "auto")
|
| 643 |
+
self.assertEqual(result["whiteSpace"], "nowrap")
|
| 644 |
+
self.assertEqual(errors, [])
|
| 645 |
+
finally:
|
| 646 |
+
context.close()
|
| 647 |
+
|
| 648 |
def test_individual_batch_and_zip_downloads_are_browser_valid(self):
|
| 649 |
context, page, errors = self.page()
|
| 650 |
try:
|
tests/test_search.py
CHANGED
|
@@ -1130,6 +1130,15 @@ class FrontendContractTests(unittest.TestCase):
|
|
| 1130 |
):
|
| 1131 |
self.assertIn(selector, source)
|
| 1132 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1133 |
def test_mobile_result_paths_keep_separators_with_their_folder(self):
|
| 1134 |
source = (Path(app.BASE_DIR) / "static" / "app.js").read_text(encoding="utf-8")
|
| 1135 |
css = (Path(app.BASE_DIR) / "static" / "style.css").read_text(encoding="utf-8")
|
|
|
|
| 1130 |
):
|
| 1131 |
self.assertIn(selector, source)
|
| 1132 |
|
| 1133 |
+
def test_global_source_lists_sort_by_count_and_breadcrumb_is_single_line(self):
|
| 1134 |
+
source = (Path(app.BASE_DIR) / "static" / "app.js").read_text(encoding="utf-8")
|
| 1135 |
+
css = (Path(app.BASE_DIR) / "static" / "style.css").read_text(encoding="utf-8")
|
| 1136 |
+
self.assertIn('(a.count || 0) - (b.count || 0)', source)
|
| 1137 |
+
self.assertIn('localeCompare(String(b.name || ""), "zh-CN")', source)
|
| 1138 |
+
self.assertIn("flex-wrap: nowrap", css)
|
| 1139 |
+
self.assertIn("overflow-x: auto", css)
|
| 1140 |
+
self.assertIn("white-space: nowrap", css)
|
| 1141 |
+
|
| 1142 |
def test_mobile_result_paths_keep_separators_with_their_folder(self):
|
| 1143 |
source = (Path(app.BASE_DIR) / "static" / "app.js").read_text(encoding="utf-8")
|
| 1144 |
css = (Path(app.BASE_DIR) / "static" / "style.css").read_text(encoding="utf-8")
|