Ghost/core/server/lib/mobiledoc/converters/mobiledoc-converter.js
Kevin Ansfield f57268daae
Koenig - Finalise Koenig HTML output and migrate existing content (#9741)
refs https://github.com/TryGhost/Ghost/issues/9742

We've identified some changes we need to make to the HTML output of the [new Koenig editor](
https://forum.ghost.org/t/koenig-editor-beta-release/1284/102) for future proofing and consistency across cards.

- the `<div class="kg-post">` wrapper around post content has been removed
- for image cards the `.kg-image-wide` and `.kg-image-full` classes have been changed to `.kg-width-wide` and `.kg-width-full` and applied to the `<figure>` element rather than the `<img>` element

Before:
```html
<div class="kg-post">
    <figure class="kg-image-card">
        <img class="kg-image kg-image-wide" src="...">
        <figcaption>example wide image</figcaption>
    </figure>
</div>
```

After:
```html
<figure class="kg-image-card kg-width-wide">
    <img class="kg-image" src="...">
    <figcaption>example wide image</figcaption>
</figure>
```
2018-07-23 12:23:02 +01:00

124 lines
3.5 KiB
JavaScript

const SimpleDom = require('simple-dom');
const Renderer = require('mobiledoc-dom-renderer').default;
const common = require('../../common');
const atoms = require('../atoms');
const cards = require('../cards');
const options = {
dom: new SimpleDom.Document(),
cards: cards,
atoms: atoms,
unknownCardHandler: function (args) {
common.logging.error(new common.errors.InternalServerError({
message: 'Mobiledoc card \'' + args.env.name + '\' not found.'
}));
}
};
// 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
class DomModifier {
constructor() {
this.usedIds = [];
}
addHeadingId(node) {
if (!node.firstChild || node.getAttribute('id')) {
return;
}
let text = this.getTextValue(node);
let id = text
.replace(/[<>&"?]/g, '')
.trim()
.replace(/[^\w]/g, '-')
.replace(/-{2,}/g, '-')
.toLowerCase();
if (this.usedIds[id] !== undefined) {
this.usedIds[id] += 1;
id += `-${this.usedIds[id]}`;
} else {
this.usedIds[id] = 0;
}
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;
}
}
modify(node) {
// add id attributes to H* tags
if (node.nodeType === 1 && node.nodeName.match(/^h\d$/i)) {
this.addHeadingId(node);
}
this.modifyChildren(node);
}
}
module.exports = {
// version 1 === Ghost 1.0 markdown-only mobiledoc
// version 2 === Ghost 2.0 full mobiledoc
render(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);
}
// Walk the DOM output and modify nodes as needed
// eg. to add ID attributes to heading elements
let modifier = new DomModifier();
modifier.modifyChildren(rendered.result);
let html = serializer.serializeChildren(rendered.result);
return html;
}
};