Skip to content

Commit

Permalink
Koenig - Versioned renderer
Browse files Browse the repository at this point in the history
refs TryGhost#9505
- updates mobiledoc converter's `render` method to accept a `version` argument
    - `1` === Ghost 1.0's markdown-only renderer output
    - `2` === Koenig's full mobiledoc renderer output
- switch between mobiledoc renderer versions in Post model's `onSaving` hook
    - version 1 by default
    - version 2 if Koenig is enabled (currently behind dev experiments config + labs flag)
    - version 2 if the post's mobiledoc is not compatible with the markdown-only renderer
- "version 2" full-Koenig mobiledoc renderer output
    - wraps content in a `.kg-post` div
    - removes wrapper around markdown and html card output
    - adds classes to image card output including selected image size/style
- standardises es6 usage across mobiledoc related files
  • Loading branch information
kevinansfield committed May 4, 2018
1 parent 58aa531 commit ed92f63
Show file tree
Hide file tree
Showing 16 changed files with 427 additions and 149 deletions.
4 changes: 3 additions & 1 deletion core/server/lib/mobiledoc/atoms/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
var softReturn = require('./soft-return');
'use strict';

const softReturn = require('./soft-return');

module.exports = [softReturn];
2 changes: 2 additions & 0 deletions core/server/lib/mobiledoc/atoms/soft-return.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use strict';

