diff --git a/.editorconfig b/.editorconfig index 5d5dea4ccc8..3ef6636eed9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,23 +11,24 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space -indent_size = 2 +indent_size = 4 [*.js] indent_style = space -indent_size = 2 +indent_size = 4 [*.hbs] +insert_final_newline = false indent_style = space -indent_size = 2 +indent_size = 4 [*.css] indent_style = space -indent_size = 2 +indent_size = 4 [*.html] indent_style = space -indent_size = 2 +indent_size = 4 -[*.md] +[*.{diff,md}] trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore index 5125d2549d2..3a9963e3d61 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ # dependencies /node_modules -/bower_components/* +/bower_components # misc /.sass-cache diff --git a/.jshintrc b/.jshintrc index 07ba69595b8..08096effaab 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,13 +1,11 @@ { - "predef": { - "document": true, - "window": true, - "-Promise": true, - "moment": true, - "google": true - }, - "browser" : true, - "boss" : true, + "predef": [ + "document", + "window", + "-Promise" + ], + "browser": true, + "boss": true, "curly": true, "debug": false, "devel": true, diff --git a/.travis.yml b/.travis.yml index 8fdf94ef19f..083b8546b02 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,16 +2,29 @@ language: rust rust: nightly sudo: false -before_script: +cache: + directories: + - node_modules + +before_install: + - nvm install 0.12 + - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH + - "npm config set spin false" + - "npm install -g npm@^2" + +install: - pip install 'travis-cargo<0.2' --user && export PATH=$HOME/.local/bin:$PATH - - psql -c 'create database cargo_registry_test;' -U postgres - - npm install -g phantomjs ember-cli + - npm install -g bower - npm install + - bower install + +before_script: + - psql -c 'create database cargo_registry_test;' -U postgres script: - cargo build - cargo test - - ember test + - npm test after_success: - travis-cargo coveralls --no-sudo diff --git a/.watchmanconfig b/.watchmanconfig new file mode 100644 index 00000000000..5e9462c2005 --- /dev/null +++ b/.watchmanconfig @@ -0,0 +1,3 @@ +{ + "ignore_dirs": ["tmp"] +} diff --git a/Brocfile.js b/Brocfile.js deleted file mode 100644 index d927ec0cf05..00000000000 --- a/Brocfile.js +++ /dev/null @@ -1,23 +0,0 @@ -/* global require, module */ - -var EmberApp = require('ember-cli/lib/broccoli/ember-app'); - -var app = new EmberApp(); - -// Use `app.import` to add additional libraries to the generated -// output files. -// -// If you need to use different assets in different -// environments, specify an object as the first parameter. That -// object's keys should be the environment name and the values -// should be the asset to use in that environment. -// -// If the library that you are including contains AMD or ES6 -// modules that you would like to import into your application -// please specify an object with the list of modules as keys -// along with the exports of each module as its value. - -app.import('bower_components/moment/moment.js'); -app.import('bower_components/normalize-css/normalize.css'); - -module.exports = app.toTree(); diff --git a/README.md b/README.md index 4abc1d408ed..8f088357dfa 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ which enables tweaking the UI of the site without actually having the server running locally. To get up and running with just the UI, run: ``` -npm run startui +npm run start:ui ``` This will give you a local server to browse while using the staging backend diff --git a/app/adapters/dependency.js b/app/adapters/dependency.js index 0104c2899e5..7829d9c818d 100644 --- a/app/adapters/dependency.js +++ b/app/adapters/dependency.js @@ -1,9 +1,9 @@ import ApplicationAdapter from './application'; export default ApplicationAdapter.extend({ - findQuery: function(store, type, query) { + query(store, type, query) { if (!query.reverse) { - return this._super(store, type, query); + return this._super(...arguments); } delete query.reverse; var crate = query.crate; diff --git a/app/app.js b/app/app.js index 78679ead567..d1c7c209956 100644 --- a/app/app.js +++ b/app/app.js @@ -3,15 +3,19 @@ import Resolver from 'ember/resolver'; import loadInitializers from 'ember/load-initializers'; import config from './config/environment'; +var App; + Ember.MODEL_FACTORY_INJECTIONS = true; -var App = Ember.Application.extend({ +App = Ember.Application.extend({ modulePrefix: config.modulePrefix, podModulePrefix: config.podModulePrefix, Resolver: Resolver }); loadInitializers(App, config.modulePrefix); -Ember.$.ajaxSetup({cache: false}); +Ember.$.ajaxSetup({ + cache: false +}); export default App; diff --git a/app/components/crate-row.js b/app/components/crate-row.js new file mode 100644 index 00000000000..33ee68e5cc6 --- /dev/null +++ b/app/components/crate-row.js @@ -0,0 +1,5 @@ +import Ember from 'ember'; + +export default Ember.Component.extend({ + classNames: ['crate', 'row'] +}); diff --git a/app/components/user-avatar.js b/app/components/user-avatar.js index 704c4c973e7..39efdc25093 100644 --- a/app/components/user-avatar.js +++ b/app/components/user-avatar.js @@ -1,12 +1,14 @@ import Ember from 'ember'; +const { computed } = Ember; + export default Ember.Component.extend({ size: 'small', user: null, attributeBindings: ['src', 'width', 'height'], tagName: 'img', - width: function() { + width: computed('size', function() { if (this.get('size') === 'small') { return 22; } else if (this.get('size') === 'medium-small') { @@ -14,13 +16,11 @@ export default Ember.Component.extend({ } else { return 85; // medium } - }.property('size'), + }), - height: function() { - return this.get('width'); - }.property('width'), + height: computed.readOnly('width'), - src: function() { + src: computed('size', 'user', function() { return this.get('user.avatar') + '&s=' + this.get('width'); - }.property('size', 'user'), + }) }); diff --git a/app/components/user-link.js b/app/components/user-link.js index dbef9d4b70d..5cff012f3d4 100644 --- a/app/components/user-link.js +++ b/app/components/user-link.js @@ -1,17 +1,16 @@ import Ember from 'ember'; +const { computed } = Ember; + export default Ember.Component.extend({ user: null, attributeBindings: ['title', 'href'], tagName: 'a', - title: function() { - return this.get('user.login'); - }.property('user'), - - 'href': function() { + title: computed.readOnly('user.login'), + href: computed('user', function() { // TODO replace this with a link to a native crates.io profile // page when they exist. return this.get('user.url'); - }.property('user'), + }) }); diff --git a/app/controllers/application.js b/app/controllers/application.js index 31d2780b858..8e407728dec 100644 --- a/app/controllers/application.js +++ b/app/controllers/application.js @@ -1,30 +1,33 @@ import Ember from 'ember'; +const { observer } = Ember; + export default Ember.Controller.extend({ - needs: ['search'], + searchController: Ember.inject.controller('search'), flashError: null, nextFlashError: null, showUserOptions: false, - search: Ember.computed.oneWay('controllers.search.q'), + search: Ember.computed.oneWay('searchController.q'), - stepFlash: function() { + stepFlash() { this.set('flashError', this.get('nextFlashError')); this.set('nextFlashError', null); }, - aboutToTransition: function() { + aboutToTransition() { Ember.$(document).trigger('mousedown'); }, - resetDropdownOption: function(controller, option) { + // don't use this from other controllers.. + resetDropdownOption(controller, option) { controller.set(option, !controller.get(option)); if (controller.get(option)) { - Ember.$(document).on('mousedown.useroptions', function(e) { + Ember.$(document).on('mousedown.useroptions', (e) => { if (Ember.$(e.target).prop('tagName') === 'INPUT') { return; } - Ember.run.later(function() { + Ember.run.later(() => { controller.set(option, false); }, 150); Ember.$(document).off('mousedown.useroptions'); @@ -32,18 +35,26 @@ export default Ember.Controller.extend({ } }, - currentPathChanged: function () { + _scrollToTop() { window.scrollTo(0, 0); - }.observes('currentPath'), + }, + + // TODO: remove observer & DOM mutation in controller.. + currentPathChanged: observer('currentPath', function () { + Ember.run.scheduleOnce('afterRender', this, this._scrollToTop); + }), actions: { - search: function(query) { + search(q) { this.transitionToRoute('search', { - queryParams: {q: query, page: 1} + queryParams: { + q, + page: 1 + } }); }, - toggleUserOptions: function() { + toggleUserOptions() { this.resetDropdownOption(this, 'showUserOptions'); }, }, diff --git a/app/controllers/catch-all.js b/app/controllers/catch-all.js index efe33d26a3a..1a37236b627 100644 --- a/app/controllers/catch-all.js +++ b/app/controllers/catch-all.js @@ -2,7 +2,7 @@ import Ember from 'ember'; export default Ember.Controller.extend({ actions: { - search: function(query) { + search(query) { return this.transitionToRoute('search', {queryParams: {q: query}}); }, }, diff --git a/app/controllers/crate/index.js b/app/controllers/crate/index.js index 0b58d77f09e..2963c2cfee4 100644 --- a/app/controllers/crate/index.js +++ b/app/controllers/crate/index.js @@ -1,11 +1,13 @@ import Ember from 'ember'; import DS from 'ember-data'; import ajax from 'ic-ajax'; +import moment from 'moment'; -var NUM_VERSIONS = 5; +const NUM_VERSIONS = 5; +const { computed } = Ember; -export default Ember.ObjectController.extend({ - needs: ['application'], +export default Ember.Controller.extend({ + applicationController: Ember.inject.controller('application'), isDownloading: false, fetchingDownloads: true, @@ -16,111 +18,97 @@ export default Ember.ObjectController.extend({ requestedVersion: null, keywords: [], - sortedVersions: function() { + sortedVersions: computed('model.versions.[]', function() { return this.get("model.versions"); - }.property('model.versions.[]'), + }), - smallSortedVersions: function() { + smallSortedVersions: computed('sortedVersions', function() { return this.get('sortedVersions').slice(0, NUM_VERSIONS); - }.property('sortedVersions'), - - hasMoreVersions: function() { - return this.get("sortedVersions.length") > NUM_VERSIONS; - }.property('sortedVersions'), - - anyLinks: function() { - return this.get('homepage') || - this.get('wiki') || - this.get('mailing_list') || - this.get('documentation') || - this.get('repository'); - }.property('homepage', 'wiki', 'mailing_list', 'documentation', 'repository'), - - versionsCount: function() { - return this.get('versions.length'); - }.property('versions.@each'), - - displayedAuthors: function() { - var self = this; + }), + + hasMoreVersions: computed.gt('sortedVersions.length', NUM_VERSIONS), + + anyLinks: computed.or('model.homepage', + 'model.wiki', + 'model.mailing_list', + 'model.documentation', + 'model.repository'), + + displayedAuthors: computed('currentVersion.authors.[]', function() { if (!this.get('currentVersion')) { return []; } + return DS.PromiseArray.create({ - promise: this.get('currentVersion.authors').then(function(authors) { - var ret = []; - authors.forEach(function(author) { - ret.push(author); - }); - var others = self.store.metadataFor('user'); + promise: this.get('currentVersion.authors').then((authors) => { + var ret = authors.slice(); + var others = this.store.metadataFor('user'); for (var i = 0; i < others.names.length; i++) { ret.push({name: others.names[i]}); } return ret; - }), + }) }); - }.property('currentVersion.authors.@each'), + }), - anyKeywords: function() { - return this.get('keywords.length') > 0; - }.property('keywords'), + anyKeywords: computed.gt('keywords.length', 0), - currentDependencies: function() { + currentDependencies: computed('currentVersion.dependencies', function() { var deps = this.get('currentVersion.dependencies'); + if (deps === null) { return []; } + return DS.PromiseArray.create({ - promise: deps.then(function(deps) { - var non_dev = deps.filter(function(dep) { - return dep.get('kind') !== 'dev'; - }); + promise: deps.then((deps) => { + var non_dev = deps.filter((dep) => dep.get('kind') !== 'dev' ); var map = {}; var ret = []; - non_dev.forEach(function(dep) { + + non_dev.forEach((dep) => { if (!(dep.get('crate_id') in map)) { map[dep.get('crate_id')] = 1; ret.push(dep); } }); + return ret; - }), + }) }); - }.property('currentVersion.dependencies'), + }), - currentDevDependencies: function() { + currentDevDependencies: computed('currentVersion.dependencies', function() { var deps = this.get('currentVersion.dependencies'); if (deps === null) { return []; } return DS.PromiseArray.create({ - promise: deps.then(function(deps) { - return deps.filter(function(dep) { - return dep.get('kind') === 'dev'; - }); + promise: deps.then((deps) => { + return deps.filterBy('kind', 'dev'); }), }); - }.property('currentVersion.dependencies'), + }), actions: { - download: function(version) { + download(version) { this.set('isDownloading', true); - var self = this; + var crate_downloads = this.get('model').get('downloads'); var ver_downloads = version.get('downloads'); + return ajax({ url: version.get('dl_path'), dataType: 'json', - }).then(function(data) { - self.get('model').set('downloads', crate_downloads + 1); + }).then((data) => { + this.get('model').set('downloads', crate_downloads + 1); version.set('downloads', ver_downloads + 1); Ember.$('#download-frame').attr('src', data.url); - }).finally(function() { - self.set('isDownloading', false); - }); + }).finally(() => this.set('isDownloading', false) ); }, - toggleVersions: function() { - this.get('controllers.application') + toggleVersions() { + this.get('applicationController') .resetDropdownOption(this, 'showAllVersions'); }, - toggleFollow: function() { + toggleFollow() { this.set('fetchingFollowing', true); this.set('following', !this.get('following')); var url = '/api/v1/crates/' + this.get('model.name') + '/follow'; @@ -130,20 +118,22 @@ export default Ember.ObjectController.extend({ } else { method = 'delete'; } - var self = this; - ajax({ method: method, url: url }).finally(function() { - self.set('fetchingFollowing', false); - }); + + ajax({ + method, + url + }).finally(() => this.set('fetchingFollowing', false)); }, - renderChart: function(downloads, extra) { + renderChart(downloads, extra) { var dates = {}; var versions = []; for (var i = 0; i < 90; i++) { var now = moment().subtract(i, 'days'); dates[now.format('MMM D')] = {date: now, cnt: {}}; } - downloads.forEach(function(d) { + + downloads.forEach((d) => { var version_id = d.get('version.id'); var key = moment(d.get('date')).utc().format('MMM D'); if (dates[key]) { @@ -151,7 +141,8 @@ export default Ember.ObjectController.extend({ dates[key].cnt[version_id] = prev + d.get('downloads'); } }); - extra.forEach(function(d) { + + extra.forEach((d) => { var key = moment(d.date).utc().format('MMM D'); if (dates[key]) { var prev = dates[key].cnt[null] || 0; @@ -166,15 +157,21 @@ export default Ember.ObjectController.extend({ } else { var tmp = this.get('smallSortedVersions'); for (i = 0; i < tmp.length; i++) { - versions.push({id: tmp[i].get('id'), num: tmp[i].get('num')}); + versions.push({ + id: tmp[i].get('id'), + num: tmp[i].get('num') + }); } } if (extra.length > 0) { - versions.push({ id: null, num: 'Other' }); + versions.push({ + id: null, + num: 'Other' + }); } var headers = ['Date']; - versions.sort(function(b) { return b.num; }).reverse(); + versions.sort((b) => b.num).reverse(); for (i = 0; i < versions.length; i++) { headers.push(versions[i].num); } @@ -187,16 +184,17 @@ export default Ember.ObjectController.extend({ data.push(row); } - var drawChart = function() { + // TODO: move this to a component + function drawChart() { if (!window.google || !window.googleChartsLoaded) { Ember.$('.graph').hide(); return; } else { Ember.$('.graph').show(); } - var myData = google.visualization.arrayToDataTable(data); + var myData = window.google.visualization.arrayToDataTable(data); - var fmt = new google.visualization.DateFormat({ + var fmt = new window.google.visualization.DateFormat({ pattern: 'LLL d, yyyy', }); fmt.format(myData, 0); @@ -204,7 +202,7 @@ export default Ember.ObjectController.extend({ if (!el) { return; } - var chart = new google.visualization.AreaChart(el); + var chart = new window.google.visualization.AreaChart(el); chart.draw(myData, { chartArea: {'left': 85, 'width': '77%', 'height': '80%'}, hAxis: { @@ -217,7 +215,7 @@ export default Ember.ObjectController.extend({ isStacked: true, focusTarget: 'category', }); - }; + } Ember.run.scheduleOnce('afterRender', this, drawChart); Ember.$(window).off('resize.chart'); @@ -227,4 +225,3 @@ export default Ember.ObjectController.extend({ }, }, }); - diff --git a/app/controllers/crate/reverse-dependencies.js b/app/controllers/crate/reverse-dependencies.js index f7305544938..514294acf25 100644 --- a/app/controllers/crate/reverse-dependencies.js +++ b/app/controllers/crate/reverse-dependencies.js @@ -1,13 +1,14 @@ import Ember from 'ember'; import PaginationMixin from 'cargo/mixins/pagination'; -export default Ember.ArrayController.extend(PaginationMixin, { +const { computed } = Ember; + +export default Ember.Controller.extend(PaginationMixin, { queryParams: ['page', 'per_page'], page: '1', per_page: 10, - totalItems: function() { + totalItems: computed('model', function() { return this.store.metadataFor('dependency').total; - }.property('model'), + }) }); - diff --git a/app/controllers/crate/versions.js b/app/controllers/crate/versions.js index bd8bd406cd3..55ff9aa587a 100644 --- a/app/controllers/crate/versions.js +++ b/app/controllers/crate/versions.js @@ -1,4 +1,4 @@ import Ember from 'ember'; -export default Ember.ObjectController.extend({ +export default Ember.Controller.extend({ }); diff --git a/app/controllers/crates.js b/app/controllers/crates.js index 5e8cc409387..835ef875af9 100644 --- a/app/controllers/crates.js +++ b/app/controllers/crates.js @@ -1,8 +1,10 @@ import Ember from 'ember'; import PaginationMixin from 'cargo/mixins/pagination'; -export default Ember.ArrayController.extend(PaginationMixin, { - needs: ['application'], +const { computed } = Ember; + +export default Ember.Controller.extend(PaginationMixin, { + applicationController: Ember.inject.controller('application'), queryParams: ['letter', 'page', 'per_page', 'sort'], letter: null, page: '1', @@ -11,24 +13,22 @@ export default Ember.ArrayController.extend(PaginationMixin, { alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(""), showSortBy: false, - totalItems: function() { + totalItems: computed('model', function() { return this.store.metadataFor('crate').total; - }.property('model'), + }), - currentSortBy: function() { + currentSortBy: computed('sort', function() { if (this.get('sort') === 'downloads') { return 'Downloads'; } else { return 'Alphabetical'; } - }.property('sort'), + }), actions: { - toggleShowSortBy: function() { + toggleShowSortBy() { var opt = 'showSortBy'; - this.get('controllers.application').resetDropdownOption(this, opt); - + this.get('applicationController').resetDropdownOption(this, opt); }, }, }); - diff --git a/app/controllers/dashboard.js b/app/controllers/dashboard.js index cd34ebb5ea8..ee0d0445b57 100644 --- a/app/controllers/dashboard.js +++ b/app/controllers/dashboard.js @@ -1,45 +1,54 @@ import Ember from 'ember'; import ajax from 'ic-ajax'; -var TO_SHOW = 5; +const TO_SHOW = 5; +const { computed } = Ember; -export default Ember.ObjectController.extend({ - fetchingFeed: true, - loadingMore: false, - hasMore: false, - myCrates: [], - myFollowing: [], - myFeed: [], +export default Ember.Controller.extend({ + init() { + this._super(...arguments); - visibleCrates: function() { + this.fetchingFeed = true; + this.loadingMore = false; + this.hasMore = false; + this.myCrates = []; + this.myFollowing = []; + this.myFeed = []; + }, + + visibleCrates: computed('myCreates', function() { return this.get('myCrates').slice(0, TO_SHOW); - }.property('myCrates'), + }), - visibleFollowing: function() { + visibleFollowing: computed('myFollowing', function() { return this.get('myFollowing').slice(0, TO_SHOW); - }.property('myFollowing'), + }), - hasMoreCrates: function() { + hasMoreCrates: computed('myCreates', function() { return this.get('myCrates.length') > TO_SHOW; - }.property('myCrates'), + }), - hasMoreFollowing: function() { + hasMoreFollowing: computed('myFollowing', function() { return this.get('myFollowing.length') > TO_SHOW; - }.property('myFollowing'), + }), actions: { - loadMore: function() { - var self = this; + loadMore() { this.set('loadingMore', true); var page = (this.get('myFeed').length / 10) + 1; - ajax('/me/updates?page=' + page).then(function(data) { - self.store.pushMany('crate', data.crates); - var versions = self.store.pushMany('version', data.versions); - self.get('myFeed').pushObjects(versions); - self.set('hasMore', data.meta.more); - }).finally(function() { - self.set('loadingMore', false); + + ajax('/me/updates?page=' + page).then((data) => { + data.crates.forEach(crate => + this.store.push(this.store.normalize('crate', crate))); + + var versions = data.versions.map(version => + this.store.push(this.store.normalize('version', version))); + + this.get('myFeed').pushObjects(versions); + this.set('hasMore', data.meta.more); + }).finally(() => { + this.set('loadingMore', false); }); - }, - }, + } + } }); diff --git a/app/controllers/index.js b/app/controllers/index.js index a451f961d79..562af000e20 100644 --- a/app/controllers/index.js +++ b/app/controllers/index.js @@ -1,7 +1,8 @@ import Ember from 'ember'; +const { computed } = Ember; -export default Ember.ObjectController.extend({ - currentPlatform: function() { +export default Ember.Controller.extend({ + currentPlatform: computed(function() { var os = null; if (navigator.platform === "Linux x86_64") { @@ -22,12 +23,11 @@ export default Ember.ObjectController.extend({ } return os; - }.property(), + }), - downloadUrl: function() { + downloadUrl: computed('currentPlatform', function() { var plat = this.get('currentPlatform'); if (plat == null) { return null; } - return "https://static.rust-lang.org/cargo-dist/cargo-nightly-" + - plat + ".tar.gz"; - }.property('currentPlatform'), + return `https://static.rust-lang.org/cargo-dist/cargo-nightly-${plat}.tar.gz`; + }) }); diff --git a/app/controllers/install.js b/app/controllers/install.js index f8687ac9a21..169d342b735 100644 --- a/app/controllers/install.js +++ b/app/controllers/install.js @@ -1,28 +1,14 @@ import Ember from 'ember'; -export default Ember.Controller.extend({ - - linux64: function() { - return this.link('x86_64-unknown-linux-gnu'); - }.property(), - linux32: function() { - return this.link('i686-unknown-linux-gnu'); - }.property(), - mac64: function() { - return this.link('x86_64-apple-darwin'); - }.property(), - mac32: function() { - return this.link('i686-apple-darwin'); - }.property(), - win64: function() { - return this.link('x86_64-pc-windows-gnu'); - }.property(), - win32: function() { - return this.link('i686-pc-windows-gnu'); - }.property(), +function link(target) { + return `https://static.rust-lang.org/cargo-dist/cargo-nightly-${target}.tar.gz`; +} - link: function(target) { - return 'https://static.rust-lang.org/cargo-dist/cargo-nightly-' + - target + '.tar.gz'; - }, +export default Ember.Controller.extend({ + linux64: link('x86_64-unknown-linux-gnu'), + linux32: link('i686-unknown-linux-gnu'), + mac64: link('x86_64-apple-darwin'), + mac32: link('i686-apple-darwin'), + win64: link('x86_64-pc-windows-gnu'), + win32: link('i686-pc-windows-gnu'), }); diff --git a/app/controllers/keyword/index.js b/app/controllers/keyword/index.js index 2a0a45701e4..f7e74772027 100644 --- a/app/controllers/keyword/index.js +++ b/app/controllers/keyword/index.js @@ -1,31 +1,32 @@ import Ember from 'ember'; import PaginationMixin from 'cargo/mixins/pagination'; -export default Ember.ArrayController.extend(PaginationMixin, { - needs: ['application'], +const { computed } = Ember; + +export default Ember.Controller.extend(PaginationMixin, { + applicationController: Ember.inject.controller('application'), queryParams: ['page', 'per_page', 'sort'], page: '1', per_page: 10, sort: 'alpha', showSortBy: false, - totalItems: function() { + totalItems: computed('model', function() { return this.store.metadataFor('crate').total; - }.property('model'), + }), - currentSortBy: function() { + currentSortBy: computed('sort', function() { if (this.get('sort') === 'downloads') { return 'Downloads'; } else { return 'Alphabetical'; } - }.property('sort'), + }), actions: { - toggleShowSortBy: function() { + toggleShowSortBy() { var opt = 'showSortBy'; - this.get('controllers.application').resetDropdownOption(this, opt); + this.get('applicationController').resetDropdownOption(this, opt); }, }, }); - diff --git a/app/controllers/keywords.js b/app/controllers/keywords.js index ce37043a2b0..d8133f50db8 100644 --- a/app/controllers/keywords.js +++ b/app/controllers/keywords.js @@ -1,31 +1,32 @@ import Ember from 'ember'; import PaginationMixin from 'cargo/mixins/pagination'; -export default Ember.ArrayController.extend(PaginationMixin, { - needs: ['application'], +const { computed } = Ember; + +export default Ember.Controller.extend(PaginationMixin, { + applicationController: Ember.inject.controller('application'), queryParams: ['page', 'per_page', 'sort'], page: '1', per_page: 10, sort: 'crates', showSortBy: false, - totalItems: function() { + totalItems: computed('model', function() { return this.store.metadataFor('keyword').total; - }.property('model'), + }), - currentSortBy: function() { + currentSortBy: computed('sort', function() { if (this.get('sort') === 'crates') { return '# Crates'; } else { return 'Alphabetical'; } - }.property('sort'), + }), actions: { - toggleShowSortBy: function() { + toggleShowSortBy() { var opt = 'showSortBy'; - this.get('controllers.application').resetDropdownOption(this, opt); - + this.get('applicationController').resetDropdownOption(this, opt); }, }, }); diff --git a/app/controllers/me/crates.js b/app/controllers/me/crates.js index e9ac9c78e52..e5fb19b8ebd 100644 --- a/app/controllers/me/crates.js +++ b/app/controllers/me/crates.js @@ -1,35 +1,34 @@ import Ember from 'ember'; import PaginationMixin from 'cargo/mixins/pagination'; +const { computed } = Ember; // TODO: reduce duplicatoin with controllers/crates -export default Ember.ArrayController.extend(PaginationMixin, { - needs: ['application'], +export default Ember.Controller.extend(PaginationMixin, { + applicationController: Ember.inject.controller('application'), queryParams: ['page', 'per_page', 'sort'], page: '1', per_page: 10, sort: 'alpha', showSortBy: false, - totalItems: function() { + totalItems: computed('model', function() { return this.store.metadataFor('crate').total; - }.property('model'), + }), - currentSortBy: function() { + currentSortBy: computed('sort', function() { if (this.get('sort') === 'downloads') { return 'Downloads'; } else { return 'Alphabetical'; } - }.property('sort'), + }), actions: { - toggleShowSortBy: function() { + toggleShowSortBy() { var opt = 'showSortBy'; - this.get('controllers.application').resetDropdownOption(this, opt); + this.get('applicationController').resetDropdownOption(this, opt); }, }, }); - - diff --git a/app/controllers/me/following.js b/app/controllers/me/following.js index ffec92152b8..347d25305eb 100644 --- a/app/controllers/me/following.js +++ b/app/controllers/me/following.js @@ -1,35 +1,34 @@ import Ember from 'ember'; import PaginationMixin from 'cargo/mixins/pagination'; +const { computed } = Ember; // TODO: reduce duplicatoin with controllers/me/crates -export default Ember.ArrayController.extend(PaginationMixin, { - needs: ['application'], +export default Ember.Controller.extend(PaginationMixin, { + applicationController: Ember.inject.controller('application'), queryParams: ['page', 'per_page', 'sort'], page: '1', per_page: 10, sort: 'alpha', showSortBy: false, - totalItems: function() { + totalItems: computed('model', function() { return this.store.metadataFor('crate').total; - }.property('model'), + }), - currentSortBy: function() { + currentSortBy: computed('sort', function() { if (this.get('sort') === 'downloads') { return 'Downloads'; } else { return 'Alphabetical'; } - }.property('sort'), + }), actions: { - toggleShowSortBy: function() { + toggleShowSortBy() { var opt = 'showSortBy'; - this.get('controllers.application').resetDropdownOption(this, opt); + this.get('applicationController').resetDropdownOption(this, opt); }, }, }); - - diff --git a/app/controllers/me/index.js b/app/controllers/me/index.js index 110edc8a221..30d14b8d6e2 100644 --- a/app/controllers/me/index.js +++ b/app/controllers/me/index.js @@ -1,30 +1,32 @@ import Ember from 'ember'; import ajax from 'ic-ajax'; -export default Ember.ObjectController.extend({ +export default Ember.Controller.extend({ isResetting: false, actions: { - resetToken: function() { + resetToken() { this.set('isResetting', true); - var self = this; + ajax({ dataType: "json", url: '/me/reset_token', method: 'put', - }).then(function(d) { - self.get('model').set('api_token', d.api_token); - }).catch(function(reason) { + }).then((d) => { + this.get('model').set('api_token', d.api_token); + }).catch((reason) => { var msg; if (reason.status === 403) { msg = "A login is required to perform this action"; } else { msg = "An unknown error occurred"; } - self.controllerFor('application').set('nextFlashError', msg); - self.transitionToRoute('index'); - }).finally(function() { - self.set('isResetting', false); + this.controllerFor('application').set('nextFlashError', msg); + // TODO: this should be an action, the route state machine + // should recieve signals not external transitions + this.transitionToRoute('index'); + }).finally(() => { + this.set('isResetting', false); }); } } diff --git a/app/controllers/search.js b/app/controllers/search.js index 059813ab4c0..2e9770c59cc 100644 --- a/app/controllers/search.js +++ b/app/controllers/search.js @@ -1,17 +1,19 @@ import Ember from 'ember'; import PaginationMixin from 'cargo/mixins/pagination'; -export default Ember.ArrayController.extend(PaginationMixin, { +const { computed } = Ember; + +export default Ember.Controller.extend(PaginationMixin, { queryParams: ['q', 'page', 'per_page'], q: null, page: '1', per_page: 10, - name: function() { + name: computed('model', function() { return this.get("q") + " - Cargo search"; - }.property('model'), + }), - totalItems: function() { + totalItems: computed('model', function() { return this.store.metadataFor('crate').total; - }.property('model'), + }) }); diff --git a/app/helpers/date-long.js b/app/helpers/date-long.js index 4206102fdb2..b0e957cd0b2 100644 --- a/app/helpers/date-long.js +++ b/app/helpers/date-long.js @@ -1,11 +1,8 @@ import Ember from 'ember'; +import moment from 'moment'; -function dateLong(value) { +export function dateLong(value) { return moment(value).format('LL'); } -export { - dateLong -}; - -export default Ember.Handlebars.makeBoundHelper(dateLong); +export default Ember.Helper.helper(params => dateLong(params[0])); diff --git a/app/helpers/date-small.js b/app/helpers/date-small.js index 7f2c28fc011..7fe11bbdeb7 100644 --- a/app/helpers/date-small.js +++ b/app/helpers/date-small.js @@ -1,11 +1,8 @@ import Ember from 'ember'; +import moment from 'moment'; -function dateSmall(value) { +export function dateSmall(value) { return moment(value).format('ll'); } -export { - dateSmall -}; - -export default Ember.Handlebars.makeBoundHelper(dateSmall); +export default Ember.Helper.helper(params => dateSmall(params[0])); diff --git a/app/helpers/format-email.js b/app/helpers/format-email.js index 41754ef7397..a958b10f187 100644 --- a/app/helpers/format-email.js +++ b/app/helpers/format-email.js @@ -2,7 +2,7 @@ import Ember from "ember"; var escape = Ember.Handlebars.Utils.escapeExpression; -function formatEmail(email) { +export function formatEmail(email) { var formatted = email.match(/^(.*?)\s*(?:<(.*)>)?$/); var ret = ""; @@ -15,9 +15,4 @@ function formatEmail(email) { return ret.htmlSafe(); } - -export { - formatEmail -}; - -export default Ember.Handlebars.makeBoundHelper(formatEmail); +export default Ember.Helper.helper(params => formatEmail(params[0])); diff --git a/app/helpers/format-num.js b/app/helpers/format-num.js index 1c1c6301350..8ef3825d455 100644 --- a/app/helpers/format-num.js +++ b/app/helpers/format-num.js @@ -1,6 +1,6 @@ import Ember from 'ember'; -function formatNum(value) { +export function formatNum(value) { if (value === 0) { return "0"; } var ret = ""; @@ -17,8 +17,4 @@ function formatNum(value) { return ret; } -export { - formatNum -}; - -export default Ember.Handlebars.makeBoundHelper(formatNum); +export default Ember.Helper.helper(params => formatNum(params[0])); diff --git a/app/helpers/format-req.js b/app/helpers/format-req.js index a15e6a3c21c..187589c43f2 100644 --- a/app/helpers/format-req.js +++ b/app/helpers/format-req.js @@ -1,7 +1,6 @@ import Ember from "ember"; -function formatReq(req) { +export default Ember.Helper.helper(function(params) { + let req = params[0]; return req === "*" ? "" : req; -} - -export default Ember.Handlebars.makeBoundHelper(formatReq); +}); diff --git a/app/helpers/from-now.js b/app/helpers/from-now.js index 5363042ae93..cc9c52d507c 100644 --- a/app/helpers/from-now.js +++ b/app/helpers/from-now.js @@ -1,11 +1,7 @@ import Ember from 'ember'; +import moment from 'moment'; -function fromNow(value) { +export default Ember.Helper.helper(function(params) { + let value = params[0]; return moment(value).fromNow(); -} - -export { - fromNow -}; - -export default Ember.Handlebars.makeBoundHelper(fromNow); +}); diff --git a/app/helpers/truncate-text.js b/app/helpers/truncate-text.js index 98fa6de8907..a57efaf3cdd 100644 --- a/app/helpers/truncate-text.js +++ b/app/helpers/truncate-text.js @@ -1,15 +1,10 @@ import Ember from 'ember'; -function truncateText(value) { +export default Ember.Helper.helper(function(params) { + let value = params[0]; if (!value) { return value; } if (value.length > 200) { return value.slice(0, 200) + ' ...'; } return value; -} - -export { - truncateText -}; - -export default Ember.Handlebars.makeBoundHelper(truncateText); +}); diff --git a/app/index.html b/app/index.html index bbd93a99862..129f19b6003 100644 --- a/app/index.html +++ b/app/index.html @@ -7,7 +7,7 @@ - {{BASE_TAG}} + {{content-for 'head'}} + - + + {{content-for 'body-footer'}} diff --git a/app/initializers/document-title-config.js b/app/initializers/document-title-config.js index 8ed43b32f44..cd9be0a602c 100644 --- a/app/initializers/document-title-config.js +++ b/app/initializers/document-title-config.js @@ -7,5 +7,5 @@ DocumentTitle.reopen({ export default { name: "document-title-config", - initialize: function () {} -}; \ No newline at end of file + initialize() {} +}; diff --git a/app/initializers/google.js b/app/initializers/google.js index bb10ab585f8..8407f7814d6 100644 --- a/app/initializers/google.js +++ b/app/initializers/google.js @@ -2,9 +2,9 @@ import Ember from 'ember'; export var initialize = function() { Ember.$.getScript('https://www.google.com/jsapi', function() { - google.load('visualization', '1.0', { - 'packages': ['corechart'], - 'callback': function() { + window.google.load('visualization', '1.0', { + packages: ['corechart'], + callback() { window.googleChartsLoaded = true; Ember.$(document).trigger('googleChartsLoaded'); } diff --git a/app/initializers/session.js b/app/initializers/session.js index 9b4bf13053a..9159077cf26 100644 --- a/app/initializers/session.js +++ b/app/initializers/session.js @@ -1,7 +1,7 @@ export default { name: 'app.session', - initialize: function(container, application) { + initialize(container, application) { application.inject('controller', 'session', 'service:session'); application.inject('route', 'session', 'service:session'); } diff --git a/app/mixins/authenticated-route.js b/app/mixins/authenticated-route.js index 4a10a45d30f..a1de42a7c97 100644 --- a/app/mixins/authenticated-route.js +++ b/app/mixins/authenticated-route.js @@ -1,7 +1,7 @@ import Ember from 'ember'; export default Ember.Mixin.create({ - beforeModel: function(transition) { + beforeModel(transition) { var user = this.session.get('currentUser'); if (user !== null) { return; } diff --git a/app/mixins/google-pageview.js b/app/mixins/google-pageview.js index 63eea145c3b..280ab2b3672 100644 --- a/app/mixins/google-pageview.js +++ b/app/mixins/google-pageview.js @@ -1,12 +1,12 @@ import Ember from 'ember'; export default Ember.Mixin.create({ - notifyGoogleAnalytics: function() { + notifyGoogleAnalytics: Ember.on('didTransition', function() { if (!window.ga) { return; } return window.ga('send', 'pageview', { - 'page': this.get('url'), - 'title': this.get('url') + page: this.get('url'), + title: this.get('url') }); - }.on('didTransition') + }) }); diff --git a/app/mixins/pagination.js b/app/mixins/pagination.js index a1405f16c63..948758b75ca 100644 --- a/app/mixins/pagination.js +++ b/app/mixins/pagination.js @@ -1,11 +1,12 @@ import Ember from 'ember'; -var VIEWABLE_PAGES = 9; +const VIEWABLE_PAGES = 9; +const { computed } = Ember; export default Ember.Mixin.create({ // Gives page numbers to the surrounding 9 pages. - pages: function() { + pages: computed('currentPage', 'availablePages', function() { var pages = []; var currentPage = this.get('currentPage'); var availablePages = this.get('availablePages'); @@ -28,23 +29,29 @@ export default Ember.Mixin.create({ pages.push(i + 1); } return pages; - }.property('currentPage', 'availablePages'), + }), - currentPage: function() { + currentPage: computed('selectedPage', function() { return parseInt(this.get('selectedPage'), 10) || 1; - }.property('selectedPage'), + }), - currentPageStart: function() { + currentPageStart: computed('currentPage', + 'itemsPerPage', + 'totalItems', + function() { if (this.get('totalItems') === 0) { return 0; } return (this.get('currentPage') - 1) * this.get('itemsPerPage') + 1; - }.property('currentPage', 'itemsPerPage', 'totalItems'), + }), - currentPageEnd: function() { + currentPageEnd: computed('currentPage', + 'itemsPerPage', + 'totalItems', + function() { return Math.min(this.get('currentPage') * this.get('itemsPerPage'), this.get('totalItems')); - }.property('currentPage', 'itemsPerPage', 'totalItems'), + }), - nextPage: function() { + nextPage: computed('currentPage', 'availablePages', function() { var nextPage = this.get('currentPage') + 1; var availablePages = this.get('availablePages'); if (nextPage <= availablePages) { @@ -52,27 +59,23 @@ export default Ember.Mixin.create({ } else { return this.get('currentPage'); } - }.property('currentPage', 'availablePages'), + }), - prevPage: function() { + prevPage: computed('currentPage', function() { var prevPage = this.get('currentPage') - 1; if (prevPage > 0) { return prevPage; } else { return this.get('currentPage'); } + }), - }.property('currentPage'), - - availablePages: function() { + availablePages: computed('totalItems', 'itemsPerPage', function() { return Math.ceil((this.get('totalItems') / this.get('itemsPerPage')) || 1); - }.property('totalItems', 'itemsPerPage'), + }), // wire up these ember-style variables to the expected query parameters - itemsPerPage: function() { - return this.get('per_page'); - }.property('per_page'), - - selectedPage: function() { return this.get('page'); }.property('page'), + itemsPerPage: computed.readOnly('per_page'), + selectedPage: computed.readOnly('page') }); diff --git a/app/models/crate.js b/app/models/crate.js index c01271bfa1e..2b77b512180 100644 --- a/app/models/crate.js +++ b/app/models/crate.js @@ -16,9 +16,9 @@ export default DS.Model.extend({ repository: DS.attr('string'), license: DS.attr('string'), - versions: DS.hasMany('versions', {async: true}), - owners: DS.hasMany('users', {async: true}), - version_downloads: DS.hasMany('version-download', {async: true}), - keywords: DS.hasMany('keywords', {async: true}), - reverse_dependencies: DS.hasMany('dependency', {async: true}), + versions: DS.hasMany('versions', { async: true }), + owners: DS.hasMany('users', { async: true }), + version_downloads: DS.hasMany('version-download', { async: true }), + keywords: DS.hasMany('keywords', { async: true }), + reverse_dependencies: DS.hasMany('dependency', { async: true }), }); diff --git a/app/models/dependency.js b/app/models/dependency.js index 17f408b09aa..657a8bf1c8c 100644 --- a/app/models/dependency.js +++ b/app/models/dependency.js @@ -3,8 +3,12 @@ import Ember from 'ember'; Ember.Inflector.inflector.irregular('dependency', 'dependencies'); +const { computed } = Ember; + export default DS.Model.extend({ - version: DS.belongsTo('version'), + version: DS.belongsTo('version', { + async: false + }), crate_id: DS.attr('string'), req: DS.attr('string'), optional: DS.attr('boolean'), @@ -12,7 +16,7 @@ export default DS.Model.extend({ features: DS.attr('string'), kind: DS.attr('string'), - featureList: function() { + featureList: computed('features', function() { return this.get('features').split(','); - }.property('features'), + }) }); diff --git a/app/models/keyword.js b/app/models/keyword.js index 31b8b8f40dc..c57d613dc70 100644 --- a/app/models/keyword.js +++ b/app/models/keyword.js @@ -5,5 +5,5 @@ export default DS.Model.extend({ created_at: DS.attr('date'), crates_cnt: DS.attr('number'), - crates: DS.hasMany('crate', {async: true}), + crates: DS.hasMany('crate', { async: true }) }); diff --git a/app/models/version-download.js b/app/models/version-download.js index 75459c4b8e3..1a738c371f2 100644 --- a/app/models/version-download.js +++ b/app/models/version-download.js @@ -1,7 +1,9 @@ import DS from 'ember-data'; export default DS.Model.extend({ - version: DS.belongsTo('version'), + version: DS.belongsTo('version', { + async: false + }), downloads: DS.attr('number'), date: DS.attr('date'), }); diff --git a/app/models/version.js b/app/models/version.js index 43ddd38ac8a..5171724b133 100644 --- a/app/models/version.js +++ b/app/models/version.js @@ -8,8 +8,10 @@ export default DS.Model.extend({ downloads: DS.attr('number'), yanked: DS.attr('boolean'), - crate: DS.belongsTo('crate'), - authors: DS.hasMany('users', {async: true}), - dependencies: DS.hasMany('dependency', {async: true}), - version_downloads: DS.hasMany('version-download', {async: true}), + crate: DS.belongsTo('crate', { + async: false + }), + authors: DS.hasMany('users', { async: true }), + dependencies: DS.hasMany('dependency', { async: true }), + version_downloads: DS.hasMany('version-download', { async: true }), }); diff --git a/app/router.js b/app/router.js index cf2246f9d2d..e9d47bd8b0d 100644 --- a/app/router.js +++ b/app/router.js @@ -7,12 +7,12 @@ var Router = Ember.Router.extend(googlePageview, { }); Router.map(function() { - this.resource('logout'); - this.resource('login'); - this.resource('github_login'); - this.resource('github_authorize', { path: '/authorize/github' }); - this.resource('crates'); - this.resource('crate', { path: '/crates/*crate_id' }, function() { + this.route('logout', { resetNamespace: true }); + this.route('login', { resetNamespace: true }); + this.route('github_login', { resetNamespace: true }); + this.route('github_authorize', { path: '/authorize/github', resetNamespace: true }); + this.route('crates', { resetNamespace: true }); + this.route('crate', { path: '/crates/*crate_id', resetNamespace: true }, function() { this.route('download'); this.route('versions'); this.route('reverse_dependencies'); @@ -24,8 +24,8 @@ Router.map(function() { this.route('install'); this.route('search'); this.route('dashboard'); - this.resource('keywords'); - this.resource('keyword', { path: '/keywords/*keyword_id' }, function() { + this.route('keywords', { resetNamespace: true }); + this.route('keyword', { path: '/keywords/*keyword_id', resetNamespace: true }, function() { }); this.route('catchAll', { path: '*path' }); }); diff --git a/app/routes/application.js b/app/routes/application.js index 2f9f1fb7d9b..9694e6d6937 100644 --- a/app/routes/application.js +++ b/app/routes/application.js @@ -2,20 +2,18 @@ import Ember from 'ember'; import ajax from 'ic-ajax'; export default Ember.Route.extend({ - title: "Cargo", + title: 'Cargo', - beforeModel: function() { - var self = this; + beforeModel() { if (this.session.get('isLoggedIn') && this.session.get('currentUser') === null) { - ajax('/me').then(function(response) { - var user = self.store.push('user', response.user); + ajax('/me').then((response) => { + var user = this.store.push(this.store.normalize('user', response.user)); user.set('api_token', response.api_token); - self.session.set('currentUser', user); - }).catch(function() { - self.session.logoutUser(); - }).finally(function() { + this.session.set('currentUser', user); + }).catch(() => this.session.logoutUser()). + finally(() => { window.currentUserDetected = true; Ember.$(window).trigger('currentUserDetected'); }); @@ -25,11 +23,11 @@ export default Ember.Route.extend({ }, actions: { - didTransition: function() { + didTransition() { this.controllerFor('application').stepFlash(); }, - willTransition: function() { + willTransition() { this.controllerFor('application').aboutToTransition(); return true; }, diff --git a/app/routes/crate.js b/app/routes/crate.js index d25855600db..d586bb75953 100644 --- a/app/routes/crate.js +++ b/app/routes/crate.js @@ -2,32 +2,38 @@ import Ember from 'ember'; import Version from 'cargo/models/version'; export default Ember.Route.extend({ - model: function(params) { + model(params) { var parts = params.crate_id.split('/'); var crate_id = parts[0]; var version = null; if (parts.length > 1 && parts[1].length > 0) { version = parts[1]; } - var self = this; + + var crate = this.store.find('crate', crate_id).catch((e) => { + if (e.status === 404) { + this.controllerFor('application').set('nextFlashError', 'No crate named: ' + params.crate_id); + return this.replaceWith('index'); + } + }); + return Ember.RSVP.hash({ - crate: this.store.find('crate', crate_id).catch(function(e) { - if (e.status === 404) { - self.controllerFor('application').set('nextFlashError', - 'No crate named: ' + params.crate_id); - return self.replaceWith('index'); - } - }), - version: version, + crate, + version }); }, - serialize: function(model) { + serialize(model) { if (model instanceof Version) { var crate = model.get('crate').get('name'); - return { crate_id: crate + '/' + model.get('num') }; + + return { + crate_id: crate + '/' + model.get('num') + }; } else { - return { crate_id: model.get('id') }; + return { + crate_id: model.get('id') + }; } }, }); diff --git a/app/routes/crate/index.js b/app/routes/crate/index.js index 976458d04e6..9729aba1115 100644 --- a/app/routes/crate/index.js +++ b/app/routes/crate/index.js @@ -4,62 +4,63 @@ import Version from 'cargo/models/version'; import Crate from 'cargo/models/crate'; export default Ember.Route.extend({ - title: Ember.computed.reads("controller.name"), + title: Ember.computed.reads('controller.model.name'), - setupController: function(controller, data) { + setupController(controller, data) { if (data instanceof Crate) { data = {crate: data, version: null}; } else if (data instanceof Version) { data = {crate: data.get('crate'), version: data.get('num')}; } - var self = this; + this._super(controller, data.crate); + controller.set('showAllVersions', false); controller.set('fetchingDownloads', true); controller.set('fetchingFollowing', true); - data.crate.get('keywords').then(function(keywords) { - controller.set('keywords', keywords); - }); + data.crate.get('keywords').then((keywords) => controller.set('keywords', keywords)); if (this.session.get('currentUser')) { var url = '/api/v1/crates/' + data.crate.get('name') + '/following'; - ajax(url).then(function(d) { - controller.set('following', d.following); - }).finally(function() { - controller.set('fetchingFollowing', false); - }); + + ajax(url) + .then((d) => controller.set('following', d.following)) + .finally(() => controller.set('fetchingFollowing', false)); } // Try to find the requested version in the versions we fetch var max = data.crate.get('max_version'); - data.crate.get('versions').then(function(array) { - var hit = array.any(function(version) { + data.crate.get('versions').then((array) => { + var hit = array.any((version) => { return data.version === version.get('num') || (data.version == null && version.get('num') === max); }); + if (!hit) { var msg = "Version `" + data.version + "` does not exist"; - self.controllerFor('application').set('flashError', msg); + this.controllerFor('application').set('flashError', msg); data.version = null; } controller.set('requestedVersion', data.version); - array.forEach(function(version) { + + array.forEach((version) => { if (data.version === version.get('num') || (data.version == null && version.get('num') === max)) { controller.set('currentVersion', version); } }); - }).then(function() { + + }).then(() => { if (controller.get('requestedVersion')) { return controller.get('currentVersion.version_downloads'); } else { return controller.get('model.version_downloads'); } - }).then(function(downloads) { + }).then((downloads) => { var meta = controller.store.metadataFor('version_download'); controller.set('fetchingDownloads', false); controller.send('renderChart', downloads, meta.extra_downloads); }); - }, + } }); diff --git a/app/routes/crate/reverse-dependencies.js b/app/routes/crate/reverse-dependencies.js index 3e4562424ae..2fd09fbc0f2 100644 --- a/app/routes/crate/reverse-dependencies.js +++ b/app/routes/crate/reverse-dependencies.js @@ -10,36 +10,35 @@ export default Ember.Route.extend({ reverse_dependencies: null, params: null, - model: function(params, transition) { + model(params, transition) { this.set('params', params); return this._super(params, transition); }, - afterModel: function(data) { + afterModel(data) { var crate; if (data instanceof Crate) { crate = data; } else { crate = data.crate; } - var self = this; var params = this.get('params'); params.reverse = true; params.crate = crate; - return this.store.find('dependency', params).then(function(deps) { - var controller = self.controllerFor('crate/reverse_dependencies'); + return this.store.query('dependency', params).then((deps) => { + var controller = this.controllerFor('crate/reverse_dependencies'); if (controller) { controller.set('model', deps); } - self.set('reverse_dependencies', deps); - self.set('crate', crate); + this.set('reverse_dependencies', deps); + this.set('crate', crate); }); }, - setupController: function(controller) { + setupController(controller) { this._super(controller, this.get('reverse_dependencies')); controller.set('crate', this.get('crate')); - }, + } }); diff --git a/app/routes/crate/versions.js b/app/routes/crate/versions.js index 5abc330fbb3..2bae33ed60c 100644 --- a/app/routes/crate/versions.js +++ b/app/routes/crate/versions.js @@ -2,7 +2,7 @@ import Ember from 'ember'; import Crate from 'cargo/models/crate'; export default Ember.Route.extend({ - afterModel: function(data) { + afterModel(data) { if (data instanceof Crate) { return data.get('versions'); } else { @@ -10,10 +10,10 @@ export default Ember.Route.extend({ } }, - setupController: function(controller, data) { + setupController(controller, data) { if (data instanceof Crate) { data = {crate: data, version: null}; } this._super(controller, data.crate); - }, + } }); diff --git a/app/routes/crates.js b/app/routes/crates.js index 52153078d99..231bf87cf41 100644 --- a/app/routes/crates.js +++ b/app/routes/crates.js @@ -7,13 +7,13 @@ export default Ember.Route.extend({ sort: { refreshModel: true }, }, - model: function(params) { + model(params) { // The backend throws an error if the letter param is // empty or null. - if(!params.letter) { + if (!params.letter) { delete params.letter; } - return this.store.find('crate', params); + return this.store.query('crate', params); }, }); diff --git a/app/routes/dashboard.js b/app/routes/dashboard.js index 45fda221c00..d259dd1277f 100644 --- a/app/routes/dashboard.js +++ b/app/routes/dashboard.js @@ -4,28 +4,35 @@ import AuthenticatedRoute from 'cargo/mixins/authenticated-route'; export default Ember.Route.extend(AuthenticatedRoute, { data: {}, - setupController: function(controller, model) { + setupController(controller, model) { this._super(controller, model); + controller.set('fetchingFeed', true); controller.set('myCrates', this.get('data.myCrates')); controller.set('myFollowing', this.get('data.myFollowing')); + if (!controller.get('loadingMore')) { controller.set('myFeed', []); controller.send('loadMore'); } }, - model: function() { + model() { return this.session.get('currentUser'); }, - afterModel: function(user) { - var self = this; - return Ember.RSVP.hash({ - myCrates: this.store.find('crate', {user_id: user.get('id')}), - myFollowing: this.store.find('crate', {following: 1}), - }).then(function(hash) { - self.set('data', hash); - }); - }, + afterModel(user) { + let myCrates = this.store.query('crate', { + user_id: user.get('id') + }); + + let myFollowing = this.store.query('crate', { + following: 1 + }); + + return Ember.RSVP.hash({ + myCrates, + myFollowing + }).then((hash) => this.set('data', hash) ); + } }); diff --git a/app/routes/github-authorize.js b/app/routes/github-authorize.js index e73d931d778..c058efc9a8f 100644 --- a/app/routes/github-authorize.js +++ b/app/routes/github-authorize.js @@ -2,18 +2,18 @@ import Ember from 'ember'; import ajax from 'ic-ajax'; export default Ember.Route.extend({ - beforeModel: function(transition) { - return ajax('/authorize', {data: transition.queryParams}).then(function(d) { + beforeModel(transition) { + return ajax('/authorize', {data: transition.queryParams}).then((d) => { var item = JSON.stringify({ ok: true, data: d }); if (window.opener) { window.opener.github_response = item; } - }).catch(function(d) { + }).catch((d) => { var item = JSON.stringify({ ok: false, data: d }); if (window.opener) { window.opener.github_response = item; } - }).finally(function() { + }).finally(() => { window.close(); }); }, diff --git a/app/routes/github-login.js b/app/routes/github-login.js index 5037585355d..3eceff836cf 100644 --- a/app/routes/github-login.js +++ b/app/routes/github-login.js @@ -2,8 +2,8 @@ import Ember from 'ember'; import ajax from 'ic-ajax'; export default Ember.Route.extend({ - beforeModel: function() { - return ajax('/authorize_url').then(function(url) { + beforeModel() { + return ajax('/authorize_url').then((url) => { window.location = url.url; }); }, diff --git a/app/routes/index.js b/app/routes/index.js index df5c0cb210e..030f4ba5480 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -2,20 +2,19 @@ import Ember from 'ember'; import ajax from 'ic-ajax'; export default Ember.Route.extend({ - model: function() { - var self = this; - - var addCrates = function(crates) { + model() { + function addCrates(store, crates) { for (var i = 0; i < crates.length; i++) { - crates[i] = self.store.push('crate', crates[i]); + crates[i] = store.push(store.normalize('crate', crates[i])); } - }; - return ajax('/summary').then(function(data) { - addCrates(data.new_crates); - addCrates(data.most_downloaded); - addCrates(data.just_updated); + } + + return ajax('/summary').then((data) => { + addCrates(this.store, data.new_crates); + addCrates(this.store, data.most_downloaded); + addCrates(this.store, data.just_updated); + return data; }); } }); - diff --git a/app/routes/keyword/index.js b/app/routes/keyword/index.js index 27a943eb79b..54156cde761 100644 --- a/app/routes/keyword/index.js +++ b/app/routes/keyword/index.js @@ -7,20 +7,19 @@ export default Ember.Route.extend({ }, loadedCrates: [], - afterModel: function(keyword, transition) { + afterModel(keyword, transition) { var params = transition.queryParams; params.keyword = keyword.get('keyword'); - var self = this; - return this.store.find('crate', params).then(function(array) { - if (self.controllerFor('keyword/index')) { - self.controllerFor('keyword/index').set('model', array); + return this.store.find('crate', params).then((array) => { + if (this.controllerFor('keyword/index')) { + this.controllerFor('keyword/index').set('model', array); } - self.set('loadedCrates', array); + this.set('loadedCrates', array); }); }, - setupController: function(controller, keyword) { + setupController(controller, keyword) { this._super(controller, this.get('loadedCrates')); controller.set('keyword', keyword); - }, + } }); diff --git a/app/routes/keywords.js b/app/routes/keywords.js index 4ce6f49a995..d039c9dbb3c 100644 --- a/app/routes/keywords.js +++ b/app/routes/keywords.js @@ -6,7 +6,7 @@ export default Ember.Route.extend({ sort: { refreshModel: true }, }, - model: function(params) { + model(params) { return this.store.find('keyword', params); }, }); diff --git a/app/routes/login.js b/app/routes/login.js index dc7f5d024b2..440e00c1ff1 100644 --- a/app/routes/login.js +++ b/app/routes/login.js @@ -1,8 +1,9 @@ import Ember from 'ember'; export default Ember.Route.extend({ - beforeModel: function(transition) { + beforeModel(transition) { try { localStorage.removeItem('github_response'); } catch (e) {} + delete window.github_response; var win = window.open('/github_login', 'Authorization', 'width=1000,height=450,' + @@ -12,8 +13,7 @@ export default Ember.Route.extend({ // For the life of me I cannot figure out how to do this other than // polling - var self = this; - var oauthInterval = window.setInterval(function(){ + var oauthInterval = window.setInterval(() => { if (!win.closed) { return; } window.clearInterval(oauthInterval); var json = window.github_response; @@ -23,26 +23,26 @@ export default Ember.Route.extend({ var response = JSON.parse(json); if (!response) { return; } if (!response.ok) { - self.controllerFor('application').set('flashError', + this.controllerFor('application').set('flashError', 'Failed to log in'); return; } var data = response.data; if (data.errors) { var error = "Failed to log in: " + data.errors[0].detail; - self.controllerFor('application').set('flashError', error); + this.controllerFor('application').set('flashError', error); return; } - var user = self.store.push('user', data.user); + var user = this.store.push(this.store.normalize('user', data.user)); user.set('api_token', data.api_token); - var transition = self.session.get('savedTransition'); - self.session.loginUser(user); + var transition = this.session.get('savedTransition'); + this.session.loginUser(user); if (transition) { transition.retry(); } }, 200); + transition.abort(); } }); - diff --git a/app/routes/logout.js b/app/routes/logout.js index 6bfea544905..4bf471cd0a3 100644 --- a/app/routes/logout.js +++ b/app/routes/logout.js @@ -1,11 +1,12 @@ import Ember from 'ember'; export default Ember.Route.extend({ - activate: function() { - var self = this; - Ember.$.getJSON('/logout', function() { - self.session.logoutUser(); - self.transitionTo('index'); + activate() { + Ember.$.getJSON('/logout', () => { + Ember.run(() => { + this.session.logoutUser(); + this.transitionTo('index'); }); + }); } }); diff --git a/app/routes/me/crates.js b/app/routes/me/crates.js index 7fbd15720f0..6a7cff676f3 100644 --- a/app/routes/me/crates.js +++ b/app/routes/me/crates.js @@ -7,7 +7,7 @@ export default Ember.Route.extend(AuthenticatedRoute, { sort: { refreshModel: true }, }, - model: function(params) { + model(params) { params.user_id = this.session.get('currentUser.id'); return this.store.find('crate', params); }, diff --git a/app/routes/me/following.js b/app/routes/me/following.js index c17be2b60c7..bad3016dc89 100644 --- a/app/routes/me/following.js +++ b/app/routes/me/following.js @@ -7,7 +7,7 @@ export default Ember.Route.extend(AuthenticatedRoute, { sort: { refreshModel: true }, }, - model: function(params) { + model(params) { params.following = 1; return this.store.find('crate', params); }, diff --git a/app/routes/me/index.js b/app/routes/me/index.js index be77db51369..b5200d7bc8e 100644 --- a/app/routes/me/index.js +++ b/app/routes/me/index.js @@ -2,7 +2,7 @@ import Ember from 'ember'; import AuthenticatedRoute from 'cargo/mixins/authenticated-route'; export default Ember.Route.extend(AuthenticatedRoute, { - model: function() { + model() { return this.session.get('currentUser'); }, }); diff --git a/app/routes/search.js b/app/routes/search.js index 7e8b434780c..b3fa9b69282 100644 --- a/app/routes/search.js +++ b/app/routes/search.js @@ -8,7 +8,7 @@ export default Ember.Route.extend({ page: { refreshModel: true }, }, - model: function(params) { + model(params) { return this.store.find('crate', params); }, }); diff --git a/app/services/session.js b/app/services/session.js index 7f8ff40cf7a..919d85b0252 100644 --- a/app/services/session.js +++ b/app/services/session.js @@ -1,11 +1,12 @@ import Ember from 'ember'; -export default Ember.Object.extend({ +export default Ember.Service.extend({ savedTransition: null, isLoggedIn: false, currentUser: null, - init: function() { + init() { + this._super(...arguments); var isLoggedIn; try { isLoggedIn = localStorage.getItem('isLoggedIn') === '1'; @@ -16,7 +17,7 @@ export default Ember.Object.extend({ this.set('currentUser', null); }, - loginUser: function(user) { + loginUser(user) { this.set('isLoggedIn', true); this.set('currentUser', user); try { @@ -24,12 +25,13 @@ export default Ember.Object.extend({ } catch (e) {} }, - logoutUser: function() { + logoutUser() { this.set('savedTransition', null); this.set('isLoggedIn', null); this.set('currentUser', null); + try { localStorage.removeItem('isLoggedIn'); } catch (e) {} - }, + } }); diff --git a/app/templates/application.hbs b/app/templates/application.hbs index 3d3f1bae291..677fc1cb26b 100644 --- a/app/templates/application.hbs +++ b/app/templates/application.hbs @@ -5,17 +5,17 @@