mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 09:36:19 -05:00
add top page
This commit is contained in:
parent
b90e811825
commit
567d2bd23c
57 changed files with 532 additions and 324 deletions
1
Gemfile
1
Gemfile
|
@ -71,7 +71,6 @@ gem 'barber'
|
|||
|
||||
gem 'message_bus'
|
||||
gem 'rails_multisite', path: 'vendor/gems/rails_multisite'
|
||||
gem 'simple_handlebars_rails', path: 'vendor/gems/simple_handlebars_rails'
|
||||
|
||||
gem 'redcarpet', require: false
|
||||
gem 'airbrake', '3.1.2', require: false # errbit is broken with 3.1.3 for now
|
||||
|
|
|
@ -8,12 +8,6 @@ PATH
|
|||
specs:
|
||||
rails_multisite (0.0.1)
|
||||
|
||||
PATH
|
||||
remote: vendor/gems/simple_handlebars_rails
|
||||
specs:
|
||||
simple_handlebars_rails (0.0.1)
|
||||
rails (> 3.1)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
|
@ -480,7 +474,6 @@ DEPENDENCIES
|
|||
sidekiq (= 2.15.1)
|
||||
sidekiq-failures
|
||||
sidetiq (>= 0.3.6)
|
||||
simple_handlebars_rails!
|
||||
simplecov
|
||||
sinatra
|
||||
slim
|
||||
|
|
|
@ -24,7 +24,7 @@ Discourse.AdminSiteContentEditRoute = Discourse.Route.extend({
|
|||
this.render('admin/templates/site_content_edit', {into: 'admin/templates/site_contents'});
|
||||
},
|
||||
|
||||
exit: function() {
|
||||
deactivate: function() {
|
||||
this._super();
|
||||
this.render('admin/templates/site_contents_empty', {into: 'admin/templates/site_contents'});
|
||||
},
|
||||
|
|
|
@ -25,19 +25,6 @@ Discourse.ListController = Discourse.Controller.extend({
|
|||
});
|
||||
}.property("category"),
|
||||
|
||||
/**
|
||||
Refresh our current topic list
|
||||
|
||||
@method refresh
|
||||
**/
|
||||
refresh: function() {
|
||||
var listTopicsController = this.get('controllers.listTopics');
|
||||
listTopicsController.set('model.loaded', false);
|
||||
this.load(this.get('filterMode')).then(function (topicList) {
|
||||
listTopicsController.set('model', topicList);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Load a list based on a filter
|
||||
|
||||
|
@ -135,5 +122,5 @@ Discourse.ListController = Discourse.Controller.extend({
|
|||
});
|
||||
|
||||
Discourse.ListController.reopenClass({
|
||||
filters: ['latest', 'hot', 'favorited', 'read', 'unread', 'new', 'posted']
|
||||
filters: <%= Discourse.filters.map(&:to_s) %>
|
||||
});
|
|
@ -6,8 +6,8 @@
|
|||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
var validNavNames = ['latest', 'hot', 'categories', 'category', 'favorited', 'unread', 'new', 'read', 'posted'];
|
||||
var validAnon = ['latest', 'hot', 'categories', 'category'];
|
||||
var validNavNames = <%= Discourse.top_menu_items.map(&:to_s) %>;
|
||||
var validAnon = <%= Discourse.anonymous_top_menu_items.map(&:to_s) %>;
|
||||
|
||||
Discourse.NavItem = Discourse.Model.extend({
|
||||
|
34
app/assets/javascripts/discourse/models/top_list.js
Normal file
34
app/assets/javascripts/discourse/models/top_list.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
A data model representing a list of top topic lists
|
||||
|
||||
@class TopList
|
||||
@extends Discourse.Model
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
|
||||
Discourse.TopList = Discourse.Model.extend({});
|
||||
|
||||
Discourse.TopList.reopenClass({
|
||||
find: function() {
|
||||
return PreloadStore.getAndRemove("top_list", function() {
|
||||
return Discourse.ajax("/top.json");
|
||||
}).then(function (result) {
|
||||
var topList = Discourse.TopList.create({
|
||||
can_create_topic: result.can_create_topic,
|
||||
yearly: Discourse.TopicList.from(result.yearly),
|
||||
monthly: Discourse.TopicList.from(result.monthly),
|
||||
weekly: Discourse.TopicList.from(result.weekly),
|
||||
daily: Discourse.TopicList.from(result.daily)
|
||||
});
|
||||
// disable sorting
|
||||
topList.setProperties({
|
||||
"yearly.sortOrder": undefined,
|
||||
"monthly.sortOrder": undefined,
|
||||
"weekly.sortOrder": undefined,
|
||||
"daily.sortOrder": undefined
|
||||
});
|
||||
return topList;
|
||||
});
|
||||
}
|
||||
});
|
|
@ -181,6 +181,28 @@ Discourse.TopicList.reopenClass({
|
|||
});
|
||||
},
|
||||
|
||||
from: function(result, filter, params) {
|
||||
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,
|
||||
draft_key: result.topic_list.draft_key,
|
||||
draft_sequence: result.topic_list.draft_sequence,
|
||||
draft: result.topic_list.draft,
|
||||
canViewRankDetails: result.topic_list.can_view_rank_details,
|
||||
loaded: true
|
||||
});
|
||||
|
||||
if (result.topic_list.filtered_category) {
|
||||
topicList.set('category', Discourse.Category.create(result.topic_list.filtered_category));
|
||||
}
|
||||
|
||||
return topicList;
|
||||
},
|
||||
|
||||
/**
|
||||
Lists topics on a given menu item
|
||||
|
||||
|
@ -206,24 +228,7 @@ Discourse.TopicList.reopenClass({
|
|||
|
||||
find: function(filter, params) {
|
||||
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,
|
||||
draft_key: result.topic_list.draft_key,
|
||||
draft_sequence: result.topic_list.draft_sequence,
|
||||
draft: result.topic_list.draft,
|
||||
canViewRankDetails: result.topic_list.can_view_rank_details,
|
||||
loaded: true
|
||||
});
|
||||
|
||||
if (result.topic_list.filtered_category) {
|
||||
topicList.set('category', Discourse.Category.create(result.topic_list.filtered_category));
|
||||
}
|
||||
return topicList;
|
||||
return Discourse.TopicList.from(result, filter, params);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -167,9 +167,9 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
|||
|
||||
lookupCount: function(name, category){
|
||||
var categoryName = Em.get(category, "name");
|
||||
if(name==="new") {
|
||||
if(name === "new") {
|
||||
return this.countNew(categoryName);
|
||||
} else if(name==="unread") {
|
||||
} else if(name === "unread") {
|
||||
return this.countUnread(categoryName);
|
||||
} else {
|
||||
categoryName = name.split("/")[1];
|
||||
|
|
|
@ -30,20 +30,25 @@ Discourse.Route.buildRoutes(function() {
|
|||
router.route(filter + "Category", { path: "/category/:slug/l/" + filter + "/more" });
|
||||
router.route(filter + "Category", { path: "/category/:parentSlug/:slug/l/" + filter });
|
||||
router.route(filter + "Category", { path: "/category/:parentSlug/:slug/l/" + filter + "/more" });
|
||||
|
||||
});
|
||||
|
||||
// the homepage is the first item of the 'top_menu' site setting
|
||||
var homepage = Discourse.SiteSettings.top_menu.split("|")[0].split(",")[0];
|
||||
this.route(homepage, { path: '/' });
|
||||
|
||||
// categories page
|
||||
this.route('categories', { path: '/categories' });
|
||||
|
||||
// category
|
||||
this.route('category', { path: '/category/:slug' });
|
||||
this.route('category', { path: '/category/:slug/more' });
|
||||
this.route('categoryNone', { path: '/category/:slug/none' });
|
||||
this.route('categoryNone', { path: '/category/:slug/none/more' });
|
||||
this.route('category', { path: '/category/:parentSlug/:slug' });
|
||||
this.route('category', { path: '/category/:parentSlug/:slug/more' });
|
||||
|
||||
// top page
|
||||
this.route('top', { path: '/top' });
|
||||
});
|
||||
|
||||
// User routes
|
||||
|
@ -58,8 +63,8 @@ Discourse.Route.buildRoutes(function() {
|
|||
});
|
||||
|
||||
this.resource('userPrivateMessages', { path: '/private-messages' }, function() {
|
||||
this.route('mine', {path: '/mine'});
|
||||
this.route('unread', {path: '/unread'});
|
||||
this.route('mine', { path: '/mine' });
|
||||
this.route('unread', { path: '/unread' });
|
||||
});
|
||||
|
||||
this.resource('preferences', { path: '/preferences' }, function() {
|
||||
|
|
|
@ -10,12 +10,13 @@ Discourse.FilteredListRoute = Discourse.Route.extend({
|
|||
|
||||
redirect: function() { Discourse.redirectIfLoginRequired(this); },
|
||||
|
||||
exit: function() {
|
||||
deactivate: function() {
|
||||
this._super();
|
||||
|
||||
var listController = this.controllerFor('list');
|
||||
listController.set('canCreateTopic', false);
|
||||
listController.set('filterMode', '');
|
||||
this.controllerFor('list').setProperties({
|
||||
canCreateTopic: false,
|
||||
filterMode: ''
|
||||
});
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
|
|
|
@ -63,9 +63,7 @@ Discourse.ListCategoryNoneRoute = Discourse.ListCategoryRoute.extend({
|
|||
});
|
||||
|
||||
Discourse.ListController.filters.forEach(function(filter) {
|
||||
Discourse["List" + (filter.capitalize()) + "CategoryRoute"] = Discourse.ListCategoryRoute.extend({
|
||||
filter: filter
|
||||
});
|
||||
Discourse["List" + filter.capitalize() + "CategoryRoute"] = Discourse.ListCategoryRoute.extend({ filter: filter });
|
||||
});
|
||||
|
||||
|
||||
|
|
24
app/assets/javascripts/discourse/routes/list_top_route.js
Normal file
24
app/assets/javascripts/discourse/routes/list_top_route.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
Discourse.ListTopRoute = Discourse.Route.extend({
|
||||
|
||||
activate: function() {
|
||||
// will mark the "top" navigation item as selected
|
||||
this.controllerFor('list').setProperties({
|
||||
filterMode: 'top',
|
||||
category: null
|
||||
});
|
||||
},
|
||||
|
||||
model: function() {
|
||||
return Discourse.TopList.find();
|
||||
},
|
||||
|
||||
renderTemplate: function() {
|
||||
this.render('top', { into: 'list', outlet: 'listView' });
|
||||
},
|
||||
|
||||
deactivate: function() {
|
||||
// Clear any filters when we leave the route
|
||||
Discourse.URL.set('queryParams', null);
|
||||
}
|
||||
|
||||
});
|
|
@ -73,7 +73,7 @@ Discourse.PreferencesAboutRoute = Discourse.RestrictedUserRoute.extend({
|
|||
},
|
||||
|
||||
// A bit odd, but if we leave to /preferences we need to re-render that outlet
|
||||
exit: function() {
|
||||
deactivate: function() {
|
||||
this._super();
|
||||
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
|
||||
},
|
||||
|
@ -119,7 +119,7 @@ Discourse.PreferencesEmailRoute = Discourse.RestrictedUserRoute.extend({
|
|||
},
|
||||
|
||||
// A bit odd, but if we leave to /preferences we need to re-render that outlet
|
||||
exit: function() {
|
||||
deactivate: function() {
|
||||
this._super();
|
||||
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ Discourse.PreferencesUsernameRoute = Discourse.RestrictedUserRoute.extend({
|
|||
},
|
||||
|
||||
// A bit odd, but if we leave to /preferences we need to re-render that outlet
|
||||
exit: function() {
|
||||
deactivate: function() {
|
||||
this._super();
|
||||
this.render('preferences', { into: 'user', outlet: 'userOutlet', controller: 'preferences' });
|
||||
},
|
||||
|
|
|
@ -73,4 +73,4 @@
|
|||
{{/if}}
|
||||
{{else}}
|
||||
<div class='spinner'>{{i18n loading}}</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<h2>{{i18n filters.top.this_year}}</h2>
|
||||
{{basic-topic-list topicList=content.yearly}}
|
||||
<h2>{{i18n filters.top.this_month}}</h2>
|
||||
{{basic-topic-list topicList=content.monthly}}
|
||||
<h2>{{i18n filters.top.this_week}}</h2>
|
||||
{{basic-topic-list topicList=content.weekly}}
|
||||
<h2>{{i18n filters.top.today}}</h2>
|
||||
{{basic-topic-list topicList=content.daily}}
|
||||
<h3>{{#link-to "list.categories"}}{{i18n topic.browse_all_categories}}{{/link-to}} {{i18n or}} {{#link-to 'list.latest'}}{{i18n topic.view_latest_topics}}{{/link-to}}</h3>
|
|
@ -60,7 +60,4 @@ Discourse.ListTopicsView = Discourse.View.extend(Discourse.LoadMore, {
|
|||
this.saveScrollPosition();
|
||||
}
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ Discourse.TopicListItemView = Discourse.GroupedView.extend({
|
|||
didInsertElement: function() {
|
||||
var session = Discourse.Session.current();
|
||||
|
||||
// // highligth the last topic viewed
|
||||
// highligth the last topic viewed
|
||||
if (session.get('lastTopicIdViewed') === this.get('content.id')) {
|
||||
session.set('lastTopicIdViewed', null);
|
||||
this.highlight();
|
||||
|
|
|
@ -454,6 +454,9 @@
|
|||
#list-area {
|
||||
margin-bottom: 300px;
|
||||
|
||||
h2 {
|
||||
margin: 20px 0 10px;
|
||||
}
|
||||
|
||||
.topic-statuses .topic-status i {font-size: 15px;}
|
||||
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
class ListController < ApplicationController
|
||||
|
||||
before_filter :ensure_logged_in, except: [:latest, :hot, :category, :category_feed, :latest_feed, :hot_feed, :topics_by]
|
||||
before_filter :ensure_logged_in, except: [:latest, :hot, :category, :top, :category_feed, :latest_feed, :hot_feed, :topics_by]
|
||||
before_filter :set_category, only: [:category, :category_feed]
|
||||
skip_before_filter :check_xhr
|
||||
|
||||
# Create our filters
|
||||
[:latest, :hot, :favorited, :read, :posted, :unread, :new].each do |filter|
|
||||
Discourse.filters.each do |filter|
|
||||
define_method(filter) do
|
||||
list_opts = build_topic_list_options
|
||||
user = list_target_user
|
||||
list = TopicQuery.new(user, list_opts).public_send("list_#{filter}")
|
||||
list.more_topics_url = construct_url_with(filter, list_opts)
|
||||
if [:latest, :hot].include?(filter)
|
||||
if Discourse.anonymous_filters.include?(filter)
|
||||
@description = SiteSetting.site_description
|
||||
@rss = filter
|
||||
end
|
||||
|
||||
respond(list)
|
||||
end
|
||||
end
|
||||
|
||||
[:latest, :hot].each do |filter|
|
||||
Discourse.anonymous_filters.each do |filter|
|
||||
define_method("#{filter}_feed") do
|
||||
discourse_expires_in 1.minute
|
||||
|
||||
|
@ -29,6 +28,7 @@ class ListController < ApplicationController
|
|||
@description = I18n.t("rss_description.#{filter}")
|
||||
@atom_link = "#{Discourse.base_url}/#{filter}.rss"
|
||||
@topic_list = TopicQuery.new.public_send("list_#{filter}")
|
||||
|
||||
render 'list', formats: [:rss]
|
||||
end
|
||||
end
|
||||
|
@ -72,6 +72,22 @@ class ListController < ApplicationController
|
|||
redirect_to latest_path, :status => 301
|
||||
end
|
||||
|
||||
def top
|
||||
sort_order = params[:sort_order] || "posts"
|
||||
top = generate_top_lists_by(sort_order)
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@top = top
|
||||
store_preloaded('top_list', MultiJson.dump(TopListSerializer.new(top, scope: guardian, root: false)))
|
||||
render 'top'
|
||||
end
|
||||
format.json do
|
||||
render json: MultiJson.dump(TopListSerializer.new(top, scope: guardian, root: false))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def category_response(extra_opts=nil)
|
||||
|
@ -84,15 +100,12 @@ class ListController < ApplicationController
|
|||
end
|
||||
|
||||
def respond(list)
|
||||
discourse_expires_in 1.minute
|
||||
|
||||
list.draft = Draft.get(current_user, list.draft_key, list.draft_sequence) if current_user
|
||||
list.draft_key = Draft::NEW_TOPIC
|
||||
list.draft_sequence = DraftSequence.current(current_user, Draft::NEW_TOPIC)
|
||||
|
||||
draft = Draft.get(current_user, list.draft_key, list.draft_sequence) if current_user
|
||||
list.draft = draft
|
||||
|
||||
discourse_expires_in 1.minute
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@list = list
|
||||
|
@ -165,4 +178,22 @@ class ListController < ApplicationController
|
|||
method = url_prefix.blank? ? "#{action}_path" : "#{url_prefix}_#{action}_path"
|
||||
public_send(method, opts.merge(next_page_params(opts)))
|
||||
end
|
||||
|
||||
def generate_top_lists_by(sort_order)
|
||||
top = {}
|
||||
topic_ids = Set.new
|
||||
|
||||
TopTopic.periods.each do |period|
|
||||
options = {
|
||||
per_page: SiteSetting.topics_per_period_in_summary,
|
||||
except_topic_ids: topic_ids.to_a
|
||||
}
|
||||
list = TopicQuery.new(current_user, options).list_top(sort_order, period)
|
||||
topic_ids.merge(list.topic_ids)
|
||||
top[period] = list
|
||||
end
|
||||
|
||||
top
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -8,7 +8,6 @@ module Jobs
|
|||
recurrence { hourly.minute_of_hour(3, 18, 33, 48) }
|
||||
|
||||
def execute(args)
|
||||
|
||||
# Update the average times
|
||||
Post.calculate_avg_time
|
||||
Topic.calculate_avg_time
|
||||
|
@ -25,6 +24,9 @@ module Jobs
|
|||
# Refresh Hot Topics
|
||||
HotTopic.refresh!
|
||||
|
||||
# Refresh Top Topics
|
||||
TopTopic.refresh!
|
||||
|
||||
# Automatically close stuff that we missed
|
||||
Topic.auto_close
|
||||
end
|
||||
|
|
|
@ -15,7 +15,7 @@ class CategoryFeaturedTopic < ActiveRecord::Base
|
|||
def self.feature_topics_for(c)
|
||||
return if c.blank?
|
||||
|
||||
query = TopicQuery.new(self.fake_admin, per_page: SiteSetting.category_featured_topics, except_topic_id: c.topic_id, visible: true)
|
||||
query = TopicQuery.new(self.fake_admin, per_page: SiteSetting.category_featured_topics, except_topic_ids: [c.topic_id], visible: true)
|
||||
results = query.list_category(c).topic_ids.to_a
|
||||
|
||||
CategoryFeaturedTopic.transaction do
|
||||
|
|
|
@ -53,7 +53,7 @@ class SiteSetting < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def self.anonymous_menu_items
|
||||
@anonymous_menu_items ||= Set.new ['latest', 'hot', 'categories', 'category']
|
||||
@anonymous_menu_items ||= Set.new Discourse.anonymous_filters.map(&:to_s)
|
||||
end
|
||||
|
||||
def self.anonymous_homepage
|
||||
|
|
81
app/models/top_topic.rb
Normal file
81
app/models/top_topic.rb
Normal file
|
@ -0,0 +1,81 @@
|
|||
class TopTopic < ActiveRecord::Base
|
||||
|
||||
belongs_to :topic
|
||||
|
||||
def self.periods
|
||||
@periods ||= %i{yearly monthly weekly daily}
|
||||
end
|
||||
|
||||
def self.sort_orders
|
||||
@sort_orders ||= %i{posts views likes}
|
||||
end
|
||||
|
||||
def self.refresh!
|
||||
transaction do
|
||||
# clean up the table
|
||||
exec_sql("DELETE FROM top_topics")
|
||||
# insert the list of all the visible topics
|
||||
exec_sql("INSERT INTO top_topics (topic_id)
|
||||
SELECT id
|
||||
FROM topics
|
||||
WHERE deleted_at IS NULL
|
||||
AND visible
|
||||
AND NOT archived")
|
||||
# update all the counter caches
|
||||
TopTopic.periods.each do |period|
|
||||
TopTopic.sort_orders.each do |sort|
|
||||
TopTopic.send("update_#{sort}_count_for", period)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.update_posts_count_for(period)
|
||||
sql = "SELECT topic_id, COUNT(*) AS count
|
||||
FROM posts p
|
||||
WHERE p.created_at >= :from
|
||||
AND p.deleted_at IS NULL
|
||||
AND NOT p.hidden
|
||||
GROUP BY topic_id"
|
||||
|
||||
TopTopic.update_top_topics(period, "posts", sql)
|
||||
end
|
||||
|
||||
def self.update_views_count_for(period)
|
||||
sql = "SELECT parent_id as topic_id, COUNT(*) AS count
|
||||
FROM views v
|
||||
WHERE v.viewed_at >= :from
|
||||
GROUP BY topic_id"
|
||||
|
||||
TopTopic.update_top_topics(period, "views", sql)
|
||||
end
|
||||
|
||||
def self.update_likes_count_for(period)
|
||||
sql = "SELECT topic_id, SUM(like_count) AS count
|
||||
FROM posts p
|
||||
WHERE p.created_at >= :from
|
||||
AND p.deleted_at IS NULL
|
||||
AND NOT p.hidden
|
||||
GROUP BY topic_id"
|
||||
|
||||
TopTopic.update_top_topics(period, "likes", sql)
|
||||
end
|
||||
|
||||
def self.start_of(period)
|
||||
case period
|
||||
when :yearly then 1.year.ago
|
||||
when :monthly then 1.month.ago
|
||||
when :weekly then 1.week.ago
|
||||
when :daily then 1.day.ago
|
||||
end
|
||||
end
|
||||
|
||||
def self.update_top_topics(period, sort, inner_join)
|
||||
exec_sql("UPDATE top_topics
|
||||
SET #{period}_#{sort}_count = c.count
|
||||
FROM top_topics tt
|
||||
INNER JOIN (#{inner_join}) c ON tt.topic_id = c.topic_id
|
||||
WHERE tt.topic_id = top_topics.topic_id", from: start_of(period))
|
||||
end
|
||||
|
||||
end
|
|
@ -82,6 +82,7 @@ class Topic < ActiveRecord::Base
|
|||
has_many :allowed_users, through: :topic_allowed_users, source: :user
|
||||
|
||||
has_one :hot_topic
|
||||
has_one :top_topic
|
||||
belongs_to :user
|
||||
belongs_to :last_poster, class_name: 'User', foreign_key: :last_post_user_id
|
||||
belongs_to :featured_user1, class_name: 'User', foreign_key: :featured_user1_id
|
||||
|
|
|
@ -51,7 +51,6 @@ class TopicList
|
|||
end
|
||||
|
||||
def has_rank_details?
|
||||
|
||||
# Only moderators can see rank details
|
||||
return false unless @current_user && @current_user.staff?
|
||||
|
||||
|
|
19
app/serializers/top_list_serializer.rb
Normal file
19
app/serializers/top_list_serializer.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
class TopListSerializer < ApplicationSerializer
|
||||
|
||||
attributes :can_create_topic,
|
||||
:yearly,
|
||||
:monthly,
|
||||
:weekly,
|
||||
:daily
|
||||
|
||||
def can_create_topic
|
||||
scope.can_create?(Topic)
|
||||
end
|
||||
|
||||
TopTopic.periods.each do |period|
|
||||
define_method(period) do
|
||||
TopicListSerializer.new(object[period], scope: scope).as_json
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,6 +1,6 @@
|
|||
<div class="topic-list">
|
||||
<% @list.topics.each do |t| %>
|
||||
<a href="<%= t.relative_url %>"><%= t.title %></a> <span title='<%= t 'posts' %>'>(<%= t.posts_count %>)</span><br/>
|
||||
<a href="<%= t.relative_url %>"><%= t.title %></a> <span title='<%= t 'posts' %>'>(<%= t.posts_count %>)</span><br/>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
|
1
app/views/list/top.erb
Normal file
1
app/views/list/top.erb
Normal file
|
@ -0,0 +1 @@
|
|||
<p><%= t 'powered_by_html' %></p>
|
|
@ -668,7 +668,7 @@ en:
|
|||
|
||||
browse_all_categories: Browse all categories
|
||||
|
||||
view_latest_topics: view latest topics
|
||||
view_latest_topics: view latest topics.
|
||||
suggest_create_topic: Why not create a topic?
|
||||
read_position_reset: "Your read position has been reset."
|
||||
jump_reply_up: jump to earlier reply
|
||||
|
@ -1102,6 +1102,13 @@ en:
|
|||
one: "{{categoryName}} (1)"
|
||||
other: "{{categoryName}} ({{count}})"
|
||||
help: "latest topics in the {{categoryName}} category"
|
||||
top:
|
||||
title: "Top"
|
||||
help: "a selection of the best topics"
|
||||
this_year: "This year"
|
||||
this_month: "This month"
|
||||
this_week: "This week"
|
||||
today: "Today"
|
||||
|
||||
browser_update: 'Unfortunately, <a href="http://www.discourse.org/faq/#browser">your browser is too old to work on this Discourse forum</a>. Please <a href="http://browsehappy.com">upgrade your browser</a>.'
|
||||
|
||||
|
|
|
@ -442,7 +442,7 @@ en:
|
|||
github_config_warning: 'The server is configured to allow signup and log in with GitHub (enable_github_logins), but the client id and secret values are not set. Go to <a href="/admin/site_settings">the Site Settings</a> and update the settings. <a href="https://github.com/discourse/discourse/wiki/The-Discourse-Admin-Quick-Start-Guide" target="_blank">See this guide to learn more</a>.'
|
||||
s3_config_warning: 'The server is configured to upload files to s3, but at least one the following setting is not set: s3_access_key_id, s3_secret_access_key or s3_upload_bucket. Go to <a href="/admin/site_settings">the Site Settings</a> and update the settings. <a href="http://meta.discourse.org/t/how-to-set-up-image-uploads-to-s3/7229" target="_blank">See "How to set up image uploads to S3?" to learn more</a>.'
|
||||
image_magick_warning: 'The server is configured to create thumbnails of large images, but ImageMagick is not installed. Install ImageMagick using your favorite package manager or <a href="http://www.imagemagick.org/script/binary-releases.php" target="_blank">download the latest release</a>.'
|
||||
failing_emails_warning: 'There are %{num_failed_jobs} email jobs that failed. Check your config/discourse.conf file and ensure that the mail server settings are correct. <a href="/sidekiq/retries" target="_blank">See the failed jobs in Sidekiq</a>.'
|
||||
failing_emails_warning: 'There are %{num_failed_jobs} email jobs that failed. Check your config/discourse.conf file and ensure that the mail server settings are correct. <a href="/sidekiq/retries" target="_blank">See the failed jobs in Sidekiq</a>.'
|
||||
default_logo_warning: "You haven't customized the logo images for your site. Update logo_url, logo_small_url, and favicon_url in the <a href='/admin/site_settings'>Site Settings</a>."
|
||||
contact_email_missing: "You haven't provided a contact email for your site. Please update contact_email in the <a href='/admin/site_settings'>Site Settings</a>."
|
||||
contact_email_invalid: "The site contact email is invalid. Please update contact_email in the <a href='/admin/site_settings'>Site Settings</a>."
|
||||
|
@ -586,6 +586,8 @@ en:
|
|||
suppress_reply_directly_below: "Don't show reply count on a post when there is a single reply directly below"
|
||||
suppress_reply_directly_above: "Don't show in-reply-to on a post when there is a single reply directly above"
|
||||
|
||||
topics_per_period_in_summary: "How many topics loaded by default on the top topics page"
|
||||
|
||||
allow_index_in_robots_txt: "Site should be indexed by search engines (update robots.txt)"
|
||||
email_domains_blacklist: "A pipe-delimited list of email domains that are not allowed. Example: mailinator.com|trashmail.net"
|
||||
email_domains_whitelist: "A pipe-delimited list of email domains that users may register with. WARNING: Users with email domains other than those listed will not be allowed."
|
||||
|
|
319
config/routes.rb
319
config/routes.rb
|
@ -1,9 +1,9 @@
|
|||
require 'sidekiq/web'
|
||||
require 'sidetiq/web'
|
||||
require "sidekiq/web"
|
||||
require "sidetiq/web"
|
||||
|
||||
require_dependency 'admin_constraint'
|
||||
require_dependency 'staff_constraint'
|
||||
require_dependency 'homepage_constraint'
|
||||
require_dependency "admin_constraint"
|
||||
require_dependency "staff_constraint"
|
||||
require_dependency "homepage_constraint"
|
||||
|
||||
# This used to be User#username_format, but that causes a preload of the User object
|
||||
# and makes Guard not work properly.
|
||||
|
@ -13,165 +13,166 @@ Discourse::Application.routes.draw do
|
|||
|
||||
match "/404", to: "exceptions#not_found", via: [:get, :post]
|
||||
|
||||
mount Sidekiq::Web => '/sidekiq', constraints: AdminConstraint.new
|
||||
mount Sidekiq::Web => "/sidekiq", constraints: AdminConstraint.new
|
||||
|
||||
resources :forums
|
||||
get 'srv/status' => 'forums#status'
|
||||
get "srv/status" => "forums#status"
|
||||
|
||||
namespace :admin, constraints: StaffConstraint.new do
|
||||
get '' => 'admin#index'
|
||||
get "" => "admin#index"
|
||||
|
||||
resources :site_settings, constraints: AdminConstraint.new do
|
||||
collection do
|
||||
get 'category/:id' => 'site_settings#index'
|
||||
get "category/:id" => "site_settings#index"
|
||||
end
|
||||
end
|
||||
|
||||
get 'reports/:type' => 'reports#show'
|
||||
get "reports/:type" => "reports#show"
|
||||
|
||||
resources :groups, constraints: AdminConstraint.new do
|
||||
collection do
|
||||
post 'refresh_automatic_groups' => 'groups#refresh_automatic_groups'
|
||||
post "refresh_automatic_groups" => "groups#refresh_automatic_groups"
|
||||
end
|
||||
get 'users'
|
||||
get "users"
|
||||
end
|
||||
|
||||
resources :users, id: USERNAME_ROUTE_FORMAT do
|
||||
collection do
|
||||
get 'list/:query' => 'users#index'
|
||||
put 'approve-bulk' => 'users#approve_bulk'
|
||||
delete 'reject-bulk' => 'users#reject_bulk'
|
||||
get "list/:query" => "users#index"
|
||||
put "approve-bulk" => "users#approve_bulk"
|
||||
delete "reject-bulk" => "users#reject_bulk"
|
||||
end
|
||||
put 'suspend'
|
||||
put 'delete_all_posts'
|
||||
put 'unsuspend'
|
||||
put 'revoke_admin', constraints: AdminConstraint.new
|
||||
put 'grant_admin', constraints: AdminConstraint.new
|
||||
post 'generate_api_key', constraints: AdminConstraint.new
|
||||
delete 'revoke_api_key', constraints: AdminConstraint.new
|
||||
put 'revoke_moderation', constraints: AdminConstraint.new
|
||||
put 'grant_moderation', constraints: AdminConstraint.new
|
||||
put 'approve'
|
||||
post 'refresh_browsers', constraints: AdminConstraint.new
|
||||
put 'activate'
|
||||
put 'deactivate'
|
||||
put 'block'
|
||||
put 'unblock'
|
||||
put 'trust_level'
|
||||
put "suspend"
|
||||
put "delete_all_posts"
|
||||
put "unsuspend"
|
||||
put "revoke_admin", constraints: AdminConstraint.new
|
||||
put "grant_admin", constraints: AdminConstraint.new
|
||||
post "generate_api_key", constraints: AdminConstraint.new
|
||||
delete "revoke_api_key", constraints: AdminConstraint.new
|
||||
put "revoke_moderation", constraints: AdminConstraint.new
|
||||
put "grant_moderation", constraints: AdminConstraint.new
|
||||
put "approve"
|
||||
post "refresh_browsers", constraints: AdminConstraint.new
|
||||
put "activate"
|
||||
put "deactivate"
|
||||
put "block"
|
||||
put "unblock"
|
||||
put "trust_level"
|
||||
end
|
||||
|
||||
resources :impersonate, constraints: AdminConstraint.new
|
||||
|
||||
resources :email do
|
||||
collection do
|
||||
post 'test'
|
||||
get 'logs'
|
||||
get 'preview-digest' => 'email#preview_digest'
|
||||
post "test"
|
||||
get "logs"
|
||||
get "preview-digest" => "email#preview_digest"
|
||||
end
|
||||
end
|
||||
|
||||
scope '/logs' do
|
||||
scope "/logs" do
|
||||
resources :staff_action_logs, only: [:index]
|
||||
resources :screened_emails, only: [:index]
|
||||
resources :screened_ip_addresses, only: [:index, :create, :update, :destroy]
|
||||
resources :screened_urls, only: [:index]
|
||||
end
|
||||
|
||||
get 'customize' => 'site_customizations#index', constraints: AdminConstraint.new
|
||||
get 'flags' => 'flags#index'
|
||||
get 'flags/:filter' => 'flags#index'
|
||||
post 'flags/agree/:id' => 'flags#agree'
|
||||
post 'flags/disagree/:id' => 'flags#disagree'
|
||||
post 'flags/defer/:id' => 'flags#defer'
|
||||
get "customize" => "site_customizations#index", constraints: AdminConstraint.new
|
||||
get "flags" => "flags#index"
|
||||
get "flags/:filter" => "flags#index"
|
||||
post "flags/agree/:id" => "flags#agree"
|
||||
post "flags/disagree/:id" => "flags#disagree"
|
||||
post "flags/defer/:id" => "flags#defer"
|
||||
resources :site_customizations, constraints: AdminConstraint.new
|
||||
resources :site_contents, constraints: AdminConstraint.new
|
||||
resources :site_content_types, constraints: AdminConstraint.new
|
||||
resources :export, constraints: AdminConstraint.new
|
||||
get 'version_check' => 'versions#show'
|
||||
get "version_check" => "versions#show"
|
||||
resources :dashboard, only: [:index] do
|
||||
collection do
|
||||
get 'problems'
|
||||
get "problems"
|
||||
end
|
||||
end
|
||||
resources :api, only: [:index], constraints: AdminConstraint.new do
|
||||
collection do
|
||||
post 'key' => 'api#create_master_key'
|
||||
put 'key' => 'api#regenerate_key'
|
||||
delete 'key' => 'api#revoke_key'
|
||||
post "key" => "api#create_master_key"
|
||||
put "key" => "api#regenerate_key"
|
||||
delete "key" => "api#revoke_key"
|
||||
end
|
||||
end
|
||||
end # admin namespace
|
||||
|
||||
get 'email_preferences' => 'email#preferences_redirect', :as => 'email_preferences_redirect'
|
||||
get 'email/unsubscribe/:key' => 'email#unsubscribe', as: 'email_unsubscribe'
|
||||
post 'email/resubscribe/:key' => 'email#resubscribe', as: 'email_resubscribe'
|
||||
get "email_preferences" => "email#preferences_redirect", :as => "email_preferences_redirect"
|
||||
get "email/unsubscribe/:key" => "email#unsubscribe", as: "email_unsubscribe"
|
||||
post "email/resubscribe/:key" => "email#resubscribe", as: "email_resubscribe"
|
||||
|
||||
|
||||
resources :session, id: USERNAME_ROUTE_FORMAT, only: [:create, :destroy] do
|
||||
collection do
|
||||
post 'forgot_password'
|
||||
post "forgot_password"
|
||||
end
|
||||
end
|
||||
|
||||
get 'session/csrf' => 'session#csrf'
|
||||
get 'composer-messages' => 'composer_messages#index'
|
||||
get "session/csrf" => "session#csrf"
|
||||
get "composer-messages" => "composer_messages#index"
|
||||
|
||||
resources :users, except: [:show, :update] do
|
||||
collection do
|
||||
get 'check_username'
|
||||
get 'is_local_username'
|
||||
get "check_username"
|
||||
get "is_local_username"
|
||||
end
|
||||
end
|
||||
|
||||
resources :static
|
||||
post 'login' => 'static#enter'
|
||||
get 'login' => 'static#show', id: 'login'
|
||||
get 'faq' => 'static#show', id: 'faq'
|
||||
get 'tos' => 'static#show', id: 'tos'
|
||||
get 'privacy' => 'static#show', id: 'privacy'
|
||||
post "login" => "static#enter"
|
||||
get "login" => "static#show", id: "login"
|
||||
get "faq" => "static#show", id: "faq"
|
||||
get "tos" => "static#show", id: "tos"
|
||||
get "privacy" => "static#show", id: "privacy"
|
||||
|
||||
get 'users/search/users' => 'users#search_users'
|
||||
get 'users/password-reset/:token' => 'users#password_reset'
|
||||
put 'users/password-reset/:token' => 'users#password_reset'
|
||||
get 'users/activate-account/:token' => 'users#activate_account'
|
||||
get 'users/authorize-email/:token' => 'users#authorize_email'
|
||||
get 'users/hp' => 'users#get_honeypot_value'
|
||||
get "users/search/users" => "users#search_users"
|
||||
get "users/password-reset/:token" => "users#password_reset"
|
||||
put "users/password-reset/:token" => "users#password_reset"
|
||||
get "users/activate-account/:token" => "users#activate_account"
|
||||
get "users/authorize-email/:token" => "users#authorize_email"
|
||||
get "users/hp" => "users#get_honeypot_value"
|
||||
|
||||
get 'user_preferences' => 'users#user_preferences_redirect'
|
||||
get 'users/:username/private-messages' => 'user_actions#private_messages', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get 'users/:username/private-messages/:filter' => 'user_actions#private_messages', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get 'users/:username' => 'users#show', as: 'userpage', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
put 'users/:username' => 'users#update', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get 'users/:username/preferences' => 'users#preferences', constraints: {username: USERNAME_ROUTE_FORMAT}, as: :email_preferences
|
||||
get 'users/:username/preferences/email' => 'users#preferences', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
put 'users/:username/preferences/email' => 'users#change_email', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get 'users/:username/preferences/about-me' => 'users#preferences', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get 'users/:username/preferences/username' => 'users#preferences', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
put 'users/:username/preferences/username' => 'users#username', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get 'users/:username/avatar(/:size)' => 'users#avatar', constraints: {username: USERNAME_ROUTE_FORMAT} # LEGACY ROUTE
|
||||
post 'users/:username/preferences/avatar' => 'users#upload_avatar', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
put 'users/:username/preferences/avatar/toggle' => 'users#toggle_avatar', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get 'users/:username/invited' => 'users#invited', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
post 'users/:username/send_activation_email' => 'users#send_activation_email', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get 'users/:username/activity' => 'users#show', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get 'users/:username/activity/:filter' => 'users#show', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "user_preferences" => "users#user_preferences_redirect"
|
||||
get "users/:username/private-messages" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "users/:username/private-messages/:filter" => "user_actions#private_messages", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "users/:username" => "users#show", as: 'userpage', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
put "users/:username" => "users#update", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "users/:username/preferences" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT}, as: :email_preferences
|
||||
get "users/:username/preferences/email" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
put "users/:username/preferences/email" => "users#change_email", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "users/:username/preferences/about-me" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "users/:username/preferences/username" => "users#preferences", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
put "users/:username/preferences/username" => "users#username", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "users/:username/avatar(/:size)" => "users#avatar", constraints: {username: USERNAME_ROUTE_FORMAT} # LEGACY ROUTE
|
||||
post "users/:username/preferences/avatar" => "users#upload_avatar", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
put "users/:username/preferences/avatar/toggle" => "users#toggle_avatar", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "users/:username/invited" => "users#invited", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
post "users/:username/send_activation_email" => "users#send_activation_email", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "users/:username/activity" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "users/:username/activity/:filter" => "users#show", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
|
||||
get 'uploads/:site/:id/:sha.:extension' => 'uploads#show', constraints: {site: /\w+/, id: /\d+/, sha: /[a-z0-9]{15,16}/i, extension: /\w{2,}/}
|
||||
post 'uploads' => 'uploads#create'
|
||||
get "uploads/:site/:id/:sha.:extension" => "uploads#show", constraints: {site: /\w+/, id: /\d+/, sha: /[a-z0-9]{15,16}/i, extension: /\w{2,}/}
|
||||
post "uploads" => "uploads#create"
|
||||
|
||||
get "posts/by_number/:topic_id/:post_number" => "posts#by_number"
|
||||
get "posts/:id/reply-history" => "posts#reply_history"
|
||||
|
||||
get 'posts/by_number/:topic_id/:post_number' => 'posts#by_number'
|
||||
get 'posts/:id/reply-history' => 'posts#reply_history'
|
||||
resources :posts do
|
||||
put 'bookmark'
|
||||
get 'replies'
|
||||
get 'revisions/:revision' => 'posts#revisions'
|
||||
put 'recover'
|
||||
put "bookmark"
|
||||
get "replies"
|
||||
get "revisions/:revision" => "posts#revisions"
|
||||
put "recover"
|
||||
collection do
|
||||
delete 'destroy_many'
|
||||
delete "destroy_many"
|
||||
end
|
||||
end
|
||||
|
||||
get 'p/:post_id/:user_id' => 'posts#short_link'
|
||||
get "p/:post_id/:user_id" => "posts#short_link"
|
||||
|
||||
resources :notifications
|
||||
|
||||
|
@ -180,38 +181,38 @@ Discourse::Application.routes.draw do
|
|||
|
||||
resources :clicks do
|
||||
collection do
|
||||
get 'track'
|
||||
get "track"
|
||||
end
|
||||
end
|
||||
|
||||
get 'excerpt' => 'excerpt#show'
|
||||
get "excerpt" => "excerpt#show"
|
||||
|
||||
resources :post_actions do
|
||||
collection do
|
||||
get 'users'
|
||||
post 'clear_flags'
|
||||
get "users"
|
||||
post "clear_flags"
|
||||
end
|
||||
end
|
||||
resources :user_actions
|
||||
|
||||
resources :categories, :except => :show
|
||||
get 'category/:id/show' => 'categories#show'
|
||||
post 'category/:category_id/move' => 'categories#move', as: 'category_move'
|
||||
get "category/:id/show" => "categories#show"
|
||||
post "category/:category_id/move" => "categories#move", as: "category_move"
|
||||
|
||||
get 'category/:category.rss' => 'list#category_feed', format: :rss, as: 'category_feed'
|
||||
get 'category/:category' => 'list#category', as: 'category_list'
|
||||
get 'category/:category/none' => 'list#category_none', as: 'category_list_none'
|
||||
get 'category/:category/more' => 'list#category', as: 'category_list_more'
|
||||
get "category/:category.rss" => "list#category_feed", format: :rss, as: "category_feed"
|
||||
get "category/:category" => "list#category", as: "category_list"
|
||||
get "category/:category/none" => "list#category_none", as: "category_list_none"
|
||||
get "category/:category/more" => "list#category", as: "category_list_more"
|
||||
|
||||
# We've renamed popular to latest. If people access it we want a permanent redirect.
|
||||
get 'popular' => 'list#popular_redirect'
|
||||
get 'popular/more' => 'list#popular_redirect'
|
||||
# We"ve renamed popular to latest. If people access it we want a permanent redirect.
|
||||
get "popular" => "list#popular_redirect"
|
||||
get "popular/more" => "list#popular_redirect"
|
||||
|
||||
[:latest, :hot].each do |filter|
|
||||
Discourse.anonymous_filters.each do |filter|
|
||||
get "#{filter}.rss" => "list##{filter}_feed", format: :rss
|
||||
end
|
||||
|
||||
[:latest, :hot, :favorited, :read, :posted, :unread, :new].each do |filter|
|
||||
Discourse.filters.each do |filter|
|
||||
get "#{filter}" => "list##{filter}"
|
||||
get "#{filter}/more" => "list##{filter}"
|
||||
|
||||
|
@ -221,71 +222,75 @@ Discourse::Application.routes.draw do
|
|||
get "category/:parent_category/:category/l/#{filter}/more" => "list##{filter}"
|
||||
end
|
||||
|
||||
get 'category/:parent_category/:category' => 'list#category', as: 'category_list_parent'
|
||||
get "top" => "list#top"
|
||||
get "category/:category/l/top" => "list#top"
|
||||
get "category/:parent_category/:category/l/top" => "list#top"
|
||||
|
||||
get 'search' => 'search#query'
|
||||
get "category/:parent_category/:category" => "list#category", as: "category_list_parent"
|
||||
|
||||
get "search" => "search#query"
|
||||
|
||||
# Topics resource
|
||||
get 't/:id' => 'topics#show'
|
||||
delete 't/:id' => 'topics#destroy'
|
||||
put 't/:id' => 'topics#update'
|
||||
post 't' => 'topics#create'
|
||||
post 'topics/timings'
|
||||
get 'topics/similar_to'
|
||||
get 'topics/created-by/:username' => 'list#topics_by', as: 'topics_by', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get 'topics/private-messages/:username' => 'list#private_messages', as: 'topics_private_messages', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get 'topics/private-messages-sent/:username' => 'list#private_messages_sent', as: 'topics_private_messages_sent', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get 'topics/private-messages-unread/:username' => 'list#private_messages_unread', as: 'topics_private_messages_unread', constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "t/:id" => "topics#show"
|
||||
delete "t/:id" => "topics#destroy"
|
||||
put "t/:id" => "topics#update"
|
||||
post "t" => "topics#create"
|
||||
post "topics/timings"
|
||||
get "topics/similar_to"
|
||||
get "topics/created-by/:username" => "list#topics_by", as: "topics_by", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "topics/private-messages/:username" => "list#private_messages", as: "topics_private_messages", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "topics/private-messages-sent/:username" => "list#private_messages_sent", as: "topics_private_messages_sent", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
get "topics/private-messages-unread/:username" => "list#private_messages_unread", as: "topics_private_messages_unread", constraints: {username: USERNAME_ROUTE_FORMAT}
|
||||
|
||||
# Topic routes
|
||||
get 't/:slug/:topic_id/wordpress' => 'topics#wordpress', constraints: {topic_id: /\d+/}
|
||||
get 't/:slug/:topic_id/moderator-liked' => 'topics#moderator_liked', constraints: {topic_id: /\d+/}
|
||||
get 't/:topic_id/wordpress' => 'topics#wordpress', constraints: {topic_id: /\d+/}
|
||||
get 't/:slug/:topic_id/summary' => 'topics#show', defaults: {summary: true}, constraints: {topic_id: /\d+/, post_number: /\d+/}
|
||||
get 't/:topic_id/summary' => 'topics#show', constraints: {topic_id: /\d+/, post_number: /\d+/}
|
||||
put 't/:slug/:topic_id' => 'topics#update', constraints: {topic_id: /\d+/}
|
||||
put 't/:slug/:topic_id/star' => 'topics#star', constraints: {topic_id: /\d+/}
|
||||
put 't/:topic_id/star' => 'topics#star', constraints: {topic_id: /\d+/}
|
||||
put 't/:slug/:topic_id/status' => 'topics#status', constraints: {topic_id: /\d+/}
|
||||
put 't/:topic_id/status' => 'topics#status', constraints: {topic_id: /\d+/}
|
||||
put 't/:topic_id/clear-pin' => 'topics#clear_pin', constraints: {topic_id: /\d+/}
|
||||
put 't/:topic_id/mute' => 'topics#mute', constraints: {topic_id: /\d+/}
|
||||
put 't/:topic_id/unmute' => 'topics#unmute', constraints: {topic_id: /\d+/}
|
||||
put 't/:topic_id/autoclose' => 'topics#autoclose', constraints: {topic_id: /\d+/}
|
||||
put 't/:topic_id/remove-allowed-user' => 'topics#remove_allowed_user', constraints: {topic_id: /\d+/}
|
||||
put 't/:topic_id/recover' => 'topics#recover', constraints: {topic_id: /\d+/}
|
||||
get 't/:topic_id/:post_number' => 'topics#show', constraints: {topic_id: /\d+/, post_number: /\d+/}
|
||||
get 't/:slug/:topic_id.rss' => 'topics#feed', format: :rss, constraints: {topic_id: /\d+/}
|
||||
get 't/:slug/:topic_id' => 'topics#show', constraints: {topic_id: /\d+/}
|
||||
get 't/:slug/:topic_id/:post_number' => 'topics#show', constraints: {topic_id: /\d+/, post_number: /\d+/}
|
||||
get 't/:topic_id/posts' => 'topics#posts', constraints: {topic_id: /\d+/}
|
||||
post 't/:topic_id/timings' => 'topics#timings', constraints: {topic_id: /\d+/}
|
||||
post 't/:topic_id/invite' => 'topics#invite', constraints: {topic_id: /\d+/}
|
||||
post 't/:topic_id/move-posts' => 'topics#move_posts', constraints: {topic_id: /\d+/}
|
||||
post 't/:topic_id/merge-topic' => 'topics#merge_topic', constraints: {topic_id: /\d+/}
|
||||
delete 't/:topic_id/timings' => 'topics#destroy_timings', constraints: {topic_id: /\d+/}
|
||||
get "t/:slug/:topic_id/wordpress" => "topics#wordpress", constraints: {topic_id: /\d+/}
|
||||
get "t/:slug/:topic_id/moderator-liked" => "topics#moderator_liked", constraints: {topic_id: /\d+/}
|
||||
get "t/:topic_id/wordpress" => "topics#wordpress", constraints: {topic_id: /\d+/}
|
||||
get "t/:slug/:topic_id/summary" => "topics#show", defaults: {summary: true}, constraints: {topic_id: /\d+/, post_number: /\d+/}
|
||||
get "t/:topic_id/summary" => "topics#show", constraints: {topic_id: /\d+/, post_number: /\d+/}
|
||||
put "t/:slug/:topic_id" => "topics#update", constraints: {topic_id: /\d+/}
|
||||
put "t/:slug/:topic_id/star" => "topics#star", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/star" => "topics#star", constraints: {topic_id: /\d+/}
|
||||
put "t/:slug/:topic_id/status" => "topics#status", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/status" => "topics#status", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/clear-pin" => "topics#clear_pin", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/mute" => "topics#mute", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/unmute" => "topics#unmute", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/autoclose" => "topics#autoclose", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/remove-allowed-user" => "topics#remove_allowed_user", constraints: {topic_id: /\d+/}
|
||||
put "t/:topic_id/recover" => "topics#recover", constraints: {topic_id: /\d+/}
|
||||
get "t/:topic_id/:post_number" => "topics#show", constraints: {topic_id: /\d+/, post_number: /\d+/}
|
||||
get "t/:slug/:topic_id.rss" => "topics#feed", format: :rss, constraints: {topic_id: /\d+/}
|
||||
get "t/:slug/:topic_id" => "topics#show", constraints: {topic_id: /\d+/}
|
||||
get "t/:slug/:topic_id/:post_number" => "topics#show", constraints: {topic_id: /\d+/, post_number: /\d+/}
|
||||
get "t/:topic_id/posts" => "topics#posts", constraints: {topic_id: /\d+/}
|
||||
post "t/:topic_id/timings" => "topics#timings", constraints: {topic_id: /\d+/}
|
||||
post "t/:topic_id/invite" => "topics#invite", constraints: {topic_id: /\d+/}
|
||||
post "t/:topic_id/move-posts" => "topics#move_posts", constraints: {topic_id: /\d+/}
|
||||
post "t/:topic_id/merge-topic" => "topics#merge_topic", constraints: {topic_id: /\d+/}
|
||||
delete "t/:topic_id/timings" => "topics#destroy_timings", constraints: {topic_id: /\d+/}
|
||||
|
||||
post 't/:topic_id/notifications' => 'topics#set_notifications' , constraints: {topic_id: /\d+/}
|
||||
post "t/:topic_id/notifications" => "topics#set_notifications" , constraints: {topic_id: /\d+/}
|
||||
|
||||
get 'raw/:topic_id(/:post_number)' => 'posts#markdown'
|
||||
get "raw/:topic_id(/:post_number)" => "posts#markdown"
|
||||
|
||||
|
||||
resources :invites
|
||||
delete 'invites' => 'invites#destroy'
|
||||
delete "invites" => "invites#destroy"
|
||||
|
||||
get 'onebox' => 'onebox#show'
|
||||
get "onebox" => "onebox#show"
|
||||
|
||||
get 'error' => 'forums#error'
|
||||
get "error" => "forums#error"
|
||||
|
||||
get 'message-bus/poll' => 'message_bus#poll'
|
||||
get "message-bus/poll" => "message_bus#poll"
|
||||
|
||||
get 'draft' => 'draft#show'
|
||||
post 'draft' => 'draft#update'
|
||||
delete 'draft' => 'draft#destroy'
|
||||
get "draft" => "draft#show"
|
||||
post "draft" => "draft#update"
|
||||
delete "draft" => "draft#destroy"
|
||||
|
||||
get 'robots.txt' => 'robots_txt#index'
|
||||
get "robots.txt" => "robots_txt#index"
|
||||
|
||||
[:latest, :hot, :unread, :new, :favorited, :read, :posted].each do |filter|
|
||||
Discourse.filters.each do |filter|
|
||||
root to: "list##{filter}", constraints: HomePageConstraint.new("#{filter}"), :as => "list_#{filter}"
|
||||
end
|
||||
# special case for categories
|
||||
|
|
|
@ -59,6 +59,8 @@ basic:
|
|||
relative_date_duration:
|
||||
client: true
|
||||
default: 30
|
||||
topics_per_period_in_summary:
|
||||
default: 10
|
||||
|
||||
users:
|
||||
enable_local_logins:
|
||||
|
|
23
db/migrate/20131223171005_create_top_topics.rb
Normal file
23
db/migrate/20131223171005_create_top_topics.rb
Normal file
|
@ -0,0 +1,23 @@
|
|||
class CreateTopTopics < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :top_topics do |t|
|
||||
t.belongs_to :topic
|
||||
|
||||
TopTopic.periods.each do |period|
|
||||
TopTopic.sort_orders.each do |sort|
|
||||
t.integer "#{period}_#{sort}_count".to_sym, null: false, default: 0
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
add_index :top_topics, :topic_id, unique: true
|
||||
|
||||
TopTopic.periods.each do |period|
|
||||
TopTopic.sort_orders.each do |sort|
|
||||
add_index :top_topics, "#{period}_#{sort}_count".to_sym, order: 'desc'
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -30,11 +30,29 @@ module Discourse
|
|||
# Cross site request forgery
|
||||
class CSRF < Exception; end
|
||||
|
||||
def self.filters
|
||||
@filters ||= [:latest, :hot, :unread, :new, :favorited, :read, :posted]
|
||||
end
|
||||
|
||||
def self.anonymous_filters
|
||||
@anonymous_filters ||= [:latest, :hot]
|
||||
end
|
||||
|
||||
def self.logged_in_filters
|
||||
@logged_in_filters ||= Discourse.filters - Discourse.anonymous_filters
|
||||
end
|
||||
|
||||
def self.top_menu_items
|
||||
@top_menu_items ||= Discourse.filters.concat([:category, :categories, :top])
|
||||
end
|
||||
|
||||
def self.anonymous_top_menu_items
|
||||
@anonymous_top_menu_items ||= Discourse.anonymous_filters.concat([:category, :categories, :top])
|
||||
end
|
||||
|
||||
def self.activate_plugins!
|
||||
@plugins = Plugin::Instance.find_all("#{Rails.root}/plugins")
|
||||
@plugins.each do |plugin|
|
||||
plugin.activate!
|
||||
end
|
||||
@plugins.each { |plugin| plugin.activate! }
|
||||
end
|
||||
|
||||
def self.plugins
|
||||
|
|
|
@ -88,7 +88,7 @@ module Oneboxer
|
|||
end
|
||||
|
||||
return nil unless @template
|
||||
Mustache.render(File.read("#{Rails.root}/lib/oneboxer/templates/discourse_#{@template}_onebox.hbrs"), args)
|
||||
Mustache.render(File.read("#{Rails.root}/lib/oneboxer/templates/discourse_#{@template}_onebox.handlebars"), args)
|
||||
rescue ActionController::RoutingError
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ module Oneboxer
|
|||
end
|
||||
|
||||
def self.template_path(template_name)
|
||||
"#{Rails.root}/lib/oneboxer/templates/#{template_name}.hbrs"
|
||||
"#{Rails.root}/lib/oneboxer/templates/#{template_name}.handlebars"
|
||||
end
|
||||
|
||||
def template_path(template_name)
|
||||
|
|
|
@ -91,8 +91,8 @@ module PrettyText
|
|||
end
|
||||
end
|
||||
|
||||
ctx['quoteTemplate'] = File.open(app_root + 'app/assets/javascripts/discourse/templates/quote.js.shbrs') {|f| f.read}
|
||||
ctx['quoteEmailTemplate'] = File.open(app_root + 'lib/assets/quote_email.js.shbrs') {|f| f.read}
|
||||
ctx['quoteTemplate'] = File.open(app_root + 'app/assets/javascripts/discourse/templates/quote.js.handlebars') {|f| f.read}
|
||||
ctx['quoteEmailTemplate'] = File.open(app_root + 'lib/assets/quote_email.js.handlebars') {|f| f.read}
|
||||
ctx.eval("HANDLEBARS_TEMPLATES = {
|
||||
'quote': Handlebars.compile(quoteTemplate),
|
||||
'quote_email': Handlebars.compile(quoteEmailTemplate),
|
||||
|
|
|
@ -8,7 +8,7 @@ require_dependency 'topic_query_sql'
|
|||
|
||||
class TopicQuery
|
||||
# Could be rewritten to %i if Ruby 1.9 is no longer supported
|
||||
VALID_OPTIONS = %w(except_topic_id
|
||||
VALID_OPTIONS = %w(except_topic_ids
|
||||
exclude_category
|
||||
limit
|
||||
page
|
||||
|
@ -84,6 +84,12 @@ class TopicQuery
|
|||
create_list(:posted) {|l| l.where('tu.user_id IS NOT NULL') }
|
||||
end
|
||||
|
||||
def list_top(sort_order, period)
|
||||
create_list(:top, unordered: true) do |topics|
|
||||
topics.joins(:top_topic).order("top_topics.#{period}_#{sort_order}_count DESC, topics.bumped_at DESC")
|
||||
end
|
||||
end
|
||||
|
||||
def list_topics_by(user)
|
||||
create_list(:user_topics) do |topics|
|
||||
topics.where(user_id: user.id)
|
||||
|
@ -227,7 +233,7 @@ class TopicQuery
|
|||
|
||||
result = result.limit(options[:per_page]) unless options[:limit] == false
|
||||
result = result.visible if options[:visible] || @user.nil? || @user.regular?
|
||||
result = result.where('topics.id <> ?', options[:except_topic_id]).references(:topics) if options[:except_topic_id]
|
||||
result = result.where.not(topics: {id: options[:except_topic_ids]}).references(:topics) if options[:except_topic_ids]
|
||||
result = result.offset(options[:page].to_i * options[:per_page]) if options[:page]
|
||||
|
||||
if options[:topic_ids]
|
||||
|
|
|
@ -6,8 +6,7 @@ module TopicQuerySQL
|
|||
class << self
|
||||
|
||||
# use the constants in conjuction with COALESCE to determine the order with regard to pinned
|
||||
# topics that have been cleared by the user. There
|
||||
# might be a cleaner way to do this.
|
||||
# topics that have been cleared by the user. There might be a cleaner way to do this.
|
||||
def lowest_date
|
||||
"2010-01-01"
|
||||
end
|
||||
|
|
|
@ -13,14 +13,14 @@ describe ListController do
|
|||
|
||||
describe 'indexes' do
|
||||
|
||||
[:latest, :hot].each do |filter|
|
||||
Discourse.anonymous_filters.each do |filter|
|
||||
context "#{filter}" do
|
||||
before { xhr :get, filter }
|
||||
it { should respond_with(:success) }
|
||||
end
|
||||
end
|
||||
|
||||
[:favorited, :read, :posted, :unread, :new].each do |filter|
|
||||
Discourse.logged_in_filters.each do |filter|
|
||||
context "#{filter}" do
|
||||
it { expect { xhr :get, filter }.to raise_error(Discourse::NotLoggedIn) }
|
||||
end
|
||||
|
@ -39,7 +39,7 @@ describe ListController do
|
|||
|
||||
describe 'RSS feeds' do
|
||||
|
||||
[:latest, :hot].each do |filter|
|
||||
Discourse.anonymous_filters.each do |filter|
|
||||
|
||||
it 'renders RSS' do
|
||||
get "#{filter}_feed", format: :rss
|
||||
|
@ -175,14 +175,6 @@ describe ListController do
|
|||
end
|
||||
end
|
||||
|
||||
context 'hot' do
|
||||
before do
|
||||
xhr :get, :hot
|
||||
end
|
||||
|
||||
it { should respond_with(:success) }
|
||||
end
|
||||
|
||||
context 'favorited' do
|
||||
it 'raises an error when not logged in' do
|
||||
lambda { xhr :get, :favorited }.should raise_error(Discourse::NotLoggedIn)
|
||||
|
|
30
spec/models/top_topic_spec.rb
Normal file
30
spec/models/top_topic_spec.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe TopTopic do
|
||||
|
||||
it { should belong_to :topic }
|
||||
|
||||
context "refresh!" do
|
||||
|
||||
let!(:t1) { Fabricate(:topic) }
|
||||
let!(:t2) { Fabricate(:topic) }
|
||||
|
||||
it "begins blank" do
|
||||
TopTopic.all.should be_blank
|
||||
end
|
||||
|
||||
context "after calculating" do
|
||||
|
||||
before do
|
||||
TopTopic.refresh!
|
||||
end
|
||||
|
||||
it "should have top topics" do
|
||||
TopTopic.pluck(:topic_id).should =~ [t1.id, t2.id]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -1,10 +0,0 @@
|
|||
require 'sprockets'
|
||||
require 'sprockets/engines'
|
||||
require 'simple_handlebars_rails/simple_handlebars_template'
|
||||
|
||||
module SimpleHandlebarsRails
|
||||
class Engine < Rails::Engine
|
||||
end
|
||||
|
||||
Sprockets.register_engine '.shbrs', SimpleHandlebarsTemplate
|
||||
end
|
|
@ -1,38 +0,0 @@
|
|||
require 'tilt/template'
|
||||
|
||||
module SimpleHandlebarsRails
|
||||
|
||||
# = Sprockets engine for MustacheTemplate templates
|
||||
class SimpleHandlebarsTemplate < Tilt::Template
|
||||
def self.default_mime_type
|
||||
'application/javascript'
|
||||
end
|
||||
|
||||
def initialize_engine
|
||||
end
|
||||
|
||||
def prepare
|
||||
end
|
||||
|
||||
# Generates Javascript code from a HandlebarsJS template.
|
||||
# The SC template name is derived from the lowercase logical asset path
|
||||
# by replacing non-alphanum characheters by underscores.
|
||||
def evaluate(scope, locals, &block)
|
||||
|
||||
template = data.dup
|
||||
template.gsub!(/"/, '\\"')
|
||||
template.gsub!(/\r?\n/, '\\n')
|
||||
template.gsub!(/\t/, '\\t')
|
||||
|
||||
# TODO: make this an option
|
||||
templateName = scope.logical_path.downcase.gsub(/[^a-z0-9\/]/, '_')
|
||||
templateName.gsub!(/^discourse\/templates\//, '')
|
||||
|
||||
# TODO precompile so we can just have handlebars-runtime in prd
|
||||
|
||||
result = "if (typeof HANDLEBARS_TEMPLATES == 'undefined') HANDLEBARS_TEMPLATES = {};\n"
|
||||
result << "HANDLEBARS_TEMPLATES[\"#{templateName}\"] = Handlebars.compile(\"#{template}\");\n"
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,17 +0,0 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "simple_handlebars_rails"
|
||||
s.version = "0.0.1"
|
||||
s.authors = ["Robin Ward"]
|
||||
s.email = ["robin.ward@gmail.com"]
|
||||
s.homepage = ""
|
||||
s.summary = %q{Basic Mustache Support for Rails}
|
||||
s.description = %q{Adds the Mustache plugin and a corresponding Sprockets engine to the asset pipeline in Rails applications.}
|
||||
|
||||
s.add_dependency "rails", ["> 3.1"]
|
||||
|
||||
s.files = Dir["lib/**/*"]
|
||||
s.require_paths = ["lib"]
|
||||
end
|
Loading…
Reference in a new issue