diff --git a/app/assets/javascripts/discourse/components/basic_topic_list_component.js b/app/assets/javascripts/discourse/components/basic_topic_list_component.js index 815b959da..03a7368d4 100644 --- a/app/assets/javascripts/discourse/components/basic_topic_list_component.js +++ b/app/assets/javascripts/discourse/components/basic_topic_list_component.js @@ -23,10 +23,7 @@ Discourse.BasicTopicListComponent = Ember.Component.extend({ _initFromTopicList: function(topicList) { if (topicList !== null) { - this.setProperties({ - topics: topicList.get('topics'), - sortOrder: topicList.get('sortOrder') - }); + this.set('topics', topicList.get('topics')); this.rerender(); } }, diff --git a/app/assets/javascripts/discourse/components/sortable_heading_component.js b/app/assets/javascripts/discourse/components/sortable_heading_component.js index 7b3317b5b..5ffd347ef 100644 --- a/app/assets/javascripts/discourse/components/sortable_heading_component.js +++ b/app/assets/javascripts/discourse/components/sortable_heading_component.js @@ -12,23 +12,16 @@ Discourse.SortableHeadingComponent = Ember.Component.extend({ attributeBindings: ['colspan'], sortable: function() { - return this.get('sortOrder') && this.get('sortBy'); - }.property('sortOrder', 'sortBy'), + return this.get('order') && this.get('sortBy'); + }.property('order', 'sortBy'), iconSortClass: function() { - var sortable = this.get('sortable'); - - if (sortable && this.get('sortBy') === this.get('sortOrder.order')) { - return this.get('sortOrder.descending') ? 'fa fa-chevron-down' : 'fa fa-chevron-up'; + if (this.get('sortable') && this.get('sortBy') === this.get('order')) { + return this.get('ascending') ? 'fa fa-chevron-up' : 'fa fa-chevron-down'; } - }.property('sortable', 'sortOrder.order', 'sortOrder.descending'), + }.property('sortable', 'order', 'ascending'), click: function() { - var sortOrder = this.get('sortOrder'), - sortBy = this.get('sortBy'); - - if (sortBy && sortOrder) { - sortOrder.toggle(sortBy); - } + this.sendAction('action', this.get('sortBy')); } }); diff --git a/app/assets/javascripts/discourse/controllers/_discovery_controller.js b/app/assets/javascripts/discourse/controllers/_discovery_controller.js index 8ecfe16df..d9a241f26 100644 --- a/app/assets/javascripts/discourse/controllers/_discovery_controller.js +++ b/app/assets/javascripts/discourse/controllers/_discovery_controller.js @@ -25,3 +25,4 @@ Discourse.DiscoveryController = Discourse.ObjectController.extend({ showMoreMonthlyUrl: function() { return this.showMoreUrl('monthly'); }.property('category', 'noSubcategories'), showMoreYearlyUrl: function() { return this.showMoreUrl('yearly'); }.property('category', 'noSubcategories') }); + diff --git a/app/assets/javascripts/discourse/controllers/discovery_sortable_controller.js b/app/assets/javascripts/discourse/controllers/discovery_sortable_controller.js new file mode 100644 index 000000000..7c5ff5c37 --- /dev/null +++ b/app/assets/javascripts/discourse/controllers/discovery_sortable_controller.js @@ -0,0 +1,6 @@ +Discourse.DiscoverySortableController = Discourse.Controller.extend({ + needs: ['discoveryTopics'], + queryParams: ['order', 'ascending'], + order: Em.computed.alias('controllers.discoveryTopics.order'), + ascending: Em.computed.alias('controllers.discoveryTopics.ascending') +}); diff --git a/app/assets/javascripts/discourse/controllers/discovery_topics_controller.js b/app/assets/javascripts/discourse/controllers/discovery_topics_controller.js index c3eaf465d..6e5d94bfa 100644 --- a/app/assets/javascripts/discourse/controllers/discovery_topics_controller.js +++ b/app/assets/javascripts/discourse/controllers/discovery_topics_controller.js @@ -11,7 +11,20 @@ Discourse.DiscoveryTopicsController = Discourse.DiscoveryController.extend({ bulkSelectEnabled: false, selected: [], + order: 'default', + ascending: false, + actions: { + + changeSort: function(sortBy) { + if (sortBy === this.get('order')) { + this.toggleProperty('ascending'); + } else { + this.setProperties({ order: sortBy, ascending: false }); + } + this.get('model').refreshSort(sortBy, this.get('ascending')); + }, + // Show newly inserted topics showInserted: function() { var tracker = Discourse.TopicTrackingState.current(); @@ -117,11 +130,6 @@ Discourse.DiscoveryTopicsController = Discourse.DiscoveryController.extend({ }.property('allLoaded', 'topics.length'), loadMoreTopics: function() { - var topicList = this.get('model'); - return topicList.loadMore().then(function(moreUrl) { - if (!Em.isEmpty(moreUrl)) { - Discourse.URL.replaceState(Discourse.getURL("/") + topicList.get('filter') + "/more"); - } - }); + return this.get('model').loadMore(); } }); diff --git a/app/assets/javascripts/discourse/controllers/topic_controller.js b/app/assets/javascripts/discourse/controllers/topic_controller.js index fc2febaf8..da9a63c73 100644 --- a/app/assets/javascripts/discourse/controllers/topic_controller.js +++ b/app/assets/javascripts/discourse/controllers/topic_controller.js @@ -22,12 +22,7 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected return this.get('postStream.summary') ? "summary" : null; }.property('postStream.summary'), - username_filters: function(key, value) { - // TODO: Ember bug? If I don't have a value parameter this does not update - if (value) { - } - return this.get('postStream.streamFilters.username_filters'); - }.property("postStream.streamFilters.username_filters"), + username_filters: Discourse.computed.queryAlias('postStream.streamFilters.username_filters'), init: function() { this._super(); diff --git a/app/assets/javascripts/discourse/lib/computed.js b/app/assets/javascripts/discourse/lib/computed.js index 952a508dc..3cf503bd0 100644 --- a/app/assets/javascripts/discourse/lib/computed.js +++ b/app/assets/javascripts/discourse/lib/computed.js @@ -110,6 +110,23 @@ Discourse.computed = { }); }); return computed.property.apply(computed, args); - } + }, + /** + Creates a one way alias to a computed property, suitable for query params. + + @method queryAlias + @param {String} path to the alias + @param {String} defaultValue for the variable (omitted if equal) + **/ + queryAlias: function(path, defaultValue) { + return Ember.computed(function(key, value) { + if (value) { + // Annoying but this ensures the parameter is present + } + var result = this.get(path); + if (typeof result !== "undefined" && result.toString() === defaultValue) { return; } + return result; + }).property(path); + } }; diff --git a/app/assets/javascripts/discourse/lib/url.js b/app/assets/javascripts/discourse/lib/url.js index da8231403..2f584c811 100644 --- a/app/assets/javascripts/discourse/lib/url.js +++ b/app/assets/javascripts/discourse/lib/url.js @@ -10,9 +10,6 @@ Discourse.URL = Em.Object.createWithMixins({ // Used for matching a topic TOPIC_REGEXP: /\/t\/([^\/]+)\/(\d+)\/?(\d+)?/, - // Used for matching a /more URL - MORE_REGEXP: /\/more$/, - /** Browser aware replaceState. Will only be invoked if the browser supports it. @@ -75,7 +72,6 @@ Discourse.URL = Em.Object.createWithMixins({ // TODO: Extract into rules we can inject into the URL handler if (this.navigatedToHome(oldPath, path)) { return; } - if (this.navigatedToListMore(oldPath, path)) { return; } if (this.navigatedToPost(oldPath, path)) { return; } if (path.match(/^\/?users\/[^\/]+$/)) { @@ -111,24 +107,6 @@ Discourse.URL = Em.Object.createWithMixins({ return false; }, - - /** - @private - - If we're viewing more topics, scroll to where we were previously. - - @method navigatedToListMore - @param {String} oldPath the previous path we were on - @param {String} path the path we're navigating to - **/ - navigatedToListMore: function(oldPath, path) { - // If we transition from a /more path, scroll to the top - if (this.MORE_REGEXP.exec(oldPath) && (oldPath.indexOf(path) === 0)) { - window.scrollTo(0, 0); - } - return false; - }, - /** @private diff --git a/app/assets/javascripts/discourse/models/sort_order.js b/app/assets/javascripts/discourse/models/sort_order.js deleted file mode 100644 index 5cf9512b8..000000000 --- a/app/assets/javascripts/discourse/models/sort_order.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - Represents the sort order of something, for example a topics list. - - @class SortOrder - @extends Ember.Object - @namespace Discourse - @module Discourse -**/ -Discourse.SortOrder = Ember.Object.extend({ - order: 'default', - descending: true, - - /** - Changes the sort to another column - - @method toggle - @params {String} order the new sort order - **/ - toggle: function(order) { - if (this.get('order') === order) { - this.toggleProperty('descending'); - } else { - this.setProperties({ - order: order, - descending: true - }); - } - } - -}); diff --git a/app/assets/javascripts/discourse/models/top_list.js b/app/assets/javascripts/discourse/models/top_list.js index a7a607862..d5ccb19ef 100644 --- a/app/assets/javascripts/discourse/models/top_list.js +++ b/app/assets/javascripts/discourse/models/top_list.js @@ -27,7 +27,6 @@ Discourse.TopList.reopenClass({ if (result[period]) { // instanciate a new topic list with no sorting topList.set(period, Discourse.TopicList.from(result[period])); - topList.set(period + ".sortOrder", undefined); } }); diff --git a/app/assets/javascripts/discourse/models/topic_list.js b/app/assets/javascripts/discourse/models/topic_list.js index 5c2d1b1cd..dbbaa112f 100644 --- a/app/assets/javascripts/discourse/models/topic_list.js +++ b/app/assets/javascripts/discourse/models/topic_list.js @@ -45,23 +45,12 @@ Discourse.TopicList = Discourse.Model.extend({ }); }, - sortOrder: function() { - return Discourse.SortOrder.create(); - }.property(), - - /** - If the sort order changes, replace the topics in the list with the new - order. - - @observes sortOrder - **/ - _sortOrderChanged: function() { + refreshSort: function(order, ascending) { var self = this, - sortOrder = this.get('sortOrder'), params = this.get('params'); - params.sort_order = sortOrder.get('order'); - params.sort_descending = sortOrder.get('descending'); + params.order = order; + params.ascending = ascending; this.set('loaded', false); var finder = finderFor(this.get('filter'), params); @@ -73,8 +62,7 @@ Discourse.TopicList = Discourse.Model.extend({ topics.pushObjects(newTopics); self.setProperties({ loaded: true, more_topics_url: result.topic_list.more_topics_url }); }); - - }.observes('sortOrder.order', 'sortOrder.descending'), + }, loadMore: function() { if (this.get('loadingMore')) { return Ember.RSVP.resolve(); } diff --git a/app/assets/javascripts/discourse/routes/application_routes.js b/app/assets/javascripts/discourse/routes/application_routes.js index fb739f585..9e46bfb9b 100644 --- a/app/assets/javascripts/discourse/routes/application_routes.js +++ b/app/assets/javascripts/discourse/routes/application_routes.js @@ -32,25 +32,17 @@ Discourse.Route.buildRoutes(function() { Discourse.Site.currentProp('periods').forEach(function(period) { var top = 'top' + period.capitalize(); router.route(top, { path: '/top/' + period }); - router.route(top, { path: '/top/' + period + '/more' }); router.route(top + 'Category', { path: '/category/:slug/l/top/' + period }); - router.route(top + 'Category', { path: '/category/:slug/l/top/' + period + '/more' }); router.route(top + 'CategoryNone', { path: '/category/:slug/none/l/top/' + period }); - router.route(top + 'CategoryNone', { path: '/category/:slug/none/l/top/' + period + '/more' }); router.route(top + 'Category', { path: '/category/:parentSlug/:slug/l/top/' + period }); - router.route(top + 'Category', { path: '/category/:parentSlug/:slug/l/top/' + period + '/more' }); }); // filters Discourse.Site.currentProp('filters').forEach(function(filter) { router.route(filter, { path: '/' + filter }); - router.route(filter, { path: '/' + filter + '/more' }); router.route(filter + 'Category', { path: '/category/:slug/l/' + filter }); - router.route(filter + 'Category', { path: '/category/:slug/l/' + filter + '/more' }); router.route(filter + 'CategoryNone', { path: '/category/:slug/none/l/' + filter }); - router.route(filter + 'CategoryNone', { path: '/category/:slug/none/l/' + filter + '/more' }); router.route(filter + 'Category', { path: '/category/:parentSlug/:slug/l/' + filter }); - router.route(filter + 'Category', { path: '/category/:parentSlug/:slug/l/' + filter + '/more' }); }); this.route('categories'); diff --git a/app/assets/javascripts/discourse/routes/discovery_route.js b/app/assets/javascripts/discourse/routes/discovery_route.js index 1f825cfb6..f6d8bf449 100644 --- a/app/assets/javascripts/discourse/routes/discovery_route.js +++ b/app/assets/javascripts/discourse/routes/discovery_route.js @@ -7,7 +7,8 @@ @namespace Discourse @module Discourse **/ -Discourse.DiscoveryRoute = Discourse.Route.extend(Discourse.OpenComposer, { +Discourse.DiscoveryRoute = Discourse.Route.extend(Discourse.ScrollTop, Discourse.OpenComposer, { + actions: { loading: function() { var controller = this.controllerFor('discovery'); @@ -25,6 +26,7 @@ Discourse.DiscoveryRoute = Discourse.Route.extend(Discourse.OpenComposer, { var controller = this.controllerFor('discovery'); Ember.run.cancel(controller.get('scheduledSpinner')); controller.setProperties({ loading: false, loadingSpinner: false }); + this._scrollTop(); }, didTransition: function() { diff --git a/app/assets/javascripts/discourse/routes/discovery_route_builders.js b/app/assets/javascripts/discourse/routes/discovery_route_builders.js index 64a8effb0..f43fa1a9a 100644 --- a/app/assets/javascripts/discourse/routes/discovery_route_builders.js +++ b/app/assets/javascripts/discourse/routes/discovery_route_builders.js @@ -6,15 +6,28 @@ **/ function buildTopicRoute(filter) { return Discourse.Route.extend({ + queryParams: { + sort: { replace: true }, + ascending: { replace: true } + }, + beforeModel: function() { this.controllerFor('navigationDefault').set('filterMode', filter); }, - model: function() { + model: function(data, transaction) { + + var params = transaction.queryParams; + // attempt to stop early cause we need this to be called before .sync Discourse.ScreenTrack.current().stop(); - return Discourse.TopicList.list(filter).then(function(list) { + var findOpts = {}; + if (params && params.order) { findOpts.order = params.order; } + if (params && params.ascending) { findOpts.ascending = params.ascending; } + + + return Discourse.TopicList.list(filter, findOpts).then(function(list) { var tracking = Discourse.TopicTrackingState.current(); if (tracking) { tracking.sync(list, filter); @@ -24,7 +37,13 @@ function buildTopicRoute(filter) { }); }, - setupController: function(controller, model) { + setupController: function(controller, model, trans) { + + controller.setProperties({ + order: Em.get(trans, 'queryParams.order'), + ascending: Em.get(trans, 'queryParams.ascending') + }); + var period = filter.indexOf('/') > 0 ? filter.split('/')[1] : '', filterText = I18n.t('filters.' + filter.replace('/', '.') + '.title', {count: 0}); @@ -141,6 +160,7 @@ Discourse.addInitializer(function() { Discourse.DiscoveryCategoryNoneRoute = buildCategoryRoute('latest', {no_subcategories: true}); Discourse.Site.currentProp('filters').forEach(function(filter) { + Discourse["Discovery" + filter.capitalize() + "Controller"] = Discourse.DiscoverySortableController.extend(); Discourse["Discovery" + filter.capitalize() + "Route"] = buildTopicRoute(filter); Discourse["Discovery" + filter.capitalize() + "CategoryRoute"] = buildCategoryRoute(filter); Discourse["Discovery" + filter.capitalize() + "CategoryNoneRoute"] = buildCategoryRoute(filter, {no_subcategories: true}); diff --git a/app/assets/javascripts/discourse/routes/topic_route.js b/app/assets/javascripts/discourse/routes/topic_route.js index 9e136bafb..fd361a60e 100644 --- a/app/assets/javascripts/discourse/routes/topic_route.js +++ b/app/assets/javascripts/discourse/routes/topic_route.js @@ -10,14 +10,8 @@ Discourse.TopicRoute = Discourse.Route.extend({ redirect: function() { Discourse.redirectIfLoginRequired(this); }, queryParams: { - filter: { - refreshModel: false, - replace: true - }, - username_filters: { - refreshModel: false, - replace: true - } + filter: { replace: true }, + username_filters: { replace: true } }, actions: { @@ -114,16 +108,18 @@ Discourse.TopicRoute = Discourse.Route.extend({ return topic; }, - model: function(params) { + model: function(params, transition) { + var queryParams = transition.queryParams; + var topic = this.modelFor('topic'); if (topic && (topic.get('id') === parseInt(params.id, 10))) { - this.setupParams(topic, params); + this.setupParams(topic, queryParams); // If we have the existing model, refresh it return topic.get('postStream').refresh().then(function() { return topic; }); } else { - return this.setupParams(Discourse.Topic.create(_.omit(params, 'username_filters', 'filter')), params); + return this.setupParams(Discourse.Topic.create(_.omit(params, 'username_filters', 'filter')), queryParams); } }, diff --git a/app/assets/javascripts/discourse/routes/user_route.js b/app/assets/javascripts/discourse/routes/user_route.js index d655adf20..7df3be76e 100644 --- a/app/assets/javascripts/discourse/routes/user_route.js +++ b/app/assets/javascripts/discourse/routes/user_route.js @@ -41,7 +41,7 @@ Discourse.UserRoute = Discourse.Route.extend({ serialize: function(model) { if (!model) return {}; - return { username: model.get('username').toLowerCase() }; + return { username: (Em.get(model, 'username') || '').toLowerCase() }; }, setupController: function(controller, user) { diff --git a/app/assets/javascripts/discourse/templates/components/basic-topic-list.js.handlebars b/app/assets/javascripts/discourse/templates/components/basic-topic-list.js.handlebars index a1eafc7a3..9be1e407e 100644 --- a/app/assets/javascripts/discourse/templates/components/basic-topic-list.js.handlebars +++ b/app/assets/javascripts/discourse/templates/components/basic-topic-list.js.handlebars @@ -3,24 +3,24 @@