Added transformer methods to mobiledoc cards

no issue

- adds abolsute->relative and relative->absolute transformer methods to card definitions
- allows for each card to tailor it's transformation to the specific needs of it's payload so that the `mobiledoc` field can be transformed successfully during API serialization/deserialization
This commit is contained in:
Kevin Ansfield 2019-10-03 10:44:05 +01:00
parent 0f2afafcbb
commit fa4e68ba13
16 changed files with 348 additions and 2 deletions

View File

@ -90,5 +90,17 @@ module.exports = createCard({
} }
return figure; return figure;
},
absoluteToRelative(urlUtils, payload, options) {
payload.metadata.url = payload.metadata.url && urlUtils.absoluteToRelative(payload.metadata.url, options);
payload.caption = payload.caption && urlUtils.htmlAbsoluteToRelative(payload.caption, options);
return payload;
},
relativeToAbsolute(urlUtils, payload, options) {
payload.metadata.url = payload.metadata.url && urlUtils.relativeToAbsolute(payload.metadata.url, options);
payload.caption = payload.caption && urlUtils.htmlRelativeToAbsolute(payload.caption, options);
return payload;
} }
}); });

View File

@ -34,5 +34,15 @@ module.exports = createCard({
} else { } else {
return pre; return pre;
} }
},
absoluteToRelative(urlUtils, payload, options) {
payload.caption = payload.caption && urlUtils.htmlAbsoluteToRelative(payload.caption, options);
return payload;
},
relativeToAbsolute(urlUtils, payload, options) {
payload.caption = payload.caption && urlUtils.htmlRelativeToAbsolute(payload.caption, options);
return payload;
} }
}); });

View File

@ -24,5 +24,15 @@ module.exports = createCard({
} }
return figure; return figure;
},
absoluteToRelative(urlUtils, payload, options) {
payload.caption = payload.caption && urlUtils.htmlAbsoluteToRelative(payload.caption, options);
return payload;
},
relativeToAbsolute(urlUtils, payload, options) {
payload.caption = payload.caption && urlUtils.htmlRelativeToAbsolute(payload.caption, options);
return payload;
} }
}); });

View File

@ -108,5 +108,31 @@ module.exports = createCard({
} }
return figure; return figure;
},
absoluteToRelative(urlUtils, payload, options) {
if (payload.images) {
payload.images.forEach((image) => {
image.src = image.src && urlUtils.absoluteToRelative(image.src, options);
image.caption = image.caption && urlUtils.htmlAbsoluteToRelative(image.caption, options);
});
}
payload.caption = payload.caption && urlUtils.htmlAbsoluteToRelative(payload.caption, options);
return payload;
},
relativeToAbsolute(urlUtils, payload, options) {
if (payload.images) {
payload.images.forEach((image) => {
image.src = image.src && urlUtils.relativeToAbsolute(image.src, options);
image.caption = image.caption && urlUtils.htmlRelativeToAbsolute(image.caption, options);
});
}
payload.caption = payload.caption && urlUtils.htmlRelativeToAbsolute(payload.caption, options);
return payload;
} }
}); });

View File

@ -11,5 +11,15 @@ module.exports = createCard({
// use the SimpleDOM document to create a raw HTML section. // use the SimpleDOM document to create a raw HTML section.
// avoids parsing/rendering of potentially broken or unsupported HTML // avoids parsing/rendering of potentially broken or unsupported HTML
return opts.env.dom.createRawHTMLSection(opts.payload.html); return opts.env.dom.createRawHTMLSection(opts.payload.html);
},
absoluteToRelative(urlUtils, payload, options) {
payload.html = payload.html && urlUtils.htmlAbsoluteToRelative(payload.html, options);
return payload;
},
relativeToAbsolute(urlUtils, payload, options) {
payload.html = payload.html && urlUtils.htmlRelativeToAbsolute(payload.html, options);
return payload;
} }
}); });

View File

