Website: Add RSS feeds for articles (#9526)
Closes: https://github.com/fleetdm/fleet/issues/6493 Changes: - Added a new action, `get-one-rss-feed.js`. This action generates and returns RSS feeds for article categories on fleetdm.com. - This action has one required input: `categoryName`. - Lives at `/rss/[Article Category Name]` e.g., `fleetdm.com/rss/releases` - If `articles` is provided as the category, it returns an RSS feed of all articles published on our blog. - Updated `view-basic-article.js` to set an `articleCategorySlug` variable, that is used to link to the RSS feed for an article category from an article page. - Added a "subscribe" button to articles and article category pages that links to an RSS feed for that category.
This commit is contained in:
@@ -45,6 +45,8 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
|
||||
let articleCategorySlug = pageUrlSuffix.split('/')[0];
|
||||
|
||||
// Setting the pages meta title and description from the articles meta tags, as well as an article image, if provided.
|
||||
// Note: Every article page should have a 'articleTitle' and a 'authorFullName' meta tag.
|
||||
// Note: Leaving title and description as `undefined` in our view means we'll default to the generic title and description set in layout.ejs.
|
||||
@@ -68,6 +70,7 @@ module.exports = {
|
||||
pageTitleForMeta,
|
||||
pageDescriptionForMeta,
|
||||
pageImageForMeta: thisPage.meta.articleImageUrl || undefined,
|
||||
articleCategorySlug
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
module.exports = {
|
||||
|
||||
|
||||
friendlyName: 'Download rss feed',
|
||||
|
||||
|
||||
description: 'Generate and return an RSS feed for a category of Fleet\'s articles',
|
||||
|
||||
|
||||
inputs: {
|
||||
|
||||
categoryName: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
isIn: [
|
||||
'success-stories',
|
||||
'securing',
|
||||
'releases',
|
||||
'engineering',
|
||||
'guides',
|
||||
'announcements',
|
||||
'deploy',
|
||||
'podcasts',
|
||||
'report',
|
||||
'articles',
|
||||
],
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
|
||||
exits: {
|
||||
success: { outputFriendlyName: 'RSS feed XML', outputType: 'string' },
|
||||
badConfig: { responseType: 'badConfig' },
|
||||
},
|
||||
|
||||
|
||||
fn: async function ({categoryName}) {
|
||||
|
||||
if (!_.isObject(sails.config.builtStaticContent)) {
|
||||
throw {badConfig: 'builtStaticContent'};
|
||||
} else if (!_.isArray(sails.config.builtStaticContent.markdownPages)) {
|
||||
throw {badConfig: 'builtStaticContent.markdownPages'};
|
||||
}
|
||||
|
||||
// Start building the rss feed
|
||||
let rssFeedXml = '<rss version="2.0"><channel>';
|
||||
|
||||
// Build the description and title for this RSS feed.
|
||||
let articleCategoryTitle = '';
|
||||
let categoryDescription = '';
|
||||
switch(categoryName) {
|
||||
case 'success-stories':
|
||||
articleCategoryTitle = 'Success stories | Fleet blog';
|
||||
categoryDescription = 'Read about how others are using Fleet and osquery.';
|
||||
break;
|
||||
case 'securing':
|
||||
articleCategoryTitle = 'Security | Fleet blog';
|
||||
categoryDescription = 'Learn more about how we secure Fleet.';
|
||||
break;
|
||||
case 'releases':
|
||||
articleCategoryTitle = 'Releases | Fleet blog';
|
||||
categoryDescription = 'Read about the latest release of Fleet.';
|
||||
break;
|
||||
case 'engineering':
|
||||
articleCategoryTitle = 'Engineering | Fleet blog';
|
||||
categoryDescription = 'Read about engineering at Fleet and beyond.';
|
||||
break;
|
||||
case 'guides':
|
||||
articleCategoryTitle = 'Guides | Fleet blog';
|
||||
categoryDescription = 'Learn more about how to use Fleet to accomplish your goals.';
|
||||
break;
|
||||
case 'announcements':
|
||||
articleCategoryTitle = 'Announcements | Fleet blog';
|
||||
categoryDescription = 'The latest news from Fleet.';
|
||||
break;
|
||||
case 'deploy':
|
||||
articleCategoryTitle = 'Deployment guides | Fleet blog';
|
||||
categoryDescription = 'Learn more about how to deploy Fleet.';
|
||||
break;
|
||||
case 'podcasts':
|
||||
articleCategoryTitle = 'Podcasts | Fleet blog';
|
||||
categoryDescription = 'Listen to the Future of Device Management podcast';
|
||||
break;
|
||||
case 'report':
|
||||
articleCategoryTitle = 'Reports | Fleet blog';
|
||||
categoryDescription = '';
|
||||
break;
|
||||
case 'articles':
|
||||
articleCategoryTitle = 'Fleet blog | Fleet for osquery';
|
||||
categoryDescription = 'Read all articles from Fleet\'s blog.';
|
||||
}
|
||||
|
||||
let rssFeedTitle = `<title>${_.escape(articleCategoryTitle)}</title>`;
|
||||
let rssFeedDescription = `<description>${_.escape(categoryDescription)}</description>`;
|
||||
let rsslastBuildDate = `<lastBuildDate>${_.escape(new Date(Date.now()))}</lastBuildDate>`;
|
||||
let rssFeedImage = `<image><link>${_.escape('https://fleetdm.com'+categoryName)}</link><title>${_.escape(articleCategoryTitle)}</title><url>${_.escape('https://fleetdm.com/images/fleet-logo-square@2x.png')}</url></image>`;
|
||||
|
||||
rssFeedXml += `${rssFeedTitle}${rssFeedDescription}${rsslastBuildDate}${rssFeedImage}`;
|
||||
|
||||
|
||||
// Determine the subset of articles that will be used to squirt out an XML string.
|
||||
let articlesToAddToFeed = [];
|
||||
if (categoryName === 'articles') {
|
||||
// If the category is `articles` we'll build a rss feed that contains all articles
|
||||
articlesToAddToFeed = sails.config.builtStaticContent.markdownPages.filter((page)=>{
|
||||
if(_.startsWith(page.htmlId, 'articles')) {
|
||||
return page;
|
||||
}
|
||||
});//∞
|
||||
} else {
|
||||
// If the user requested a specific category, we'll only build a feed with articles in that category
|
||||
articlesToAddToFeed = sails.config.builtStaticContent.markdownPages.filter((page)=>{
|
||||
if(_.startsWith(page.url, '/'+categoryName)) {
|
||||
return page;
|
||||
}
|
||||
});//∞
|
||||
}
|
||||
|
||||
// Iterate through the filtered array of articles, adding <item> elements for each article.
|
||||
for (let pageInfo of articlesToAddToFeed) {
|
||||
let rssItemTitle = `<title>${_.escape(pageInfo.meta.articleTitle)}</title>`;
|
||||
let rssItemDescription = `<description>${_.escape(pageInfo.meta.description)}</description>`;
|
||||
let rssItemLink = `<link>${_.escape('https://fleetdm.com'+pageInfo.url)}</link>`;
|
||||
let rssItemPublishDate = `<pubDate>${_.escape(new Date(pageInfo.meta.publishedOn).toJSON())}</pubDate>`;
|
||||
// Add the article to the feed.
|
||||
rssFeedXml += `<item>${rssItemTitle}${rssItemDescription}${rssItemLink}${rssItemPublishDate}</item>`;
|
||||
}
|
||||
|
||||
rssFeedXml += `</channel></rss>`;
|
||||
|
||||
// Set the response type
|
||||
this.res.type('text/xml');
|
||||
|
||||
// Return the generated RSS feed
|
||||
return rssFeedXml;
|
||||
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
@@ -33,6 +33,21 @@
|
||||
padding-top: 80px;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
[purpose='rss-button'] {
|
||||
padding: 4px 8px;
|
||||
display: inline;
|
||||
max-width: min-content;
|
||||
color: #192147;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
span::before {
|
||||
font-family: 'FontAwesome';
|
||||
content: '\f09e';
|
||||
color: #192147;
|
||||
padding-right: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
[purpose='articles'] {
|
||||
padding-bottom: 80px;
|
||||
|
||||
@@ -24,6 +24,22 @@
|
||||
}
|
||||
}
|
||||
|
||||
[purpose='rss-button'] {
|
||||
padding: 4px 8px;
|
||||
display: inline;
|
||||
max-width: min-content;
|
||||
color: #192147;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
span::before {
|
||||
vertical-align: baseline;
|
||||
font-family: 'FontAwesome';
|
||||
content: '\f09e';
|
||||
color: #192147;
|
||||
padding-right: 8px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
[purpose='article-details'] {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
Vendored
+1
@@ -50,4 +50,5 @@ module.exports.policies = {
|
||||
'view-fleet-mdm': true,
|
||||
'deliver-mdm-beta-signup': true,
|
||||
'deliver-apple-csr': true,
|
||||
'download-rss-feed': true,
|
||||
};
|
||||
|
||||
Vendored
+4
@@ -355,6 +355,10 @@ module.exports.routes = {
|
||||
// XML file, which helps search engines know which pages are available on the website.
|
||||
'GET /sitemap.xml': { action: 'download-sitemap' },
|
||||
|
||||
// RSS feeds
|
||||
// =============================================================================================================
|
||||
'GET /rss/:categoryName': {action: 'download-rss-feed'},
|
||||
|
||||
// Potential future pages
|
||||
// =============================================================================================================
|
||||
// Things that are not webpages here (in the Sails app) yet, but could be in the future. For now they are just
|
||||
|
||||
+4
-1
@@ -19,7 +19,10 @@
|
||||
</div>
|
||||
<div purpose="category-title" v-else>
|
||||
<h1>{{articleCategory}}</h1>
|
||||
<p>{{categoryDescription}}</p>
|
||||
<div class="d-flex flex-sm-row flex-column justify-content-between">
|
||||
<p>{{categoryDescription}}</p>
|
||||
<a purpose="rss-button" class="px-0 px-sm-2 pt-sm-1" :href="'/rss/'+category" target="_blank"><span>Subscribe</span></a>
|
||||
</div>
|
||||
</div>
|
||||
<div purpose="articles" class="card-deck d-flex justify-content-center" v-if="selectedArticles.length > 0">
|
||||
<div purpose="article-card" class="card" v-for="article in selectedArticles">
|
||||
|
||||
+8
-5
@@ -4,11 +4,14 @@
|
||||
<h1><%=thisPage.meta.articleTitle %></h1>
|
||||
<h2 v-if="articleHasSubtitle && articleSubtitle !== undefined">{{articleSubtitle}}</h2>
|
||||
</div>
|
||||
<div purpose="article-details" class="d-flex flex-row align-items-center">
|
||||
<span><js-timestamp format="billing" :at="thisPage.meta.publishedOn"></js-timestamp></span>
|
||||
<span class="px-2">|</span>
|
||||
<img style="height: 28px; width: 28px; border-radius: 100%;" alt="The author's GitHub profile picture" :src="'https://github.com/'+thisPage.meta.authorGitHubUsername+'.png?size=200'">
|
||||
<p class="pl-2 font-weight-bold"><%=thisPage.meta.authorFullName %></p>
|
||||
<div class="d-flex flex-sm-row flex-column justify-content-between">
|
||||
<div purpose="article-details" class="d-flex flex-row align-items-center">
|
||||
<span><js-timestamp format="billing" :at="thisPage.meta.publishedOn"></js-timestamp></span>
|
||||
<span class="px-2">|</span>
|
||||
<img style="height: 28px; width: 28px; border-radius: 100%;" alt="The author's GitHub profile picture" :src="'https://github.com/'+thisPage.meta.authorGitHubUsername+'.png?size=200'">
|
||||
<p class="pl-2 font-weight-bold"><%=thisPage.meta.authorFullName %></p>
|
||||
</div>
|
||||
<a purpose="rss-button" class="px-0 px-sm-2 pt-3 pt-sm-1" taget="_blank" :href="'/rss/'+articleCategorySlug"><span>Subscribe</span></a>
|
||||
</div>
|
||||
<div purpose="article-content" class="d-flex flex-column" parasails-has-no-page-script>
|
||||
<%- partial(path.relative(path.dirname(__filename), path.resolve( sails.config.appPath, path.join(sails.config.builtStaticContent.compiledPagePartialsAppPath, thisPage.htmlId)))) %>
|
||||
|
||||
Reference in New Issue
Block a user