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 currentPageGameIds = [];
|
||||||
let currentTotalPages = 1;
|
let currentTotalPages = 1;
|
||||||
let currentView = "catalogue";
|
let currentView = "catalogue";
|
||||||
|
let pendingFocusGameId = null;
|
||||||
// V2 is now the default UI. Use ?ui=v1 to force legacy mode if needed.
|
// 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 uiParam = new URLSearchParams(window.location.search).get("ui");
|
||||||
const uiV2Enabled = uiParam !== "v1";
|
const uiV2Enabled = uiParam !== "v1";
|
||||||
@@ -256,6 +257,40 @@ quickSearchInput.addEventListener("input", (event) => {
|
|||||||
renderSearchResults();
|
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", () => {
|
loanedFilterBtn.addEventListener("click", () => {
|
||||||
setCurrentView(currentView === "loans" ? "catalogue" : "loans");
|
setCurrentView(currentView === "loans" ? "catalogue" : "loans");
|
||||||
});
|
});
|
||||||
@@ -1381,7 +1416,7 @@ function renderSearchResults() {
|
|||||||
]
|
]
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.join(" | ");
|
.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("");
|
.join("");
|
||||||
|
|
||||||
@@ -1393,6 +1428,37 @@ function renderSearchResults() {
|
|||||||
quickSearchResults.innerHTML = `${header}${items}${more}`;
|
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) {
|
function escapeHtml(text) {
|
||||||
return String(text)
|
return String(text)
|
||||||
.replaceAll("&", "&")
|
.replaceAll("&", "&")
|
||||||
@@ -1597,6 +1663,12 @@ function renderGames() {
|
|||||||
|
|
||||||
const totalFilteredCount = games.length;
|
const totalFilteredCount = games.length;
|
||||||
const pageSize = pageSizeForViewport();
|
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));
|
currentTotalPages = Math.max(1, Math.ceil(totalFilteredCount / pageSize));
|
||||||
if (currentPage > currentTotalPages) {
|
if (currentPage > currentTotalPages) {
|
||||||
currentPage = currentTotalPages;
|
currentPage = currentTotalPages;
|
||||||
@@ -1615,6 +1687,7 @@ function renderGames() {
|
|||||||
for (const game of pageGames) {
|
for (const game of pageGames) {
|
||||||
const card = gameCardTemplate.content.cloneNode(true);
|
const card = gameCardTemplate.content.cloneNode(true);
|
||||||
const article = card.querySelector(".game-card");
|
const article = card.querySelector(".game-card");
|
||||||
|
article.dataset.gameId = game.id;
|
||||||
article.classList.add(consoleThemeClass(game.consoleName));
|
article.classList.add(consoleThemeClass(game.consoleName));
|
||||||
if (inlineEditingGameId === game.id) {
|
if (inlineEditingGameId === game.id) {
|
||||||
article.classList.add("editing");
|
article.classList.add("editing");
|
||||||
@@ -1722,6 +1795,14 @@ function renderGames() {
|
|||||||
|
|
||||||
gamesList.append(card);
|
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) {
|
function startEditMode(game) {
|
||||||
|
|||||||
10
styles.css
10
styles.css
@@ -398,6 +398,16 @@ h1 {
|
|||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
padding: 0.5rem 0.6rem;
|
padding: 0.5rem 0.6rem;
|
||||||
background: #ffffff;
|
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 {
|
.search-hit strong {
|
||||||
|
|||||||
Reference in New Issue
Block a user