From cce07b52712ea6f3b8a9e8e5f4b072c4278bd023 Mon Sep 17 00:00:00 2001 From: Ben Beltran Date: Mon, 1 Jun 2020 13:28:28 +0200 Subject: Add the spec for this feature --- doc/specs/20200601-serving-different-versions.md | 198 +++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 doc/specs/20200601-serving-different-versions.md diff --git a/doc/specs/20200601-serving-different-versions.md b/doc/specs/20200601-serving-different-versions.md new file mode 100644 index 0000000..351cc8c --- /dev/null +++ b/doc/specs/20200601-serving-different-versions.md @@ -0,0 +1,198 @@ +# Problem + +Feed reader users should be able to subscribe to the blog + +# Background + +As of this writing, the blog is served as HTML which is appropriate for +web browsers but maybe not for other mechanisms like feed readers. + +Feed readers have different formats that they support: + * h-feed is a microformat built into the page + * rss and atom are XML based publishing formats + * JSON feed is a JSON based publishing format + * rss 3.0 is a text based pblishing format :P + +Currently the blog contains a single generator function that copies +assets and generates HTML out of markdown files. This is good enough for +the current setup, but if it were to generate more it would get messy +real quick. + +Given the constraints listed below, some formats are not recommended: + * RSS 3.0 is not a good candidate at the moment as it would require + us to parse the markdown to extract the title. + * Atom would work, however, given the requirement for an id, title, and + date this would require more effort than a more lenient format. + * RSS 2.0 fits the constraints as we wouldn't need to specify anything + for the item. + * JSON Feed would work, however given the requirement for an id, thtis + would require more effort than a more lenient format. + +It is unclear whether the current constraints are good enough for feed +readers. If this causes issues, it's likely we will have to include date, +id or title as required in the other formats. + +After reviewing the functionality of existing readers, it has been found +that an id and publication date would be needed for readers to behave +correctly. This means that ATOM and JSON Feed would be equally valid +as solutions than RSS 2.0 + +The current generator function depends on knowing a source for the post +being generated, and a target on where the assets will be placed. + +# Hypothesis + +Given we serve the blog in a feed reader friendly format, users will be able to subscribe. + +# Test of Hypothesis + +Given I add the blog to a feed reader service like Reeder or Feedly, I will be able to see the entries. +Given I add a new entry to the blog, the entries will be updated. + +# Assumptions + +* We can generate a valid feed with just the entries themselves and the existing + blog data. + * We can: Validated by generating an example file. +* Including just a list of items with the whole content is good enough for + feed readers. + * We can't: It seems like we'll require at least a guid. The old reader + behaves correctly with just the guid. It's unclear whether feedly + does since it has caching. Will leave grok running. +* It isn't required to link back, and we can include the whole text. + * This is correct, however it might make sense to just link to the + blog itself. + +# Constraints + +* We won't be parsing the markdown to generate feed items. +* We won't be adding any sort of frontmatter to the entries. +* The blog will remain ephemeral, and we won't introduce permalinks. +* We won't have configurable templating or options to add/remove + output types. + +# Solution Proposal + +(Note: This solution is incomplete. We need to add guid and pubDate +generation, as this is required for the RSS to be usable) + +We will split the current generator function into generators, and create +a new generator that will generate an RSS 2.0 file + +# Blackbox + +``` + ┌─────────────────┐ ┌───────────────┐ + │ │ │ │ + ┌─────▶│ StaticGenerator │──────────▶│ Static Assets │ + │ │ │ │ │ + │ └─────────────────┘ └───────────────┘ +┌───────┐ │ ┌───────────────┐ ┌───────────┐ +│ │ │ │ │ │ │ +│ Blog │──────┼─────▶│ HTMLGenerator │────────────▶│ HTML File │ +│ │ │ │ │ │ │ +└───────┘ │ └───────────────┘ └───────────┘ + │ ┌──────────────┐ ┌──────────┐ + │ │ │ │ │ + └─────▶│ RSSGenerator │─────────────▶│ RSS File │ + │ │ │ │ + └──────────────┘ └──────────┘ +``` + +# Theory of Operation + +When the generate function of the blog is triggered, it will iterate over +a list of generator functions and call them with the source and target +directories, and an array containing the parsed markdown from the post. +Each generator function will do its work, throwing an exception if they +encounter an error. + +When the static generator is called, it will remove the current assets +directory in the target directory, and recursively copy the assets from +the source directory. + +When the HTML generator is called, it will parse an `html` template, using +the posts as the context, and will place the resulting file in the target +directory. + +When the RSS generator is called, it will parse an `rss` template, using +the posts as the context, and will place the resulting file in the target +directory. + +# Technical Specification + +## The Generator Interface + +Every generator must implement this interface in order to work with +Blog. + +* Generators MUST be a function +* Generators SHOULD read the source, destination, and posts parameters to + write files. +* Generators MUST NOT write anything into the source directory +* Generators MUST return a promise +* Generators SHOULD NOT resolve the promise with any information, as it will + be discarded +* Generators MUST throw exceptions if they encounter an unrecoverable error + +``` +IGenerator(source, destination, posts>) => Promise +``` + +## New Generators + +### Static Generator + +This generator will have the logic to move static assets around. It will +re-use the current asset logic in the `#_generate` method in Blog. + +``` +StaticGenerator +``` + +### HTML Generator + +This generator will have the logic to generate an HTML file. It will +re-use the current HTML logic in the `#_generate` method in Blog. + +``` +HTMLGenerator +``` + +### RSS Generator + +This generator will have the logic to generate an RSS file. It will +re-use the current HTML logic in the `#_generate` method in Blog, +however, instead of using the `index.html` template it will use a +`feed.xml` template that generates a valid RSS 2.0 feed document. + +``` +RSSGenerator +``` + +## Modifications to existing parts of the code + +The `#_generate` function will be modified so it will now parse the +post markdown, and then iterate over the generators, calling them +so they create the appropriatet files. + +## Important Metrics + +Given we're only processing 3 blog posts, and this is a compile time +activity and not runtime, there are no recommended metrics in terms +of file throughput performance or runtime performance. + +This should change if this would ever handle a higher volume, or would +be expected to run this process runtime. + +## Furhter Improvements + +It's recommended to eventually put more effort in assigning a unique ID +to each post so we can use more feed formats. + +For more compatibility and future proofing, the same solution for +RSS could be used to generate other feed formats, just adding +a new generator + +This same solution could be extended to serve the blog in different formats +(eg. a .txt that is easy to read in terminals) -- cgit From ae442b8f192ab0f9d4de483462d5d18c43d9ec23 Mon Sep 17 00:00:00 2001 From: Ben Beltran Date: Mon, 1 Jun 2020 18:53:59 +0200 Subject: Update spec witth the metadata step --- doc/specs/20200601-serving-different-versions.md | 82 +++++++++++++++++------- 1 file changed, 60 insertions(+), 22 deletions(-) diff --git a/doc/specs/20200601-serving-different-versions.md b/doc/specs/20200601-serving-different-versions.md index 351cc8c..545febb 100644 --- a/doc/specs/20200601-serving-different-versions.md +++ b/doc/specs/20200601-serving-different-versions.md @@ -73,8 +73,9 @@ Given I add a new entry to the blog, the entries will be updated. # Solution Proposal -(Note: This solution is incomplete. We need to add guid and pubDate -generation, as this is required for the RSS to be usable) +We will add a new step in the creation process to create metadata for the +post that will allow each post to be uniquely identified, as well as +having a publish date related to them. We will split the current generator function into generators, and create a new generator that will generate an RSS 2.0 file @@ -82,30 +83,56 @@ a new generator that will generate an RSS 2.0 file # Blackbox ``` - ┌─────────────────┐ ┌───────────────┐ - │ │ │ │ - ┌─────▶│ StaticGenerator │──────────▶│ Static Assets │ - │ │ │ │ │ - │ └─────────────────┘ └───────────────┘ -┌───────┐ │ ┌───────────────┐ ┌───────────┐ -│ │ │ │ │ │ │ -│ Blog │──────┼─────▶│ HTMLGenerator │────────────▶│ HTML File │ -│ │ │ │ │ │ │ -└───────┘ │ └───────────────┘ └───────────┘ - │ ┌──────────────┐ ┌──────────┐ - │ │ │ │ │ - └─────▶│ RSSGenerator │─────────────▶│ RSS File │ - │ │ │ │ - └──────────────┘ └──────────┘ + ╔══════════════════════╗ + ║ When Adding a Post ║ + ╚══════════════════════╝ + ┌───────────────┐ ┌───────────────┐ + │ │ │ │ + ┌────────────────▶│ writeMetadata │─────────▶│ Metadata File │ + │ │ │ │ │ + │ └───────────────┘ └───────────────┘ + │ + │ + │ ╔════════════════════════╗ + │ ║ When Generating Output ║ + │ ╚════════════════════════╝ + │ ┌─────────────────┐ ┌───────────────┐ + │ │ │ │ │ + │ ┌─────▶│ StaticGenerator │───────▶│ Static Assets │ + │ │ │ │ │ │ + │ │ └─────────────────┘ └───────────────┘ +┌───────┐ │ ┌───────────────┐ ┌───────────┐ +│ │ │ │ │ │ │ +│ Blog │──────┼─────▶│ HTMLGenerator │─────────▶│ HTML File │ +│ │ │ │ │ │ │ +└───────┘ │ └───────────────┘ └───────────┘ + │ ┌──────────────┐ ┌──────────┐ + │ │ │ │ │ + └─────▶│ RSSGenerator │──────────▶│ RSS File │ + │ │ │ │ + └──────────────┘ └──────────┘ ``` # Theory of Operation -When the generate function of the blog is triggered, it will iterate over -a list of generator functions and call them with the source and target -directories, and an array containing the parsed markdown from the post. -Each generator function will do its work, throwing an exception if they -encounter an error. +## When Adding a Post + +When the add function of the blog is triggered, it will shift the posts +as it currently does and then will generate a new UUID and take the +current timestamp. This will be saved in a JSON file in the output +directory called "metadata.json" + +## When Generating Output + +When the generate function of the blog is triggered, it will iterate +over every post. For each of them it will parse the markdown content, +and the metadata, creating an object of type `tPost` and pushing it +to an array. + +Next, it will iterate from a list of generator functions and call them +with the source and target directories, and an array containing the `tPost` +objects. Each generator function will do its work, throwing an exception +if they encounter an error. When the static generator is called, it will remove the current assets directory in the target directory, and recursively copy the assets from @@ -121,6 +148,17 @@ directory. # Technical Specification +## The Post Data Structure + +This spec introduces a data structure to help generate output. + +``` +tPost + +html // The markup of the post + +publishedOn // The timestamp when this post was added + +uuid // The UUID for this post +``` + ## The Generator Interface Every generator must implement this interface in order to work with -- cgit From d2dd1134bc8065f001ebb3feff54a309098f861a Mon Sep 17 00:00:00 2001 From: Ben Beltran Date: Mon, 1 Jun 2020 22:43:53 +0200 Subject: Clarify type of id used --- doc/specs/20200601-serving-different-versions.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/specs/20200601-serving-different-versions.md b/doc/specs/20200601-serving-different-versions.md index 545febb..9eb1bd8 100644 --- a/doc/specs/20200601-serving-different-versions.md +++ b/doc/specs/20200601-serving-different-versions.md @@ -118,7 +118,7 @@ a new generator that will generate an RSS 2.0 file ## When Adding a Post When the add function of the blog is triggered, it will shift the posts -as it currently does and then will generate a new UUID and take the +as it currently does and then will generate a new unique ID and take the current timestamp. This will be saved in a JSON file in the output directory called "metadata.json" @@ -156,9 +156,14 @@ This spec introduces a data structure to help generate output. tPost +html // The markup of the post +publishedOn // The timestamp when this post was added - +uuid // The UUID for this post + +id // The Unique ID for this post ``` +Given that posts won't come in at a high enough rate, and that the +purpouse is only to help feed readers identify each unique piece of +content, for this version the `id` will be the same number as the +`publishedOn`. + ## The Generator Interface Every generator must implement this interface in order to work with -- cgit From 6f72ad0f8b1d147a3d2e27db539a0e17ca2ac917 Mon Sep 17 00:00:00 2001 From: Ben Beltran Date: Mon, 1 Jun 2020 22:44:39 +0200 Subject: Add metadata writing functionality --- lib/blog.js | 48 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/lib/blog.js b/lib/blog.js index 9024a00..6b3397f 100644 --- a/lib/blog.js +++ b/lib/blog.js @@ -22,7 +22,7 @@ const internals = { kIndexName: 'index.html', kFileNotFoundError: 'ENOENT', kMarkdownRe: /\.md$/i, - kRemoveCommand: 'rm -rf', + kMetadataFilename: 'metadata.json', // Strings @@ -59,6 +59,7 @@ module.exports = class Blog { */ async add(postLocation) { + await this._ensurePostsDirectoryExists(); await this._shift(); await this.update(postLocation); } @@ -75,7 +76,11 @@ module.exports = class Blog { */ async update(postLocation) { + const metadata = await this._getMetadata(); + await this._ensurePostsDirectoryExists(); await this._copyPost(postLocation); + await this._writeMetadata(metadata); + await this._generate(); } @@ -148,18 +153,17 @@ module.exports = class Blog { async _shift() { - await this._ensurePostsDirectoryExists(); - for (let i = this.maxPosts - 1; i > 0; --i) { + for (let i = this.maxPosts - 1; i >= 0; --i) { const targetPath = join(this.postsDirectory, `${i}`); const sourcePath = join(this.postsDirectory, `${i - 1}`); try { - await access(sourcePath); - internals.debuglog(`Removing ${targetPath}`); await rmdir(targetPath, { recursive: true }); + await access(sourcePath); // check the source path + internals.debuglog(`Shifting blog post ${sourcePath} to ${targetPath}`); await internals.ncp(sourcePath, targetPath); } @@ -174,12 +178,42 @@ module.exports = class Blog { } } + // Attempts to read existing metadata. Otherwise generates new set. + + async _getMetadata() { + + const metadataTarget = join(this.postsDirectory, '0', internals.kMetadataFilename); + + try { + internals.debuglog(`Looking for metadata at ${metadataTarget}`); + return await readFile(metadataTarget); + } + catch (e) { + internals.debuglog(`Metadata not found or unreadable. Generating new set.`); + const createdOn = Date.now(); + const metadata = { + id: String(createdOn), + createdOn + }; + + return JSON.stringify(metadata, null, 2); + } + } + + // Writes metadata. Assumes post 0 since it only gets written + // on create + + async _writeMetadata(metadata) { + + const metadataTarget = join(this.postsDirectory, '0', internals.kMetadataFilename); + internals.debuglog(`Writing ${metadataTarget}`); + await writeFile(metadataTarget, metadata); + } + // Copies a post directory to the latest slot. async _copyPost(postLocation) { - await this._ensurePostsDirectoryExists(); - const targetPath = join(this.postsDirectory, '0'); internals.debuglog(`Removing ${targetPath}`); -- cgit From 67fdfa7cc2e1690657cd0c7190373847de25311a Mon Sep 17 00:00:00 2001 From: Ben Beltran Date: Mon, 1 Jun 2020 23:33:40 +0200 Subject: Use new generators --- lib/blog.js | 65 ++++++++++++++++++++++++------------------------ lib/generators/html.js | 35 ++++++++++++++++++++++++++ lib/generators/rss.js | 42 +++++++++++++++++++++++++++++++ lib/generators/static.js | 50 +++++++++++++++++++++++++++++++++++++ 4 files changed, 160 insertions(+), 32 deletions(-) create mode 100644 lib/generators/html.js create mode 100644 lib/generators/rss.js create mode 100644 lib/generators/static.js diff --git a/lib/blog.js b/lib/blog.js index 6b3397f..98efabd 100644 --- a/lib/blog.js +++ b/lib/blog.js @@ -1,25 +1,24 @@ 'use strict'; -const { exec } = require('child_process'); const { access, mkdir, readdir, readFile, rmdir, writeFile } = require('fs/promises'); -const { template } = require('dot'); const { ncp } = require('ncp'); const { join } = require('path'); const Marked = require('marked'); const { debuglog, promisify } = require('util'); +const StaticGenerator = require('./generators/static'); +const HTMLGenerator = require('./generators/html'); +const RSSGenerator = require('./generators/rss'); + const internals = { // Promisified functions ncp: promisify(ncp), - exec: promisify(exec), debuglog: debuglog('blog'), // constants - kAssetsDirectoryName: 'assets', - kIndexName: 'index.html', kFileNotFoundError: 'ENOENT', kMarkdownRe: /\.md$/i, kMetadataFilename: 'metadata.json', @@ -36,7 +35,7 @@ const internals = { * updating posts, and handling the publishing. * * @class Blog - * @param {Potluck.tConfiguration} config the initialization options to + * @param {Blog.tConfiguration} config the initialization options to * extend the instance */ module.exports = class Blog { @@ -102,33 +101,40 @@ module.exports = class Blog { async _generate() { - const assetsTarget = join(this.staticDirectory, internals.kAssetsDirectoryName); - const indexTarget = join(this.staticDirectory, internals.kIndexName); - const indexLocation = join(this.templatesDirectory, internals.kIndexName); - const posts = []; + internals.debuglog('Generating output'); + + const posts = await this._readPosts(this.postsDirectory); + + await StaticGenerator(this.postsDirectory, this.staticDirectory, posts); + await HTMLGenerator(this.templatesDirectory, this.staticDirectory, posts); + await RSSGenerator(this.templatesDirectory, this.staticDirectory, posts); + } + + // Reads the posts into an array - internals.debuglog(`Removing ${assetsTarget}`); - await rmdir(assetsTarget, { recursive: true }); + async _readPosts(source) { + + internals.debuglog('Reading posts'); + const posts = []; for (let i = 0; i < this.maxPosts; ++i) { - const sourcePath = join(this.postsDirectory, `${i}`); + const postSourcePath = join(source, `${i}`); - try { - await access(this.postsDirectory); + internals.debuglog(`Reading ${postSourcePath} into posts array`); - const assetsSource = join(sourcePath, internals.kAssetsDirectoryName); - const postContentPath = await this._findBlogContent(sourcePath); + try { + await access(postSourcePath); - internals.debuglog(`Copying ${assetsSource} to ${assetsTarget}`); - await internals.ncp(assetsSource, assetsTarget); + const metadata = await this._getMetadata(i); + const postContentPath = await this._findBlogContent(postSourcePath); internals.debuglog(`Reading ${postContentPath}`); const postContent = await readFile(postContentPath, { encoding: 'utf8' }); internals.debuglog('Parsing markdown'); posts.push({ - html: Marked(postContent), - id: i + 1 + ...metadata, + html: Marked(postContent) }); } catch (error) { @@ -141,12 +147,7 @@ module.exports = class Blog { } } - internals.debuglog(`Reading ${indexLocation}`); - const indexTemplate = await readFile(indexLocation, { encoding: 'utf8' }); - - internals.debuglog('Generating HTML'); - const indexHtml = template(indexTemplate)({ posts }); - await writeFile(indexTarget, indexHtml); + return posts; } // Shift the posts, delete any remainder. @@ -180,13 +181,13 @@ module.exports = class Blog { // Attempts to read existing metadata. Otherwise generates new set. - async _getMetadata() { + async _getMetadata(index = 0) { - const metadataTarget = join(this.postsDirectory, '0', internals.kMetadataFilename); + const metadataTarget = join(this.postsDirectory, String(index), internals.kMetadataFilename); try { internals.debuglog(`Looking for metadata at ${metadataTarget}`); - return await readFile(metadataTarget); + return JSON.parse(await readFile(metadataTarget, { encoding: 'utf8' })); } catch (e) { internals.debuglog(`Metadata not found or unreadable. Generating new set.`); @@ -196,7 +197,7 @@ module.exports = class Blog { createdOn }; - return JSON.stringify(metadata, null, 2); + return metadata; } } @@ -207,7 +208,7 @@ module.exports = class Blog { const metadataTarget = join(this.postsDirectory, '0', internals.kMetadataFilename); internals.debuglog(`Writing ${metadataTarget}`); - await writeFile(metadataTarget, metadata); + await writeFile(metadataTarget, JSON.stringify(metadata, null, 2)); } // Copies a post directory to the latest slot. diff --git a/lib/generators/html.js b/lib/generators/html.js new file mode 100644 index 0000000..ba2676c --- /dev/null +++ b/lib/generators/html.js @@ -0,0 +1,35 @@ +'use strict'; + +const { template } = require('dot'); +const { readFile, writeFile } = require('fs/promises'); +const { join } = require('path'); +const { debuglog } = require('util'); + +const internals = { + debuglog: debuglog('blog'), + + kIndexName: 'index.html' +}; + +/** + * Generates the blog index page + * + * @name HTMLGenerator + * @param {string} source the source directory + * @param {string} target the target directory + * @param {Array.} posts the list of posts + */ +module.exports = async function HTMLGenerator(source, target, posts) { + + internals.debuglog('Generating HTML'); + const indexTarget = join(target, internals.kIndexName); + const indexLocation = join(source, internals.kIndexName); + + internals.debuglog(`Reading ${indexLocation}`); + const indexTemplate = await readFile(indexLocation, { encoding: 'utf8' }); + + internals.debuglog('Writing HTML'); + const indexHtml = template(indexTemplate)({ posts }); + await writeFile(indexTarget, indexHtml); +}; + diff --git a/lib/generators/rss.js b/lib/generators/rss.js new file mode 100644 index 0000000..a1fcb08 --- /dev/null +++ b/lib/generators/rss.js @@ -0,0 +1,42 @@ +'use strict'; + +const { template } = require('dot'); +const { encodeXML } = require('entities'); +const { readFile, writeFile } = require('fs/promises'); +const { join } = require('path'); +const { debuglog } = require('util'); + +const internals = { + debuglog: debuglog('blog'), + + kFeedName: 'feed.xml' +}; + +/** + * Generates an RSS feed XML file + * + * @name RSSGenerator + * @param {string} source the source directory + * @param {string} target the target directory + * @param {Array.} posts the list of posts + */ +module.exports = async function RSSGenerator(source, target, posts) { + + internals.debuglog('Generating RSS'); + const feedTarget = join(target, internals.kFeedName); + const feedLocation = join(source, internals.kFeedName); + + internals.debuglog(`Reading ${feedLocation}`); + const feedTemplate = await readFile(feedLocation, { encoding: 'utf8' }); + + internals.debuglog('Writing RSS'); + posts = posts.map((post) => ({ + ...post, + createdOn: (new Date(post.createdOn)).toUTCString(), + html: encodeXML(post.html) + })); + const feedXml = template(feedTemplate)({ posts }); + await writeFile(feedTarget, feedXml); +}; + + diff --git a/lib/generators/static.js b/lib/generators/static.js new file mode 100644 index 0000000..eb3c631 --- /dev/null +++ b/lib/generators/static.js @@ -0,0 +1,50 @@ +'use strict'; + +const { access, rmdir } = require('fs/promises'); +const { ncp } = require('ncp'); +const { join } = require('path'); +const { debuglog, promisify } = require('util'); + +const internals = { + ncp: promisify(ncp), + debuglog: debuglog('blog'), + + kAssetsDirectoryName: 'assets' +}; + +/** + * Generates the static assets required for the blog + * + * @name StaticGenerator + * @param {string} source the source directory + * @param {string} target the target directory + * @param {Array.} posts the list of posts + */ +module.exports = async function StaticGenerator(source, target, posts) { + + const assetsTarget = join(target, internals.kAssetsDirectoryName); + + internals.debuglog(`Removing ${assetsTarget}`); + await rmdir(assetsTarget, { recursive: true }); + + for (let i = 0; i < posts.length; ++i) { + const postSourcePath = join(source, `${i}`); + + try { + await access(postSourcePath); + + const assetsSource = join(postSourcePath, internals.kAssetsDirectoryName); + + internals.debuglog(`Copying ${assetsSource} to ${assetsTarget}`); + await internals.ncp(assetsSource, assetsTarget); + } + catch (error) { + if (error.code === internals.kFileNotFoundError) { + internals.debuglog(`Skipping ${i}`); + continue; + } + + throw error; + } + } +}; -- cgit From 96c7a635e540d675ca06d004cfe77e71b74471c7 Mon Sep 17 00:00:00 2001 From: Ben Beltran Date: Mon, 1 Jun 2020 23:34:00 +0200 Subject: Add h-feed fields to markup --- templates/index.html | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/templates/index.html b/templates/index.html index 0c45ce2..3e37304 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1,28 +1,33 @@ - + + + - blog 🍕 + + + Blog at Unlimited Pizza 🍕 - + + -
+
Blog
{{~ it.posts: post}} -
+
{{= post.html}}

