Open alternative titles in modal overlay
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.40.2 - 2026-05-25
|
||||||
|
|
||||||
|
- Replaced the inline `Alternative titles` expansion with a centered overlay dialog so opening AniDB title variants no longer shifts the watchlist layout.
|
||||||
|
|
||||||
## 0.40.1 - 2026-05-25
|
## 0.40.1 - 2026-05-25
|
||||||
|
|
||||||
- Replaced the per-card auto-download expansion with a centered overlay dialog so opening settings no longer shifts the watchlist grid layout.
|
- Replaced the per-card auto-download expansion with a centered overlay dialog so opening settings no longer shifts the watchlist grid layout.
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ Main behavior:
|
|||||||
6. Each card has an inline category selector.
|
6. Each card has an inline category selector.
|
||||||
7. Summary cards show total tracked shows plus `sub` and `dub` counts.
|
7. Summary cards show total tracked shows plus `sub` and `dub` counts.
|
||||||
8. Pagination shows 30 cards per page.
|
8. Pagination shows 30 cards per page.
|
||||||
9. `Alternative titles` expands English alternate titles from AniDB when the watchlist refresh can match the show to an AniDB entry.
|
9. `Alternative titles` opens a centered overlay with English alternate titles from AniDB when the watchlist refresh can match the show to an AniDB entry.
|
||||||
10. `Auto-download` opens a centered overlay with per-series settings for language, quality, series number, and the sanitized library name used for automatically queued episodes.
|
10. `Auto-download` opens a centered overlay with per-series settings for language, quality, series number, and the sanitized library name used for automatically queued episodes.
|
||||||
|
|
||||||
Status behavior:
|
Status behavior:
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,7 @@ from uuid import uuid4
|
|||||||
PROJECT_ROOT = Path(__file__).resolve().parent
|
PROJECT_ROOT = Path(__file__).resolve().parent
|
||||||
ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli"
|
ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli"
|
||||||
APP_NAME = "ani-cli-web"
|
APP_NAME = "ani-cli-web"
|
||||||
VERSION = "0.40.1"
|
VERSION = "0.40.2"
|
||||||
ALLANIME_BASE = "allanime.day"
|
ALLANIME_BASE = "allanime.day"
|
||||||
ALLANIME_API = f"https://api.{ALLANIME_BASE}"
|
ALLANIME_API = f"https://api.{ALLANIME_BASE}"
|
||||||
ALLANIME_REFERER = "https://allmanga.to"
|
ALLANIME_REFERER = "https://allmanga.to"
|
||||||
|
|||||||
+3
-1
@@ -1981,7 +1981,9 @@ class TemplateHelperTests(unittest.TestCase):
|
|||||||
def test_watchlist_page_renders_alternative_titles_toggle(self):
|
def test_watchlist_page_renders_alternative_titles_toggle(self):
|
||||||
self.assertIn("Alternative titles", APP.WATCHLIST_HTML)
|
self.assertIn("Alternative titles", APP.WATCHLIST_HTML)
|
||||||
self.assertIn('class="ghost alt-titles-toggle"', APP.WATCHLIST_HTML)
|
self.assertIn('class="ghost alt-titles-toggle"', APP.WATCHLIST_HTML)
|
||||||
self.assertIn('altTitlesPanel.classList.toggle("open")', APP.WATCHLIST_HTML)
|
self.assertIn('id="altTitlesOverlay"', APP.WATCHLIST_HTML)
|
||||||
|
self.assertIn("openAltTitlesOverlay(item)", APP.WATCHLIST_HTML)
|
||||||
|
self.assertIn("closeAltTitlesOverlay()", APP.WATCHLIST_HTML)
|
||||||
self.assertIn("AniDB English alternative titles were not available for this show.", APP.WATCHLIST_HTML)
|
self.assertIn("AniDB English alternative titles were not available for this show.", APP.WATCHLIST_HTML)
|
||||||
|
|
||||||
def test_watchlist_page_renders_auto_download_editor(self):
|
def test_watchlist_page_renders_auto_download_editor(self):
|
||||||
|
|||||||
+88
-43
@@ -501,10 +501,6 @@ WATCHLIST_HTML = r"""<!doctype html>
|
|||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
white-space: pre-line;
|
white-space: pre-line;
|
||||||
}
|
}
|
||||||
.alt-titles {
|
|
||||||
display: grid;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
.alt-titles-toggle {
|
.alt-titles-toggle {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 34px;
|
min-height: 34px;
|
||||||
@@ -513,16 +509,31 @@ WATCHLIST_HTML = r"""<!doctype html>
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.alt-titles-panel {
|
.alt-titles-modal {
|
||||||
display: none;
|
width: min(640px, 100%);
|
||||||
gap: 8px;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
border-radius: 14px;
|
|
||||||
background: rgba(255, 255, 255, 0.04);
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
.alt-titles-panel.open {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
|
gap: 14px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 22px;
|
||||||
|
background: linear-gradient(180deg, rgba(20, 27, 40, 0.98), rgba(12, 17, 28, 0.98));
|
||||||
|
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.45);
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
.alt-titles-modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.alt-titles-modal-header p {
|
||||||
|
margin: 4px 0 0;
|
||||||
|
}
|
||||||
|
.alt-titles-modal-close {
|
||||||
|
min-width: 36px;
|
||||||
|
min-height: 36px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
.alt-titles-list {
|
.alt-titles-list {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -764,6 +775,20 @@ WATCHLIST_HTML = r"""<!doctype html>
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="overlay" id="altTitlesOverlay" hidden>
|
||||||
|
<div class="alt-titles-modal" role="dialog" aria-modal="true" aria-labelledby="altTitlesTitle">
|
||||||
|
<div class="alt-titles-modal-header">
|
||||||
|
<div>
|
||||||
|
<h2 id="altTitlesTitle">Alternative titles</h2>
|
||||||
|
<p class="muted" id="altTitlesSubtitle">AniDB English titles for the selected show.</p>
|
||||||
|
</div>
|
||||||
|
<button class="ghost alt-titles-modal-close" id="altTitlesCloseBtn" type="button" aria-label="Close alternative titles">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="alt-titles-list" id="altTitlesList"></div>
|
||||||
|
<div class="muted" id="altTitlesEmpty">AniDB English alternative titles were not available for this show.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="overlay" id="autoDownloadOverlay" hidden>
|
<div class="overlay" id="autoDownloadOverlay" hidden>
|
||||||
<div class="auto-download-modal" role="dialog" aria-modal="true" aria-labelledby="autoDownloadTitle">
|
<div class="auto-download-modal" role="dialog" aria-modal="true" aria-labelledby="autoDownloadTitle">
|
||||||
<div class="auto-download-modal-header">
|
<div class="auto-download-modal-header">
|
||||||
@@ -819,6 +844,7 @@ WATCHLIST_HTML = r"""<!doctype html>
|
|||||||
refreshRunning: false,
|
refreshRunning: false,
|
||||||
refreshJobId: null,
|
refreshJobId: null,
|
||||||
watchlistRequestId: 0,
|
watchlistRequestId: 0,
|
||||||
|
altTitlesDraft: null,
|
||||||
autoDownloadDraft: null
|
autoDownloadDraft: null
|
||||||
};
|
};
|
||||||
const categoryLabels = {
|
const categoryLabels = {
|
||||||
@@ -828,6 +854,11 @@ WATCHLIST_HTML = r"""<!doctype html>
|
|||||||
dropped: "Dropped"
|
dropped: "Dropped"
|
||||||
};
|
};
|
||||||
const autoDownloadOverlay = document.getElementById("autoDownloadOverlay");
|
const autoDownloadOverlay = document.getElementById("autoDownloadOverlay");
|
||||||
|
const altTitlesOverlay = document.getElementById("altTitlesOverlay");
|
||||||
|
const altTitlesCloseBtn = document.getElementById("altTitlesCloseBtn");
|
||||||
|
const altTitlesSubtitle = document.getElementById("altTitlesSubtitle");
|
||||||
|
const altTitlesList = document.getElementById("altTitlesList");
|
||||||
|
const altTitlesEmpty = document.getElementById("altTitlesEmpty");
|
||||||
const autoDownloadCloseBtn = document.getElementById("autoDownloadCloseBtn");
|
const autoDownloadCloseBtn = document.getElementById("autoDownloadCloseBtn");
|
||||||
const autoDownloadSubtitle = document.getElementById("autoDownloadSubtitle");
|
const autoDownloadSubtitle = document.getElementById("autoDownloadSubtitle");
|
||||||
const autoDownloadStatus = document.getElementById("autoDownloadStatus");
|
const autoDownloadStatus = document.getElementById("autoDownloadStatus");
|
||||||
@@ -956,6 +987,44 @@ WATCHLIST_HTML = r"""<!doctype html>
|
|||||||
controls.append(prev, next);
|
controls.append(prev, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeAltTitlesOverlay() {
|
||||||
|
state.altTitlesDraft = null;
|
||||||
|
altTitlesOverlay.hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openAltTitlesOverlay(item) {
|
||||||
|
state.altTitlesDraft = { ...item };
|
||||||
|
altTitlesSubtitle.textContent = `${item.title} · AniDB English titles for the selected show.`;
|
||||||
|
altTitlesList.innerHTML = "";
|
||||||
|
const alternativeTitles = Array.isArray(item.alternative_titles) ? item.alternative_titles : [];
|
||||||
|
if (alternativeTitles.length) {
|
||||||
|
for (const title of alternativeTitles) {
|
||||||
|
const pill = document.createElement("span");
|
||||||
|
pill.className = "alt-title-pill";
|
||||||
|
const label = document.createElement("strong");
|
||||||
|
label.textContent = title.label;
|
||||||
|
const value = document.createElement("span");
|
||||||
|
value.textContent = title.value;
|
||||||
|
pill.append(label, value);
|
||||||
|
altTitlesList.appendChild(pill);
|
||||||
|
}
|
||||||
|
altTitlesEmpty.textContent = "Fetched from AniDB English titles.";
|
||||||
|
altTitlesList.hidden = false;
|
||||||
|
} else {
|
||||||
|
altTitlesEmpty.textContent = "AniDB English alternative titles were not available for this show.";
|
||||||
|
altTitlesList.hidden = true;
|
||||||
|
}
|
||||||
|
altTitlesEmpty.hidden = false;
|
||||||
|
altTitlesOverlay.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
altTitlesCloseBtn.addEventListener("click", closeAltTitlesOverlay);
|
||||||
|
altTitlesOverlay.addEventListener("click", (event) => {
|
||||||
|
if (event.target === altTitlesOverlay) {
|
||||||
|
closeAltTitlesOverlay();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function closeAutoDownloadOverlay() {
|
function closeAutoDownloadOverlay() {
|
||||||
state.autoDownloadDraft = null;
|
state.autoDownloadDraft = null;
|
||||||
autoDownloadOverlay.hidden = true;
|
autoDownloadOverlay.hidden = true;
|
||||||
@@ -1006,6 +1075,10 @@ WATCHLIST_HTML = r"""<!doctype html>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
window.addEventListener("keydown", (event) => {
|
window.addEventListener("keydown", (event) => {
|
||||||
|
if (event.key === "Escape" && !altTitlesOverlay.hidden) {
|
||||||
|
closeAltTitlesOverlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.key === "Escape" && !autoDownloadOverlay.hidden) {
|
if (event.key === "Escape" && !autoDownloadOverlay.hidden) {
|
||||||
closeAutoDownloadOverlay();
|
closeAutoDownloadOverlay();
|
||||||
}
|
}
|
||||||
@@ -1257,13 +1330,7 @@ WATCHLIST_HTML = r"""<!doctype html>
|
|||||||
<span class="count-pill${item.dub_complete ? " complete" : ""}">Dub <strong>${formatModeProgress(item.dub_count, item.expected_count)}</strong></span>
|
<span class="count-pill${item.dub_complete ? " complete" : ""}">Dub <strong>${formatModeProgress(item.dub_count, item.expected_count)}</strong></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-row"></div>
|
<div class="status-row"></div>
|
||||||
<div class="alt-titles">
|
<button class="ghost alt-titles-toggle" type="button">Alternative titles</button>
|
||||||
<button class="ghost alt-titles-toggle" type="button" aria-expanded="false">Alternative titles</button>
|
|
||||||
<div class="alt-titles-panel">
|
|
||||||
<div class="alt-titles-list"></div>
|
|
||||||
<div class="muted alt-titles-empty"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="category-editor">
|
<div class="category-editor">
|
||||||
<label>Category
|
<label>Category
|
||||||
<select class="category-select">${categoryOptions(item.category)}</select>
|
<select class="category-select">${categoryOptions(item.category)}</select>
|
||||||
@@ -1307,29 +1374,7 @@ WATCHLIST_HTML = r"""<!doctype html>
|
|||||||
appendStatusTag(statusRow, "Dub ready", "complete");
|
appendStatusTag(statusRow, "Dub ready", "complete");
|
||||||
}
|
}
|
||||||
const altTitlesToggle = card.querySelector(".alt-titles-toggle");
|
const altTitlesToggle = card.querySelector(".alt-titles-toggle");
|
||||||
const altTitlesPanel = card.querySelector(".alt-titles-panel");
|
altTitlesToggle.addEventListener("click", () => openAltTitlesOverlay(item));
|
||||||
const altTitlesList = card.querySelector(".alt-titles-list");
|
|
||||||
const altTitlesEmpty = card.querySelector(".alt-titles-empty");
|
|
||||||
const alternativeTitles = Array.isArray(item.alternative_titles) ? item.alternative_titles : [];
|
|
||||||
if (alternativeTitles.length) {
|
|
||||||
for (const title of alternativeTitles) {
|
|
||||||
const pill = document.createElement("span");
|
|
||||||
pill.className = "alt-title-pill";
|
|
||||||
const label = document.createElement("strong");
|
|
||||||
label.textContent = title.label;
|
|
||||||
const value = document.createElement("span");
|
|
||||||
value.textContent = title.value;
|
|
||||||
pill.append(label, value);
|
|
||||||
altTitlesList.appendChild(pill);
|
|
||||||
}
|
|
||||||
altTitlesEmpty.textContent = "Fetched from AniDB English titles.";
|
|
||||||
} else {
|
|
||||||
altTitlesEmpty.textContent = "AniDB English alternative titles were not available for this show.";
|
|
||||||
}
|
|
||||||
altTitlesToggle.addEventListener("click", () => {
|
|
||||||
const open = altTitlesPanel.classList.toggle("open");
|
|
||||||
altTitlesToggle.setAttribute("aria-expanded", open ? "true" : "false");
|
|
||||||
});
|
|
||||||
card.querySelector(".card-date").textContent = item.status_message
|
card.querySelector(".card-date").textContent = item.status_message
|
||||||
? `Last checked: ${checked}\n${item.status_message}`
|
? `Last checked: ${checked}\n${item.status_message}`
|
||||||
: `Last checked: ${checked}`;
|
: `Last checked: ${checked}`;
|
||||||
|
|||||||
Reference in New Issue
Block a user