Remove auto-inclusion of jQuery via ghost_foot

closes #5298
- remove all harcoded instances of jQuery throughout the front-end of the blog
- add migration function to add cdn link to ghost_foot code injection when migrating up from version 003
- migration version bump
This commit is contained in:
Austin Burdine 2015-08-18 09:08:52 -04:00 committed by Hannah Wolfe
parent 52e35a282b
commit 6c5fea40ca
8 changed files with 134 additions and 132 deletions

View File

@ -403,24 +403,10 @@ var _ = require('lodash'),
// ### grunt-contrib-copy // ### grunt-contrib-copy
// Copy files into their correct locations as part of building assets, or creating release zips // Copy files into their correct locations as part of building assets, or creating release zips
copy: { copy: {
jquery: {
cwd: 'core/client/bower_components/jquery/dist/',
src: 'jquery.js',
dest: 'core/built/public/',
expand: true,
nonull: true
},
release: { release: {
files: [{
cwd: 'core/client/bower_components/jquery/dist/',
src: 'jquery.js',
dest: 'core/built/public/',
expand: true
}, {
expand: true, expand: true,
src: buildGlob, src: buildGlob,
dest: '<%= paths.releaseBuild %>/' dest: '<%= paths.releaseBuild %>/'
}]
} }
}, },
@ -437,27 +423,6 @@ var _ = require('lodash'),
} }
}, },
// ### grunt-contrib-uglify
// Minify concatenated javascript files ready for production
uglify: {
prod: {
options: {
sourceMap: false
},
files: {
'core/built/public/jquery.min.js': 'core/built/public/jquery.js'
}
},
release: {
options: {
sourceMap: false
},
files: {
'core/built/public/jquery.min.js': 'core/built/public/jquery.js'
}
}
},
// ### grunt-update-submodules // ### grunt-update-submodules
// Grunt task to update git submodules // Grunt task to update git submodules
update_submodules: { update_submodules: {
@ -920,7 +885,7 @@ var _ = require('lodash'),
// ### Basic Asset Building // ### Basic Asset Building
// Builds and moves necessary client assets. Prod additionally builds the ember app. // Builds and moves necessary client assets. Prod additionally builds the ember app.
grunt.registerTask('assets', 'Basic asset building & moving', grunt.registerTask('assets', 'Basic asset building & moving',
['clean:tmp', 'buildAboutPage', 'copy:jquery']); ['clean:tmp', 'buildAboutPage']);
// ### Default asset build // ### Default asset build
// `grunt` - default grunt task // `grunt` - default grunt task
@ -934,7 +899,7 @@ var _ = require('lodash'),
// //
// It is otherwise the same as running `grunt`, but is only used when running Ghost in the `production` env. // It is otherwise the same as running `grunt`, but is only used when running Ghost in the `production` env.
grunt.registerTask('prod', 'Build JS & templates for production', grunt.registerTask('prod', 'Build JS & templates for production',
['shell:ember:prod', 'uglify:prod', 'master-warn']); ['shell:ember:prod', 'master-warn']);
// ### Live reload // ### Live reload
// `grunt dev` - build assets on the fly whilst developing // `grunt dev` - build assets on the fly whilst developing
@ -961,7 +926,7 @@ var _ = require('lodash'),
' - Copy files to release-folder/#/#{version} directory\n' + ' - Copy files to release-folder/#/#{version} directory\n' +
' - Clean out unnecessary files (travis, .git*, etc)\n' + ' - Clean out unnecessary files (travis, .git*, etc)\n' +
' - Zip files in release-folder to dist-folder/#{version} directory', ' - Zip files in release-folder to dist-folder/#{version} directory',
['init', 'shell:ember:prod', 'uglify:release', 'clean:release', 'shell:shrinkwrap', 'copy:release', 'compress:release']); ['init', 'shell:ember:prod', 'clean:release', 'shell:shrinkwrap', 'copy:release', 'compress:release']);
}; };
module.exports = configureGrunt; module.exports = configureGrunt;

View File

