Open alternative titles in modal overlay
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# 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
|
||||
|
||||
- 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.
|
||||
7. Summary cards show total tracked shows plus `sub` and `dub` counts.
|
||||
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.
|
||||
|
||||
Status behavior:
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ from uuid import uuid4
|
||||
PROJECT_ROOT = Path(__file__).resolve().parent
|
||||
ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli"
|
||||
APP_NAME = "ani-cli-web"
|
||||
VERSION = "0.40.1"
|
||||
VERSION = "0.40.2"
|
||||
ALLANIME_BASE = "allanime.day"
|
||||
ALLANIME_API = f"https://api.{ALLANIME_BASE}"
|
||||
ALLANIME_REFERER = "https://allmanga.to"
|
||||
|
||||
+3
-1
@@ -1981,7 +1981,9 @@ class TemplateHelperTests(unittest.TestCase):
|
||||
def test_watchlist_page_renders_alternative_titles_toggle(self):
|
||||
self.assertIn("Alternative titles", 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)
|
||||
|
||||
def test_watchlist_page_renders_auto_download_editor(self):
|
||||
|
||||
+88
-43
@@ -501,10 +501,6 @@ WATCHLIST_HTML = r"""<!doctype html>
|
||||
line-height: 1.35;
|
||||
white-space: pre-line;
|
||||
}
|
||||
.alt-titles {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
.alt-titles-toggle {
|
||||
width: 100%;
|
||||
min-height: 34px;
|
||||
@@ -513,16 +509,31 @@ WATCHLIST_HTML = r"""<!doctype html>
|
||||
font-weight: 700;
|
||||
justify-content: center;
|
||||
}
|
||||
.alt-titles-panel {
|
||||
display: none;
|
||||
gap: 8px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
padding: 10px;
|
||||
}
|
||||
.alt-titles-panel.open {
|
||||
.alt-titles-modal {
|
||||
width: min(640px, 100%);
|
||||
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 {
|
||||
display: flex;
|
||||
@@ -764,6 +775,20 @@ WATCHLIST_HTML = r"""<!doctype html>
|
||||
</main>
|
||||
</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="auto-download-modal" role="dialog" aria-modal="true" aria-labelledby="autoDownloadTitle">
|
||||
<div class="auto-download-modal-header">
|
||||
@@ -819,6 +844,7 @@ WATCHLIST_HTML = r"""<!doctype html>
|
||||
refreshRunning: false,
|
||||
refreshJobId: null,
|
||||
watchlistRequestId: 0,
|
||||
altTitlesDraft: null,
|
||||
autoDownloadDraft: null
|
||||
};
|
||||
const categoryLabels = {
|
||||
@@ -828,6 +854,11 @@ WATCHLIST_HTML = r"""<!doctype html>
|
||||
dropped: "Dropped"
|
||||
};
|
||||
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 autoDownloadSubtitle = document.getElementById("autoDownloadSubtitle");
|
||||
const autoDownloadStatus = document.getElementById("autoDownloadStatus");
|
||||
@@ -956,6 +987,44 @@ WATCHLIST_HTML = r"""<!doctype html>
|
||||
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() {
|
||||
state.autoDownloadDraft = null;
|
||||
autoDownloadOverlay.hidden = true;
|
||||
@@ -1006,6 +1075,10 @@ WATCHLIST_HTML = r"""<!doctype html>
|
||||
}
|
||||
});
|
||||
window.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Escape" && !altTitlesOverlay.hidden) {
|
||||
closeAltTitlesOverlay();
|
||||
return;
|
||||
}
|
||||
if (event.key === "Escape" && !autoDownloadOverlay.hidden) {
|
||||
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>
|
||||
</div>
|
||||
<div class="status-row"></div>
|
||||
<div class="alt-titles">
|
||||
<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>
|
||||
<button class="ghost alt-titles-toggle" type="button">Alternative titles</button>
|
||||
<div class="category-editor">
|
||||
<label>Category
|
||||
<select class="category-select">${categoryOptions(item.category)}</select>
|
||||
@@ -1307,29 +1374,7 @@ WATCHLIST_HTML = r"""<!doctype html>
|
||||
appendStatusTag(statusRow, "Dub ready", "complete");
|
||||
}
|
||||
const altTitlesToggle = card.querySelector(".alt-titles-toggle");
|
||||
const altTitlesPanel = card.querySelector(".alt-titles-panel");
|
||||
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");
|
||||
});
|
||||
altTitlesToggle.addEventListener("click", () => openAltTitlesOverlay(item));
|
||||
card.querySelector(".card-date").textContent = item.status_message
|
||||
? `Last checked: ${checked}\n${item.status_message}`
|
||||
: `Last checked: ${checked}`;
|
||||
|
||||
Reference in New Issue
Block a user