Skip to content

Commit

Permalink
feat(feed): support atom & rss feed
Browse files Browse the repository at this point in the history
  • Loading branch information
Octobug committed Dec 30, 2023
1 parent c3f254e commit 9bd57ba
Show file tree
Hide file tree
Showing 12 changed files with 190 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .env.tmpl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
BASE_URL=/blog/
BASE_PATH=/blog/
# GISCUS Configs: generated from https://giscus.app/
GISCUS_REPO_ID=
GISCUS_CATEGORY_ID=
Expand Down
9 changes: 9 additions & 0 deletions .github/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ npm run dev
npm run build
```

### Atom Feed Preview

Feed contents are built with VitePress's [`buildEnd`](https://vitepress.dev/reference/site-config#buildend) build hook, so you need to run `build` & `preview` to see the results:

```sh
npm run build
npm run preview
```

There are several other npm script commands in the `scripts` part of [`package.json`](../package.json) that you might want to use.

### Visual Studio Code
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ on:
workflow_dispatch:

env:
BASE_URL: ${{ vars.BASE_URL }}
BASE_PATH: ${{ vars.BASE_PATH }}
GISCUS_REPO_ID: ${{ vars.GISCUS_REPO_ID }}
GISCUS_CATEGORY_ID: ${{ vars.GISCUS_CATEGORY_ID }}

Expand Down
9 changes: 5 additions & 4 deletions .vitepress/config.theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ export default {
text: "Acknowledgments",
link: "/acknowledgments",
},
// {
// text: "Atom Feed",
// link: "/atom.xml",
// },
{
text: "Atom Feed",
link: withBaseURL("/atom.xml"),
target: "_blank",
},
{
text: "Repository",
link: "https://github.com/Octobug/blog",
Expand Down
10 changes: 7 additions & 3 deletions .vitepress/config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { defineConfigWithTheme } from "vitepress";
import mdImageFigures from "markdown-it-image-figures";
import type { ThemeConfig } from "./theme/types/theme-config";
import themeConfig from "./config.theme";
import { BASE_URL, withBaseURL } from "./config.utils";
import { BASE_PATH, withBaseURL } from "./config.utils";
import gaConfig from "./theme/ganalytics";
import mdImageFigures from "markdown-it-image-figures";
import { buildFeed } from "./theme/feed";

export default defineConfigWithTheme<ThemeConfig>({
title: "WhaleVocal",
description: "Octobug's blog.",
base: BASE_URL,
base: BASE_PATH,
cleanUrls: true,
lastUpdated: true,
head: [
Expand Down Expand Up @@ -46,6 +47,9 @@ export default defineConfigWithTheme<ThemeConfig>({
"./README.md",
],
themeConfig,
buildEnd: async ({ outDir }) => {
await buildFeed(outDir);
},
markdown: {
config: (md) => {
// usage: ![alt](https://link-to-image 'title'){.class}
Expand Down
11 changes: 8 additions & 3 deletions .vitepress/config.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import "dotenv/config";
import { env } from "process";
import * as path from "path";

export const BASE_URL = env.BASE_URL || undefined;
export const BASE_PATH = env.BASE_PATH || undefined;

export const giscus = {
repo_id: env.GISCUS_REPO_ID || "",
Expand All @@ -12,8 +12,13 @@ export const giscus = {
export const gMeasurementID = env.G_MEASUREMENT_ID || "";

export function withBaseURL(urlPath: string) {
if (BASE_URL && urlPath.includes(BASE_URL)) {
if (BASE_PATH && urlPath.includes(BASE_PATH)) {
return urlPath;
}
return path.join(BASE_URL || "/", urlPath);
return path.join(BASE_PATH || "/", urlPath);
}

export function joinURL(baseURL: string, ...paths: string[]) {
const urlPath = path.join(...paths);
return new URL(urlPath, baseURL).href;
}
78 changes: 78 additions & 0 deletions .vitepress/theme/feed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import path from "path";
import { promises as fs } from "fs";
import { ContentData } from "vitepress";
import type { FeedOptions, Item } from "feed";
import { Feed } from "feed";
import themeConfig from "../config.theme";
import { BASE_PATH, withBaseURL, joinURL } from "../config.utils";
import postLoader from "./posts.loader";

const DOMAIN_PRODUCTION = "blog.octobug.site";
const DOMAIN_STAGING = "octobug.github.io";

const DOMAIN = BASE_PATH ? DOMAIN_STAGING : DOMAIN_PRODUCTION;
const BASE_URL = `https://${DOMAIN}${withBaseURL("/")}`;
const AUTHOR = {
name: "Octobug",
email: "[email protected]",
link: BASE_URL,
};

const FEED = {
ATOM: "atom.xml",
RSS: "rss.xml",
};

