diff --git a/core/server/lib/mobiledoc/cards/bookmark.js b/core/server/lib/mobiledoc/cards/bookmark.js index 9cb1300fee..ba041b29b5 100644 --- a/core/server/lib/mobiledoc/cards/bookmark.js +++ b/core/server/lib/mobiledoc/cards/bookmark.js @@ -90,5 +90,17 @@ module.exports = createCard({ } 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; } }); diff --git a/core/server/lib/mobiledoc/cards/code.js b/core/server/lib/mobiledoc/cards/code.js index 1be54cc03f..dc5bf00a8b 100644 --- a/core/server/lib/mobiledoc/cards/code.js +++ b/core/server/lib/mobiledoc/cards/code.js @@ -34,5 +34,15 @@ module.exports = createCard({ } else { 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; } }); diff --git a/core/server/lib/mobiledoc/cards/embed.js b/core/server/lib/mobiledoc/cards/embed.js index 57d1bffe3e..8fd53a435e 100644 --- a/core/server/lib/mobiledoc/cards/embed.js +++ b/core/server/lib/mobiledoc/cards/embed.js @@ -24,5 +24,15 @@ module.exports = createCard({ } 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; } }); diff --git a/core/server/lib/mobiledoc/cards/gallery.js b/core/server/lib/mobiledoc/cards/gallery.js index f61ddf7092..c7a02931db 100644 --- a/core/server/lib/mobiledoc/cards/gallery.js +++ b/core/server/lib/mobiledoc/cards/gallery.js @@ -108,5 +108,31 @@ module.exports = createCard({ } 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; } }); diff --git a/core/server/lib/mobiledoc/cards/html.js b/core/server/lib/mobiledoc/cards/html.js index 56ea0a3573..380aef4d33 100644 --- a/core/server/lib/mobiledoc/cards/html.js +++ b/core/server/lib/mobiledoc/cards/html.js @@ -11,5 +11,15 @@ module.exports = createCard({ // use the SimpleDOM document to create a raw HTML section. // avoids parsing/rendering of potentially broken or unsupported 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; } }); diff --git a/core/server/lib/mobiledoc/cards/image.js b/core/server/lib/mobiledoc/cards/image.js index 74fd1d97b9..14684761e7 100644 --- a/core/server/lib/mobiledoc/cards/image.js +++ b/core/server/lib/mobiledoc/cards/image.js @@ -39,5 +39,17 @@ module.exports = createCard({ } 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; } }); diff --git a/core/server/lib/mobiledoc/cards/markdown.js b/core/server/lib/mobiledoc/cards/markdown.js index fcd06b9ce7..1cf7ba2374 100644 --- a/core/server/lib/mobiledoc/cards/markdown.js +++ b/core/server/lib/mobiledoc/cards/markdown.js @@ -24,5 +24,15 @@ module.exports = createCard({ // 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; } }); diff --git a/core/server/lib/mobiledoc/create-card.js b/core/server/lib/mobiledoc/create-card.js index 53e4770f75..23a1ed5812 100644 --- a/core/server/lib/mobiledoc/create-card.js +++ b/core/server/lib/mobiledoc/create-card.js @@ -1,9 +1,21 @@ +let urlUtils; + module.exports = function createCard(card) { - const {name, type} = card; + const defaultTransformer = function (payload) { + return payload; + }; + + const { + name, + type, + absoluteToRelative = defaultTransformer, + relativeToAbsolute = defaultTransformer + } = card; return { name, type, + render({env, payload, options}) { const {dom} = env; const cleanName = name.replace(/^card-/, ''); @@ -23,6 +35,26 @@ module.exports = function createCard(card) { fragment.appendChild(endComment); 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); } }; }; diff --git a/core/server/lib/url-utils/index.js b/core/server/lib/url-utils/index.js index 372f103a40..2d70e248c9 100644 --- a/core/server/lib/url-utils/index.js +++ b/core/server/lib/url-utils/index.js @@ -1,5 +1,6 @@ const UrlUtils = require('@tryghost/url-utils'); const config = require('../../config'); +const cards = require('../mobiledoc/cards'); const urlUtils = new UrlUtils({ url: config.get('url'), @@ -7,7 +8,8 @@ const urlUtils = new UrlUtils({ apiVersions: config.get('api:versions'), slugs: config.get('slugs').protected, redirectCacheMaxAge: config.get('caching:301:maxAge'), - baseApiPath: '/ghost/api' + baseApiPath: '/ghost/api', + cardTransformers: cards }); module.exports = urlUtils; diff --git a/core/test/unit/lib/mobiledoc/cards/bookmark_spec.js b/core/test/unit/lib/mobiledoc/cards/bookmark_spec.js index 7e2bcff424..2edde9a288 100644 --- a/core/test/unit/lib/mobiledoc/cards/bookmark_spec.js +++ b/core/test/unit/lib/mobiledoc/cards/bookmark_spec.js @@ -201,4 +201,38 @@ describe('Bookmark card', function () { serializer.serialize(card.render(opts)) .should.equal(''); }); + + it('transforms urls absolute to relative', function () { + let payload = { + metadata: { + url: 'http://127.0.0.1:2369/post' + }, + caption: 'A link to an internal post' + }; + + const transformed = card.absoluteToRelative(payload, {}); + + transformed.metadata.url + .should.equal('/post'); + + transformed.caption + .should.equal('A link to an internal post'); + }); + + it('transforms urls relative to absolute', function () { + let payload = { + metadata: { + url: '/post' + }, + caption: 'A link to an internal post' + }; + + const transformed = card.relativeToAbsolute(payload, {}); + + transformed.metadata.url + .should.equal('http://127.0.0.1:2369/post'); + + transformed.caption + .should.equal('A link to an internal post'); + }); }); diff --git a/core/test/unit/lib/mobiledoc/cards/code_spec.js b/core/test/unit/lib/mobiledoc/cards/code_spec.js index 57723aef1e..9baa1e3c75 100644 --- a/core/test/unit/lib/mobiledoc/cards/code_spec.js +++ b/core/test/unit/lib/mobiledoc/cards/code_spec.js @@ -58,4 +58,26 @@ describe('Code card', function () { serializer.serialize(card.render(opts)).should.match('
<p>Test</p>
Some HTML
'); }); + + it('transforms urls absolute to relative', function () { + let payload = { + caption: 'A link to an internal post' + }; + + const transformed = card.absoluteToRelative(payload, {}); + + transformed.caption + .should.equal('A link to an internal post'); + }); + + it('transforms urls relative to absolute', function () { + let payload = { + caption: 'A link to an internal post' + }; + + const transformed = card.relativeToAbsolute(payload, {}); + + transformed.caption + .should.equal('A link to an internal post'); + }); }); diff --git a/core/test/unit/lib/mobiledoc/cards/embed_spec.js b/core/test/unit/lib/mobiledoc/cards/embed_spec.js index 5f2c274f4d..dbdc2d825c 100644 --- a/core/test/unit/lib/mobiledoc/cards/embed_spec.js +++ b/core/test/unit/lib/mobiledoc/cards/embed_spec.js @@ -69,4 +69,26 @@ describe('Embed card', function () { serializer.serialize(card.render(opts)).should.match('
Testing
Caption
'); }); + + it('transforms urls absolute to relative', function () { + let payload = { + caption: 'A link to an internal post' + }; + + const transformed = card.absoluteToRelative(payload, {}); + + transformed.caption + .should.equal('A link to an internal post'); + }); + + it('transforms urls relative to absolute', function () { + let payload = { + caption: 'A link to an internal post' + }; + + const transformed = card.relativeToAbsolute(payload, {}); + + transformed.caption + .should.equal('A link to an internal post'); + }); }); diff --git a/core/test/unit/lib/mobiledoc/cards/gallery_spec.js b/core/test/unit/lib/mobiledoc/cards/gallery_spec.js index 35f4dcb099..b2c48dc320 100644 --- a/core/test/unit/lib/mobiledoc/cards/gallery_spec.js +++ b/core/test/unit/lib/mobiledoc/cards/gallery_spec.js @@ -138,4 +138,74 @@ describe('Gallery card', function () { serializer.serialize(card.render(opts)).should.eql(''); }); + + 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 an internal post' + }, + { + row: 0, + fileName: 'NatGeo02.jpg', + src: 'http://127.0.0.1:2369/content/images/2018/08/NatGeo02-10.jpg', + caption: 'A link to an internal post' + } + ], + caption: 'A link to an internal post' + }; + + 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 an internal post'); + + transformed.images[1].src + .should.equal('/content/images/2018/08/NatGeo02-10.jpg'); + + transformed.images[1].caption + .should.equal('A link to an internal post'); + + transformed.caption + .should.equal('A link to an internal post'); + }); + + 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 an internal post' + }; + + 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 an internal post'); + }); }); diff --git a/core/test/unit/lib/mobiledoc/cards/html_spec.js b/core/test/unit/lib/mobiledoc/cards/html_spec.js index 32fdd21308..c900cd9439 100644 --- a/core/test/unit/lib/mobiledoc/cards/html_spec.js +++ b/core/test/unit/lib/mobiledoc/cards/html_spec.js @@ -55,4 +55,26 @@ describe('HTML card', function () { serializer.serialize(card.render(opts)).should.eql(''); }); + + it('transforms urls absolute to relative', function () { + let payload = { + html: 'A link to an internal post' + }; + + const transformed = card.absoluteToRelative(payload, {}); + + transformed.html + .should.equal('A link to an internal post'); + }); + + it('transforms urls relative to absolute', function () { + let payload = { + html: 'A link to an internal post' + }; + + const transformed = card.relativeToAbsolute(payload, {}); + + transformed.html + .should.equal('A link to an internal post'); + }); }); diff --git a/core/test/unit/lib/mobiledoc/cards/image_spec.js b/core/test/unit/lib/mobiledoc/cards/image_spec.js index 04f061c774..e3296b2671 100644 --- a/core/test/unit/lib/mobiledoc/cards/image_spec.js +++ b/core/test/unit/lib/mobiledoc/cards/image_spec.js @@ -116,4 +116,34 @@ describe('Image card', function () { serializer.serialize(card.render(opts)).should.eql('
'); }); }); + + 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 an internal post' + }; + + const transformed = card.absoluteToRelative(payload, {}); + + transformed.src + .should.equal('/content/images/2018/08/NatGeo01-9.jpg'); + + transformed.caption + .should.equal('A link to an internal post'); + }); + + it('transforms urls relative to absolute', function () { + let payload = { + src: '/content/images/2018/08/NatGeo01-9.jpg', + caption: 'A link to an internal post' + }; + + 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 an internal post'); + }); }); diff --git a/core/test/unit/lib/mobiledoc/cards/markdown_spec.js b/core/test/unit/lib/mobiledoc/cards/markdown_spec.js index 2b28a330e5..ecd8e718b4 100644 --- a/core/test/unit/lib/mobiledoc/cards/markdown_spec.js +++ b/core/test/unit/lib/mobiledoc/cards/markdown_spec.js @@ -58,4 +58,26 @@ describe('Markdown card', function () { serializer.serialize(card.render(opts)).should.eql('

HEADING

\n\n
'); }); + + 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)'); + }); });