From ab8a2d874ccb551682127e5b67f9565c45a497b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20M=C3=BCller?= Date: Sat, 9 Sep 2017 00:07:33 +0200 Subject: [PATCH] preparing for 3.0 --- Makefile | 3 - README.md | 26 +-- api_spec.js | 234 ----------------------- {resources => assets}/footer.html | 4 +- assets/markdown/Demo.md | 36 ++++ {resources => assets/markdown}/TOS.md | 0 {resources => assets}/public/favicon.ico | Bin {resources => assets}/public/index.html | 5 +- {resources => assets}/public/robots.txt | 0 {resources => assets}/public/style.css | 0 {resources => assets}/template.html | 0 package.json | 39 ---- resources/edit.html | 45 ----- resources/public/js/publishing.js | 73 ------- server.js | 181 ------------------ storage.js | 64 ------- view.js | 55 ------ 17 files changed, 46 insertions(+), 719 deletions(-) delete mode 100644 Makefile delete mode 100644 api_spec.js rename {resources => assets}/footer.html (78%) create mode 100644 assets/markdown/Demo.md rename {resources => assets/markdown}/TOS.md (100%) rename {resources => assets}/public/favicon.ico (100%) rename {resources => assets}/public/index.html (90%) rename {resources => assets}/public/robots.txt (100%) rename {resources => assets}/public/style.css (100%) rename {resources => assets}/template.html (100%) delete mode 100644 package.json delete mode 100644 resources/edit.html delete mode 100644 resources/public/js/publishing.js delete mode 100644 server.js delete mode 100644 storage.js delete mode 100644 view.js diff --git a/Makefile b/Makefile deleted file mode 100644 index 1db5a30..0000000 --- a/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -push: - git push prod - git push origin diff --git a/README.md b/README.md index 69e04aa..dcb61f4 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,12 @@ # README -## About - -[NoteHub](https://www.notehub.org) is a free and hassle-free pastebin for one-off markdown publishing and was implemented as a one-app-one-language [experiment](https://notehub.org/ikqz8). - -## Implementation - -NoteHub's implementation aims for ultimate simplicity and performance. +> "Make every detail perfect and limit the number of details to perfect."  +> — _Jack Dorsey_ -### Design +## About -1. At a request, the server first checks, if a note is already rendered and is present in the LRU cache. - (a) If _yes_, it return the rendered HTML code and increases the views counter. - (b) Otherwise, the note is retrieved from the DB, rendered and put into the LRU cache; the views counter will be increased. -2. The rendering of note pages: there are HTML file tempates with placeholders, which will be trivially filled with replacements. -3. The LRU cache holds the rendered HTML for the most popular notes, which makes their access a static O(1) operation without any DB I/O. -4. The server keeps corresponding models for all notes in the cache for statistics updates. These models are persisted every 5 minutes to the DB. +Dead simple hosting for markdown notes. ## Installation & Deployment -To run NoteHub as a standalone version, execute: - -1. `git clone https://github.com/chmllr/NoteHub.git` -2. `npm install` -3. `npm start` - -This starts a NoteHub instance on port 3000. \ No newline at end of file +TBD diff --git a/api_spec.js b/api_spec.js deleted file mode 100644 index a5a7f2d..0000000 --- a/api_spec.js +++ /dev/null @@ -1,234 +0,0 @@ -var frisby = require('frisby'); -var md5 = require('md5'); - -frisby.create('Landing page') - .get('http://localhost:3000/') - .expectStatus(200) - .expectHeaderContains('content-type', 'text/html; charset=utf-8') - .expectBodyContains('Hassle-free') - .toss(); - -frisby.create('Open note page') - .get('http://localhost:3000/new') - .expectStatus(200) - .expectHeaderContains('content-type', 'text/html; charset=utf-8') - .expectBodyContains('Terms of Service') - .toss(); - -frisby.create('Open TOS') - .get('http://localhost:3000/TOS') - .expectStatus(200) - .expectHeaderContains('content-type', 'text/html; charset=utf-8') - .expectBodyContains('Site Terms of Use Modifications') - .toss(); - -frisby.create('Incurrect URL') - .get('http://localhost:3000/abcdef') - .expectStatus(404) - .expectBodyContains('Not found') - .toss(); - -frisby.create('Invalid posting') - .post('http://localhost:3000/note') - .expectStatus(400) - .expectHeaderContains('content-type', 'text/html; charset=utf-8') - .expectBodyContains('Bad request') - .toss(); - -let testNote = 'This is a test note'; - -frisby.create('Invalid posting 2') - .post('http://localhost:3000/note', { - action: 'POST', - note: testNote - }) - .expectStatus(400) - .expectHeaderContains('content-type', 'text/html; charset=utf-8') - .expectBodyContains('Bad request') - .toss(); - -frisby.create('Invalid posting 3') - .post('http://localhost:3000/note', { - action: 'POST', - session: md5('new'), - signature: 'assdss', - note: testNote - }) - .expectStatus(400) - .expectHeaderContains('content-type', 'text/html; charset=utf-8') - .expectBodyContains('Signature mismatch') - .toss(); - -frisby.create('Valid posting') - .post('http://localhost:3000/note', { - action: 'POST', - session: md5('new'), - signature: md5(md5('new') + testNote), - password: '', - note: testNote - }) - .expectStatus(302) - .expectBodyContains('Found. Redirecting to') - .expectHeaderContains('content-type', 'text/plain; charset=utf-8') - .after(function(err, res, body) { - let noteId = body.replace('Found. Redirecting to /', ''); - frisby.create('Read posted note') - .get('http://localhost:3000/' + noteId) - .expectStatus(200) - .expectBodyContains(testNote) - .after((err, res, body) => { - frisby.create('Illegal note editing attempt with empty password') - .post('http://localhost:3000/note', { - id: noteId, - action: 'UPDATE', - session: md5('new'), - signature: md5(md5('new') + testNote+'!!!'), - note: testNote + '!!!', - password: '' - }) - .expectStatus(400) - .expectBodyContains('Password is wrong') - .toss(); - }) - .after((err, res, body) => { - frisby.create('Illegal note editing attempt') - .post('http://localhost:3000/note', { - id: noteId, - action: 'UPDATE', - session: md5('new'), - signature: md5(md5('new') + testNote+'!!!'), - note: testNote + '!!!', - password: 'aaabbb' - }) - .expectStatus(400) - .expectBodyContains('Password is wrong') - .toss(); - - }) - .toss(); - }) - .toss(); - -frisby.create('Valid posting, editing and removal') - .post('http://localhost:3000/note', { - action: 'POST', - session: md5('new'), - signature: md5(md5('new') + testNote), - password: 'aabbcc', - note: testNote - }) - .expectStatus(302) - .expectBodyContains('Found. Redirecting to') - .expectHeaderContains('content-type', 'text/plain; charset=utf-8') - .after(function(err, res, body) { - var noteId = body.replace('Found. Redirecting to /', ''); - frisby.create('Export posted note') - .get('http://localhost:3000/' + noteId + '/export') - .expectStatus(200) - .expectHeaderContains('content-type', 'text/plain; charset=utf-8') - .expectBodyContains(testNote) - .toss(); - frisby.create('Read posted note') - .get('http://localhost:3000/' + noteId) - .expectStatus(200) - .expectBodyContains(testNote) - .expectHeaderContains('content-type', 'text/html; charset=utf-8') - .after((err, res, body) => { - frisby.create('Unauthorized note editing attempt') - .post('http://localhost:3000/note', { - id: noteId, - action: 'UPDATE', - session: md5('new'), - signature: md5(md5('new') + testNote+'!!!'), - note: testNote + '!!!', - password: 'abbcc' - }) - .expectStatus(400) - .expectBodyContains('Password is wrong') - .toss(); - }) - .after((err, res, body) => { - frisby.create('Valid note editing attempt') - .post('http://localhost:3000/note', { - id: noteId, - action: 'UPDATE', - session: md5('new'), - signature: md5(md5('new') + 'Changed!'), - note: 'Changed!', - password: 'aabbcc' - }) - .expectStatus(302) - .after((err, res, body) => { - frisby.create('Read changed note') - .get('http://localhost:3000/' + noteId) - .expectStatus(200) - .expectBodyContains('Changed!') - .toss(); - }) - .after((err, res, body) => { - frisby.create('Delete posted note') - .post('http://localhost:3000/note',{ - id: noteId, - button: 'Delete', - action: 'UPDATE', - session: md5('new'), - signature: md5(md5('new') + 'Changed!'), - note: 'Changed!', - password: 'aabbcc' - }) - .expectStatus(200) - .expectBodyContains('Note deleted') - .toss(); - }) - .toss(); - }) - .toss(); - frisby.create('Read stats of posted note') - .expectStatus(200) - .get('http://localhost:3000/' + noteId).toss(); - frisby.create('Read stats of posted note') - .expectStatus(200) - .get('http://localhost:3000/' + noteId).toss(); - frisby.create('Read stats of posted note') - .expectStatus(200) - .get('http://localhost:3000/' + noteId).toss(); - frisby.create('Read stats of posted note') - .get('http://localhost:3000/' + noteId + '/stats') - .expectHeaderContains('content-type', 'text/html; charset=utf-8') - .expectStatus(200) - .expectBodyContains('Statistics') - .expectBodyContains('Views4') - .toss(); - }) - .toss(); - -var tooLongNote = 'ABCD'; - -while (tooLongNote.length < 1024*200) tooLongNote += tooLongNote; - -frisby.create('Invalid posting of too long note') - .post('http://localhost:3000/note', { - action: 'POST', - session: md5('new'), - signature: md5(md5('new') + testNote), - password: 'aabbcc', - note: tooLongNote - }) - .expectStatus(400) - .expectHeaderContains('content-type', 'text/html; charset=utf-8') - .expectBodyContains('too large') - .toss(); - -frisby.create('Invalid update without id') - .post('http://localhost:3000/note', { - action: 'UPDATE', - session: md5('new'), - signature: md5(md5('new') + 'Any note'), - password: 'aabbcc', - note: 'Any note' - }) - .expectStatus(400) - .expectHeaderContains('content-type', 'text/html; charset=utf-8') - .expectBodyContains('Wrong note ID') - .toss(); - diff --git a/resources/footer.html b/assets/footer.html similarity index 78% rename from resources/footer.html rename to assets/footer.html index fbe8bc9..b40ab32 100644 --- a/resources/footer.html +++ b/assets/footer.html @@ -3,5 +3,5 @@ statistics · edit · export · - terms of service - \ No newline at end of file + terms of service + diff --git a/assets/markdown/Demo.md b/assets/markdown/Demo.md new file mode 100644 index 0000000..45c591f --- /dev/null +++ b/assets/markdown/Demo.md @@ -0,0 +1,36 @@ +# Demo Note + +## Text Formatting + +This is a _short_ note demonstrating the **capabilities** of Markdown. [Markdown](http://en.wikipedia.org/wiki/Markdown) is a _markup language_ with plain text +formatting syntax. But you also can use standard HTML tags. + +## Backquotes, Lists & Code + +This is a backquote: + +> _"Our greatest glory is not in never falling but in rising every time we fall."_ +> — Confucius + +To create simple lists, just enumerate all items using a dash in the prefix: + +- Alpha +- Beta +- Gamma + +Also you can either mark some special `words` or write entire `code` blocks: + + (defn fact [n] + (if (< n 2) 1 + (* n (fact (- n 1))))) + +## Tables + +Also simple tables is a piece of cake: + +Column 1 | Column 2 | Column 3 +--- | --- | --- +Text 1 | Text 3 | Text 5 +Text 2 | Text 4 | Text 6 + +Take a look at the [source code](/Demo.md/export) of this page, to see how it works. diff --git a/resources/TOS.md b/assets/markdown/TOS.md similarity index 100% rename from resources/TOS.md rename to assets/markdown/TOS.md diff --git a/resources/public/favicon.ico b/assets/public/favicon.ico similarity index 100% rename from resources/public/favicon.ico rename to assets/public/favicon.ico diff --git a/resources/public/index.html b/assets/public/index.html similarity index 90% rename from resources/public/index.html rename to assets/public/index.html index 49ffa0a..862337d 100644 --- a/resources/public/index.html +++ b/assets/public/index.html @@ -11,13 +11,14 @@

