mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-29 23:27:19 -05:00
feat: short code marks, lazy search, index fields
Inline code of 30 characters or fewer is marked rb-short at build time and the options table rb-options. The search UI loads on first use. Title, crumbs and footer leave the search index, and consoles and terms weigh as a title.
This commit is contained in:
1 parent
3566016189
commit
e14f935994
7 files changed
+625
-9
No files matched your search
@@ -52,7 +52,7 @@ class TablesStackWhenBuilt(unittest.TestCase):
|
||||
def test_the_plugin_is_registered_and_the_runtime_rule_is_gone(self):
|
||||
config = _read(CONFIG)
|
||||
self.assertIn("import stackTables from './src/plugins/rehype-stack-tables.mjs'", config)
|
||||
self.assertIn("hastPlugins: [stackTables()]", config)
|
||||
self.assertIn("hastPlugins: [stackTables(), ", config)
|
||||
self.assertNotIn("stackActionTable", _read(SITE_TS))
|
||||
|
||||
def test_setup_tables_carry_the_layout_in_the_html(self):
|
||||
|
||||
@@ -13,6 +13,7 @@ import starlightLinksValidator from 'starlight-links-validator';
|
||||
import sidebar from './src/generated/sidebar.json' with { type: 'json' };
|
||||
import lastmod from './src/generated/lastmod.json' with { type: 'json' };
|
||||
import stackTables from './src/plugins/rehype-stack-tables.mjs';
|
||||
import shortCode from './src/plugins/rehype-short-code.mjs';
|
||||
import unbrokenTokens from './src/plugins/ec-unbroken-tokens.mjs';
|
||||
|
||||
const SITE = 'https://abdess.github.io';
|
||||
@@ -52,7 +53,7 @@ export default defineConfig({
|
||||
redirects: readRedirects(),
|
||||
markdown: {
|
||||
processor: satteri({
|
||||
hastPlugins: [stackTables()],
|
||||
hastPlugins: [stackTables(), shortCode()],
|
||||
features: {
|
||||
headingAttributes: true,
|
||||
definitionList: true,
|
||||
@@ -81,6 +82,7 @@ export default defineConfig({
|
||||
MarkdownContent: './src/components/MarkdownContent.astro',
|
||||
Footer: './src/components/Footer.astro',
|
||||
Header: './src/components/Header.astro',
|
||||
Search: './src/components/Search.astro',
|
||||
},
|
||||
routeMiddleware: ['./src/routeData.ts'],
|
||||
tableOfContents: { minHeadingLevel: 2, maxHeadingLevel: 3 },
|
||||
@@ -109,15 +111,20 @@ export default defineConfig({
|
||||
// every other query. Two fields name the page's subject in other words
|
||||
// and weigh half a title: `aliases`, the ids setups file a system under
|
||||
// (psx, dreamcast, gba), and `devices`, the handhelds and boxes a
|
||||
// setup's own site names (R36S, Miyoo Mini). All three are attributes
|
||||
// on an empty element, so no text of theirs reaches an excerpt.
|
||||
// setup's own site names (R36S, Miyoo Mini). Two more weigh as much as
|
||||
// a title, since each names what a reader types to reach the page:
|
||||
// `consoles`, the consoles a manufacturer page holds (Neo Geo,
|
||||
// WonderSwan Color), and `terms`, the words of a task or a message on
|
||||
// screen (bios not found, mame version, check dump). All of them are
|
||||
// attributes on an empty element, so no text of theirs reaches an
|
||||
// excerpt.
|
||||
pagefind: {
|
||||
ranking: {
|
||||
pageLength: 0.1,
|
||||
termFrequency: 0.5,
|
||||
termSaturation: 1.4,
|
||||
termSimilarity: 9,
|
||||
metaWeights: { title: 10, files: 0.5, aliases: 5, devices: 5 },
|
||||
metaWeights: { title: 10, files: 0.5, aliases: 5, devices: 5, consoles: 10, terms: 10 },
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// Starlight's footer (edit link, previous and next), who keeps the project
|
||||
// and where to report a problem, the release the packs come from, and the
|
||||
// licence line. The release is read from release.json, written when the
|
||||
// packs were published: the site may be newer than the packs.
|
||||
// packs were published: the site may be newer than the packs. None of it
|
||||
// is in the search index: "MIT for the tooling" was the excerpt of short
|
||||
// pages, and "bios", "release" and "problem" matched every page.
|
||||
import Default from '@astrojs/starlight/components/Footer.astro';
|
||||
import release from '../../../release.json';
|
||||
|
||||
@@ -19,6 +21,7 @@ const tag: string = release.tag;
|
||||
const date = releaseDate(tag);
|
||||
---
|
||||
|
||||
<div class="rb-footer-root" data-pagefind-ignore>
|
||||
<Default />
|
||||
<div class="rb-footer">
|
||||
<p>
|
||||
@@ -33,8 +36,11 @@ const date = releaseDate(tag);
|
||||
archival and interoperability.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* The wrapper only keeps the footer out of the search index: it adds no box. */
|
||||
.rb-footer-root { display: contents; }
|
||||
.rb-footer {
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1rem;
|
||||
|
||||
@@ -51,4 +51,11 @@ const { searchWeight } = Astro.locals.starlightRoute.entry.data;
|
||||
.sl-markdown-content .rb-file-pick-note { color: var(--sl-color-gray-3); font-size: var(--sl-text-sm); }
|
||||
.sl-markdown-content .rb-file-pick-hashes code { overflow-wrap: anywhere; }
|
||||
.sl-markdown-content .rb-file-pick-hashes[hidden] { display: none; }
|
||||
/* On touch the copy button is always shown, and it covered the end of a
|
||||
command's first line. A titled frame (Terminal, PowerShell) has a
|
||||
header row with room on its right: the button sits there. */
|
||||
@media (pointer: coarse) {
|
||||
.expressive-code .frame.has-title .copy { inset-block-start: calc(var(--ec-brdWd) + 0.0625rem); }
|
||||
.expressive-code .frame.has-title .copy button { width: 2rem; height: 2rem; }
|
||||
}
|
||||
</style>
|
||||
@@ -7,6 +7,10 @@
|
||||
// a dark mark stays visible in the dark theme. A setup page with no logo file
|
||||
// gets its initials in the same square, as its tile does (sitemarkup.py
|
||||
// platform_tiles).
|
||||
//
|
||||
// The crumbs and the title stay out of the search index: every excerpt
|
||||
// began with the title and the crumbs matched "bios" on every page. The
|
||||
// title still names the result, as the page's title field.
|
||||
import setups from '../../public/data/picker/index.json';
|
||||
|
||||
const PAGE_TITLE_ID = '_top';
|
||||
@@ -46,7 +50,7 @@ const setup = !logoSrc && setups.find((entry) => id === `platforms/${entry.platf
|
||||
|
||||
{
|
||||
crumbs.length > 0 && (
|
||||
<nav class="rb-breadcrumbs" aria-label="Breadcrumb">
|
||||
<nav class="rb-breadcrumbs" aria-label="Breadcrumb" data-pagefind-ignore>
|
||||
<ol>
|
||||
{crumbs.map((crumb) => (
|
||||
<li>{crumb.href ? <a href={crumb.href}>{crumb.label}</a> : crumb.label}</li>
|
||||
@@ -68,10 +72,10 @@ const setup = !logoSrc && setups.find((entry) => id === `platforms/${entry.platf
|
||||
</text>
|
||||
</svg>
|
||||
)}
|
||||
<h1 id={PAGE_TITLE_ID}>{title}</h1>
|
||||
<h1 id={PAGE_TITLE_ID} data-pagefind-ignore data-pagefind-meta="title">{title}</h1>
|
||||
</div>
|
||||
) : (
|
||||
<h1 id={PAGE_TITLE_ID}>{title}</h1>
|
||||
<h1 id={PAGE_TITLE_ID} data-pagefind-ignore data-pagefind-meta="title">{title}</h1>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,498 @@
|
||||
---
|
||||
// Starlight's search (@astrojs/starlight 0.42.6, components/Search.astro),
|
||||
// with one change: the Pagefind UI (ui-core.js, 26 KiB) loads when the
|
||||
// reader reaches for search, a pointer on the button, focus on it, a tap,
|
||||
// Ctrl+K, and not on idle on every page. It was a third of the home page's
|
||||
// weight. The markup and the styles are Starlight's own.
|
||||
import { Icon } from '@astrojs/starlight/components';
|
||||
import project from 'virtual:starlight/project-context';
|
||||
|
||||
const pagefindTranslations = {
|
||||
placeholder: Astro.locals.t('search.label'),
|
||||
...Object.fromEntries(
|
||||
Object.entries(Astro.locals.t.all())
|
||||
.filter(([key]) => key.startsWith('pagefind.'))
|
||||
.map(([key, value]) => [key.replace('pagefind.', ''), value])
|
||||
),
|
||||
};
|
||||
|
||||
const dataAttributes: DOMStringMap = { 'data-translations': JSON.stringify(pagefindTranslations) };
|
||||
if (project.trailingSlash === 'never') dataAttributes['data-strip-trailing-slash'] = '';
|
||||
---
|
||||
|
||||
<site-search class={Astro.props.class} {...dataAttributes}>
|
||||
<button
|
||||
data-open-modal
|
||||
disabled
|
||||
aria-label={Astro.locals.t('search.label')}
|
||||
aria-keyshortcuts="Control+K"
|
||||
>
|
||||
<Icon name="magnifier" />
|
||||
<span class="sl-hidden md:sl-block" aria-hidden="true">{Astro.locals.t('search.label')}</span>
|
||||
<kbd class="sl-hidden md:sl-flex" style="display: none;">
|
||||
<kbd>{Astro.locals.t('search.ctrlKey')}</kbd><kbd>K</kbd>
|
||||
</kbd>
|
||||
</button>
|
||||
|
||||
<dialog style="padding:0" aria-label={Astro.locals.t('search.label')}>
|
||||
<div class="dialog-frame sl-flex">
|
||||
<button data-close-modal class="sl-flex md:sl-hidden">
|
||||
{Astro.locals.t('search.cancelLabel')}
|
||||
</button>
|
||||
{
|
||||
import.meta.env.DEV ? (
|
||||
<div style="margin: auto; text-align: center; white-space: pre-line;" dir="ltr">
|
||||
<p>{Astro.locals.t('search.devWarning')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<div class="search-container">
|
||||
<div id="starlight__search" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</dialog>
|
||||
</site-search>
|
||||
|
||||
{
|
||||
/**
|
||||
* This is intentionally inlined to avoid briefly showing an invalid shortcut.
|
||||
* Purposely using the deprecated `navigator.platform` property to detect Apple devices, as the
|
||||
* user agent is spoofed by some browsers when opening the devtools.
|
||||
*/
|
||||
}
|
||||
<script is:inline>
|
||||
(() => {
|
||||
const openBtn = document.querySelector('button[data-open-modal]');
|
||||
const shortcut = openBtn?.querySelector('kbd');
|
||||
if (!openBtn || !(shortcut instanceof HTMLElement)) return;
|
||||
const platformKey = shortcut.querySelector('kbd');
|
||||
if (platformKey && /(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)) {
|
||||
platformKey.textContent = '⌘';
|
||||
openBtn.setAttribute('aria-keyshortcuts', 'Meta+K');
|
||||
}
|
||||
shortcut.style.display = '';
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script>
|
||||
import { pagefindUserConfig } from 'virtual:starlight/pagefind-config';
|
||||
|
||||
class SiteSearch extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
const openBtn = this.querySelector<HTMLButtonElement>('button[data-open-modal]')!;
|
||||
const closeBtn = this.querySelector<HTMLButtonElement>('button[data-close-modal]')!;
|
||||
const dialog = this.querySelector('dialog')!;
|
||||
const dialogFrame = this.querySelector('.dialog-frame')!;
|
||||
|
||||
/** Close the modal if a user clicks on a link or outside of the modal. */
|
||||
const onClick = (event: MouseEvent) => {
|
||||
const isLink = 'href' in (event.target || {});
|
||||
if (
|
||||
isLink ||
|
||||
(document.body.contains(event.target as Node) &&
|
||||
!dialogFrame.contains(event.target as Node))
|
||||
) {
|
||||
closeModal();
|
||||
}
|
||||
};
|
||||
|
||||
const openModal = (event?: MouseEvent) => {
|
||||
dialog.showModal();
|
||||
document.body.toggleAttribute('data-search-modal-open', true);
|
||||
this.querySelector('input')?.focus();
|
||||
event?.stopPropagation();
|
||||
window.addEventListener('click', onClick);
|
||||
// Opened before the UI was there: its input takes the focus once drawn.
|
||||
loadSearch()?.then(() => {
|
||||
if (dialog.open) this.querySelector('input')?.focus();
|
||||
});
|
||||
};
|
||||
|
||||
const closeModal = () => dialog.close();
|
||||
|
||||
openBtn.addEventListener('click', openModal);
|
||||
openBtn.disabled = false;
|
||||
closeBtn.addEventListener('click', closeModal);
|
||||
|
||||
dialog.addEventListener('close', () => {
|
||||
document.body.toggleAttribute('data-search-modal-open', false);
|
||||
window.removeEventListener('click', onClick);
|
||||
});
|
||||
|
||||
// Listen for `ctrl + k` and `cmd + k` keyboard shortcuts.
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if ((e.metaKey === true || e.ctrlKey === true) && e.key === 'k') {
|
||||
dialog.open ? closeModal() : openModal();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
let translations = {};
|
||||
try {
|
||||
translations = JSON.parse(this.dataset.translations || '{}');
|
||||
} catch {}
|
||||
|
||||
const shouldStrip = this.dataset.stripTrailingSlash !== undefined;
|
||||
const stripTrailingSlash = (path: string) => path.replace(/(.)\/(#.*)?$/, '$1$2');
|
||||
const formatURL = shouldStrip ? stripTrailingSlash : (path: string) => path;
|
||||
|
||||
let loading: Promise<void> | undefined;
|
||||
const loadSearch = () => {
|
||||
if (import.meta.env.DEV) return undefined;
|
||||
loading ??= (async () => {
|
||||
// @ts-expect-error: the @pagefind/default-ui package has no types.
|
||||
const { PagefindUI } = await import('@pagefind/default-ui');
|
||||
new PagefindUI({
|
||||
...pagefindUserConfig,
|
||||
element: '#starlight__search',
|
||||
baseUrl: import.meta.env.BASE_URL,
|
||||
bundlePath: import.meta.env.BASE_URL.replace(/\/$/, '') + '/pagefind/',
|
||||
showImages: false,
|
||||
translations,
|
||||
showSubResults: true,
|
||||
processResult: (result: { url: string; sub_results: Array<{ url: string }> }) => {
|
||||
result.url = formatURL(result.url);
|
||||
result.sub_results = result.sub_results.map((sub_result) => {
|
||||
sub_result.url = formatURL(sub_result.url);
|
||||
return sub_result;
|
||||
});
|
||||
},
|
||||
});
|
||||
})();
|
||||
return loading;
|
||||
};
|
||||
for (const type of ['pointerenter', 'pointerdown', 'focus']) {
|
||||
openBtn.addEventListener(type, () => loadSearch(), { once: true, passive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
customElements.define('site-search', SiteSearch);
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@layer starlight.core {
|
||||
site-search {
|
||||
display: contents;
|
||||
}
|
||||
button[data-open-modal] {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: var(--sl-color-gray-1);
|
||||
cursor: pointer;
|
||||
height: 2.5rem;
|
||||
font-size: var(--sl-text-xl);
|
||||
}
|
||||
|
||||
@media (min-width: 50rem) {
|
||||
button[data-open-modal] {
|
||||
border: 1px solid var(--sl-color-gray-5);
|
||||
border-radius: 0.5rem;
|
||||
padding-inline-start: 0.75rem;
|
||||
padding-inline-end: 0.5rem;
|
||||
background-color: var(--sl-color-black);
|
||||
color: var(--sl-color-gray-2);
|
||||
font-size: var(--sl-text-sm);
|
||||
width: 100%;
|
||||
max-width: 22rem;
|
||||
}
|
||||
button[data-open-modal]:hover {
|
||||
border-color: var(--sl-color-gray-2);
|
||||
color: var(--sl-color-white);
|
||||
}
|
||||
|
||||
button[data-open-modal] > :last-child {
|
||||
margin-inline-start: auto;
|
||||
}
|
||||
}
|
||||
|
||||
button > kbd {
|
||||
border-radius: 0.25rem;
|
||||
font-size: var(--sl-text-2xs);
|
||||
gap: 0.25em;
|
||||
padding-inline: 0.375rem;
|
||||
background-color: var(--sl-color-gray-6);
|
||||
}
|
||||
|
||||
kbd {
|
||||
font-family: var(--__sl-font);
|
||||
}
|
||||
|
||||
dialog {
|
||||
margin: 0;
|
||||
background-color: var(--sl-color-gray-6);
|
||||
border: 1px solid var(--sl-color-gray-5);
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
box-shadow: var(--sl-shadow-lg);
|
||||
}
|
||||
dialog[open] {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
dialog::backdrop {
|
||||
background-color: var(--sl-color-backdrop-overlay);
|
||||
-webkit-backdrop-filter: blur(0.25rem);
|
||||
backdrop-filter: blur(0.25rem);
|
||||
}
|
||||
|
||||
.dialog-frame {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
gap: 1rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
button[data-close-modal] {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
align-items: center;
|
||||
align-self: flex-end;
|
||||
height: calc(64px * var(--pagefind-ui-scale));
|
||||
padding: 0.25rem;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
color: var(--sl-color-text-accent);
|
||||
}
|
||||
|
||||
#starlight__search {
|
||||
--pagefind-ui-primary: var(--sl-color-text);
|
||||
--pagefind-ui-text: var(--sl-color-gray-2);
|
||||
--pagefind-ui-font: var(--__sl-font);
|
||||
--pagefind-ui-background: var(--sl-color-black);
|
||||
--pagefind-ui-border: var(--sl-color-gray-5);
|
||||
--pagefind-ui-border-width: 1px;
|
||||
--pagefind-ui-tag: var(--sl-color-gray-5);
|
||||
--sl-search-cancel-space: 5rem;
|
||||
}
|
||||
|
||||
:root[data-theme='light'] #starlight__search {
|
||||
--pagefind-ui-tag: var(--sl-color-gray-6);
|
||||
}
|
||||
|
||||
@media (min-width: 50rem) {
|
||||
#starlight__search {
|
||||
--sl-search-cancel-space: 0px;
|
||||
}
|
||||
|
||||
dialog {
|
||||
margin: 4rem auto auto;
|
||||
border-radius: 0.5rem;
|
||||
width: 90%;
|
||||
max-width: 40rem;
|
||||
height: max-content;
|
||||
min-height: 15rem;
|
||||
max-height: calc(100% - 8rem);
|
||||
}
|
||||
|
||||
.dialog-frame {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style is:global>
|
||||
@import url('@pagefind/default-ui/css/ui.css') layer(starlight.core);
|
||||
|
||||
@layer starlight.core {
|
||||
[data-search-modal-open] {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#starlight__search {
|
||||
--sl-search-result-spacing: calc(1.25rem * var(--pagefind-ui-scale));
|
||||
--sl-search-result-pad-inline-start: calc(3.75rem * var(--pagefind-ui-scale));
|
||||
--sl-search-result-pad-inline-end: calc(1.25rem * var(--pagefind-ui-scale));
|
||||
--sl-search-result-pad-block: calc(0.9375rem * var(--pagefind-ui-scale));
|
||||
--sl-search-result-nested-pad-block: calc(0.625rem * var(--pagefind-ui-scale));
|
||||
--sl-search-corners: calc(0.3125rem * var(--pagefind-ui-scale));
|
||||
--sl-search-page-icon-size: calc(1.875rem * var(--pagefind-ui-scale));
|
||||
--sl-search-page-icon-inline-start: calc(
|
||||
(var(--sl-search-result-pad-inline-start) - var(--sl-search-page-icon-size)) / 2
|
||||
);
|
||||
--sl-search-tree-diagram-size: calc(2.5rem * var(--pagefind-ui-scale));
|
||||
--sl-search-tree-diagram-inline-start: calc(
|
||||
(var(--sl-search-result-pad-inline-start) - var(--sl-search-tree-diagram-size)) / 2
|
||||
);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__form::before {
|
||||
--pagefind-ui-text: var(--sl-color-gray-1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__search-input {
|
||||
color: var(--sl-color-white);
|
||||
font-weight: 400;
|
||||
width: calc(100% - var(--sl-search-cancel-space));
|
||||
}
|
||||
|
||||
#starlight__search input:focus {
|
||||
--pagefind-ui-border: var(--sl-color-accent);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__search-clear {
|
||||
inset-inline-end: var(--sl-search-cancel-space);
|
||||
width: calc(60px * var(--pagefind-ui-scale));
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
}
|
||||
#starlight__search .pagefind-ui__search-clear:focus {
|
||||
outline: 1px solid var(--sl-color-accent);
|
||||
}
|
||||
#starlight__search .pagefind-ui__search-clear::before {
|
||||
content: '';
|
||||
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m13.41 12 6.3-6.29a1 1 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1 1 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 .33 1.64 1 1 0 0 0 1.09-.22l6.29-6.3 6.29 6.3a1 1 0 0 0 1.64-.33 1 1 0 0 0-.22-1.09L13.41 12Z'/%3E%3C/svg%3E")
|
||||
center / 50% no-repeat;
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='m13.41 12 6.3-6.29a1 1 0 1 0-1.42-1.42L12 10.59l-6.29-6.3a1 1 0 0 0-1.42 1.42l6.3 6.29-6.3 6.29a1 1 0 0 0 .33 1.64 1 1 0 0 0 1.09-.22l6.29-6.3 6.29 6.3a1 1 0 0 0 1.64-.33 1 1 0 0 0-.22-1.09L13.41 12Z'/%3E%3C/svg%3E")
|
||||
center / 50% no-repeat;
|
||||
background-color: var(--sl-color-text-accent);
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__results > * + * {
|
||||
margin-top: var(--sl-search-result-spacing);
|
||||
}
|
||||
#starlight__search .pagefind-ui__result {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-nested {
|
||||
position: relative;
|
||||
padding: var(--sl-search-result-nested-pad-block) var(--sl-search-result-pad-inline-end);
|
||||
padding-inline-start: var(--sl-search-result-pad-inline-start);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)),
|
||||
#starlight__search .pagefind-ui__result-nested {
|
||||
position: relative;
|
||||
background-color: var(--sl-color-black);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):hover,
|
||||
#starlight__search
|
||||
.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):focus-within,
|
||||
#starlight__search .pagefind-ui__result-nested:hover,
|
||||
#starlight__search .pagefind-ui__result-nested:focus-within {
|
||||
outline: 1px solid var(--sl-color-accent-high);
|
||||
}
|
||||
|
||||
#starlight__search
|
||||
.pagefind-ui__result-title:not(:where(.pagefind-ui__result-nested *)):focus-within,
|
||||
#starlight__search .pagefind-ui__result-nested:focus-within {
|
||||
background-color: var(--sl-color-accent-low);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-thumb,
|
||||
#starlight__search .pagefind-ui__result-inner {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-inner > :first-child {
|
||||
border-radius: var(--sl-search-corners) var(--sl-search-corners) 0 0;
|
||||
}
|
||||
#starlight__search .pagefind-ui__result-inner > :last-child {
|
||||
border-radius: 0 0 var(--sl-search-corners) var(--sl-search-corners);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-inner > .pagefind-ui__result-title {
|
||||
padding: var(--sl-search-result-pad-block) var(--sl-search-result-pad-inline-end);
|
||||
padding-inline-start: var(--sl-search-result-pad-inline-start);
|
||||
}
|
||||
#starlight__search .pagefind-ui__result-inner > .pagefind-ui__result-title::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline-start: var(--sl-search-page-icon-inline-start);
|
||||
width: var(--sl-search-page-icon-size);
|
||||
background: var(--sl-color-gray-3);
|
||||
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3V8l-6-6a1 1 0 0 0-1 0H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V9Zm-6-4 3 3h-2a1 1 0 0 1-1-1V5Zm4 14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z'/%3E%3C/svg%3E")
|
||||
center no-repeat;
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='currentColor' viewBox='0 0 24 24'%3E%3Cpath d='M9 10h1a1 1 0 1 0 0-2H9a1 1 0 0 0 0 2Zm0 2a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2H9Zm11-3V8l-6-6a1 1 0 0 0-1 0H7a3 3 0 0 0-3 3v14a3 3 0 0 0 3 3h10a3 3 0 0 0 3-3V9Zm-6-4 3 3h-2a1 1 0 0 1-1-1V5Zm4 14a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1V5a1 1 0 0 1 1-1h5v3a3 3 0 0 0 3 3h3v9Zm-3-3H9a1 1 0 0 0 0 2h6a1 1 0 0 0 0-2Z'/%3E%3C/svg%3E")
|
||||
center no-repeat;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-inner {
|
||||
align-items: stretch;
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-link {
|
||||
position: unset;
|
||||
--pagefind-ui-text: var(--sl-color-white);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-nested .pagefind-ui__result-link::before {
|
||||
content: unset;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-nested::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
inset-inline-start: var(--sl-search-tree-diagram-inline-start);
|
||||
width: var(--sl-search-tree-diagram-size);
|
||||
background: var(--sl-color-gray-4);
|
||||
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m6-988H8'/%3E%3C/svg%3E")
|
||||
0% 0% / 100% no-repeat;
|
||||
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' viewBox='0 0 16 1000' preserveAspectRatio='xMinYMin slice'%3E%3Cpath d='M8 0v1000m6-988H8'/%3E%3C/svg%3E")
|
||||
0% 0% / 100% no-repeat;
|
||||
}
|
||||
#starlight__search .pagefind-ui__result-nested:last-of-type::before {
|
||||
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='M8 0v12m6 0H8'/%3E%3C/svg%3E");
|
||||
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' viewBox='0 0 16 16'%3E%3Cpath d='M8 0v12m6 0H8'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
/* Flip page and tree icons around the vertical axis when in an RTL layout. */
|
||||
[dir='rtl'] .pagefind-ui__result-title::before,
|
||||
[dir='rtl'] .pagefind-ui__result-nested::before {
|
||||
transform: matrix(-1, 0, 0, 1, 0, 0);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-link::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-excerpt {
|
||||
font-size: calc(1rem * var(--pagefind-ui-scale));
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#starlight__search mark {
|
||||
color: var(--sl-color-gray-2);
|
||||
background-color: transparent;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__filter-value::before {
|
||||
border-color: var(--sl-color-text-invert);
|
||||
}
|
||||
|
||||
#starlight__search .pagefind-ui__result-tags {
|
||||
background-color: var(--sl-color-black);
|
||||
margin-top: 0;
|
||||
padding: var(--sl-search-result-nested-pad-block) var(--sl-search-result-pad-inline-end);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,94 @@
|
||||
// Code that must read in one piece, marked when the page is built (the CSS
|
||||
// is retrobios.css, code.rb-short and table.rb-options).
|
||||
//
|
||||
// - Inline code of SHORT_CODE characters or fewer gets `rb-short` and never
|
||||
// breaks: `--restore` split into `--` and `restore` on the home page at
|
||||
// 1440 px, and `--list-` / `systems` in the installer's options table.
|
||||
// Longer inline code (a path, a full command) still wraps.
|
||||
// - A table whose first column is "Option" gets `rb-options`, so its flags
|
||||
// keep their column.
|
||||
//
|
||||
// Satteri runs hast plugins (the rehype stage of its pipeline): this one is
|
||||
// registered in astro.config.mjs through satteri({ hastPlugins }).
|
||||
|
||||
export const SHORT_CODE = 30;
|
||||
const OPTION_HEADER = /^options?$/i;
|
||||
|
||||
const classes = (node) => {
|
||||
const value = node.properties?.className;
|
||||
if (Array.isArray(value)) return value.map(String);
|
||||
return value ? String(value).split(/\s+/) : [];
|
||||
};
|
||||
|
||||
const addClass = (ctx, node, name) => {
|
||||
const present = classes(node);
|
||||
if (!present.includes(name)) ctx.setProperty(node, 'className', [...present, name]);
|
||||
};
|
||||
|
||||
const firstHeader = (ctx, table) => {
|
||||
for (const part of table.children ?? []) {
|
||||
if (part.type !== 'element' || part.tagName !== 'thead') continue;
|
||||
for (const row of part.children ?? []) {
|
||||
if (row.type !== 'element' || row.tagName !== 'tr') continue;
|
||||
const cell = (row.children ?? []).find((child) => child.type === 'element' && child.tagName === 'th');
|
||||
return cell ? ctx.textContent(cell).trim() : '';
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
// A code element is inline unless a `pre` holds it.
|
||||
export const isShort = (text, inPre) => !inPre && text.length > 0 && text.length <= SHORT_CODE;
|
||||
|
||||
// The cards and tables the generator writes as HTML reach this step as raw
|
||||
// text: their code elements are marked by rewriting the tag. A block that
|
||||
// holds a `pre` is left as written.
|
||||
const RAW_CODE = /<code(\s[^>]*)?>([^<]*)<\/code>/g;
|
||||
const ENTITY = /&(?:#\d+|#x[0-9a-f]+|[a-z]+);/gi;
|
||||
|
||||
export const markRawCode = (html) => {
|
||||
if (html.includes('<pre')) return html;
|
||||
return html.replace(RAW_CODE, (whole, attributes = '', text) => {
|
||||
if (/\bclass=/.test(attributes) || !isShort(text.replace(ENTITY, '_'), false)) return whole;
|
||||
return `<code class="rb-short"${attributes}>${text}</code>`;
|
||||
});
|
||||
};
|
||||
|
||||
export default function shortCode() {
|
||||
return {
|
||||
name: 'rb-short-code',
|
||||
raw(node, ctx) {
|
||||
// Inline HTML in Markdown (a hash in a table cell) arrives in three
|
||||
// nodes: the opening tag, the text, the closing tag.
|
||||
const open = /^<code(\s[^>]*)?>$/.exec(node.value ?? '');
|
||||
if (open) {
|
||||
const siblings = ctx.parent(node)?.children ?? [];
|
||||
const index = ctx.indexOf(node) ?? -1;
|
||||
const [text, close] = [siblings[index + 1], siblings[index + 2]];
|
||||
if (text?.type === 'text' && close?.type === 'raw' && close.value === '</code>'
|
||||
&& !/\bclass=/.test(open[1] ?? '') && isShort(text.value, false)) {
|
||||
ctx.replaceNode(node, { type: 'raw', value: `<code class="rb-short"${open[1] ?? ''}>` });
|
||||
}
|
||||
return;
|
||||
}
|
||||
const value = markRawCode(node.value ?? '');
|
||||
if (value !== node.value) ctx.replaceNode(node, { type: 'raw', value });
|
||||
},
|
||||
element: [
|
||||
{
|
||||
filter: ['code'],
|
||||
visit(code, ctx) {
|
||||
const parent = ctx.parent(code);
|
||||
const inPre = parent?.type === 'element' && parent.tagName === 'pre';
|
||||
if (isShort(ctx.textContent(code), inPre)) addClass(ctx, code, 'rb-short');
|
||||
},
|
||||
},
|
||||
{
|
||||
filter: ['table'],
|
||||
visit(table, ctx) {
|
||||
if (OPTION_HEADER.test(firstHeader(ctx, table))) addClass(ctx, table, 'rb-options');
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user