mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-01 07:33:45 -06:00
feat: setup tab, letter crumbs, console words
get/ pages join the tab of the setup pages of the same name, so no page is excluded from every tab and the tabs keep their names. Groups filing emulators by first letter are no crumb. A console page carries "<name> BIOS" in its terms field. The search gate runs the console, checker, schema, license and iOS queries.
This commit is contained in:
1 parent
f80dcb6d3c
commit
15f7c72077
5 files changed
+92
-22
No files matched your search
+62
-15
@@ -417,10 +417,6 @@ def page_aliases(rel: str) -> list[str]:
|
||||
return list(_page_aliases.get(rel, []))
|
||||
|
||||
|
||||
# Search fields a page's own front matter fills, never typed here.
|
||||
SEARCH_FIELDS = ("consoles", "terms")
|
||||
|
||||
|
||||
def field_words(value: object) -> list[str]:
|
||||
"""The words of a front matter search field: a list, or one string
|
||||
with its words or phrases split at commas."""
|
||||
@@ -431,6 +427,14 @@ def field_words(value: object) -> list[str]:
|
||||
return []
|
||||
|
||||
|
||||
def console_words(names: list[str]) -> list[str]:
|
||||
"""What a visitor types for a console: its name with "BIOS". A page that
|
||||
answers "No file needed" says BIOS only where the search does not read,
|
||||
so "nes bios" found NES.emu and never the NES page. As a task word it
|
||||
weighs like one: in the consoles field, NES.emu's title still won."""
|
||||
return [f"{name} BIOS" for name in names]
|
||||
|
||||
|
||||
def _neighbour(rel: str, value: object) -> object:
|
||||
"""A `prev:` or `next:` the page states, its link resolved like any
|
||||
other link of the page."""
|
||||
@@ -503,8 +507,9 @@ def finalize_page(
|
||||
r'<span data-pagefind-ignore class="\1"', body)
|
||||
if rel.startswith("systems/"):
|
||||
body += file_names_meta(body) + search_field("aliases", page_aliases(rel))
|
||||
for key in SEARCH_FIELDS:
|
||||
body += search_field(key, field_words(authored.get(key)))
|
||||
consoles = field_words(authored.get("consoles"))
|
||||
body += search_field("consoles", consoles)
|
||||
body += search_field("terms", field_words(authored.get("terms")) + console_words(consoles))
|
||||
components = used_components(body)
|
||||
stem = rel.rsplit(".", 1)[0]
|
||||
if components:
|
||||
@@ -594,12 +599,33 @@ def reads_in_order(rel: str) -> bool:
|
||||
return not rel.startswith(CARD_PAGES) or rel.endswith("/index.md")
|
||||
|
||||
|
||||
_DIGIT_RANGE = re.compile(r"[0-9] to [0-9]")
|
||||
|
||||
|
||||
def by_initial(label: str, children: list[dict]) -> bool:
|
||||
"""A group that files its pages by the first character of their names
|
||||
(A, B, "0 to 9"): a way through a long list, not a place, so it is no
|
||||
crumb."""
|
||||
if not children or any("items" in child for child in children):
|
||||
return False
|
||||
firsts = {str(child["label"])[:1].upper() for child in children}
|
||||
if len(label) == 1:
|
||||
return firsts == {label}
|
||||
return bool(_DIGIT_RANGE.fullmatch(label)) and not any("A" <= first <= "Z" for first in firsts)
|
||||
|
||||
|
||||
def sidebar(nav: list, present: set[str], anchors: dict[str, set[str]] | None = None) -> dict:
|
||||
"""The navigation as Starlight topics, one per tab, with what the route
|
||||
middleware (website/src/routeData.ts) reads beside it.
|
||||
|
||||
A page the navigation does not list (the parts of a split page) joins
|
||||
the topic that holds most pages of its directory.
|
||||
the topic that holds most pages of its directory. In a directory the
|
||||
navigation lists nothing of (get/), it joins the topic of the pages of
|
||||
the same name, and a page with neither joins the first topic: no page
|
||||
is left out of every tab.
|
||||
|
||||
A group that files names by their first letter is marked `crumb: false`:
|
||||
the breadcrumbs skip it.
|
||||
|
||||
`groups` gives each group, keyed by the labels from its tab down, the
|
||||
page that introduces it: its own overview page, or the heading named
|
||||
@@ -649,7 +675,8 @@ def sidebar(nav: list, present: set[str], anchors: dict[str, set[str]] | None =
|
||||
children = [child for child in (item(v, topic, here, own or parent) for v in value) if child]
|
||||
if not children:
|
||||
return None
|
||||
groups["/".join(here)] = {"href": href, "prune": topic in PRUNED_TABS and href is not None}
|
||||
groups["/".join(here)] = {"href": href, "prune": topic in PRUNED_TABS and href is not None,
|
||||
"crumb": not by_initial(label, children)}
|
||||
return {"label": label, "items": children, "collapsed": True}
|
||||
|
||||
topics = []
|
||||
@@ -670,15 +697,35 @@ def sidebar(nav: list, present: set[str], anchors: dict[str, set[str]] | None =
|
||||
# is the way back in. Excluded from every tab, the theme named the tabs
|
||||
# by their position, 0 to 4.
|
||||
extra: dict[str, list[str]] = {topics[0]["id"]: ["/404"]} if topics else {}
|
||||
exclude: list[str] = []
|
||||
for rel in sorted(present - owner.keys()):
|
||||
tally = votes.get(rel.rpartition("/")[0])
|
||||
path = "/" + (_sidebar_link(rel).strip("/") or "index")
|
||||
# A folder the navigation lists nothing of (get/, one download page per
|
||||
# setup) has no vote, and excluded from every tab its pages had the
|
||||
# tabs numbered 0 to 4 too. Its pages take the tab of the pages of the
|
||||
# same name (get/batocera.md, platforms/batocera.md), and its overview
|
||||
# the tab its pages took.
|
||||
by_name: dict[str, dict[str, int]] = {}
|
||||
for rel, topic in owner.items():
|
||||
tally = by_name.setdefault(PurePosixPath(rel).name, {})
|
||||
tally[topic] = tally.get(topic, 0) + 1
|
||||
unlisted = sorted(present - owner.keys())
|
||||
adopted: dict[str, dict[str, int]] = {}
|
||||
for rel in unlisted:
|
||||
folder, name = rel.rpartition("/")[0], PurePosixPath(rel).name
|
||||
if folder in votes or name == "index.md":
|
||||
continue
|
||||
tally = adopted.setdefault(folder, {})
|
||||
for topic, count in by_name.get(name, {}).items():
|
||||
tally[topic] = tally.get(topic, 0) + count
|
||||
for rel in unlisted:
|
||||
folder = rel.rpartition("/")[0]
|
||||
tally = votes.get(folder) or adopted.get(folder)
|
||||
if tally:
|
||||
extra.setdefault(max(tally, key=tally.get), []).append(path)
|
||||
topic = max(tally, key=tally.get)
|
||||
elif topics:
|
||||
topic = topics[0]["id"]
|
||||
else:
|
||||
exclude.append(path)
|
||||
return {"topics": topics, "options": {"topics": extra, "exclude": exclude},
|
||||
continue
|
||||
extra.setdefault(topic, []).append("/" + (_sidebar_link(rel).strip("/") or "index"))
|
||||
return {"topics": topics, "options": {"topics": extra, "exclude": []},
|
||||
"groups": groups, "reading": _reading(nav, present)}
|
||||
|
||||
|
||||
|
||||
@@ -117,10 +117,12 @@ class GroupsAreGroups(unittest.TestCase):
|
||||
|
||||
def test_explore_groups_collapse_to_their_overview(self):
|
||||
explore = {key: value for key, value in self.groups.items() if key.startswith("Explore/")}
|
||||
self.assertEqual(explore["Explore/Systems"], {"href": "/retrobios/systems/", "prune": True})
|
||||
self.assertEqual(explore["Explore/Systems"],
|
||||
{"href": "/retrobios/systems/", "prune": True, "crumb": True})
|
||||
self.assertEqual(explore["Explore/Systems/Consoles and computers"]["href"],
|
||||
"/retrobios/systems/#consoles-and-computers")
|
||||
self.assertEqual(explore["Explore/Emulators/D"], {"href": "/retrobios/emulators/#d", "prune": True})
|
||||
self.assertEqual(explore["Explore/Emulators/D"],
|
||||
{"href": "/retrobios/emulators/#d", "prune": True, "crumb": False})
|
||||
|
||||
def test_no_group_points_at_one_of_its_own_pages(self):
|
||||
"""The overview a group opens on is its own index page or a section of
|
||||
|
||||
@@ -120,7 +120,8 @@ class FieldsFromFrontMatter(unittest.TestCase):
|
||||
"terms": "bios not found, bios missing",
|
||||
})["text"]
|
||||
self.assertIn('data-pagefind-meta="consoles:Neo Geo Neo Geo Pocket Color"', text)
|
||||
self.assertIn('data-pagefind-meta="terms:bios not found bios missing"', text)
|
||||
self.assertIn('data-pagefind-meta="terms:bios not found bios missing Neo Geo BIOS '
|
||||
'Neo Geo Pocket Color BIOS"', text)
|
||||
# A field value is an attribute: none of it reaches an excerpt.
|
||||
self.assertNotRegex(text, r">[^<]*bios missing[^<]*<")
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// What each page knows of its place in the site, computed once per page
|
||||
// from what scripts/sitestarlight.py writes beside the sidebar:
|
||||
// - breadcrumbs: the tab and the groups above the page, each linked to the
|
||||
// page or the section that introduces it, never to a sibling page;
|
||||
// page or the section that introduces it, never to a sibling page. A
|
||||
// group of names by their first letter (A, B) is no crumb;
|
||||
// - previous and next: the reading order of `reading`. Start here runs on
|
||||
// into Your setup, every other tab is read on its own, and a system,
|
||||
// emulator or platform page has none;
|
||||
@@ -19,7 +20,7 @@ import sidebar from './generated/sidebar.json' with { type: 'json' };
|
||||
|
||||
type Entry = StarlightRouteData['sidebar'][number];
|
||||
type Link = Extract<Entry, { type: 'link' }>;
|
||||
type Group = { href: string | null; prune: boolean };
|
||||
type Group = { href: string | null; prune: boolean; crumb?: boolean };
|
||||
export type Crumb = { label: string; href?: string };
|
||||
|
||||
const BASE = import.meta.env.BASE_URL.replace(/\/?$/, '/');
|
||||
@@ -55,7 +56,11 @@ const trail = (entries: Entry[], labels: string[]): Crumb[] | undefined => {
|
||||
const here = [...labels, entry.label];
|
||||
const below = trail(entry.entries, here);
|
||||
if (below) {
|
||||
const href = groupOf(here)?.href;
|
||||
const group = groupOf(here);
|
||||
// A group of names by their first letter is a way through the
|
||||
// list, not a place: the crumb stops at the list.
|
||||
if (group?.crumb === false) return below;
|
||||
const href = group?.href;
|
||||
return [{ label: entry.label, href: href ? withBase(href) : undefined }, ...below];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,13 @@ QUERIES: dict[str, tuple[tuple[str, ...], int]] = {
|
||||
"psx bios": (("systems/sony",), 3),
|
||||
"dc bios missing": (("systems/sega", "emulators/flycast/"), 3),
|
||||
"gba bios": (("systems/nintendo",), 3),
|
||||
# A console whose page answers "No file needed" says BIOS only where the
|
||||
# search does not read: its `terms` field carries "<name> BIOS".
|
||||
"snes bios": (("systems/nintendo-snes/",), 1),
|
||||
"nes bios": (("systems/nintendo-nes/",), 1),
|
||||
"gamecube bios": (("systems/nintendo-gamecube/",), 1),
|
||||
"game boy bios": (("systems/nintendo-game-boy/",), 1),
|
||||
"3ds bios": (("systems/nintendo-3ds/",), 1),
|
||||
# The words an emulator prints, and the page that answers them.
|
||||
"bios not found": (("wiki/troubleshooting/",), 3),
|
||||
"please insert playstation cd-rom": (("wiki/troubleshooting/",), 3),
|
||||
@@ -74,12 +81,20 @@ QUERIES: dict[str, tuple[tuple[str, ...], int]] = {
|
||||
# Checking a dump is what the Find a file page does.
|
||||
"check my dump": (("files/",), 1),
|
||||
"md5 check": (("files/",), 1),
|
||||
"is my bios correct": (("files/",), 3),
|
||||
# A folder is checked by the setup's command, which troubleshooting runs.
|
||||
"check my bios folder": (("wiki/troubleshooting/",), 3),
|
||||
# A tool author's words: the data reference or the installer's.
|
||||
"manifest": (("data/", "wiki/installer/"), 3),
|
||||
"events": (("data/", "wiki/installer/"), 3),
|
||||
"exit code": (("data/", "wiki/installer/"), 3),
|
||||
"rate limit": (("data/", "wiki/installer/"), 3),
|
||||
"ios": (("wiki/faq/",), 3),
|
||||
"schema": (("data/",), 3),
|
||||
"json schema": (("data/",), 3),
|
||||
"license": (("data/", "wiki/legal/"), 3),
|
||||
"check bios folder script": (("data/",), 3),
|
||||
# The iPhone and iPad answer: copy the files to the app, no terminal.
|
||||
"ios": (("which-pack/", "wiki/faq/"), 3),
|
||||
# A device name: the guide of the handhelds that run it.
|
||||
"r36s": (("platforms/", "which-pack/"), 3),
|
||||
"steam deck": (("platforms/emudeck/", "platforms/retrodeck/", "which-pack/"), 1),
|
||||
|
||||
Reference in new issue
Block a user