const OPTIONS: FeedOptions = {
title: "WhaleVocal",
description: "Octobug's Blog",
id: BASE_URL,
link: BASE_URL,
copyright: themeConfig.footer.copyright,
feedLinks: {
atom: joinURL(BASE_URL, FEED.ATOM),
rss: joinURL(BASE_URL, FEED.RSS),
},
author: AUTHOR,
image: joinURL(BASE_URL, "avatar.png"),
favicon: joinURL(BASE_URL, "avatar.png"),
};

export async function buildFeed(outDir: string) {
const posts = await generateContents();

const feed = new Feed(OPTIONS);
posts.forEach(item => feed.addItem(item));

await fs.writeFile(path.join(outDir, FEED.ATOM), feed.atom1(), "utf8");
await fs.writeFile(path.join(outDir, FEED.RSS), feed.rss2(), "utf8");
}

function withGitHubImages(post: ContentData) {
const baseURL = "https://raw.githubusercontent.com/";
const { frontmatter } = post;
const imgBaseURL = joinURL(baseURL, "Octobug/blog/main", frontmatter.mdpath);
return post.html?.replace(/<img src="./g, `<img src="${imgBaseURL}`);
}

async function generateContents(): Promise<Item[]> {
const loader = await postLoader({
render: true,
excerpt: true,
});
const allPosts = await loader.load();

return await Promise.all(
allPosts.map(async p => {
return {
title: p.frontmatter.title,
id: p.url,
link: joinURL(BASE_URL, p.url),
date: p.frontmatter.date,
content: withGitHubImages(p),
category: [p.frontmatter.sort].map(s => { return { name: s }; }),
author: [AUTHOR],
} satisfies Item;
})
);
}
32 changes: 5 additions & 27 deletions .vitepress/theme/posts.data.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,12 @@
// https://vitepress.dev/guide/data-loading
import { createContentLoader, ContentData } from "vitepress";
import readingTime from "reading-time";
import extendedConfig from "../config.theme";
import { withBaseURL } from "../config.utils";
import getLocation from "./locations";
import { ContentData } from "vitepress";
import postLoader from "./posts.loader";

declare const data: ContentData[];
export { data };

// Title Workaround
function extractTile(text: string) {
const titlePattern = /---\n\n# (?<title>.*)\n/;
const match = text.match(titlePattern);
return match?.groups?.title || "NonTitled";
}

export default createContentLoader(extendedConfig.mdfilePatterns, {
export default await postLoader({
includeSrc: true,
transform(rawData) {
return rawData.map(p => {
const rt = readingTime(p.src || "");
p.frontmatter.title = extractTile(p.src || "");
p.frontmatter.datetime = new Date(p.frontmatter.date);
p.frontmatter.location = getLocation(p.frontmatter.spot);
p.frontmatter.readingTime = rt.text;
p.frontmatter.words = rt.words;
p.url = withBaseURL(p.url.replace("/README", ""));
return p;
}).sort((a, b) => {
return b.frontmatter.datetime - a.frontmatter.datetime;
});
}
render: false,
excerpt: false,
});
43 changes: 43 additions & 0 deletions .vitepress/theme/posts.loader.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// https://vitepress.dev/guide/data-loading
import { createContentLoader } from "vitepress";
import readingTime from "reading-time";
import extendedConfig from "../config.theme";
import { withBaseURL } from "../config.utils";
import getLocation from "./locations";

// Title Workaround
function extractTile(text: string) {
const titlePattern = /---\n\n# (?<title>.*)\n/;
const match = text.match(titlePattern);
return match?.groups?.title || "NonTitled";
}

export default async (options) => {
const {
includeSrc = true,
render = false,
excerpt = false,
} = options;

return createContentLoader(extendedConfig.mdfilePatterns, {
includeSrc,
render,
excerpt,
transform(rawData) {
return rawData.map(p => {
const rt = readingTime(p.src || "");
const mdpath = p.url.replace("/README", "");
p.url = withBaseURL(mdpath);
p.frontmatter.title = extractTile(p.src || "");
p.frontmatter.datetime = new Date(p.frontmatter.date);
p.frontmatter.location = getLocation(p.frontmatter.spot);
p.frontmatter.readingTime = rt.text;
p.frontmatter.words = rt.words;
p.frontmatter.mdpath = mdpath;
return p;
}).sort((a, b) => {
return b.frontmatter.datetime - a.frontmatter.datetime;
});
}
});
};
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ This blog is powered by [VitePress](https://vitepress.dev/) with a customized th
- [x] Posts (Archives)
- [x] Sorts (Categories)
- [x] Tags
- [ ] Atom Feed
- [x] Atom Feed
- [x] Others
- [x] Local Search (supports Chinese)
- [x] [Google Analytics](https://analytics.google.com/)
Expand Down
31 changes: 31 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"dotenv": "^16.3.1",
"eslint": "^8.50.0",
"eslint-plugin-vue": "^9.17.0",
"feed": "^4.2.2",
"husky": "^8.0.3",
"lint-staged": "^15.0.2",
"markdown-it-image-figures": "^2.1.1",
Expand Down

0 comments on commit 9bd57ba

Please sign in to comment.