0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-16 21:46:22 -05:00

fix: improve docs example (#4355)

* fix: improve docs example

* final touches

* chore: prettier

* lockfile

* ci?

* downgrade types node

* fresh lockfile

* lockfile and npmrc

* remove debug log

* Merge branch 'main' into docs-template-ts

* merging lockfiles suck

* update lockfile

* satisfy linter
This commit is contained in:
Julius Marminge 2022-08-29 18:00:08 +02:00 committed by GitHub
parent 046bfd908d
commit feb88afb8c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1067 additions and 890 deletions

1
.npmrc
View file

@ -2,6 +2,7 @@
prefer-workspace-packages=true prefer-workspace-packages=true
link-workspace-packages=true link-workspace-packages=true
save-workspace-protocol=false # This prevents the examples to have the `workspace:` prefix save-workspace-protocol=false # This prevents the examples to have the `workspace:` prefix
auto-install-peers=false
shamefully-hoist=true shamefully-hoist=true
# TODO: We would like to move to individual opt-in hoisting, but Astro was not originally # TODO: We would like to move to individual opt-in hoisting, but Astro was not originally

View file

@ -5,6 +5,7 @@
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev",
"start": "astro dev", "start": "astro dev",
"check": "astro check && tsc",
"build": "astro build", "build": "astro build",
"preview": "astro preview", "preview": "astro preview",
"astro": "astro" "astro": "astro"
@ -20,6 +21,8 @@
}, },
"devDependencies": { "devDependencies": {
"@astrojs/preact": "^1.0.2", "@astrojs/preact": "^1.0.2",
"@types/node": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@astrojs/react": "^1.1.0", "@astrojs/react": "^1.1.0",
"astro": "^1.1.1" "astro": "^1.1.1"
} }

View file

