🐛 Fixed rendering and url transformation of v1 "card-markdown" aliased cards

no issue

- Ghost 1.x stored markdown cards with the name `card-markdown`, this was changed in Ghost 2.x to be `markdown`. To keep compatibility with the older mobiledoc content the `markdown` card was aliased using a straightforward `Object.assign()`. Unfortunately this failed to work adequately when the url transformation functions were added to cards and resulted in corrupted data being returned in API responses
- moved the markdown card definition into a factory function so that a clean card definition object can be used for both the `markdown` and `card-markdown` cards
This commit is contained in:
Kevin Ansfield 2019-10-10 16:35:29 +01:00
parent 4f0ca2914f
commit 6b3c4a59b4
4 changed files with 132 additions and 40 deletions

View File

@ -0,0 +1,40 @@
// this is a function so that when it's aliased across multiple cards we do not
// end up modifying the object by reference
module.exports = function markdownCardDefinition() {
return {
name: 'markdown',
type: 'dom',
render: function (opts) {
let converters = require('../converters');
let payload = opts.payload;
let version = opts.options && opts.options.version || 2;
// convert markdown to HTML ready for insertion into dom
let html = converters.markdownConverter.render(payload.markdown || '');
if (!html) {
return '';
}
/**
* @deprecated Ghost 1.0's markdown-only renderer wrapped cards. Remove in Ghost 3.0
*/
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
return opts.env.dom.createRawHTMLSection(html);
},
absoluteToRelative(urlUtils, payload, options) {
payload.markdown = payload.markdown && urlUtils.markdownAbsoluteToRelative(payload.markdown, options);
return payload;
},
relativeToAbsolute(urlUtils, payload, options) {
payload.markdown = payload.markdown && urlUtils.markdownRelativeToAbsolute(payload.markdown, options);
return payload;
}
};
};

View File

@ -1,8 +1,9 @@
// this card is just an alias of the `markdown` card which is necessary because
// our markdown-only editor was using the `card-markdown` card name
const markdownCard = require('./markdown');
const markdownCard = require('./_markdown');
const createCard = require('../create-card');
module.exports = createCard(
Object.assign({}, markdownCard, {name: 'card-markdown'})
);
const v1CompatMarkdownCard = markdownCard();
v1CompatMarkdownCard.name = 'card-markdown';
module.exports = createCard(v1CompatMarkdownCard);

View File

@ -1,38 +1,6 @@
const markdownCard = require('./_markdown');
const createCard = require('../create-card');
module.exports = createCard({
name: 'markdown',
type: 'dom',
render: function (opts) {
let converters = require('../converters');
let payload = opts.payload;
let version = opts.options && opts.options.version || 2;
// convert markdown to HTML ready for insertion into dom
let html = converters.markdownConverter.render(payload.markdown || '');
if (!html) {
return '';
}
/**
* @deprecated Ghost 1.0's markdown-only renderer wrapped cards. Remove in Ghost 3.0
*/
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
return opts.env.dom.createRawHTMLSection(html);
},
absoluteToRelative(urlUtils, payload, options) {
payload.markdown = payload.markdown && urlUtils.markdownAbsoluteToRelative(payload.markdown, options);
return payload;
},
relativeToAbsolute(urlUtils, payload, options) {
payload.markdown = payload.markdown && urlUtils.markdownRelativeToAbsolute(payload.markdown, options);
return payload;
}
});
// uses the _markdown card definition function so that the card definition
// can be re-used in aliased cards
module.exports = createCard(markdownCard());

View File

@ -0,0 +1,83 @@
const should = require('should');
const card = require('../../../../../server/lib/mobiledoc/cards/card-markdown');
const SimpleDom = require('simple-dom');
const serializer = new SimpleDom.HTMLSerializer(SimpleDom.voidMap);
describe('Markdown card (v1 compatibility card)', function () {
it('renders', function () {
let opts = {
env: {
dom: new SimpleDom.Document()
},
payload: {
markdown: '#HEADING\r\n- list\r\n- items'
}
};
serializer.serialize(card.render(opts)).should.eql('<!--kg-card-begin: markdown--><h1 id="heading">HEADING</h1>\n<ul>\n<li>list</li>\n<li>items</li>\n</ul>\n<!--kg-card-end: markdown-->');
});
it('Accepts invalid HTML in markdown', function () {
let opts = {
env: {
dom: new SimpleDom.Document()
},
payload: {
markdown: '#HEADING\r\n<h2>Heading 2>'
}
};
serializer.serialize(card.render(opts)).should.eql('<!--kg-card-begin: markdown--><h1 id="heading">HEADING</h1>\n<h2>Heading 2><!--kg-card-end: markdown-->');
});
it('Renders nothing when payload is undefined', function () {
let opts = {
env: {
dom: new SimpleDom.Document()
},
payload: {
markdown: undefined
}
};
serializer.serialize(card.render(opts)).should.eql('');
});
it('[deprecated] version 1', function () {
let opts = {
env: {
dom: new SimpleDom.Document()
},
payload: {
markdown: '#HEADING\r\n- list\r\n- items'
},
options: {
version: 1
}
};
serializer.serialize(card.render(opts)).should.eql('<!--kg-card-begin: markdown--><div class="kg-card-markdown"><h1 id="heading">HEADING</h1>\n<ul>\n<li>list</li>\n<li>items</li>\n</ul>\n</div><!--kg-card-end: markdown-->');
});
it('transforms urls absolute to relative', function () {
let payload = {
markdown: 'A link to [an internal post](http://127.0.0.1:2369/post)'
};
const transformed = card.absoluteToRelative(payload, {});
transformed.markdown
.should.equal('A link to [an internal post](/post)');
});
it('transforms urls relative to absolute', function () {
let payload = {
markdown: 'A link to [an internal post](/post)'
};
const transformed = card.relativeToAbsolute(payload, {});
transformed.markdown
.should.equal('A link to [an internal post](http://127.0.0.1:2369/post)');
});
});