{{~}} - {{? it.posts}} + {{? it.posts.length === 0}}

This is a fresh blog!

There are no posts yet.

{{?}} -- cgit From 53d4936e24ea996978eed58b370247606b25bd77 Mon Sep 17 00:00:00 2001 From: Ben Beltran Date: Mon, 1 Jun 2020 23:34:39 +0200 Subject: Add entities parser --- package-lock.json | 3 +-- package.json | 7 ++++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 939bce7..eca5389 100644 --- a/package-lock.json +++ b/package-lock.json @@ -536,8 +536,7 @@ "entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.2.tgz", - "integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw==", - "dev": true + "integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw==" }, "escape-string-regexp": { "version": "1.0.5", diff --git a/package.json b/package.json index d127963..f2ed098 100644 --- a/package.json +++ b/package.json @@ -22,11 +22,12 @@ }, "homepage": "https://gitlab.com/rbdr/blog#readme", "dependencies": { + "dot": "^1.1.3", + "entities": "^2.0.2", "getenv": "^1.0.0", + "marked": "^1.0.0", "minimist": "^1.2.5", - "dot": "^1.1.3", - "ncp": "^2.0.0", - "marked": "^1.0.0" + "ncp": "^2.0.0" }, "devDependencies": { "eslint": "^7.1.0", -- cgit From de0ee360d0317da471912be6a3041fc95448b018 Mon Sep 17 00:00:00 2001 From: Ben Beltran Date: Mon, 1 Jun 2020 23:35:20 +0200 Subject: Add RSS template --- templates/feed.xml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 templates/feed.xml diff --git a/templates/feed.xml b/templates/feed.xml new file mode 100644 index 0000000..d22d656 --- /dev/null +++ b/templates/feed.xml @@ -0,0 +1,18 @@ + + + + Blog at Unlimited Pizza 🍕 + https://blog.unlimited.pizza + This is the blog at unlimited.pizza + en + {{~ it.posts: post}} + + unlimited-pizza:{{= post.id}} + {{= post.createdOn}} + + {{= post.html}} + + + {{~}} + + -- cgit From cbd8c5bc0cc74fe2a8cb02ac4832ccc228e079be Mon Sep 17 00:00:00 2001 From: Ben Beltran Date: Mon, 1 Jun 2020 23:37:07 +0200 Subject: Ignore static directory --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 997ace3..790ecb1 100644 --- a/.gitignore +++ b/.gitignore @@ -64,5 +64,4 @@ typings/ .posts # Generated files -static/assets -static/index.html +static -- cgit From 04b71c5478dea42937a3288aed9598663c6dab0d Mon Sep 17 00:00:00 2001 From: Ben Beltran Date: Tue, 2 Jun 2020 21:34:05 +0200 Subject: Update CHANGELOG --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a643cc0..1e3b1f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). +## Unreleased +### Added +- Post Metadata support +- RSS Support + +### Changed +- Updated dependencies +- Replace jsdoc with jsdoc-to-markdown +- Replace showdown with marked +- Replace mustache with dot + ## [1.0.1] - 2017-07-03 ### Added - Add styling for code -- cgit