From 981650bb72ebc5f7d1e753b21a4337236feefb0e Mon Sep 17 00:00:00 2001
From: hayden <1623906259@qq.com>
Date: Sun, 28 Jun 2026 23:31:19 +0800
Subject: [PATCH] Improve project detail panel performance
---
website/static/main.js | 90 +++++++++++++++++++++---
website/static/style.css | 148 +++++++++++++++++++++++++++++++++++++++
2 files changed, 229 insertions(+), 9 deletions(-)
diff --git a/website/static/main.js b/website/static/main.js
index d5b337b341..0b79b52a96 100644
--- a/website/static/main.js
+++ b/website/static/main.js
@@ -14,6 +14,8 @@ const noResults = document.querySelector(".no-results");
const rows = document.querySelectorAll(".table tbody tr.row");
const tags = document.querySelectorAll(".tag");
const tbody = document.querySelector(".table tbody");
+let activeDetailRow = null;
+let detailPanel = null;
function initRevealSections() {
const sections = document.querySelectorAll("[data-reveal]");
@@ -128,6 +130,77 @@ function collapseAll() {
row.classList.remove("open");
row.setAttribute("aria-expanded", "false");
});
+ closeDetailPanel();
+}
+
+function getDetailPanel() {
+ if (detailPanel) return detailPanel;
+
+ detailPanel = document.createElement("aside");
+ detailPanel.id = "project-detail-panel";
+ detailPanel.className = "detail-panel";
+ detailPanel.setAttribute("role", "complementary");
+ detailPanel.setAttribute("aria-hidden", "true");
+ detailPanel.setAttribute("aria-labelledby", "project-detail-panel-title");
+ detailPanel.innerHTML =
+ '
";
+
+ detailPanel
+ .querySelector(".detail-panel-close")
+ .addEventListener("click", closeDetailPanel);
+ document.body.appendChild(detailPanel);
+ return detailPanel;
+}
+
+function closeDetailPanel() {
+ const rowToFocus = activeDetailRow;
+ if (activeDetailRow) {
+ activeDetailRow.classList.remove("selected");
+ activeDetailRow.setAttribute("aria-expanded", "false");
+ activeDetailRow.removeAttribute("aria-controls");
+ activeDetailRow = null;
+ }
+ if (!detailPanel) return;
+ detailPanel.classList.remove("visible");
+ detailPanel.setAttribute("aria-hidden", "true");
+ return rowToFocus;
+}
+
+function openDetailPanel(row, options) {
+ const shouldFocus = options && options.focusPanel;
+ if (!row._expandRow) return;
+ if (activeDetailRow === row) {
+ const closedRow = closeDetailPanel();
+ if (shouldFocus && closedRow) closedRow.focus();
+ return;
+ }
+
+ closeDetailPanel();
+
+ const panel = getDetailPanel();
+ const body = panel.querySelector(".detail-panel-body");
+ const title = panel.querySelector(".detail-panel-title");
+ const name = row.querySelector(".col-name a");
+ const content = row._expandRow.querySelector(".expand-content");
+ title.textContent = name ? name.textContent.trim() : "Project details";
+ body.innerHTML = content ? content.innerHTML : "";
+
+ row.classList.add("selected");
+ row.setAttribute("aria-expanded", "true");
+ row.setAttribute("aria-controls", panel.id);
+ activeDetailRow = row;
+ panel.classList.add("visible");
+ panel.setAttribute("aria-hidden", "false");
+ if (shouldFocus) panel.querySelector(".detail-panel-close").focus();
}
function applyFilters() {
@@ -247,6 +320,7 @@ function getSortValue(row, col) {
function sortRows() {
if (!tbody) return;
+ closeDetailPanel();
const arr = Array.prototype.slice.call(rows);
const col = activeSort.col;
@@ -313,14 +387,7 @@ if (tbody) {
}
if (!row) return;
- const isOpen = row.classList.contains("open");
- if (isOpen) {
- row.classList.remove("open");
- row.setAttribute("aria-expanded", "false");
- } else {
- row.classList.add("open");
- row.setAttribute("aria-expanded", "true");
- }
+ openDetailPanel(row);
});
// Keyboard: Enter or Space on focused .row toggles expand
@@ -329,7 +396,7 @@ if (tbody) {
const row = e.target.closest("tr.row");
if (!row) return;
e.preventDefault();
- row.click();
+ openDetailPanel(row, { focusPanel: true });
});
}
@@ -401,6 +468,11 @@ if (searchInput) {
});
document.addEventListener("keydown", function (e) {
+ if (e.key === "Escape" && detailPanel && activeDetailRow) {
+ const rowToFocus = closeDetailPanel();
+ if (rowToFocus) rowToFocus.focus();
+ return;
+ }
if (
e.key === "/" &&
!["INPUT", "TEXTAREA", "SELECT"].includes(
diff --git a/website/static/style.css b/website/static/style.css
index 93056570aa..73f93aacd3 100644
--- a/website/static/style.css
+++ b/website/static/style.css
@@ -800,6 +800,10 @@ kbd {
border-bottom-color: transparent;
}
+.row.selected td {
+ background: linear-gradient(180deg, var(--row-open-start), var(--row-open-end));
+}
+
.col-num {
width: 3.5rem;
color: var(--ink-muted);
@@ -1036,6 +1040,119 @@ th[data-sort].sort-asc::after {
vertical-align: bottom;
}
+.detail-panel {
+ position: fixed;
+ top: 4.75rem;
+ right: 0.75rem;
+ z-index: 40;
+ width: min(30rem, calc(100vw - 1.5rem));
+ max-height: calc(100vh - 6rem);
+ overflow: hidden;
+ color: var(--ink-soft);
+ background:
+ linear-gradient(180deg, oklch(100% 0 0 / 0.9), oklch(97% 0.01 75 / 0.96)),
+ var(--bg-paper);
+ border: 1px solid var(--line-strong);
+ border-radius: 0.9rem;
+ box-shadow:
+ 0 1.7rem 3.8rem -2.4rem oklch(0% 0 0 / 0.55),
+ 0 0.35rem 1.2rem -0.9rem oklch(0% 0 0 / 0.28);
+ opacity: 0;
+ pointer-events: none;
+ transform: translateX(1rem) scale(0.985);
+ transition:
+ opacity 180ms ease,
+ transform 220ms cubic-bezier(0.22, 1, 0.36, 1);
+}
+
+.detail-panel.visible {
+ opacity: 1;
+ pointer-events: auto;
+ transform: translateX(0) scale(1);
+}
+
+.detail-panel-shell {
+ display: grid;
+ grid-template-rows: auto minmax(0, 1fr);
+ max-height: inherit;
+}
+
+.detail-panel-header {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto;
+ gap: 1rem;
+ align-items: start;
+ padding: 1.05rem 1.15rem 0.95rem;
+ border-bottom: 1px solid var(--line);
+ background: oklch(98% 0.01 75 / 0.72);
+}
+
+.detail-panel-kicker {
+ margin-bottom: 0.32rem;
+ color: var(--ink-muted);
+ font-size: 0.68rem;
+ font-weight: 800;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.detail-panel-body {
+ max-height: calc(100vh - 12rem);
+ padding: 1.05rem 1.15rem 1.15rem;
+ overflow: auto;
+}
+
+.detail-panel-close {
+ width: 2rem;
+ height: 2rem;
+ border: 1px solid var(--line);
+ border-radius: 999px;
+ color: var(--ink-muted);
+ background: var(--bg-paper);
+ font: inherit;
+ font-size: 1.15rem;
+ line-height: 1;
+ cursor: pointer;
+ transition:
+ color 160ms ease,
+ border-color 160ms ease,
+ background-color 160ms ease,
+ transform 160ms ease;
+}
+
+.detail-panel-close:hover {
+ color: var(--ink);
+ background: var(--accent-soft);
+ border-color: oklch(68% 0.08 58 / 0.5);
+}
+
+.detail-panel-close:focus-visible {
+ outline: 2px solid var(--accent);
+ outline-offset: 2px;
+}
+
+.detail-panel-close:active {
+ transform: translateY(1px);
+}
+
+.detail-panel-title {
+ margin: 0;
+ color: var(--ink);
+ font-size: var(--text-lg);
+ font-weight: 800;
+ line-height: 1.2;
+ overflow-wrap: break-word;
+}
+
+.detail-panel .expand-content {
+ animation: none;
+}
+
+.detail-panel .expand-meta {
+ padding-top: 0.8rem;
+ border-top: 1px solid var(--line);
+}
+
.expand-sep {
margin-inline: 0.25rem;
color: var(--line-strong);
@@ -1722,6 +1839,33 @@ th[data-sort].sort-asc::after {
padding-right: 0.8rem;
}
+ .detail-panel {
+ top: auto;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ width: auto;
+ max-height: min(74vh, 34rem);
+ border-right: 0;
+ border-bottom: 0;
+ border-left: 0;
+ border-radius: 1rem 1rem 0 0;
+ transform: translateY(1.2rem);
+ }
+
+ .detail-panel.visible {
+ transform: translateY(0);
+ }
+
+ .detail-panel-header,
+ .detail-panel-body {
+ padding-inline: 1rem;
+ }
+
+ .detail-panel-body {
+ max-height: calc(min(74vh, 34rem) - 5.5rem);
+ }
+
.col-stars {
width: 5.4rem;
}
@@ -1736,6 +1880,10 @@ th[data-sort].sort-asc::after {
scroll-behavior: auto;
}
+ .detail-panel {
+ transition: none;
+ }
+
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;