diff --git a/migrations/20170615170503_posts.js b/migrations/20170615170503_posts.js new file mode 100644 index 00000000..5367aa29 --- /dev/null +++ b/migrations/20170615170503_posts.js @@ -0,0 +1,12 @@ +export async function up(knex) { + await knex.schema.table('posts', table => { + table.text('html'); + table.string('text_type'); + }); +} + +export async function down(knex) { + await knex.schema.table('posts', table => { + table.dropColumns(['html', 'text_type']); + }); +} diff --git a/package.json b/package.json index a8e99e63..152f0646 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "redux": "~3.6.0", "redux-immutablejs": "0.0.8", "reselect": "~3.0.0", + "sanitize-html": "^1.14.1", "sendgrid": "^2.0.0", "slug": "^0.9.1", "smoothscroll-polyfill": "^0.3.4", diff --git a/src/api/db/index.js b/src/api/db/index.js index 45199883..298752a6 100644 --- a/src/api/db/index.js +++ b/src/api/db/index.js @@ -31,6 +31,7 @@ import { break as breakGraphemes, countBreaks } from 'grapheme-breaker'; import { OnigRegExp } from 'oniguruma'; import Checkit from 'checkit'; import MarkdownIt from 'markdown-it'; +import sanitizeHtml from 'sanitize-html'; import slug from 'slug'; import { uploadAttachment, downloadAttachment, generateName } from '../../utils/attachments'; @@ -182,6 +183,41 @@ export function initBookshelfFromKnex(knex) { const Post = bookshelf.Model.extend({ tableName: 'posts', + initialize() { + this.on('saving', this.ensureTextType.bind(this)); + this.on('saving', this.renderHtml.bind(this)); + }, + ensureTextType() { + const allowedTypes = ['markdown', 'html', 'plain']; + + // The default type of text is 'plain'. Plain text must be rendered using posts.text attribute. + if (!_.includes(allowedTypes, this.get('text_type'))) { + this.set('text_type', 'plain'); + this.set('html', null); + } + }, + renderHtml() { + const text = this.get('text'); + if (!_.isString(text) || _.isEmpty(text)) { + return; + } + + let html; + switch (this.get('text_type')) { + case 'markdown': { + html = postMarkdown.render(text); + break; + } + case 'html': { + // Adjust the defaults if needed. + html = sanitizeHtml(text); + break; + } + default: return; + } + + this.set('html', html); + }, /** * Common logic for creating and updating. Must only be used for posts with text. */