NoteHub

Free and Hassle-free Pastebin for Markdown Notes


- See Demo Note + See Demo Note New Note

Changelog

diff --git a/resources/public/robots.txt b/assets/public/robots.txt similarity index 100% rename from resources/public/robots.txt rename to assets/public/robots.txt diff --git a/resources/public/style.css b/assets/public/style.css similarity index 100% rename from resources/public/style.css rename to assets/public/style.css diff --git a/resources/template.html b/assets/template.html similarity index 100% rename from resources/template.html rename to assets/template.html diff --git a/package.json b/package.json deleted file mode 100644 index 69f5236..0000000 --- a/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "NoteHub", - "version": "3.0.0", - "description": "Free Pastebin for One-Off Markdown Publishing", - "main": "server.js", - "scripts": { - "start": "node server.js", - "dev": "./node_modules/nodemon/bin/nodemon.js server.js", - "test": "jasmine-node ." - }, - "repository": { - "type": "git", - "url": "https://github.com/chmllr/NoteHub.git" - }, - "keywords": [ - "markdown", - "pastebin" - ], - "author": "Christian Müller (http://twitter.com/drmllr)", - "license": "ISC", - "bugs": { - "url": "https://github.com/chmllr/NoteHub/issues" - }, - "homepage": "https://github.com/chmllr/NoteHub", - "dependencies": { - "body-parser": "^1.15.0", - "express": "^4.13.4", - "highlight.js": "^9.5.0", - "lru-cache": "^4.0.0", - "marked": "^0.3.5", - "md5": "^2.1.0", - "sequelize": "^3.19.3", - "sqlite3": "*" - }, - "devDependencies": { - "frisby": "^0.8.5", - "nodemon": "^1.9.1" - } -} diff --git a/resources/edit.html b/resources/edit.html deleted file mode 100644 index 3b07a0d..0000000 --- a/resources/edit.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - - NoteHub — New Page - - - - - - - - - - - -
-
- - - - - - -
-   - - - - - 0 words - -
-
-
-
-
-
- - - diff --git a/resources/public/js/publishing.js b/resources/public/js/publishing.js deleted file mode 100644 index 663fbaf..0000000 --- a/resources/public/js/publishing.js +++ /dev/null @@ -1,73 +0,0 @@ -var $ = function(id) { - return document.getElementById(id); -}; -var iosDetected = navigator.userAgent.match('(iPad|iPod|iPhone)'); -var timer = null; -var timerDelay = iosDetected ? 800 : 400; -var $note, $action, $preview, $plain_password, $tableau; -var backendTimer; - -document.addEventListener('DOMContentLoaded', function () { - marked.setOptions({ - langPrefix: 'hljs lang-', - highlight: function (code) { - return hljs.highlightAuto(code).value; - }, - }); -}); - -function md2html(input) { - return marked(input); -} - -function saveDraft() { - if ($action == 'UPDATE') return; - console.log('draft autosave...'); - $tableau.innerHTML = 'Draft autosaved.'; - localStorage.setItem('draft', $note.value); -} - -function enableButton() { - var checkbox = $('tos'); - var button = $('publish-button'); - button.disabled = !checkbox.checked; -} - -function onLoad() { - $note = $('note'); - $action = $('action').value; - $preview = $('draft'); - $tableau = $('tableau'); - $plain_password = $('plain-password'); - var updatePreview = function() { - clearTimeout(timer); - var content = $note.value; - var delay = Math.min(timerDelay, timerDelay * (content.length / 400)); - timer = setTimeout(function() { - $preview.innerHTML = md2html(content); - $tableau.innerHTML = content.split(/\s+/).length + ' words'; - }, delay); - }; - if ($action == 'UPDATE') updatePreview(); - else { - $('delete-button').style.display = 'none'; - $note.value = ''; - var draft = localStorage.getItem('draft'); - if (draft) { - $note.value = draft; - updatePreview(); - } - } - $note.onkeyup = updatePreview; - $('delete-button').onclick = $('publish-button').onclick = function(e) { - localStorage.removeItem('draft'); - self.onbeforeunload = null; - if ($plain_password.value !== '') $('password').value = md5($plain_password.value); - $plain_password.value = null; - $('signature').value = md5($('session').value + $note.value.replace(/[\n\r]/g, '')); - }; - if (iosDetected) $note.className += ' ui-border'; - else $note.focus(); - self.onbeforeunload = saveDraft; - setInterval(saveDraft, 60 * 1000); -} diff --git a/server.js b/server.js deleted file mode 100644 index 5f911a9..0000000 --- a/server.js +++ /dev/null @@ -1,181 +0,0 @@ -var express = require('express'); -var view = require('./view'); -var storage = require('./storage'); -var md5 = require('md5'); -var LRU = require('lru-cache'); -var bodyParser = require('body-parser'); -var fs = require('fs'); -var blackList = new Set(); - -var app = express(); - -app.use(bodyParser.urlencoded({ extended: true, limit: '200kb' })); -app.use(express.static(__dirname + '/resources/public')); -app.use(function (error, req, res, next) { - if (error) { - sendResponse(res, 400, 'Bad request', error.message); - log('REQUEST ERROR:', error); - } else { - next(); - } -}); - -var MODELS = {}; -var CACHE = new LRU({ - max: 50, - dispose: key => { - log('disposing', key, 'from cache'); - var model = MODELS[key]; - if (model) model.save(); - delete MODELS[key]; - } -}); - -var log = function() { - var date = new Date(); - var timestamp = date.getDate() + '/' + date.getMonth() + ' ' + date.getHours() + ':' + - date.getMinutes() + ':' + date.getSeconds() + '.' + date.getMilliseconds(); - var message = Array.prototype.slice.call(arguments); - message.unshift('--'); - message.unshift(timestamp); - console.log.apply(console, message); -}; - -app.get('/TOS', (req, res) => res.send(view.renderTOS())); - -app.get('/new', (req, res) => { - log(req.ip, 'opens /new'); - res.send(view.newNotePage(md5('new'))); -}); - -app.post('/note', (req, res) => { - var body = req.body, - session = body.session, - note = body.note, - password = body.password, - action = body.action, - id = body.id; - log(req.ip, 'calls /note to', action, id); - var goToNote = note => res.redirect('/' + note.id); - if (!note || !session || session.indexOf(md5('edit/' + id)) !== 0 && session.indexOf(md5('new')) !== 0) - return sendResponse(res, 400, 'Bad request'); - if (body.signature != md5(session + note.replace(/[\n\r]/g, ''))) - return sendResponse(res, 400, 'Signature mismatch'); - if (action == 'POST') - storage.addNote(note, password).then(goToNote); - else { - if (!id) return sendResponse(res, 400, 'Wrong note ID'); - CACHE.del(id); - if (body.button == 'Delete') { - log('deleting note', id); - storage.deleteNote(id, password).then( - () => sendResponse(res, 200, 'Note deleted'), - error => sendResponse(res, 400, 'Bad request', error.message)); - } else { - log('updating note', id); - storage.updateNote(id, password, note).then(goToNote, - error => sendResponse(res, 400, 'Bad request', error.message)); - } - } -}); - -app.get('/:year/:month/:day/:title', (req, res) => { - var P = req.params, url = P.year + '/' + P.month + '/' + P.day + '/' + P.title; - log(req.ip, 'resolves deprecated id', url); - if (CACHE.has(url)) { - log(url, 'is cached!'); - var id = CACHE.get(url); - if (id) res.redirect('/' + id); - else notFound(res); - } else storage.getNoteId(url).then(note => { - log(url, 'is not cached, resolving...'); - if (note) { - CACHE.set(url, note.id); - res.redirect('/' + note.id); - } else { - CACHE.set(url, null); - notFound(res); - } - }); -}); - -app.get(/\/([a-z0-9]+)\/edit/, (req, res) => { - var id = req.params['0']; - log(req.ip, 'calls /edit on', id); - storage.getNote(id).then(note => res.send(note ? - view.editNotePage(md5('edit/' + id), note) : - notFound(res))); -}); - -app.get(/\/([a-z0-9]+)\/export/, (req, res) => { - var id = req.params['0']; - log(req.ip, 'calls /export on', id); - res.set({ 'Content-Type': 'text/plain', 'Charset': 'utf-8' }); - storage.getNote(id).then(note => note ? - res.send(note.text) : - notFound(res)); -}); - -app.get(/\/([a-z0-9]+)\/stats/, (req, res) => { - var id = req.params['0']; - log(req.ip, 'calls /stats on', id); - var promise = id in MODELS ? - new Promise(resolve => resolve(MODELS[id])) : - storage.getNote(id); - promise.then(note => note ? - res.send(view.renderStats(note)) : - notFound(res)); -}); - -app.get(/\/([a-z0-9]+)/, (req, res) => { - var id = req.params['0']; - log(req.ip, 'open note', id, 'from', req.get('Referer')); - if (CACHE.has(id)) { - log(id, 'is cached!'); - var obj = MODELS[id]; - if (!obj) return notFound(res); - obj.views++; - res.send(CACHE.get(id)); - } else storage.getNote(id).then(note => { - log(id, 'is not cached, resolving...'); - if (!note) { - CACHE.set(id, null); - return notFound(res); - } - var content = view.renderNote(note, blackList); - CACHE.set(id, content); - MODELS[id] = note; - note.views++; - res.send(content); - }); -}); - -var sendResponse = (res, code, message, details) => { - log('sending response', code, message); - res.status(code).send(view.renderPage(null, message, - `

${message}


` + - `
${details || '¯\\_(ツ)_/¯'}
`, '')); -}; - -var notFound = res => sendResponse(res, 404, 'Not found'); - -var server = app.listen(process.env.PORT || 3000, - () => log('NoteHub server listening on port', server.address().port)); - -setInterval(() => { - var keys = Object.keys(MODELS); - log('saving stats for', keys.length, 'models...'); - keys.forEach(id => MODELS[id].save()); -}, 5 * 60 * 1000); - -var updateBlackList = () => { - var ids = fs.readFileSync(process.env.BLACK_LIST || '/dev/null', 'utf-8').split(/\n+/).filter(Boolean); - ids.filter(id => !blackList.has(id)).forEach(id => CACHE.del(id)); - blackList = new Set(ids); - log('black list updated, entries:', blackList.size); -}; - -setInterval(updateBlackList, 60 * 60 * 1000); - -updateBlackList(); - diff --git a/storage.js b/storage.js deleted file mode 100644 index bc73204..0000000 --- a/storage.js +++ /dev/null @@ -1,64 +0,0 @@ -var Sequelize = require('sequelize'); -var sequelize = new Sequelize('database', null, null, { - dialect: 'sqlite', - pool: { - max: 5, - min: 0, - idle: 10000 - }, - storage: 'database.sqlite' -}); - -var Note = sequelize.define('Note', { - id: { type: Sequelize.STRING(6), unique: true, primaryKey: true }, - deprecatedId: Sequelize.TEXT, - text: Sequelize.TEXT, - published: { type: Sequelize.DATE, defaultValue: Sequelize.NOW }, - edited: { type: Sequelize.DATE, allowNull: true, defaultValue: null }, - password: Sequelize.STRING(16), - views: { type: Sequelize.INTEGER, defaultValue: 0 } -}); - -sequelize.sync(); - -module.exports.getNote = id => Note.findById(id); - -module.exports.getNoteId = deprecatedId => Note.findOne({ - where: { deprecatedId: deprecatedId } -}); - -var generateId = () => [1, 1, 1, 1, 1] - .map(() => { - var code = Math.floor(Math.random() * 36); - return String.fromCharCode(code + (code < 10 ? 48 : 87)); - }) - .join(''); - -var getFreeId = () => { - var id = generateId(); - return Note.findById(id).then(result => result ? getFreeId() : id); -}; - -module.exports.addNote = (note, password) => getFreeId().then(id => Note.create({ - id: id, - text: note, - password: password -})); - -var passwordCheck = (note, password, callback) => - (!note || note.password.length === 0 || note.password !== password) ? - new Promise((resolve, reject) => reject({ message: 'Password is wrong' })) : - callback(); - -module.exports.updateNote = (id, password, text) => - Note.findById(id).then(note => - passwordCheck(note, password, () => { - note.text = text; - note.edited = new Date(); - return note.save(); - })); - -module.exports.deleteNote = (id, password) => - Note.findById(id).then(note => - passwordCheck(note, password, () => note.destroy())); - diff --git a/view.js b/view.js deleted file mode 100644 index 3dde677..0000000 --- a/view.js +++ /dev/null @@ -1,55 +0,0 @@ -var marked = require('marked'); -var fs = require('fs'); -var hljs = require('highlight.js'); - -var TOS = fs.readFileSync('resources/TOS.md', 'utf-8'); -var pageTemplate = fs.readFileSync('resources/template.html', 'utf-8'); -var footerTemplate = fs.readFileSync('resources/footer.html', 'utf-8'); -var editTemplate = fs.readFileSync('resources/edit.html', 'utf-8'); -var header = fs.readFileSync(process.env.HEADER || '/dev/null', 'utf-8'); - -var deriveTitle = text => text - .split(/[\n\r]/)[0].slice(0,25) - .replace(/[`~!@#\$%^&\*_|\+=\?;:'",.<>\{\}\\\/]/g, ''); - -var renderPage = (id, title, content, footer, blackList) => pageTemplate - .replace('%HEADER%', blackList && blackList.has(id) ? header : '') - .replace('%TITLE%', title) - .replace('%CONTENT%', content.replace(//gi, '').replace(//gi, '')) - .replace('%FOOTER%', footer || ''); - -marked.setOptions({ - langPrefix: 'hljs lang-', - highlight: code => hljs.highlightAuto(code).value, -}); - -module.exports.renderPage = renderPage; - -module.exports.renderStats = note => renderPage(note.id, deriveTitle(note.text), - `

Statistics

- - - - -
Published${note.published}
Edited${note.edited || 'N/A'}
Views${note.views}
`); - -module.exports.renderTOS = () => renderPage('tos', 'Terms of Service', marked(TOS)); - -module.exports.renderNote = (note, blackList) => renderPage(note.id, - deriveTitle(note.text), - marked(note.text), - footerTemplate.replace(/%LINK%/g, note.id), - blackList); - -module.exports.newNotePage = session => editTemplate - .replace('%ACTION%', 'POST') - .replace('%SESSION%', session) - .replace('%CONTENT%', 'Loading...'); - -module.exports.editNotePage = (session, note) => editTemplate - .replace('%ACTION%', 'UPDATE') - .replace('%SESSION%', session) - .replace('%ID%', note.id) - .replace('%CONTENT%', escape$(note.text)); - -var escape$ = s => s.split('').map(chr => chr == '$' ? '$$' : chr).join('');