From 0c1b46e3950c3064ceeec7f03f3250c756330494 Mon Sep 17 00:00:00 2001 From: Dymas Date: Sat, 23 May 2026 19:01:30 +0200 Subject: [PATCH] Collapse watchlist alternative titles --- CHANGELOG.md | 4 ++++ README.md | 5 +++-- VERSION | 2 +- app.py | 10 ++++++++++ app_support.py | 2 +- test_app.py | 14 +++++++++----- watchlist_page.py | 38 ++++++++++++++++++++++++++++++-------- 7 files changed, 58 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fcfafd3..cf2d538 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.37.1 - 2026-05-23 + +- Fixed AllManga alternative title parsing to read the visible `span.altnames` markup and changed watchlist cards to reveal those titles only when the new `Alternative titles` button is pressed. + ## 0.37.0 - 2026-05-23 - Added watchlist support for alternative English and synonym titles pulled from the AllManga show page and shown directly on each watchlist card. diff --git a/README.md b/README.md index 13153de..c33bbd1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Small local web UI for a system-wide `ani-cli` install. -Current version: `0.37.0` +Current version: `0.37.1` ## Project Layout @@ -242,7 +242,8 @@ Thumbnail behavior: Title behavior: - Watchlist refreshes also try to pull alternative English and synonym titles from the AllManga show page. -- Up to three alternate names are shown as a muted secondary line under the main watchlist title. +- Watchlist cards show an `Alternative titles` button when names are available. +- Pressing that button expands the stored alternative titles on demand. - Missing covers are warmed in small batches. - Failed lookups back off before retrying. - You can force `Reload` or use `Upload` when no thumbnail exists. diff --git a/VERSION b/VERSION index 0f1a7df..9b1bb85 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.37.0 +0.37.1 diff --git a/app.py b/app.py index 840cc0d..3d869e7 100644 --- a/app.py +++ b/app.py @@ -199,6 +199,16 @@ def parse_allmanga_alternative_titles(html_text, primary_title=""): for match in re.findall(r'"([^"]+)"', body): add_candidate(match, english_preferred=english_preferred) + for match in re.findall( + r']*class="[^"]*\baltnames\b[^"]*"[^>]*>\s*(.*?)\s*', + text, + flags=re.IGNORECASE | re.DOTALL, + ): + cleaned = re.sub(r"<[^>]+>", " ", match) + cleaned = cleaned.replace(" ", " ") + cleaned = re.sub(r"^[\s\u25cf\u26aa\u2022]+", "", cleaned) + add_candidate(cleaned, english_preferred=not preferred_english) + deduped = unique_title_values(candidates, primary) english_title = "" for candidate in unique_title_values([preferred_english], primary): diff --git a/app_support.py b/app_support.py index 7af5d56..ece506c 100644 --- a/app_support.py +++ b/app_support.py @@ -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.37.0" +VERSION = "0.37.1" ALLANIME_BASE = "allanime.day" ALLANIME_API = f"https://api.{ALLANIME_BASE}" ALLANIME_REFERER = "https://allmanga.to" diff --git a/test_app.py b/test_app.py index 8a67001..58d72fd 100644 --- a/test_app.py +++ b/test_app.py @@ -1746,8 +1746,10 @@ class TemplateHelperTests(unittest.TestCase): self.assertIn("Enter sub or dub", APP.WATCHLIST_HTML) def test_watchlist_page_shows_alternative_titles(self): - self.assertIn('class="alt-title"', APP.WATCHLIST_HTML) - self.assertIn('Alt: ${item.display_alt_titles.join(" · ")}', APP.WATCHLIST_HTML) + self.assertIn('class="ghost alt-toggle"', APP.WATCHLIST_HTML) + self.assertIn('class="alt-titles"', APP.WATCHLIST_HTML) + self.assertIn('Alternative titles', APP.WATCHLIST_HTML) + self.assertIn('altTitlesNode.classList.toggle("visible")', APP.WATCHLIST_HTML) def test_watchlist_thumbnail_warmup_skips_repeated_attempts(self): self.assertIn("thumbnailWarmupPending: new Set()", APP.WATCHLIST_HTML) @@ -1757,9 +1759,11 @@ class TemplateHelperTests(unittest.TestCase): def test_parse_allmanga_alternative_titles_extracts_english_and_synonyms(self): html_text = """ - +
+ ⚪ Example Show English   + ⚪ Sample Alias   + ⚪ Example Show   +
""" parsed = APP.parse_allmanga_alternative_titles(html_text, primary_title="Example Show") self.assertEqual(parsed["english_title"], "Example Show English") diff --git a/watchlist_page.py b/watchlist_page.py index b19a23c..73cdccf 100644 --- a/watchlist_page.py +++ b/watchlist_page.py @@ -413,13 +413,23 @@ WATCHLIST_HTML = r""" flex: 1; min-width: 0; } - .alt-title { + .alt-toggle { + min-height: 30px; + padding: 0 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 700; + } + .alt-titles { color: var(--muted); font-size: 12px; line-height: 1.35; - margin-top: 4px; + margin-top: 8px; overflow-wrap: anywhere; - min-height: 1.35em; + display: none; + } + .alt-titles.visible { + display: block; } .card-title a { color: inherit; @@ -1001,7 +1011,8 @@ WATCHLIST_HTML = r"""
-
+ +
${item.finished_badge ? "✓" : ""}
@@ -1030,10 +1041,21 @@ WATCHLIST_HTML = r""" titleLink.rel = "noopener noreferrer"; titleLink.textContent = item.title; titleNode.replaceChildren(titleLink); - const altTitleNode = card.querySelector(".alt-title"); - altTitleNode.textContent = (item.display_alt_titles || []).length - ? `Alt: ${item.display_alt_titles.join(" · ")}` - : ""; + const altToggle = card.querySelector(".alt-toggle"); + const altTitlesNode = card.querySelector(".alt-titles"); + const altTitles = item.display_alt_titles || []; + if (altTitles.length) { + altToggle.hidden = false; + altTitlesNode.hidden = false; + altTitlesNode.textContent = altTitles.join(" · "); + altToggle.setAttribute("aria-expanded", "false"); + altToggle.addEventListener("click", () => { + const visible = altTitlesNode.classList.toggle("visible"); + altToggle.setAttribute("aria-expanded", visible ? "true" : "false"); + altToggle.textContent = visible ? "Hide alternative titles" : "Alternative titles"; + }); + altToggle.textContent = "Alternative titles"; + } const finishCheck = card.querySelector(".finish-check"); if (finishCheck && item.finished_badge) { finishCheck.title = item.finished_badge === "downloaded"