@ -2,10 +2,11 @@
// RESTful API for creating notifications // RESTful API for creating notifications
var Promise = require('bluebird'), var Promise = require('bluebird'),
_ = require('lodash'), _ = require('lodash'),
canThis = require('../permissions').canThis, permissions = require('../permissions'),
errors = require('../errors'), errors = require('../errors'),
utils = require('./utils'), utils = require('./utils'),
pipeline = require('../utils/pipeline'), pipeline = require('../utils/pipeline'),
canThis = permissions.canThis,
// Holds the persistent notifications // Holds the persistent notifications
notificationsStore = [], notificationsStore = [],
@ -57,6 +58,10 @@ notifications = {
* @returns {Object} options * @returns {Object} options
*/ */
function handlePermissions(options) { function handlePermissions(options) {
if (permissions.parseContext(options.context).internal) {
return Promise.resolve(options);
}
return canThis(options.context).add.notification().then(function () { return canThis(options.context).add.notification().then(function () {
return options; return options;
}, function () { }, function () {

View File

@ -9,14 +9,17 @@ var Promise = require('bluebird'),
sequence = require('../../utils/sequence'), sequence = require('../../utils/sequence'),
_ = require('lodash'), _ = require('lodash'),
errors = require('../../errors'), errors = require('../../errors'),
config = require('../../config'),
utils = require('../../utils'), utils = require('../../utils'),
models = require('../../models'), models = require('../../models'),
fixtures = require('./fixtures'), fixtures = require('./fixtures'),
permissions = require('./permissions'), permissions = require('./permissions'),
notifications = require('../../api/notifications'),
// Private // Private
logInfo, logInfo,
to003, to003,
to004,
convertAdminToOwner, convertAdminToOwner,
createOwner, createOwner,
options = {context: {internal: true}}, options = {context: {internal: true}},
@ -125,7 +128,7 @@ to003 = function () {
Role = models.Role, Role = models.Role,
Client = models.Client; Client = models.Client;
logInfo('Upgrading fixtures'); logInfo('Upgrading fixtures to 003');
// Add the client fixture if missing // Add the client fixture if missing
upgradeOp = Client.findOne({secret: fixtures.clients[0].secret}).then(function (client) { upgradeOp = Client.findOne({secret: fixtures.clients[0].secret}).then(function (client) {
@ -156,13 +159,61 @@ to003 = function () {
}); });
}; };
/**
* Update ghost_foot to include a CDN of jquery if the DB is migrating from
* @return {Promise}
*/
to004 = function () {
var value,
jquery = [
'<!-- You can safely delete this line if your theme does not require jQuery -->\n',
'<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>\n\n'
],
privacyMessage = [
'jQuery has been removed from Ghost core and is now being loaded from the jQuery Foundation\'s CDN.',
'This can be changed or removed in your <strong>Code Injection</strong> settings area.'
];
logInfo('Upgrading fixtures to 004');
return models.Settings.findOne('ghost_foot').then(function (setting) {
if (setting) {
value = setting.attributes.value;
// Only add jQuery if it's not already in there
if (value.indexOf(jquery.join('')) === -1) {
logInfo('Adding jQuery link to ghost_foot');
value = jquery.join('') + value;
return models.Settings.edit({key: 'ghost_foot', value: value}, options).then(function () {
if (_.isEmpty(config.privacy)) {
return Promise.resolve();
}
logInfo(privacyMessage.join(' ').replace(/<\/?strong>/g, ''));
return notifications.add({notifications: [{
type: 'info',
message: privacyMessage.join(' ')
}]}, options);
});
}
}
});
};
update = function (fromVersion, toVersion) { update = function (fromVersion, toVersion) {
var ops = [];
logInfo('Updating fixtures'); logInfo('Updating fixtures');
// Are we migrating to, or past 003? // Are we migrating to, or past 003?
if ((fromVersion < '003' && toVersion >= '003') || if ((fromVersion < '003' && toVersion >= '003') ||
fromVersion === '003' && toVersion === '003' && process.env.FORCE_MIGRATION) { fromVersion === '003' && toVersion === '003' && process.env.FORCE_MIGRATION) {
return to003(); ops.push(to003);
} }
if (fromVersion < '004' && toVersion === '004' ||
fromVersion === '004' && toVersion === '004' && process.env.FORCE_MIGRATION) {
ops.push(to004);
}
return sequence(ops);
}; };
module.exports = { module.exports = {

View File

@ -8,21 +8,13 @@
var hbs = require('express-hbs'), var hbs = require('express-hbs'),
_ = require('lodash'), _ = require('lodash'),
config = require('../config'),
filters = require('../filters'), filters = require('../filters'),
api = require('../api'), api = require('../api'),
utils = require('./utils'),
ghost_foot; ghost_foot;
ghost_foot = function (options) { ghost_foot = function (options) {
/*jshint unused:false*/ /*jshint unused:false*/
var jquery = utils.isProduction ? 'jquery.min.js' : 'jquery.js', var foot = [];
foot = [];
foot.push(utils.scriptTemplate({
source: config.paths.subdir + '/public/' + jquery,
version: config.assetHash
}));
return api.settings.read({key: 'ghost_foot'}).then(function (response) { return api.settings.read({key: 'ghost_foot'}).then(function (response) {
foot.push(response.settings[0].value); foot.push(response.settings[0].value);

View File

@ -128,7 +128,11 @@ describe('Tags API', function () {
}); });
describe('Browse', function () { describe('Browse', function () {
beforeEach(testUtils.setup('tags')); beforeEach(function (done) {
testUtils.fixtures.insertMoreTags().then(function () {
done();
});
});
it('can browse (internal)', function (done) { it('can browse (internal)', function (done) {
TagAPI.browse(testUtils.context.internal).then(function (results) { TagAPI.browse(testUtils.context.internal).then(function (results) {

View File

@ -0,0 +1,40 @@
/*globals describe, before, beforeEach, afterEach, it */
/*jshint expr:true*/
var testUtils = require('../utils'),
should = require('should'),
migration = require('../../server/data/migration/index'),
Models = require('../../server/models');
describe('Database Migration (special functions)', function () {
before(testUtils.teardown);
afterEach(testUtils.teardown);
describe('004', function () {
beforeEach(testUtils.setup('settings'));
it('should add jQuery to ghost_foot injection setting', function (done) {
Models.Settings.findOne('ghost_foot').then(function (setting) {
should.exist(setting);
should.exist(setting.attributes);
setting.attributes.value.should.equal('');
process.env.FORCE_MIGRATION = true; // force a migration
migration.init().then(function () {
Models.Settings.findOne('ghost_foot').then(function (result) {
var jquery = [
'<!-- You can safely delete this line if your theme does not require jQuery -->\n',
'<script type="text/javascript" src="https://code.jquery.com/jquery-1.11.3.min.js"></script>\n\n'
];
should.exist(result);
should.exist(result.attributes);
result.attributes.value.should.equal(jquery.join(''));
done();
});
});
});
});
});
});

View File

@ -19,85 +19,31 @@ describe('{{ghost_foot}} helper', function () {
utils.loadHelpers(); utils.loadHelpers();
}); });
afterEach(function () {
sandbox.restore();
utils.restoreConfig();
helpers.__set__('utils.isProduction', false);
});
describe('without Code Injection', function () {
beforeEach(function () { beforeEach(function () {
sandbox = sinon.sandbox.create(); sandbox = sinon.sandbox.create();
sandbox.stub(api.settings, 'read', function () {
return Promise.resolve({
settings: [{value: ''}]
});
});
}); });
it('has loaded ghost_foot helper', function () { it('has loaded ghost_foot helper', function () {
should.exist(handlebars.helpers.ghost_foot); should.exist(handlebars.helpers.ghost_foot);
}); });
it('outputs correct jquery for development mode', function (done) { it('outputs correct injected code', function (done) {
utils.overrideConfig({assetHash: 'abc'});
helpers.ghost_foot.call().then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<script src=".*\/public\/jquery.js\?v=abc"><\/script>/);
done();
}).catch(done);
});
it('outputs correct jquery for production mode', function (done) {
utils.overrideConfig({assetHash: 'abc'});
helpers.__set__('utils.isProduction', true);
helpers.ghost_foot.call().then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<script src=".*\/public\/jquery.min.js\?v=abc"><\/script>/);
done();
}).catch(done);
});
});
describe('with Code Injection', function () {
beforeEach(function () {
sandbox = sinon.sandbox.create();
sandbox.stub(api.settings, 'read', function () { sandbox.stub(api.settings, 'read', function () {
return Promise.resolve({ return Promise.resolve({
settings: [{value: '<script></script>'}] settings: [{value: '<script type="text/javascript">var test = \'I am a variable!\'</script>'}]
}); });
}); });
helpers.ghost_foot.call().then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<script type="text\/javascript">var test = 'I am a variable!'<\/script>/);
done();
}).catch(done);
}); });
afterEach(function () { afterEach(function () {
sandbox.restore(); sandbox.restore();
}); utils.restoreConfig();
it('outputs correct jquery for development mode', function (done) {
utils.overrideConfig({assetHash: 'abc'});
helpers.ghost_foot.call().then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<script src=".*\/public\/jquery.js\?v=abc"><\/script> <script><\/script>/);
done();
}).catch(done);
});
it('outputs correct jquery for production mode', function (done) {
utils.overrideConfig({assetHash: 'abc'});
helpers.__set__('utils.isProduction', true);
helpers.ghost_foot.call().then(function (rendered) {
should.exist(rendered);
rendered.string.should.match(/<script src=".*\/public\/jquery.min.js\?v=abc"><\/script> <script><\/script>/);
done();
}).catch(done);
});
}); });
}); });

View File

@ -79,7 +79,6 @@
"grunt-contrib-compress": "0.13.0", "grunt-contrib-compress": "0.13.0",
"grunt-contrib-copy": "0.8.0", "grunt-contrib-copy": "0.8.0",
"grunt-contrib-jshint": "0.11.2", "grunt-contrib-jshint": "0.11.2",
"grunt-contrib-uglify": "0.9.1",
"grunt-contrib-watch": "0.6.1", "grunt-contrib-watch": "0.6.1",
"grunt-docker": "0.0.10", "grunt-docker": "0.0.10",
"grunt-express-server": "0.5.1", "grunt-express-server": "0.5.1",