...
 
Commits (3)
{
"extends": "eslint-config-hapi",
"extends": "@hapi/eslint-config-hapi",
"parserOptions": {
"ecmaVersion": 2017
"ecmaVersion": 2020
},
"rules": {
"indent": [
......
# blog
Blog at unlimited.pizza -> Only
Blog at unlimited.pizza -> Only 3 at a time.
## How to install
......@@ -26,7 +26,7 @@ all entries and remove the oldest one if limit of posts is reached
`blog --add path/to/blog_post`
These commands will regenerate the static files. At that point you can
preview your blog by serving the files on the `static` directory.
preview your blog by serving the files on the `static` directory.
If you need to make corrections use:
......@@ -37,12 +37,18 @@ shifting the existing entries.
`blog --publish`
Will publish the blog.
Will publish the blog. (Currently unsupported)
## How to publish
At the moment, the app does not include any publishers. [surge][surge] is an easy
way to do it, just point it to your static directory.
## Debugging
If you want to know more about what's going on when blog generates
data, set the environment variable `NODE_DEBUG=blog`. This will
enable the debug messages
[surge]: https://surge.sh
......@@ -38,6 +38,7 @@ const internals = {
}
}
}
console.log('Not yet implemented');
}
catch (err) {
......
<a name="Blog"></a>
## Blog
**Kind**: global class
* [Blog](#Blog)
* [new Blog(config)](#new_Blog_new)
* [.add(postLocation)](#Blog+add)<code>Promise.&lt;undefined&gt;</code>
* [.update(postLocation)](#Blog+update)<code>Promise.&lt;undefined&gt;</code>
* [.publish()](#Blog+publish)<code>Promise.&lt;undefined&gt;</code>
<a name="new_Blog_new"></a>
### new Blog(config)
The Blog class is the blog generator, it's in charge of adding and
updating posts, and handling the publishing.
| Param | Type | Description |
| --- | --- | --- |
| config | <code>Potluck.tConfiguration</code> | the initialization options to extend the instance |
<a name="Blog+add"></a>
### blog.add(postLocation) ⇒ <code>Promise.&lt;undefined&gt;</code>
Shifts the blog posts, adds the passed path to slot 0, and
generates files.
**Kind**: instance method of [<code>Blog</code>](#Blog)
**Returns**: <code>Promise.&lt;undefined&gt;</code> - empty promise, returns no value
| Param | Type | Description |
| --- | --- | --- |
| postLocation | <code>string</code> | the path to the directory containing the post structure |
<a name="Blog+update"></a>
### blog.update(postLocation) ⇒ <code>Promise.&lt;undefined&gt;</code>
Adds the passed path to slot 0, and generates files.
**Kind**: instance method of [<code>Blog</code>](#Blog)
**Returns**: <code>Promise.&lt;undefined&gt;</code> - empty promise, returns no value
| Param | Type | Description |
| --- | --- | --- |
| postLocation | <code>string</code> | the path to the directory containing the post structure |
<a name="Blog+publish"></a>
### blog.publish() ⇒ <code>Promise.&lt;undefined&gt;</code>
Publishes the files to a static host.
**Kind**: instance method of [<code>Blog</code>](#Blog)
**Returns**: <code>Promise.&lt;undefined&gt;</code> - empty promise, returns no value
'use strict';
const Fs = require('fs');
const Mustache = require('mustache');
const Ncp = require('ncp');
const Path = require('path');
const Rimraf = require('rimraf');
const Showdown = require('showdown');
const Util = require('util');
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 internals = {
// Promisified functions
ncp: promisify(ncp),
fs: {
access: Util.promisify(Fs.access),
mkdir: Util.promisify(Fs.mkdir),
readdir: Util.promisify(Fs.readdir),
readFile: Util.promisify(Fs.readFile),
writeFile: Util.promisify(Fs.writeFile)
},
ncp: Util.promisify(Ncp.ncp),
rimraf: Util.promisify(Rimraf),
debuglog: Util.debuglog('blog'),
exec: promisify(exec),
debuglog: debuglog('blog'),
// constants
......@@ -29,6 +22,7 @@ const internals = {
kIndexName: 'index.html',
kFileNotFoundError: 'ENOENT',
kMarkdownRe: /\.md$/i,
kRemoveCommand: 'rm -rf',
// Strings
......@@ -93,42 +87,42 @@ module.exports = class Blog {
* @return {Promise<undefined>} empty promise, returns no value
* @instance
*/
async publish() {
publish() {
console.error('Publishing not yet implemented');
return Promise.resolve();
}
// Parses markdown for each page, copies assets and generates index.
async _generate() {
const assetsTarget = Path.join(this.staticDirectory, internals.kAssetsDirectoryName);
const indexTarget = Path.join(this.staticDirectory, internals.kIndexName);
const indexLocation = Path.join(this.templatesDirectory, internals.kIndexName);
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(`Removing ${assetsTarget}`);
await internals.rimraf(assetsTarget);
await rmdir(assetsTarget, { recursive: true });
for (let i = 0; i < this.maxPosts; ++i) {
const sourcePath = Path.join(this.postsDirectory, `${i}`);
const sourcePath = join(this.postsDirectory, `${i}`);
try {
await internals.fs.access(this.postsDirectory);
await access(this.postsDirectory);
const assetsSource = Path.join(sourcePath, internals.kAssetsDirectoryName);
const assetsSource = join(sourcePath, internals.kAssetsDirectoryName);
const postContentPath = await this._findBlogContent(sourcePath);
internals.debuglog(`Copying ${assetsSource} to ${assetsTarget}`);
await internals.ncp(assetsSource, assetsTarget);
internals.debuglog(`Reading ${postContentPath}`);
const postContent = await internals.fs.readFile(postContentPath, { encoding: 'utf8' });
const postContent = await readFile(postContentPath, { encoding: 'utf8' });
internals.debuglog('Parsing markdown');
const parser = new Showdown.Converter();
posts.push({
html: parser.makeHtml(postContent),
html: Marked(postContent),
id: i + 1
});
}
......@@ -143,11 +137,11 @@ module.exports = class Blog {
}
internals.debuglog(`Reading ${indexLocation}`);
const indexTemplate = await internals.fs.readFile(indexLocation, { encoding: 'utf8' });
const indexTemplate = await readFile(indexLocation, { encoding: 'utf8' });
internals.debuglog('Generating HTML');
const indexHtml = Mustache.render(indexTemplate, { posts });
await internals.fs.writeFile(indexTarget, indexHtml);
const indexHtml = template(indexTemplate)({ posts });
await writeFile(indexTarget, indexHtml);
}
// Shift the posts, delete any remainder.
......@@ -157,14 +151,14 @@ module.exports = class Blog {
await this._ensurePostsDirectoryExists();
for (let i = this.maxPosts - 1; i > 0; --i) {
const targetPath = Path.join(this.postsDirectory, `${i}`);
const sourcePath = Path.join(this.postsDirectory, `${i - 1}`);
const targetPath = join(this.postsDirectory, `${i}`);
const sourcePath = join(this.postsDirectory, `${i - 1}`);
try {
await internals.fs.access(sourcePath);
await access(sourcePath);
internals.debuglog(`Removing ${targetPath}`);
await internals.rimraf(targetPath);
await rmdir(targetPath, { recursive: true });
internals.debuglog(`Shifting blog post ${sourcePath} to ${targetPath}`);
await internals.ncp(sourcePath, targetPath);
......@@ -186,10 +180,10 @@ module.exports = class Blog {
await this._ensurePostsDirectoryExists();
const targetPath = Path.join(this.postsDirectory, '0');
const targetPath = join(this.postsDirectory, '0');
internals.debuglog(`Removing ${targetPath}`);
await internals.rimraf(targetPath);
await rmdir(targetPath, { recursive: true });
internals.debuglog(`Adding ${postLocation} to ${targetPath}`);
await internals.ncp(postLocation, targetPath);
......@@ -201,12 +195,12 @@ module.exports = class Blog {
internals.debuglog(`Checking if ${this.postsDirectory} exists.`);
try {
await internals.fs.access(this.postsDirectory);
await access(this.postsDirectory);
}
catch (error) {
if (error.code === internals.kFileNotFoundError) {
internals.debuglog('Creating posts directory');
await internals.fs.mkdir(this.postsDirectory);
await mkdir(this.postsDirectory);
return;
}
......@@ -218,11 +212,11 @@ module.exports = class Blog {
async _findBlogContent(directory) {
const entries = await internals.fs.readdir(directory);
const entries = await readdir(directory);
const markdownEntries = entries
.filter((entry) => internals.kMarkdownRe.test(entry))
.map((entry) => Path.join(directory, entry));
.map((entry) => join(directory, entry));
if (markdownEntries.length > 0) {
internals.debuglog(`Found markdown file: ${markdownEntries[0]}`);
......
This diff is collapsed.
{
"name": "blog",
"version": "1.0.1",
"version": "2.0.0-alpha1",
"description": "An ephemeral blog",
"main": "lib/blog.js",
"bin": {
"blog": "./bin/blog.js"
},
"scripts": {
"document": "jsdoc -c ./config/jsdoc.json lib config",
"document": "jsdoc2md -f 'lib/**/*.js' > doc/README.md",
"lint": "eslint .",
"test": "echo \":(\""
"test": "echo ':('"
},
"repository": {
"type": "git",
"url": "git+https://github.com/rbdr/blog.git"
"url": "git+https://gitlab.com/rbdr/blog.git"
},
"author": "Ben Beltran <[email protected]>",
"license": "Apache-2.0",
"bugs": {
"url": "https://github.com/rbdr/blog/issues"
"url": "https://gitlab.com/rbdr/blog/issues"
},
"homepage": "https://github.com/rbdr/blog#readme",
"homepage": "https://gitlab.com/rbdr/blog#readme",
"dependencies": {
"getenv": "0.7.x",
"minimist": "1.2.x",
"mustache": "2.3.x",
"ncp": "2.0.x",
"rimraf": "2.6.x",
"showdown": "1.7.x"
"getenv": "^1.0.0",
"minimist": "^1.2.5",
"dot": "^1.1.3",
"ncp": "^2.0.0",
"marked": "^1.0.0"
},
"devDependencies": {
"docdash": "0.4.x",
"eslint": "4.1.x",
"eslint-config-hapi": "10.0.x",
"eslint-plugin-hapi": "4.0.x",
"jsdoc": "3.4.x"
"eslint": "^7.1.0",
"@hapi/eslint-config-hapi": "^13.0.2",
"@hapi/eslint-plugin-hapi": "^4.3.5",
"jsdoc-to-markdown": "^6.0.1"
},
"engines": {
"node": ">=8.0.0"
"node": ">=14.0.0"
}
}
......@@ -31,12 +31,14 @@ main {
h1, h2, h3, h4 {
margin: 1.414em 0 0.5em;
font-weight: 400;
width: 100%;
max-width: 640px;
}
p, ul, ol, img {
width: 100%;
margin: 1.414em 0;
max-width: 30em;
max-width: 480px;;
}
ul, ol { margin-left: 1.414em; }
......
......@@ -16,16 +16,16 @@
<a href="/">Blog</a>
</header>
<main>
{{#posts}}
<article id="{{id}}">
{{{html}}}
{{~ it.posts: post}}
<article id="{{= post.id}}">
{{= post.html}}
</article>
<hr>
{{/posts}}
{{^posts}}
{{~}}
{{? it.posts}}
<h1>This is a fresh blog!</h1>
<p>There are no posts yet.</p>
{{/posts}}
{{?}}
</main>
<footer>
<p>Only 3 entries kept at any time. Press 1, 2, and 3 to switch. <a href="https://unlimited.pizza">unlimited.pizza</a></p>
......