create-riebeckite 0.0.17 → 0.0.18
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/README.md +7 -7
- package/README_ja.md +2 -2
- package/dist/src/arguments.d.ts +3 -5
- package/dist/src/index.js +3161 -37
- package/dist/src/interactive.d.ts +8 -9
- package/dist/src/scaffold/content-pages.d.ts +16 -0
- package/dist/src/scaffold/deployment.d.ts +7 -0
- package/dist/src/scaffold/index.d.ts +7 -0
- package/dist/src/scaffold/index.js +3134 -0
- package/dist/src/scaffold/next-steps.d.ts +7 -0
- package/dist/src/scaffold/options.d.ts +43 -0
- package/dist/src/scaffold/presets.d.ts +102 -0
- package/dist/src/scaffold/template-loader.d.ts +4 -0
- package/dist/src/scaffold/templates.d.ts +53 -0
- package/dist/src/scaffold/version.d.ts +1 -0
- package/dist/src/scaffold/wrangler-defaults.d.ts +46 -0
- package/package.json +16 -5
- package/templates/scaffold/base/app/client.ts +14 -0
- package/templates/scaffold/base/app/config.ts +18 -0
- package/templates/scaffold/base/app/constants/paths.ts +10 -0
- package/templates/scaffold/base/app/content.ts +12 -0
- package/templates/scaffold/base/app/global.d.ts +15 -0
- package/templates/scaffold/base/app/server.ts +13 -0
- package/templates/scaffold/base/public/favicon.ico +0 -0
- package/templates/scaffold/base/public/riebeckite-logo.png +0 -0
- package/templates/scaffold/base/tsconfig.json +30 -0
- package/templates/scaffold/base/vite.config.ts +34 -0
- package/templates/scaffold/biome.json +6 -0
- package/templates/scaffold/deployment/github-actions/content-checkout.yml +6 -0
- package/templates/scaffold/deployment/github-actions/deploy.yml +54 -0
- package/templates/scaffold/deployment/github-actions/notify-site.yml +31 -0
- package/templates/scaffold/presets/empty/app/routes/_renderer.tsx +21 -0
- package/templates/scaffold/presets/empty/app/routes/index.tsx +11 -0
- package/templates/scaffold/presets/empty/app/style.css +17 -0
- package/templates/scaffold/presets/minimal/app/components/article.tsx +66 -0
- package/templates/scaffold/presets/minimal/app/routes/[slug{.+}].tsx +48 -0
- package/templates/scaffold/presets/minimal/app/routes/_renderer.tsx +59 -0
- package/templates/scaffold/presets/minimal/app/routes/index.tsx +29 -0
- package/templates/scaffold/presets/minimal/app/style.css +84 -0
- package/templates/scaffold/presets/minimal/content/index.en.md +12 -0
- package/templates/scaffold/presets/showcase/app/components/article.tsx +66 -0
- package/templates/scaffold/presets/showcase/app/components/site-header.tsx +113 -0
- package/templates/scaffold/presets/showcase/app/routes/[slug{.+}].tsx +52 -0
- package/templates/scaffold/presets/showcase/app/routes/_renderer.tsx +69 -0
- package/templates/scaffold/presets/showcase/app/routes/index.tsx +39 -0
- package/templates/scaffold/presets/showcase/app/style.css +227 -0
- package/templates/scaffold/presets/showcase/content/attachments/project-brief.pdf +5 -0
- package/templates/scaffold/presets/showcase/content/demo/planning.md +16 -0
- package/templates/scaffold/presets/showcase/content/demo/writing.md +15 -0
- package/templates/scaffold/presets/showcase/content/drawings/Architecture.excalidraw +1 -0
- package/templates/scaffold/presets/showcase/content/drawings/site.canvas +1 -0
- package/templates/scaffold/presets/showcase/content/guide.en.md +46 -0
- package/templates/scaffold/presets/showcase/content/images/demo.svg +1 -0
- package/templates/scaffold/presets/showcase/content/index.de.md +17 -0
- package/templates/scaffold/presets/showcase/content/index.en.md +17 -0
- package/templates/scaffold/presets/showcase/content/index.es.md +17 -0
- package/templates/scaffold/presets/showcase/content/index.fr.md +17 -0
- package/templates/scaffold/presets/showcase/content/index.ja.md +17 -0
- package/templates/scaffold/presets/showcase/content/index.ko.md +17 -0
- package/templates/scaffold/presets/showcase/content/index.zh-CN.md +17 -0
- package/templates/scaffold/presets/starter/app/components/article.tsx +66 -0
- package/templates/scaffold/presets/starter/app/components/site-header.tsx +113 -0
- package/templates/scaffold/presets/starter/app/routes/[slug{.+}].tsx +52 -0
- package/templates/scaffold/presets/starter/app/routes/_renderer.tsx +69 -0
- package/templates/scaffold/presets/starter/app/routes/index.tsx +37 -0
- package/templates/scaffold/presets/starter/app/style.css +227 -0
- package/templates/scaffold/presets/starter/content/guide.en.md +60 -0
- package/templates/scaffold/presets/starter/content/index.de.md +19 -0
- package/templates/scaffold/presets/starter/content/index.en.md +19 -0
- package/templates/scaffold/presets/starter/content/index.es.md +19 -0
- package/templates/scaffold/presets/starter/content/index.fr.md +19 -0
- package/templates/scaffold/presets/starter/content/index.ja.md +19 -0
- package/templates/scaffold/presets/starter/content/index.ko.md +19 -0
- package/templates/scaffold/presets/starter/content/index.zh-CN.md +19 -0
- package/templates/scaffold/presets/starter/content/notes/planning.md +16 -0
- package/templates/scaffold/presets/starter/content/notes/writing.md +15 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { PostContent } from "@riebeckite/core";
|
|
2
|
+
import { Article, ArticleContent, ArticleLayout } from "@riebeckite/honox/ui";
|
|
3
|
+
|
|
4
|
+
export function SiteArticle({
|
|
5
|
+
post,
|
|
6
|
+
bodySlots,
|
|
7
|
+
asideContent,
|
|
8
|
+
afterContent,
|
|
9
|
+
footerContent,
|
|
10
|
+
}: {
|
|
11
|
+
post: PostContent;
|
|
12
|
+
bodySlots?: Readonly<Record<string, string>>;
|
|
13
|
+
asideContent?: unknown;
|
|
14
|
+
afterContent?: unknown;
|
|
15
|
+
footerContent?: unknown;
|
|
16
|
+
}) {
|
|
17
|
+
return (
|
|
18
|
+
<Article class="site-article">
|
|
19
|
+
<ArticleLayout>
|
|
20
|
+
{asideContent}
|
|
21
|
+
{bodySlots?.["article.aside"] ? (
|
|
22
|
+
<div
|
|
23
|
+
class="site-article__aside"
|
|
24
|
+
dangerouslySetInnerHTML={{ __html: bodySlots["article.aside"] }}
|
|
25
|
+
/>
|
|
26
|
+
) : null}
|
|
27
|
+
<ArticleContent>
|
|
28
|
+
{bodySlots?.["article.header"] ? (
|
|
29
|
+
<div
|
|
30
|
+
class="site-article__header"
|
|
31
|
+
dangerouslySetInnerHTML={{ __html: bodySlots["article.header"] }}
|
|
32
|
+
/>
|
|
33
|
+
) : null}
|
|
34
|
+
{bodySlots?.["article.metadata"] ? (
|
|
35
|
+
<div
|
|
36
|
+
class="site-article__metadata"
|
|
37
|
+
dangerouslySetInnerHTML={{ __html: bodySlots["article.metadata"] }}
|
|
38
|
+
/>
|
|
39
|
+
) : null}
|
|
40
|
+
{bodySlots?.["article.before-content"] ? (
|
|
41
|
+
<div
|
|
42
|
+
class="site-article__before-content"
|
|
43
|
+
dangerouslySetInnerHTML={{ __html: bodySlots["article.before-content"] }}
|
|
44
|
+
/>
|
|
45
|
+
) : null}
|
|
46
|
+
<div dangerouslySetInnerHTML={{ __html: post.html ?? "" }} />
|
|
47
|
+
{afterContent}
|
|
48
|
+
{bodySlots?.["article.after-content"] ? (
|
|
49
|
+
<div
|
|
50
|
+
class="site-article__after-content"
|
|
51
|
+
dangerouslySetInnerHTML={{ __html: bodySlots["article.after-content"] }}
|
|
52
|
+
/>
|
|
53
|
+
) : null}
|
|
54
|
+
</ArticleContent>
|
|
55
|
+
{bodySlots?.["article.footer"] || footerContent ? (
|
|
56
|
+
<div class="site-article__footer">
|
|
57
|
+
{bodySlots?.["article.footer"] ? (
|
|
58
|
+
<div dangerouslySetInnerHTML={{ __html: bodySlots["article.footer"] }} />
|
|
59
|
+
) : null}
|
|
60
|
+
{footerContent}
|
|
61
|
+
</div>
|
|
62
|
+
) : null}
|
|
63
|
+
</ArticleLayout>
|
|
64
|
+
</Article>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { getEntryLanguage } from "@riebeckite/core";
|
|
2
|
+
import {
|
|
3
|
+
contentRouteSsgParams,
|
|
4
|
+
pluginPageSsgParams,
|
|
5
|
+
resolveRiebeckiteRoute,
|
|
6
|
+
} from "@riebeckite/honox/server";
|
|
7
|
+
import { createRoute } from "honox/factory";
|
|
8
|
+
import { SiteArticle } from "../components/article";
|
|
9
|
+
import { content } from "../content";
|
|
10
|
+
|
|
11
|
+
export default createRoute(
|
|
12
|
+
contentRouteSsgParams("/:slug{.+}", async () => {
|
|
13
|
+
const manifest = await content.getManifest();
|
|
14
|
+
const contentPaths = manifest.publicEntries
|
|
15
|
+
.filter((entry) => entry.permalink !== "/")
|
|
16
|
+
.map((entry) => ({ slug: entry.permalink.replace(/^\/+/, "") }));
|
|
17
|
+
return [...contentPaths, ...(await pluginPageSsgParams(content))];
|
|
18
|
+
}),
|
|
19
|
+
async (c) => {
|
|
20
|
+
const requestedSlug = c.req.param("slug");
|
|
21
|
+
if (!requestedSlug) return c.notFound();
|
|
22
|
+
if (/\.[a-zA-Z0-9]+$/.test(requestedSlug)) return c.notFound();
|
|
23
|
+
|
|
24
|
+
const route = await resolveRiebeckiteRoute(content, c.req.path);
|
|
25
|
+
if (!route) return c.notFound();
|
|
26
|
+
if (route.kind === "redirect")
|
|
27
|
+
return c.redirect(route.location, route.status);
|
|
28
|
+
if (route.kind === "page") {
|
|
29
|
+
c.set("headTags", route.page.headTags ?? []);
|
|
30
|
+
c.set("htmlLanguage", route.page.language);
|
|
31
|
+
return c.render(
|
|
32
|
+
<div dangerouslySetInnerHTML={{ __html: route.page.body }} />,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const post = await content.getProcessedContent(route.entry.slug);
|
|
37
|
+
|
|
38
|
+
c.set("htmlLanguage", getEntryLanguage(route.entry));
|
|
39
|
+
c.set("headTags", route.entry.headTags ?? []);
|
|
40
|
+
|
|
41
|
+
return c.render(
|
|
42
|
+
<SiteArticle
|
|
43
|
+
post={post}
|
|
44
|
+
bodySlots={route.entry.bodySlots}
|
|
45
|
+
/>,
|
|
46
|
+
);
|
|
47
|
+
},
|
|
48
|
+
);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { PluginHeadTag } from "@riebeckite/core";
|
|
2
|
+
import { jsxRenderer } from "hono/jsx-renderer";
|
|
3
|
+
import { Link, Script } from "honox/server";
|
|
4
|
+
import { config } from "../config";
|
|
5
|
+
|
|
6
|
+
function themeAttributes() {
|
|
7
|
+
const { theme } = config;
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
...theme.attributes,
|
|
11
|
+
"data-theme": theme.colorMode === "system" ? undefined : theme.colorMode,
|
|
12
|
+
"data-theme-name": theme.name,
|
|
13
|
+
"data-typography": theme.typography,
|
|
14
|
+
"data-article-layout": theme.articleLayout,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Riebeckite plugin client entries need the HonoX client bundle even without
|
|
19
|
+
// an island component.
|
|
20
|
+
export const __importing_islands = true;
|
|
21
|
+
|
|
22
|
+
export default jsxRenderer(async ({ children }, c) => {
|
|
23
|
+
const headTags: readonly PluginHeadTag[] = c.get("headTags") ?? [];
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<html
|
|
27
|
+
lang={c.get("htmlLanguage") ?? config.site.locale}
|
|
28
|
+
{...themeAttributes()}
|
|
29
|
+
>
|
|
30
|
+
<head>
|
|
31
|
+
<meta charset="utf-8" />
|
|
32
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
33
|
+
<title>{config.site.title}</title>
|
|
34
|
+
<link rel="icon" href="/favicon.ico" />
|
|
35
|
+
<Link href="/app/style.css" rel="stylesheet" />
|
|
36
|
+
{headTags.map(renderHeadTag)}
|
|
37
|
+
<Script src="/app/client.ts" async />
|
|
38
|
+
</head>
|
|
39
|
+
<body class="riebeckite-page rb-site">
|
|
40
|
+
{children}
|
|
41
|
+
</body>
|
|
42
|
+
</html>
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
function renderHeadTag(tag: PluginHeadTag, index: number) {
|
|
47
|
+
const key = `${tag.tag}-${index}`;
|
|
48
|
+
if (tag.tag === "meta") return <meta {...tag.attrs} key={key} />;
|
|
49
|
+
if (tag.tag === "link") return <link {...tag.attrs} key={key} />;
|
|
50
|
+
return (
|
|
51
|
+
<script
|
|
52
|
+
{...tag.attrs}
|
|
53
|
+
key={key}
|
|
54
|
+
dangerouslySetInnerHTML={
|
|
55
|
+
tag.children ? { __html: tag.children } : undefined
|
|
56
|
+
}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { getEntryLanguage } from "@riebeckite/core";
|
|
2
|
+
import { createRoute } from "honox/factory";
|
|
3
|
+
import { SiteArticle } from "../components/article";
|
|
4
|
+
import { content } from "../content";
|
|
5
|
+
|
|
6
|
+
export default createRoute(async (c) => {
|
|
7
|
+
const manifest = await content.getManifest();
|
|
8
|
+
const indexEntry = manifest.bySlug.get("index");
|
|
9
|
+
if (indexEntry && indexEntry.permalink !== "/") {
|
|
10
|
+
return c.redirect(indexEntry.permalink, 308);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const post = await content.getProcessedContent("index");
|
|
14
|
+
if (indexEntry?.publishing?.routable === false) {
|
|
15
|
+
return c.notFound();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (indexEntry) {
|
|
19
|
+
c.set("htmlLanguage", getEntryLanguage(indexEntry));
|
|
20
|
+
c.set("headTags", indexEntry.headTags ?? []);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return c.render(
|
|
24
|
+
<SiteArticle
|
|
25
|
+
post={post}
|
|
26
|
+
bodySlots={indexEntry?.bodySlots}
|
|
27
|
+
/>,
|
|
28
|
+
);
|
|
29
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
@import "./.riebeckite/plugin-styles.css";
|
|
2
|
+
@import "./.riebeckite/theme-styles.css";
|
|
3
|
+
|
|
4
|
+
*,
|
|
5
|
+
*::before,
|
|
6
|
+
*::after {
|
|
7
|
+
box-sizing: border-box;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
margin: 0;
|
|
12
|
+
font-family: system-ui, sans-serif;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.riebeckite-page {
|
|
16
|
+
display: flex;
|
|
17
|
+
min-height: 100vh;
|
|
18
|
+
width: 100%;
|
|
19
|
+
flex-direction: column;
|
|
20
|
+
align-items: center;
|
|
21
|
+
padding-block: var(--rb-space-8, 4rem);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.site-article {
|
|
25
|
+
display: block;
|
|
26
|
+
width: min(100% - 2rem, var(--rb-layout-article-max, 48rem));
|
|
27
|
+
padding-block: 2rem;
|
|
28
|
+
margin: 0 auto;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.rb-article-layout {
|
|
32
|
+
display: grid;
|
|
33
|
+
grid-template-columns: minmax(0, var(--rb-layout-article-max, 48rem));
|
|
34
|
+
justify-content: center;
|
|
35
|
+
gap: var(--rb-layout-gap, 2rem);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.rb-article-body {
|
|
39
|
+
min-width: 0;
|
|
40
|
+
padding-inline: var(--rb-space-2, 1rem);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.site-article__aside {
|
|
44
|
+
display: none;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.site-article__footer {
|
|
48
|
+
min-width: 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@media (min-width: 88rem) {
|
|
52
|
+
:is(:root, .rb-theme-root)[data-article-layout="article"] .rb-article-layout {
|
|
53
|
+
grid-template-columns: minmax(0, var(--rb-layout-article-max, 48rem));
|
|
54
|
+
position: relative;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
:is(:root, .rb-theme-root)[data-article-layout="article"] .rb-article-body {
|
|
58
|
+
grid-column: 1;
|
|
59
|
+
grid-row: 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
:is(:root, .rb-theme-root)[data-article-layout="article"] .site-article__footer {
|
|
63
|
+
grid-column: 1;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
:is(:root, .rb-theme-root)[data-article-layout="article"] .site-article__aside {
|
|
67
|
+
display: block;
|
|
68
|
+
grid-column: 1;
|
|
69
|
+
grid-row: 1;
|
|
70
|
+
position: sticky;
|
|
71
|
+
top: 4rem;
|
|
72
|
+
align-self: start;
|
|
73
|
+
width: var(--rb-layout-sidebar, 14rem);
|
|
74
|
+
max-height: calc(100dvh - 8rem);
|
|
75
|
+
margin-inline-start: calc(
|
|
76
|
+
-1 * var(--rb-layout-sidebar, 14rem) -
|
|
77
|
+
var(--rb-layout-gap, 2rem)
|
|
78
|
+
);
|
|
79
|
+
overflow: auto;
|
|
80
|
+
padding-inline-end: 1rem;
|
|
81
|
+
border-inline-end: 1px solid
|
|
82
|
+
color-mix(in oklab, currentColor 18%, transparent);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
publish: true
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# {{title}}
|
|
6
|
+
Welcome to your Riebeckite site.
|
|
7
|
+
Every page of this starter is available in seven languages. Switch with the selector below the page title.
|
|
8
|
+
## What is Riebeckite?
|
|
9
|
+
Riebeckite is an extensible, content-first framework that builds fast static sites from plain Markdown — the same notes you keep in Obsidian. The ecosystem includes 50+ plugins and six themes, and this site demos both.
|
|
10
|
+
## Edit this site
|
|
11
|
+
Content lives in `content/` as plain Markdown. Add a file, give it `publish: true` in the frontmatter, and it appears in the built site.
|
|
12
|
+
Localized pages use the `<base>.<lang>.md` convention next to the default file — for example `about.ja.md`. The l10n plugin serves them under `/lang/` paths and links them automatically.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { PostContent } from "@riebeckite/core";
|
|
2
|
+
import { Article, ArticleContent, ArticleLayout } from "@riebeckite/honox/ui";
|
|
3
|
+
|
|
4
|
+
export function SiteArticle({
|
|
5
|
+
post,
|
|
6
|
+
bodySlots,
|
|
7
|
+
asideContent,
|
|
8
|
+
afterContent,
|
|
9
|
+
footerContent,
|
|
10
|
+
}: {
|
|
11
|
+
post: PostContent;
|
|
12
|
+
bodySlots?: Readonly<Record<string, string>>;
|
|
13
|
+
asideContent?: unknown;
|
|
14
|
+
afterContent?: unknown;
|
|
15
|
+
footerContent?: unknown;
|
|
16
|
+
}) {
|
|
17
|
+
return (
|
|
18
|
+
<Article class="site-article">
|
|
19
|
+
<ArticleLayout>
|
|
20
|
+
{asideContent}
|
|
21
|
+
{bodySlots?.["article.aside"] ? (
|
|
22
|
+
<div
|
|
23
|
+
class="site-article__aside"
|
|
24
|
+
dangerouslySetInnerHTML={{ __html: bodySlots["article.aside"] }}
|
|
25
|
+
/>
|
|
26
|
+
) : null}
|
|
27
|
+
<ArticleContent>
|
|
28
|
+
{bodySlots?.["article.header"] ? (
|
|
29
|
+
<div
|
|
30
|
+
class="site-article__header"
|
|
31
|
+
dangerouslySetInnerHTML={{ __html: bodySlots["article.header"] }}
|
|
32
|
+
/>
|
|
33
|
+
) : null}
|
|
34
|
+
{bodySlots?.["article.metadata"] ? (
|
|
35
|
+
<div
|
|
36
|
+
class="site-article__metadata"
|
|
37
|
+
dangerouslySetInnerHTML={{ __html: bodySlots["article.metadata"] }}
|
|
38
|
+
/>
|
|
39
|
+
) : null}
|
|
40
|
+
{bodySlots?.["article.before-content"] ? (
|
|
41
|
+
<div
|
|
42
|
+
class="site-article__before-content"
|
|
43
|
+
dangerouslySetInnerHTML={{ __html: bodySlots["article.before-content"] }}
|
|
44
|
+
/>
|
|
45
|
+
) : null}
|
|
46
|
+
<div dangerouslySetInnerHTML={{ __html: post.html ?? "" }} />
|
|
47
|
+
{afterContent}
|
|
48
|
+
{bodySlots?.["article.after-content"] ? (
|
|
49
|
+
<div
|
|
50
|
+
class="site-article__after-content"
|
|
51
|
+
dangerouslySetInnerHTML={{ __html: bodySlots["article.after-content"] }}
|
|
52
|
+
/>
|
|
53
|
+
) : null}
|
|
54
|
+
</ArticleContent>
|
|
55
|
+
{bodySlots?.["article.footer"] || footerContent ? (
|
|
56
|
+
<div class="site-article__footer">
|
|
57
|
+
{bodySlots?.["article.footer"] ? (
|
|
58
|
+
<div dangerouslySetInnerHTML={{ __html: bodySlots["article.footer"] }} />
|
|
59
|
+
) : null}
|
|
60
|
+
{footerContent}
|
|
61
|
+
</div>
|
|
62
|
+
) : null}
|
|
63
|
+
</ArticleLayout>
|
|
64
|
+
</Article>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { NavigationItem } from "@riebeckite/plugin-navigation";
|
|
2
|
+
import { ColorModeToggle } from "@riebeckite/plugin-color-mode";
|
|
3
|
+
import { config } from "../config";
|
|
4
|
+
|
|
5
|
+
export function SiteHeader({
|
|
6
|
+
path,
|
|
7
|
+
items,
|
|
8
|
+
}: {
|
|
9
|
+
path: string;
|
|
10
|
+
items: readonly NavigationItem[];
|
|
11
|
+
}) {
|
|
12
|
+
const navigation = items.filter((item) => item.href !== "/");
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<header class="site-header rb-site-header">
|
|
16
|
+
<a href="/" class="site-header__home rb-site-header__home">
|
|
17
|
+
<img
|
|
18
|
+
src="/riebeckite-logo.png"
|
|
19
|
+
alt=""
|
|
20
|
+
class="site-header__logo"
|
|
21
|
+
width="28"
|
|
22
|
+
height="28"
|
|
23
|
+
/>
|
|
24
|
+
{config.site.title}
|
|
25
|
+
</a>
|
|
26
|
+
{navigation.length > 0 ? (
|
|
27
|
+
<>
|
|
28
|
+
<SiteNavigation items={navigation} path={path} />
|
|
29
|
+
<details class="site-navigation__mobile rb-nav__mobile">
|
|
30
|
+
<summary class="rb-nav__toggle">Menu</summary>
|
|
31
|
+
<SiteNavigation items={navigation} path={path} />
|
|
32
|
+
</details>
|
|
33
|
+
</>
|
|
34
|
+
) : null}
|
|
35
|
+
<ColorModeToggle />
|
|
36
|
+
</header>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function SiteFooter({
|
|
41
|
+
path,
|
|
42
|
+
items,
|
|
43
|
+
}: {
|
|
44
|
+
path: string;
|
|
45
|
+
items: readonly NavigationItem[];
|
|
46
|
+
}) {
|
|
47
|
+
if (items.length === 0) return null;
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<footer class="site-footer rb-site-footer">
|
|
51
|
+
<SiteNavigation items={items} path={path} />
|
|
52
|
+
</footer>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function SiteNavigation({
|
|
57
|
+
items,
|
|
58
|
+
path,
|
|
59
|
+
}: {
|
|
60
|
+
items: readonly NavigationItem[];
|
|
61
|
+
path: string;
|
|
62
|
+
}) {
|
|
63
|
+
return (
|
|
64
|
+
<nav class="site-navigation rb-nav" aria-label="Site navigation">
|
|
65
|
+
<NavigationItems items={items} path={path} />
|
|
66
|
+
</nav>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function NavigationItems({
|
|
71
|
+
items,
|
|
72
|
+
path,
|
|
73
|
+
isChildList = false,
|
|
74
|
+
}: {
|
|
75
|
+
items: readonly NavigationItem[];
|
|
76
|
+
path: string;
|
|
77
|
+
isChildList?: boolean;
|
|
78
|
+
}) {
|
|
79
|
+
return (
|
|
80
|
+
<ul class={isChildList ? "site-navigation__list rb-nav__list rb-nav__children" : "site-navigation__list rb-nav__list"}>
|
|
81
|
+
{items.map((item) => {
|
|
82
|
+
const active = item.href ? isActive(item.href, path) : false;
|
|
83
|
+
return (
|
|
84
|
+
<li class="site-navigation__item rb-nav__item">
|
|
85
|
+
{item.href ? (
|
|
86
|
+
<a
|
|
87
|
+
href={item.href}
|
|
88
|
+
class={active ? "site-navigation__link rb-nav__link rb-nav__link--active is-active" : "site-navigation__link rb-nav__link"}
|
|
89
|
+
aria-current={active ? "page" : undefined}
|
|
90
|
+
target={item.external ? "_blank" : undefined}
|
|
91
|
+
rel={item.external ? "noreferrer" : undefined}
|
|
92
|
+
>
|
|
93
|
+
{item.label}
|
|
94
|
+
</a>
|
|
95
|
+
) : (
|
|
96
|
+
<span class="site-navigation__label rb-nav__label">{item.label}</span>
|
|
97
|
+
)}
|
|
98
|
+
{item.children && item.children.length > 0 ? (
|
|
99
|
+
<NavigationItems items={item.children} path={path} isChildList />
|
|
100
|
+
) : null}
|
|
101
|
+
</li>
|
|
102
|
+
);
|
|
103
|
+
})}
|
|
104
|
+
</ul>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isActive(href: string, path: string): boolean {
|
|
109
|
+
if (!href.startsWith("/")) return false;
|
|
110
|
+
const target = href.replace(/\/+$/, "") || "/";
|
|
111
|
+
const current = path.replace(/^\/[a-z]{2}(?:-[A-Z]{2})?(?=\/|$)/, "").replace(/\/+$/, "") || "/";
|
|
112
|
+
return target === "/" ? current === target : current === target || current.startsWith(`${target}/`);
|
|
113
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { getEntryLanguage } from "@riebeckite/core";
|
|
2
|
+
import {
|
|
3
|
+
contentRouteSsgParams,
|
|
4
|
+
pluginPageSsgParams,
|
|
5
|
+
resolveRiebeckiteRoute,
|
|
6
|
+
} from "@riebeckite/honox/server";
|
|
7
|
+
import { extractTableOfContents, TableOfContents } from "@riebeckite/plugin-toc";
|
|
8
|
+
import { createRoute } from "honox/factory";
|
|
9
|
+
import { SiteArticle } from "../components/article";
|
|
10
|
+
import { content } from "../content";
|
|
11
|
+
|
|
12
|
+
export default createRoute(
|
|
13
|
+
contentRouteSsgParams("/:slug{.+}", async () => {
|
|
14
|
+
const manifest = await content.getManifest();
|
|
15
|
+
const contentPaths = manifest.publicEntries
|
|
16
|
+
.filter((entry) => entry.permalink !== "/")
|
|
17
|
+
.map((entry) => ({ slug: entry.permalink.replace(/^\/+/, "") }));
|
|
18
|
+
return [...contentPaths, ...(await pluginPageSsgParams(content))];
|
|
19
|
+
}),
|
|
20
|
+
async (c) => {
|
|
21
|
+
const requestedSlug = c.req.param("slug");
|
|
22
|
+
if (!requestedSlug) return c.notFound();
|
|
23
|
+
if (/\.[a-zA-Z0-9]+$/.test(requestedSlug)) return c.notFound();
|
|
24
|
+
|
|
25
|
+
const route = await resolveRiebeckiteRoute(content, c.req.path);
|
|
26
|
+
if (!route) return c.notFound();
|
|
27
|
+
if (route.kind === "redirect")
|
|
28
|
+
return c.redirect(route.location, route.status);
|
|
29
|
+
if (route.kind === "page") {
|
|
30
|
+
c.set("headTags", route.page.headTags ?? []);
|
|
31
|
+
c.set("htmlLanguage", route.page.language);
|
|
32
|
+
return c.render(
|
|
33
|
+
<div dangerouslySetInnerHTML={{ __html: route.page.body }} />,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const post = await content.getProcessedContent(route.entry.slug);
|
|
38
|
+
|
|
39
|
+
const tableOfContents = extractTableOfContents(post.html ?? "");
|
|
40
|
+
|
|
41
|
+
c.set("htmlLanguage", getEntryLanguage(route.entry));
|
|
42
|
+
c.set("headTags", route.entry.headTags ?? []);
|
|
43
|
+
|
|
44
|
+
return c.render(
|
|
45
|
+
<SiteArticle
|
|
46
|
+
post={post}
|
|
47
|
+
bodySlots={route.entry.bodySlots}
|
|
48
|
+
asideContent={<TableOfContents className="table-of-contents--desktop" items={tableOfContents} />}
|
|
49
|
+
/>,
|
|
50
|
+
);
|
|
51
|
+
},
|
|
52
|
+
);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { PluginHeadTag } from "@riebeckite/core";
|
|
2
|
+
import { jsxRenderer } from "hono/jsx-renderer";
|
|
3
|
+
import { Link, Script } from "honox/server";
|
|
4
|
+
import { ColorModeScript } from "@riebeckite/plugin-color-mode";
|
|
5
|
+
import { SearchBar } from "@riebeckite/plugin-search";
|
|
6
|
+
import { resolveSiteNavigation } from "@riebeckite/plugin-navigation";
|
|
7
|
+
import { SiteFooter, SiteHeader } from "../components/site-header";
|
|
8
|
+
import { content } from "../content";
|
|
9
|
+
import { config } from "../config";
|
|
10
|
+
|
|
11
|
+
function themeAttributes() {
|
|
12
|
+
const { theme } = config;
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
...theme.attributes,
|
|
16
|
+
"data-theme": theme.colorMode === "system" ? undefined : theme.colorMode,
|
|
17
|
+
"data-theme-name": theme.name,
|
|
18
|
+
"data-typography": theme.typography,
|
|
19
|
+
"data-article-layout": theme.articleLayout,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Riebeckite plugin client entries need the HonoX client bundle even without
|
|
24
|
+
// an island component.
|
|
25
|
+
export const __importing_islands = true;
|
|
26
|
+
|
|
27
|
+
export default jsxRenderer(async ({ children }, c) => {
|
|
28
|
+
const headTags: readonly PluginHeadTag[] = c.get("headTags") ?? [];
|
|
29
|
+
const navigation = resolveSiteNavigation(config, await content.getManifest(), c.get("htmlLanguage")) ?? { primary: [], secondary: [] };
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<html
|
|
33
|
+
lang={c.get("htmlLanguage") ?? config.site.locale}
|
|
34
|
+
{...themeAttributes()}
|
|
35
|
+
>
|
|
36
|
+
<head>
|
|
37
|
+
<meta charset="utf-8" />
|
|
38
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
39
|
+
<title>{config.site.title}</title>
|
|
40
|
+
<link rel="icon" href="/favicon.ico" />
|
|
41
|
+
<ColorModeScript />
|
|
42
|
+
<Link href="/app/style.css" rel="stylesheet" />
|
|
43
|
+
{headTags.map(renderHeadTag)}
|
|
44
|
+
<Script src="/app/client.ts" async />
|
|
45
|
+
</head>
|
|
46
|
+
<body class="riebeckite-page rb-site">
|
|
47
|
+
<SiteHeader path={c.req.path} items={navigation.primary} />
|
|
48
|
+
<SearchBar />
|
|
49
|
+
{children}
|
|
50
|
+
<SiteFooter path={c.req.path} items={navigation.secondary} />
|
|
51
|
+
</body>
|
|
52
|
+
</html>
|
|
53
|
+
);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function renderHeadTag(tag: PluginHeadTag, index: number) {
|
|
57
|
+
const key = `${tag.tag}-${index}`;
|
|
58
|
+
if (tag.tag === "meta") return <meta {...tag.attrs} key={key} />;
|
|
59
|
+
if (tag.tag === "link") return <link {...tag.attrs} key={key} />;
|
|
60
|
+
return (
|
|
61
|
+
<script
|
|
62
|
+
{...tag.attrs}
|
|
63
|
+
key={key}
|
|
64
|
+
dangerouslySetInnerHTML={
|
|
65
|
+
tag.children ? { __html: tag.children } : undefined
|
|
66
|
+
}
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getEntryLanguage } from "@riebeckite/core";
|
|
2
|
+
import { createRoute } from "honox/factory";
|
|
3
|
+
import { DailyNotes, getDailyNotes } from "@riebeckite/plugin-daily-notes";
|
|
4
|
+
import { RecentPosts, getRecentPosts } from "@riebeckite/plugin-recent-posts";
|
|
5
|
+
import { extractTableOfContents, TableOfContents } from "@riebeckite/plugin-toc";
|
|
6
|
+
import { SiteArticle } from "../components/article";
|
|
7
|
+
import { config } from "../config";
|
|
8
|
+
import { content } from "../content";
|
|
9
|
+
|
|
10
|
+
export default createRoute(async (c) => {
|
|
11
|
+
const manifest = await content.getManifest();
|
|
12
|
+
const indexEntry = manifest.bySlug.get("index");
|
|
13
|
+
if (indexEntry && indexEntry.permalink !== "/") {
|
|
14
|
+
return c.redirect(indexEntry.permalink, 308);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const post = await content.getProcessedContent("index");
|
|
18
|
+
if (indexEntry?.publishing?.routable === false) {
|
|
19
|
+
return c.notFound();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const recentPosts = await getRecentPosts({ posts: manifest.discoverableEntries, config, getProcessedContent: (slug) => content.getProcessedContent(slug), resolveTitle: (slug, title) => typeof title === "string" && title.trim() ? title : slug.split("/").at(-1) ?? slug });
|
|
23
|
+
const dailyNotes = getDailyNotes({ manifest, config });
|
|
24
|
+
const tableOfContents = extractTableOfContents(post.html ?? "");
|
|
25
|
+
|
|
26
|
+
if (indexEntry) {
|
|
27
|
+
c.set("htmlLanguage", getEntryLanguage(indexEntry));
|
|
28
|
+
c.set("headTags", indexEntry.headTags ?? []);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return c.render(
|
|
32
|
+
<SiteArticle
|
|
33
|
+
post={post}
|
|
34
|
+
bodySlots={indexEntry?.bodySlots}
|
|
35
|
+
asideContent={<TableOfContents className="table-of-contents--desktop" items={tableOfContents} />}
|
|
36
|
+
afterContent={<><RecentPosts posts={recentPosts} /><DailyNotes notes={dailyNotes} /></>}
|
|
37
|
+
/>,
|
|
38
|
+
);
|
|
39
|
+
});
|