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 51c92bae95..ca725e889d 100644
--- a/app/assets/javascripts/discourse/components/basic_topic_list_component.js
+++ b/app/assets/javascripts/discourse/components/basic_topic_list_component.js
@@ -6,4 +6,30 @@
@namespace Discourse
@module Discourse
**/
-Discourse.BasicTopicListComponent = Ember.Component.extend({});
+Discourse.DiscourseBasicTopicListComponent = Ember.Component.extend({
+
+ loaded: function() {
+ var topicList = this.get('topicList');
+ if (topicList) {
+ return topicList.get('loaded');
+ } else {
+ return true;
+ }
+ }.property('topicList.loaded'),
+
+ init: function() {
+ this._super();
+
+ var topicList = this.get('topicList');
+ if (topicList) {
+ this.setProperties({
+ topics: topicList.get('topics'),
+ sortOrder: topicList.get('sortOrder')
+ });
+ } else {
+ // Without a topic list, we assume it's loaded always.
+ this.set('loaded', true);
+ }
+ }
+
+});
diff --git a/app/assets/javascripts/discourse/components/heading_component.js b/app/assets/javascripts/discourse/components/heading_component.js
new file mode 100644
index 0000000000..80fe7a0d0d
--- /dev/null
+++ b/app/assets/javascripts/discourse/components/heading_component.js
@@ -0,0 +1,27 @@
+Discourse.DiscourseHeadingComponent = Ember.Component.extend({
+ tagName: 'th',
+
+ classNameBindings: ['number:num', 'sortBy', 'iconSortClass:sorting', 'sortable'],
+ attributeBindings: ['colspan'],
+
+ sortable: function() {
+ return this.get('sortOrder') && this.get('sortBy');
+ }.property('sortOrder', 'sortBy'),
+
+ iconSortClass: function() {
+ var sortable = this.get('sortable');
+
+ if (sortable && this.get('sortBy') === this.get('sortOrder.order')) {
+ return this.get('sortOrder.descending') ? 'icon-chevron-down' : 'icon-chevron-up';
+ }
+ }.property('sortable', 'sortOrder.order', 'sortOrder.descending'),
+
+ click: function() {
+ var sortOrder = this.get('sortOrder'),
+ sortBy = this.get('sortBy');
+
+ if (sortBy && sortOrder) {
+ sortOrder.toggle(sortBy);
+ }
+ }
+});
diff --git a/app/assets/javascripts/discourse/controllers/user_topics_list_controller.js b/app/assets/javascripts/discourse/controllers/user_topics_list_controller.js
index 2e13d91fd2..b5a304e6d2 100644
--- a/app/assets/javascripts/discourse/controllers/user_topics_list_controller.js
+++ b/app/assets/javascripts/discourse/controllers/user_topics_list_controller.js
@@ -11,6 +11,10 @@ Discourse.UserTopicsListController = Discourse.ObjectController.extend({
actions: {
loadMore: function() {
this.get('model').loadMore();
+ },
+
+ changeSort: function() {
+ console.log('sort changed!');
}
}
diff --git a/app/assets/javascripts/discourse/helpers/i18n_helpers.js b/app/assets/javascripts/discourse/helpers/i18n_helpers.js
index 40622566e9..eea353f031 100644
--- a/app/assets/javascripts/discourse/helpers/i18n_helpers.js
+++ b/app/assets/javascripts/discourse/helpers/i18n_helpers.js
@@ -25,12 +25,13 @@ I18n.toHumanSize = function(number, options) {
**/
Ember.Handlebars.registerHelper('i18n', function(property, options) {
// Resolve any properties
- var params,
+ var params = options.hash,
self = this;
- params = options.hash;
+
_.each(params, function(value, key) {
params[key] = Em.Handlebars.get(self, value, options);
});
+
return I18n.t(property, params);
});
diff --git a/app/assets/javascripts/discourse/models/sort_order.js b/app/assets/javascripts/discourse/models/sort_order.js
new file mode 100644
index 0000000000..5cf9512b87
--- /dev/null
+++ b/app/assets/javascripts/discourse/models/sort_order.js
@@ -0,0 +1,30 @@
+/**
+ 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/topic_list.js b/app/assets/javascripts/discourse/models/topic_list.js
index ea0fb75e10..308260dde4 100644
--- a/app/assets/javascripts/discourse/models/topic_list.js
+++ b/app/assets/javascripts/discourse/models/topic_list.js
@@ -7,6 +7,26 @@
@module Discourse
**/
+function finderFor(filter, params) {
+ return function() {
+ var url = Discourse.getURL("/") + filter + ".json";
+
+ if (params) {
+ var keys = Object.keys(params);
+
+ if (keys.length > 0) {
+ var encoded = [];
+ keys.forEach(function(p) {
+ encoded.push(p + "=" + params[p]);
+ });
+
+ url += "?" + encoded.join('&');
+ }
+ }
+ return Discourse.ajax(url);
+ }
+}
+
Discourse.TopicList = Discourse.Model.extend({
forEachNew: function(topics, callback) {
@@ -22,8 +42,38 @@ Discourse.TopicList = Discourse.Model.extend({
});
},
- loadMore: function() {
+ 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() {
+ var self = this,
+ sortOrder = this.get('sortOrder'),
+ params = this.get('params');
+
+ params.sort_order = sortOrder.get('order');
+ params.sort_descending = sortOrder.get('descending');
+
+ this.set('loaded', false);
+ var finder = finderFor(this.get('filter'), params);
+ finder().then(function (result) {
+ var newTopics = Discourse.TopicList.topicsFrom(result),
+ topics = self.get('topics');
+
+ topics.clear();
+ topics.pushObjects(newTopics);
+ self.set('loaded', true);
+ });
+
+ }.observes('sortOrder.order', 'sortOrder.descending'),
+
+ loadMore: function() {
if (this.get('loadingMore')) { return Ember.RSVP.reject(); }
var moreUrl = this.get('more_topics_url');
@@ -146,26 +196,16 @@ Discourse.TopicList.reopenClass({
return Ember.RSVP.resolve(list);
}
session.setProperties({topicList: null, topicListScrollPos: null});
- return Discourse.TopicList.find(filter, menuItem.get('excludeCategory'));
- }
-});
+ return Discourse.TopicList.find(filter, {exclude_category: menuItem.get('excludeCategory')});
+ },
+ find: function(filter, params) {
-Discourse.TopicList.reopenClass({
-
- find: function(filter, excludeCategory) {
-
- // How we find our topic list
- var finder = function() {
- var url = Discourse.getURL("/") + filter + ".json";
- if (excludeCategory) { url += "?exclude_category=" + excludeCategory; }
- return Discourse.ajax(url);
- };
-
- return PreloadStore.getAndRemove("topic_list", finder).then(function(result) {
+ return PreloadStore.getAndRemove("topic_list", finderFor(filter, params)).then(function(result) {
var topicList = Discourse.TopicList.create({
inserted: Em.A(),
filter: filter,
+ params: params || {},
topics: Discourse.TopicList.topicsFrom(result),
can_create_topic: result.topic_list.can_create_topic,
more_topics_url: result.topic_list.more_topics_url,
diff --git a/app/assets/javascripts/discourse/routes/user_topic_list_routes.js b/app/assets/javascripts/discourse/routes/user_topic_list_routes.js
index cbf9d60d73..99830a2a5e 100644
--- a/app/assets/javascripts/discourse/routes/user_topic_list_routes.js
+++ b/app/assets/javascripts/discourse/routes/user_topic_list_routes.js
@@ -45,6 +45,6 @@ Discourse.UserActivityFavoritesRoute = Discourse.UserTopicListRoute.extend({
userActionType: Discourse.UserAction.TYPES.favorites,
model: function() {
- return Discourse.TopicList.find('favorited?user_id=' + this.modelFor('user').get('id'));
+ return Discourse.TopicList.find('favorited', {user_id: this.modelFor('user').get('id') });
}
});
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/templates/components/discourse-basic-topic-list.js.handlebars b/app/assets/javascripts/discourse/templates/components/discourse-basic-topic-list.js.handlebars
index 2b3152da26..a8b67a62a3 100644
--- a/app/assets/javascripts/discourse/templates/components/discourse-basic-topic-list.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/components/discourse-basic-topic-list.js.handlebars
@@ -1,66 +1,76 @@
-{{#if topics}}
-
-
- |
- {{i18n topic.title}}
- |
- {{#unless hideCategories}}
- {{i18n category_title}} |
- {{/unless}}
- {{i18n posts}} |
- {{i18n likes}} |
- {{i18n views}} |
- {{i18n activity}} |
-
+{{#if loaded}}
+ {{#if topics}}
+
+
+ {{else}}
+
+ {{i18n choose_topic.none_found}}
+
+ {{/if}}
{{else}}
-
- {{i18n choose_topic.none_found}}
-
-{{/if}}
+ {{i18n loading}}
+{{/if}}
\ No newline at end of file
diff --git a/app/assets/javascripts/discourse/templates/components/discourse-heading.js.handlebars b/app/assets/javascripts/discourse/templates/components/discourse-heading.js.handlebars
new file mode 100644
index 0000000000..773a9c5ded
--- /dev/null
+++ b/app/assets/javascripts/discourse/templates/components/discourse-heading.js.handlebars
@@ -0,0 +1,2 @@
+{{yield}}
+
diff --git a/app/assets/javascripts/discourse/templates/list/user_topics_list.js.handlebars b/app/assets/javascripts/discourse/templates/list/user_topics_list.js.handlebars
index 5cfef1025e..7eb83d9574 100644
--- a/app/assets/javascripts/discourse/templates/list/user_topics_list.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/list/user_topics_list.js.handlebars
@@ -1 +1 @@
-{{discourse-basic-topic-list topics=model.topics hideCategories="true"}}
+{{discourse-basic-topic-list topicList=model}}
diff --git a/app/assets/stylesheets/desktop/topic-list.scss b/app/assets/stylesheets/desktop/topic-list.scss
index c8d7b5897c..820c67c02e 100644
--- a/app/assets/stylesheets/desktop/topic-list.scss
+++ b/app/assets/stylesheets/desktop/topic-list.scss
@@ -21,9 +21,9 @@
font-weight: normal;
}
- a.badge-category {padding: 3px 12px; font-size: 16px;
+ a.badge-category {padding: 3px 12px; font-size: 16px;
- &.category-dropdown-button {
+ &.category-dropdown-button {
padding: 3px 9px 2px 9px;
i {height: 20px;}
@@ -104,6 +104,7 @@
font-size: 13px;
background: #eee;
+
}
td {
//border-top: 1px solid $topic-list-td-border-color;
@@ -197,6 +198,16 @@
color: inherit;
}
}
+ .sorting {
+ color: #009;
+ }
+ .sortable {
+ cursor: pointer;
+ &:hover {
+ background-color: #e6e6e6;
+ }
+ @include unselectable;
+ }
.likes {
width: 50px;
}
@@ -208,6 +219,21 @@
}
}
+.paginated-topics-list {
+ #topic-list {
+ .posts {
+ width: 95px;
+ }
+ .likes {
+ width: 95px;
+ }
+ .views {
+ width: 95px;
+ }
+
+ }
+}
+
#topic-list tbody tr.has-excerpt .star {
vertical-align: top;
margin-top: 3px;
diff --git a/app/controllers/list_controller.rb b/app/controllers/list_controller.rb
index bbae95c6ca..446a9b6512 100644
--- a/app/controllers/list_controller.rb
+++ b/app/controllers/list_controller.rb
@@ -140,7 +140,9 @@ class ListController < ApplicationController
page: params[:page],
topic_ids: param_to_integer_list(:topic_ids),
exclude_category: (params[:exclude_category] || menu_item.try(:filter)),
- category: params[:category]
+ category: params[:category],
+ sort_order: params[:sort_order],
+ sort_descending: params[:sort_order]
}
end
diff --git a/lib/topic_query.rb b/lib/topic_query.rb
index 003dd7565a..8c464390e8 100644
--- a/lib/topic_query.rb
+++ b/lib/topic_query.rb
@@ -7,7 +7,16 @@ require_dependency 'suggested_topics_builder'
class TopicQuery
# Could be rewritten to %i if Ruby 1.9 is no longer supported
- VALID_OPTIONS = %w(except_topic_id exclude_category limit page per_page topic_ids visible category).map(&:to_sym)
+ VALID_OPTIONS = %w(except_topic_id
+ exclude_category
+ limit
+ page
+ per_page
+ topic_ids
+ visible
+ category
+ sort_order
+ sort_descending).map(&:to_sym)
class << self
# use the constants in conjuction with COALESCE to determine the order with regard to pinned
@@ -30,8 +39,8 @@ class TopicQuery
END DESC"
end
- def order_hotness
- if @user
+ def order_hotness(user)
+ if user
# When logged in take into accounts what pins you've closed
"CASE
WHEN (COALESCE(topics.pinned_at, '#{lowest_date}') > COALESCE(tu.cleared_pinned_at, '#{lowest_date}'))
@@ -113,7 +122,7 @@ class TopicQuery
def list_hot
create_list(:hot, unordered: true) do |topics|
- topics.joins(:hot_topic).order(TopicQuery.order_hotness)
+ topics.joins(:hot_topic).order(TopicQuery.order_hotness(@user))
end
end
diff --git a/test/javascripts/models/sort_order_test.js b/test/javascripts/models/sort_order_test.js
new file mode 100644
index 0000000000..2cbd86c050
--- /dev/null
+++ b/test/javascripts/models/sort_order_test.js
@@ -0,0 +1,23 @@
+module("Discourse.SortOrder");
+
+test('defaults', function() {
+ var sortOrder = Discourse.SortOrder.create();
+ equal(sortOrder.get('order'), 'default', 'it is `default` by default');
+ equal(sortOrder.get('descending'), true, 'it is descending by default');
+});
+
+test('toggle', function() {
+ var sortOrder = Discourse.SortOrder.create();
+
+ sortOrder.toggle('default');
+ equal(sortOrder.get('descending'), false, 'if we toggle the same name it swaps the asc/desc');
+
+ sortOrder.toggle('name');
+ equal(sortOrder.get('order'), 'name', 'it changes the order');
+ equal(sortOrder.get('descending'), true, 'when toggling names it switches back to descending');
+
+ sortOrder.toggle('name');
+ sortOrder.toggle('name');
+ equal(sortOrder.get('descending'), true, 'toggling twice goes back to descending');
+
+});
\ No newline at end of file