diff --git a/Gruntfile.js b/Gruntfile.js index 09b7ff211c..9f6c9494a7 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -130,7 +130,6 @@ var _ = require('lodash'), '!config*.js', // note: i added this, do we want this linted? 'core/*.js', 'core/server/**/*.js', - 'core/shared/**/*.js', 'core/test/**/*.js', '!core/test/coverage/**', '!core/shared/vendor/**/*.js' @@ -167,7 +166,6 @@ var _ = require('lodash'), '!config*.js', // note: i added this, do we want this linted? 'core/*.js', 'core/server/**/*.js', - 'core/shared/**/*.js', 'core/test/**/*.js', '!core/test/coverage/**', '!core/shared/vendor/**/*.js' diff --git a/core/server/helpers/ghost_head.js b/core/server/helpers/ghost_head.js index 8bfe1ba8f3..38e857c779 100644 --- a/core/server/helpers/ghost_head.js +++ b/core/server/helpers/ghost_head.js @@ -10,6 +10,8 @@ var hbs = require('express-hbs'), moment = require('moment'), _ = require('lodash'), Promise = require('bluebird'), + fs = require('fs'), + path = require('path'), config = require('../config'), filters = require('../filters'), @@ -279,6 +281,23 @@ function finaliseSchema(schema, head) { return head; } +function getAjaxHelper() { + var ghostUrlScript = fs.readFileSync(path.join(config.paths.corePath, 'shared', 'ghost-url.js'), 'utf8'), + template = hbs.compile(ghostUrlScript, path.join(config.paths.subdir || '/', 'shared', 'ghost-url.js')), + apiPath = require('../routes').apiBaseUri, + url, useOrigin; + + if (config.forceAdminSSL) { + url = 'https://' + (config.urlSSL || config.url).replace(/.*?:\/\//g, '').replace(/\/$/, '') + apiPath; + useOrigin = false; + } else { + url = config.paths.subdir + apiPath; + useOrigin = true; + } + + return ''; +} + ghost_head = function (options) { // create a shortcut for theme config blog = config.theme; @@ -341,6 +360,7 @@ ghost_head = function (options) { if (metaData.clientId && metaData.clientSecret) { head.push(writeMetaTag('ghost:client_id', metaData.clientId)); head.push(writeMetaTag('ghost:client_secret', metaData.clientSecret)); + head.push(getAjaxHelper()); } } diff --git a/core/shared/ghost-url.js b/core/shared/ghost-url.js new file mode 100644 index 0000000000..2681fe4c2d --- /dev/null +++ b/core/shared/ghost-url.js @@ -0,0 +1,64 @@ +(function () { + 'use strict'; + + function generateQueryString(object) { + var url = '?', + i; + + if (!object) { + return ''; + } + + for (i in object) { + if (object.hasOwnProperty(i) && (!!object[i] || object[i] === false)) { + url += i + '=' + encodeURIComponent(object[i]) + '&'; + } + } + + return url.substring(0, url.length - 1); + } + + var url = { + config: { + url: '{{api_url}}', + useOrigin: '{{useOrigin}}', + origin: '', + clientId: '', + clientSecret: '' + }, + + api: function () { + var args = Array.prototype.slice.call(arguments), + url = ((this.config.useOrigin === 'true')) ? this.config.origin + this.config.url : this.config.url, + queryOptions; + + if (args.length && typeof args[args.length - 1] === 'object') { + queryOptions = args.pop(); + } else { + queryOptions = {}; + } + + queryOptions.client_id = this.config.clientId; + queryOptions.client_secret = this.config.clientSecret; + + if (args.length) { + args.forEach(function (el) { + url += el.replace(/^\/|\/$/g, '') + '/'; + }); + } + + return url + generateQueryString(queryOptions); + } + }; + + if (typeof window !== 'undefined') { + url.config.origin = window.location.origin; + url.config.clientId = document.querySelector('meta[property=\'ghost:client_id\']').content; + url.config.clientSecret = document.querySelector('meta[property=\'ghost:client_secret\']').content; + window.ghost = window.ghost || {}; + window.ghost.url = url; + } + if (typeof module !== 'undefined') { + module.exports = url; + } +})(); diff --git a/core/test/unit/ghost_url_spec.js b/core/test/unit/ghost_url_spec.js new file mode 100644 index 0000000000..7836bfa918 --- /dev/null +++ b/core/test/unit/ghost_url_spec.js @@ -0,0 +1,90 @@ +/* globals describe, afterEach, it */ +/*jshint expr:true*/ +var url = require('../../shared/ghost-url'); + +describe('Ghost Ajax Helper', function () { + afterEach(function () { + url.config = {}; + }); + + it('renders basic url correctly when no arguments are presented & useOrigin is set to false', function () { + url.config = { + url: 'http://testblog.com/', + useOrigin: 'false', + clientId: '', + clientSecret: '' + }; + + url.api().should.equal('http://testblog.com/'); + }); + + it('renders basic url correctly when no arguments are presented & useOrigin is set to true', function () { + url.config = { + url: '/url/', + useOrigin: 'true', + origin: 'http://originblog.com', + clientId: '', + clientSecret: '' + }; + + url.api().should.equal('http://originblog.com/url/'); + }); + + it('strips arguments of forward and trailing slashes correctly', function () { + url.config = { + url: 'http://testblog.com/', + useOrigin: 'false', + clientId: '', + clientSecret: '' + }; + + url.api('a/', '/b', '/c/').should.equal('http://testblog.com/a/b/c/'); + }); + + it('appends client_id & client_secret to query string automatically', function () { + url.config = { + url: 'http://testblog.com/', + useOrigin: 'false', + clientId: 'ghost-frontend', + clientSecret: 'notasecret' + }; + + url.api().should.equal('http://testblog.com/?client_id=ghost-frontend&client_secret=notasecret'); + }); + + it('generates query parameters correctly', function () { + url.config = { + url: 'http://testblog.com/', + useOrigin: 'false', + clientId: 'ghost-frontend', + clientSecret: 'notasecret' + }; + + var rendered = url.api({a: 'string', b: 5, c: 'en coded'}); + + rendered.should.match(/http:\/\/testblog\.com\/\?/); + rendered.should.match(/client_id=ghost-frontend/); + rendered.should.match(/client_secret=notasecret/); + rendered.should.match(/a/); + rendered.should.match(/b=5/); + rendered.should.match(/c=en\%20coded/); + }); + + it('generates complex query correctly', function () { + url.config = { + url: '/blog/ghost/api/v0.1/', + useOrigin: 'true', + origin: 'https://testblog.com', + clientId: 'ghost-frontend', + clientSecret: 'notasecret' + }; + + var rendered = url.api('posts/', '/tags/', '/count', {include: 'tags,tests', page: 2}); + + rendered.should.match(/https:\/\/testblog\.com\/blog\/ghost\/api\/v0\.1\/posts\/tags\/count\/\?/); + rendered.should.match(/client_id=ghost-frontend/); + rendered.should.match(/client_secret=notasecret/); + rendered.should.match(/include=tags%2Ctests/); + rendered.should.match(/page=2/); + }); +}); diff --git a/core/test/unit/server_helpers/ghost_head_spec.js b/core/test/unit/server_helpers/ghost_head_spec.js index 8c04e94141..f7fc2a2458 100644 --- a/core/test/unit/server_helpers/ghost_head_spec.js +++ b/core/test/unit/server_helpers/ghost_head_spec.js @@ -778,4 +778,104 @@ describe('{{ghost_head}} helper', function () { }).catch(done); }); }); + + describe('with Ajax Helper', function () { + beforeEach(function () { + utils.overrideConfig({ + url: '', + urlSSL: '', + forceAdminSSL: false + }); + }); + + it('renders script tags with basic configuration', function (done) { + helpers.ghost_head.call( + {safeVersion: '0.3', context: ['paged', 'index'], post: false}, + {data: {root: {context: []}}} + ).then(function (rendered) { + should.exist(rendered); + expectGhostClientMeta(rendered); + rendered.string.should.match(/