commit
626f68f9fa
@ -0,0 +1,19 @@ |
||||
# build output |
||||
dist/ |
||||
|
||||
# dependencies |
||||
node_modules/ |
||||
|
||||
# logs |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
pnpm-debug.log* |
||||
|
||||
|
||||
# environment variables |
||||
.env |
||||
.env.production |
||||
|
||||
# macOS-specific files |
||||
.DS_Store |
@ -0,0 +1,4 @@ |
||||
{ |
||||
"recommendations": ["astro-build.astro-vscode"], |
||||
"unwantedRecommendations": [] |
||||
} |
@ -0,0 +1,11 @@ |
||||
{ |
||||
"version": "0.2.0", |
||||
"configurations": [ |
||||
{ |
||||
"command": "./node_modules/.bin/astro dev", |
||||
"name": "Development server", |
||||
"request": "launch", |
||||
"type": "node-terminal" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,145 @@ |
||||
# NextGraph documentation site |
||||
|
||||
Based on [Astro Starter Kit: Docs Site](https://github.com/ccutch/biplane) |
||||
|
||||
## Commands Cheatsheet |
||||
|
||||
All commands are run from the root of the project, from a terminal: |
||||
|
||||
| Command | Action | |
||||
| :--------------------- | :----------------------------------------------- | |
||||
| `npm install` | Installs dependencies | |
||||
| `npm run dev` | Starts local dev server at `localhost:3000` | |
||||
| `npm run build` | Build your production site to `./dist/` | |
||||
| `npm run preview` | Preview your build locally, before deploying | |
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` | |
||||
| `npm run astro --help` | Get help using the Astro CLI | |
||||
|
||||
## New to Astro? |
||||
|
||||
Welcome! Check out [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat). |
||||
|
||||
## Customize This Theme |
||||
|
||||
### Site metadata |
||||
|
||||
`src/config.ts` contains several data objects that describe metadata about your site like title, description, default language, and Open Graph details. You can customize these to match your project. |
||||
|
||||
### CSS styling |
||||
|
||||
The theme's look and feel is controlled by a few key variables that you can customize yourself. You'll find them in the `src/styles/theme.css` CSS file. |
||||
|
||||
If you've never worked with CSS variables before, give [MDN's guide on CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) a quick read. |
||||
|
||||
This theme uses a "cool blue" accent color by default. To customize this for your project, change the `--theme-accent` variable to whatever color you'd like: |
||||
|
||||
```diff |
||||
/* src/styles/theme.css */ |
||||
:root { |
||||
color-scheme: light; |
||||
- --theme-accent: hsla(var(--color-blue), 1); |
||||
+ --theme-accent: hsla(var(--color-red), 1); /* or: hsla(#FF0000, 1); */ |
||||
``` |
||||
|
||||
## Page metadata |
||||
|
||||
Astro uses frontmatter in Markdown pages to choose layouts and pass properties to those layouts. If you are using the default layout, you can customize the page in many different ways to optimize SEO and other things. For example, you can use the `title` and `description` properties to set the document title, meta title, meta description, and Open Graph description. |
||||
|
||||
```markdown |
||||
--- |
||||
title: Example title |
||||
description: Really cool docs example that uses Astro |
||||
layout: ../../layouts/MainLayout.astro |
||||
--- |
||||
|
||||
# Page content... |
||||
``` |
||||
|
||||
For more SEO related properties, look at `src/components/HeadSEO.astro` |
||||
|
||||
### Sidebar navigation |
||||
|
||||
The sidebar navigation is controlled by the `SIDEBAR` variable in your `src/config.ts` file. You can customize the sidebar by modifying this object. A default, starter navigation has already been created for you. |
||||
|
||||
```ts |
||||
export const SIDEBAR = { |
||||
en: [ |
||||
{ text: "Section Header", header: true }, |
||||
{ text: "Introduction", link: "en/introduction" }, |
||||
{ text: "Page 2", link: "en/page-2" }, |
||||
{ text: "Page 3", link: "en/page-3" }, |
||||
|
||||
{ text: "Another Section", header: true }, |
||||
{ text: "Page 4", link: "en/page-4" }, |
||||
], |
||||
}; |
||||
``` |
||||
|
||||
Note the top-level `en` key: This is needed for multi-language support. You can change it to whatever language you'd like, or add new languages as you go. More details on this below. |
||||
|
||||
### Multiple Languages support |
||||
|
||||
The Astro docs template supports multiple langauges out of the box. The default theme only shows `en` documentation, but you can enable multi-language support features by adding a second language to your project. |
||||
|
||||
To add a new language to your project, you'll want to extend the current `src/pages/[lang]/...` layout: |
||||
|
||||
```diff |
||||
๐ src/pages |
||||
โฃ ๐ en |
||||
โ โฃ ๐ page-1.md |
||||
โ โฃ ๐ page-2.md |
||||
โ โฃ ๐ page-3.astro |
||||
+ โฃ ๐ es |
||||
+ โ โฃ ๐ page-1.md |
||||
+ โ โฃ ๐ page-2.md |
||||
+ โ โฃ ๐ page-3.astro |
||||
``` |
||||
|
||||
You'll also need to add the new language name to the `KNOWN_LANGUAGES` map in your `src/config.ts` file. This will enable your new language switcher in the site header. |
||||
|
||||
```diff |
||||
// src/config.ts |
||||
export const KNOWN_LANGUAGES = { |
||||
English: 'en', |
||||
+ Spanish: 'es', |
||||
}; |
||||
``` |
||||
|
||||
Last step: you'll need to add a new entry to your sidebar, to create the table of contents for that language. While duplicating every page might not sound ideal to everyone, this extra control allows you to create entirely custom content for every language. |
||||
|
||||
> Make sure the sidebar `link` value points to the correct language! |
||||
|
||||
```diff |
||||
// src/config.ts |
||||
export const SIDEBAR = { |
||||
en: [ |
||||
{ text: 'Section Header', header: true, }, |
||||
{ text: 'Introduction', link: 'en/introduction' }, |
||||
// ... |
||||
], |
||||
+ es: [ |
||||
+ { text: 'Encabezado de secciรณn', header: true, }, |
||||
+ { text: 'Introducciรณn', link: 'es/introduction' }, |
||||
+ // ... |
||||
+ ], |
||||
}; |
||||
|
||||
// ... |
||||
``` |
||||
|
||||
If you plan to use Spanish as the the default language, you just need to modify the redirect path in `src/pages/index.astro`: |
||||
|
||||
```diff |
||||
<script> |
||||
- window.location.pathname = `/en/introduction`; |
||||
+ window.location.pathname = `/es/introduction`; |
||||
</script> |
||||
``` |
||||
|
||||
You can also remove the above script and write a landing page in Spanish instead. |
||||
|
||||
### What if I don't plan to support multiple languages? |
||||
|
||||
That's totally fine! Not all projects need (or can support) multiple languages. You can continue to use this theme without ever adding a second language. |
||||
|
||||
If that single language is not English, you can just replace `en` in directory layouts and configurations with the preferred language. |
@ -0,0 +1,16 @@ |
||||
import { defineConfig } from 'astro/config'; |
||||
import preact from '@astrojs/preact'; |
||||
import react from '@astrojs/react'; |
||||
|
||||
// https://astro.build/config
|
||||
import sitemap from "@astrojs/sitemap"; |
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({ |
||||
integrations: [ |
||||
// Enable Preact to support Preact JSX components.
|
||||
preact(), |
||||
// Enable React for the Algolia search component.
|
||||
react(), sitemap()], |
||||
site: `https://docs.nextgraph.org` |
||||
}); |
@ -0,0 +1,31 @@ |
||||
{ |
||||
"name": "@example/docs", |
||||
"type": "module", |
||||
"version": "0.0.1", |
||||
"private": true, |
||||
"scripts": { |
||||
"dev": "astro dev", |
||||
"start": "astro dev", |
||||
"check": "astro check && tsc", |
||||
"build": "astro build", |
||||
"preview": "astro preview", |
||||
"astro": "astro" |
||||
}, |
||||
"dependencies": { |
||||
"@algolia/client-search": "^4.9.1", |
||||
"@astrojs/preact": "^1.2.0", |
||||
"@astrojs/react": "^1.2.2", |
||||
"@astrojs/sitemap": "^1.0.0", |
||||
"@babel/runtime": ">=7.17.2 <8.0.0", |
||||
"@types/node": "^18.0.0", |
||||
"@types/react": "^17.0.45", |
||||
"@types/react-dom": "^18.0.0", |
||||
"algoliasearch": "^4.9.1", |
||||
"astro": "^1.6.7", |
||||
"astro-analytics": "^2.5.0", |
||||
"preact": "^10.7.3", |
||||
"react": "^18.1.0", |
||||
"react-dom": "^18.1.0", |
||||
"typesense-docsearch-react": "^0.2.3" |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 136 KiB |
After Width: | Height: | Size: 13 KiB |
@ -0,0 +1,3 @@ |
||||
Array.from(document.getElementsByTagName('pre')).forEach((element) => { |
||||
element.setAttribute('tabindex', '0'); |
||||
}); |
File diff suppressed because one or more lines are too long
@ -0,0 +1,171 @@ |
||||
--- |
||||
// fetch all commits for just this page's path |
||||
type Props = { |
||||
path: string; |
||||
}; |
||||
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}`; |
||||
|
||||
type Commit = { |
||||
author: { |
||||
id: string; |
||||
login: string; |
||||
}; |
||||
}; |
||||
|
||||
async function getCommits(url: string) { |
||||
try { |
||||
const token = import.meta.env.SNOWPACK_PUBLIC_GITHUB_TOKEN ?? 'hello'; |
||||
if (!token) { |
||||
throw new Error( |
||||
'Cannot find "SNOWPACK_PUBLIC_GITHUB_TOKEN" used for escaping rate-limiting.' |
||||
); |
||||
} |
||||
|
||||
const auth = `Basic ${Buffer.from(token, 'binary').toString('base64')}`; |
||||
|
||||
const res = await fetch(url, { |
||||
method: 'GET', |
||||
headers: { |
||||
Authorization: auth, |
||||
'User-Agent': 'astro-docs/1.0', |
||||
}, |
||||
}); |
||||
|
||||
const data = await res.json(); |
||||
|
||||
if (!res.ok) { |
||||
throw new Error( |
||||
`Request to fetch commits failed. Reason: ${res.statusText} |
||||
Message: ${data.message}` |
||||
); |
||||
} |
||||
|
||||
return data as Commit[]; |
||||
} catch (e) { |
||||
console.warn(`[error] /src/components/AvatarList.astro |
||||
${(e as any)?.message ?? e}`); |
||||
return [] as Commit[]; |
||||
} |
||||
} |
||||
|
||||
function removeDups(arr: Commit[]) { |
||||
const map = new Map<string, Commit['author']>(); |
||||
|
||||
for (let item of arr) { |
||||
const author = item.author; |
||||
// Deduplicate based on author.id |
||||
map.set(author.id, { login: author.login, id: author.id }); |
||||
} |
||||
|
||||
return [...map.values()]; |
||||
} |
||||
|
||||
const data = await getCommits(url); |
||||
const unique = removeDups(data); |
||||
const recentContributors = unique.slice(0, 3); // only show avatars for the 3 most recent contributors |
||||
const additionalContributors = unique.length - recentContributors.length; // list the rest of them as # of extra contributors |
||||
--- |
||||
|
||||
<!-- Thanks to @5t3ph for https://smolcss.dev/#smol-avatar-list! --> |
||||
<div class="contributors"> |
||||
<ul class="avatar-list" style={`--avatar-count: ${recentContributors.length}`}> |
||||
{ |
||||
recentContributors.map((item) => ( |
||||
<li> |
||||
<a href={`https://github.com/${item.login}`}> |
||||
<img |
||||
alt={`Contributor ${item.login}`} |
||||
title={`Contributor ${item.login}`} |
||||
width="64" |
||||
height="64" |
||||
src={`https://avatars.githubusercontent.com/u/${item.id}`} |
||||
/> |
||||
</a> |
||||
</li> |
||||
)) |
||||
} |
||||
</ul> |
||||
{ |
||||
additionalContributors > 0 && ( |
||||
<span> |
||||
<a href={commitsURL}>{`and ${additionalContributors} additional contributor${ |
||||
additionalContributors > 1 ? 's' : '' |
||||
}.`}</a> |
||||
</span> |
||||
) |
||||
} |
||||
{unique.length === 0 && <a href={commitsURL}>Contributors</a>} |
||||
</div> |
||||
|
||||
<style> |
||||
.avatar-list { |
||||
--avatar-size: 2.5rem; |
||||
--avatar-count: 3; |
||||
|
||||
display: grid; |
||||
list-style: none; |
||||
/* Default to displaying most of the avatar to |
||||
enable easier access on touch devices, ensuring |
||||
the WCAG touch target size is met or exceeded */ |
||||
grid-template-columns: repeat(var(--avatar-count), max(44px, calc(var(--avatar-size) / 1.15))); |
||||
/* `padding` matches added visual dimensions of |
||||
the `box-shadow` to help create a more accurate |
||||
computed component size */ |
||||
padding: 0.08em; |
||||
font-size: var(--avatar-size); |
||||
} |
||||
|
||||
@media (any-hover: hover) and (any-pointer: fine) { |
||||
.avatar-list { |
||||
/* We create 1 extra cell to enable the computed |
||||
width to match the final visual width */ |
||||
grid-template-columns: repeat(calc(var(--avatar-count) + 1), calc(var(--avatar-size) / 1.75)); |
||||
} |
||||
} |
||||
|
||||
.avatar-list li { |
||||
width: var(--avatar-size); |
||||
height: var(--avatar-size); |
||||
} |
||||
|
||||
.avatar-list li:hover ~ li a, |
||||
.avatar-list li:focus-within ~ li a { |
||||
transform: translateX(33%); |
||||
} |
||||
|
||||
.avatar-list img, |
||||
.avatar-list a { |
||||
display: block; |
||||
border-radius: 50%; |
||||
} |
||||
|
||||
.avatar-list a { |
||||
transition: transform 180ms ease-in-out; |
||||
} |
||||
|
||||
.avatar-list img { |
||||
width: 100%; |
||||
height: 100%; |
||||
object-fit: cover; |
||||
background-color: #fff; |
||||
box-shadow: 0 0 0 0.05em #fff, 0 0 0 0.08em rgba(0, 0, 0, 0.15); |
||||
} |
||||
|
||||
.avatar-list a:focus { |
||||
outline: 2px solid transparent; |
||||
/* Double-layer trick to work for dark and light backgrounds */ |
||||
box-shadow: 0 0 0 0.08em var(--theme-accent), 0 0 0 0.12em white; |
||||
} |
||||
|
||||
.contributors { |
||||
display: flex; |
||||
align-items: center; |
||||
} |
||||
|
||||
.contributors > * + * { |
||||
margin-left: 0.75rem; |
||||
} |
||||
</style> |
@ -0,0 +1,19 @@ |
||||
--- |
||||
import AvatarList from "./AvatarList.astro"; |
||||
type Props = { |
||||
path: string; |
||||
}; |
||||
const { path } = Astro.props as Props; |
||||
--- |
||||
|
||||
<footer> |
||||
<!-- <AvatarList path={path} /> --> |
||||
</footer> |
||||
|
||||
<style> |
||||
footer { |
||||
margin-top: auto; |
||||
padding: 2rem; |
||||
border-top: 3px solid var(--theme-divider); |
||||
} |
||||
</style> |
@ -0,0 +1,47 @@ |
||||
--- |
||||
import "../styles/theme.css"; |
||||
import "../styles/index.css"; |
||||
--- |
||||
|
||||
<!-- Global Metadata --> |
||||
<meta charset="utf-8" /> |
||||
<meta name="viewport" content="width=device-width" /> |
||||
<meta name="generator" content={Astro.generator} /> |
||||
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> |
||||
|
||||
<link rel="sitemap" href="/sitemap.xml" /> |
||||
|
||||
<!-- Preload Fonts --> |
||||
<!-- <link rel="preconnect" href="https://fonts.googleapis.com" /> |
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> |
||||
<link |
||||
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital@0;1&display=swap" |
||||
rel="stylesheet" |
||||
/> --> |
||||
|
||||
<!-- Scrollable a11y code helper --> |
||||
<script src="/make-scrollable-code-focusable.js" is:inline></script> |
||||
|
||||
<!-- This is intentionally inlined to avoid FOUC --> |
||||
<script is:inline> |
||||
const root = document.documentElement; |
||||
const theme = localStorage.getItem("theme"); |
||||
if ( |
||||
theme === "dark" || |
||||
(!theme && window.matchMedia("(prefers-color-scheme: dark)").matches) |
||||
) { |
||||
root.classList.add("theme-dark"); |
||||
} else { |
||||
root.classList.remove("theme-dark"); |
||||
} |
||||
</script> |
||||
|
||||
<!-- Global site tag (gtag.js) - Google Analytics --> |
||||
<!-- <script async src="https://www.googletagmanager.com/gtag/js?id=G-TEL60V1WM9" is:inline></script> |
||||
<script> |
||||
window.dataLayer = window.dataLayer || []; |
||||
function gtag(){dataLayer.push(arguments);} |
||||
gtag('js', new Date()); |
||||
gtag('config', 'G-TEL60V1WM9'); |
||||
</script> --> |
@ -0,0 +1,56 @@ |
||||
--- |
||||
import { SITE, OPEN_GRAPH, Frontmatter } from "../config"; |
||||
|
||||
export interface Props { |
||||
frontmatter: Frontmatter; |
||||
canonicalUrl: URL; |
||||
} |
||||
|
||||
const { frontmatter, canonicalUrl } = Astro.props as Props; |
||||
const formattedContentTitle = `${frontmatter.title} ๐ ${SITE.title}`; |
||||
const imageSrc = frontmatter.image?.src ?? OPEN_GRAPH.image.src; |
||||
const canonicalImageSrc = new URL(imageSrc, Astro.site); |
||||
const imageAlt = frontmatter.image?.alt ?? OPEN_GRAPH.image.alt; |
||||
--- |
||||
|
||||
<!-- Page Metadata --> |
||||
<link rel="canonical" href={canonicalUrl} /> |
||||
|
||||
<!-- OpenGraph Tags --> |
||||
<meta property="og:title" content={formattedContentTitle} /> |
||||
<meta property="og:type" content="article" /> |
||||
<meta property="og:url" content={canonicalUrl} /> |
||||
<meta |
||||
property="og:locale" |
||||
content={frontmatter.ogLocale ?? SITE.defaultLanguage} |
||||
/> |
||||
<meta |
||||
name="docsearch:language_tag" |
||||
content={frontmatter.ogLocale ?? SITE.defaultLanguage} |
||||
/> |
||||
<meta property="og:image" content={canonicalImageSrc} /> |
||||
<meta property="og:image:alt" content={imageAlt} /> |
||||
<meta |
||||
name="description" |
||||
property="og:description" |
||||
content={frontmatter.description ?? SITE.description} |
||||
/> |
||||
<meta property="og:site_name" content={SITE.title} /> |
||||
|
||||
<!-- Twitter Tags --> |
||||
<meta name="twitter:card" content="summary_large_image" /> |
||||
<meta name="twitter:site" content={OPEN_GRAPH.twitter} /> |
||||
<meta name="twitter:title" content={formattedContentTitle} /> |
||||
<meta |
||||
name="twitter:description" |
||||
content={frontmatter.description ?? SITE.description} |
||||
/> |
||||
<meta name="twitter:image" content={canonicalImageSrc} /> |
||||
<meta name="twitter:image:alt" content={imageAlt} /> |
||||
|
||||
<!-- |
||||
TODO: Add json+ld data, maybe https://schema.org/APIReference makes sense? |
||||
Docs: https://developers.google.com/search/docs/advanced/structured-data/intro-structured-data |
||||
https://www.npmjs.com/package/schema-dts seems like a great resource for implementing this. |
||||
Even better, there's a React component that integrates with `schema-dts`: https://github.com/google/react-schemaorg |
||||
--> |
@ -0,0 +1,62 @@ |
||||
--- |
||||
type Props = { |
||||
size: number; |
||||
}; |
||||
const { size } = Astro.props as Props; |
||||
--- |
||||
|
||||
<svg |
||||
xmlns:dc="http://purl.org/dc/elements/1.1/" |
||||
xmlns:cc="http://creativecommons.org/ns#" |
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" |
||||
xmlns:svg="http://www.w3.org/2000/svg" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" |
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" |
||||
sodipodi:docname="nextgraph.svg" |
||||
inkscape:version="1.0rc1 (09960d6, 2020-04-09)" |
||||
id="svg1480" |
||||
version="1.1" |
||||
viewBox="0 0 210 200" |
||||
class="logo" |
||||
width={size} |
||||
height={size} |
||||
> |
||||
<defs id="defs1474"></defs> |
||||
<sodipodi:namedview |
||||
inkscape:window-maximized="0" |
||||
inkscape:window-y="51" |
||||
inkscape:window-x="1120" |
||||
inkscape:window-height="1174" |
||||
inkscape:window-width="1252" |
||||
showgrid="false" |
||||
inkscape:document-rotation="0" |
||||
inkscape:current-layer="layer1" |
||||
inkscape:document-units="mm" |
||||
inkscape:cy="560" |
||||
inkscape:cx="400" |
||||
inkscape:zoom="0.35" |
||||
inkscape:pageshadow="2" |
||||
inkscape:pageopacity="0.0" |
||||
borderopacity="1.0" |
||||
bordercolor="#666666" |
||||
pagecolor="#ffffff" |
||||
id="base"></sodipodi:namedview> |
||||
<metadata id="metadata1477"> |
||||
<rdf:RDF> |
||||
<cc:Work rdf:about=""> |
||||
<dc:format>image/svg+xml</dc:format> |
||||
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" |
||||
></dc:type> |
||||
<dc:title></dc:title> |
||||
</cc:Work> |
||||
</rdf:RDF> |
||||
</metadata> |
||||
<g id="layer1" inkscape:groupmode="layer" inkscape:label="Layer 1"> |
||||
<path |
||||
style="fill:#4972a5;fill-opacity:1;stroke:#4972a5;stroke-width:0.377976;stroke-opacity:1" |
||||
d="M 79.176627,175.63013 C 61.237057,172.90259 45.845217,164.94744 33.441507,151.99232 19.435372,137.36353 12.011338,118.78286 12.011338,98.35746 c 0,-10.218891 1.700058,-19.443961 5.221234,-28.332121 4.28678,-10.820699 10.037295,-19.39063 18.535095,-27.62263 4.72982,-4.58187 6.60687,-6.10643 11.28099,-9.16256 11.89869,-7.779841 24.17388,-11.879991 38.0958,-12.724761 19.804373,-1.2017 39.111653,5.11306 54.602843,17.858751 1.50718,1.24006 2.72951,2.35934 2.71628,2.48729 -0.0132,0.12795 -3.85821,3.63443 -8.54442,7.79217 -4.6862,4.157729 -10.04724,8.96276 -11.91342,10.677819 -1.86617,1.715071 -3.54094,3.11831 -3.7217,3.11831 -0.18075,0 -1.39985,-0.745188 -2.70911,-1.655969 -7.53011,-5.23834 -15.994283,-7.82188 -25.625973,-7.82188 -12.73163,0 -23.24919,4.3379 -32.14388,13.257541 -6.39594,6.413869 -10.70387,14.555269 -12.50018,23.623579 -0.69099,3.48832 -0.68968,13.530721 0.002,17.008931 3.70508,18.62577 18.31886,33.10194 36.64232,36.29729 4.16439,0.72621 11.98099,0.71223 15.98975,-0.0286 14.031873,-2.59311 25.860473,-11.36806 32.265333,-23.93578 0.77379,-1.51834 1.26018,-2.88461 1.08086,-3.03616 -0.17934,-0.15156 -6.87448,-1.1779 -14.87813,-2.28078 -9.7795,-1.34758 -14.92353,-2.21379 -15.68471,-2.64117 -1.520673,-0.85379 -2.836113,-2.88806 -2.836113,-4.3859 0,-1.1732 2.02687,-15.868761 2.490853,-18.059621 0.29676,-1.40127 2.42559,-3.4934 3.84317,-3.77691 0.62227,-0.12445 8.82712,0.85555 18.28065,2.18348 9.43343,1.32511 17.26269,2.29453 17.39833,2.15427 0.13566,-0.14026 1.11808,-6.54833 2.18313,-14.24014 1.10778,-8.000209 2.20407,-14.601841 2.56177,-15.42623 0.34392,-0.792599 1.11019,-1.849131 1.70287,-2.34782 2.06321,-1.736079 3.1433,-1.785011 12.20439,-0.55291 9.63637,1.310309 10.70873,1.56224 12.28077,2.88503 1.64359,1.382979 2.2732,2.810909 2.25906,5.123309 -0.007,1.10173 -0.92172,8.296451 -2.03332,15.988261 -1.11158,7.69182 -1.97159,14.040914 -1.91113,14.109104 0.0605,0.0682 7.16644,1.11143 15.79109,2.318317 11.10566,1.55407 16.00827,2.38757 16.80223,2.85657 1.53015,0.90389 2.48023,2.64785 2.45017,4.49756 -0.0462,2.84349 -2.41252,18.12279 -2.97521,19.21089 -0.66164,1.27949 -2.60244,2.54696 -3.92109,2.56074 -0.51973,0.005 -7.87449,-0.95937 -16.34391,-2.144 -8.46944,-1.18464 -15.47588,-2.077 -15.56986,-1.98301 -0.094,0.094 -1.18792,7.34163 -2.43097,16.10589 -1.44004,10.15311 -2.49792,16.43621 -2.91556,17.31631 -0.72531,1.52848 -2.76261,3.06291 -4.53817,3.41802 -0.95688,0.19138 -10.90014,-0.92798 -13.59859,-1.53084 -0.5471,-0.12223 -1.89146,0.67252 -4.50941,2.66588 -11.2627,8.57562 -24.34195,13.90917 -38.357413,15.64164 -4.40038,0.54395 -15.72658,0.43298 -19.85366,-0.19451 z" |
||||
id="path1472" |
||||
inkscape:connector-curvature="0"></path> |
||||
</g> |
||||
</svg> |
@ -0,0 +1,153 @@ |
||||
--- |
||||
import { getLanguageFromURL, KNOWN_LANGUAGE_CODES } from "../../languages"; |
||||
import * as CONFIG from "../../config"; |
||||
import AstroLogo from "./AstroLogo.astro"; |
||||
import SkipToContent from "./SkipToContent.astro"; |
||||
import SidebarToggle from "./SidebarToggle"; |
||||
import LanguageSelect from "./LanguageSelect"; |
||||
import Search from "./Search"; |
||||
|
||||
type Props = { |
||||
currentPage: string; |
||||
}; |
||||
|
||||
const { currentPage } = Astro.props as Props; |
||||
const lang = getLanguageFromURL(currentPage); |
||||
--- |
||||
|
||||
<header> |
||||
<SkipToContent /> |
||||
<nav class="nav-wrapper" title="Top Navigation"> |
||||
<div class="menu-toggle"> |
||||
<SidebarToggle client:idle /> |
||||
</div> |
||||
<div class="logo flex"> |
||||
<a href="/"> |
||||
<AstroLogo size={40} /> |
||||
<h1>{CONFIG.SITE.title ?? "Documentation"}</h1> |
||||
</a> |
||||
</div> |
||||
<div style="flex-grow: 1;"></div> |
||||
{ |
||||
KNOWN_LANGUAGE_CODES.length > 1 && ( |
||||
<LanguageSelect lang={lang} client:idle /> |
||||
) |
||||
} |
||||
<div class="search-item"> |
||||
<Search client:idle /> |
||||
</div> |
||||
</nav> |
||||
</header> |
||||
|
||||
<style> |
||||
header { |
||||
z-index: 11; |
||||
height: var(--theme-navbar-height); |
||||
width: 100%; |
||||
background-color: var(--theme-navbar-bg); |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: center; |
||||
overflow: hidden; |
||||
position: sticky; |
||||
top: 0; |
||||
} |
||||
|
||||
.logo { |
||||
flex: 1; |
||||
display: flex; |
||||
overflow: hidden; |
||||
width: 30px; |
||||
font-size: 2rem; |
||||
flex-shrink: 0; |
||||
font-weight: 600; |
||||
line-height: 1; |
||||
color: hsla(var(--color-base-white), 100%, 1); |
||||
gap: 0.25em; |
||||
z-index: -1; |
||||
} |
||||
|
||||
.logo a { |
||||
display: flex; |
||||
padding: 0.5em 0.25em; |
||||
margin: -0.5em -0.25em; |
||||
text-decoration: none; |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.logo a { |
||||
transition: color 100ms ease-out; |
||||
color: var(--theme-text); |
||||
} |
||||
|
||||
.logo a:hover, |
||||
.logo a:focus { |
||||
color: var(--theme-text-accent); |
||||
} |
||||
|
||||
.logo h1 { |
||||
display: none; |
||||
font: inherit; |
||||
color: inherit; |
||||
margin: 0; |
||||
} |
||||
|
||||
.nav-wrapper { |
||||
display: flex; |
||||
align-items: center; |
||||
justify-content: flex-end; |
||||
gap: 1em; |
||||
width: 100%; |
||||
max-width: 82em; |
||||
padding: 0 1rem; |
||||
} |
||||
|
||||
@media (min-width: 50em) { |
||||
header { |
||||
position: static; |
||||
padding: 2rem 0rem; |
||||
} |
||||
|
||||
.logo { |
||||
width: auto; |
||||
margin: 0; |
||||
z-index: 0; |
||||
} |
||||
|
||||
.logo h1 { |
||||
display: initial; |
||||
} |
||||
|
||||
.menu-toggle { |
||||
display: none; |
||||
} |
||||
} |
||||
|
||||
/** Style Algolia */ |
||||
:root { |
||||
--docsearch-primary-color: var(--theme-accent); |
||||
--docsearch-logo-color: var(--theme-text); |
||||
} |
||||
|
||||
.search-item { |
||||
display: none; |
||||
position: relative; |
||||
z-index: 10; |
||||
flex-grow: 1; |
||||
padding-right: 0.7rem; |
||||
display: flex; |
||||
max-width: 200px; |
||||
} |
||||
|
||||
@media (min-width: 50em) { |
||||
.search-item { |
||||
max-width: 400px; |
||||
} |
||||
} |
||||
</style> |
||||
|
||||
<style is:global> |
||||
.search-item > * { |
||||
flex-grow: 1; |
||||
} |
||||
</style> |
@ -0,0 +1,47 @@ |
||||
.language-select { |
||||
flex-grow: 1; |
||||
width: 48px; |
||||
box-sizing: border-box; |
||||
margin: 0; |
||||
padding: 0.33em 0.5em; |
||||
overflow: visible; |
||||
font-weight: 500; |
||||
font-size: 1rem; |
||||
font-family: inherit; |
||||
line-height: inherit; |
||||
background-color: var(--theme-bg); |
||||
border-color: var(--theme-text-lighter); |
||||
color: var(--theme-text-light); |
||||
border-style: solid; |
||||
border-width: 1px; |
||||
border-radius: 0.25rem; |
||||
outline: 0; |
||||
cursor: pointer; |
||||
transition-timing-function: ease-out; |
||||
transition-duration: 0.2s; |
||||
transition-property: border-color, color; |
||||
-webkit-font-smoothing: antialiased; |
||||
padding-left: 30px; |
||||
padding-right: 1rem; |
||||
} |
||||
.language-select-wrapper .language-select:hover, |
||||
.language-select-wrapper .language-select:focus { |
||||
color: var(--theme-text); |
||||
border-color: var(--theme-text-light); |
||||
} |
||||
.language-select-wrapper { |
||||
color: var(--theme-text-light); |
||||
position: relative; |
||||
} |
||||
.language-select-wrapper > svg { |
||||
position: absolute; |
||||
top: 7px; |
||||
left: 10px; |
||||
pointer-events: none; |
||||
} |
||||
|
||||
@media (min-width: 50em) { |
||||
.language-select { |
||||
width: 100%; |
||||
} |
||||
} |
@ -0,0 +1,49 @@ |
||||
/** @jsxImportSource react */ |
||||
import type { FunctionComponent } from 'react'; |
||||
import './LanguageSelect.css'; |
||||
import { KNOWN_LANGUAGES, langPathRegex } from '../../languages'; |
||||
|
||||
const LanguageSelect: FunctionComponent<{ lang: string }> = ({ lang }) => { |
||||
return ( |
||||
<div className="language-select-wrapper"> |
||||
<svg |
||||
aria-hidden="true" |
||||
focusable="false" |
||||
role="img" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
viewBox="0 0 88.6 77.3" |
||||
height="1.2em" |
||||
width="1.2em" |
||||
> |
||||
<path |
||||
fill="currentColor" |
||||
d="M61,24.6h7.9l18.7,51.6h-7.7l-5.4-15.5H54.3l-5.6,15.5h-7.2L61,24.6z M72.6,55l-8-22.8L56.3,55H72.6z" |
||||
/> |
||||
<path |
||||
fill="currentColor" |
||||
d="M53.6,60.6c-10-4-16-9-22-14c0,0,1.3,1.3,0,0c-6,5-20,13-20,13l-4-6c8-5,10-6,19-13c-2.1-1.9-12-13-13-19h8 c4,9,10,14,10,14c10-8,10-19,10-19h8c0,0-1,13-12,24l0,0c5,5,10,9,19,13L53.6,60.6z M1.6,16.6h56v-8h-23v-7h-9v7h-24V16.6z" |
||||
/> |
||||
</svg> |
||||
<select |
||||
className="language-select" |
||||
value={lang} |
||||
onChange={(e) => { |
||||
const newLang = e.target.value; |
||||
let actualDest = window.location.pathname.replace(langPathRegex, '/'); |
||||
if (actualDest == '/') actualDest = `/introduction`; |
||||
window.location.pathname = '/' + newLang + actualDest; |
||||
}} |
||||
> |
||||
{Object.entries(KNOWN_LANGUAGES).map(([key, value]) => { |
||||
return ( |
||||
<option value={value} key={value}> |
||||
{key} |
||||
</option> |
||||
); |
||||
})} |
||||
</select> |
||||
</div> |
||||
); |
||||
}; |
||||
|
||||
export default LanguageSelect; |
@ -0,0 +1,76 @@ |
||||
/** Style Algolia */ |
||||
:root { |
||||
--docsearch-primary-color: var(--theme-accent); |
||||
--docsearch-logo-color: var(--theme-text); |
||||
} |
||||
.search-input { |
||||
flex-grow: 1; |
||||
box-sizing: border-box; |
||||
width: 100%; |
||||
margin: 0; |
||||
padding: 0.33em 0.5em; |
||||
overflow: visible; |
||||
font-weight: 500; |
||||
font-size: 1rem; |
||||
font-family: inherit; |
||||
line-height: inherit; |
||||
background-color: var(--theme-divider); |
||||
border-color: var(--theme-divider); |
||||
color: var(--theme-text-light); |
||||
border-style: solid; |
||||
border-width: 1px; |
||||
border-radius: 0.25rem; |
||||
outline: 0; |
||||
cursor: pointer; |
||||
transition-timing-function: ease-out; |
||||
transition-duration: 0.2s; |
||||
transition-property: border-color, color; |
||||
-webkit-font-smoothing: antialiased; |
||||
} |
||||
.search-input:hover, |
||||
.search-input:focus { |
||||
color: var(--theme-text); |
||||
border-color: var(--theme-text-light); |
||||
} |
||||
.search-input:hover::placeholder, |
||||
.search-input:focus::placeholder { |
||||
color: var(--theme-text-light); |
||||
} |
||||
.search-input::placeholder { |
||||
color: var(--theme-text-light); |
||||
} |
||||
.search-hint { |
||||
position: absolute; |
||||
top: 7px; |
||||
right: 19px; |
||||
padding: 3px 5px; |
||||
display: none; |
||||
display: none; |
||||
align-items: center; |
||||
justify-content: center; |
||||
letter-spacing: 0.125em; |
||||
font-size: 13px; |
||||
font-family: var(--font-mono); |
||||
pointer-events: none; |
||||
border-color: var(--theme-text-lighter); |
||||
color: var(--theme-text-light); |
||||
border-style: solid; |
||||
border-width: 1px; |
||||
border-radius: 0.25rem; |
||||
line-height: 14px; |
||||
} |
||||
|
||||
@media (min-width: 50em) { |
||||
.search-hint { |
||||
display: flex; |
||||
} |
||||
} |
||||
|
||||
/* ------------------------------------------------------------ *\ |
||||
DocSearch (Algolia) |
||||
\* ------------------------------------------------------------ */ |
||||
|
||||
.DocSearch-Modal .DocSearch-Hit a { |
||||
box-shadow: none; |
||||
border: 1px solid var(--theme-accent); |
||||
} |
@ -0,0 +1,110 @@ |
||||
/** @jsxImportSource react */ |
||||
import { useState, useCallback, useRef } from 'react'; |
||||
import { ALGOLIA } from '../../config'; |
||||
import 'typesense-docsearch-react'; |
||||
import './Search.css'; |
||||
|
||||
import { createPortal } from 'react-dom'; |
||||
import * as docSearchReact from 'typesense-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() { |
||||
const [isOpen, setIsOpen] = useState(false); |
||||
const searchButtonRef = useRef<HTMLButtonElement>(null); |
||||
const [initialQuery, setInitialQuery] = useState(''); |
||||
|
||||
const onOpen = useCallback(() => { |
||||
setIsOpen(true); |
||||
}, [setIsOpen]); |
||||
|
||||
const onClose = useCallback(() => { |
||||
setIsOpen(false); |
||||
}, [setIsOpen]); |
||||
|
||||
const onInput = useCallback( |
||||
(e) => { |
||||
setIsOpen(true); |
||||
setInitialQuery(e.key); |
||||
}, |
||||
[setIsOpen, setInitialQuery] |
||||
); |
||||
|
||||
useDocSearchKeyboardEvents({ |
||||
isOpen, |
||||
onOpen, |
||||
onClose, |
||||
onInput, |
||||
searchButtonRef, |
||||
}); |
||||
|
||||
const serverConfig = { |
||||
apiKey: ALGOLIA.apiKey, |
||||
nodes: [ |
||||
{ |
||||
host: 'search.nextgraph.org', |
||||
port: 443, |
||||
protocol: 'https', |
||||
}]}; |
||||
|
||||
const searchParams = {}; |
||||
|
||||
return ( |
||||
<> |
||||
<button type="button" ref={searchButtonRef} onClick={onOpen} className="search-input"> |
||||
<svg width="24" height="24" fill="none"> |
||||
<path |
||||
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" |
||||
stroke="currentColor" |
||||
strokeWidth="2" |
||||
strokeLinecap="round" |
||||
strokeLinejoin="round" |
||||
/> |
||||
</svg> |
||||
|
||||
<span>Search</span> |
||||
|
||||
<span className="search-hint"> |
||||
<span className="sr-only">Press </span> |
||||
|
||||
<kbd>/</kbd> |
||||
|
||||
<span className="sr-only"> to search</span> |
||||
</span> |
||||
</button> |
||||
|
||||
{isOpen && |
||||
createPortal( |
||||
<DocSearchModal |
||||
initialQuery={initialQuery} |
||||
initialScrollY={window.scrollY} |
||||
onClose={onClose} |
||||
typesenseCollectionName={ALGOLIA.indexName} |
||||
typesenseServerConfig={serverConfig} |
||||
//typesenseSearchParameters={searchParams}
|
||||
// appId={ALGOLIA.appId}
|
||||
// apiKey={ALGOLIA.apiKey}
|
||||
transformItems={(items) => { |
||||
return items.map((item) => { |
||||
// We transform the absolute URL into a relative URL to
|
||||
// work better on localhost, preview URLS.
|
||||
const a = document.createElement('a'); |
||||
a.href = item.url; |
||||
const hash = a.hash === '#overview' ? '' : a.hash; |
||||
return { |
||||
...item, |
||||
url: `${a.pathname}${hash}`, |
||||
}; |
||||
}); |
||||
}} |
||||
/>, |
||||
document.body |
||||
)} |
||||
</> |
||||
); |
||||
} |
@ -0,0 +1,44 @@ |
||||
/** @jsxImportSource preact */ |
||||
import type { FunctionalComponent } from 'preact'; |
||||
import { useState, useEffect } from 'preact/hooks'; |
||||
|
||||
const MenuToggle: FunctionalComponent = () => { |
||||
const [sidebarShown, setSidebarShown] = useState(false); |
||||
|
||||
useEffect(() => { |
||||
const body = document.querySelector('body')!; |
||||
if (sidebarShown) { |
||||
body.classList.add('mobile-sidebar-toggle'); |
||||
} else { |
||||
body.classList.remove('mobile-sidebar-toggle'); |
||||
} |
||||
}, [sidebarShown]); |
||||
|
||||
return ( |
||||
<button |
||||
type="button" |
||||
aria-pressed={sidebarShown ? 'true' : 'false'} |
||||
id="menu-toggle" |
||||
onClick={() => setSidebarShown(!sidebarShown)} |
||||
> |
||||
<svg |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
width="1em" |
||||
height="1em" |
||||
fill="none" |
||||
viewBox="0 0 24 24" |
||||
stroke="currentColor" |
||||
> |
||||
<path |
||||
stroke-linecap="round" |
||||
stroke-linejoin="round" |
||||
stroke-width="2" |
||||
d="M4 6h16M4 12h16M4 18h16" |
||||
/> |
||||
</svg> |
||||
<span className="sr-only">Toggle sidebar</span> |
||||
</button> |
||||
); |
||||
}; |
||||
|
||||
export default MenuToggle; |
@ -0,0 +1,26 @@ |
||||
--- |
||||
type Props = {}; |
||||
--- |
||||
|
||||
<a href="#article" class="sr-only focus:not-sr-only skiplink"><span>Skip to Content</span></a> |
||||
|
||||
<style> |
||||
.skiplink, |
||||
.skiplink:focus, |
||||
.skiplink:focus-visible { |
||||
position: absolute; |
||||
padding: 0.25em; |
||||
font-size: larger; |
||||
top: 0; |
||||
left: 0; |
||||
right: 0; |
||||
z-index: 9; |
||||
display: block; |
||||
text-align: center; |
||||
background-color: var(--theme-text-accent); |
||||
color: var(--theme-bg); |
||||
border-radius: 0.25em; |
||||
outline: var(--theme-bg) solid 1px; |
||||
outline-offset: 0; |
||||
} |
||||
</style> |
@ -0,0 +1,130 @@ |
||||
--- |
||||
import { getLanguageFromURL } from "../../languages"; |
||||
import { SIDEBAR } from "../../config"; |
||||
|
||||
type Props = { |
||||
currentPage: string; |
||||
}; |
||||
|
||||
const { currentPage } = Astro.props as Props; |
||||
const currentPageMatch = currentPage.endsWith("/") |
||||
? currentPage.slice(1, -1) |
||||
: currentPage.slice(1); |
||||
const langCode = getLanguageFromURL(currentPage); |
||||
const sidebar = SIDEBAR[langCode]; |
||||
--- |
||||
|
||||
<nav aria-labelledby="grid-left"> |
||||
<ul class="nav-groups"> |
||||
{ |
||||
Object.entries(sidebar).map(([header, children]) => ( |
||||
<li> |
||||
<div class="nav-group"> |
||||
<h4>{header}</h4> |
||||
<ul> |
||||
{children.map((child) => { |
||||
const url = Astro.site?.pathname + child.link; |
||||
return ( |
||||
<li class="nav-link"> |
||||
<a |
||||
href={url} |
||||
aria-current={ |
||||
currentPageMatch === child.link ? "page" : false |
||||
} |
||||
> |
||||
{child.text} |
||||
</a> |
||||
</li> |
||||
); |
||||
})} |
||||
</ul> |
||||
</div> |
||||
</li> |
||||
)) |
||||
} |
||||
</ul> |
||||
</nav> |
||||
|
||||
<script is:inline> |
||||
window.addEventListener("DOMContentLoaded", () => { |
||||
var target = document.querySelector('[aria-current="page"]'); |
||||
if (target && target.offsetTop > window.innerHeight - 100) { |
||||
document.querySelector(".nav-groups").scrollTop = target.offsetTop; |
||||
} |
||||
}); |
||||
</script> |
||||
|
||||
<style> |
||||
nav { |
||||
width: 100%; |
||||
margin-right: 1rem; |
||||
} |
||||
|
||||
.nav-groups { |
||||
height: 100%; |
||||
padding: 2rem 0; |
||||
overflow-x: visible; |
||||
overflow-y: auto; |
||||
max-height: 100vh; |
||||
} |
||||
|
||||
.nav-groups > li + li { |
||||
margin-top: 2rem; |
||||
} |
||||
|
||||
.nav-groups > :first-child { |
||||
padding-top: var(--doc-padding); |
||||
} |
||||
|
||||
.nav-groups > :last-child { |
||||
padding-bottom: 2rem; |
||||
margin-bottom: var(--theme-navbar-height); |
||||
} |
||||
|
||||
.nav-group-title { |
||||
font-size: 1rem; |
||||
font-weight: 700; |
||||
padding: 0.1rem 1rem; |
||||
text-transform: uppercase; |
||||
margin-bottom: 0.5rem; |
||||
} |
||||
|
||||
.nav-link a { |
||||
font-size: 1rem; |
||||
margin: 1px; |
||||
padding: 0.3rem 1rem; |
||||
font: inherit; |
||||
color: inherit; |
||||
text-decoration: none; |
||||
display: block; |
||||
} |
||||
|
||||
.nav-link a:hover, |
||||
.nav-link a:focus { |
||||
background-color: var(--theme-bg-hover); |
||||
} |
||||
|
||||
.nav-link a[aria-current="page"] { |
||||
color: var(--theme-text-accent); |
||||
background-color: var(--theme-bg-accent); |
||||
font-weight: 600; |
||||
} |
||||
|
||||
@media (min-width: 50em) { |
||||
.nav-groups { |
||||
padding: 0; |
||||
} |
||||
} |
||||
@media (max-width: 50em) { |
||||
.nav-groups { |
||||
margin-left: 1em; |
||||
margin-right: 1em; |
||||
} |
||||
} |
||||
</style> |
||||
|
||||
<style is:global> |
||||
:root.theme-dark .nav-link a[aria-current="page"] { |
||||
color: hsla(var(--color-base-white), 100%, 1); |
||||
} |
||||
</style> |
@ -0,0 +1,53 @@ |
||||
--- |
||||
import type { Frontmatter } from "../../config"; |
||||
import MoreMenu from "../RightSidebar/MoreMenu.astro"; |
||||
import TableOfContents from "../RightSidebar/TableOfContents"; |
||||
import type { MarkdownHeading } from "astro"; |
||||
|
||||
type Props = { |
||||
frontmatter: Frontmatter; |
||||
headings: MarkdownHeading[]; |
||||
githubEditUrl: string; |
||||
}; |
||||
|
||||
const { frontmatter, headings, githubEditUrl } = Astro.props as Props; |
||||
const title = frontmatter.title; |
||||
--- |
||||
|
||||
<article id="article" class="content"> |
||||
<section class="main-section"> |
||||
<h2 class="content-title" id="overview">{title}</h2> |
||||
<nav class="block sm:hidden"> |
||||
<TableOfContents client:media="(max-width: 50em)" headings={headings} /> |
||||
</nav> |
||||
<slot /> |
||||
</section> |
||||
<nav class="block sm:hidden"> |
||||
<MoreMenu editHref={githubEditUrl} /> |
||||
</nav> |
||||
</article> |
||||
|
||||
<style> |
||||
.content { |
||||
padding: 0; |
||||
max-width: 75ch; |
||||
width: 100%; |
||||
height: 100%; |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
.content > section { |
||||
margin-bottom: 4rem; |
||||
} |
||||
|
||||
.block { |
||||
display: block; |
||||
} |
||||
|
||||
@media (min-width: 50em) { |
||||
.sm\:hidden { |
||||
display: none; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,101 @@ |
||||
--- |
||||
import ThemeToggleButton from "./ThemeToggleButton"; |
||||
import * as CONFIG from "../../config"; |
||||
|
||||
type Props = { |
||||
editHref: string; |
||||
}; |
||||
|
||||
const { editHref } = Astro.props as Props; |
||||
const showMoreSection = CONFIG.COMMUNITY_INVITE_URL; |
||||
--- |
||||
|
||||
{showMoreSection && <h2 class="heading">More</h2>} |
||||
<ul> |
||||
{ |
||||
editHref && ( |
||||
<li class={`heading-link depth-2`}> |
||||
<a class="edit-on-github" href={editHref} target="_blank"> |
||||
<svg |
||||
aria-hidden="true" |
||||
focusable="false" |
||||
data-prefix="fas" |
||||
data-icon="pen" |
||||
class="svg-inline--fa fa-pen fa-w-16" |
||||
role="img" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
viewBox="0 0 512 512" |
||||
height="1em" |
||||
width="1em" |
||||
> |
||||
<path |
||||
fill="currentColor" |
||||
d="M290.74 93.24l128.02 128.02-277.99 277.99-114.14 12.6C11.35 513.54-1.56 500.62.14 485.34l12.7-114.22 277.9-277.88zm207.2-19.06l-60.11-60.11c-18.75-18.75-49.16-18.75-67.91 0l-56.55 56.55 128.02 128.02 56.55-56.55c18.75-18.76 18.75-49.16 0-67.91z" |
||||
/> |
||||
</svg> |
||||
<span>Edit this page</span> |
||||
</a> |
||||
</li> |
||||
) |
||||
} |
||||
{ |
||||
CONFIG.COMMUNITY_INVITE_URL && ( |
||||
<li class={`heading-link depth-2`}> |
||||
<a href={CONFIG.COMMUNITY_INVITE_URL} target="_blank"> |
||||
<svg |
||||
aria-hidden="true" |
||||
focusable="false" |
||||
data-prefix="fas" |
||||
data-icon="comment-alt" |
||||
class="svg-inline--fa fa-comment-alt fa-w-16" |
||||
role="img" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
viewBox="0 0 512 512" |
||||
height="1em" |
||||
width="1em" |
||||
> |
||||
<path |
||||
fill="currentColor" |
||||
d="M448 0H64C28.7 0 0 28.7 0 64v288c0 35.3 28.7 64 64 64h96v84c0 9.8 11.2 15.5 19.1 9.7L304 416h144c35.3 0 64-28.7 64-64V64c0-35.3-28.7-64-64-64z" |
||||
/> |
||||
</svg> |
||||
<span>Join our community</span> |
||||
</a> |
||||
</li> |
||||
) |
||||
} |
||||
<li class={`heading-link depth-2`}> |
||||
<a href="https://nextgraph.org" target="_blank"> |
||||
<svg |
||||
aria-hidden="true" |
||||
focusable="false" |
||||
data-prefix="fas" |
||||
data-icon="home" |
||||
class="svg-inline--fa fa-home fa-w-16" |
||||
role="img" |
||||
xmlns="http://www.w3.org/2000/svg" |
||||
viewBox="0 0 576 512" |
||||
height="1em" |
||||
width="1em" |
||||
> |
||||
<path |
||||
fill="currentColor" |
||||
d="M575.8 255.5c0 18-15 32.1-32 32.1h-32l.7 160.2c0 2.7-.2 5.4-.5 8.1V472c0 22.1-17.9 40-40 40H456c-1.1 0-2.2 0-3.3-.1c-1.4 .1-2.8 .1-4.2 .1H416 392c-22.1 0-40-17.9-40-40V448 384c0-17.7-14.3-32-32-32H256c-17.7 0-32 14.3-32 32v64 24c0 22.1-17.9 40-40 40H160 128.1c-1.5 0-3-.1-4.5-.2c-1.2 .1-2.4 .2-3.6 .2H104c-22.1 0-40-17.9-40-40V360c0-.9 0-1.9 .1-2.8V287.6H32c-18 0-32-14-32-32.1c0-9 3-17 10-24L266.4 8c7-7 15-8 22-8s15 2 21 7L564.8 231.5c8 7 12 15 11 24z" |
||||
></path> |
||||
</svg> |
||||
<span>nextgraph.org</span> |
||||
</a> |
||||
</li> |
||||
</ul> |
||||
<div style="margin: 2rem 0; text-align: center;"> |
||||
<ThemeToggleButton client:visible /> |
||||
</div> |
||||
|
||||
<style> |
||||
.edit-on-github { |
||||
text-decoration: none; |
||||
font: inherit; |
||||
color: inherit; |
||||
font-size: 1rem; |
||||
} |
||||
</style> |
@ -0,0 +1,34 @@ |
||||
--- |
||||
import TableOfContents from './TableOfContents'; |
||||
import MoreMenu from './MoreMenu.astro'; |
||||
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"> |
||||
<div class="sidebar-nav-inner"> |
||||
<TableOfContents client:media="(min-width: 50em)" headings={headings} /> |
||||
<MoreMenu editHref={githubEditUrl} /> |
||||
</div> |
||||
</nav> |
||||
|
||||
<style> |
||||
.sidebar-nav { |
||||
width: 100%; |
||||
position: sticky; |
||||
top: 0; |
||||
} |
||||
|
||||
.sidebar-nav-inner { |
||||
height: 100%; |
||||
padding: 0; |
||||
padding-top: var(--doc-padding); |
||||
overflow: auto; |
||||
} |
||||
</style> |
@ -0,0 +1,56 @@ |
||||
import type { FunctionalComponent } from 'preact'; |
||||
import { useState, useEffect, useRef } from 'preact/hooks'; |
||||
import type { MarkdownHeading } from 'astro'; |
||||
|
||||
type ItemOffsets = { |
||||
id: string; |
||||
topOffset: number; |
||||
}; |
||||
|
||||
const TableOfContents: FunctionalComponent<{ headings: MarkdownHeading[] }> = ({ |
||||
headings = [], |
||||
}) => { |
||||
const itemOffsets = useRef<ItemOffsets[]>([]); |
||||
// FIXME: Not sure what this state is doing. It was never set to anything truthy.
|
||||