From f26bd2fe9c1896fa1a687458ffcc2455356aa7f1 Mon Sep 17 00:00:00 2001 From: Michal Vanko Date: Sun, 14 Jun 2020 11:33:34 +0200 Subject: [PATCH] Create and link RSS feed --- package-lock.json | 16 ++++ package.json | 1 + src/components/Footer.svelte | 28 ++++++- src/routes/blog/_content.js | 55 ++++++++++++++ src/routes/blog/index.json.js | 57 +------------- src/routes/feed/_feed.js | 39 ++++++++++ src/routes/feed/index.json.js | 11 +++ src/routes/feed/index.xml.js | 11 +++ src/routes/index.svelte | 6 +- src/svg/icon.svg | 20 +++++ src/svg/iconfinder_icon-social-rss_211914.svg | 1 + src/svg/json_feed_icon.svg | 75 +++++++++++++++++++ static/images/json-feed-logo.png | 3 + static/images/rss-feed-logo.png | 3 + 14 files changed, 269 insertions(+), 57 deletions(-) create mode 100644 src/routes/blog/_content.js create mode 100644 src/routes/feed/_feed.js create mode 100644 src/routes/feed/index.json.js create mode 100644 src/routes/feed/index.xml.js create mode 100644 src/svg/icon.svg create mode 100644 src/svg/iconfinder_icon-social-rss_211914.svg create mode 100644 src/svg/json_feed_icon.svg create mode 100644 static/images/json-feed-logo.png create mode 100644 static/images/rss-feed-logo.png diff --git a/package-lock.json b/package-lock.json index 0198730..12d60b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1932,6 +1932,14 @@ "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==" }, + "feed": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/feed/-/feed-4.2.0.tgz", + "integrity": "sha512-nLU4Fn+5TCJ1Zu9kBDqXPxsaTXaL/hZgZ3pmT87TUzS1kfaL91iIKJ+DFWygL8CrOeYw80z7QWxabkMV/x+g2g==", + "requires": { + "xml-js": "^1.6.11" + } + }, "file-type": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", @@ -4037,6 +4045,14 @@ "xtend": "^4.0.0" } }, + "xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "requires": { + "sax": "^1.2.4" + } + }, "xml-parse-from-string": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", diff --git a/package.json b/package.json index 1eafae2..1ca920b 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "classnames": "^2.2.6", "compression": "^1.7.4", "date-fns": "^2.11.1", + "feed": "^4.2.0", "front-matter": "^3.1.0", "marked": "^0.8.2", "polka": "^0.5.2", diff --git a/src/components/Footer.svelte b/src/components/Footer.svelte index 4f4d67b..a4a2c26 100644 --- a/src/components/Footer.svelte +++ b/src/components/Footer.svelte @@ -5,6 +5,8 @@ import twitchLogo from '../svg/iconfinder_twitch_306173.svg' import instagramLogo from '../svg/iconfinder_38-instagram_1161953.svg' import emailIcon from '../svg/iconfinder_mail_5474819.svg' + import rssIcon from '../svg/iconfinder_icon-social-rss_211914.svg' + import jsonFeedIcon from '../svg/json_feed_icon.svg' export let latestPosts @@ -65,7 +67,7 @@ .twitter :global(svg path) { /* fill: rgb(29, 161, 242); */ - stroke: #fff; + stroke: #eae9be; stroke-width: 2px; fill: #eae9be; } @@ -85,6 +87,14 @@ fill: #eae9be; } + .rss :global(svg) { + fill: #eae9be; + } + + .json-feed :global(svg) { + fill: #eae9be; + } + :global(svg) { height: 1em; width: 1em; @@ -110,6 +120,15 @@ color: #a7a574; } + .subscribe { + font-weight: bold; + } + + hr { + color: #86856f; + margin: 0.2em 0; + } + @media only screen and (min-width: 900px) { .site-footer { font-size: 0.8em; @@ -182,6 +201,13 @@ {/each} +
+
+ + Subscribe {@html rssIcon} + + {@html jsonFeedIcon} +

Contact

diff --git a/src/routes/blog/_content.js b/src/routes/blog/_content.js new file mode 100644 index 0000000..ea9f0b8 --- /dev/null +++ b/src/routes/blog/_content.js @@ -0,0 +1,55 @@ +import { readdir, readFile } from 'fs' +import { promisify } from 'util' +import { basename } from 'path' +import { pipe, partial, prop, sortBy, reverse, filter } from 'ramda' +import fm from 'front-matter' +import marked from 'marked' + +const { NODE_ENV } = process.env + +export async function getBlogListing(tag) { + const files = await promisify(readdir)(`_posts/blog/`, 'utf-8') + const filteredFiles = filterDevelopmentFiles(files) + + const contents = await Promise.all( + filteredFiles.map(async file => { + const fileContent = await promisify(readFile)( + `_posts/blog/${file}`, + 'utf-8' + ) + const parsedAttributes = fm(fileContent) + + const lineOfTextRegExp = /^(?:\w|\[).+/gm + const lines = parsedAttributes.body + .match(lineOfTextRegExp) + .slice(0, 2) + .join('\n') + + const preview = marked(lines) + return { + ...parsedAttributes.attributes, + preview, + slug: basename(file, '.md'), + } + }) + ) + const filteredContents = pipe( + sortBy(prop('date')), + reverse, + filter(article => article.published), + partial(filterByTag, [tag]) + )(contents) + + return filteredContents +} + +function filterDevelopmentFiles(files) { + return NODE_ENV !== 'production' + ? files + : files.filter(file => !file.startsWith('dev-')) +} + +function filterByTag(tag, contents) { + return tag ? contents.filter(content => content.tags.includes(tag)) : contents +} + diff --git a/src/routes/blog/index.json.js b/src/routes/blog/index.json.js index 671b639..770d759 100644 --- a/src/routes/blog/index.json.js +++ b/src/routes/blog/index.json.js @@ -1,63 +1,10 @@ -import { readdir, readFile } from 'fs' -import { promisify } from 'util' -import { basename } from 'path' -import { pipe, partial, prop, sortBy, reverse, filter } from 'ramda' -import fm from 'front-matter' -import marked from 'marked' - -const { NODE_ENV } = process.env +import { getBlogListing } from './_content' export async function get(req, res) { const { tag } = req.query - const files = await promisify(readdir)(`_posts/blog/`, 'utf-8') - const filteredFiles = filterDevelopmentFiles(files) - - const contents = await Promise.all( - filteredFiles.map(async file => { - const fileContent = await promisify(readFile)( - `_posts/blog/${file}`, - 'utf-8' - ) - const parsedAttributes = fm(fileContent) - - const lineOfTextRegExp = /^(?:\w|\[).+/gm - const sentenceRegExp = /(?:\w|\[).[.?!]/ - const lines = parsedAttributes.body - .match(lineOfTextRegExp) - .slice(0, 2) - .join('\n') - - const preview = marked(lines) - return { - ...parsedAttributes.attributes, - preview, - slug: basename(file, '.md'), - } - }) - ) - const filteredContents = pipe( - sortBy(prop('date')), - reverse, - filter(article => article.published), - partial(filterByTag, [tag]) - )(contents) - + const filteredContents = await getBlogListing(tag) res.writeHead(200, { 'Content-Type': 'application/json', }) res.end(JSON.stringify(filteredContents)) } - -function filterDevelopmentFiles(files) { - return NODE_ENV !== 'production' - ? files - : files.filter(file => !file.startsWith('dev-')) -} - -function filterByTag(tag, contents) { - return tag ? contents.filter(content => content.tags.includes(tag)) : contents -} - -function filterPublished(article) { - return article.published -} diff --git a/src/routes/feed/_feed.js b/src/routes/feed/_feed.js new file mode 100644 index 0000000..0629aa4 --- /dev/null +++ b/src/routes/feed/_feed.js @@ -0,0 +1,39 @@ +import { Feed } from 'feed' +import { getBlogListing } from '../blog/_content' + +export async function getFeed() { + const feed = new Feed({ + title: 'michalvanko.dev latest posts', + id: 'https://michalvanko.dev', + link: 'https://michalvanko.dev', + description: 'Latest posts published on michalvanko.dev', + copyright: 'All rights reserved 2020, Michal Vanko', + generator: 'sapper with Feed for node.js', + updated: new Date(), + image: 'https://michalvanko.dev/eye.png', + favicon: 'https://michalvanko.dev/m-favicon-192x192.png', + language: 'en', + author: { + name: 'Michal Vanko', + email: 'michalvankosk@gmail.com', + link: 'https://michalvanko.dev', + }, + feedLinks: { + json: 'https://michalvanko.dev/feed.json', + rss: 'https://michalvanko.dev/feed.xml', + }, + }) + + const blogListing = await getBlogListing() + blogListing.forEach(post => { + feed.addItem({ + title: post.title, + id: `https://michalvanko.dev/blog/${post.slug}`, + link: `https://michalvanko.dev/blog/${post.slug}`, + description: post.preview, + date: post.date, + image: post.thumbnail ? `https://michalvanko.dev/${post.thumbnail}` : undefined, + }) + }) + return feed +} diff --git a/src/routes/feed/index.json.js b/src/routes/feed/index.json.js new file mode 100644 index 0000000..ee6edd9 --- /dev/null +++ b/src/routes/feed/index.json.js @@ -0,0 +1,11 @@ +import { getFeed } from './_feed' + +export async function get(req, res) { + const feed = await getFeed() + + res.writeHead(200, { + 'Content-Type': 'application/json', + }) + res.end(feed.json1()) +} + diff --git a/src/routes/feed/index.xml.js b/src/routes/feed/index.xml.js new file mode 100644 index 0000000..d2cb394 --- /dev/null +++ b/src/routes/feed/index.xml.js @@ -0,0 +1,11 @@ +import { getFeed } from './_feed' + +export async function get(req, res) { + const feed = await getFeed() + + res.writeHead(200, { + 'Content-Type': 'application/xml', + }) + res.end(feed.rss2()) +} + diff --git a/src/routes/index.svelte b/src/routes/index.svelte index 67a21e5..078ba81 100644 --- a/src/routes/index.svelte +++ b/src/routes/index.svelte @@ -33,7 +33,11 @@ - michalvanko.dev index page + Introduction @michalvankodev + + + +
diff --git a/src/svg/icon.svg b/src/svg/icon.svg new file mode 100644 index 0000000..2462a80 --- /dev/null +++ b/src/svg/icon.svg @@ -0,0 +1,20 @@ + + + + icon + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/svg/iconfinder_icon-social-rss_211914.svg b/src/svg/iconfinder_icon-social-rss_211914.svg new file mode 100644 index 0000000..e5fd969 --- /dev/null +++ b/src/svg/iconfinder_icon-social-rss_211914.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/svg/json_feed_icon.svg b/src/svg/json_feed_icon.svg new file mode 100644 index 0000000..9640626 --- /dev/null +++ b/src/svg/json_feed_icon.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + diff --git a/static/images/json-feed-logo.png b/static/images/json-feed-logo.png new file mode 100644 index 0000000..4c54476 --- /dev/null +++ b/static/images/json-feed-logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7d7e2a76afa2cf812aae1a3bd2b95391698a836ce02a9ff0ba2d0e97005d7a40 +size 4068 diff --git a/static/images/rss-feed-logo.png b/static/images/rss-feed-logo.png new file mode 100644 index 0000000..33d39c5 --- /dev/null +++ b/static/images/rss-feed-logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:efe269e45f1ffdb8dfe229406efd6e40f76334c1de0df6fa88e24ac6e0da409b +size 4375