@ -1,12 +1,23 @@
--- ---
// fetch all commits for just this page's path // fetch all commits for just this page's path
const path = 'docs/' + Astro.props.path; type Props = {
const url = `https://api.github.com/repos/withastro/astro/commits?path=${path}`; path: string;
const commitsURL = `https://github.com/withastro/astro/commits/main/${path}`; };
const { path } = Astro.props as Props;
const resolvedPath = `examples/docs/${path}`;
const url = `https://api.github.com/repos/withastro/astro/commits?path=${resolvedPath}`;
const commitsURL = `https://github.com/withastro/astro/commits/main/${resolvedPath}`;
async function getCommits(url) { type Commit = {
author: {
id: string;
login: string;
};
};
async function getCommits(url: string) {
try { try {
const token = import.meta.env.SNOWPACK_PUBLIC_GITHUB_TOKEN; const token = import.meta.env.SNOWPACK_PUBLIC_GITHUB_TOKEN ?? 'hello';
if (!token) { if (!token) {
throw new Error( throw new Error(
'Cannot find "SNOWPACK_PUBLIC_GITHUB_TOKEN" used for escaping rate-limiting.' 'Cannot find "SNOWPACK_PUBLIC_GITHUB_TOKEN" used for escaping rate-limiting.'
@ -32,27 +43,24 @@ async function getCommits(url) {
); );
} }
return data; return data as Commit[];
} catch (e) { } catch (e) {
console.warn(`[error] /src/components/AvatarList.astro console.warn(`[error] /src/components/AvatarList.astro
${e?.message ?? e}`); ${(e as any)?.message ?? e}`);
return new Array(); return [] as Commit[];
} }
} }
function removeDups(arr) { function removeDups(arr: Commit[]) {
if (!arr) { const map = new Map<string, Commit['author']>();
return new Array();
}
let map = new Map();
for (let item of arr) { for (let item of arr) {
let author = item.author; const author = item.author;
// Deduplicate based on author.id // Deduplicate based on author.id
map.set(author.id, { login: author.login, id: author.id }); map.set(author.id, { login: author.login, id: author.id });
} }
return Array.from(map.values()); return [...map.values()];
} }
const data = await getCommits(url); const data = await getCommits(url);

View file

@ -1,16 +1,19 @@
--- ---
import AvatarList from './AvatarList.astro'; import AvatarList from './AvatarList.astro';
const { path } = Astro.props; type Props = {
path: string;
};
const { path } = Astro.props as Props;
--- ---
<footer> <footer>
<AvatarList {path} /> <AvatarList path={path} />
</footer> </footer>
<style> <style>
footer { footer {
margin-top: auto; margin-top: auto;
padding: 2rem 0; padding: 2rem;
border-top: 3px solid var(--theme-divider); border-top: 3px solid var(--theme-divider);
} }
</style> </style>

View file

@ -1,35 +1,32 @@
--- ---
import { SITE, OPEN_GRAPH } from '../config'; import { SITE, OPEN_GRAPH, Frontmatter } from '../config';
export interface Props { export interface Props {
frontmatter: any; frontmatter: Frontmatter;
site: any; canonicalUrl: URL;
canonicalURL: URL | string;
} }
const canonicalURL = new URL(Astro.url.pathname, Astro.site); const { frontmatter, canonicalUrl } = Astro.props as Props;
const { frontmatter = {} } = Astro.props; const formattedContentTitle = `${frontmatter.title} 🚀 ${SITE.title}`;
const formattedContentTitle = frontmatter.title const imageSrc = frontmatter.image?.src ?? OPEN_GRAPH.image.src;
? `${frontmatter.title} 🚀 ${SITE.title}`
: SITE.title;
const imageSrc = frontmatter?.image?.src ?? OPEN_GRAPH.image.src;
const canonicalImageSrc = new URL(imageSrc, Astro.site); const canonicalImageSrc = new URL(imageSrc, Astro.site);
const imageAlt = frontmatter?.image?.alt ?? OPEN_GRAPH.image.alt; const imageAlt = frontmatter.image?.alt ?? OPEN_GRAPH.image.alt;
--- ---
<!-- Page Metadata --> <!-- Page Metadata -->
<link rel="canonical" href={canonicalURL} /> <link rel="canonical" href={canonicalUrl} />
<!-- OpenGraph Tags --> <!-- OpenGraph Tags -->
<meta property="og:title" content={formattedContentTitle} /> <meta property="og:title" content={formattedContentTitle} />
<meta property="og:type" content="article" /> <meta property="og:type" content="article" />
<meta property="og:url" content={canonicalURL} /> <meta property="og:url" content={canonicalUrl} />
<meta property="og:locale" content={frontmatter.ogLocale ?? SITE.defaultLanguage} /> <meta property="og:locale" content={frontmatter.ogLocale ?? SITE.defaultLanguage} />
<meta property="og:image" content={canonicalImageSrc} /> <meta property="og:image" content={canonicalImageSrc} />
<meta property="og:image:alt" content={imageAlt} /> <meta property="og:image:alt" content={imageAlt} />
<meta <meta
name="description" name="description"
property="og:description" property="og:description"
content={frontmatter.description ? frontmatter.description : SITE.description} content={frontmatter.description ?? SITE.description}
/> />
<meta property="og:site_name" content={SITE.title} /> <meta property="og:site_name" content={SITE.title} />
@ -37,10 +34,7 @@ const imageAlt = frontmatter?.image?.alt ?? OPEN_GRAPH.image.alt;
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content={OPEN_GRAPH.twitter} /> <meta name="twitter:site" content={OPEN_GRAPH.twitter} />
<meta name="twitter:title" content={formattedContentTitle} /> <meta name="twitter:title" content={formattedContentTitle} />
<meta <meta name="twitter:description" content={frontmatter.description ?? SITE.description} />
name="twitter:description"
content={frontmatter.description ? frontmatter.description : SITE.description}
/>
<meta name="twitter:image" content={canonicalImageSrc} /> <meta name="twitter:image" content={canonicalImageSrc} />
<meta name="twitter:image:alt" content={imageAlt} /> <meta name="twitter:image:alt" content={imageAlt} />

View file

@ -1,5 +1,8 @@
--- ---
const { size } = Astro.props; type Props = {
size: number;
};
const { size } = Astro.props as Props;
--- ---
<svg <svg
@ -14,6 +17,7 @@ const { size } = Astro.props;
#flame { #flame {
fill: var(--theme-text-accent); fill: var(--theme-text-accent);
} }
#a { #a {
fill: var(--theme-text-accent); fill: var(--theme-text-accent);
} }
@ -24,11 +28,13 @@ const { size } = Astro.props;
fill-rule="evenodd" fill-rule="evenodd"
clip-rule="evenodd" clip-rule="evenodd"
d="M163.008 18.929c1.944 2.413 2.935 5.67 4.917 12.181l43.309 142.27a180.277 180.277 0 00-51.778-17.53l-28.198-95.29a3.67 3.67 0 00-7.042.01l-27.857 95.232a180.225 180.225 0 00-52.01 17.557l43.52-142.281c1.99-6.502 2.983-9.752 4.927-12.16a15.999 15.999 0 016.484-4.798c2.872-1.154 6.271-1.154 13.07-1.154h31.085c6.807 0 10.211 0 13.086 1.157a16.004 16.004 0 016.487 4.806z" d="M163.008 18.929c1.944 2.413 2.935 5.67 4.917 12.181l43.309 142.27a180.277 180.277 0 00-51.778-17.53l-28.198-95.29a3.67 3.67 0 00-7.042.01l-27.857 95.232a180.225 180.225 0 00-52.01 17.557l43.52-142.281c1.99-6.502 2.983-9.752 4.927-12.16a15.999 15.999 0 016.484-4.798c2.872-1.154 6.271-1.154 13.07-1.154h31.085c6.807 0 10.211 0 13.086 1.157a16.004 16.004 0 016.487 4.806z"
></path> >
</path>
<path <path
id="flame" id="flame"
fill-rule="evenodd" fill-rule="evenodd"
clip-rule="evenodd" clip-rule="evenodd"
d="M168.19 180.151c-7.139 6.105-21.39 10.268-37.804 10.268-20.147 0-37.033-6.272-41.513-14.707-1.602 4.835-1.961 10.367-1.961 13.902 0 0-1.056 17.355 11.015 29.426 0-6.268 5.081-11.349 11.349-11.349 10.743 0 10.731 9.373 10.721 16.977v.679c0 11.542 7.054 21.436 17.086 25.606a23.27 23.27 0 01-2.339-10.2c0-11.008 6.463-15.107 13.974-19.87 5.976-3.79 12.616-8.001 17.192-16.449a31.024 31.024 0 003.743-14.82c0-3.299-.513-6.479-1.463-9.463z" d="M168.19 180.151c-7.139 6.105-21.39 10.268-37.804 10.268-20.147 0-37.033-6.272-41.513-14.707-1.602 4.835-1.961 10.367-1.961 13.902 0 0-1.056 17.355 11.015 29.426 0-6.268 5.081-11.349 11.349-11.349 10.743 0 10.731 9.373 10.721 16.977v.679c0 11.542 7.054 21.436 17.086 25.606a23.27 23.27 0 01-2.339-10.2c0-11.008 6.463-15.107 13.974-19.87 5.976-3.79 12.616-8.001 17.192-16.449a31.024 31.024 0 003.743-14.82c0-3.299-.513-6.479-1.463-9.463z"
></path> >
</path>
</svg> </svg>

