From 936acdb693dd5a02825faebacd8f7711ce246f5b Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Thu, 31 Jul 2014 16:29:35 -0400 Subject: [PATCH 1/2] Remove lodash from admin client. * Adds `bind`, `isFinite`, and `isNumber` utility functions from lodash. * Use new util funtions instead of lodash throughout the codebase. * Remove lodash from vendor builds. --- ghost/admin/components/gh-codemirror.js | 12 ++++++++---- ghost/admin/components/gh-markdown.js | 17 ++++++++++++----- ghost/admin/controllers/post-settings-menu.js | 3 ++- ghost/admin/controllers/settings/users/user.js | 3 ++- ghost/admin/mixins/pagination-route.js | 6 +++++- ghost/admin/models/user.js | 16 +++++++++------- ghost/admin/routes/editor/edit.js | 4 +++- ghost/admin/routes/posts/post.js | 4 +++- ghost/admin/routes/settings/index.js | 1 + ghost/admin/utils/bind.js | 15 +++++++++++++++ ghost/admin/utils/isFinite.js | 9 +++++++++ ghost/admin/utils/isNumber.js | 10 ++++++++++ ghost/admin/utils/validator-extensions.js | 2 +- ghost/admin/validators/user.js | 2 +- ghost/admin/views/settings.js | 1 + 15 files changed, 82 insertions(+), 23 deletions(-) create mode 100644 ghost/admin/utils/bind.js create mode 100644 ghost/admin/utils/isFinite.js create mode 100644 ghost/admin/utils/isNumber.js diff --git a/ghost/admin/components/gh-codemirror.js b/ghost/admin/components/gh-codemirror.js index 6e289344ab..a65b4acb72 100644 --- a/ghost/admin/components/gh-codemirror.js +++ b/ghost/admin/components/gh-codemirror.js @@ -4,20 +4,21 @@ import MarkerManager from 'ghost/mixins/marker-manager'; import mobileCodeMirror from 'ghost/utils/codemirror-mobile'; import setScrollClassName from 'ghost/utils/set-scroll-classname'; import codeMirrorShortcuts from 'ghost/utils/codemirror-shortcuts'; +import bind from 'ghost/utils/bind'; codeMirrorShortcuts.init(); var onChangeHandler = function (cm, changeObj) { var line, - component = cm.component; + component = cm.component, // fill array with a range of numbers for (line = changeObj.from.line; line < changeObj.from.line + changeObj.text.length; line += 1) { - component.checkLine(line, changeObj.origin); + component.checkLine.call(component, line, changeObj.origin); } // Is this a line which may have had a marker on it? - component.checkMarkers(); + component.checkMarkers.call(component); cm.component.set('value', cm.getValue()); @@ -50,7 +51,10 @@ var Codemirror = Ember.TextArea.extend(MarkerManager, { }, afterRenderEvent: function () { - var initMarkers = _.bind(this.initMarkers, this); + var self = this; + function initMarkers () { + self.initMarkers.apply(self, arguments); + } // replaces CodeMirror with TouchEditor only if we're on mobile mobileCodeMirror.createIfMobile(); diff --git a/ghost/admin/components/gh-markdown.js b/ghost/admin/components/gh-markdown.js index 39d6101c20..b83e0d6252 100644 --- a/ghost/admin/components/gh-markdown.js +++ b/ghost/admin/components/gh-markdown.js @@ -16,17 +16,24 @@ var Markdown = Ember.Component.extend({ // might need to make sure markdown has been processed first reInitDropzones: function () { Ember.run.scheduleOnce('afterRender', this, function () { - var dropzones = $('.js-drop-zone'); + var dropzones = $('.js-drop-zone'), + self = this; uploader.call(dropzones, { editor: true, fileStorage: this.get('config.fileStorage') }); - dropzones.on('uploadstart', _.bind(this.sendAction, this, 'uploadStarted')); - dropzones.on('uploadfailure', _.bind(this.sendAction, this, 'uploadFinished')); - dropzones.on('uploadsuccess', _.bind(this.sendAction, this, 'uploadFinished')); - dropzones.on('uploadsuccess', _.bind(this.sendAction, this, 'uploadSuccess')); + function boundSendAction(actionName) { + return function() { + self.sendAction.call(self, actionName); + } + } + + dropzones.on('uploadstart', boundSendAction('uploadStarted')); + dropzones.on('uploadfailure', boundSendAction('uploadFinished')); + dropzones.on('uploadsuccess', boundSendAction('uploadFinished')); + dropzones.on('uploadsuccess', boundSendAction('uploadSuccess')); }); }.observes('markdown') }); diff --git a/ghost/admin/controllers/post-settings-menu.js b/ghost/admin/controllers/post-settings-menu.js index cac3c9ed0c..a015ad1259 100644 --- a/ghost/admin/controllers/post-settings-menu.js +++ b/ghost/admin/controllers/post-settings-menu.js @@ -2,6 +2,7 @@ import {parseDateString, formatDate} from 'ghost/utils/date-formatting'; import SlugGenerator from 'ghost/models/slug-generator'; import boundOneWay from 'ghost/utils/bound-one-way'; +import isNumber from 'ghost/utils/isNumber'; var PostSettingsMenuController = Ember.ObjectController.extend({ //State for if the user is viewing a tab's pane. @@ -274,7 +275,7 @@ var PostSettingsMenuController = Ember.ObjectController.extend({ // if the candidate slug is the same as the existing slug except // for the incrementor then the existing slug should be used - if (_.isNumber(check) && check > 0) { + if (isNumber(check) && check > 0) { if (slug === slugTokens.join('-') && serverSlug !== newSlug) { self.set('slugValue', slug); diff --git a/ghost/admin/controllers/settings/users/user.js b/ghost/admin/controllers/settings/users/user.js index 95d5214b84..dc641a6235 100644 --- a/ghost/admin/controllers/settings/users/user.js +++ b/ghost/admin/controllers/settings/users/user.js @@ -1,4 +1,5 @@ import SlugGenerator from 'ghost/models/slug-generator'; +import isNumber from 'ghost/utils/isNumber'; var SettingsUserController = Ember.ObjectController.extend({ @@ -207,7 +208,7 @@ var SettingsUserController = Ember.ObjectController.extend({ // if the candidate slug is the same as the existing slug except // for the incrementor then the existing slug should be used - if (_.isNumber(check) && check > 0) { + if (isNumber(check) && check > 0) { if (slug === slugTokens.join('-') && serverSlug !== newSlug) { self.set('slugValue', slug); diff --git a/ghost/admin/mixins/pagination-route.js b/ghost/admin/mixins/pagination-route.js index 874e7a7cc7..40f6e076b8 100644 --- a/ghost/admin/mixins/pagination-route.js +++ b/ghost/admin/mixins/pagination-route.js @@ -12,7 +12,11 @@ var PaginationRoute = Ember.Mixin.create({ setupPagination: function (settings) { settings = settings || {}; - settings = _.defaults(settings, defaultPaginationSettings); + for (var key in defaultPaginationSettings) { + if (!settings.hasOwnProperty(key)) { + settings[key] = defaultPaginationSettings[key]; + } + } this.set('paginationSettings', settings); this.controller.set('paginationSettings', settings); diff --git a/ghost/admin/models/user.js b/ghost/admin/models/user.js index de05236b82..cf4d9414de 100644 --- a/ghost/admin/models/user.js +++ b/ghost/admin/models/user.js @@ -87,13 +87,15 @@ var User = DS.Model.extend(NProgressSaveMixin, SelectiveSaveMixin, ValidationEng isPasswordValid: Ember.computed.empty('passwordValidationErrors.[]'), - active: Ember.computed('status', function () { - return _.contains(['active', 'warn-1', 'warn-2', 'warn-3', 'warn-4', 'locked'], this.get('status')); - }), - invited: Ember.computed('status', function () { - return _.contains(['invited', 'invited-pending'], this.get('status')); - }), - pending: Ember.computed.equal('status', 'invited-pending') + active: function () { + return ['active', 'warn-1', 'warn-2', 'warn-3', 'warn-4', 'locked'].indexOf(this.get('status')) > -1; + }.property('status'), + + invited: function () { + return ['invited', 'invited-pending'].indexOf(this.get('status')) > -1; + }.property('status'), + + pending: Ember.computed.equal('status', 'invited-pending').property('status') }); export default User; diff --git a/ghost/admin/routes/editor/edit.js b/ghost/admin/routes/editor/edit.js index 208714259b..6d987bd81d 100644 --- a/ghost/admin/routes/editor/edit.js +++ b/ghost/admin/routes/editor/edit.js @@ -1,4 +1,6 @@ import base from 'ghost/mixins/editor-route-base'; +import isNumber from 'ghost/utils/isNumber'; +import isFinite from 'ghost/utils/isFinite'; var EditorEditRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, base, { classNames: ['editor'], @@ -11,7 +13,7 @@ var EditorEditRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, bas postId = Number(params.post_id); - if (!_.isNumber(postId) || !_.isFinite(postId) || postId % 1 !== 0 || postId <= 0) { + if (!isNumber(postId) || !isFinite(postId) || postId % 1 !== 0 || postId <= 0) { return this.transitionTo('error404', 'editor/' + params.post_id); } diff --git a/ghost/admin/routes/posts/post.js b/ghost/admin/routes/posts/post.js index b75f1ee2fa..c493f6c130 100644 --- a/ghost/admin/routes/posts/post.js +++ b/ghost/admin/routes/posts/post.js @@ -1,5 +1,7 @@ import loadingIndicator from 'ghost/mixins/loading-indicator'; import ShortcutsRoute from 'ghost/mixins/shortcuts-route'; +import isNumber from 'ghost/utils/isNumber'; +import isFinite from 'ghost/utils/isFinite'; var PostsPostRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, loadingIndicator, ShortcutsRoute, { model: function (params) { @@ -10,7 +12,7 @@ var PostsPostRoute = Ember.Route.extend(SimpleAuth.AuthenticatedRouteMixin, load postId = Number(params.post_id); - if (!_.isNumber(postId) || !_.isFinite(postId) || postId % 1 !== 0 || postId <= 0) + if (!isNumber(postId) || !isFinite(postId) || postId % 1 !== 0 || postId <= 0) { return this.transitionTo('error404', params.post_id); } diff --git a/ghost/admin/routes/settings/index.js b/ghost/admin/routes/settings/index.js index a48b516456..0cf8c5c091 100644 --- a/ghost/admin/routes/settings/index.js +++ b/ghost/admin/routes/settings/index.js @@ -1,6 +1,7 @@ import MobileIndexRoute from 'ghost/routes/mobile-index-route'; import CurrentUserSettings from 'ghost/mixins/current-user-settings'; import mobileQuery from 'ghost/utils/mobile'; +import bind from 'ghost/utils/bind'; var SettingsIndexRoute = MobileIndexRoute.extend(SimpleAuth.AuthenticatedRouteMixin, CurrentUserSettings, { // Redirect users without permission to view settings, diff --git a/ghost/admin/utils/bind.js b/ghost/admin/utils/bind.js new file mode 100644 index 0000000000..ef0fcf6448 --- /dev/null +++ b/ghost/admin/utils/bind.js @@ -0,0 +1,15 @@ +var slice = Array.prototype.slice; + +function bind(/* func, args, thisArg */) { + var args = slice.call(arguments), + func = args.shift(), + thisArg = args.pop(); + + function bound() { + return func.apply(thisArg, args); + } + + return bound; +} + +export default bind; diff --git a/ghost/admin/utils/isFinite.js b/ghost/admin/utils/isFinite.js new file mode 100644 index 0000000000..808669ba5a --- /dev/null +++ b/ghost/admin/utils/isFinite.js @@ -0,0 +1,9 @@ +/* globals window */ + +// isFinite function from lodash + +function isFinite(value) { + return window.isFinite(value) && !window.isNaN(parseFloat(value)); +} + +export default isFinite; diff --git a/ghost/admin/utils/isNumber.js b/ghost/admin/utils/isNumber.js new file mode 100644 index 0000000000..01e7e54722 --- /dev/null +++ b/ghost/admin/utils/isNumber.js @@ -0,0 +1,10 @@ +// isNumber function from lodash + +var toString = Object.prototype.toString; + +function isNumber(value) { + return typeof value === 'number' || + value && typeof value === 'object' && toString.call(value) === '[object Number]' || false; +} + +export default isNumber; diff --git a/ghost/admin/utils/validator-extensions.js b/ghost/admin/utils/validator-extensions.js index 3319034f3f..7da53d14ae 100644 --- a/ghost/admin/utils/validator-extensions.js +++ b/ghost/admin/utils/validator-extensions.js @@ -6,7 +6,7 @@ function init() { }); validator.extend('notContains', function (str, badString) { - return !_.contains(str, badString); + return str.indexOf(badString) === -1; }); } diff --git a/ghost/admin/validators/user.js b/ghost/admin/validators/user.js index 5c386c7e36..2c1fae5777 100644 --- a/ghost/admin/validators/user.js +++ b/ghost/admin/validators/user.js @@ -50,7 +50,7 @@ var UserValidator = Ember.Object.create({ validationErrors.push({ message: 'Location is too long' }); } - if (!_.isEmpty(website) && + if (!Ember.isEmpty(website) && (!validator.isURL(website, { protocols: ['http', 'https'], require_protocol: true }) || !validator.isLength(website, 0, 2000))) { diff --git a/ghost/admin/views/settings.js b/ghost/admin/views/settings.js index 516ca3adc7..29f0aa44da 100644 --- a/ghost/admin/views/settings.js +++ b/ghost/admin/views/settings.js @@ -1,4 +1,5 @@ import MobileParentView from 'ghost/views/mobile/parent-view'; +import bind from 'ghost/utils/bind'; var SettingsView = MobileParentView.extend({ // MobileParentView callbacks From b4e3ac34cd1484601857900ff69a723d1b84f496 Mon Sep 17 00:00:00 2001 From: Jason Williams Date: Sat, 4 Oct 2014 16:38:15 +0000 Subject: [PATCH 2/2] Fix up PR #3491, remove lodash from admin Refs #3491 --- ghost/admin/components/gh-codemirror.js | 3 +-- ghost/admin/components/gh-markdown.js | 21 +++++++------------ ghost/admin/controllers/post-settings-menu.js | 2 +- ghost/admin/mixins/pagination-route.js | 6 ++++-- ghost/admin/models/user.js | 2 +- ghost/admin/routes/settings/index.js | 1 - ghost/admin/views/application.js | 3 ++- ghost/admin/views/settings.js | 1 - 8 files changed, 17 insertions(+), 22 deletions(-) diff --git a/ghost/admin/components/gh-codemirror.js b/ghost/admin/components/gh-codemirror.js index a65b4acb72..2febdbfc2f 100644 --- a/ghost/admin/components/gh-codemirror.js +++ b/ghost/admin/components/gh-codemirror.js @@ -4,13 +4,12 @@ import MarkerManager from 'ghost/mixins/marker-manager'; import mobileCodeMirror from 'ghost/utils/codemirror-mobile'; import setScrollClassName from 'ghost/utils/set-scroll-classname'; import codeMirrorShortcuts from 'ghost/utils/codemirror-shortcuts'; -import bind from 'ghost/utils/bind'; codeMirrorShortcuts.init(); var onChangeHandler = function (cm, changeObj) { var line, - component = cm.component, + component = cm.component; // fill array with a range of numbers for (line = changeObj.from.line; line < changeObj.from.line + changeObj.text.length; line += 1) { diff --git a/ghost/admin/components/gh-markdown.js b/ghost/admin/components/gh-markdown.js index b83e0d6252..f949e5d71f 100644 --- a/ghost/admin/components/gh-markdown.js +++ b/ghost/admin/components/gh-markdown.js @@ -15,26 +15,21 @@ var Markdown = Ember.Component.extend({ // fire off 'enable' API function from uploadManager // might need to make sure markdown has been processed first reInitDropzones: function () { - Ember.run.scheduleOnce('afterRender', this, function () { - var dropzones = $('.js-drop-zone'), - self = this; + function handleDropzoneEvents() { + var dropzones = $('.js-drop-zone'); uploader.call(dropzones, { editor: true, fileStorage: this.get('config.fileStorage') }); - function boundSendAction(actionName) { - return function() { - self.sendAction.call(self, actionName); - } - } + dropzones.on('uploadstart', Ember.run.bind(this, 'sendAction', 'uploadStarted')); + dropzones.on('uploadfailure', Ember.run.bind(this, 'sendAction', 'uploadFinished')); + dropzones.on('uploadsuccess', Ember.run.bind(this, 'sendAction', 'uploadFinished')); + dropzones.on('uploadsuccess', Ember.run.bind(this, 'sendAction', 'uploadSuccess')); + } - dropzones.on('uploadstart', boundSendAction('uploadStarted')); - dropzones.on('uploadfailure', boundSendAction('uploadFinished')); - dropzones.on('uploadsuccess', boundSendAction('uploadFinished')); - dropzones.on('uploadsuccess', boundSendAction('uploadSuccess')); - }); + Ember.run.scheduleOnce('afterRender', this, handleDropzoneEvents); }.observes('markdown') }); diff --git a/ghost/admin/controllers/post-settings-menu.js b/ghost/admin/controllers/post-settings-menu.js index a015ad1259..0808619992 100644 --- a/ghost/admin/controllers/post-settings-menu.js +++ b/ghost/admin/controllers/post-settings-menu.js @@ -136,7 +136,7 @@ var PostSettingsMenuController = Ember.ObjectController.extend({ el = $('.rendered-markdown'); // Get rendered markdown - if (!_.isUndefined(el) && el.length > 0) { + if (el !== undefined && el.length > 0) { html = el.clone(); html.find('.image-uploader').remove(); html = html[0].innerHTML; diff --git a/ghost/admin/mixins/pagination-route.js b/ghost/admin/mixins/pagination-route.js index 40f6e076b8..192fb15f13 100644 --- a/ghost/admin/mixins/pagination-route.js +++ b/ghost/admin/mixins/pagination-route.js @@ -13,8 +13,10 @@ var PaginationRoute = Ember.Mixin.create({ settings = settings || {}; for (var key in defaultPaginationSettings) { - if (!settings.hasOwnProperty(key)) { - settings[key] = defaultPaginationSettings[key]; + if (defaultPaginationSettings.hasOwnProperty(key)) { + if (!settings.hasOwnProperty(key)) { + settings[key] = defaultPaginationSettings[key]; + } } } diff --git a/ghost/admin/models/user.js b/ghost/admin/models/user.js index cf4d9414de..520576c417 100644 --- a/ghost/admin/models/user.js +++ b/ghost/admin/models/user.js @@ -90,7 +90,7 @@ var User = DS.Model.extend(NProgressSaveMixin, SelectiveSaveMixin, ValidationEng active: function () { return ['active', 'warn-1', 'warn-2', 'warn-3', 'warn-4', 'locked'].indexOf(this.get('status')) > -1; }.property('status'), - + invited: function () { return ['invited', 'invited-pending'].indexOf(this.get('status')) > -1; }.property('status'), diff --git a/ghost/admin/routes/settings/index.js b/ghost/admin/routes/settings/index.js index 0cf8c5c091..a48b516456 100644 --- a/ghost/admin/routes/settings/index.js +++ b/ghost/admin/routes/settings/index.js @@ -1,7 +1,6 @@ import MobileIndexRoute from 'ghost/routes/mobile-index-route'; import CurrentUserSettings from 'ghost/mixins/current-user-settings'; import mobileQuery from 'ghost/utils/mobile'; -import bind from 'ghost/utils/bind'; var SettingsIndexRoute = MobileIndexRoute.extend(SimpleAuth.AuthenticatedRouteMixin, CurrentUserSettings, { // Redirect users without permission to view settings, diff --git a/ghost/admin/views/application.js b/ghost/admin/views/application.js index 4f1f5db79c..f9dece9b78 100644 --- a/ghost/admin/views/application.js +++ b/ghost/admin/views/application.js @@ -1,4 +1,5 @@ import mobileQuery from 'ghost/utils/mobile'; +import bind from 'ghost/utils/bind'; var ApplicationView = Ember.View.extend({ elementId: 'container', @@ -43,7 +44,7 @@ var ApplicationView = Ember.View.extend({ }.observes('controller.showGlobalMobileNav'), setupCloseNavOnDesktop: function () { - this.set('closeGlobalMobileNavOnDesktop', _.bind(function closeGlobalMobileNavOnDesktop(mq) { + this.set('closeGlobalMobileNavOnDesktop', bind(function closeGlobalMobileNavOnDesktop(mq) { if (!mq.matches) { // Is desktop sized this.set('controller.showGlobalMobileNav', false); diff --git a/ghost/admin/views/settings.js b/ghost/admin/views/settings.js index 29f0aa44da..516ca3adc7 100644 --- a/ghost/admin/views/settings.js +++ b/ghost/admin/views/settings.js @@ -1,5 +1,4 @@ import MobileParentView from 'ghost/views/mobile/parent-view'; -import bind from 'ghost/utils/bind'; var SettingsView = MobileParentView.extend({ // MobileParentView callbacks