From b7a2fd0740aca50fecad454140ddfa5f1e14cf7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20GOUIN?= Date: Thu, 20 Jun 2019 13:49:44 +0200 Subject: [PATCH] Articles rendering system --- sample_data/home/template.ejs | 13 +++++++++ src/app.js | 40 ++++++++++++++++++++++---- src/config.default.json | 9 ++++++ src/file_walker.js | 8 +++--- src/renderer.js | 16 +++++++++++ test/file_walker.test.js | 4 +-- test/renderer.test.js | 53 +++++++++++++++++++++++++++++++++++ 7 files changed, 131 insertions(+), 12 deletions(-) create mode 100644 sample_data/home/template.ejs create mode 100644 src/renderer.js create mode 100644 test/renderer.test.js diff --git a/sample_data/home/template.ejs b/sample_data/home/template.ejs new file mode 100644 index 0000000..6cd69bf --- /dev/null +++ b/sample_data/home/template.ejs @@ -0,0 +1,13 @@ + + + + + GitBlog.md - Home + + + +
+ <%- article.content %> +
+ + \ No newline at end of file diff --git a/src/app.js b/src/app.js index 257490c..4b3d7bb 100644 --- a/src/app.js +++ b/src/app.js @@ -15,13 +15,14 @@ const cons = { module.exports = (config) => { const fw = require('./file_walker')(config); + const renderer = require('./renderer')(config); // set view engine from configuration app.set('view engine', config['view_engine']); // reroute the views folder to the root folder app.set('views', path.join(__dirname, '..')); - const articles = []; + const articles = {}; /** * Fetch articles from the data folder and send success as a response @@ -34,8 +35,9 @@ module.exports = (config) => { return console.error(cons.error, 'loading articles : ' + err); } articles.splice(0, articles.length, ...list); - if (articles.length > 0) - console.log(cons.ok, `loaded ${articles.length} article${articles.length > 1 ? 's' : ''}`); + const nb = Object.keys(articles).length; + if (nb > 0) + console.log(cons.ok, `loaded ${nb} article${nb > 1 ? 's' : ''}`); else console.log(cons.warn, `no articles loaded, check your configuration`); callback(true); @@ -79,16 +81,42 @@ module.exports = (config) => { // home endpoint : send the correct index page or error if not existing app.get('/', (req, res) => { - const homePath = `${config['data_dir']}/${config['home']['index']}`; + const homePath = path.join(config['data_dir'], config['home']['index']); fs.access(homePath, fs.constants.R_OK, (err) => { if (err) showError(req.path, 404, res); else - render(res, homePath, {articles: articles}); + render(res, homePath, {articles: Object.values(articles)}); }); }); - // catch all gets and return 404 if it's an hidden file type + // catch all article urls and render them + app.get('*', (req, res, next) => { + if (req.path.test(/^\/\d{4}\/\d{2}\/\d{2}\/(\w*\/)?$/)) { + const articlePath = req.path.substr(0, 11); + const article = articles[articlePath]; + if (!article) + showError(req.path, 404, res); + else { + renderer.render(article.realPath, (err, html) => { + if (err) + return showError(req.path, 500, res); + article.content = html; + const templatePath = path.join(config['data_dir'], config['home']['index']); + fs.access(templatePath, fs.constants.R_OK, (err) => { + if (err) + showError(req.path, 404, res); + else + render(res, templatePath, {article: article}); + }); + }); + } + } else { + next(); + } + }); + + // catch all hidden file type and return 404 app.get('*', (req, res, next) => { if (config['home']['hidden'].includes(path.extname(req.path))) showError(req.path, 404, res); diff --git a/src/config.default.json b/src/config.default.json index ece0152..065a961 100644 --- a/src/config.default.json +++ b/src/config.default.json @@ -14,6 +14,7 @@ }, "article": { "index": "index.md", + "template": "template.ejs", "thumbnail_tag": "thumbnail", "default_title": "Untitled", "default_thumbnail": null @@ -25,5 +26,13 @@ "webhook": { "endpoint": "/webhook", "secretFile": "git_secret" + }, + "showdown": { + "parseImgDimensions": true, + "strikethrough": true, + "tables": true, + "tasklists": true, + "openLinksInNewWindow": true, + "emoji": true } } \ No newline at end of file diff --git a/src/file_walker.js b/src/file_walker.js index 98a2126..ac30e12 100644 --- a/src/file_walker.js +++ b/src/file_walker.js @@ -76,7 +76,7 @@ module.exports = (config) => { .filter((matches) => matches && matches.length > 1); if (paths.length === 0) cb(null, []); - const list = []; + const articles = {}; let remaining = 0; paths.forEach((matches) => { const article = { @@ -94,11 +94,11 @@ module.exports = (config) => { article.title = info.title || config['article']['default_title']; article.thumbnail = info.thumbnail ? path.join(article.path, info.thumbnail) : config['article']['default_thumbnail']; article.escapedTitle = article.title.toLowerCase().replace(/[^\w]/gm, ' ').trim().replace(/ /gm, '_'); - article.url = path.join(article.path, article.escapedTitle); - list.push(article); + article.url = path.join(article.path, article.escapedTitle)+'/'; + articles[article.path]=article; remaining--; if (remaining === 0) - cb(null, list); + cb(null, articles); }); }); diff --git a/src/renderer.js b/src/renderer.js new file mode 100644 index 0000000..139956b --- /dev/null +++ b/src/renderer.js @@ -0,0 +1,16 @@ +const fs = require('fs'); +const showdown = require('showdown'); + +module.exports = (config) => { + const converter = new showdown.Converter(config['showdown']); + return { + render : (file, cb) => { + fs.readFile(file, {encoding:'UTF-8'}, (err, data) => { + if(err) + return cb(err); + const html = converter.makeHtml(data); + cb(null,html); + }); + } + }; +}; \ No newline at end of file diff --git a/test/file_walker.test.js b/test/file_walker.test.js index c36a58e..70abf86 100644 --- a/test/file_walker.test.js +++ b/test/file_walker.test.js @@ -234,7 +234,7 @@ describe('Test article fetching', () => { title: 'Untitled', thumbnail: 'default.png', escapedTitle: 'untitled', - url: path.join('2019', '05', '05', 'untitled'), + url: path.join('2019', '05', '05', 'untitled')+'/', }); done(); }); @@ -262,7 +262,7 @@ describe('Test article fetching', () => { title: 'Title with : info !', thumbnail: path.join('2019', '05', '05', './thumbnail.jpg'), escapedTitle: 'title_with___info', - url: path.join('2019', '05', '05', 'title_with___info'), + url: path.join('2019', '05', '05', 'title_with___info')+'/', }); done(); }); diff --git a/test/renderer.test.js b/test/renderer.test.js new file mode 100644 index 0000000..1a42d27 --- /dev/null +++ b/test/renderer.test.js @@ -0,0 +1,53 @@ +/* jshint -W117 */ +const fs = require('fs'); +const path = require('path'); +const utils = require('./test_utils'); + +const dataDir = 'test_data'; +const file = path.join(dataDir, 'test.md'); + +const config = { + 'showdown': { + 'simplifiedAutoLink': true, + 'smartIndentationFix': true + } +}; + +const renderer = require('../src/renderer')(config); + +beforeEach(() => { + utils.deleteFolderSync(dataDir); + fs.mkdirSync(dataDir); +}); + +afterAll(() => { + if (fs.existsSync(dataDir)) { + utils.deleteFolderSync(dataDir); + } +}); + +test('invalid file', (done) => { + renderer.render('invalid file', (err, html) => { + expect(err).not.toBeNull(); + expect(html).not.toBeDefined(); + done(); + }); +}); + +test('normal file', (done) => { + fs.writeFileSync(file, `# Hello`); + renderer.render(file, (err, html) => { + expect(err).toBeNull(); + expect(html).toBe('

Hello

'); + done(); + }); +}); + +test('custom rules', (done) => { + fs.writeFileSync(file, `www.google.com`); + renderer.render(file, (err, html) => { + expect(err).toBeNull(); + expect(html).toBe('

www.google.com

'); + done(); + }); +}); \ No newline at end of file