module.exports = {
name: 'soft-return',
type: 'dom',
Expand Down
2 changes: 2 additions & 0 deletions core/server/lib/mobiledoc/cards/hr.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use strict';

module.exports = {
name: 'hr',
type: 'dom',
Expand Down
14 changes: 1 addition & 13 deletions core/server/lib/mobiledoc/cards/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,8 @@ module.exports = {
name: 'html',
type: 'dom',
render(opts) {
let payload = opts.payload;
let dom = opts.env.dom;
let caption = '';

if (payload.caption) {
caption = `<p>${payload.caption}</p>`;
}

let html = `<div class="kg-card-html">${payload.html}${caption}</div>`;

// use the SimpleDOM document to create a raw HTML section.
// avoids parsing/rendering of potentially broken or unsupported HTML
let element = dom.createRawHTMLSection(html);

return element;
return opts.env.dom.createRawHTMLSection(opts.payload.html);
}
};
10 changes: 9 additions & 1 deletion core/server/lib/mobiledoc/cards/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ module.exports = {
type: 'dom',
render(opts) {
let payload = opts.payload;
// let version = opts.options.version;
let dom = opts.env.dom;

let figure = dom.createElement('figure');
figure.setAttribute('class', 'kg-image-card');

let img = dom.createElement('img');
img.className = 'kg-card-image';
let imgClass = 'kg-image';
if (payload.imageStyle) {
imgClass = `${imgClass} kg-image--${payload.imageStyle}`;
}
img.setAttribute('src', payload.src);
img.setAttribute('class', imgClass);

figure.appendChild(img);

if (payload.caption) {
Expand Down
12 changes: 7 additions & 5 deletions core/server/lib/mobiledoc/cards/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
var hr = require('./hr'),
html = require('./html'),
image = require('./image'),
markdown = require('./markdown'),
cardMarkdown = require('./card-markdown');
'use strict';

const hr = require('./hr');
const html = require('./html');
const image = require('./image');
const markdown = require('./markdown');
const cardMarkdown = require('./card-markdown');

module.exports = [hr, html, image, markdown, cardMarkdown];
21 changes: 12 additions & 9 deletions core/server/lib/mobiledoc/cards/markdown.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
'use strict';

module.exports = {
name: 'markdown',
type: 'dom',
render: function (opts) {
var converters = require('../converters'),
html, element;

let converters = require('../converters');
let payload = opts.payload;
let version = opts.options.version;
// convert markdown to HTML ready for insertion into dom
html = '<div class="kg-card-markdown">'
+ converters.markdownConverter.render(opts.payload.markdown || '')
+ '</div>';
let html = converters.markdownConverter.render(payload.markdown || '');

// Ghost 1.0's markdown-only renderer wrapped cards
if (version === 1) {
html = `<div class="kg-card-markdown">${html}</div>`;
}

// use the SimpleDOM document to create a raw HTML section.
// avoids parsing/rendering of potentially broken or unsupported HTML
element = opts.env.dom.createRawHTMLSection(html);

return element;
return opts.env.dom.createRawHTMLSection(html);
}
};
37 changes: 32 additions & 5 deletions core/server/lib/mobiledoc/converters/mobiledoc-converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,38 @@ var SimpleDom = require('simple-dom'),
// }

module.exports = {
render: function (mobiledoc) {
var renderer = new Renderer(options),
rendered = renderer.render(mobiledoc),
serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap),
html = serializer.serializeChildren(rendered.result);
// version 1 === Ghost 1.0 markdown-only mobiledoc
// version 2 === Ghost 2.0 full mobiledoc
render: function (mobiledoc, version) {
version = version || 1;

// pass the version through to the card renderers.
// create a new object here to avoid modifying the default options
// object because the version can change per-render until 2.0 is released
let versionedOptions = Object.assign({}, options, {
cardOptions: {version}
});

let renderer = new Renderer(versionedOptions);
let rendered = renderer.render(mobiledoc);
let serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap);

// Koenig keeps a blank paragraph at the end of a doc but we want to
// make sure it doesn't get rendered
let lastChild = rendered.result.lastChild;
if (lastChild && lastChild.tagName === 'P' && !lastChild.firstChild) {
rendered.result.removeChild(lastChild);
}

let html = serializer.serializeChildren(rendered.result);

// full version of Koenig wraps the content with a specific class to
// be targetted with our default stylesheet for vertical rhythm and
// card-specific styles
if (version === 2) {
html = `<div class="kg-post">\n${html}\n</div>`;
}

return html;
}
};
34 changes: 32 additions & 2 deletions core/server/models/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var _ = require('lodash'),
htmlToText = require('html-to-text'),
ghostBookshelf = require('./base'),
config = require('../config'),
labs = require('../services/labs'),
converters = require('../lib/mobiledoc/converters'),
urlService = require('../services/url'),
relations = require('./relations'),
Expand Down Expand Up @@ -185,7 +186,7 @@ Post = ghostBookshelf.Model.extend({
prevSlug = this.previous('slug'),
publishedAt = this.get('published_at'),
publishedAtHasChanged = this.hasDateChanged('published_at', {beforeWrite: true}),
mobiledoc = this.get('mobiledoc'),
mobiledoc = JSON.parse(this.get('mobiledoc') || null),
generatedFields = ['html', 'plaintext'],
tagsToSave,
ops = [];
Expand Down Expand Up @@ -249,8 +250,37 @@ Post = ghostBookshelf.Model.extend({
}
});

// render mobiledoc to HTML. Switch render version if Koenig is enabled
// or has been edited with Koenig and is no longer compatible with the
// Ghost 1.0 markdown-only renderer
// TODO: re-render all content and remove the version toggle for Ghost 2.0
if (mobiledoc) {
this.set('html', converters.mobiledocConverter.render(JSON.parse(mobiledoc)));
let version = 1;
let devExperimentsEnabled = config.get('enableDeveloperExperiments');
let koenigEnabled = labs.isSet('koenigEditor') === true;

let mobiledocIsCompatibleWithV1 = function mobiledocIsCompatibleWithV1(doc) {
if (doc
&& doc.markups.length === 0
&& doc.cards.length === 1
&& doc.cards[0][0].match(/(?:card-)?markdown/)
&& doc.sections.length === 1
&& doc.sections[0].length === 2
&& doc.sections[0][0] === 10
&& doc.sections[0][1] === 0
) {
return true;
}

return false;
};

if ((devExperimentsEnabled && koenigEnabled) || !mobiledocIsCompatibleWithV1(mobiledoc)) {
version = 2;
}

let html = converters.mobiledocConverter.render(mobiledoc, version);
this.set('html', html);
}

if (this.hasChanged('html') || !this.get('plaintext')) {
Expand Down
17 changes: 9 additions & 8 deletions core/test/unit/lib/mobiledoc/atoms/soft-return_spec.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
var should = require('should'), // jshint ignore:line
card = require('../../../../../server/lib/mobiledoc/atoms/soft-return'),
SimpleDom = require('simple-dom'),
opts;
'use strict';

describe('Soft return card', function () {
const should = require('should'); // jshint ignore:line
const atom = require('../../../../../server/lib/mobiledoc/atoms/soft-return');
const SimpleDom = require('simple-dom');
const serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap);

describe('Soft return atom', function () {
it('generates a `br` tag', function () {
opts = {
let opts = {
env: {
dom: new SimpleDom.Document()
}
};

var serializer = new SimpleDom.HTMLSerializer([]);
serializer.serialize(card.render(opts)).should.match('<br></br>');
serializer.serialize(atom.render(opts)).should.match('<br>');
});
});
15 changes: 8 additions & 7 deletions core/test/unit/lib/mobiledoc/cards/hr_spec.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
var should = require('should'), // jshint ignore:line
card = require('../../../../../server/lib/mobiledoc/cards/hr'),
SimpleDom = require('simple-dom'),
opts;
'use strict';

const should = require('should'); // jshint ignore:line
const card = require('../../../../../server/lib/mobiledoc/cards/hr');
const SimpleDom = require('simple-dom');
const serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap);

describe('HR card', function () {
it('generates a horizontal rule', function () {
opts = {
let opts = {
env: {
dom: new SimpleDom.Document()
}
};

var serializer = new SimpleDom.HTMLSerializer([]);
serializer.serialize(card.render(opts)).should.match('<hr></hr>');
serializer.serialize(card.render(opts)).should.match('<hr>');
});
});
40 changes: 12 additions & 28 deletions core/test/unit/lib/mobiledoc/cards/html_spec.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
var should = require('should'), // jshint ignore:line
card = require('../../../../../server/lib/mobiledoc/cards/html'),
SimpleDom = require('simple-dom'),
opts;
'use strict';

const should = require('should'); // jshint ignore:line
const card = require('../../../../../server/lib/mobiledoc/cards/html');
const SimpleDom = require('simple-dom');
const serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap);

describe('HTML card', function () {
it('HTML Card renders', function () {
opts = {
let opts = {
env: {
dom: new SimpleDom.Document()
},
Expand All @@ -14,12 +16,11 @@ describe('HTML card', function () {
}
};

var serializer = new SimpleDom.HTMLSerializer([]);
serializer.serialize(card.render(opts)).should.match('<div class="kg-card-html"><h1>HEADING</h1><p>PARAGRAPH</p></div>');
serializer.serialize(card.render(opts)).should.match('<h1>HEADING</h1><p>PARAGRAPH</p>');
});

it('Plain content renders', function () {
opts = {
let opts = {
env: {
dom: new SimpleDom.Document()
},
Expand All @@ -28,12 +29,11 @@ describe('HTML card', function () {
}
};

var serializer = new SimpleDom.HTMLSerializer([]);
serializer.serialize(card.render(opts)).should.match('<div class="kg-card-html">CONTENT</div>');
serializer.serialize(card.render(opts)).should.match('CONTENT');
});

it('Invalid HTML returns', function () {
opts = {
let opts = {
env: {
dom: new SimpleDom.Document()
},
Expand All @@ -42,22 +42,6 @@ describe('HTML card', function () {
}
};

var serializer = new SimpleDom.HTMLSerializer([]);
serializer.serialize(card.render(opts)).should.match('<div class="kg-card-html"><h1>HEADING<</div>');
});

it('Caption renders', function () {
opts = {
env: {
dom: new SimpleDom.Document()
},
payload: {
html: '<iframe src="http://vimeo.com"></iframe>',
caption: 'Embed caption test'
}
};

var serializer = new SimpleDom.HTMLSerializer([]);
serializer.serialize(card.render(opts)).should.match('<div class="kg-card-html"><iframe src="http://vimeo.com"></iframe><p>Embed caption test</p></div>');
serializer.serialize(card.render(opts)).should.match('<h1>HEADING<');
});
});
Loading

0 comments on commit ed92f63

Please sign in to comment.