|
| 1 | +const fs = require("fs"); |
| 2 | +const path = require("path"); |
| 3 | +const matter = require("gray-matter"); |
| 4 | + |
| 5 | +const RELEASES_DIR_REL = path.join("versioned_docs", "version-3", "releases"); |
| 6 | + |
| 7 | +function escapeXml(str) { |
| 8 | + return String(str) |
| 9 | + .replace(/&/g, "&") |
| 10 | + .replace(/</g, "<") |
| 11 | + .replace(/>/g, ">") |
| 12 | + .replace(/"/g, """); |
| 13 | +} |
| 14 | + |
| 15 | +function generateDescription(content) { |
| 16 | + const featuresMatch = content.match(/###\s+New Features([\s\S]*?)(?:###|$)/); |
| 17 | + if (!featuresMatch) return ""; |
| 18 | + const names = []; |
| 19 | + const re = /\*\*([^*]+)\*\*/g; |
| 20 | + let m; |
| 21 | + while ((m = re.exec(featuresMatch[1])) !== null) { |
| 22 | + names.push(m[1]); |
| 23 | + if (names.length === 5) break; |
| 24 | + } |
| 25 | + return names.length ? names.join(", ") + (names.length === 5 ? ", and more." : ".") : ""; |
| 26 | +} |
| 27 | + |
| 28 | +function getReleases(siteDir) { |
| 29 | + const releasesDir = path.join(siteDir, RELEASES_DIR_REL); |
| 30 | + return fs |
| 31 | + .readdirSync(releasesDir) |
| 32 | + .filter((f) => (f.endsWith(".mdx") || f.endsWith(".md")) && f !== "index.mdx") |
| 33 | + .sort() |
| 34 | + .reverse() |
| 35 | + .map((file) => { |
| 36 | + const raw = fs.readFileSync(path.join(releasesDir, file), "utf8"); |
| 37 | + const { data, content } = matter(raw); |
| 38 | + const slug = path.basename(file).replace(/\.(mdx|md)$/, ""); |
| 39 | + return { |
| 40 | + title: data.sidebar_label ?? data.title ?? slug, |
| 41 | + date: data.date ? new Date(data.date).toISOString() : null, |
| 42 | + description: data.description || generateDescription(content), |
| 43 | + slug, |
| 44 | + id: file, |
| 45 | + }; |
| 46 | + }); |
| 47 | +} |
| 48 | + |
| 49 | +function generateRSS(releases, siteUrl) { |
| 50 | + const items = releases |
| 51 | + .filter((r) => r.date) |
| 52 | + .map( |
| 53 | + (r) => |
| 54 | + `\n <item>\n <title>${escapeXml(r.title)}</title>\n <link>${siteUrl}/3/releases/${escapeXml(r.slug)}</link>\n <guid isPermaLink="true">${siteUrl}/3/releases/${escapeXml(r.slug)}</guid>\n <description>${escapeXml(r.description)}</description>\n <pubDate>${new Date(r.date).toUTCString()}</pubDate>\n </item>` |
| 55 | + ) |
| 56 | + .join(""); |
| 57 | + const lastBuild = releases[0]?.date ? new Date(releases[0].date).toUTCString() : new Date().toUTCString(); |
| 58 | + const year = new Date().getFullYear(); |
| 59 | + return `<?xml version="1.0" encoding="UTF-8"?>\n<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">\n <channel>\n <title>TCAdmin v3 Releases</title>\n <link>${siteUrl}/3/releases</link>\n <description>Latest TCAdmin v3 release notes</description>\n <language>en</language>\n <lastBuildDate>${lastBuild}</lastBuildDate>\n <atom:link href="${siteUrl}/releases/rss.xml" rel="self" type="application/rss+xml"/>\n <copyright>Copyright \u00a9 ${year} TCADMIN.COM</copyright>${items}\n </channel>\n</rss>`; |
| 60 | +} |
| 61 | + |
| 62 | +function generateAtom(releases, siteUrl) { |
| 63 | + const entries = releases |
| 64 | + .filter((r) => r.date) |
| 65 | + .map( |
| 66 | + (r) => |
| 67 | + `\n <entry>\n <title>${escapeXml(r.title)}</title>\n <link href="${siteUrl}/3/releases/${escapeXml(r.slug)}"/>\n <id>${siteUrl}/3/releases/${escapeXml(r.slug)}</id>\n <updated>${r.date}</updated>\n <summary>${escapeXml(r.description)}</summary>\n </entry>` |
| 68 | + ) |
| 69 | + .join(""); |
| 70 | + const updated = releases.find((r) => r.date)?.date ?? new Date().toISOString(); |
| 71 | + const year = new Date().getFullYear(); |
| 72 | + return `<?xml version="1.0" encoding="UTF-8"?>\n<feed xmlns="http://www.w3.org/2005/Atom">\n <title>TCAdmin v3 Releases</title>\n <link href="${siteUrl}/3/releases"/>\n <link rel="self" href="${siteUrl}/releases/atom.xml"/>\n <id>${siteUrl}/releases/atom.xml</id>\n <updated>${updated}</updated>\n <rights>Copyright \u00a9 ${year} TCADMIN.COM</rights>${entries}\n</feed>`; |
| 73 | +} |
| 74 | + |
| 75 | +module.exports = function releasesDataPlugin(context) { |
| 76 | + return { |
| 77 | + name: "releases-data-plugin", |
| 78 | + async contentLoaded({ actions }) { |
| 79 | + const { setGlobalData } = actions; |
| 80 | + setGlobalData({ releases: getReleases(context.siteDir) }); |
| 81 | + }, |
| 82 | + async postBuild({ outDir }) { |
| 83 | + const releases = getReleases(context.siteDir); |
| 84 | + const siteUrl = context.siteConfig.url; |
| 85 | + const feedsDir = path.join(outDir, "releases"); |
| 86 | + fs.mkdirSync(feedsDir, { recursive: true }); |
| 87 | + fs.writeFileSync(path.join(feedsDir, "rss.xml"), generateRSS(releases, siteUrl)); |
| 88 | + fs.writeFileSync(path.join(feedsDir, "atom.xml"), generateAtom(releases, siteUrl)); |
| 89 | + }, |
| 90 | + }; |
| 91 | +}; |
0 commit comments