add top page

This commit is contained in:
Régis Hanol 2013-12-24 00:50:36 +01:00
parent b90e811825
commit 567d2bd23c
57 changed files with 532 additions and 324 deletions

View file

@ -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

View file

@ -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

View file

@ -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'});
},

View file

@ -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) %>
});

View file

@ -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({

View 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;
});
}
});

View file

@ -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);
});
}

View file

@ -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];

View file

@ -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() {

View file

@ -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() {

View file

@ -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 });
});

View 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);
}
});

View file

@ -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' });
},

View file

@ -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>

View file

@ -60,7 +60,4 @@ Discourse.ListTopicsView = Discourse.View.extend(Discourse.LoadMore, {
this.saveScrollPosition();
}
});

View file

@ -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();

View file

@ -454,6 +454,9 @@
#list-area {
margin-bottom: 300px;
h2 {
margin: 20px 0 10px;
}
.topic-statuses .topic-status i {font-size: 15px;}

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
View 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

View file

@ -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

View file

@ -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?

View 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

View file

@ -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
View file

@ -0,0 +1 @@
<p><%= t 'powered_by_html' %></p>

View file

@ -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>.'

View file

@ -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."

View file

@ -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

View file

@ -59,6 +59,8 @@ basic:
relative_date_duration:
client: true
default: 30
topics_per_period_in_summary:
default: 10
users:
enable_local_logins:

View 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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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),

View file

@ -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]

View file

@ -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

View file

@ -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)

View 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

View file

@ -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

View file

@ -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

View file

@ -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