@ -39,5 +39,17 @@ module.exports = createCard({
} }
return figure; return figure;
},
absoluteToRelative(urlUtils, payload, options) {
payload.src = payload.src && urlUtils.absoluteToRelative(payload.src, options);
payload.caption = payload.caption && urlUtils.htmlAbsoluteToRelative(payload.caption, options);
return payload;
},
relativeToAbsolute(urlUtils, payload, options) {
payload.src = payload.src && urlUtils.relativeToAbsolute(payload.src, options);
payload.caption = payload.caption && urlUtils.htmlRelativeToAbsolute(payload.caption, options);
return payload;
} }
}); });

View File

@ -24,5 +24,15 @@ module.exports = createCard({
// use the SimpleDOM document to create a raw HTML section. // use the SimpleDOM document to create a raw HTML section.
// avoids parsing/rendering of potentially broken or unsupported HTML // avoids parsing/rendering of potentially broken or unsupported HTML
return opts.env.dom.createRawHTMLSection(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,9 +1,21 @@
let urlUtils;
module.exports = function createCard(card) { module.exports = function createCard(card) {
const {name, type} = card; const defaultTransformer = function (payload) {
return payload;
};
const {
name,
type,
absoluteToRelative = defaultTransformer,
relativeToAbsolute = defaultTransformer
} = card;
return { return {
name, name,
type, type,
render({env, payload, options}) { render({env, payload, options}) {
const {dom} = env; const {dom} = env;
const cleanName = name.replace(/^card-/, ''); const cleanName = name.replace(/^card-/, '');
@ -23,6 +35,26 @@ module.exports = function createCard(card) {
fragment.appendChild(endComment); fragment.appendChild(endComment);
return fragment; return fragment;
},
absoluteToRelative() {
// it's necessary to wait until the method is called to require
// urlUtils to ensure the class has actually been instantiated
// as cards are passed in as an arg to the class instantiation
if (!urlUtils) {
urlUtils = require('../url-utils');
}
return absoluteToRelative(urlUtils, ...arguments);
},
relativeToAbsolute() {
// it's necessary to wait until the method is called to require
// urlUtils to ensure the class has actually been instantiated
// as cards are passed in as an arg to the class instantiation
if (!urlUtils) {
urlUtils = require('../url-utils');
}
return relativeToAbsolute(urlUtils, ...arguments);
} }
}; };
}; };

View File

@ -1,5 +1,6 @@
const UrlUtils = require('@tryghost/url-utils'); const UrlUtils = require('@tryghost/url-utils');
const config = require('../../config'); const config = require('../../config');
const cards = require('../mobiledoc/cards');
const urlUtils = new UrlUtils({ const urlUtils = new UrlUtils({
url: config.get('url'), url: config.get('url'),
@ -7,7 +8,8 @@ const urlUtils = new UrlUtils({
apiVersions: config.get('api:versions'), apiVersions: config.get('api:versions'),
slugs: config.get('slugs').protected, slugs: config.get('slugs').protected,
redirectCacheMaxAge: config.get('caching:301:maxAge'), redirectCacheMaxAge: config.get('caching:301:maxAge'),
baseApiPath: '/ghost/api' baseApiPath: '/ghost/api',
cardTransformers: cards
}); });
module.exports = urlUtils; module.exports = urlUtils;

View File

@ -201,4 +201,38 @@ describe('Bookmark card', function () {
serializer.serialize(card.render(opts)) serializer.serialize(card.render(opts))
.should.equal(''); .should.equal('');
}); });
it('transforms urls absolute to relative', function () {
let payload = {
metadata: {
url: 'http://127.0.0.1:2369/post'
},
caption: 'A link to <a href="http://127.0.0.1:2369/post">an internal post</a>'
};
const transformed = card.absoluteToRelative(payload, {});
transformed.metadata.url
.should.equal('/post');
transformed.caption
.should.equal('A link to <a href="/post">an internal post</a>');
});
it('transforms urls relative to absolute', function () {
let payload = {
metadata: {
url: '/post'
},
caption: 'A link to <a href="/post">an internal post</a>'
};
const transformed = card.relativeToAbsolute(payload, {});
transformed.metadata.url
.should.equal('http://127.0.0.1:2369/post');
transformed.caption
.should.equal('A link to <a href="http://127.0.0.1:2369/post">an internal post</a>');
});
}); });

