@rspress/plugin-rss 1.17.0 → 1.18.0

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/dist/index.cjs CHANGED
@@ -82,6 +82,9 @@ function toDate(s) {
82
82
  const d = new Date(s);
83
83
  return Number.isNaN(d.getDate()) ? null : d;
84
84
  }
85
+ function sortByDate(l, r) {
86
+ return (r ? r.getTime() : 0) - (l ? l.getTime() : 0);
87
+ }
85
88
 
86
89
  // src/internals/node.ts
87
90
  var import_promises = require("fs/promises");
@@ -189,7 +192,8 @@ function getOutputInfo({ id, output }, {
189
192
  const url = [publicPath, `${dir}/`, filename].reduce(
190
193
  (u, part) => u ? (0, import_node_url2.resolve)(u, part) : part
191
194
  );
192
- return { type, mime, filename, getContent, dir, publicPath, url };
195
+ const sorting = output?.sorting || globalOutput?.sorting || ((l, r) => sortByDate(l.date, r.date));
196
+ return { type, mime, filename, getContent, dir, publicPath, url, sorting };
193
197
  }
194
198
 
195
199
  // src/plugin-rss.ts
@@ -289,6 +293,7 @@ function pluginRss(pluginRssOptions) {
289
293
  }
290
294
  for (const [channel, feed] of Object.entries(feeds)) {
291
295
  const { output } = feedsSet.get(channel);
296
+ feed.items.sort(output.sorting);
292
297
  const path = import_node_path.default.resolve(
293
298
  config.outDir || "doc_build",
294
299
  output.dir,
@@ -1 +1 @@
1
- {"version":3,"mappings":"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","names":["import_node_url","_writeFile","resolveUrl","author","NodePath"],"ignoreList":[],"sources":["../src/index.ts","../src/plugin-rss.ts","../src/exports.ts","../src/feed.ts","../src/internals/lang.ts","../src/internals/node.ts","../src/options.ts"],"sourcesContent":["export * from './plugin-rss';\nexport * from './type';\nexport * from './exports';\nexport * from './options';\nexport * from './feed';\n","import NodePath from 'node:path';\nimport { resolve as resolveUrl } from 'node:url';\nimport type { PageIndexInfo, RspressPlugin, UserConfig } from '@rspress/shared';\nimport { Feed } from 'feed';\nimport { PluginComponents, PluginName } from './exports';\nimport { createFeed, generateFeedItem } from './feed';\n\nimport {\n PageWithFeeds,\n ResolvedOutput,\n concatArray,\n writeFile,\n} from './internals';\nimport { getDefaultFeedOption, getOutputInfo, testPage } from './options';\nimport type { FeedChannel, FeedItem, PluginRssOptions } from './type';\n\ntype FeedItemWithChannel = FeedItem & { channel: string };\ntype TransformedFeedChannel = FeedChannel & { output: ResolvedOutput };\n\nclass FeedsSet {\n feeds: TransformedFeedChannel[] = [];\n feedsMapById: Record<string, TransformedFeedChannel> = Object.create(null);\n set({ feed, output, siteUrl }: PluginRssOptions, config: UserConfig) {\n this.feeds = (\n Array.isArray(feed) ? feed : [{ ...getDefaultFeedOption(), ...feed }]\n ).map(options => ({\n title: config.title || '',\n description: config.description || '',\n favicon: config.icon && resolveUrl(siteUrl, config.icon),\n copyright: config.themeConfig?.footer?.message || '',\n link: siteUrl,\n docs: '',\n ...options,\n output: getOutputInfo(options, { siteUrl, output }),\n }));\n\n this.feedsMapById = this.feeds.reduce(\n (m, f) => ({ ...m, [f.id]: f }),\n Object.create(null),\n );\n }\n get(): TransformedFeedChannel[];\n get(id: string): TransformedFeedChannel | null;\n get(id?: string): TransformedFeedChannel[] | TransformedFeedChannel | null {\n if (id) {\n return this.feedsMapById[id] || null;\n }\n return this.feeds.slice(0);\n }\n}\n\nfunction getRssItems(\n feeds: TransformedFeedChannel[],\n page: PageIndexInfo,\n config: UserConfig,\n siteUrl: string,\n): Promise<FeedItemWithChannel[]> {\n return Promise.all(\n feeds\n .filter(options => testPage(options.test, page, config.base))\n .map(async options => {\n const after = options.item || ((feed: FeedItem) => feed);\n const item = await after(\n generateFeedItem(page, siteUrl),\n page,\n siteUrl,\n );\n return { ...item, channel: options.id };\n }),\n );\n}\n\nexport function pluginRss(pluginRssOptions: PluginRssOptions): RspressPlugin {\n const feedsSet = new FeedsSet();\n\n /**\n * workaround for retrieving data of pages in `afterBuild`\n * TODO: get pageData list directly in `afterBuild`\n **/\n let _rssWorkaround: null | Record<\n string,\n PromiseLike<FeedItemWithChannel[]>\n > = null;\n let _config: null | UserConfig;\n\n return {\n name: PluginName,\n globalUIComponents: Object.values(PluginComponents),\n beforeBuild(config, isProd) {\n if (!isProd) {\n _rssWorkaround = null;\n return;\n }\n _rssWorkaround = {};\n _config = config;\n feedsSet.set(pluginRssOptions, config);\n },\n async extendPageData(_pageData) {\n if (!_rssWorkaround) return;\n\n const pageData = _pageData as PageWithFeeds;\n\n // rspress run `extendPageData` for each page\n // - let's cache rss items within a complete rspress build\n _rssWorkaround[pageData.id] =\n _rssWorkaround[pageData.id] ||\n getRssItems(\n feedsSet.get(),\n pageData,\n _config!,\n pluginRssOptions.siteUrl,\n );\n\n const feeds = await _rssWorkaround[pageData.id];\n const showRssList = new Set(\n concatArray(pageData.frontmatter['link-rss'] as string[] | string),\n );\n for (const feed of feeds) {\n showRssList.add(feed.channel);\n }\n\n pageData.feeds = Array.from(showRssList, id => {\n const { output, language } = feedsSet.get(id)!;\n return {\n url: output.url,\n mime: output.mime,\n language: language || pageData.lang,\n };\n });\n },\n async afterBuild(config) {\n if (!_rssWorkaround) return;\n\n const items = concatArray(\n ...(await Promise.all(Object.values(_rssWorkaround))),\n );\n const feeds: Record<string, Feed> = Object.create(null);\n\n for (const { channel, ...item } of items) {\n feeds[channel] =\n feeds[channel] ||\n new Feed(createFeed(feedsSet.get(channel)!, config));\n feeds[channel].addItem(item);\n }\n\n for (const [channel, feed] of Object.entries(feeds)) {\n const { output } = feedsSet.get(channel)!;\n\n const path = NodePath.resolve(\n config.outDir || 'doc_build',\n output.dir,\n output.filename,\n );\n await writeFile(path, output.getContent(feed));\n }\n _rssWorkaround = null;\n _config = null;\n },\n };\n}\n","export const PluginName = '@rspress/plugin-rss';\n\nexport const PluginComponents = {\n FeedsAnnotations: '@rspress/plugin-rss/FeedsAnnotations',\n} as const;\n","import { resolve as resolveUrl } from 'node:url';\nimport type { PageIndexInfo, UserConfig } from '@rspress/shared';\nimport type { Author, FeedOptions } from 'feed';\nimport {\n ResolvedOutput,\n concatArray,\n selectNonNullishProperty,\n toDate,\n} from './internals';\nimport type { FeedChannel, FeedItem } from './type';\n\n/**\n * @public\n * @param page Rspress Page Data\n * @param siteUrl\n */\nexport function generateFeedItem(page: PageIndexInfo, siteUrl: string) {\n const { frontmatter: fm } = page;\n return {\n id: selectNonNullishProperty(fm.slug, fm.id, page.id) || '',\n title: selectNonNullishProperty(fm.title, page.title) || '',\n author: toAuthors(fm.author),\n link: resolveUrl(\n siteUrl,\n selectNonNullishProperty(fm.permalink, page.routePath) || '',\n ),\n description: selectNonNullishProperty(fm.description) || '',\n content: selectNonNullishProperty(fm.summary, page.content) || '',\n date: toDate((fm.date as string) || (fm.published_at as string))!,\n category: concatArray(fm.categories as string[], fm.category as string).map(\n cat => ({ name: cat }),\n ),\n } satisfies FeedItem;\n}\n\nexport function createFeed(\n options: Omit<FeedChannel, 'test' | 'item' | 'output'> & {\n item?: any;\n test?: any;\n output: ResolvedOutput;\n },\n config: UserConfig,\n): FeedOptions {\n const { output, item, id, title, ..._options } = options;\n return {\n id,\n copyright: config.themeConfig?.footer?.message || '',\n description: config.description || '',\n link: output.url,\n ..._options,\n title: title || config.title || '',\n };\n}\n\nfunction toAuthors(author: unknown): Author[] | undefined {\n const authors = (Array.isArray(author) ? author : [author])\n .filter(Boolean)\n .map(author => ({\n // email is mandatory for RSS 2.0.\n ...(typeof author === 'string' ? { name: author } : author),\n }));\n return authors.length ? authors : undefined;\n}\n","export type PartialPartial<T, K extends keyof T> = Partial<Pick<T, K>> &\n Omit<T, K>;\n\nexport type ItemOf<T> = T extends Array<infer K> ? K : never;\n\nexport function notNullish<T>(n: T | undefined | null): n is T {\n return n !== undefined && n !== null;\n}\nexport function concatArray<T>(...arrList: (T[] | T | undefined)[]) {\n return arrList.reduce<T[]>(\n (arr, item) =>\n arr.concat((Array.isArray(item) ? item : [item]).filter(notNullish)),\n [] as T[],\n );\n}\n\nexport function selectNonNullishProperty(...list: unknown[]) {\n for (const item of list) {\n if (item === '') return '';\n if (item === 0) return '0';\n if (typeof item === 'number') return `${item}`;\n if (typeof item === 'string') return item;\n }\n}\n\nexport function toDate(s: string | Date): null | Date {\n const d = new Date(s);\n return Number.isNaN(d.getDate()) ? null : d;\n}\n","import { mkdir, writeFile as _writeFile } from 'node:fs/promises';\nimport * as NodePath from 'node:path';\n\nexport async function writeFile(path: string, content: string | Buffer) {\n const dir = NodePath.dirname(path);\n await mkdir(dir, { mode: 0o755, recursive: true });\n return _writeFile(path, content);\n}\n","import { resolve as resolveUrl } from 'node:url';\nimport type { PageIndexInfo } from '@rspress/shared';\nimport { Feed } from 'feed';\nimport type { ResolvedOutput } from './internals';\nimport type { FeedChannel, FeedOutputType, PluginRssOptions } from './type';\n\nexport function testPage(\n test: FeedChannel['test'],\n page: PageIndexInfo,\n base = '/',\n): boolean {\n if (Array.isArray(test)) {\n return test.some(item => testPage(item, page, base));\n }\n if (typeof test === 'function') {\n return test(page, base);\n }\n const routePath = page.routePath;\n const pureRoutePath = `/${\n routePath.startsWith(base) ? routePath.slice(base.length) : routePath\n }`.replace(/^\\/+/, '/');\n if (typeof test === 'string') {\n return [routePath, pureRoutePath].some(path => path.startsWith(test));\n }\n if (test instanceof RegExp) {\n return [routePath, pureRoutePath].some(path => test.test(path));\n }\n\n throw new Error(\n 'test must be of `RegExp` or `string` or `(page: PageIndexInfo, base: string) => boolean`',\n );\n}\n\nexport function getDefaultFeedOption() {\n return { id: 'blog', test: '/blog/' } satisfies FeedChannel;\n}\n\nexport function getFeedFileType(type: FeedOutputType) {\n switch (type) {\n case 'rss':\n return {\n extension: 'rss',\n mime: 'application/rss+xml',\n getContent: (feed: Feed) => feed.rss2(),\n };\n case 'json':\n return {\n extension: 'json',\n mime: 'application/json',\n getContent: (feed: Feed) => feed.json1(),\n };\n case 'atom':\n default:\n return {\n extension: 'xml',\n mime: 'application/atom+xml',\n getContent: (feed: Feed) => feed.atom1(),\n };\n }\n}\nexport function getOutputInfo(\n { id, output }: Pick<FeedChannel, 'id' | 'output'>,\n {\n siteUrl,\n output: globalOutput,\n }: Pick<PluginRssOptions, 'output' | 'siteUrl'>,\n): ResolvedOutput {\n const type = output?.type || globalOutput?.type || 'atom';\n const { extension, mime, getContent } = getFeedFileType(type);\n const filename = output?.filename || `${id}.${extension}`;\n const dir = output?.dir || globalOutput?.dir || 'rss';\n const publicPath = output?.publicPath || globalOutput?.publicPath || siteUrl;\n const url = [publicPath, `${dir}/`, filename].reduce((u, part) =>\n u ? resolveUrl(u, part) : part,\n );\n return { type, mime, filename, getContent, dir, publicPath, url };\n}\n"]}
1
+ {"version":3,"mappings":"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","names":["import_node_url","_writeFile","resolveUrl","author","NodePath"],"ignoreList":[],"sources":["../src/index.ts","../src/plugin-rss.ts","../src/exports.ts","../src/feed.ts","../src/internals/lang.ts","../src/internals/node.ts","../src/options.ts"],"sourcesContent":["export * from './plugin-rss';\nexport * from './type';\nexport * from './exports';\nexport * from './options';\nexport * from './feed';\n","import NodePath from 'node:path';\nimport { resolve as resolveUrl } from 'node:url';\nimport type { PageIndexInfo, RspressPlugin, UserConfig } from '@rspress/shared';\nimport { Feed } from 'feed';\nimport { PluginComponents, PluginName } from './exports';\nimport { createFeed, generateFeedItem } from './feed';\n\nimport {\n PageWithFeeds,\n ResolvedOutput,\n concatArray,\n writeFile,\n} from './internals';\nimport { getDefaultFeedOption, getOutputInfo, testPage } from './options';\nimport type { FeedChannel, FeedItem, PluginRssOptions } from './type';\n\ntype FeedItemWithChannel = FeedItem & { channel: string };\ntype TransformedFeedChannel = FeedChannel & { output: ResolvedOutput };\n\nclass FeedsSet {\n feeds: TransformedFeedChannel[] = [];\n feedsMapById: Record<string, TransformedFeedChannel> = Object.create(null);\n set({ feed, output, siteUrl }: PluginRssOptions, config: UserConfig) {\n this.feeds = (\n Array.isArray(feed) ? feed : [{ ...getDefaultFeedOption(), ...feed }]\n ).map(options => ({\n title: config.title || '',\n description: config.description || '',\n favicon: config.icon && resolveUrl(siteUrl, config.icon),\n copyright: config.themeConfig?.footer?.message || '',\n link: siteUrl,\n docs: '',\n ...options,\n output: getOutputInfo(options, { siteUrl, output }),\n }));\n\n this.feedsMapById = this.feeds.reduce(\n (m, f) => ({ ...m, [f.id]: f }),\n Object.create(null),\n );\n }\n get(): TransformedFeedChannel[];\n get(id: string): TransformedFeedChannel | null;\n get(id?: string): TransformedFeedChannel[] | TransformedFeedChannel | null {\n if (id) {\n return this.feedsMapById[id] || null;\n }\n return this.feeds.slice(0);\n }\n}\n\nfunction getRssItems(\n feeds: TransformedFeedChannel[],\n page: PageIndexInfo,\n config: UserConfig,\n siteUrl: string,\n): Promise<FeedItemWithChannel[]> {\n return Promise.all(\n feeds\n .filter(options => testPage(options.test, page, config.base))\n .map(async options => {\n const after = options.item || ((feed: FeedItem) => feed);\n const item = await after(\n generateFeedItem(page, siteUrl),\n page,\n siteUrl,\n );\n return { ...item, channel: options.id };\n }),\n );\n}\n\nexport function pluginRss(pluginRssOptions: PluginRssOptions): RspressPlugin {\n const feedsSet = new FeedsSet();\n\n /**\n * workaround for retrieving data of pages in `afterBuild`\n * TODO: get pageData list directly in `afterBuild`\n **/\n let _rssWorkaround: null | Record<\n string,\n PromiseLike<FeedItemWithChannel[]>\n > = null;\n let _config: null | UserConfig;\n\n return {\n name: PluginName,\n globalUIComponents: Object.values(PluginComponents),\n beforeBuild(config, isProd) {\n if (!isProd) {\n _rssWorkaround = null;\n return;\n }\n _rssWorkaround = {};\n _config = config;\n feedsSet.set(pluginRssOptions, config);\n },\n async extendPageData(_pageData) {\n if (!_rssWorkaround) return;\n\n const pageData = _pageData as PageWithFeeds;\n\n // rspress run `extendPageData` for each page\n // - let's cache rss items within a complete rspress build\n _rssWorkaround[pageData.id] =\n _rssWorkaround[pageData.id] ||\n getRssItems(\n feedsSet.get(),\n pageData,\n _config!,\n pluginRssOptions.siteUrl,\n );\n\n const feeds = await _rssWorkaround[pageData.id];\n const showRssList = new Set(\n concatArray(pageData.frontmatter['link-rss'] as string[] | string),\n );\n for (const feed of feeds) {\n showRssList.add(feed.channel);\n }\n\n pageData.feeds = Array.from(showRssList, id => {\n const { output, language } = feedsSet.get(id)!;\n return {\n url: output.url,\n mime: output.mime,\n language: language || pageData.lang,\n };\n });\n },\n async afterBuild(config) {\n if (!_rssWorkaround) return;\n\n const items = concatArray(\n ...(await Promise.all(Object.values(_rssWorkaround))),\n );\n const feeds: Record<string, Feed> = Object.create(null);\n\n for (const { channel, ...item } of items) {\n feeds[channel] =\n feeds[channel] ||\n new Feed(createFeed(feedsSet.get(channel)!, config));\n feeds[channel].addItem(item);\n }\n\n for (const [channel, feed] of Object.entries(feeds)) {\n const { output } = feedsSet.get(channel)!;\n feed.items.sort(output.sorting);\n const path = NodePath.resolve(\n config.outDir || 'doc_build',\n output.dir,\n output.filename,\n );\n await writeFile(path, output.getContent(feed));\n }\n _rssWorkaround = null;\n _config = null;\n },\n };\n}\n","export const PluginName = '@rspress/plugin-rss';\n\nexport const PluginComponents = {\n FeedsAnnotations: '@rspress/plugin-rss/FeedsAnnotations',\n} as const;\n","import { resolve as resolveUrl } from 'node:url';\nimport type { PageIndexInfo, UserConfig } from '@rspress/shared';\nimport type { Author, FeedOptions } from 'feed';\nimport {\n ResolvedOutput,\n concatArray,\n selectNonNullishProperty,\n toDate,\n} from './internals';\nimport type { FeedChannel, FeedItem } from './type';\n\n/**\n * @public\n * @param page Rspress Page Data\n * @param siteUrl\n */\nexport function generateFeedItem(page: PageIndexInfo, siteUrl: string) {\n const { frontmatter: fm } = page;\n return {\n id: selectNonNullishProperty(fm.slug, fm.id, page.id) || '',\n title: selectNonNullishProperty(fm.title, page.title) || '',\n author: toAuthors(fm.author),\n link: resolveUrl(\n siteUrl,\n selectNonNullishProperty(fm.permalink, page.routePath) || '',\n ),\n description: selectNonNullishProperty(fm.description) || '',\n content: selectNonNullishProperty(fm.summary, page.content) || '',\n date: toDate((fm.date as string) || (fm.published_at as string))!,\n category: concatArray(fm.categories as string[], fm.category as string).map(\n cat => ({ name: cat }),\n ),\n } satisfies FeedItem;\n}\n\nexport function createFeed(\n options: Omit<FeedChannel, 'test' | 'item' | 'output'> & {\n item?: any;\n test?: any;\n output: ResolvedOutput;\n },\n config: UserConfig,\n): FeedOptions {\n const { output, item, id, title, ..._options } = options;\n return {\n id,\n copyright: config.themeConfig?.footer?.message || '',\n description: config.description || '',\n link: output.url,\n ..._options,\n title: title || config.title || '',\n };\n}\n\nfunction toAuthors(author: unknown): Author[] | undefined {\n const authors = (Array.isArray(author) ? author : [author])\n .filter(Boolean)\n .map(author => ({\n // email is mandatory for RSS 2.0.\n ...(typeof author === 'string' ? { name: author } : author),\n }));\n return authors.length ? authors : undefined;\n}\n","export type PartialPartial<T, K extends keyof T> = Partial<Pick<T, K>> &\n Omit<T, K>;\n\nexport type ItemOf<T> = T extends Array<infer K> ? K : never;\n\nexport function notNullish<T>(n: T | undefined | null): n is T {\n return n !== undefined && n !== null;\n}\nexport function concatArray<T>(...arrList: (T[] | T | undefined)[]) {\n return arrList.reduce<T[]>(\n (arr, item) =>\n arr.concat((Array.isArray(item) ? item : [item]).filter(notNullish)),\n [] as T[],\n );\n}\n\nexport function selectNonNullishProperty(...list: unknown[]) {\n for (const item of list) {\n if (item === '') return '';\n if (item === 0) return '0';\n if (typeof item === 'number') return `${item}`;\n if (typeof item === 'string') return item;\n }\n}\n\nexport function toDate(s: string | Date): null | Date {\n const d = new Date(s);\n return Number.isNaN(d.getDate()) ? null : d;\n}\n\nexport function sortByDate(l: null | Date, r: null | Date): number {\n return (r ? r.getTime() : 0) - (l ? l.getTime() : 0);\n}\n","import { mkdir, writeFile as _writeFile } from 'node:fs/promises';\nimport * as NodePath from 'node:path';\n\nexport async function writeFile(path: string, content: string | Buffer) {\n const dir = NodePath.dirname(path);\n await mkdir(dir, { mode: 0o755, recursive: true });\n return _writeFile(path, content);\n}\n","import { resolve as resolveUrl } from 'node:url';\nimport type { PageIndexInfo } from '@rspress/shared';\nimport { Feed } from 'feed';\nimport { ResolvedOutput, sortByDate } from './internals';\nimport type { FeedChannel, FeedOutputType, PluginRssOptions } from './type';\n\nexport function testPage(\n test: FeedChannel['test'],\n page: PageIndexInfo,\n base = '/',\n): boolean {\n if (Array.isArray(test)) {\n return test.some(item => testPage(item, page, base));\n }\n if (typeof test === 'function') {\n return test(page, base);\n }\n const routePath = page.routePath;\n const pureRoutePath = `/${\n routePath.startsWith(base) ? routePath.slice(base.length) : routePath\n }`.replace(/^\\/+/, '/');\n if (typeof test === 'string') {\n return [routePath, pureRoutePath].some(path => path.startsWith(test));\n }\n if (test instanceof RegExp) {\n return [routePath, pureRoutePath].some(path => test.test(path));\n }\n\n throw new Error(\n 'test must be of `RegExp` or `string` or `(page: PageIndexInfo, base: string) => boolean`',\n );\n}\n\nexport function getDefaultFeedOption() {\n return { id: 'blog', test: '/blog/' } satisfies FeedChannel;\n}\n\nexport function getFeedFileType(type: FeedOutputType) {\n switch (type) {\n case 'rss':\n return {\n extension: 'rss',\n mime: 'application/rss+xml',\n getContent: (feed: Feed) => feed.rss2(),\n };\n case 'json':\n return {\n extension: 'json',\n mime: 'application/json',\n getContent: (feed: Feed) => feed.json1(),\n };\n case 'atom':\n default:\n return {\n extension: 'xml',\n mime: 'application/atom+xml',\n getContent: (feed: Feed) => feed.atom1(),\n };\n }\n}\nexport function getOutputInfo(\n { id, output }: Pick<FeedChannel, 'id' | 'output'>,\n {\n siteUrl,\n output: globalOutput,\n }: Pick<PluginRssOptions, 'output' | 'siteUrl'>,\n): ResolvedOutput {\n const type = output?.type || globalOutput?.type || 'atom';\n const { extension, mime, getContent } = getFeedFileType(type);\n const filename = output?.filename || `${id}.${extension}`;\n const dir = output?.dir || globalOutput?.dir || 'rss';\n const publicPath = output?.publicPath || globalOutput?.publicPath || siteUrl;\n const url = [publicPath, `${dir}/`, filename].reduce((u, part) =>\n u ? resolveUrl(u, part) : part,\n );\n const sorting =\n output?.sorting ||\n globalOutput?.sorting ||\n ((l, r) => sortByDate(l.date, r.date));\n return { type, mime, filename, getContent, dir, publicPath, url, sorting };\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -9,6 +9,7 @@ interface ResolvedOutput {
9
9
  dir: string;
10
10
  publicPath: string;
11
11
  url: string;
12
+ sorting: (left: FeedItem, right: FeedItem) => number;
12
13
  }
13
14
 
14
15
  type PartialPartial<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K>;
@@ -47,6 +48,10 @@ interface FeedOutputOptions {
47
48
  * public path of feed files. siteUrl by default
48
49
  */
49
50
  publicPath?: string;
51
+ /**
52
+ * sort feed items
53
+ */
54
+ sorting?: (left: FeedItem, right: FeedItem) => number;
50
55
  }
51
56
  interface FeedChannel extends PartialPartial<FeedOptions, 'title' | 'copyright'> {
52
57
  /**
package/dist/index.mjs CHANGED
@@ -38,6 +38,9 @@ function toDate(s) {
38
38
  const d = new Date(s);
39
39
  return Number.isNaN(d.getDate()) ? null : d;
40
40
  }
41
+ function sortByDate(l, r) {
42
+ return (r ? r.getTime() : 0) - (l ? l.getTime() : 0);
43
+ }
41
44
 
42
45
  // src/internals/node.ts
43
46
  import { mkdir, writeFile as _writeFile } from "fs/promises";
@@ -145,7 +148,8 @@ function getOutputInfo({ id, output }, {
145
148
  const url = [publicPath, `${dir}/`, filename].reduce(
146
149
  (u, part) => u ? resolveUrl2(u, part) : part
147
150
  );
148
- return { type, mime, filename, getContent, dir, publicPath, url };
151
+ const sorting = output?.sorting || globalOutput?.sorting || ((l, r) => sortByDate(l.date, r.date));
152
+ return { type, mime, filename, getContent, dir, publicPath, url, sorting };
149
153
  }
150
154
 
151
155
  // src/plugin-rss.ts
@@ -245,6 +249,7 @@ function pluginRss(pluginRssOptions) {
245
249
  }
246
250
  for (const [channel, feed] of Object.entries(feeds)) {
247
251
  const { output } = feedsSet.get(channel);
252
+ feed.items.sort(output.sorting);
248
253
  const path = NodePath2.resolve(
249
254
  config.outDir || "doc_build",
250
255
  output.dir,
@@ -1 +1 @@
1
- {"version":3,"mappings":";AAAA,OAAOA,eAAc;AACrB,SAAS,WAAWC,mBAAkB;AAEtC,SAAS,YAAY;;;ACHd,IAAM,aAAa;AAEnB,IAAM,mBAAmB;AAAA,EAC9B,kBAAkB;AACpB;;;ACJA,SAAS,WAAW,kBAAkB;;;ACK/B,SAAS,WAAc,GAAiC;AAC7D,SAAO,MAAM,UAAa,MAAM;AAClC;AACO,SAAS,eAAkB,SAAkC;AAClE,SAAO,QAAQ;AAAA,IACb,CAAC,KAAK,SACJ,IAAI,QAAQ,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,IAAI,GAAG,OAAO,UAAU,CAAC;AAAA,IACrE,CAAC;AAAA,EACH;AACF;AAEO,SAAS,4BAA4B,MAAiB;AAC3D,aAAW,QAAQ,MAAM;AACvB,QAAI,SAAS;AAAI,aAAO;AACxB,QAAI,SAAS;AAAG,aAAO;AACvB,QAAI,OAAO,SAAS;AAAU,aAAO,GAAG,IAAI;AAC5C,QAAI,OAAO,SAAS;AAAU,aAAO;AAAA,EACvC;AACF;AAEO,SAAS,OAAO,GAA+B;AACpD,QAAM,IAAI,IAAI,KAAK,CAAC;AACpB,SAAO,OAAO,MAAM,EAAE,QAAQ,CAAC,IAAI,OAAO;AAC5C;;;AC5BA,SAAS,OAAO,aAAa,kBAAkB;AAC/C,YAAY,cAAc;AAE1B,eAAsB,UAAU,MAAc,SAA0B;AACtE,QAAM,MAAe,iBAAQ,IAAI;AACjC,QAAM,MAAM,KAAK,EAAE,MAAM,KAAO,WAAW,KAAK,CAAC;AACjD,SAAO,WAAW,MAAM,OAAO;AACjC;;;AFSO,SAAS,iBAAiB,MAAqB,SAAiB;AACrE,QAAM,EAAE,aAAa,GAAG,IAAI;AAC5B,SAAO;AAAA,IACL,IAAI,yBAAyB,GAAG,MAAM,GAAG,IAAI,KAAK,EAAE,KAAK;AAAA,IACzD,OAAO,yBAAyB,GAAG,OAAO,KAAK,KAAK,KAAK;AAAA,IACzD,QAAQ,UAAU,GAAG,MAAM;AAAA,IAC3B,MAAM;AAAA,MACJ;AAAA,MACA,yBAAyB,GAAG,WAAW,KAAK,SAAS,KAAK;AAAA,IAC5D;AAAA,IACA,aAAa,yBAAyB,GAAG,WAAW,KAAK;AAAA,IACzD,SAAS,yBAAyB,GAAG,SAAS,KAAK,OAAO,KAAK;AAAA,IAC/D,MAAM,OAAQ,GAAG,QAAoB,GAAG,YAAuB;AAAA,IAC/D,UAAU,YAAY,GAAG,YAAwB,GAAG,QAAkB,EAAE;AAAA,MACtE,UAAQ,EAAE,MAAM,IAAI;AAAA,IACtB;AAAA,EACF;AACF;AAEO,SAAS,WACd,SAKA,QACa;AACb,QAAM,EAAE,QAAQ,MAAM,IAAI,OAAO,GAAG,SAAS,IAAI;AACjD,SAAO;AAAA,IACL;AAAA,IACA,WAAW,OAAO,aAAa,QAAQ,WAAW;AAAA,IAClD,aAAa,OAAO,eAAe;AAAA,IACnC,MAAM,OAAO;AAAA,IACb,GAAG;AAAA,IACH,OAAO,SAAS,OAAO,SAAS;AAAA,EAClC;AACF;AAEA,SAAS,UAAU,QAAuC;AACxD,QAAM,WAAW,MAAM,QAAQ,MAAM,IAAI,SAAS,CAAC,MAAM,GACtD,OAAO,OAAO,EACd,IAAI,CAAAC,aAAW;AAAA;AAAA,IAEd,GAAI,OAAOA,YAAW,WAAW,EAAE,MAAMA,QAAO,IAAIA;AAAA,EACtD,EAAE;AACJ,SAAO,QAAQ,SAAS,UAAU;AACpC;;;AG9DA,SAAS,WAAWD,mBAAkB;AAM/B,SAAS,SACd,MACA,MACA,OAAO,KACE;AACT,MAAI,MAAM,QAAQ,IAAI,GAAG;AACvB,WAAO,KAAK,KAAK,UAAQ,SAAS,MAAM,MAAM,IAAI,CAAC;AAAA,EACrD;AACA,MAAI,OAAO,SAAS,YAAY;AAC9B,WAAO,KAAK,MAAM,IAAI;AAAA,EACxB;AACA,QAAM,YAAY,KAAK;AACvB,QAAM,gBAAgB,IACpB,UAAU,WAAW,IAAI,IAAI,UAAU,MAAM,KAAK,MAAM,IAAI,SAC9D,GAAG,QAAQ,QAAQ,GAAG;AACtB,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO,CAAC,WAAW,aAAa,EAAE,KAAK,UAAQ,KAAK,WAAW,IAAI,CAAC;AAAA,EACtE;AACA,MAAI,gBAAgB,QAAQ;AAC1B,WAAO,CAAC,WAAW,aAAa,EAAE,KAAK,UAAQ,KAAK,KAAK,IAAI,CAAC;AAAA,EAChE;AAEA,QAAM,IAAI;AAAA,IACR;AAAA,EACF;AACF;AAEO,SAAS,uBAAuB;AACrC,SAAO,EAAE,IAAI,QAAQ,MAAM,SAAS;AACtC;AAEO,SAAS,gBAAgB,MAAsB;AACpD,UAAQ,MAAM;AAAA,IACZ,KAAK;AACH,aAAO;AAAA,QACL,WAAW;AAAA,QACX,MAAM;AAAA,QACN,YAAY,CAAC,SAAe,KAAK,KAAK;AAAA,MACxC;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,WAAW;AAAA,QACX,MAAM;AAAA,QACN,YAAY,CAAC,SAAe,KAAK,MAAM;AAAA,MACzC;AAAA,IACF,KAAK;AAAA,IACL;AACE,aAAO;AAAA,QACL,WAAW;AAAA,QACX,MAAM;AAAA,QACN,YAAY,CAAC,SAAe,KAAK,MAAM;AAAA,MACzC;AAAA,EACJ;AACF;AACO,SAAS,cACd,EAAE,IAAI,OAAO,GACb;AAAA,EACE;AAAA,EACA,QAAQ;AACV,GACgB;AAChB,QAAM,OAAO,QAAQ,QAAQ,cAAc,QAAQ;AACnD,QAAM,EAAE,WAAW,MAAM,WAAW,IAAI,gBAAgB,IAAI;AAC5D,QAAM,WAAW,QAAQ,YAAY,GAAG,EAAE,IAAI,SAAS;AACvD,QAAM,MAAM,QAAQ,OAAO,cAAc,OAAO;AAChD,QAAM,aAAa,QAAQ,cAAc,cAAc,cAAc;AACrE,QAAM,MAAM,CAAC,YAAY,GAAG,GAAG,KAAK,QAAQ,EAAE;AAAA,IAAO,CAAC,GAAG,SACvD,IAAIA,YAAW,GAAG,IAAI,IAAI;AAAA,EAC5B;AACA,SAAO,EAAE,MAAM,MAAM,UAAU,YAAY,KAAK,YAAY,IAAI;AAClE;;;ALzDA,IAAM,WAAN,MAAe;AAAA,EAAf;AACE,iBAAkC,CAAC;AACnC,wBAAuD,uBAAO,OAAO,IAAI;AAAA;AAAA,EACzE,IAAI,EAAE,MAAM,QAAQ,QAAQ,GAAqB,QAAoB;AACnE,SAAK,SACH,MAAM,QAAQ,IAAI,IAAI,OAAO,CAAC,EAAE,GAAG,qBAAqB,GAAG,GAAG,KAAK,CAAC,GACpE,IAAI,cAAY;AAAA,MAChB,OAAO,OAAO,SAAS;AAAA,MACvB,aAAa,OAAO,eAAe;AAAA,MACnC,SAAS,OAAO,QAAQA,YAAW,SAAS,OAAO,IAAI;AAAA,MACvD,WAAW,OAAO,aAAa,QAAQ,WAAW;AAAA,MAClD,MAAM;AAAA,MACN,MAAM;AAAA,MACN,GAAG;AAAA,MACH,QAAQ,cAAc,SAAS,EAAE,SAAS,OAAO,CAAC;AAAA,IACpD,EAAE;AAEF,SAAK,eAAe,KAAK,MAAM;AAAA,MAC7B,CAAC,GAAG,OAAO,EAAE,GAAG,GAAG,CAAC,EAAE,EAAE,GAAG,EAAE;AAAA,MAC7B,uBAAO,OAAO,IAAI;AAAA,IACpB;AAAA,EACF;AAAA,EAGA,IAAI,IAAuE;AACzE,QAAI,IAAI;AACN,aAAO,KAAK,aAAa,EAAE,KAAK;AAAA,IAClC;AACA,WAAO,KAAK,MAAM,MAAM,CAAC;AAAA,EAC3B;AACF;AAEA,SAAS,YACP,OACA,MACA,QACA,SACgC;AAChC,SAAO,QAAQ;AAAA,IACb,MACG,OAAO,aAAW,SAAS,QAAQ,MAAM,MAAM,OAAO,IAAI,CAAC,EAC3D,IAAI,OAAM,YAAW;AACpB,YAAM,QAAQ,QAAQ,SAAS,CAAC,SAAmB;AACnD,YAAM,OAAO,MAAM;AAAA,QACjB,iBAAiB,MAAM,OAAO;AAAA,QAC9B;AAAA,QACA;AAAA,MACF;AACA,aAAO,EAAE,GAAG,MAAM,SAAS,QAAQ,GAAG;AAAA,IACxC,CAAC;AAAA,EACL;AACF;AAEO,SAAS,UAAU,kBAAmD;AAC3E,QAAM,WAAW,IAAI,SAAS;AAM9B,MAAI,iBAGA;AACJ,MAAI;AAEJ,SAAO;AAAA,IACL,MAAM;AAAA,IACN,oBAAoB,OAAO,OAAO,gBAAgB;AAAA,IAClD,YAAY,QAAQ,QAAQ;AAC1B,UAAI,CAAC,QAAQ;AACX,yBAAiB;AACjB;AAAA,MACF;AACA,uBAAiB,CAAC;AAClB,gBAAU;AACV,eAAS,IAAI,kBAAkB,MAAM;AAAA,IACvC;AAAA,IACA,MAAM,eAAe,WAAW;AAC9B,UAAI,CAAC;AAAgB;AAErB,YAAM,WAAW;AAIjB,qBAAe,SAAS,EAAE,IACxB,eAAe,SAAS,EAAE,KAC1B;AAAA,QACE,SAAS,IAAI;AAAA,QACb;AAAA,QACA;AAAA,QACA,iBAAiB;AAAA,MACnB;AAEF,YAAM,QAAQ,MAAM,eAAe,SAAS,EAAE;AAC9C,YAAM,cAAc,IAAI;AAAA,QACtB,YAAY,SAAS,YAAY,UAAU,CAAsB;AAAA,MACnE;AACA,iBAAW,QAAQ,OAAO;AACxB,oBAAY,IAAI,KAAK,OAAO;AAAA,MAC9B;AAEA,eAAS,QAAQ,MAAM,KAAK,aAAa,QAAM;AAC7C,cAAM,EAAE,QAAQ,SAAS,IAAI,SAAS,IAAI,EAAE;AAC5C,eAAO;AAAA,UACL,KAAK,OAAO;AAAA,UACZ,MAAM,OAAO;AAAA,UACb,UAAU,YAAY,SAAS;AAAA,QACjC;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,MAAM,WAAW,QAAQ;AACvB,UAAI,CAAC;AAAgB;AAErB,YAAM,QAAQ;AAAA,QACZ,GAAI,MAAM,QAAQ,IAAI,OAAO,OAAO,cAAc,CAAC;AAAA,MACrD;AACA,YAAM,QAA8B,uBAAO,OAAO,IAAI;AAEtD,iBAAW,EAAE,SAAS,GAAG,KAAK,KAAK,OAAO;AACxC,cAAM,OAAO,IACX,MAAM,OAAO,KACb,IAAI,KAAK,WAAW,SAAS,IAAI,OAAO,GAAI,MAAM,CAAC;AACrD,cAAM,OAAO,EAAE,QAAQ,IAAI;AAAA,MAC7B;AAEA,iBAAW,CAAC,SAAS,IAAI,KAAK,OAAO,QAAQ,KAAK,GAAG;AACnD,cAAM,EAAE,OAAO,IAAI,SAAS,IAAI,OAAO;AAEvC,cAAM,OAAOD,UAAS;AAAA,UACpB,OAAO,UAAU;AAAA,UACjB,OAAO;AAAA,UACP,OAAO;AAAA,QACT;AACA,cAAM,UAAU,MAAM,OAAO,WAAW,IAAI,CAAC;AAAA,MAC/C;AACA,uBAAiB;AACjB,gBAAU;AAAA,IACZ;AAAA,EACF;AACF","names":["NodePath","resolveUrl","author"],"ignoreList":[],"sources":["../src/plugin-rss.ts","../src/exports.ts","../src/feed.ts","../src/internals/lang.ts","../src/internals/node.ts","../src/options.ts"],"sourcesContent":["import NodePath from 'node:path';\nimport { resolve as resolveUrl } from 'node:url';\nimport type { PageIndexInfo, RspressPlugin, UserConfig } from '@rspress/shared';\nimport { Feed } from 'feed';\nimport { PluginComponents, PluginName } from './exports';\nimport { createFeed, generateFeedItem } from './feed';\n\nimport {\n PageWithFeeds,\n ResolvedOutput,\n concatArray,\n writeFile,\n} from './internals';\nimport { getDefaultFeedOption, getOutputInfo, testPage } from './options';\nimport type { FeedChannel, FeedItem, PluginRssOptions } from './type';\n\ntype FeedItemWithChannel = FeedItem & { channel: string };\ntype TransformedFeedChannel = FeedChannel & { output: ResolvedOutput };\n\nclass FeedsSet {\n feeds: TransformedFeedChannel[] = [];\n feedsMapById: Record<string, TransformedFeedChannel> = Object.create(null);\n set({ feed, output, siteUrl }: PluginRssOptions, config: UserConfig) {\n this.feeds = (\n Array.isArray(feed) ? feed : [{ ...getDefaultFeedOption(), ...feed }]\n ).map(options => ({\n title: config.title || '',\n description: config.description || '',\n favicon: config.icon && resolveUrl(siteUrl, config.icon),\n copyright: config.themeConfig?.footer?.message || '',\n link: siteUrl,\n docs: '',\n ...options,\n output: getOutputInfo(options, { siteUrl, output }),\n }));\n\n this.feedsMapById = this.feeds.reduce(\n (m, f) => ({ ...m, [f.id]: f }),\n Object.create(null),\n );\n }\n get(): TransformedFeedChannel[];\n get(id: string): TransformedFeedChannel | null;\n get(id?: string): TransformedFeedChannel[] | TransformedFeedChannel | null {\n if (id) {\n return this.feedsMapById[id] || null;\n }\n return this.feeds.slice(0);\n }\n}\n\nfunction getRssItems(\n feeds: TransformedFeedChannel[],\n page: PageIndexInfo,\n config: UserConfig,\n siteUrl: string,\n): Promise<FeedItemWithChannel[]> {\n return Promise.all(\n feeds\n .filter(options => testPage(options.test, page, config.base))\n .map(async options => {\n const after = options.item || ((feed: FeedItem) => feed);\n const item = await after(\n generateFeedItem(page, siteUrl),\n page,\n siteUrl,\n );\n return { ...item, channel: options.id };\n }),\n );\n}\n\nexport function pluginRss(pluginRssOptions: PluginRssOptions): RspressPlugin {\n const feedsSet = new FeedsSet();\n\n /**\n * workaround for retrieving data of pages in `afterBuild`\n * TODO: get pageData list directly in `afterBuild`\n **/\n let _rssWorkaround: null | Record<\n string,\n PromiseLike<FeedItemWithChannel[]>\n > = null;\n let _config: null | UserConfig;\n\n return {\n name: PluginName,\n globalUIComponents: Object.values(PluginComponents),\n beforeBuild(config, isProd) {\n if (!isProd) {\n _rssWorkaround = null;\n return;\n }\n _rssWorkaround = {};\n _config = config;\n feedsSet.set(pluginRssOptions, config);\n },\n async extendPageData(_pageData) {\n if (!_rssWorkaround) return;\n\n const pageData = _pageData as PageWithFeeds;\n\n // rspress run `extendPageData` for each page\n // - let's cache rss items within a complete rspress build\n _rssWorkaround[pageData.id] =\n _rssWorkaround[pageData.id] ||\n getRssItems(\n feedsSet.get(),\n pageData,\n _config!,\n pluginRssOptions.siteUrl,\n );\n\n const feeds = await _rssWorkaround[pageData.id];\n const showRssList = new Set(\n concatArray(pageData.frontmatter['link-rss'] as string[] | string),\n );\n for (const feed of feeds) {\n showRssList.add(feed.channel);\n }\n\n pageData.feeds = Array.from(showRssList, id => {\n const { output, language } = feedsSet.get(id)!;\n return {\n url: output.url,\n mime: output.mime,\n language: language || pageData.lang,\n };\n });\n },\n async afterBuild(config) {\n if (!_rssWorkaround) return;\n\n const items = concatArray(\n ...(await Promise.all(Object.values(_rssWorkaround))),\n );\n const feeds: Record<string, Feed> = Object.create(null);\n\n for (const { channel, ...item } of items) {\n feeds[channel] =\n feeds[channel] ||\n new Feed(createFeed(feedsSet.get(channel)!, config));\n feeds[channel].addItem(item);\n }\n\n for (const [channel, feed] of Object.entries(feeds)) {\n const { output } = feedsSet.get(channel)!;\n\n const path = NodePath.resolve(\n config.outDir || 'doc_build',\n output.dir,\n output.filename,\n );\n await writeFile(path, output.getContent(feed));\n }\n _rssWorkaround = null;\n _config = null;\n },\n };\n}\n","export const PluginName = '@rspress/plugin-rss';\n\nexport const PluginComponents = {\n FeedsAnnotations: '@rspress/plugin-rss/FeedsAnnotations',\n} as const;\n","import { resolve as resolveUrl } from 'node:url';\nimport type { PageIndexInfo, UserConfig } from '@rspress/shared';\nimport type { Author, FeedOptions } from 'feed';\nimport {\n ResolvedOutput,\n concatArray,\n selectNonNullishProperty,\n toDate,\n} from './internals';\nimport type { FeedChannel, FeedItem } from './type';\n\n/**\n * @public\n * @param page Rspress Page Data\n * @param siteUrl\n */\nexport function generateFeedItem(page: PageIndexInfo, siteUrl: string) {\n const { frontmatter: fm } = page;\n return {\n id: selectNonNullishProperty(fm.slug, fm.id, page.id) || '',\n title: selectNonNullishProperty(fm.title, page.title) || '',\n author: toAuthors(fm.author),\n link: resolveUrl(\n siteUrl,\n selectNonNullishProperty(fm.permalink, page.routePath) || '',\n ),\n description: selectNonNullishProperty(fm.description) || '',\n content: selectNonNullishProperty(fm.summary, page.content) || '',\n date: toDate((fm.date as string) || (fm.published_at as string))!,\n category: concatArray(fm.categories as string[], fm.category as string).map(\n cat => ({ name: cat }),\n ),\n } satisfies FeedItem;\n}\n\nexport function createFeed(\n options: Omit<FeedChannel, 'test' | 'item' | 'output'> & {\n item?: any;\n test?: any;\n output: ResolvedOutput;\n },\n config: UserConfig,\n): FeedOptions {\n const { output, item, id, title, ..._options } = options;\n return {\n id,\n copyright: config.themeConfig?.footer?.message || '',\n description: config.description || '',\n link: output.url,\n ..._options,\n title: title || config.title || '',\n };\n}\n\nfunction toAuthors(author: unknown): Author[] | undefined {\n const authors = (Array.isArray(author) ? author : [author])\n .filter(Boolean)\n .map(author => ({\n // email is mandatory for RSS 2.0.\n ...(typeof author === 'string' ? { name: author } : author),\n }));\n return authors.length ? authors : undefined;\n}\n","export type PartialPartial<T, K extends keyof T> = Partial<Pick<T, K>> &\n Omit<T, K>;\n\nexport type ItemOf<T> = T extends Array<infer K> ? K : never;\n\nexport function notNullish<T>(n: T | undefined | null): n is T {\n return n !== undefined && n !== null;\n}\nexport function concatArray<T>(...arrList: (T[] | T | undefined)[]) {\n return arrList.reduce<T[]>(\n (arr, item) =>\n arr.concat((Array.isArray(item) ? item : [item]).filter(notNullish)),\n [] as T[],\n );\n}\n\nexport function selectNonNullishProperty(...list: unknown[]) {\n for (const item of list) {\n if (item === '') return '';\n if (item === 0) return '0';\n if (typeof item === 'number') return `${item}`;\n if (typeof item === 'string') return item;\n }\n}\n\nexport function toDate(s: string | Date): null | Date {\n const d = new Date(s);\n return Number.isNaN(d.getDate()) ? null : d;\n}\n","import { mkdir, writeFile as _writeFile } from 'node:fs/promises';\nimport * as NodePath from 'node:path';\n\nexport async function writeFile(path: string, content: string | Buffer) {\n const dir = NodePath.dirname(path);\n await mkdir(dir, { mode: 0o755, recursive: true });\n return _writeFile(path, content);\n}\n","import { resolve as resolveUrl } from 'node:url';\nimport type { PageIndexInfo } from '@rspress/shared';\nimport { Feed } from 'feed';\nimport type { ResolvedOutput } from './internals';\nimport type { FeedChannel, FeedOutputType, PluginRssOptions } from './type';\n\nexport function testPage(\n test: FeedChannel['test'],\n page: PageIndexInfo,\n base = '/',\n): boolean {\n if (Array.isArray(test)) {\n return test.some(item => testPage(item, page, base));\n }\n if (typeof test === 'function') {\n return test(page, base);\n }\n const routePath = page.routePath;\n const pureRoutePath = `/${\n routePath.startsWith(base) ? routePath.slice(base.length) : routePath\n }`.replace(/^\\/+/, '/');\n if (typeof test === 'string') {\n return [routePath, pureRoutePath].some(path => path.startsWith(test));\n }\n if (test instanceof RegExp) {\n return [routePath, pureRoutePath].some(path => test.test(path));\n }\n\n throw new Error(\n 'test must be of `RegExp` or `string` or `(page: PageIndexInfo, base: string) => boolean`',\n );\n}\n\nexport function getDefaultFeedOption() {\n return { id: 'blog', test: '/blog/' } satisfies FeedChannel;\n}\n\nexport function getFeedFileType(type: FeedOutputType) {\n switch (type) {\n case 'rss':\n return {\n extension: 'rss',\n mime: 'application/rss+xml',\n getContent: (feed: Feed) => feed.rss2(),\n };\n case 'json':\n return {\n extension: 'json',\n mime: 'application/json',\n getContent: (feed: Feed) => feed.json1(),\n };\n case 'atom':\n default:\n return {\n extension: 'xml',\n mime: 'application/atom+xml',\n getContent: (feed: Feed) => feed.atom1(),\n };\n }\n}\nexport function getOutputInfo(\n { id, output }: Pick<FeedChannel, 'id' | 'output'>,\n {\n siteUrl,\n output: globalOutput,\n }: Pick<PluginRssOptions, 'output' | 'siteUrl'>,\n): ResolvedOutput {\n const type = output?.type || globalOutput?.type || 'atom';\n const { extension, mime, getContent } = getFeedFileType(type);\n const filename = output?.filename || `${id}.${extension}`;\n const dir = output?.dir || globalOutput?.dir || 'rss';\n const publicPath = output?.publicPath || globalOutput?.publicPath || siteUrl;\n const url = [publicPath, `${dir}/`, filename].reduce((u, part) =>\n u ? resolveUrl(u, part) : part,\n );\n return { type, mime, filename, getContent, dir, publicPath, url };\n}\n"]}
1
+ {"version":3,"mappings":"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","names":["NodePath","resolveUrl","author"],"ignoreList":[],"sources":["../src/plugin-rss.ts","../src/exports.ts","../src/feed.ts","../src/internals/lang.ts","../src/internals/node.ts","../src/options.ts"],"sourcesContent":["import NodePath from 'node:path';\nimport { resolve as resolveUrl } from 'node:url';\nimport type { PageIndexInfo, RspressPlugin, UserConfig } from '@rspress/shared';\nimport { Feed } from 'feed';\nimport { PluginComponents, PluginName } from './exports';\nimport { createFeed, generateFeedItem } from './feed';\n\nimport {\n PageWithFeeds,\n ResolvedOutput,\n concatArray,\n writeFile,\n} from './internals';\nimport { getDefaultFeedOption, getOutputInfo, testPage } from './options';\nimport type { FeedChannel, FeedItem, PluginRssOptions } from './type';\n\ntype FeedItemWithChannel = FeedItem & { channel: string };\ntype TransformedFeedChannel = FeedChannel & { output: ResolvedOutput };\n\nclass FeedsSet {\n feeds: TransformedFeedChannel[] = [];\n feedsMapById: Record<string, TransformedFeedChannel> = Object.create(null);\n set({ feed, output, siteUrl }: PluginRssOptions, config: UserConfig) {\n this.feeds = (\n Array.isArray(feed) ? feed : [{ ...getDefaultFeedOption(), ...feed }]\n ).map(options => ({\n title: config.title || '',\n description: config.description || '',\n favicon: config.icon && resolveUrl(siteUrl, config.icon),\n copyright: config.themeConfig?.footer?.message || '',\n link: siteUrl,\n docs: '',\n ...options,\n output: getOutputInfo(options, { siteUrl, output }),\n }));\n\n this.feedsMapById = this.feeds.reduce(\n (m, f) => ({ ...m, [f.id]: f }),\n Object.create(null),\n );\n }\n get(): TransformedFeedChannel[];\n get(id: string): TransformedFeedChannel | null;\n get(id?: string): TransformedFeedChannel[] | TransformedFeedChannel | null {\n if (id) {\n return this.feedsMapById[id] || null;\n }\n return this.feeds.slice(0);\n }\n}\n\nfunction getRssItems(\n feeds: TransformedFeedChannel[],\n page: PageIndexInfo,\n config: UserConfig,\n siteUrl: string,\n): Promise<FeedItemWithChannel[]> {\n return Promise.all(\n feeds\n .filter(options => testPage(options.test, page, config.base))\n .map(async options => {\n const after = options.item || ((feed: FeedItem) => feed);\n const item = await after(\n generateFeedItem(page, siteUrl),\n page,\n siteUrl,\n );\n return { ...item, channel: options.id };\n }),\n );\n}\n\nexport function pluginRss(pluginRssOptions: PluginRssOptions): RspressPlugin {\n const feedsSet = new FeedsSet();\n\n /**\n * workaround for retrieving data of pages in `afterBuild`\n * TODO: get pageData list directly in `afterBuild`\n **/\n let _rssWorkaround: null | Record<\n string,\n PromiseLike<FeedItemWithChannel[]>\n > = null;\n let _config: null | UserConfig;\n\n return {\n name: PluginName,\n globalUIComponents: Object.values(PluginComponents),\n beforeBuild(config, isProd) {\n if (!isProd) {\n _rssWorkaround = null;\n return;\n }\n _rssWorkaround = {};\n _config = config;\n feedsSet.set(pluginRssOptions, config);\n },\n async extendPageData(_pageData) {\n if (!_rssWorkaround) return;\n\n const pageData = _pageData as PageWithFeeds;\n\n // rspress run `extendPageData` for each page\n // - let's cache rss items within a complete rspress build\n _rssWorkaround[pageData.id] =\n _rssWorkaround[pageData.id] ||\n getRssItems(\n feedsSet.get(),\n pageData,\n _config!,\n pluginRssOptions.siteUrl,\n );\n\n const feeds = await _rssWorkaround[pageData.id];\n const showRssList = new Set(\n concatArray(pageData.frontmatter['link-rss'] as string[] | string),\n );\n for (const feed of feeds) {\n showRssList.add(feed.channel);\n }\n\n pageData.feeds = Array.from(showRssList, id => {\n const { output, language } = feedsSet.get(id)!;\n return {\n url: output.url,\n mime: output.mime,\n language: language || pageData.lang,\n };\n });\n },\n async afterBuild(config) {\n if (!_rssWorkaround) return;\n\n const items = concatArray(\n ...(await Promise.all(Object.values(_rssWorkaround))),\n );\n const feeds: Record<string, Feed> = Object.create(null);\n\n for (const { channel, ...item } of items) {\n feeds[channel] =\n feeds[channel] ||\n new Feed(createFeed(feedsSet.get(channel)!, config));\n feeds[channel].addItem(item);\n }\n\n for (const [channel, feed] of Object.entries(feeds)) {\n const { output } = feedsSet.get(channel)!;\n feed.items.sort(output.sorting);\n const path = NodePath.resolve(\n config.outDir || 'doc_build',\n output.dir,\n output.filename,\n );\n await writeFile(path, output.getContent(feed));\n }\n _rssWorkaround = null;\n _config = null;\n },\n };\n}\n","export const PluginName = '@rspress/plugin-rss';\n\nexport const PluginComponents = {\n FeedsAnnotations: '@rspress/plugin-rss/FeedsAnnotations',\n} as const;\n","import { resolve as resolveUrl } from 'node:url';\nimport type { PageIndexInfo, UserConfig } from '@rspress/shared';\nimport type { Author, FeedOptions } from 'feed';\nimport {\n ResolvedOutput,\n concatArray,\n selectNonNullishProperty,\n toDate,\n} from './internals';\nimport type { FeedChannel, FeedItem } from './type';\n\n/**\n * @public\n * @param page Rspress Page Data\n * @param siteUrl\n */\nexport function generateFeedItem(page: PageIndexInfo, siteUrl: string) {\n const { frontmatter: fm } = page;\n return {\n id: selectNonNullishProperty(fm.slug, fm.id, page.id) || '',\n title: selectNonNullishProperty(fm.title, page.title) || '',\n author: toAuthors(fm.author),\n link: resolveUrl(\n siteUrl,\n selectNonNullishProperty(fm.permalink, page.routePath) || '',\n ),\n description: selectNonNullishProperty(fm.description) || '',\n content: selectNonNullishProperty(fm.summary, page.content) || '',\n date: toDate((fm.date as string) || (fm.published_at as string))!,\n category: concatArray(fm.categories as string[], fm.category as string).map(\n cat => ({ name: cat }),\n ),\n } satisfies FeedItem;\n}\n\nexport function createFeed(\n options: Omit<FeedChannel, 'test' | 'item' | 'output'> & {\n item?: any;\n test?: any;\n output: ResolvedOutput;\n },\n config: UserConfig,\n): FeedOptions {\n const { output, item, id, title, ..._options } = options;\n return {\n id,\n copyright: config.themeConfig?.footer?.message || '',\n description: config.description || '',\n link: output.url,\n ..._options,\n title: title || config.title || '',\n };\n}\n\nfunction toAuthors(author: unknown): Author[] | undefined {\n const authors = (Array.isArray(author) ? author : [author])\n .filter(Boolean)\n .map(author => ({\n // email is mandatory for RSS 2.0.\n ...(typeof author === 'string' ? { name: author } : author),\n }));\n return authors.length ? authors : undefined;\n}\n","export type PartialPartial<T, K extends keyof T> = Partial<Pick<T, K>> &\n Omit<T, K>;\n\nexport type ItemOf<T> = T extends Array<infer K> ? K : never;\n\nexport function notNullish<T>(n: T | undefined | null): n is T {\n return n !== undefined && n !== null;\n}\nexport function concatArray<T>(...arrList: (T[] | T | undefined)[]) {\n return arrList.reduce<T[]>(\n (arr, item) =>\n arr.concat((Array.isArray(item) ? item : [item]).filter(notNullish)),\n [] as T[],\n );\n}\n\nexport function selectNonNullishProperty(...list: unknown[]) {\n for (const item of list) {\n if (item === '') return '';\n if (item === 0) return '0';\n if (typeof item === 'number') return `${item}`;\n if (typeof item === 'string') return item;\n }\n}\n\nexport function toDate(s: string | Date): null | Date {\n const d = new Date(s);\n return Number.isNaN(d.getDate()) ? null : d;\n}\n\nexport function sortByDate(l: null | Date, r: null | Date): number {\n return (r ? r.getTime() : 0) - (l ? l.getTime() : 0);\n}\n","import { mkdir, writeFile as _writeFile } from 'node:fs/promises';\nimport * as NodePath from 'node:path';\n\nexport async function writeFile(path: string, content: string | Buffer) {\n const dir = NodePath.dirname(path);\n await mkdir(dir, { mode: 0o755, recursive: true });\n return _writeFile(path, content);\n}\n","import { resolve as resolveUrl } from 'node:url';\nimport type { PageIndexInfo } from '@rspress/shared';\nimport { Feed } from 'feed';\nimport { ResolvedOutput, sortByDate } from './internals';\nimport type { FeedChannel, FeedOutputType, PluginRssOptions } from './type';\n\nexport function testPage(\n test: FeedChannel['test'],\n page: PageIndexInfo,\n base = '/',\n): boolean {\n if (Array.isArray(test)) {\n return test.some(item => testPage(item, page, base));\n }\n if (typeof test === 'function') {\n return test(page, base);\n }\n const routePath = page.routePath;\n const pureRoutePath = `/${\n routePath.startsWith(base) ? routePath.slice(base.length) : routePath\n }`.replace(/^\\/+/, '/');\n if (typeof test === 'string') {\n return [routePath, pureRoutePath].some(path => path.startsWith(test));\n }\n if (test instanceof RegExp) {\n return [routePath, pureRoutePath].some(path => test.test(path));\n }\n\n throw new Error(\n 'test must be of `RegExp` or `string` or `(page: PageIndexInfo, base: string) => boolean`',\n );\n}\n\nexport function getDefaultFeedOption() {\n return { id: 'blog', test: '/blog/' } satisfies FeedChannel;\n}\n\nexport function getFeedFileType(type: FeedOutputType) {\n switch (type) {\n case 'rss':\n return {\n extension: 'rss',\n mime: 'application/rss+xml',\n getContent: (feed: Feed) => feed.rss2(),\n };\n case 'json':\n return {\n extension: 'json',\n mime: 'application/json',\n getContent: (feed: Feed) => feed.json1(),\n };\n case 'atom':\n default:\n return {\n extension: 'xml',\n mime: 'application/atom+xml',\n getContent: (feed: Feed) => feed.atom1(),\n };\n }\n}\nexport function getOutputInfo(\n { id, output }: Pick<FeedChannel, 'id' | 'output'>,\n {\n siteUrl,\n output: globalOutput,\n }: Pick<PluginRssOptions, 'output' | 'siteUrl'>,\n): ResolvedOutput {\n const type = output?.type || globalOutput?.type || 'atom';\n const { extension, mime, getContent } = getFeedFileType(type);\n const filename = output?.filename || `${id}.${extension}`;\n const dir = output?.dir || globalOutput?.dir || 'rss';\n const publicPath = output?.publicPath || globalOutput?.publicPath || siteUrl;\n const url = [publicPath, `${dir}/`, filename].reduce((u, part) =>\n u ? resolveUrl(u, part) : part,\n );\n const sorting =\n output?.sorting ||\n globalOutput?.sorting ||\n ((l, r) => sortByDate(l.date, r.date));\n return { type, mime, filename, getContent, dir, publicPath, url, sorting };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rspress/plugin-rss",
3
- "version": "1.17.0",
3
+ "version": "1.18.0",
4
4
  "description": "A plugin for rss generation for rspress",
5
5
  "bugs": "https://github.com/web-infra-dev/rspress/issues",
6
6
  "repository": {
@@ -15,8 +15,8 @@
15
15
  "module": "./dist/index.mjs",
16
16
  "exports": {
17
17
  ".": {
18
- "import": "./dist/index.mjs",
19
18
  "types": "./dist/index.d.ts",
19
+ "import": "./dist/index.mjs",
20
20
  "default": "./dist/index.cjs"
21
21
  },
22
22
  "./FeedsAnnotations": "./static/global-components/FeedsAnnotations"
@@ -25,20 +25,19 @@
25
25
  "node": ">=14.17.6"
26
26
  },
27
27
  "dependencies": {
28
- "feed": "^4.2.2"
28
+ "feed": "^4.2.2",
29
+ "@rspress/shared": "1.18.0"
29
30
  },
30
31
  "devDependencies": {
31
32
  "@types/node": "^18.11.17",
32
33
  "@types/react": "^18",
33
34
  "react": "^18",
34
35
  "typescript": "^5",
35
- "@rspress/shared": "1.17.0",
36
- "@rspress/runtime": "1.17.0"
36
+ "@rspress/runtime": "1.18.0"
37
37
  },
38
38
  "peerDependencies": {
39
39
  "react": ">=17.0.0",
40
- "@types/react": ">=17.0.0",
41
- "@rspress/runtime": "^1.0.0"
40
+ "rspress": "^1.17.0"
42
41
  },
43
42
  "files": [
44
43
  "dist",
@@ -53,15 +52,6 @@
53
52
  "dev": "modern build -w",
54
53
  "build": "modern build",
55
54
  "reset": "rimraf ./**/node_modules",
56
- "lint": "modern lint",
57
- "change": "modern change",
58
- "bump": "modern bump",
59
- "pre": "modern pre",
60
- "change-status": "modern change-status",
61
- "gen-release-note": "modern gen-release-note",
62
- "release": "modern release",
63
- "new": "modern new",
64
- "test": "vitest run --passWithNoTests",
65
- "upgrade": "modern upgrade"
55
+ "test": "vitest run --passWithNoTests"
66
56
  }
67
57
  }
@@ -1,5 +1,5 @@
1
1
  import type { PageFeedData } from '@rspress/plugin-rss';
2
- import { Helmet, usePageData } from '@rspress/runtime';
2
+ import { Helmet, usePageData } from 'rspress/runtime';
3
3
  import { LinkHTMLAttributes } from 'react';
4
4
 
5
5
  export default function FeedsAnnotations() {
@@ -10,7 +10,7 @@ export default function FeedsAnnotations() {
10
10
  <Helmet>
11
11
  {feeds.map(({ language, url, mime }) => {
12
12
  const props: LinkHTMLAttributes<HTMLLinkElement> = {
13
- rel: 'alternative',
13
+ rel: 'alternate',
14
14
  type: mime,
15
15
  href: url,
16
16
  };