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(/