From f54dfda5f419780a481bef07e923a72f63609fed Mon Sep 17 00:00:00 2001 From: jekkinoopy Date: Wed, 3 Jun 2026 16:04:13 +0800 Subject: [PATCH] =?UTF-8?q?style:=20=E6=BC=94=E5=94=B1=E6=9C=83=E6=90=9C?= =?UTF-8?q?=E5=B0=8B=E8=AA=AA=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- concert/concert.html | 34 ++++++++++++-- concert/search.js | 104 +++++++++++++++++++++++++++++++++---------- 2 files changed, 112 insertions(+), 26 deletions(-) diff --git a/concert/concert.html b/concert/concert.html index c5d582b..391f529 100644 --- a/concert/concert.html +++ b/concert/concert.html @@ -103,6 +103,17 @@ margin: 0; } + .concert-search-hint { + width: 100%; + max-width: 720px; + margin: 0 0 12px; + padding: 0 6px; + text-align: left; + font-size: 0.82rem; + line-height: 1.6; + color: rgba(255, 255, 255, 0.78); + } + .concert-search-summary { margin: 0 0 12px; text-align: center; @@ -130,6 +141,18 @@ line-height: 1.5; } + .concert-search-hits li.concert-search-hit-jump { + cursor: pointer; + transition: background 0.2s ease, border-color 0.2s ease; + } + + .concert-search-hits li.concert-search-hit-jump:hover, + .concert-search-hits li.concert-search-hit-jump:focus-visible { + background: rgba(255, 204, 0, 0.15); + border-color: #ffcc00; + outline: none; + } + .concert-search-hits .hit-track { display: block; font-weight: 700; @@ -171,12 +194,17 @@
diff --git a/concert/search.js b/concert/search.js index 42ab4cd..347a564 100644 --- a/concert/search.js +++ b/concert/search.js @@ -43,6 +43,36 @@ function clearSearchHighlights() { }); } +function expandVenueForTrackItem(trackItem) { + const venueBlock = trackItem?.closest(".track-list")?.parentElement; + const expand = venueBlock?.querySelector(".expand-trigger"); + if (expand) { + expand.checked = true; + } +} + +function scrollToSearchResults() { + if (!myResult) return; + myResult.scrollIntoView({ behavior: "smooth", block: "start" }); +} + +function jumpToTrackHit(hit) { + if (!hit?.el) return; + + clearSearchHighlights(); + expandVenueForTrackItem(hit.el); + hit.el.classList.add("is-search-hit"); + hit.el.scrollIntoView({ behavior: "smooth", block: "center" }); +} + +function setActiveSearchListItem(list, activeLi) { + if (!list) return; + list.querySelectorAll(".concert-search-hit-jump").forEach(function (item) { + item.classList.remove("is-active"); + }); + if (activeLi) activeLi.classList.add("is-active"); +} + function renderSearchResults(getQuery, getMember, hits) { if (!myResult) return; @@ -52,15 +82,15 @@ function renderSearchResults(getQuery, getMember, hits) { summary.className = "concert-search-summary"; if (!hits.length) { - summary.textContent = `關鍵字「${getQuery || "(未填)"}」${getMember.trim() ? `、成員「${getMember}」` : ""}:沒有比對到曲目,請換字再試。`; + summary.textContent = `曲目或場次「${getQuery || "(未填)"}」${getMember.trim() ? `、成員「${getMember}」` : ""}:沒有比對到曲目,請換字再試。`; myResult.replaceChildren(summary); return; } if (getMember.trim()) { - summary.textContent = `關鍵字「${getQuery || "(未填)"}」、成員「${getMember}」:共 ${hits.length} 筆,以下列出前 ${Math.min(hits.length, SEARCH_LIST_MAX)} 筆。`; + summary.textContent = `曲目或場次「${getQuery || "(未填)"}」、成員「${getMember}」:共 ${hits.length} 筆。點任一列可跳到歌單。`; } else { - summary.textContent = `關鍵字「${getQuery}」:共 ${hits.length} 筆,以下列出前 ${Math.min(hits.length, SEARCH_LIST_MAX)} 筆。`; + summary.textContent = `曲目或場次「${getQuery}」:共 ${hits.length} 筆。點任一列可跳到歌單。`; } const list = document.createElement("ul"); @@ -70,6 +100,11 @@ function renderSearchResults(getQuery, getMember, hits) { if (hit.el) hit.el.classList.add("is-search-hit"); const li = document.createElement("li"); + li.className = "concert-search-hit-jump"; + li.setAttribute("role", "button"); + li.setAttribute("tabindex", "0"); + li.setAttribute("aria-label", `跳到曲目:${hit.trackName}`); + const trackLine = document.createElement("span"); trackLine.className = "hit-track"; trackLine.textContent = hit.trackName; @@ -80,41 +115,64 @@ function renderSearchResults(getQuery, getMember, hits) { li.appendChild(trackLine); if (metaLine.textContent) li.appendChild(metaLine); + + li.addEventListener("click", function () { + jumpToTrackHit(hit); + }); + li.addEventListener("keydown", function (event) { + if (event.key === "Enter" || event.key === " ") { + event.preventDefault(); + jumpToTrackHit(hit); + } + }); + list.appendChild(li); }); if (hits.length > SEARCH_LIST_MAX) { const more = document.createElement("li"); more.className = "hit-more"; - more.textContent = `另有 ${hits.length - SEARCH_LIST_MAX} 筆未顯示,請縮小關鍵字。`; + more.textContent = `另有 ${hits.length - SEARCH_LIST_MAX} 筆未顯示,請縮小曲目或場次。`; list.appendChild(more); } myResult.replaceChildren(summary, list); - const firstHit = hits[0]?.el; - if (firstHit) { - firstHit.scrollIntoView({ behavior: "smooth", block: "nearest" }); + jumpToTrackHit(hits[0]); +} + +function runSearch() { + let getQuery = myQuery ? myQuery.value : ""; + let getMember = myMember ? myMember.value : ""; + + if (!getQuery.trim() && !getMember.trim()) { + myResult.replaceChildren(); + const summary = document.createElement("p"); + summary.className = "concert-search-summary"; + summary.textContent = "請至少輸入曲目/場次,或成員其中一項。"; + myResult.appendChild(summary); + clearSearchHighlights(); + return; } + + const hits = findMatchingTracks(getQuery, getMember); + renderSearchResults(getQuery, getMember, hits); +} + +function handleSearchEnter(event) { + if (event.key !== "Enter") return; + event.preventDefault(); + runSearch(); } // 2.action if (myBtn && myResult) { - myBtn.addEventListener("click", function () { - let getQuery = myQuery ? myQuery.value : ""; - let getMember = myMember ? myMember.value : ""; + myBtn.addEventListener("click", runSearch); - if (!getQuery.trim() && !getMember.trim()) { - myResult.replaceChildren(); - const summary = document.createElement("p"); - summary.className = "concert-search-summary"; - summary.textContent = "請至少輸入曲目/場次,或成員其中一項。"; - myResult.appendChild(summary); - clearSearchHighlights(); - return; - } - - const hits = findMatchingTracks(getQuery, getMember); - renderSearchResults(getQuery, getMember, hits); - }); + if (myQuery) { + myQuery.addEventListener("keydown", handleSearchEnter); + } + if (myMember) { + myMember.addEventListener("keydown", handleSearchEnter); + } }