0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00

🐛 Fixed last paragraph not rendering on front-end when it's styled

no issue
- the conditional for removal of trailing blank paragraphs was not sufficient to handle paragraphs where the first child element was not a text node such as when the content of the last paragraph is italic
- switched to a method that fully walks the DOM of the last paragraph node to extract its equivalent `.textContent` value for use in the "last para is blank?" check
This commit is contained in:
Kevin Ansfield 2019-04-08 16:25:20 +01:00
parent 12e0366fc9
commit 47692b1081
2 changed files with 50 additions and 41 deletions

View file

@ -14,6 +14,28 @@ const options = {
}
};
const walkDom = function (node, func) {
func(node);
node = node.firstChild;
while (node) {
walkDom(node, func);
node = node.nextSibling;
}
};
const nodeTextContent = function (node) {
let textContent = '';
walkDom(node, (node) => {
if (node.nodeType === 3) {
textContent += node.nodeValue;
}
});
return textContent;
};
// used to walk the rendered SimpleDOM output and modify elements before
// serializing to HTML. Saves having a large HTML parsing dependency such as
// jsdom that may break on malformed HTML in MD or HTML cards
@ -27,7 +49,7 @@ class DomModifier {
return;
}
let text = this.getTextValue(node);
let text = nodeTextContent(node);
let id = text
.replace(/[<>&"?]/g, '')
.trim()
@ -45,36 +67,8 @@ class DomModifier {
node.setAttribute('id', id);
}
// extract to util?
getTextValue(node) {
let buffer = '';
let next = node.firstChild;
while (next !== null) {
buffer += this._extractTextValue(next);
next = next.nextSibling;
}
return buffer;
}
_extractTextValue(node) {
let buffer = '';
if (node.nodeType === 3) {
buffer += node.nodeValue;
}
buffer += this.getTextValue(node);
return buffer;
}
modifyChildren(node) {
let next = node.firstChild;
while (next !== null) {
this.modify(next);
next = next.nextSibling;
}
walkDom(node, this.modify.bind(this));
}
modify(node) {
@ -82,8 +76,6 @@ class DomModifier {
if (node.nodeType === 1 && node.nodeName.match(/^h\d$/i)) {
this.addHeadingId(node);
}
this.modifyChildren(node);
}
}
@ -97,25 +89,26 @@ module.exports = {
*/
version = version || 2;
let versionedOptions = Object.assign({}, options, {
const versionedOptions = Object.assign({}, options, {
cardOptions: {version}
});
let renderer = new Renderer(versionedOptions);
let rendered = renderer.render(mobiledoc);
let serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap);
const renderer = new Renderer(versionedOptions);
const rendered = renderer.render(mobiledoc);
const 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 && lastChild.firstChild.nodeValue)) {
const lastChild = rendered.result.lastChild;
if (lastChild && lastChild.tagName === 'P') {
if (!nodeTextContent(lastChild)) {
rendered.result.removeChild(lastChild);
}
}
// Walk the DOM output and modify nodes as needed
// eg. to add ID attributes to heading elements
let modifier = new DomModifier();
const modifier = new DomModifier();
modifier.modifyChildren(rendered.result);
return serializer.serializeChildren(rendered.result);

View file

@ -107,6 +107,22 @@ describe('Mobiledoc converter', function () {
converter.render(mobiledoc, 2).should.eql('');
});
it('doesn\'t remove last paragraph if it has markups', function () {
let mobiledoc = {
version: '0.3.1',
markups: [['em']],
atoms: [],
cards: [],
sections: [
[1, 'p', [
[0, [0], 1, 'This should be kept']
]]
]
};
converter.render(mobiledoc, 2).should.eql('<p><em>This should be kept</em></p>');
});
it('adds id attributes to headings', function () {
let mobiledoc = {
version: '0.3.1',