Feature: open and focus game card from quick search results
This commit is contained in:
83
app.js
83
app.js
@@ -116,6 +116,7 @@ let currentPage = 1;
|
||||
let currentPageGameIds = [];
|
||||
let currentTotalPages = 1;
|
||||
let currentView = "catalogue";
|
||||
let pendingFocusGameId = null;
|
||||
// V2 is now the default UI. Use ?ui=v1 to force legacy mode if needed.
|
||||
const uiParam = new URLSearchParams(window.location.search).get("ui");
|
||||
const uiV2Enabled = uiParam !== "v1";
|
||||
@@ -256,6 +257,40 @@ quickSearchInput.addEventListener("input", (event) => {
|
||||
renderSearchResults();
|
||||
});
|
||||
|
||||
quickSearchResults.addEventListener("click", (event) => {
|
||||
if (!(event.target instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
const target = event.target.closest(".search-hit[data-game-id]");
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
const gameId = normalizeText(target.dataset.gameId);
|
||||
if (!gameId) {
|
||||
return;
|
||||
}
|
||||
openGameFromSearchResult(gameId);
|
||||
});
|
||||
|
||||
quickSearchResults.addEventListener("keydown", (event) => {
|
||||
if (!(event.target instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
if (event.key !== "Enter" && event.key !== " ") {
|
||||
return;
|
||||
}
|
||||
const target = event.target.closest(".search-hit[data-game-id]");
|
||||
if (!(target instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
const gameId = normalizeText(target.dataset.gameId);
|
||||
if (!gameId) {
|
||||
return;
|
||||
}
|
||||
openGameFromSearchResult(gameId);
|
||||
});
|
||||
|
||||
loanedFilterBtn.addEventListener("click", () => {
|
||||
setCurrentView(currentView === "loans" ? "catalogue" : "loans");
|
||||
});
|
||||
@@ -1381,7 +1416,7 @@ function renderSearchResults() {
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" | ");
|
||||
return `<article class="search-hit"><strong>${escapeHtml(game.title || "")}</strong><p>${escapeHtml(meta)}</p></article>`;
|
||||
return `<article class="search-hit" data-game-id="${escapeHtml(game.id)}" role="button" tabindex="0" title="Ouvrir la fiche du jeu"><strong>${escapeHtml(game.title || "")}</strong><p>${escapeHtml(meta)}</p></article>`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
@@ -1393,6 +1428,37 @@ function renderSearchResults() {
|
||||
quickSearchResults.innerHTML = `${header}${items}${more}`;
|
||||
}
|
||||
|
||||
function openGameFromSearchResult(gameId) {
|
||||
const allGames = collectAllGames();
|
||||
const game = allGames.find((item) => item.id === gameId);
|
||||
if (!game) {
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentView("catalogue");
|
||||
if (game.brand && state.brands[game.brand]) {
|
||||
state.selectedBrand = game.brand;
|
||||
}
|
||||
if (game.consoleName) {
|
||||
state.selectedConsole = game.consoleName;
|
||||
}
|
||||
|
||||
// Narrow the catalogue to quickly open the target card and start inline edit.
|
||||
v2ConsoleValue = normalizeText(game.consoleName);
|
||||
v2GenreValue = "";
|
||||
v2SearchTerm = normalizeText(game.title);
|
||||
if (v2SearchInput) {
|
||||
v2SearchInput.value = v2SearchTerm;
|
||||
}
|
||||
|
||||
pendingFocusGameId = gameId;
|
||||
inlineEditingGameId = gameId;
|
||||
editingGameId = null;
|
||||
resetPaging();
|
||||
persist();
|
||||
render();
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
return String(text)
|
||||
.replaceAll("&", "&")
|
||||
@@ -1597,6 +1663,12 @@ function renderGames() {
|
||||
|
||||
const totalFilteredCount = games.length;
|
||||
const pageSize = pageSizeForViewport();
|
||||
if (pendingFocusGameId) {
|
||||
const focusIndex = games.findIndex((game) => game.id === pendingFocusGameId);
|
||||
if (focusIndex >= 0) {
|
||||
currentPage = Math.floor(focusIndex / pageSize) + 1;
|
||||
}
|
||||
}
|
||||
currentTotalPages = Math.max(1, Math.ceil(totalFilteredCount / pageSize));
|
||||
if (currentPage > currentTotalPages) {
|
||||
currentPage = currentTotalPages;
|
||||
@@ -1615,6 +1687,7 @@ function renderGames() {
|
||||
for (const game of pageGames) {
|
||||
const card = gameCardTemplate.content.cloneNode(true);
|
||||
const article = card.querySelector(".game-card");
|
||||
article.dataset.gameId = game.id;
|
||||
article.classList.add(consoleThemeClass(game.consoleName));
|
||||
if (inlineEditingGameId === game.id) {
|
||||
article.classList.add("editing");
|
||||
@@ -1722,6 +1795,14 @@ function renderGames() {
|
||||
|
||||
gamesList.append(card);
|
||||
}
|
||||
|
||||
if (pendingFocusGameId) {
|
||||
const focusedCard = gamesList.querySelector(`.game-card[data-game-id="${pendingFocusGameId}"]`);
|
||||
if (focusedCard instanceof HTMLElement) {
|
||||
focusedCard.scrollIntoView({ behavior: "smooth", block: "center" });
|
||||
}
|
||||
pendingFocusGameId = null;
|
||||
}
|
||||
}
|
||||
|
||||
function startEditMode(game) {
|
||||
|
||||
10
styles.css
10
styles.css
@@ -398,6 +398,16 @@ h1 {
|
||||
border-radius: 10px;
|
||||
padding: 0.5rem 0.6rem;
|
||||
background: #ffffff;
|
||||
cursor: pointer;
|
||||
transition: border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
|
||||
}
|
||||
|
||||
.search-hit:hover,
|
||||
.search-hit:focus-visible {
|
||||
border-color: #9fc3e6;
|
||||
box-shadow: 0 0 0 3px rgba(70, 130, 180, 0.16);
|
||||
transform: translateY(-1px);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.search-hit strong {
|
||||
|
||||
Reference in New Issue
Block a user