Skip to content

Commit fc3bf6f

Browse files
authored
feat(website): blog and rss setup. (#1803)
* feat(website): blog setup * fix typos * giscus setup
1 parent 2593039 commit fc3bf6f

35 files changed

+4205
-109
lines changed

tests/test-utils/examples/v3.0/github.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

website/.env

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
NEXT_PUBLIC_GISCUS_REPO=samchon/typia
2+
NEXT_PUBLIC_GISCUS_REPO_ID=R_kgDOHMk6Xg
3+
NEXT_PUBLIC_GISCUS_CATEGORY=Blog
4+
NEXT_PUBLIC_GISCUS_CATEGORY_ID=DIC_kwDOHMk6Xs4C5Bch
5+
NEXT_PUBLIC_GISCUS_MAPPING=pathname
6+
NEXT_PUBLIC_GISCUS_STRICT=0
7+
NEXT_PUBLIC_GISCUS_REACTIONS_ENABLED=1
8+
NEXT_PUBLIC_GISCUS_EMIT_METADATA=0
9+
NEXT_PUBLIC_GISCUS_INPUT_POSITION=bottom
10+
NEXT_PUBLIC_GISCUS_LANG=en

website/.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ node_modules/
44
out/
55
packages/
66
public/api/
7+
public/blog/rss.xml
78
public/compiler/
89
public/robots.txt
910
public/sitemap*.xml
1011
src/raw/
1112
typedoc-json/
1213

13-
src/compiler/PlaygroundExampleStorage.ts
14+
src/compiler/PlaygroundExampleStorage.ts
15+
!.env

website/build/blog.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
const CONTENT_DIR = path.join(__dirname, "..", "src", "content", "blog");
5+
const OUTPUT_DIR = path.join(__dirname, "..", "public", "blog");
6+
const SITE_URL = "https://typia.io";
7+
8+
const escapeXml = (value) =>
9+
value
10+
.replaceAll("&", "&")
11+
.replaceAll("<", "&lt;")
12+
.replaceAll(">", "&gt;")
13+
.replaceAll('"', "&quot;")
14+
.replaceAll("'", "&apos;");
15+
16+
const parseFrontmatter = (content) => {
17+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
18+
if (!match) return {};
19+
20+
const metadata = {};
21+
let currentKey = null;
22+
for (const rawLine of match[1].split(/\r?\n/)) {
23+
if (!rawLine.trim()) continue;
24+
25+
const listMatch = rawLine.match(/^\s*-\s+(.*)$/);
26+
if (listMatch && currentKey) {
27+
metadata[currentKey].push(listMatch[1].trim().replace(/^"|"$/g, ""));
28+
continue;
29+
}
30+
31+
const keyValueMatch = rawLine.match(/^\s*([A-Za-z0-9_]+):\s*(.*)$/);
32+
if (!keyValueMatch) continue;
33+
34+
const [, key, value] = keyValueMatch;
35+
if (value === "") {
36+
metadata[key] = [];
37+
currentKey = key;
38+
} else {
39+
metadata[key] = value.trim().replace(/^"|"$/g, "");
40+
currentKey = key;
41+
}
42+
}
43+
return metadata;
44+
};
45+
46+
const posts = fs
47+
.readdirSync(CONTENT_DIR)
48+
.filter((file) => file.endsWith(".md") || file.endsWith(".mdx"))
49+
.map((file) => {
50+
const fullPath = path.join(CONTENT_DIR, file);
51+
const source = fs.readFileSync(fullPath, "utf8");
52+
const metadata = parseFrontmatter(source);
53+
const slug = file.replace(/\.mdx?$/, "");
54+
return {
55+
title: metadata.title ?? slug,
56+
description: metadata.description ?? "",
57+
date: metadata.date ?? new Date().toISOString().slice(0, 10),
58+
slug,
59+
};
60+
})
61+
.sort((a, b) => new Date(b.date) - new Date(a.date));
62+
63+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
64+
<rss version="2.0">
65+
<channel>
66+
<title>Typia Blog</title>
67+
<link>${SITE_URL}/blog/</link>
68+
<description>Engineering notes, releases, and deep dives from Typia.</description>
69+
<language>en</language>
70+
${posts
71+
.map(
72+
(post) => ` <item>
73+
<title>${escapeXml(post.title)}</title>
74+
<link>${SITE_URL}/blog/${post.slug}/</link>
75+
<guid>${SITE_URL}/blog/${post.slug}/</guid>
76+
<pubDate>${new Date(post.date).toUTCString()}</pubDate>
77+
<description>${escapeXml(post.description)}</description>
78+
</item>`,
79+
)
80+
.join("\n")}
81+
</channel>
82+
</rss>
83+
`;
84+
85+
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
86+
fs.writeFileSync(path.join(OUTPUT_DIR, "rss.xml"), xml);

website/mdx-components-blog.jsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { useMDXComponents as getBlogMDXComponents } from "nextra-theme-blog";
2+
import GiscusComments from "./src/app/blog/GiscusComments";
3+
4+
function formatDate(value) {
5+
const date = value ? new Date(value) : null;
6+
return Number.isNaN(date?.getTime?.()) ? null : date;
7+
}
8+
9+
export function useMDXComponents(components) {
10+
const blogComponents = getBlogMDXComponents(components);
11+
12+
return {
13+
...blogComponents,
14+
wrapper({ children, metadata }) {
15+
const date = formatDate(metadata?.date);
16+
const tags = metadata?.tags ?? [];
17+
18+
return (
19+
<>
20+
{metadata?.ogImage ? (
21+
<img
22+
src={metadata.ogImage}
23+
alt={metadata.title ?? "Blog cover image"}
24+
className="typia-blog-hero"
25+
/>
26+
) : null}
27+
<h1>{metadata?.title}</h1>
28+
<div className="typia-blog-meta">
29+
{date ? <time dateTime={date.toISOString()}>{date.toLocaleDateString()}</time> : null}
30+
{metadata?.author ? <span>{metadata.author}</span> : null}
31+
{metadata?.devtoUrl ? (
32+
<a href={metadata.devtoUrl} target="_blank" rel="noreferrer">
33+
Original on DEV
34+
</a>
35+
) : null}
36+
</div>
37+
{tags.length ? (
38+
<div className="typia-blog-tags">
39+
{tags.map((tag) => (
40+
<span key={tag}>#{tag}</span>
41+
))}
42+
</div>
43+
) : null}
44+
{children}
45+
<GiscusComments />
46+
</>
47+
);
48+
},
49+
};
50+
}

website/next-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3+
/// <reference types="next/navigation-types/compat/navigation" />
34
/// <reference path="./.next/types/routes.d.ts" />
45

56
// NOTE: This file should not be edited

website/next.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ export default withNextra({
99
// ... Other Next.js config options
1010
output: "export",
1111
trailingSlash: true,
12+
eslint: {
13+
ignoreDuringBuilds: true,
14+
},
1215
images: {
1316
unoptimized: true,
1417
},

website/package.json

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
"description": "Typia Guide Documents",
66
"scripts": {
77
"build": "npm run prebuild && next build && npm run postbuild",
8-
"prebuild": "rimraf .next && rimraf out && npm run build:compiler && npm run build:typedoc",
8+
"prebuild": "rimraf .next && rimraf out && npm run build:compiler && npm run build:typedoc && npm run build:blog:rss",
99
"postbuild": "npm run build:pagefind && npm run build:sitemap",
10+
"build:blog:rss": "node build/blog",
1011
"build:compiler": "node build/compiler",
1112
"build:pagefind": "pagefind --site .next/server/app --output-path out/_pagefind",
1213
"build:sitemap": "node build/sitemap",
@@ -31,21 +32,22 @@
3132
"@mui/icons-material": "^5.15.4",
3233
"@mui/material": "^5.12.0",
3334
"@rollup/browser": "4.13.0",
35+
"@typia/core": "../experiments/tarballs/core.tgz",
36+
"@typia/interface": "../experiments/tarballs/interface.tgz",
37+
"@typia/transform": "../experiments/tarballs/transform.tgz",
3438
"embed-typescript": "^1.1.0",
3539
"lz-string": "^1.5.0",
3640
"monaco-editor": "^0.50.0",
3741
"next": "^15.3.4",
3842
"nextra": "^4.6.0",
43+
"nextra-theme-blog": "^4.6.1",
3944
"nextra-theme-docs": "^4.6.0",
4045
"path": "^0.12.7",
4146
"prettier": "^3.2.5",
4247
"react": "^18.2.0",
4348
"react-dom": "^18.2.0",
4449
"tgrid": "^1.0.3",
4550
"tstl": "^3.0.0",
46-
"@typia/interface": "../experiments/tarballs/interface.tgz",
47-
"@typia/core": "../experiments/tarballs/core.tgz",
48-
"@typia/transform": "../experiments/tarballs/transform.tgz",
4951
"typia": "../experiments/tarballs/typia.tgz"
5052
},
5153
"devDependencies": {

website/src/app/[[...mdxPath]]/page.jsx renamed to website/src/app/(docs)/[[...mdxPath]]/page.jsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
import { generateStaticParamsFor, importPage } from "nextra/pages";
22

3-
import { useMDXComponents as getMDXComponents } from "../../../mdx-components";
3+
import { useMDXComponents as getMDXComponents } from "../../../../mdx-components";
44

5-
export const generateStaticParams = generateStaticParamsFor("mdxPath");
5+
const generateAllStaticParams = generateStaticParamsFor("mdxPath");
6+
7+
export async function generateStaticParams() {
8+
const params = await generateAllStaticParams();
9+
return params.filter(({ mdxPath = [] }) => mdxPath[0] !== "blog");
10+
}
611

712
export async function generateMetadata(props) {
813
const params = await props.params;

website/src/app/(docs)/layout.jsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import "nextra-theme-docs/style.css";
2+
import { getPageMap } from "nextra/page-map";
3+
import { Footer, Layout, Navbar } from "nextra-theme-docs";
4+
5+
const footer = <Footer>
6+
Released under the MIT License.
7+
<br />
8+
<br />
9+
Copyright 2022 - {new Date().getFullYear()}{" "}
10+
<a
11+
href="https://github.com/samchon"
12+
target="_blank"
13+
style={{ color: "initial" }}
14+
>
15+
<u>Samchon</u>
16+
</a>{" "}
17+
& Contributors
18+
</Footer>;
19+
20+
export default async function DocsLayout(props) {
21+
return (
22+
<Layout
23+
navbar={
24+
<Navbar
25+
logo={<>
26+
<img src="/favicon/android-chrome-192x192.png" width={32} height={32} />
27+
<span
28+
style={{
29+
fontWeight: "bold",
30+
fontSize: "1.2rem",
31+
paddingLeft: 15,
32+
paddingRight: 10,
33+
}}
34+
>
35+
Typia
36+
</span>
37+
</>}
38+
projectLink="https://github.com/samchon/typia"
39+
>
40+
<a href="/blog/" style={{ fontWeight: 600 }}>
41+
Blog
42+
</a>
43+
</Navbar>
44+
}
45+
pageMap={await getPageMap()}
46+
docsRepositoryBase="https://github.com/samchon/typia/tree/master/website"
47+
editLink="Edit this page on GitHub"
48+
sidebar={{ autoCollapse: false }}
49+
nextThemes={{
50+
defaultTheme: "dark",
51+
}}
52+
darkMode={false}
53+
footer={footer}
54+
>
55+
{props.children}
56+
</Layout>
57+
);
58+
}

0 commit comments

Comments
 (0)