View File

@ -58,4 +58,26 @@ describe('Code card', function () {
serializer.serialize(card.render(opts)).should.match('<!--kg-card-begin: code--><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;p&gt;Test&lt;/p&gt;</code></pre><figcaption>Some <strong>HTML</strong></figcaption></figure><!--kg-card-end: code-->'); serializer.serialize(card.render(opts)).should.match('<!--kg-card-begin: code--><figure class="kg-card kg-code-card"><pre><code class="language-html">&lt;p&gt;Test&lt;/p&gt;</code></pre><figcaption>Some <strong>HTML</strong></figcaption></figure><!--kg-card-end: code-->');
}); });
it('transforms urls absolute to relative', function () {
let payload = {
caption: 'A link to <a href="http://127.0.0.1:2369/post">an internal post</a>'
};
const transformed = card.absoluteToRelative(payload, {});
transformed.caption
.should.equal('A link to <a href="/post">an internal post</a>');
});
it('transforms urls relative to absolute', function () {
let payload = {
caption: 'A link to <a href="/post">an internal post</a>'
};
const transformed = card.relativeToAbsolute(payload, {});
transformed.caption
.should.equal('A link to <a href="http://127.0.0.1:2369/post">an internal post</a>');
});
}); });

View File

@ -69,4 +69,26 @@ describe('Embed card', function () {
serializer.serialize(card.render(opts)).should.match('<!--kg-card-begin: embed--><figure class="kg-card kg-embed-card kg-card-hascaption">Testing<figcaption><strong>Caption</strong></figcaption></figure><!--kg-card-end: embed-->'); serializer.serialize(card.render(opts)).should.match('<!--kg-card-begin: embed--><figure class="kg-card kg-embed-card kg-card-hascaption">Testing<figcaption><strong>Caption</strong></figcaption></figure><!--kg-card-end: embed-->');
}); });
it('transforms urls absolute to relative', function () {
let payload = {
caption: 'A link to <a href="http://127.0.0.1:2369/post">an internal post</a>'
};
const transformed = card.absoluteToRelative(payload, {});
transformed.caption
.should.equal('A link to <a href="/post">an internal post</a>');
});
it('transforms urls relative to absolute', function () {
let payload = {
caption: 'A link to <a href="/post">an internal post</a>'
};
const transformed = card.relativeToAbsolute(payload, {});
transformed.caption
.should.equal('A link to <a href="http://127.0.0.1:2369/post">an internal post</a>');
});
}); });

View File

