mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-11 20:43:50 -06:00
feat: guide a stale link and the first download
Readers reached the 404 page with a pasted command (/i.ps1%20%7C%20iex), a file name, a GitHub path or a section below a page: the page now says which it is and leads on. Beside "Start with", the first file's download sits next to its name.
This commit is contained in:
1 parent
a7a27028ab
commit
29aeccba3c
3 files changed
+148
-12
No files matched your search
@@ -11584,7 +11584,7 @@ def generate_not_found() -> str:
|
||||
"- Browse [platforms](platforms/index.md), [systems](systems/index.md) "
|
||||
"or [emulators](emulators/index.md)",
|
||||
"",
|
||||
'<p id="rb-notfound-hint" hidden></p>',
|
||||
'<div id="rb-notfound-hint" hidden></div>',
|
||||
"",
|
||||
])
|
||||
|
||||
|
||||
+132
-11
@@ -1282,19 +1282,139 @@ const filterFiles = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// The 404 page: a search button, and the list a moved page belongs to.
|
||||
// "Start with" names the file to get first and links its card, where the
|
||||
// download is. A console's first file is the most downloaded of the site
|
||||
// (the PS2's: 436 in three days): its download sits beside its name too,
|
||||
// the card's own link reused, never a second address.
|
||||
const startDownloads = () => {
|
||||
for (const strong of document.querySelectorAll<HTMLElement>('.sl-markdown-content strong')) {
|
||||
if (strong.textContent?.trim() !== 'Start with') continue;
|
||||
const block = strong.closest('p')?.nextElementSibling?.matches('ul') && !strong.closest('p')?.querySelector('a[href^="#sha1-"]')
|
||||
? strong.closest('p')!.nextElementSibling!
|
||||
: strong.closest('p');
|
||||
for (const named of block?.querySelectorAll<HTMLAnchorElement>('a[href^="#sha1-"]') ?? []) {
|
||||
const card = document.getElementById(named.hash.slice(1))?.closest('.rb-sys-file');
|
||||
const source = card?.querySelector<HTMLAnchorElement>('a.rb-download');
|
||||
if (!source || named.nextElementSibling?.classList.contains('rb-start-download')) continue;
|
||||
const download = document.createElement('a');
|
||||
download.className = 'rb-start-download';
|
||||
download.href = source.href;
|
||||
download.textContent = 'Download';
|
||||
download.setAttribute('aria-label', source.getAttribute('aria-label') ?? 'Download');
|
||||
named.after(' ', download);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// The 404 page: a search button, and what the address it was asked for
|
||||
// looks like. The addresses readers reached (counted, wiki/privacy.md): a
|
||||
// command pasted as a link (/i.ps1%20%7C%20iex), a file name under files/,
|
||||
// a GitHub path (/blob/main/bios), a section below a page
|
||||
// (/platforms/emudeck/install). The address is only ever set as text.
|
||||
const INSTALL_COMMANDS: [string, string][] = [
|
||||
['Windows (PowerShell)', 'irm https://abdess.github.io/retrobios/i.ps1 | iex'],
|
||||
['Linux, macOS, Steam Deck, handhelds', 'curl -fsSL https://abdess.github.io/retrobios/i | sh'],
|
||||
];
|
||||
|
||||
const openSearch = (query = '') => {
|
||||
document.querySelector<HTMLButtonElement>('site-search button[data-open-modal]')?.click();
|
||||
if (!query) return;
|
||||
// The search box exists once the search has loaded: fill it then.
|
||||
const fill = (tries: number) => {
|
||||
const input = document.querySelector<HTMLInputElement>('.pagefind-ui__search-input');
|
||||
if (input) {
|
||||
input.value = query;
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
} else if (tries > 0) {
|
||||
setTimeout(() => fill(tries - 1), 100);
|
||||
}
|
||||
};
|
||||
fill(30);
|
||||
};
|
||||
|
||||
const hintLine = (...parts: (string | Node)[]) => {
|
||||
const line = document.createElement('p');
|
||||
line.append(...parts);
|
||||
return line;
|
||||
};
|
||||
|
||||
const notFound = () => {
|
||||
document.querySelector('[data-rb-open-search]')?.addEventListener('click', () => {
|
||||
document.querySelector<HTMLButtonElement>('site-search button[data-open-modal]')?.click();
|
||||
});
|
||||
document.querySelector('[data-rb-open-search]')?.addEventListener('click', () => openSearch());
|
||||
const hint = document.getElementById('rb-notfound-hint');
|
||||
const match = location.pathname.match(/\/(emulators|systems|platforms)\/([^/]+)\/?$/);
|
||||
if (!hint || !match) return;
|
||||
const link = document.createElement('a');
|
||||
link.href = new URL(`${match[1]}/`, siteRoot()).href;
|
||||
link.textContent = `the list of ${match[1]}`;
|
||||
hint.append(`No page is named ${decodeURIComponent(match[2])} here. It may be listed under another name in `, link, '.');
|
||||
hint.hidden = false;
|
||||
if (!hint) return;
|
||||
const base = siteRoot().pathname;
|
||||
let rest = location.pathname.startsWith(base) ? location.pathname.slice(base.length) : location.pathname;
|
||||
try {
|
||||
rest = decodeURIComponent(rest);
|
||||
} catch {
|
||||
// A malformed escape stays as typed.
|
||||
}
|
||||
const show = (...lines: HTMLElement[]) => {
|
||||
hint.replaceChildren(...lines);
|
||||
hint.hidden = false;
|
||||
};
|
||||
|
||||
// A command that a chat or a forum turned into a link.
|
||||
if (/^i(\.ps1)?[\s|)(;'"&-]/.test(rest)) {
|
||||
const list = document.createElement('ul');
|
||||
for (const [where, command] of INSTALL_COMMANDS) {
|
||||
const item = document.createElement('li');
|
||||
const code = document.createElement('code');
|
||||
code.textContent = command;
|
||||
item.append(`${where}: `, code);
|
||||
list.append(item);
|
||||
}
|
||||
show(hintLine('This address is an install command, not a page. Paste the command into a terminal, not into the browser:'), list);
|
||||
return;
|
||||
}
|
||||
// A GitHub path: the files are browsed on the repository.
|
||||
const github = rest.match(/^(blob|tree|raw)\/(.+)$/);
|
||||
if (github) {
|
||||
const link = document.createElement('a');
|
||||
link.href = `https://github.com/Abdess/retrobios/${github[1] === 'raw' ? 'blob' : github[1]}/${github[2]}`;
|
||||
link.textContent = 'the same path on GitHub';
|
||||
show(hintLine('This is a path of the GitHub repository. Open ', link, '.'));
|
||||
return;
|
||||
}
|
||||
// A file name: the search finds the pages that list it.
|
||||
const file = rest.match(/(?:^|\/)([^/]+\.[a-z0-9]{1,5})\/?$/i);
|
||||
const word = file ? file[1] : rest.replace(/\/$/, '').split('/').pop() ?? '';
|
||||
const lines: HTMLElement[] = [];
|
||||
const known = rest.match(/^(emulators|systems|platforms)\/([^/]+)\/?$/);
|
||||
if (known) {
|
||||
const link = document.createElement('a');
|
||||
link.href = new URL(`${known[1]}/`, siteRoot()).href;
|
||||
link.textContent = `the list of ${known[1]}`;
|
||||
lines.push(hintLine(`No page is named ${known[2]} here. It may be listed under another name in `, link, '.'));
|
||||
}
|
||||
if (word && word.length <= 60) {
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'rb-notfound-search';
|
||||
button.textContent = `Search for ${word}`;
|
||||
button.addEventListener('click', () => openSearch(word));
|
||||
lines.push(hintLine(button));
|
||||
}
|
||||
if (lines.length) show(...lines);
|
||||
// A section below a page: the nearest page above it that exists.
|
||||
const parts = rest.replace(/\/$/, '').split('/');
|
||||
const parents = parts.slice(0, -1).map((_, index) => parts.slice(0, parts.length - 1 - index).join('/'));
|
||||
(async () => {
|
||||
for (const parent of parents) {
|
||||
const url = new URL(`${parent}/`, siteRoot());
|
||||
try {
|
||||
if ((await fetch(url, { method: 'HEAD' })).ok) {
|
||||
const link = document.createElement('a');
|
||||
link.href = url.href;
|
||||
link.textContent = url.pathname;
|
||||
show(...lines, hintLine('The page above it: ', link, '.'));
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
}
|
||||
})();
|
||||
};
|
||||
|
||||
resolveFileAddress();
|
||||
@@ -1310,4 +1430,5 @@ filterFiles();
|
||||
osTabs();
|
||||
installPickers();
|
||||
showCurrentPage();
|
||||
startDownloads();
|
||||
notFound();
|
||||
@@ -504,6 +504,21 @@ body:has(.rb-sys-file, .rb-file-entry) { --sl-content-width: var(--rb-grid-width
|
||||
vertical-align: middle;
|
||||
}
|
||||
.rb-download:hover { background: var(--sl-color-accent-low); }
|
||||
/* The download beside a "Start with" file (site.ts startDownloads): the
|
||||
card's button, at the size of the line it sits in. */
|
||||
.sl-markdown-content a.rb-start-download {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 24px;
|
||||
padding: 0 0.6rem;
|
||||
border: 1px solid var(--rb-link);
|
||||
border-radius: 4px;
|
||||
font-size: 0.8em;
|
||||
font-weight: 600;
|
||||
color: var(--rb-link);
|
||||
text-decoration: none;
|
||||
}
|
||||
.sl-markdown-content a.rb-start-download:hover { background: var(--sl-color-accent-low); }
|
||||
|
||||
/* The facts of a file card: label column, value column, one column on a phone. */
|
||||
.sl-markdown-content dl.rb-facts {
|
||||
|
||||
Reference in new issue
Block a user