-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Summary: * Add separate feeds for notes and articles * Add feed with articles excerpt only * Add feed with articles excerpts and complete notes * Add new feeds to `size-limit`
- Loading branch information
Showing
4 changed files
with
182 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,6 @@ | |
}, | ||
{ | ||
"name": "Feeds", | ||
"path": ["public/feed.xml"] | ||
"path": ["public/feed.xml", "public/feed-*.xml"] | ||
} | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,99 @@ | ||
import path from 'path' | ||
import { writeFileSync } from 'fs' | ||
import { Feed } from 'feed' | ||
import { createContentLoader } from 'vitepress' | ||
import { isArticle, isNote } from './utils/content-type.mjs' | ||
import { comparePublicationDate, isPublished } from './utils/frontmatter.mjs' | ||
import { baseFeedOptions, compareItemsDates, feedItem, writeFeed } from './utils/rss.mjs' | ||
|
||
/** @todo: should come from .env */ | ||
const APP_URL = `https://blog.mehdi.cc` | ||
|
||
/** @param {import('vitepress').SiteConfig} config */ | ||
export async function rss(config) { | ||
const feedOptions = baseFeedOptions(config) | ||
|
||
// Load content and turn it into feed items. | ||
|
||
/** @type {import('vitepress').ContentData[]} */ | ||
const content = (await createContentLoader(['articles/*.md', 'notes/*.md'], { excerpt: true, render: true }).load()) | ||
.filter(isPublished) | ||
.toSorted(comparePublicationDate) | ||
|
||
const notesItems = content.filter(isNote).map(feedItem) | ||
const articlesItems = content.filter(isArticle).map(feedItem) | ||
const articlesItemsExcerptOnly = content | ||
.filter(isArticle) | ||
.map(content => feedItem(content, { content: content.excerpt })) | ||
|
||
/** | ||
* https://www.rssboard.org/rss-profile | ||
* https://github.com/jpmonette/feed | ||
* Generate all feeds and store them on disk. | ||
* | ||
* - spec: https://www.rssboard.org/rss-specification | ||
* - spec best practices: https://www.rssboard.org/rss-profile | ||
* - `feed` package: https://github.com/jpmonette/feed | ||
*/ | ||
const feed = new Feed({ | ||
docs: 'https://www.rssboard.org/rss-specification', | ||
link: APP_URL + 'link', | ||
title: config.site.title, | ||
|
||
// Feed 1: full content | ||
|
||
const feedWithEverything = new Feed({ | ||
title: 'Mehdi’s notes and articles', | ||
description: config.site.description, | ||
language: config.site.lang, | ||
// image: 'https://blog.mehdi.cc/file.png', | ||
// favicon: `${APP_URL}/favicon.ico`, | ||
copyright: 'Copyright © 2023-present, Mehdi Merah', | ||
feed: `${APP_URL}/feed.xml`, | ||
ttl: 2880, // 1 day, | ||
}); | ||
...feedOptions, | ||
}) | ||
|
||
(await createContentLoader(['articles/*.md', 'notes/*.md'], { excerpt: true, render: true }).load()) | ||
.filter(isPublished) | ||
.toSorted(comparePublicationDate) | ||
.forEach(({ url, excerpt, frontmatter, html }) => | ||
feed.addItem({ | ||
title: frontmatter.title, | ||
id: `${APP_URL}${url}`, | ||
link: `${APP_URL}${url}`, | ||
description: frontmatter.description || excerpt, | ||
content: html, | ||
date: frontmatter.publishedAt, | ||
author: [{ | ||
name: 'Mehdi Merah', | ||
link: 'https://mehdi.cc', | ||
email: ' ', // hack, otherwise <author> is missing in RSS | ||
}], | ||
}) | ||
) | ||
|
||
writeFileSync(path.join(config.outDir, 'feed.xml'), feed.rss2()) | ||
feedWithEverything.items = [...notesItems, ...articlesItems].toSorted(compareItemsDates) | ||
|
||
writeFeed('feed', feedWithEverything) | ||
|
||
// Feed 2: notes | ||
|
||
const feedWithNotes = new Feed({ | ||
title: 'Mehdi’s notes', | ||
description: 'A chronological gathering of… notes.', | ||
feed: `${APP_URL}/feed-notes-only.xml`, | ||
...feedOptions, | ||
}) | ||
|
||
feedWithNotes.items = notesItems | ||
|
||
writeFeed('feed-notes-only', feedWithNotes) | ||
|
||
// Feed 3: articles | ||
|
||
const feedWithArticles = new Feed({ | ||
title: 'Mehdi’s articles', | ||
description: 'A chronological gathering of… articles.', | ||
feed: `${APP_URL}/feed-articles-only.xml`, | ||
...feedOptions, | ||
}) | ||
|
||
feedWithArticles.items = articlesItems | ||
|
||
writeFeed('feed-articles-only', feedWithArticles) | ||
|
||
// Feed 4: articles excerpts | ||
|
||
const feedWithArticlesExcerpts = new Feed({ | ||
title: 'Mehdi’s articles (excerpts only)', | ||
description: 'Excerpt of my articles.', | ||
feed: `${APP_URL}/feed-articles-excerpts-only.xml`, | ||
...feedOptions, | ||
}) | ||
|
||
feedWithArticlesExcerpts.items = articlesItemsExcerptOnly | ||
|
||
writeFeed('feed-articles-excerpts-only', feedWithArticlesExcerpts) | ||
|
||
// Feed 5: articles excerpts and notes | ||
|
||
const feedWithArticlesExcerptsAndNotes = new Feed({ | ||
title: 'Mehdi’s light feed', | ||
description: 'Articles excerpts and notes.', | ||
feed: `${APP_URL}/feed-articles-excerpts-and-notes.xml`, | ||
...feedOptions, | ||
}) | ||
|
||
feedWithArticlesExcerptsAndNotes.items = [...notesItems, ...articlesItemsExcerptOnly].toSorted(compareItemsDates) | ||
|
||
writeFeed('feed-articles-excerpts-and-notes', feedWithArticlesExcerptsAndNotes) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/** @typedef {import('vitepress').ContentData} ContentData */ | ||
|
||
/** | ||
* Check the type of content based on URL. | ||
* | ||
* @param {ContentData} content | ||
* @param {string} type | ||
*/ | ||
const isContentType = ({ url }, type) => url.startsWith(`/${type}s/`) | ||
|
||
/** | ||
* The content is an article. | ||
* | ||
* @param {ContentData} content | ||
*/ | ||
export const isArticle = content => isContentType(content, 'article') | ||
|
||
/** | ||
* The content is a note. | ||
* | ||
* @param {ContentData} content | ||
*/ | ||
export const isNote = content => isContentType(content, 'note') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import path from 'path' | ||
import { writeFileSync } from 'fs' | ||
import { Feed } from 'feed' | ||
|
||
/** @typedef {import('feed').FeedOptions} FeedOptions */ | ||
/** @typedef {import('feed').Item} Item */ | ||
/** @typedef {import('vitepress').ContentData} ContentData */ | ||
/** @typedef {import('vitepress').SiteConfig} SiteConfig */ | ||
|
||
/** @todo: should come from .env */ | ||
const APP_URL = `https://blog.mehdi.cc` | ||
|
||
/** @type SiteConfig */ | ||
let config = null | ||
|
||
/** | ||
* Compare frontmatter publication date | ||
* @param {Item} a | ||
* @param {Item} b | ||
*/ | ||
export const compareItemsDates = (a, b) => new Date(b.date) - new Date(a.date) | ||
|
||
/** | ||
* @param {SiteConfig} config | ||
* @returns {FeedOptions} | ||
*/ | ||
export const baseFeedOptions = siteConfig => { | ||
if (!config) { | ||
config = { ...siteConfig } | ||
} | ||
|
||
return { | ||
docs: 'https://www.rssboard.org/rss-specification', | ||
link: APP_URL, | ||
language: config.site.lang, | ||
// image: 'https://blog.mehdi.cc/file.png', | ||
// favicon: `${APP_URL}/favicon.ico`, | ||
copyright: 'Copyright © 2023-present, Mehdi Merah', | ||
ttl: 2880, // 1 day, | ||
} | ||
} | ||
|
||
/** | ||
* @param {ContentData} content | ||
* @param {Item?} overrides | ||
* @returns {Item} | ||
*/ | ||
export const feedItem = ({ url, excerpt, frontmatter, html }, { content } = null) => ({ | ||
title: frontmatter.title, | ||
id: `${APP_URL}${url}`, | ||
link: `${APP_URL}${url}`, | ||
description: frontmatter.description || excerpt, | ||
content: content ?? html, | ||
date: frontmatter.publishedAt, | ||
author, | ||
}) | ||
|
||
/** @type {import('feed').Author[]} */ | ||
export const author = [{ | ||
name: 'Mehdi Merah', | ||
link: 'https://mehdi.cc', | ||
email: '[email protected]', | ||
}] | ||
|
||
/** | ||
* Save a feed on the file system. | ||
* | ||
* @param {string} filename The name of the feed without file extension. | ||
* @param {Feed} feed | ||
*/ | ||
export const writeFeed = (filename, feed) => writeFileSync( | ||
path.join(config.outDir, `${filename}.xml`), | ||
feed.rss2(), | ||
) |