View file

@ -7,8 +7,12 @@ import SidebarToggle from './SidebarToggle';
import LanguageSelect from './LanguageSelect'; import LanguageSelect from './LanguageSelect';
import Search from './Search'; import Search from './Search';
const { currentPage } = Astro.props; type Props = {
const lang = currentPage && getLanguageFromURL(currentPage); currentPage: string;
};
const { currentPage } = Astro.props as Props;
const lang = getLanguageFromURL(currentPage);
--- ---
<header> <header>
@ -25,11 +29,9 @@ const lang = currentPage && getLanguageFromURL(currentPage);
</div> </div>
<div style="flex-grow: 1;"></div> <div style="flex-grow: 1;"></div>
{KNOWN_LANGUAGE_CODES.length > 1 && <LanguageSelect lang={lang} client:idle />} {KNOWN_LANGUAGE_CODES.length > 1 && <LanguageSelect lang={lang} client:idle />}
{CONFIG.ALGOLIA && ( <div class="search-item">
<div class="search-item"> <Search client:idle />
<Search client:idle /> </div>
</div>
)}
</nav> </nav>
</header> </header>
@ -101,14 +103,17 @@ const lang = currentPage && getLanguageFromURL(currentPage);
position: static; position: static;
padding: 2rem 0rem; padding: 2rem 0rem;
} }
.logo { .logo {
width: auto; width: auto;
margin: 0; margin: 0;
z-index: 0; z-index: 0;
} }
.logo h1 { .logo h1 {
display: initial; display: initial;
} }
.menu-toggle { .menu-toggle {
display: none; display: none;
} }
@ -129,9 +134,11 @@ const lang = currentPage && getLanguageFromURL(currentPage);
display: flex; display: flex;
max-width: 200px; max-width: 200px;
} }
:global(.search-item > *) { :global(.search-item > *) {
flex-grow: 1; flex-grow: 1;
} }
@media (min-width: 50em) { @media (min-width: 50em) {
.search-item { .search-item {
max-width: 400px; max-width: 400px;

View file

@ -1,11 +1,11 @@
import type { FunctionalComponent } from 'preact'; /** @jsxImportSource react */
import { h } from 'preact'; import type { FunctionComponent } from 'react';
import './LanguageSelect.css'; import './LanguageSelect.css';
import { KNOWN_LANGUAGES, langPathRegex } from '../../languages'; import { KNOWN_LANGUAGES, langPathRegex } from '../../languages';
const LanguageSelect: FunctionalComponent<{ lang: string }> = ({ lang }) => { const LanguageSelect: FunctionComponent<{ lang: string }> = ({ lang }) => {
return ( return (
<div class="language-select-wrapper"> <div className="language-select-wrapper">
<svg <svg
aria-hidden="true" aria-hidden="true"
focusable="false" focusable="false"
@ -25,7 +25,7 @@ const LanguageSelect: FunctionalComponent<{ lang: string }> = ({ lang }) => {
/> />
</svg> </svg>
<select <select
class="language-select" className="language-select"
value={lang} value={lang}
onChange={(e) => { onChange={(e) => {
const newLang = e.target.value; const newLang = e.target.value;
@ -34,9 +34,9 @@ const LanguageSelect: FunctionalComponent<{ lang: string }> = ({ lang }) => {
window.location.pathname = '/' + newLang + actualDest; window.location.pathname = '/' + newLang + actualDest;
}} }}
> >
{Object.keys(KNOWN_LANGUAGES).map((key) => { {Object.entries(KNOWN_LANGUAGES).map(([key, value]) => {
return ( return (
<option value={KNOWN_LANGUAGES[key]}> <option value={value}>
<span>{key}</span> <span>{key}</span>
</option> </option>
); );

View file

@ -1,23 +1,23 @@
/* jsxImportSource: react */ /** @jsxImportSource react */
import { useState, useCallback, useRef } from 'react'; import { useState, useCallback, useRef } from 'react';
import * as CONFIG from '../../config'; import { ALGOLIA } from '../../config';
import '@docsearch/css/dist/style.css'; import '@docsearch/css';
import './Search.css'; import './Search.css';
// @ts-ignore
import * as docSearchReact from '@docsearch/react';
// @ts-ignore
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import * as docSearchReact from '@docsearch/react';
/** FIXME: This is still kinda nasty, but DocSearch is not ESM ready. */
const DocSearchModal =
docSearchReact.DocSearchModal || (docSearchReact as any).default.DocSearchModal;
const useDocSearchKeyboardEvents =
docSearchReact.useDocSearchKeyboardEvents ||
(docSearchReact as any).default.useDocSearchKeyboardEvents;
export default function Search() { export default function Search() {
const DocSearchModal = docSearchReact.DocSearchModal || docSearchReact.default.DocSearchModal;
const useDocSearchKeyboardEvents =
docSearchReact.useDocSearchKeyboardEvents || docSearchReact.default.useDocSearchKeyboardEvents;
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const searchButtonRef = useRef(); const searchButtonRef = useRef<HTMLButtonElement>(null);
const [initialQuery, setInitialQuery] = useState(null); const [initialQuery, setInitialQuery] = useState('');
const onOpen = useCallback(() => { const onOpen = useCallback(() => {
setIsOpen(true); setIsOpen(true);
@ -73,9 +73,9 @@ export default function Search() {
initialQuery={initialQuery} initialQuery={initialQuery}
initialScrollY={window.scrollY} initialScrollY={window.scrollY}
onClose={onClose} onClose={onClose}
indexName={(CONFIG as any).ALGOLIA.indexName} indexName={ALGOLIA.indexName}
appId={(CONFIG as any).ALGOLIA.appId} appId={ALGOLIA.appId}
apiKey={(CONFIG as any).ALGOLIA.apiKey} apiKey={ALGOLIA.apiKey}
transformItems={(items) => { transformItems={(items) => {
return items.map((item) => { return items.map((item) => {
// We transform the absolute URL into a relative URL to // We transform the absolute URL into a relative URL to

View file

@ -1,12 +1,12 @@
/** @jsxImportSource preact */
import type { FunctionalComponent } from 'preact'; import type { FunctionalComponent } from 'preact';
import { h, Fragment } from 'preact';
import { useState, useEffect } from 'preact/hooks'; import { useState, useEffect } from 'preact/hooks';
const MenuToggle: FunctionalComponent = () => { const MenuToggle: FunctionalComponent = () => {
const [sidebarShown, setSidebarShown] = useState(false); const [sidebarShown, setSidebarShown] = useState(false);
useEffect(() => { useEffect(() => {
const body = document.getElementsByTagName('body')[0]; const body = document.querySelector('body')!;
if (sidebarShown) { if (sidebarShown) {
body.classList.add('mobile-sidebar-toggle'); body.classList.add('mobile-sidebar-toggle');
} else { } else {

View file

@ -1,3 +1,7 @@
---
type Props = {};
---
<a href="#article" class="sr-only focus:not-sr-only skiplink"><span>Skip to Content</span></a> <a href="#article" class="sr-only focus:not-sr-only skiplink"><span>Skip to Content</span></a>
<style> <style>

View file

@ -1,44 +1,34 @@
--- ---
import { getLanguageFromURL } from '../../languages'; import { getLanguageFromURL } from '../../languages';
import { SIDEBAR } from '../../config'; import { SIDEBAR } from '../../config';
const { currentPage } = Astro.props;
type Props = {
currentPage: string;
};
const { currentPage } = Astro.props as Props;
const currentPageMatch = currentPage.slice(1); const currentPageMatch = currentPage.slice(1);
const langCode = getLanguageFromURL(currentPage); const langCode = getLanguageFromURL(currentPage);
// SIDEBAR is a flat array. Group it by sections to properly render. const sidebar = SIDEBAR[langCode];
const sidebarSections = SIDEBAR[langCode].reduce((col, item, i) => {
// If the first item is not a section header, create a new container section.
if (i === 0) {
if (!item.header) {
const pseudoSection = { text: '' };
col.push({ ...pseudoSection, children: [] });
}
}
if (item.header) {
col.push({ ...item, children: [] });
} else {
col[col.length - 1].children.push(item);
}
return col;
}, []);
--- ---
<nav aria-labelledby="grid-left"> <nav aria-labelledby="grid-left">
<ul class="nav-groups"> <ul class="nav-groups">
{sidebarSections.map((section) => ( {Object.entries(sidebar).map(([header, children]) => (
<li> <li>
<div class="nav-group"> <div class="nav-group">
<h2 class="nav-group-title">{section.text}</h2> <h2>{header}</h2>
<ul> <ul>
{section.children.map((child) => ( {children.map((child) => {
<li class="nav-link"> const url = Astro.site?.pathname + child.link;
<a return (
href={`${Astro.site.pathname}${child.link}`} <li class="nav-link">
aria-current={`${currentPageMatch === child.link ? 'page' : 'false'}`} <a href={url} aria-current={currentPageMatch === child.link ? 'page' : false}>
> {child.text}
{child.text} </a>
</a> </li>
</li> );
))} })}
</ul> </ul>
</div> </div>
</li> </li>
@ -47,7 +37,7 @@ const sidebarSections = SIDEBAR[langCode].reduce((col, item, i) => {
</nav> </nav>
<script is:inline> <script is:inline>
window.addEventListener('DOMContentLoaded', (event) => { window.addEventListener('DOMContentLoaded', () => {
var target = document.querySelector('[aria-current="page"]'); var target = document.querySelector('[aria-current="page"]');
if (target && target.offsetTop > window.innerHeight - 100) { if (target && target.offsetTop > window.innerHeight - 100) {
document.querySelector('.nav-groups').scrollTop = target.offsetTop; document.querySelector('.nav-groups').scrollTop = target.offsetTop;
@ -60,6 +50,7 @@ const sidebarSections = SIDEBAR[langCode].reduce((col, item, i) => {
width: 100%; width: 100%;
margin-right: 1rem; margin-right: 1rem;
} }
.nav-groups { .nav-groups {
height: 100%; height: 100%;
padding: 2rem 0; padding: 2rem 0;
@ -98,6 +89,7 @@ const sidebarSections = SIDEBAR[langCode].reduce((col, item, i) => {
text-decoration: none; text-decoration: none;
display: block; display: block;
} }
.nav-link a:hover, .nav-link a:hover,
.nav-link a:focus { .nav-link a:focus {
background-color: var(--theme-bg-hover); background-color: var(--theme-bg-hover);

View file

@ -1,8 +1,16 @@
--- ---
import type { Frontmatter } from '../../config';
import MoreMenu from '../RightSidebar/MoreMenu.astro'; import MoreMenu from '../RightSidebar/MoreMenu.astro';
import TableOfContents from '../RightSidebar/TableOfContents'; import TableOfContents from '../RightSidebar/TableOfContents';
import type { MarkdownHeading } from 'astro';
const { frontmatter, headings, githubEditUrl } = Astro.props; type Props = {
frontmatter: Frontmatter;
headings: MarkdownHeading[];
githubEditUrl: string;
};
const { frontmatter, headings, githubEditUrl } = Astro.props as Props;
const title = frontmatter.title; const title = frontmatter.title;
--- ---
@ -10,7 +18,7 @@ const title = frontmatter.title;
<section class="main-section"> <section class="main-section">
<h1 class="content-title" id="overview">{title}</h1> <h1 class="content-title" id="overview">{title}</h1>
<nav class="block sm:hidden"> <nav class="block sm:hidden">
<TableOfContents client:media="(max-width: 50em)" {headings} /> <TableOfContents client:media="(max-width: 50em)" headings={headings} />
</nav> </nav>
<slot /> <slot />
</section> </section>

View file

@ -1,8 +1,13 @@
--- ---
import ThemeToggleButton from './ThemeToggleButton'; import ThemeToggleButton from './ThemeToggleButton';
import * as CONFIG from '../../config'; import * as CONFIG from '../../config';
const { editHref } = Astro.props;
const showMoreSection = CONFIG.COMMUNITY_INVITE_URL || editHref; type Props = {
editHref: string;
};
const { editHref } = Astro.props as Props;
const showMoreSection = CONFIG.COMMUNITY_INVITE_URL;
--- ---
{showMoreSection && <h2 class="heading">More</h2>} {showMoreSection && <h2 class="heading">More</h2>}

View file

@ -1,12 +1,19 @@
--- ---
import TableOfContents from './TableOfContents'; import TableOfContents from './TableOfContents';
import MoreMenu from './MoreMenu.astro'; import MoreMenu from './MoreMenu.astro';
const { headings, githubEditUrl } = Astro.props; import type { MarkdownHeading } from 'astro';
type Props = {
headings: MarkdownHeading[];
githubEditUrl: string;
};
const { headings, githubEditUrl } = Astro.props as Props;
--- ---
<nav class="sidebar-nav" aria-labelledby="grid-right"> <nav class="sidebar-nav" aria-labelledby="grid-right">
<div class="sidebar-nav-inner"> <div class="sidebar-nav-inner">
<TableOfContents client:media="(min-width: 50em)" {headings} /> <TableOfContents client:media="(min-width: 50em)" headings={headings} />
<MoreMenu editHref={githubEditUrl} /> <MoreMenu editHref={githubEditUrl} />
</div> </div>
</nav> </nav>

View file

@ -1,13 +1,18 @@
import type { FunctionalComponent } from 'preact'; import type { FunctionalComponent } from 'preact';
import { h, Fragment } from 'preact';
import { useState, useEffect, useRef } from 'preact/hooks'; import { useState, useEffect, useRef } from 'preact/hooks';
import { MarkdownHeading } from 'astro'; import type { MarkdownHeading } from 'astro';
type ItemOffsets = {
id: string;
topOffset: number;
};
const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[] }> = ({ const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[] }> = ({
headings = [], headings = [],
}) => { }) => {
const itemOffsets = useRef([]); const itemOffsets = useRef<ItemOffsets[]>([]);
const [activeId, setActiveId] = useState<string>(undefined); // FIXME: Not sure what this state is doing. It was never set to anything truthy.
const [activeId] = useState<string>('');
useEffect(() => { useEffect(() => {
const getItemOffsets = () => { const getItemOffsets = () => {
const titles = document.querySelectorAll('article :is(h1, h2, h3, h4)'); const titles = document.querySelectorAll('article :is(h1, h2, h3, h4)');
@ -27,16 +32,16 @@ const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[] }> = ({
return ( return (
<> <>
<h2 class="heading">On this page</h2> <h2 className="heading">On this page</h2>
<ul> <ul>
<li class={`heading-link depth-2 ${activeId === 'overview' ? 'active' : ''}`.trim()}> <li className={`heading-link depth-2 ${activeId === 'overview' ? 'active' : ''}`.trim()}>
<a href="#overview">Overview</a> <a href="#overview">Overview</a>
</li> </li>
{headings {headings
.filter(({ depth }) => depth > 1 && depth < 4) .filter(({ depth }) => depth > 1 && depth < 4)
.map((heading) => ( .map((heading) => (
<li <li
class={`heading-link depth-${heading.depth} ${ className={`heading-link depth-${heading.depth} ${
activeId === heading.slug ? 'active' : '' activeId === heading.slug ? 'active' : ''
}`.trim()} }`.trim()}
> >

View file

@ -1,5 +1,4 @@
import type { FunctionalComponent } from 'preact'; import type { FunctionalComponent } from 'preact';
import { h, Fragment } from 'preact';
import { useState, useEffect } from 'preact/hooks'; import { useState, useEffect } from 'preact/hooks';
import './ThemeToggleButton.css'; import './ThemeToggleButton.css';
@ -35,7 +34,7 @@ const ThemeToggle: FunctionalComponent = () => {
if (import.meta.env.SSR) { if (import.meta.env.SSR) {
return undefined; return undefined;
} }
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) { if (typeof localStorage !== undefined && localStorage.getItem('theme')) {
return localStorage.getItem('theme'); return localStorage.getItem('theme');
} }
if (window.matchMedia('(prefers-color-scheme: dark)').matches) { if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
@ -54,7 +53,7 @@ const ThemeToggle: FunctionalComponent = () => {
}, [theme]); }, [theme]);
return ( return (
<div class="theme-toggle"> <div className="theme-toggle">
{themes.map((t, i) => { {themes.map((t, i) => {
const icon = icons[i]; const icon = icons[i];
const checked = t === theme; const checked = t === theme;

View file

@ -14,33 +14,44 @@ export const OPEN_GRAPH = {
twitter: 'astrodotbuild', twitter: 'astrodotbuild',
}; };
// This is the type of the frontmatter you put in the docs markdown files.
export type Frontmatter = {
title: string;
description: string;
layout: string;
image?: { src: string; alt: string };
dir?: 'ltr' | 'rtl';
ogLocale?: string;
lang?: string;
};
export const KNOWN_LANGUAGES = { export const KNOWN_LANGUAGES = {
English: 'en', English: 'en',
}; } as const;
export const KNOWN_LANGUAGE_CODES = Object.values(KNOWN_LANGUAGES);
// Uncomment this to add an "Edit this page" button to every page of documentation. export const GITHUB_EDIT_URL = `https://github.com/withastro/astro/tree/main/examples/docs`;
// export const GITHUB_EDIT_URL = `https://github.com/withastro/astro/blob/main/docs/`;
// Uncomment this to add an "Join our Community" button to every page of documentation. export const COMMUNITY_INVITE_URL = `https://astro.build/chat`;
// export const COMMUNITY_INVITE_URL = `https://astro.build/chat`;
// Uncomment this to enable site search.
// See "Algolia" section of the README for more information. // See "Algolia" section of the README for more information.
// export const ALGOLIA = { export const ALGOLIA = {
// indexName: 'XXXXXXXXXX', indexName: 'XXXXXXXXXX',
// appId: 'XXXXXXXXXX', appId: 'XXXXXXXXXX',
// apiKey: 'XXXXXXXXXX', apiKey: 'XXXXXXXXXX',
// } };
export const SIDEBAR = { export type Sidebar = Record<
en: [ typeof KNOWN_LANGUAGE_CODES[number],
{ text: '', header: true }, Record<string, { text: string; link: string }[]>
{ text: 'Section Header', header: true }, >;
{ text: 'Introduction', link: 'en/introduction' }, export const SIDEBAR: Sidebar = {
{ text: 'Page 2', link: 'en/page-2' }, en: {
{ text: 'Page 3', link: 'en/page-3' }, 'Section Header': [
{ text: 'Introduction', link: 'en/introduction' },
{ text: 'Another Section', header: true }, { text: 'Page 2', link: 'en/page-2' },
{ text: 'Page 4', link: 'en/page-4' }, { text: 'Page 3', link: 'en/page-3' },
], ],
'Another Section': [{ text: 'Page 4', link: 'en/page-4' }],
},
}; };

View file

@ -1,10 +1,10 @@
import { KNOWN_LANGUAGES } from './config'; import { KNOWN_LANGUAGES, KNOWN_LANGUAGE_CODES } from './config';
export { KNOWN_LANGUAGES, KNOWN_LANGUAGE_CODES };
export { KNOWN_LANGUAGES };
export const KNOWN_LANGUAGE_CODES = Object.values(KNOWN_LANGUAGES);
export const langPathRegex = /\/([a-z]{2}-?[A-Z]{0,2})\//; export const langPathRegex = /\/([a-z]{2}-?[A-Z]{0,2})\//;
export function getLanguageFromURL(pathname: string) { export function getLanguageFromURL(pathname: string) {
const langCodeMatch = pathname.match(langPathRegex); const langCodeMatch = pathname.match(langPathRegex);
return langCodeMatch ? langCodeMatch[1] : 'en'; const langCode = langCodeMatch ? langCodeMatch[1] : 'en';
return langCode as typeof KNOWN_LANGUAGE_CODES[number];
} }

View file

@ -6,18 +6,25 @@ import PageContent from '../components/PageContent/PageContent.astro';
import LeftSidebar from '../components/LeftSidebar/LeftSidebar.astro'; import LeftSidebar from '../components/LeftSidebar/LeftSidebar.astro';
import RightSidebar from '../components/RightSidebar/RightSidebar.astro'; import RightSidebar from '../components/RightSidebar/RightSidebar.astro';
import * as CONFIG from '../config'; import * as CONFIG from '../config';
import type { MarkdownHeading } from 'astro';
import Footer from '../components/Footer/Footer.astro';
const { frontmatter = {}, headings } = Astro.props; type Props = {
frontmatter: CONFIG.Frontmatter;
headings: MarkdownHeading[];
};
const { frontmatter, headings } = Astro.props as Props;
const canonicalURL = new URL(Astro.url.pathname, Astro.site); const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const currentPage = Astro.url.pathname; const currentPage = Astro.url.pathname;
const currentFile = `src/pages${currentPage.replace(/\/$/, '')}.md`; const currentFile = `src/pages${currentPage.replace(/\/$/, '')}.md`;
const githubEditUrl = CONFIG.GITHUB_EDIT_URL && CONFIG.GITHUB_EDIT_URL + currentFile; const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
--- ---
<html dir={frontmatter.dir ?? 'ltr'} lang={frontmatter.lang ?? 'en-us'} class="initial"> <html dir={frontmatter.dir ?? 'ltr'} lang={frontmatter.lang ?? 'en-us'} class="initial">
<head> <head>
<HeadCommon /> <HeadCommon />
<HeadSEO {frontmatter} canonicalURL={canonicalURL} /> <HeadSEO frontmatter={frontmatter} canonicalUrl={canonicalURL} />
<title> <title>
{frontmatter.title ? `${frontmatter.title} 🚀 ${CONFIG.SITE.title}` : CONFIG.SITE.title} {frontmatter.title ? `${frontmatter.title} 🚀 ${CONFIG.SITE.title}` : CONFIG.SITE.title}
</title> </title>
@ -29,31 +36,36 @@ const githubEditUrl = CONFIG.GITHUB_EDIT_URL && CONFIG.GITHUB_EDIT_URL + current
--gutter: 0.5rem; --gutter: 0.5rem;
--doc-padding: 2rem; --doc-padding: 2rem;
} }
.layout { .layout {
display: grid; display: grid;
grid-auto-flow: column; grid-auto-flow: column;
grid-template-columns: grid-template-columns: minmax(var(--gutter), 1fr) minmax(0, var(--max-width)) minmax(
minmax(var(--gutter), 1fr) var(--gutter),
minmax(0, var(--max-width)) 1fr
minmax(var(--gutter), 1fr); );
overflow-x: hidden; overflow-x: hidden;
} }
.layout :global(> *) { .layout :global(> *) {
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
.grid-sidebar { .grid-sidebar {
height: 100vh; height: 100vh;
position: sticky; position: sticky;
top: 0; top: 0;
padding: 0; padding: 0;
} }
#grid-left { #grid-left {
position: fixed; position: fixed;
background-color: var(--theme-bg); background-color: var(--theme-bg);
z-index: 10; z-index: 10;
display: none; display: none;
} }
#grid-main { #grid-main {
padding: var(--doc-padding) var(--gutter); padding: var(--doc-padding) var(--gutter);
grid-column: 2; grid-column: 2;
@ -61,24 +73,27 @@ const githubEditUrl = CONFIG.GITHUB_EDIT_URL && CONFIG.GITHUB_EDIT_URL + current
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
} }
#grid-right { #grid-right {
display: none; display: none;
} }
:global(.mobile-sidebar-toggle) { :global(.mobile-sidebar-toggle) {
overflow: hidden; overflow: hidden;
} }
:global(.mobile-sidebar-toggle) #grid-left { :global(.mobile-sidebar-toggle) #grid-left {
display: block; display: block;
top: 2rem; top: 2rem;
} }
@media (min-width: 50em) { @media (min-width: 50em) {
.layout { .layout {
overflow: initial; overflow: initial;
grid-template-columns: grid-template-columns: 20rem minmax(0, var(--max-width));
20rem
minmax(0, var(--max-width));
gap: 1em; gap: 1em;
} }
#grid-left { #grid-left {
display: flex; display: flex;
padding-left: 2rem; padding-left: 2rem;
@ -89,14 +104,12 @@ const githubEditUrl = CONFIG.GITHUB_EDIT_URL && CONFIG.GITHUB_EDIT_URL + current
@media (min-width: 72em) { @media (min-width: 72em) {
.layout { .layout {
grid-template-columns: grid-template-columns: 20rem minmax(0, var(--max-width)) 18rem;
20rem
minmax(0, var(--max-width))
18rem;
padding-left: 0; padding-left: 0;
padding-right: 0; padding-right: 0;
margin: 0 auto; margin: 0 auto;
} }
#grid-right { #grid-right {
grid-column: 3; grid-column: 3;
display: flex; display: flex;
@ -106,19 +119,20 @@ const githubEditUrl = CONFIG.GITHUB_EDIT_URL && CONFIG.GITHUB_EDIT_URL + current
</head> </head>
<body> <body>
<Header {currentPage} /> <Header currentPage={currentPage} />
<main class="layout"> <main class="layout">
<aside id="grid-left" class="grid-sidebar" title="Site Navigation"> <aside id="grid-left" class="grid-sidebar" title="Site Navigation">
<LeftSidebar {currentPage} /> <LeftSidebar currentPage={currentPage} />
</aside> </aside>
<div id="grid-main"> <div id="grid-main">
<PageContent {frontmatter} {headings} {githubEditUrl}> <PageContent frontmatter={frontmatter} headings={headings} githubEditUrl={githubEditUrl}>
<slot /> <slot />
</PageContent> </PageContent>
</div> </div>
<aside id="grid-right" class="grid-sidebar" title="Table of Contents"> <aside id="grid-right" class="grid-sidebar" title="Table of Contents">
<RightSidebar {headings} {githubEditUrl} /> <RightSidebar headings={headings} githubEditUrl={githubEditUrl} />
</aside> </aside>
</main> </main>
<Footer path={currentFile} />
</body> </body>
</html> </html>

View file

@ -1,3 +1,7 @@
{ {
"extends": "astro/tsconfigs/base" "extends": "astro/tsconfigs/base",
"compilerOptions": {
"jsx": "preserve",
"skipLibCheck": true
}
} }

File diff suppressed because it is too large Load diff