@jaamd/jaad 0.0.0-stage → 1.0.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +58 -2
- package/dist/jaad.css +1331 -0
- package/docs/00-overview.md +25 -0
- package/docs/01-getting-started/01-installation.md +216 -0
- package/docs/01-getting-started/02-pages.md +109 -0
- package/docs/01-getting-started/03-deployment.md +100 -0
- package/docs/02-configurations/01-reference.md +215 -0
- package/docs/02-configurations/02-frontmatter.md +105 -0
- package/docs/02-configurations/03-i18n.md +210 -0
- package/docs/02-configurations/04-search.md +58 -0
- package/docs/02-configurations/05-themes.md +104 -0
- package/docs/02-configurations/06-styles.md +156 -0
- package/docs/02-configurations/07-custom-pages.md +119 -0
- package/docs/02-configurations/08-advanced-layout.md +139 -0
- package/docs/03-upgrading.md +208 -0
- package/index.ts +31 -0
- package/package.json +71 -3
- package/site.ts +36 -0
- package/src/config.ts +85 -0
- package/src/docs/advanced.ts +23 -0
- package/src/docs/collection.ts +109 -0
- package/src/docs/components/Breadcrumbs.astro +29 -0
- package/src/docs/components/DocsMobileNav.astro +90 -0
- package/src/docs/components/DocsNavTree.astro +57 -0
- package/src/docs/components/DocsPage.astro +66 -0
- package/src/docs/components/DocsPageNav.astro +53 -0
- package/src/docs/components/DocsSidebarLeft.astro +53 -0
- package/src/docs/components/PageActions.astro +83 -0
- package/src/docs/components/StructuredData.astro +55 -0
- package/src/docs/components/TableOfContents.astro +71 -0
- package/src/docs/config.ts +167 -0
- package/src/docs/content.ts +36 -0
- package/src/docs/endpoints.ts +89 -0
- package/src/docs/integration.ts +249 -0
- package/src/docs/layouts/DefaultDocsFrame.astro +33 -0
- package/src/docs/layouts/Docs.astro +169 -0
- package/src/docs/layouts/Page.astro +18 -0
- package/src/docs/link-plugins.ts +52 -0
- package/src/docs/links.ts +42 -0
- package/src/docs/locale-tree.ts +51 -0
- package/src/docs/model/ids.ts +87 -0
- package/src/docs/model/rendered.ts +185 -0
- package/src/docs/model/tree.ts +124 -0
- package/src/docs/model/validate.ts +75 -0
- package/src/docs/navigation.ts +75 -0
- package/src/docs/routes/404.astro +16 -0
- package/src/docs/routes/docs-index.astro +19 -0
- package/src/docs/routes/docs-slug.astro +54 -0
- package/src/docs/routes/docs-slug.md.ts +35 -0
- package/src/docs/routes/llms-locale.txt.ts +8 -0
- package/src/docs/routes/llms.txt.ts +4 -0
- package/src/docs/routes/search-index-locale.json.ts +8 -0
- package/src/docs/routes/search-index.json.ts +4 -0
- package/src/docs/runtime.ts +17 -0
- package/src/docs/search-index.ts +39 -0
- package/src/docs/styles/components/breadcrumbs.css +26 -0
- package/src/docs/styles/components/docs-mobile-nav.css +47 -0
- package/src/docs/styles/components/docs-page-nav.css +41 -0
- package/src/docs/styles/components/docs-page.css +9 -0
- package/src/docs/styles/components/docs-sidebar.css +59 -0
- package/src/docs/styles/components/page-actions.css +51 -0
- package/src/docs/styles/components/toc.css +38 -0
- package/src/docs/styles/components.css +7 -0
- package/src/docs/styles/docs.css +81 -0
- package/src/docs/styles/jaamd.css +11 -0
- package/src/docs/types.ts +39 -0
- package/src/docs/urls.ts +35 -0
- package/src/env.d.ts +13 -0
- package/src/integration.ts +36 -0
- package/src/site/components/Footer.astro +18 -0
- package/src/site/components/Head.astro +119 -0
- package/src/site/components/Header.astro +50 -0
- package/src/site/components/LocaleSwitcher.astro +111 -0
- package/src/site/components/Nav.astro +22 -0
- package/src/site/components/NavMobile.astro +105 -0
- package/src/site/components/SearchPalette.astro +409 -0
- package/src/site/components/SocialLinks.astro +27 -0
- package/src/site/components/ThemeSwitcher.astro +55 -0
- package/src/site/components/locale-switcher.ts +6 -0
- package/src/site/config.ts +149 -0
- package/src/site/fonts/OFL.txt +94 -0
- package/src/site/fonts/inter.woff2 +0 -0
- package/src/site/fonts/merriweather-italic.woff2 +0 -0
- package/src/site/fonts/merriweather.woff2 +0 -0
- package/src/site/fonts.ts +29 -0
- package/src/site/forges.ts +60 -0
- package/src/site/helpers.ts +4 -0
- package/src/site/infer.ts +75 -0
- package/src/site/integration.ts +59 -0
- package/src/site/layouts/Shell.astro +41 -0
- package/src/site/locales.ts +199 -0
- package/src/site/runtime.ts +19 -0
- package/src/site/search.ts +227 -0
- package/src/site/social.ts +44 -0
- package/src/site/styles/components/footer.css +8 -0
- package/src/site/styles/components/header.css +31 -0
- package/src/site/styles/components/locale-switcher.css +20 -0
- package/src/site/styles/components/nav-mobile.css +15 -0
- package/src/site/styles/components/nav.css +11 -0
- package/src/site/styles/components/not-found.css +11 -0
- package/src/site/styles/components/search.css +118 -0
- package/src/site/styles/components/shell.css +77 -0
- package/src/site/styles/components/social-links.css +8 -0
- package/src/site/styles/components/theme-switcher.css +14 -0
- package/src/site/styles/components.css +10 -0
- package/src/site/styles/global.css +140 -0
- package/src/site/styles/jaamd-forward.css +16 -0
- package/src/site/styles/jaamd-reverse.css +18 -0
- package/src/site/styles/tailwind.css +30 -0
- package/src/site/themes.ts +26 -0
- package/src/site/ui/en.ts +27 -0
- package/src/site/ui/it.ts +28 -0
- package/src/site/ui.ts +49 -0
- package/src/site/urls.ts +14 -0
- package/src/site/virtual.ts +42 -0
- package/src/styles.css +5 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.docs-page-actions {
|
|
3
|
+
display: flex;
|
|
4
|
+
flex-wrap: wrap;
|
|
5
|
+
gap: 0.25rem 1.25rem;
|
|
6
|
+
margin-top: 2rem;
|
|
7
|
+
font-size: 0.8125rem;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.docs-page-action {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
gap: 0.5rem;
|
|
14
|
+
min-height: 2.75rem;
|
|
15
|
+
padding: 0;
|
|
16
|
+
border: 0;
|
|
17
|
+
background: none;
|
|
18
|
+
color: var(--color-foreground-muted);
|
|
19
|
+
font: inherit;
|
|
20
|
+
text-decoration: none;
|
|
21
|
+
cursor: pointer;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.docs-page-action:hover {
|
|
25
|
+
color: var(--color-foreground-bright);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.docs-page-action-icon {
|
|
29
|
+
flex: none;
|
|
30
|
+
width: 0.875rem;
|
|
31
|
+
height: 0.875rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Under the table of contents: a column of quiet links. */
|
|
35
|
+
@media (min-width: 64rem) {
|
|
36
|
+
.docs-page-actions {
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
margin-top: 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.docs-page-action {
|
|
42
|
+
min-height: 1.75rem;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.docs-toc ~ .docs-page-actions {
|
|
46
|
+
margin-top: 1.5rem;
|
|
47
|
+
padding-top: 1rem;
|
|
48
|
+
border-top: 1px solid var(--color-border);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
@layer components {
|
|
2
|
+
.docs-toc-title {
|
|
3
|
+
margin: 0 0 0.5rem;
|
|
4
|
+
font-family: var(--font-sans);
|
|
5
|
+
font-size: 0.75rem;
|
|
6
|
+
font-weight: 500;
|
|
7
|
+
color: var(--color-foreground-muted);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.docs-toc-list {
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
list-style: none;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.docs-toc-link {
|
|
17
|
+
display: block;
|
|
18
|
+
padding: 0.1875rem 0 0.1875rem calc(0.625rem - 2px);
|
|
19
|
+
border-left: 2px solid transparent;
|
|
20
|
+
color: var(--color-foreground-muted);
|
|
21
|
+
font-size: 0.8125rem;
|
|
22
|
+
line-height: 1.5;
|
|
23
|
+
text-decoration: none;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.docs-toc-link-nested {
|
|
27
|
+
padding-left: calc(1.375rem - 2px);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.docs-toc-link:hover {
|
|
31
|
+
color: var(--color-foreground-bright);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.docs-toc-link[aria-current='location'] {
|
|
35
|
+
border-color: var(--color-foreground-bright);
|
|
36
|
+
color: var(--color-foreground-bright);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
@import './components/docs-page.css';
|
|
2
|
+
@import './components/docs-sidebar.css';
|
|
3
|
+
@import './components/toc.css';
|
|
4
|
+
@import './components/docs-mobile-nav.css';
|
|
5
|
+
@import './components/docs-page-nav.css';
|
|
6
|
+
@import './components/page-actions.css';
|
|
7
|
+
@import './components/breadcrumbs.css';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/* Layered, so a page's own utilities still win. */
|
|
2
|
+
@layer components {
|
|
3
|
+
.docs-frame {
|
|
4
|
+
display: contents;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* The header's column. Wide, it holds the page between two sidebars fixed
|
|
8
|
+
to the window: only the page scrolls. */
|
|
9
|
+
.docs-container {
|
|
10
|
+
width: 100%;
|
|
11
|
+
max-width: var(--jaad-chrome-width);
|
|
12
|
+
margin-inline: auto;
|
|
13
|
+
padding-inline: 1rem;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.docs-main {
|
|
17
|
+
width: 100%;
|
|
18
|
+
min-width: 0;
|
|
19
|
+
max-width: var(--jaad-content-width);
|
|
20
|
+
margin-inline: auto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.docs-page-main {
|
|
24
|
+
padding-top: 2rem;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
[data-jaad-default-frame] .docs-container > .jaad-footer {
|
|
28
|
+
width: 100%;
|
|
29
|
+
max-width: var(--jaad-content-width);
|
|
30
|
+
margin-inline: auto;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@media (48rem <= width < 64rem) {
|
|
34
|
+
.docs-page-main {
|
|
35
|
+
padding-inline: var(--jaad-page-padding);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@media (min-width: 64rem) {
|
|
40
|
+
.docs-page-main {
|
|
41
|
+
padding-top: 2.5rem;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[data-jaad-default-frame] .docs-container {
|
|
45
|
+
display: grid;
|
|
46
|
+
grid-template-columns: var(--jaad-sidebar-width) minmax(0, 1fr) var(--jaad-sidebar-width);
|
|
47
|
+
column-gap: 3rem;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[data-jaad-default-frame] .docs-main,
|
|
51
|
+
[data-jaad-default-frame] .docs-container > .jaad-footer {
|
|
52
|
+
grid-column: 2;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
[data-jaad-default-frame] .docs-sidebar-left,
|
|
56
|
+
[data-jaad-default-frame] .docs-sidebar-right {
|
|
57
|
+
position: fixed;
|
|
58
|
+
top: var(--jaad-header-height);
|
|
59
|
+
bottom: 0;
|
|
60
|
+
padding-block: 2rem;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Widened into the gutter, where the bar of the current page sits. */
|
|
64
|
+
[data-jaad-default-frame] .docs-sidebar-left {
|
|
65
|
+
left: max(1rem, calc(50% - var(--jaad-chrome-width) / 2 + 1rem));
|
|
66
|
+
margin-left: -0.75rem;
|
|
67
|
+
padding-left: 0.75rem;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
[data-jaad-default-frame] .docs-sidebar-right {
|
|
71
|
+
right: max(1rem, calc(50% - var(--jaad-chrome-width) / 2 + 1rem));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@media (width < 64rem) {
|
|
76
|
+
[data-jaad-default-frame] .docs-sidebar-left,
|
|
77
|
+
[data-jaad-default-frame] .docs-sidebar-right .docs-toc {
|
|
78
|
+
display: none;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { RenderedMarkdown } from './model/rendered.ts'
|
|
2
|
+
|
|
3
|
+
export interface DocsNavItem {
|
|
4
|
+
title: string
|
|
5
|
+
/** Set when the docs are split into sections. */
|
|
6
|
+
section?: string
|
|
7
|
+
chapter?: string
|
|
8
|
+
href: string
|
|
9
|
+
isActive: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface DocsHeading {
|
|
13
|
+
depth: 2 | 3
|
|
14
|
+
slug: string
|
|
15
|
+
text: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Resolved frontmatter. Mirrors the schema in content.ts. */
|
|
19
|
+
export interface DocsPageData {
|
|
20
|
+
title?: string
|
|
21
|
+
description?: string
|
|
22
|
+
label?: string
|
|
23
|
+
translationKey?: string
|
|
24
|
+
order?: number
|
|
25
|
+
draft: boolean
|
|
26
|
+
keywords: string[]
|
|
27
|
+
author?: string
|
|
28
|
+
ogImage?: string | false
|
|
29
|
+
lastUpdated?: Date
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The shape jaad requires of a docs collection entry. Astro generates
|
|
33
|
+
* collection types in the consumer project, so the package states its own contract. */
|
|
34
|
+
export interface DocsEntry {
|
|
35
|
+
id: string
|
|
36
|
+
body?: string
|
|
37
|
+
data: DocsPageData
|
|
38
|
+
rendered?: RenderedMarkdown
|
|
39
|
+
}
|
package/src/docs/urls.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { localeSegment } from '../site/locales.ts'
|
|
2
|
+
import { normaliseBasePath, withDeploymentBase } from '../site/urls.ts'
|
|
3
|
+
|
|
4
|
+
export interface DocsUrlOptions {
|
|
5
|
+
/** routeBase, normalised here: empty at the application root. */
|
|
6
|
+
docsBase: string
|
|
7
|
+
/** Astro's deployment base. */
|
|
8
|
+
deploymentBase?: string
|
|
9
|
+
/** Locale of the page, when the docs are localised. */
|
|
10
|
+
locale?: string
|
|
11
|
+
/** The one locale that owns the unprefixed urls. */
|
|
12
|
+
defaultLocale?: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Public href for a documentation page. Empty slug means the opening page. */
|
|
16
|
+
export function docsPageHref(slug: string, urls: DocsUrlOptions): string {
|
|
17
|
+
return docsHref(slug, urls)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Raw Markdown keeps the page's named slug, including for the opener. */
|
|
21
|
+
export function docsMarkdownHref(slug: string, urls: DocsUrlOptions): string {
|
|
22
|
+
return docsHref(slug, urls, '.md')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function docsHref(
|
|
26
|
+
slug: string,
|
|
27
|
+
{ docsBase, deploymentBase = '', locale, defaultLocale }: DocsUrlOptions,
|
|
28
|
+
suffix = '',
|
|
29
|
+
): string {
|
|
30
|
+
const mount = normaliseBasePath(docsBase)
|
|
31
|
+
const path = [localeSegment(locale, defaultLocale), slug].filter(Boolean).join('/')
|
|
32
|
+
// Without a suffix an empty path is the mount itself; with one it is a file.
|
|
33
|
+
const href = path || suffix ? `${mount}/${path}${suffix}` : mount || '/'
|
|
34
|
+
return withDeploymentBase(href, deploymentBase)
|
|
35
|
+
}
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="astro/client" />
|
|
2
|
+
|
|
3
|
+
declare module 'virtual:jaad/config' {
|
|
4
|
+
// oxlint-disable-next-line typescript/consistent-type-imports
|
|
5
|
+
const config: import('./config.ts').JaadResolvedConfig
|
|
6
|
+
export default config
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
declare module 'virtual:jaad/docs-frame' {
|
|
10
|
+
// oxlint-disable-next-line typescript/consistent-type-imports
|
|
11
|
+
const DocsFrame: typeof import('./docs/layouts/DefaultDocsFrame.astro').default
|
|
12
|
+
export default DocsFrame
|
|
13
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { JaadResolvedConfig } from './config.ts'
|
|
2
|
+
import type { IntegrationState } from './docs/integration.ts'
|
|
3
|
+
import type { SiteStylesheets } from './site/virtual.ts'
|
|
4
|
+
import type { AstroIntegration } from 'astro'
|
|
5
|
+
import { setupDocs } from './docs/integration.ts'
|
|
6
|
+
import { setupSite } from './site/integration.ts'
|
|
7
|
+
import { normaliseBasePath } from './site/urls.ts'
|
|
8
|
+
|
|
9
|
+
/** The site, then the docs on it. */
|
|
10
|
+
export function createJaadIntegration(
|
|
11
|
+
config: JaadResolvedConfig,
|
|
12
|
+
stylesheets: SiteStylesheets,
|
|
13
|
+
state: IntegrationState,
|
|
14
|
+
discoverAliases: boolean,
|
|
15
|
+
): AstroIntegration {
|
|
16
|
+
return {
|
|
17
|
+
name: 'jaad',
|
|
18
|
+
hooks: {
|
|
19
|
+
'astro:config:setup': (params) => {
|
|
20
|
+
setupSite(params, config, stylesheets)
|
|
21
|
+
setupDocs(params, config, state, discoverAliases)
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
'astro:config:done': ({ config: astroConfig, injectTypes }) => {
|
|
25
|
+
state.deploymentBase = normaliseBasePath(astroConfig.base)
|
|
26
|
+
injectTypes({
|
|
27
|
+
filename: 'jaad.d.ts',
|
|
28
|
+
content: `declare module "virtual:jaad/config" {
|
|
29
|
+
const config: import("@jaamd/jaad").JaadResolvedConfig;
|
|
30
|
+
export default config;
|
|
31
|
+
}`,
|
|
32
|
+
})
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
import config from 'virtual:jaad/config'
|
|
3
|
+
|
|
4
|
+
const CREDIT = 'Built with <a href="https://docs.jaamd.org/jaad" rel="noopener">JAAD</a>.'
|
|
5
|
+
|
|
6
|
+
// Unset is the credit alone; `footer: false` removes it.
|
|
7
|
+
const line = config.footer === undefined ? ':credit' : config.footer
|
|
8
|
+
const html = line === false ? null : line.replaceAll(':credit', CREDIT)
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
{html && (
|
|
12
|
+
<footer class="jaad-chrome jaad-footer" aria-labelledby="footer-heading">
|
|
13
|
+
<h2 class="jaad-sr-only" id="footer-heading">
|
|
14
|
+
Site footer
|
|
15
|
+
</h2>
|
|
16
|
+
<p class="jaad-footer-credit" set:html={html} />
|
|
17
|
+
</footer>
|
|
18
|
+
)}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { Font } from 'astro:assets'
|
|
3
|
+
import config from 'virtual:jaad/config'
|
|
4
|
+
import { openGraphLocale } from '../locales.ts'
|
|
5
|
+
import { withDeploymentBase } from '../urls.ts'
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
title: string
|
|
9
|
+
description: string
|
|
10
|
+
/** Page frontmatter; each field overrides the site's. */
|
|
11
|
+
keywords?: string[]
|
|
12
|
+
author?: string
|
|
13
|
+
image?: string | false
|
|
14
|
+
modified?: Date
|
|
15
|
+
/** BCP-47 tag of this page's locale. */
|
|
16
|
+
language?: string
|
|
17
|
+
/** Every locale that has this page, for hreflang and og:locale:alternate. */
|
|
18
|
+
alternates?: { code: string; tag: string; href: string }[]
|
|
19
|
+
/** Where a reader with no matching language should land. */
|
|
20
|
+
xDefault?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
title,
|
|
25
|
+
description,
|
|
26
|
+
keywords = [],
|
|
27
|
+
author,
|
|
28
|
+
image,
|
|
29
|
+
modified,
|
|
30
|
+
language = config.lang,
|
|
31
|
+
alternates = [],
|
|
32
|
+
xDefault,
|
|
33
|
+
} = Astro.props
|
|
34
|
+
const canonical = Astro.site ? Astro.url.href : null
|
|
35
|
+
|
|
36
|
+
// Open Graph rejects relative paths, so these have to be absolute.
|
|
37
|
+
const resolvedImage = image ?? config.ogImage
|
|
38
|
+
const ogImage =
|
|
39
|
+
resolvedImage === false
|
|
40
|
+
? null
|
|
41
|
+
: /^https?:\/\//.test(resolvedImage)
|
|
42
|
+
? resolvedImage
|
|
43
|
+
: Astro.site
|
|
44
|
+
? new URL(withDeploymentBase(resolvedImage, import.meta.env.BASE_URL), Astro.site).href
|
|
45
|
+
: null
|
|
46
|
+
const favicon = config.favicon ? withDeploymentBase(config.favicon, import.meta.env.BASE_URL) : null
|
|
47
|
+
|
|
48
|
+
// Applied before the first paint, so the page never flashes the wrong theme.
|
|
49
|
+
const themeScript = `(function () {
|
|
50
|
+
function isDark() {
|
|
51
|
+
var t = localStorage.getItem("theme");
|
|
52
|
+
return (
|
|
53
|
+
t === "dark" || (!t && matchMedia("(prefers-color-scheme: dark)").matches)
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
function applyTheme(root) {
|
|
57
|
+
if (isDark()) {
|
|
58
|
+
root.classList.add("dark");
|
|
59
|
+
} else {
|
|
60
|
+
root.classList.remove("dark");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
applyTheme(document.documentElement);
|
|
64
|
+
document.addEventListener("astro:before-swap", function (e) {
|
|
65
|
+
applyTheme(e.newDocument.documentElement);
|
|
66
|
+
});
|
|
67
|
+
document.addEventListener("astro:after-swap", function () {
|
|
68
|
+
applyTheme(document.documentElement);
|
|
69
|
+
});
|
|
70
|
+
})();`
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
<meta charset="UTF-8" />
|
|
74
|
+
<meta name="viewport" content="width=device-width" />
|
|
75
|
+
<meta name="description" content={description} />
|
|
76
|
+
{keywords.length > 0 && <meta name="keywords" content={keywords.join(', ')} />}
|
|
77
|
+
{author && <meta name="author" content={author} />}
|
|
78
|
+
<title>{title}</title>
|
|
79
|
+
{canonical && <link rel="canonical" href={canonical} />}
|
|
80
|
+
{Astro.site &&
|
|
81
|
+
alternates.map((alternate) => (
|
|
82
|
+
<link rel="alternate" hreflang={alternate.tag} href={new URL(alternate.href, Astro.site).href} />
|
|
83
|
+
))}
|
|
84
|
+
{Astro.site && xDefault && <link rel="alternate" hreflang="x-default" href={new URL(xDefault, Astro.site).href} />}
|
|
85
|
+
|
|
86
|
+
{favicon && <link rel="icon" href={favicon} />}
|
|
87
|
+
|
|
88
|
+
<Font cssVariable="--font-inter" preload />
|
|
89
|
+
<Font cssVariable="--font-merriweather" preload />
|
|
90
|
+
|
|
91
|
+
{canonical && <meta property="og:url" content={canonical} />}
|
|
92
|
+
<meta property="og:type" content="website" />
|
|
93
|
+
<meta property="og:site_name" content={config.title} />
|
|
94
|
+
<meta property="og:locale" content={openGraphLocale(language)} />
|
|
95
|
+
{alternates
|
|
96
|
+
.filter((alternate) => alternate.tag !== language)
|
|
97
|
+
.map((alternate) => (
|
|
98
|
+
<meta property="og:locale:alternate" content={openGraphLocale(alternate.tag)} />
|
|
99
|
+
))}
|
|
100
|
+
<meta property="og:title" content={title} />
|
|
101
|
+
<meta property="og:description" content={description} />
|
|
102
|
+
{modified && <meta property="article:modified_time" content={modified.toISOString()} />}
|
|
103
|
+
{ogImage && <meta property="og:image" content={ogImage} />}
|
|
104
|
+
|
|
105
|
+
<meta name="twitter:card" content={ogImage ? 'summary_large_image' : 'summary'} />
|
|
106
|
+
{canonical && <meta property="twitter:domain" content={Astro.url.hostname} />}
|
|
107
|
+
{canonical && <meta property="twitter:url" content={canonical} />}
|
|
108
|
+
<meta name="twitter:title" content={title} />
|
|
109
|
+
<meta name="twitter:description" content={description} />
|
|
110
|
+
{ogImage && <meta name="twitter:image" content={ogImage} />}
|
|
111
|
+
|
|
112
|
+
<meta name="generator" content={Astro.generator} />
|
|
113
|
+
|
|
114
|
+
{config.head.map((tag) => {
|
|
115
|
+
const Tag = tag.tag as keyof HTMLElementTagNameMap
|
|
116
|
+
return <Tag {...tag.attrs} set:html={tag.content ?? ''} />
|
|
117
|
+
})}
|
|
118
|
+
|
|
119
|
+
{config.appearance === 'auto' && <script is:inline set:html={themeScript} />}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { LocaleAlternate } from '../locales.ts'
|
|
3
|
+
import config from 'virtual:jaad/config'
|
|
4
|
+
import { DEFAULT_LOCALE, ui } from '../runtime.ts'
|
|
5
|
+
import { withDeploymentBase } from '../urls.ts'
|
|
6
|
+
import LocaleSwitcher from './LocaleSwitcher.astro'
|
|
7
|
+
import Nav from './Nav.astro'
|
|
8
|
+
import NavMobile from './NavMobile.astro'
|
|
9
|
+
import SearchPalette from './SearchPalette.astro'
|
|
10
|
+
import SocialLinks from './SocialLinks.astro'
|
|
11
|
+
import ThemeSwitcher from './ThemeSwitcher.astro'
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
locale?: string
|
|
15
|
+
/** The page in every locale, for the switcher. */
|
|
16
|
+
alternates: LocaleAlternate[]
|
|
17
|
+
search: {
|
|
18
|
+
/** `search-index.json` of the reader's locale. */
|
|
19
|
+
indexUrl: string
|
|
20
|
+
/** The full search field on wide screens, not just its icon. */
|
|
21
|
+
prominent?: boolean
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const { locale = DEFAULT_LOCALE, alternates, search } = Astro.props
|
|
26
|
+
const t = ui(locale)
|
|
27
|
+
const homeHref = withDeploymentBase(config.homeLink, import.meta.env.BASE_URL)
|
|
28
|
+
const logoSrc = config.logo ? withDeploymentBase(config.logo, import.meta.env.BASE_URL) : null
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
<header class="jaad-header" transition:persist>
|
|
32
|
+
<div class="jaad-header-inner">
|
|
33
|
+
<div class="jaad-header-row">
|
|
34
|
+
<span class="jaad-brand font-brand">
|
|
35
|
+
<a href={homeHref} title={t('home')} class="jaad-brand-link">
|
|
36
|
+
{logoSrc ? <img src={logoSrc} alt={config.title} class="jaad-logo" /> : config.title}
|
|
37
|
+
</a>
|
|
38
|
+
</span>
|
|
39
|
+
|
|
40
|
+
<div class="jaad-header-actions">
|
|
41
|
+
<SearchPalette indexUrl={search.indexUrl} prominent={search.prominent} {locale} />
|
|
42
|
+
<Nav />
|
|
43
|
+
<SocialLinks />
|
|
44
|
+
{config.appearance === 'auto' && <ThemeSwitcher {locale} />}
|
|
45
|
+
{alternates.length > 1 && <LocaleSwitcher {alternates} current={locale} />}
|
|
46
|
+
<NavMobile {locale} />
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</header>
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { LocaleAlternate } from '../locales.ts'
|
|
3
|
+
import { ui } from '../runtime.ts'
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
alternates: LocaleAlternate[]
|
|
7
|
+
current: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { alternates, current } = Astro.props
|
|
11
|
+
const active = alternates.find((entry) => entry.locale.code === current)
|
|
12
|
+
const t = ui(current)
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<jaad-locale-switcher class="jaad-locale-switcher">
|
|
16
|
+
<button
|
|
17
|
+
type="button"
|
|
18
|
+
data-trigger
|
|
19
|
+
aria-expanded="false"
|
|
20
|
+
aria-haspopup="true"
|
|
21
|
+
aria-controls="jaad-locale-menu"
|
|
22
|
+
aria-label={t('locale.change')}
|
|
23
|
+
title={t('locale.change')}
|
|
24
|
+
class="jaad-icon-button jaad-locale-trigger"
|
|
25
|
+
>
|
|
26
|
+
{active?.locale.flag && <span aria-hidden="true">{active.locale.flag}</span>}
|
|
27
|
+
<span class="jaad-locale-code">{current}</span>
|
|
28
|
+
<svg class="jaad-locale-chevron" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
|
|
29
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
|
30
|
+
</svg>
|
|
31
|
+
</button>
|
|
32
|
+
|
|
33
|
+
<div id="jaad-locale-menu" data-menu hidden class="jaad-menu jaad-locale-menu">
|
|
34
|
+
{alternates.map((entry) => (
|
|
35
|
+
<a
|
|
36
|
+
href={entry.href}
|
|
37
|
+
hreflang={entry.locale.tag}
|
|
38
|
+
lang={entry.locale.tag}
|
|
39
|
+
aria-current={entry.locale.code === current ? 'true' : undefined}
|
|
40
|
+
class="jaad-menu-item jaad-locale-item"
|
|
41
|
+
>
|
|
42
|
+
{entry.locale.flag && <span aria-hidden="true">{entry.locale.flag}</span>}
|
|
43
|
+
<span>{entry.locale.name}</span>
|
|
44
|
+
</a>
|
|
45
|
+
))}
|
|
46
|
+
</div>
|
|
47
|
+
</jaad-locale-switcher>
|
|
48
|
+
|
|
49
|
+
<script>
|
|
50
|
+
import { nextSwitcherMarkup } from './locale-switcher.ts'
|
|
51
|
+
|
|
52
|
+
class JaadLocaleSwitcher extends HTMLElement {
|
|
53
|
+
#onDocumentPointerDown = (event: PointerEvent) => {
|
|
54
|
+
if (!this.contains(event.target as Node)) this.#close()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
#onKeyDown = (event: KeyboardEvent) => {
|
|
58
|
+
if (event.key === 'Escape') this.#close()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The header persists across navigations; its links must not. */
|
|
62
|
+
#onBeforeSwap = (event: Event) => {
|
|
63
|
+
const markup = nextSwitcherMarkup((event as Event & { newDocument: Document }).newDocument)
|
|
64
|
+
if (markup !== null) this.innerHTML = markup
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Delegated: #onBeforeSwap replaces the button this would bind to. */
|
|
68
|
+
#onClick = (event: MouseEvent) => {
|
|
69
|
+
const target = event.target as Element | null
|
|
70
|
+
if (!target?.closest('[data-trigger]')) return
|
|
71
|
+
if (this.#menu()?.hidden) this.#open()
|
|
72
|
+
else this.#close()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
connectedCallback() {
|
|
76
|
+
this.addEventListener('click', this.#onClick)
|
|
77
|
+
document.addEventListener('pointerdown', this.#onDocumentPointerDown)
|
|
78
|
+
document.addEventListener('keydown', this.#onKeyDown)
|
|
79
|
+
document.addEventListener('astro:before-swap', this.#onBeforeSwap)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
disconnectedCallback() {
|
|
83
|
+
this.removeEventListener('click', this.#onClick)
|
|
84
|
+
document.removeEventListener('pointerdown', this.#onDocumentPointerDown)
|
|
85
|
+
document.removeEventListener('keydown', this.#onKeyDown)
|
|
86
|
+
document.removeEventListener('astro:before-swap', this.#onBeforeSwap)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#menu() {
|
|
90
|
+
return this.querySelector<HTMLElement>('[data-menu]')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#open() {
|
|
94
|
+
const menu = this.#menu()
|
|
95
|
+
if (menu) menu.hidden = false
|
|
96
|
+
this.querySelector('[data-trigger]')?.setAttribute('aria-expanded', 'true')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#close() {
|
|
100
|
+
const menu = this.#menu()
|
|
101
|
+
if (menu) menu.hidden = true
|
|
102
|
+
const trigger = this.querySelector<HTMLElement>('[data-trigger]')
|
|
103
|
+
trigger?.setAttribute('aria-expanded', 'false')
|
|
104
|
+
if (this.contains(document.activeElement)) trigger?.focus()
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!customElements.get('jaad-locale-switcher')) {
|
|
109
|
+
customElements.define('jaad-locale-switcher', JaadLocaleSwitcher)
|
|
110
|
+
}
|
|
111
|
+
</script>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
import config from 'virtual:jaad/config'
|
|
3
|
+
import { withDeploymentBase } from '../urls.ts'
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
{config.nav.length > 0 && (
|
|
7
|
+
<nav class="jaad-nav">
|
|
8
|
+
<ul class="jaad-nav-list">
|
|
9
|
+
{config.nav.map((link) => (
|
|
10
|
+
<li>
|
|
11
|
+
<a
|
|
12
|
+
href={withDeploymentBase(link.href, import.meta.env.BASE_URL)}
|
|
13
|
+
class="jaad-nav-link"
|
|
14
|
+
{...(link.href.startsWith('http') ? { target: '_blank', rel: 'noopener noreferrer' } : {})}
|
|
15
|
+
>
|
|
16
|
+
{link.label}
|
|
17
|
+
</a>
|
|
18
|
+
</li>
|
|
19
|
+
))}
|
|
20
|
+
</ul>
|
|
21
|
+
</nav>
|
|
22
|
+
)}
|