@ -138,4 +138,74 @@ describe('Gallery card', function () {
serializer.serialize(card.render(opts)).should.eql('<!--kg-card-begin: gallery--><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="/content/images/2018/08/NatGeo01-9.jpg" width="3200" height="1600"></div><div class="kg-gallery-image"><img src="/content/images/2018/08/NatGeo03-6.jpg" width="3200" height="1600"></div></div></div><figcaption>Test caption</figcaption></figure><!--kg-card-end: gallery-->'); serializer.serialize(card.render(opts)).should.eql('<!--kg-card-begin: gallery--><figure class="kg-card kg-gallery-card kg-width-wide kg-card-hascaption"><div class="kg-gallery-container"><div class="kg-gallery-row"><div class="kg-gallery-image"><img src="/content/images/2018/08/NatGeo01-9.jpg" width="3200" height="1600"></div><div class="kg-gallery-image"><img src="/content/images/2018/08/NatGeo03-6.jpg" width="3200" height="1600"></div></div></div><figcaption>Test caption</figcaption></figure><!--kg-card-end: gallery-->');
}); });
it('transforms urls absolute to relative', function () {
let payload = {
images: [
{
row: 0,
fileName: 'NatGeo01.jpg',
src: 'http://127.0.0.1:2369/content/images/2018/08/NatGeo01-9.jpg',
width: 3200,
height: 1600,
caption: 'A link to <a href="http://127.0.0.1:2369/post">an internal post</a>'
},
{
row: 0,
fileName: 'NatGeo02.jpg',
src: 'http://127.0.0.1:2369/content/images/2018/08/NatGeo02-10.jpg',
caption: 'A link to <a href="http://127.0.0.1:2369/post">an internal post</a>'
}
],
caption: 'A link to <a href="http://127.0.0.1:2369/post">an internal post</a>'
};
const transformed = card.absoluteToRelative(payload, {});
transformed.images[0].src
.should.equal('/content/images/2018/08/NatGeo01-9.jpg');
transformed.images[0].caption
.should.equal('A link to <a href="/post">an internal post</a>');
transformed.images[1].src
.should.equal('/content/images/2018/08/NatGeo02-10.jpg');
transformed.images[1].caption
.should.equal('A link to <a href="/post">an internal post</a>');
transformed.caption
.should.equal('A link to <a href="/post">an internal post</a>');
});
it('transforms urls relative to absolute', function () {
let payload = {
images: [
{
row: 0,
fileName: 'NatGeo01.jpg',
src: '/content/images/2018/08/NatGeo01-9.jpg',
width: 3200,
height: 1600
},
{
row: 0,
fileName: 'NatGeo02.jpg',
src: '/content/images/2018/08/NatGeo02-10.jpg'
}
],
caption: 'A link to <a href="/post">an internal post</a>'
};
const transformed = card.relativeToAbsolute(payload, {});
transformed.images[0].src
.should.equal('http://127.0.0.1:2369/content/images/2018/08/NatGeo01-9.jpg');
transformed.images[1].src
.should.equal('http://127.0.0.1:2369/content/images/2018/08/NatGeo02-10.jpg');
transformed.caption
.should.equal('A link to <a href="http://127.0.0.1:2369/post">an internal post</a>');
});
}); });

View File

@ -55,4 +55,26 @@ describe('HTML card', function () {
serializer.serialize(card.render(opts)).should.eql(''); serializer.serialize(card.render(opts)).should.eql('');
}); });
it('transforms urls absolute to relative', function () {
let payload = {
html: 'A link to <a href="http://127.0.0.1:2369/post">an internal post</a>'
};
const transformed = card.absoluteToRelative(payload, {});
transformed.html
.should.equal('A link to <a href="/post">an internal post</a>');
});
it('transforms urls relative to absolute', function () {
let payload = {
html: 'A link to <a href="/post">an internal post</a>'
};
const transformed = card.relativeToAbsolute(payload, {});
transformed.html
.should.equal('A link to <a href="http://127.0.0.1:2369/post">an internal post</a>');
});
}); });

View File

@ -116,4 +116,34 @@ describe('Image card', function () {
serializer.serialize(card.render(opts)).should.eql('<!--kg-card-begin: image--><figure class="kg-card kg-image-card kg-width-full"><img src="https://www.ghost.org/image.png" class="kg-image"></figure><!--kg-card-end: image-->'); serializer.serialize(card.render(opts)).should.eql('<!--kg-card-begin: image--><figure class="kg-card kg-image-card kg-width-full"><img src="https://www.ghost.org/image.png" class="kg-image"></figure><!--kg-card-end: image-->');
}); });
}); });
it('transforms urls absolute to relative', function () {
let payload = {
src: 'http://127.0.0.1:2369/content/images/2018/08/NatGeo01-9.jpg',
caption: 'A link to <a href="http://127.0.0.1:2369/post">an internal post</a>'
};
const transformed = card.absoluteToRelative(payload, {});
transformed.src
.should.equal('/content/images/2018/08/NatGeo01-9.jpg');
transformed.caption
.should.equal('A link to <a href="/post">an internal post</a>');
});
it('transforms urls relative to absolute', function () {
let payload = {
src: '/content/images/2018/08/NatGeo01-9.jpg',
caption: 'A link to <a href="/post">an internal post</a>'
};
const transformed = card.relativeToAbsolute(payload, {});
transformed.src
.should.equal('http://127.0.0.1:2369/content/images/2018/08/NatGeo01-9.jpg');
transformed.caption
.should.equal('A link to <a href="http://127.0.0.1:2369/post">an internal post</a>');
});
}); });

View File

@ -58,4 +58,26 @@ describe('Markdown card', function () {
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-->'); 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)');
});
}); });