Merge branch 'master' of github.com:discourse/discourse
This commit is contained in:
commit
ad88487a32
|
@ -10,7 +10,7 @@ on Discourse with:
|
|||
1. Install VirtualBox: https://www.virtualbox.org/wiki/Downloads
|
||||
2. Install Vagrant: https://www.vagrantup.com/
|
||||
3. Open a terminal
|
||||
4. Clone the project: `git@github.com:discourse/discourse.git`
|
||||
4. Clone the project: `git clone git@github.com:discourse/discourse.git`
|
||||
5. Enter the project directory: `cd discourse`
|
||||
|
||||
### Using Vagrant
|
||||
|
@ -51,7 +51,7 @@ bundle exec rails server
|
|||
|
||||
In a few seconds, rails will start server pages. To access them, open a web browser to http://localhost:4000 - if it all worked you should see discourse! Congratulations, you are ready to start working!
|
||||
|
||||
You can now edit files on your local file system, using your favorite text editor or IDE. When you reload your web browser, it should have the latest changed.
|
||||
You can now edit files on your local file system, using your favorite text editor or IDE. When you reload your web browser, it should have the latest changes.
|
||||
|
||||
### Guard + Rspec
|
||||
|
||||
|
@ -71,6 +71,12 @@ Wait a minute while it runs all our unit tests. Once it has completed, live relo
|
|||
|
||||
### Sending Email
|
||||
|
||||
Mail is sent asynchronously by Sidekiq, so you'll need to have sidekiq running to process jobs. Run it with this command:
|
||||
|
||||
```
|
||||
bundle exec sidekiq
|
||||
```
|
||||
|
||||
Mailcatcher is used to avoid the whole issue of actually sending emails: https://github.com/sj26/mailcatcher
|
||||
|
||||
To start mailcatcher, run the following command in the vagrant image:
|
||||
|
|
58
Gemfile.lock
58
Gemfile.lock
|
@ -63,34 +63,34 @@ PATH
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (3.2.11)
|
||||
actionpack (= 3.2.11)
|
||||
actionmailer (3.2.12)
|
||||
actionpack (= 3.2.12)
|
||||
mail (~> 2.4.4)
|
||||
actionpack (3.2.11)
|
||||
activemodel (= 3.2.11)
|
||||
activesupport (= 3.2.11)
|
||||
actionpack (3.2.12)
|
||||
activemodel (= 3.2.12)
|
||||
activesupport (= 3.2.12)
|
||||
builder (~> 3.0.0)
|
||||
erubis (~> 2.7.0)
|
||||
journey (~> 1.0.4)
|
||||
rack (~> 1.4.0)
|
||||
rack (~> 1.4.5)
|
||||
rack-cache (~> 1.2)
|
||||
rack-test (~> 0.6.1)
|
||||
sprockets (~> 2.2.1)
|
||||
activemodel (3.2.11)
|
||||
activesupport (= 3.2.11)
|
||||
activemodel (3.2.12)
|
||||
activesupport (= 3.2.12)
|
||||
builder (~> 3.0.0)
|
||||
activerecord (3.2.11)
|
||||
activemodel (= 3.2.11)
|
||||
activesupport (= 3.2.11)
|
||||
activerecord (3.2.12)
|
||||
activemodel (= 3.2.12)
|
||||
activesupport (= 3.2.12)
|
||||
arel (~> 3.0.2)
|
||||
tzinfo (~> 0.3.29)
|
||||
activerecord-postgres-hstore (0.7.1)
|
||||
rails
|
||||
rake
|
||||
activeresource (3.2.11)
|
||||
activemodel (= 3.2.11)
|
||||
activesupport (= 3.2.11)
|
||||
activesupport (3.2.11)
|
||||
activeresource (3.2.12)
|
||||
activemodel (= 3.2.12)
|
||||
activesupport (= 3.2.12)
|
||||
activesupport (3.2.12)
|
||||
i18n (~> 0.6)
|
||||
multi_json (~> 1.0)
|
||||
acts_as_paranoid (0.4.1)
|
||||
|
@ -194,7 +194,7 @@ GEM
|
|||
jquery-rails (2.2.0)
|
||||
railties (>= 3.0, < 5.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (1.7.6)
|
||||
json (1.7.7)
|
||||
koala (1.6.0)
|
||||
addressable (~> 2.2)
|
||||
faraday (~> 0.8)
|
||||
|
@ -208,10 +208,10 @@ GEM
|
|||
treetop (~> 1.4.8)
|
||||
metaclass (0.0.1)
|
||||
method_source (0.8.1)
|
||||
mime-types (1.20.1)
|
||||
mime-types (1.21)
|
||||
mocha (0.10.5)
|
||||
metaclass (~> 0.0.1)
|
||||
multi_json (1.5.0)
|
||||
multi_json (1.5.1)
|
||||
multipart-post (1.1.5)
|
||||
mustache (0.99.4)
|
||||
net-scp (1.0.4)
|
||||
|
@ -239,17 +239,17 @@ GEM
|
|||
rack
|
||||
rack-test (0.6.2)
|
||||
rack (>= 1.0)
|
||||
rails (3.2.11)
|
||||
actionmailer (= 3.2.11)
|
||||
actionpack (= 3.2.11)
|
||||
activerecord (= 3.2.11)
|
||||
activeresource (= 3.2.11)
|
||||
activesupport (= 3.2.11)
|
||||
rails (3.2.12)
|
||||
actionmailer (= 3.2.12)
|
||||
actionpack (= 3.2.12)
|
||||
activerecord (= 3.2.12)
|
||||
activeresource (= 3.2.12)
|
||||
activesupport (= 3.2.12)
|
||||
bundler (~> 1.0)
|
||||
railties (= 3.2.11)
|
||||
railties (3.2.11)
|
||||
actionpack (= 3.2.11)
|
||||
activesupport (= 3.2.11)
|
||||
railties (= 3.2.12)
|
||||
railties (3.2.12)
|
||||
actionpack (= 3.2.12)
|
||||
activesupport (= 3.2.12)
|
||||
rack-ssl (~> 1.3.2)
|
||||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
|
@ -258,7 +258,7 @@ GEM
|
|||
rb-fsevent (0.9.3)
|
||||
rb-inotify (0.8.8)
|
||||
ffi (>= 0.5.0)
|
||||
rdoc (3.12)
|
||||
rdoc (3.12.1)
|
||||
json (~> 1.4)
|
||||
redis (3.0.2)
|
||||
redis-actionpack (3.2.3)
|
||||
|
|
|
@ -84,9 +84,11 @@
|
|||
ul = div.find('ul')
|
||||
selectedOption = 0
|
||||
markSelected()
|
||||
|
||||
ul.find('li').click ->
|
||||
selectedOption = ul.find('li').index(this)
|
||||
completeTerm(autocompleteOptions[selectedOption])
|
||||
false
|
||||
|
||||
pos = null
|
||||
if isInput
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<h3><i class='icon icon-lock'></i> {{i18n private_message_info.title}}</h3>
|
||||
<p>{{{i18n private_message_info.description}}}</p>
|
||||
<div class='participants clearfix'>
|
||||
{{#each content.allowed_users}}
|
||||
<div class='user'>
|
||||
|
|
|
@ -38,6 +38,7 @@ window.Discourse.TopicFooterButtonsView = Ember.ContainerView.extend
|
|||
|
||||
@addObject Discourse.ButtonView.createWithMixins
|
||||
classNames: ['btn', 'btn-primary', 'create']
|
||||
attributeBindings: ['disabled']
|
||||
text: (->
|
||||
archetype = @get('controller.content.archetype')
|
||||
return customTitle if customTitle = @get("parentView.replyButtonText#{archetype.capitalize()}")
|
||||
|
@ -46,6 +47,7 @@ window.Discourse.TopicFooterButtonsView = Ember.ContainerView.extend
|
|||
renderIcon: (buffer) -> buffer.push("<i class='icon icon-plus'></i>")
|
||||
click: -> @get('controller').reply()
|
||||
helpKey: 'topic.reply.help'
|
||||
disabled: !@get('controller.content.can_create_post')
|
||||
|
||||
unless topic.get('isPrivateMessage')
|
||||
@addObject Discourse.DropdownButtonView.createWithMixins
|
||||
|
|
|
@ -387,7 +387,7 @@ kbd {
|
|||
z-index: 1;
|
||||
}
|
||||
button {
|
||||
padding: 0;
|
||||
padding: 0 1px;
|
||||
cursor: pointer;
|
||||
z-index: 1000;
|
||||
position: absolute;
|
||||
|
|
|
@ -66,12 +66,12 @@ class ApplicationController < ActionController::Base
|
|||
# for now do a simple remap, we may look at cleaner ways of doing the render
|
||||
raise ActiveRecord::RecordNotFound
|
||||
else
|
||||
render file: 'public/404', layout: false, status: 404
|
||||
render file: 'public/404', formats: [:html], layout: false, status: 404
|
||||
end
|
||||
end
|
||||
|
||||
rescue_from Discourse::InvalidAccess do
|
||||
render file: 'public/403', layout: false, status: 403
|
||||
render file: 'public/403', formats: [:html], layout: false, status: 403
|
||||
end
|
||||
|
||||
def store_preloaded(key, json)
|
||||
|
|
|
@ -22,9 +22,14 @@ class SessionController < ApplicationController
|
|||
|
||||
# If their password is correct
|
||||
if @user.confirm_password?(params[:password])
|
||||
log_on_user(@user)
|
||||
render_serialized(@user, UserSerializer)
|
||||
return
|
||||
if @user.email_confirmed?
|
||||
log_on_user(@user)
|
||||
render_serialized(@user, UserSerializer)
|
||||
return
|
||||
else
|
||||
render :json => {error: I18n.t("login.not_activated")}
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
module CommonHelper
|
||||
def render_google_analytics_code
|
||||
if Rails.env == "production" && SiteSetting.ga_tracking_code.present?
|
||||
render :partial => "common/google_analytics"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
module TopicsHelper
|
||||
|
||||
def render_topic_title(topic)
|
||||
link_to(topic.title,topic.relative_url)
|
||||
end
|
||||
|
||||
def render_topic_next_page_link(topic, next_page)
|
||||
link_to("next page", "#{topic.relative_url}?page=#{next_page}")
|
||||
end
|
||||
end
|
|
@ -342,19 +342,21 @@ class Post < ActiveRecord::Base
|
|||
# This calculates the geometric mean of the post timings and stores it along with
|
||||
# each post.
|
||||
def self.calculate_avg_time
|
||||
exec_sql("UPDATE posts
|
||||
SET avg_time = (x.gmean / 1000)
|
||||
FROM (SELECT post_timings.topic_id,
|
||||
post_timings.post_number,
|
||||
round(exp(avg(ln(msecs)))) AS gmean
|
||||
FROM post_timings
|
||||
INNER JOIN posts AS p2
|
||||
ON p2.post_number = post_timings.post_number
|
||||
AND p2.topic_id = post_timings.topic_id
|
||||
AND p2.user_id <> post_timings.user_id
|
||||
GROUP BY post_timings.topic_id, post_timings.post_number) AS x
|
||||
WHERE x.topic_id = posts.topic_id
|
||||
AND x.post_number = posts.post_number")
|
||||
retry_lock_error do
|
||||
exec_sql("UPDATE posts
|
||||
SET avg_time = (x.gmean / 1000)
|
||||
FROM (SELECT post_timings.topic_id,
|
||||
post_timings.post_number,
|
||||
round(exp(avg(ln(msecs)))) AS gmean
|
||||
FROM post_timings
|
||||
INNER JOIN posts AS p2
|
||||
ON p2.post_number = post_timings.post_number
|
||||
AND p2.topic_id = post_timings.topic_id
|
||||
AND p2.user_id <> post_timings.user_id
|
||||
GROUP BY post_timings.topic_id, post_timings.post_number) AS x
|
||||
WHERE x.topic_id = posts.topic_id
|
||||
AND x.post_number = posts.post_number")
|
||||
end
|
||||
end
|
||||
|
||||
before_save do
|
||||
|
|
|
@ -47,6 +47,10 @@ class TopicLink < ActiveRecord::Base
|
|||
internal = true
|
||||
|
||||
route = Rails.application.routes.recognize_path(parsed.path)
|
||||
|
||||
# We aren't interested in tracking internal links to users
|
||||
next if route[:controller] == 'users'
|
||||
|
||||
topic_id = route[:topic_id]
|
||||
post_number = route[:post_number] || 1
|
||||
end
|
||||
|
|
|
@ -380,6 +380,10 @@ class User < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def email_confirmed?
|
||||
email_tokens.where(email: self.email, confirmed: true).present?
|
||||
end
|
||||
|
||||
|
||||
protected
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<%- if mini_profiler_enabled? %>
|
||||
<%- Rack::MiniProfiler.step "application" do %>
|
||||
<%= javascript_include_tag "application" %>
|
||||
<%-end%>
|
||||
|
||||
<%- Rack::MiniProfiler.step "admin" do %>
|
||||
<%= javascript_include_tag "admin"%>
|
||||
<%-end%>
|
||||
<%- else %>
|
||||
<%= javascript_include_tag "application" %>
|
||||
<%- if admin? %>
|
||||
<%= javascript_include_tag "admin"%>
|
||||
<%- end %>
|
||||
<%- end%>
|
||||
|
||||
<script>
|
||||
Discourse.CDN = '<%= Rails.configuration.action_controller.asset_host %>';
|
||||
Discourse.BaseUrl = '<%= RailsMultisite::ConnectionManagement.current_hostname %>';
|
||||
Discourse.Environment = '<%= Rails.env %>';
|
||||
window.Discourse.Router.map(function() {
|
||||
return Discourse.routeBuilder.call(this);
|
||||
});
|
||||
Discourse.start()
|
||||
Discourse.initialize()
|
||||
</script>
|
|
@ -0,0 +1,12 @@
|
|||
<%- unless SiteCustomization.override_default_style(session[:preview_style]) %>
|
||||
<%=stylesheet_link_tag "application"%>
|
||||
<%- end %>
|
||||
|
||||
<%- if mini_profiler_enabled? %>
|
||||
<%- Rack::MiniProfiler.step "stylsheet" do%>
|
||||
<%= stylesheet_link_tag "admin"%>
|
||||
<%-end%>
|
||||
<%- elsif admin? %>
|
||||
<%= stylesheet_link_tag "admin"%>
|
||||
<%-end%>
|
||||
<%=SiteCustomization.custom_stylesheet(session[:preview_style])%>
|
|
@ -0,0 +1,14 @@
|
|||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', '<%= SiteSetting.ga_tracking_code %>']);
|
||||
_gaq.push(['_setCustomVar', 1, 'Anonymous', <%= !current_user %>, 2]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
<%#
|
||||
The fonts are loaded outside of the stylesheet so that we can dynamically change
|
||||
the path. This is to get around CDN caching on the Origin:
|
||||
|
||||
https://forums.aws.amazon.com/thread.jspa?threadID=114646
|
||||
%>
|
||||
|
||||
<% font_domain = "#{request.protocol}#{request.host_with_port}" %>
|
||||
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
src: url('<%=asset_path "fontawesome-webfont.eot" %>?<%= font_domain %>');
|
||||
src: url('<%=asset_path "fontawesome-webfont.eot" %>?<%= font_domain %>#iefix') format('embedded-opentype'),
|
||||
url('<%=asset_path "fontawesome-webfont.woff" %>?<%= font_domain %>') format('woff'),
|
||||
url('<%=asset_path "fontawesome-webfont.ttf" %>?<%= font_domain %>') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'zocial';
|
||||
src: url('/assets/zocial-regular-webfont.woff?<%= font_domain %>') format('woff'),
|
||||
url('/assets/zocial-regular-webfont.ttf?<%= font_domain %>') format('truetype'),
|
||||
url('/assets/zocial-regular-webfont.svg?<%= font_domain %>#zocialregular') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
|
||||
}
|
||||
</style>
|
|
@ -19,48 +19,11 @@
|
|||
<%= javascript_include_tag "preload_store" %>
|
||||
|
||||
|
||||
<%#
|
||||
The fonts are loaded outside of the stylesheet so that we can dynamically change
|
||||
the path. This is to get around CDN caching on the Origin:
|
||||
|
||||
https://forums.aws.amazon.com/thread.jspa?threadID=114646
|
||||
%>
|
||||
<%= render :partial => "common/special_font_face" %>
|
||||
<%= render :partial => "common/discourse_stylesheet" %>
|
||||
|
||||
<%- font_domain = "#{request.protocol}#{request.host_with_port}" %>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: 'FontAwesome';
|
||||
src: url('<%=asset_path "fontawesome-webfont.eot" %>?<%= font_domain %>');
|
||||
src: url('<%=asset_path "fontawesome-webfont.eot" %>?<%= font_domain %>#iefix') format('embedded-opentype'),
|
||||
url('<%=asset_path "fontawesome-webfont.woff" %>?<%= font_domain %>') format('woff'),
|
||||
url('<%=asset_path "fontawesome-webfont.ttf" %>?<%= font_domain %>') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'zocial';
|
||||
src: url('/assets/zocial-regular-webfont.woff?<%= font_domain %>') format('woff'),
|
||||
url('/assets/zocial-regular-webfont.ttf?<%= font_domain %>') format('truetype'),
|
||||
url('/assets/zocial-regular-webfont.svg?<%= font_domain %>#zocialregular') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
|
||||
}
|
||||
</style>
|
||||
|
||||
<%- unless SiteCustomization.override_default_style(session[:preview_style]) %>
|
||||
<%=stylesheet_link_tag "application"%>
|
||||
<%- end %>
|
||||
|
||||
<%- if mini_profiler_enabled? %>
|
||||
<%- Rack::MiniProfiler.step "stylsheet" do%>
|
||||
<%= stylesheet_link_tag "admin"%>
|
||||
<%-end%>
|
||||
<%- elsif admin? %>
|
||||
<%= stylesheet_link_tag "admin"%>
|
||||
<%-end%>
|
||||
<%=SiteCustomization.custom_stylesheet(session[:preview_style])%>
|
||||
<%=csrf_meta_tags%>
|
||||
</head>
|
||||
|
||||
|
@ -98,48 +61,8 @@
|
|||
|
||||
<footer id='bottom'></footer>
|
||||
|
||||
<%- if mini_profiler_enabled? %>
|
||||
<%- Rack::MiniProfiler.step "application" do %>
|
||||
<%= javascript_include_tag "application" %>
|
||||
<%-end%>
|
||||
|
||||
<%- Rack::MiniProfiler.step "admin" do %>
|
||||
<%= javascript_include_tag "admin"%>
|
||||
<%-end%>
|
||||
<%- else %>
|
||||
<%= javascript_include_tag "application" %>
|
||||
<%- if admin? %>
|
||||
<%= javascript_include_tag "admin"%>
|
||||
<%- end %>
|
||||
<%- end%>
|
||||
|
||||
<script>
|
||||
Discourse.CDN = '<%= Rails.configuration.action_controller.asset_host %>';
|
||||
Discourse.BaseUrl = '<%= RailsMultisite::ConnectionManagement.current_hostname %>';
|
||||
Discourse.Environment = '<%= Rails.env %>';
|
||||
window.Discourse.Router.map(function() {
|
||||
return Discourse.routeBuilder.call(this);
|
||||
});
|
||||
Discourse.start()
|
||||
Discourse.initialize()
|
||||
</script>
|
||||
|
||||
<%- if Rails.env == "production" and SiteSetting.ga_tracking_code.present? %>
|
||||
<script type="text/javascript">
|
||||
|
||||
var _gaq = _gaq || [];
|
||||
_gaq.push(['_setAccount', '<%= SiteSetting.ga_tracking_code %>']);
|
||||
_gaq.push(['_setCustomVar', 1, 'Anonymous', <%= !current_user %>, 2]);
|
||||
_gaq.push(['_trackPageview']);
|
||||
|
||||
(function() {
|
||||
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
|
||||
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
|
||||
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
|
||||
})();
|
||||
|
||||
</script>
|
||||
<%-end%>
|
||||
<%= render :partial => "common/discourse_javascript" %>
|
||||
<%= render_google_analytics_code %>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<h2>
|
||||
<a href="<%= @topic_view.topic.relative_url %>"><%= @topic_view.topic.title %></a>
|
||||
<%= render_topic_title(@topic_view.topic) %>
|
||||
|
||||
</h2>
|
||||
<% (@post ? [@post] : @topic_view.posts).each do |post| %>
|
||||
<div class='creator'>
|
||||
|
@ -12,7 +13,7 @@
|
|||
|
||||
<% if @next_page%>
|
||||
<p>
|
||||
<b><a href="<%= @topic_view.topic.relative_url %>?page=<%= @next_page%>">next page</a></b>
|
||||
<b><%= render_topic_next_page_link(@topic_view.topic, @next_page) %></b>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
|
|
|
@ -596,7 +596,6 @@ en:
|
|||
|
||||
private_message_info:
|
||||
title: "Private Conversation"
|
||||
description: "Participants in this private conversation"
|
||||
invite: "Invite Others..."
|
||||
|
||||
email: 'Email'
|
||||
|
@ -1039,6 +1038,7 @@ en:
|
|||
wait_approval: "Thanks for signing up. We will notify you when your account has been approved."
|
||||
active: "Your account is active and ready."
|
||||
activate_email: "You're almost done! We sent an activation email to <b>%{email}</b>. Please follow the instructions in the email to activate your account."
|
||||
not_activated: "You can't log in yet. We sent an activation email to you. Please follow the instructions in the email to activate your account."
|
||||
errors: "Failed to create account: %{errors}"
|
||||
not_available: "Not available. Try %{suggestion}?"
|
||||
|
||||
|
|
|
@ -21,9 +21,9 @@ module CurrentUser
|
|||
@not_logged_in = session[:current_user_id].blank?
|
||||
if @current_user
|
||||
@current_user.update_last_seen!
|
||||
if @current_user.ip_address != request.remote_ip
|
||||
if (@current_user.ip_address != request.remote_ip) and request.remote_ip.present?
|
||||
@current_user.ip_address = request.remote_ip
|
||||
User.exec_sql('update users set ip_address = ? where id = ?', request.remote_ip, @current_user.id)
|
||||
@current_user.update_column(:ip_address, request.remote_ip)
|
||||
end
|
||||
end
|
||||
@current_user
|
||||
|
|
|
@ -9,10 +9,21 @@ class DiscoursePluginRegistry
|
|||
attr_accessor :stylesheets
|
||||
end
|
||||
|
||||
def register_js(filename, options={})
|
||||
self.class.javascripts ||= Set.new
|
||||
self.class.server_side_javascripts ||= Set.new
|
||||
# Default accessor values
|
||||
#
|
||||
def self.stylesheets
|
||||
@stylesheets ||= Set.new
|
||||
end
|
||||
|
||||
def self.javascripts
|
||||
@javascripts ||= Set.new
|
||||
end
|
||||
|
||||
def self.server_side_javascripts
|
||||
@server_side_javascripts ||= Set.new
|
||||
end
|
||||
|
||||
def register_js(filename, options={})
|
||||
# If we have a server side option, add that too.
|
||||
self.class.server_side_javascripts << options[:server_side] if options[:server_side].present?
|
||||
|
||||
|
@ -20,12 +31,11 @@ class DiscoursePluginRegistry
|
|||
end
|
||||
|
||||
def register_css(filename)
|
||||
self.class.stylesheets ||= Set.new
|
||||
self.class.stylesheets << filename
|
||||
end
|
||||
|
||||
def stylesheets
|
||||
self.class.stylesheets || Set.new
|
||||
self.class.stylesheets
|
||||
end
|
||||
|
||||
def register_archetype(name, options={})
|
||||
|
@ -33,17 +43,17 @@ class DiscoursePluginRegistry
|
|||
end
|
||||
|
||||
def server_side_javascripts
|
||||
self.class.javascripts || Set.new
|
||||
self.class.javascripts
|
||||
end
|
||||
|
||||
def javascripts
|
||||
self.class.javascripts || Set.new
|
||||
self.class.javascripts
|
||||
end
|
||||
|
||||
def self.clear
|
||||
self.stylesheets = Set.new
|
||||
self.server_side_javascripts = Set.new
|
||||
self.javascripts = Set.new
|
||||
self.stylesheets = nil
|
||||
self.server_side_javascripts = nil
|
||||
self.javascripts = nil
|
||||
end
|
||||
|
||||
def self.setup(plugin_class)
|
||||
|
@ -52,6 +62,4 @@ class DiscoursePluginRegistry
|
|||
plugin.setup
|
||||
end
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
|
|
@ -15,6 +15,24 @@ class ActiveRecord::Base
|
|||
ActiveRecord::Base.exec_sql(*args)
|
||||
end
|
||||
|
||||
|
||||
# Executes the given block +retries+ times (or forever, if explicitly given nil),
|
||||
# catching and retrying SQL Deadlock errors.
|
||||
#
|
||||
# Thanks to: http://stackoverflow.com/a/7427186/165668
|
||||
#
|
||||
def self.retry_lock_error(retries=5, &block)
|
||||
begin
|
||||
yield
|
||||
rescue ActiveRecord::StatementInvalid => e
|
||||
if e.message =~ /Deadlock found when trying to get lock/ and (retries.nil? || retries > 0)
|
||||
retry_lock_error(retries ? retries - 1 : nil, &block)
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Support for psql. If we want to support multiple RDBMs in the future we can
|
||||
# split this.
|
||||
def exec_sql_row_count(*args)
|
||||
|
|
|
@ -4,6 +4,27 @@ require 'discourse_plugin_registry'
|
|||
describe DiscoursePluginRegistry do
|
||||
|
||||
let(:registry) { DiscoursePluginRegistry.new }
|
||||
|
||||
context '#stylesheets' do
|
||||
it 'defaults to an empty Set' do
|
||||
DiscoursePluginRegistry.stylesheets = nil
|
||||
DiscoursePluginRegistry.stylesheets.should == Set.new
|
||||
end
|
||||
end
|
||||
|
||||
context '#javascripts' do
|
||||
it 'defaults to an empty Set' do
|
||||
DiscoursePluginRegistry.javascripts = nil
|
||||
DiscoursePluginRegistry.javascripts.should == Set.new
|
||||
end
|
||||
end
|
||||
|
||||
context '#server_side_javascripts' do
|
||||
it 'defaults to an empty Set' do
|
||||
DiscoursePluginRegistry.server_side_javascripts = nil
|
||||
DiscoursePluginRegistry.server_side_javascripts.should == Set.new
|
||||
end
|
||||
end
|
||||
|
||||
context '.register_css' do
|
||||
before do
|
||||
|
|
|
@ -6,77 +6,93 @@ describe SessionController do
|
|||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
it "raises an error when the login isn't present" do
|
||||
lambda { xhr :post, :create }.should raise_error(Discourse::InvalidParameters)
|
||||
end
|
||||
|
||||
describe 'invalid password' do
|
||||
|
||||
it "should return an error with an invalid password" do
|
||||
xhr :post, :create, login: user.username, password: 'sssss'
|
||||
::JSON.parse(response.body)['error'].should be_present
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'success by username' do
|
||||
context 'when email is confirmed' do
|
||||
before do
|
||||
xhr :post, :create, login: user.username, password: 'myawesomepassword'
|
||||
user.reload
|
||||
token = user.email_tokens.where(email: user.email).first
|
||||
EmailToken.confirm(token.token)
|
||||
end
|
||||
|
||||
it 'sets a session id' do
|
||||
session[:current_user_id].should == user.id
|
||||
it "raises an error when the login isn't present" do
|
||||
lambda { xhr :post, :create }.should raise_error(Discourse::InvalidParameters)
|
||||
end
|
||||
|
||||
it 'gives the user an auth token' do
|
||||
user.auth_token.should be_present
|
||||
describe 'invalid password' do
|
||||
it "should return an error with an invalid password" do
|
||||
xhr :post, :create, login: user.username, password: 'sssss'
|
||||
::JSON.parse(response.body)['error'].should be_present
|
||||
end
|
||||
end
|
||||
|
||||
it 'sets a cookie with the auth token' do
|
||||
cookies[:_t].should == user.auth_token
|
||||
end
|
||||
end
|
||||
describe 'success by username' do
|
||||
before do
|
||||
xhr :post, :create, login: user.username, password: 'myawesomepassword'
|
||||
user.reload
|
||||
end
|
||||
|
||||
describe 'strips leading @ symbol' do
|
||||
before do
|
||||
xhr :post, :create, login: "@" + user.username, password: 'myawesomepassword'
|
||||
user.reload
|
||||
it 'sets a session id' do
|
||||
session[:current_user_id].should == user.id
|
||||
end
|
||||
|
||||
it 'gives the user an auth token' do
|
||||
user.auth_token.should be_present
|
||||
end
|
||||
|
||||
it 'sets a cookie with the auth token' do
|
||||
cookies[:_t].should == user.auth_token
|
||||
end
|
||||
end
|
||||
|
||||
it 'sets a session id' do
|
||||
session[:current_user_id].should == user.id
|
||||
end
|
||||
end
|
||||
describe 'strips leading @ symbol' do
|
||||
before do
|
||||
xhr :post, :create, login: "@" + user.username, password: 'myawesomepassword'
|
||||
user.reload
|
||||
end
|
||||
|
||||
describe 'also allow login by email' do
|
||||
before do
|
||||
xhr :post, :create, login: user.email, password: 'myawesomepassword'
|
||||
it 'sets a session id' do
|
||||
session[:current_user_id].should == user.id
|
||||
end
|
||||
end
|
||||
|
||||
it 'sets a session id' do
|
||||
session[:current_user_id].should == user.id
|
||||
end
|
||||
end
|
||||
|
||||
describe "when the site requires approval of users" do
|
||||
before do
|
||||
SiteSetting.expects(:must_approve_users?).returns(true)
|
||||
end
|
||||
|
||||
context 'with an unapproved user' do
|
||||
describe 'also allow login by email' do
|
||||
before do
|
||||
xhr :post, :create, login: user.email, password: 'myawesomepassword'
|
||||
end
|
||||
|
||||
it "doesn't log in the user" do
|
||||
session[:current_user_id].should be_blank
|
||||
it 'sets a session id' do
|
||||
session[:current_user_id].should == user.id
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "when the site requires approval of users" do
|
||||
before do
|
||||
SiteSetting.expects(:must_approve_users?).returns(true)
|
||||
end
|
||||
|
||||
context 'with an unapproved user' do
|
||||
before do
|
||||
xhr :post, :create, login: user.email, password: 'myawesomepassword'
|
||||
end
|
||||
|
||||
it "doesn't log in the user" do
|
||||
session[:current_user_id].should be_blank
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when email has not been confirmed' do
|
||||
before do
|
||||
xhr :post, :create, login: user.email, password: 'myawesomepassword'
|
||||
end
|
||||
|
||||
it "doesn't log in the user" do
|
||||
session[:current_user_id].should be_blank
|
||||
end
|
||||
|
||||
it 'returns an error message' do
|
||||
::JSON.parse(response.body)['error'].should be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.destroy' do
|
||||
|
|
|
@ -44,125 +44,139 @@ describe TopicLink do
|
|||
|
||||
end
|
||||
|
||||
describe 'domain-less link' do
|
||||
let(:post) { @topic.posts.create(user: @user, raw: "<a href='/users'>hello</a>") }
|
||||
let!(:link) do
|
||||
TopicLink.extract_from(post)
|
||||
@topic.topic_links.first
|
||||
end
|
||||
|
||||
it 'is extracted' do
|
||||
link.should be_present
|
||||
end
|
||||
|
||||
it 'has the correct domain' do
|
||||
link.domain.should == test_uri.host
|
||||
end
|
||||
|
||||
it "is not destroyed when we call extract from again" do
|
||||
TopicLink.extract_from(post)
|
||||
link.reload
|
||||
link.should be_present
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'internal links' do
|
||||
|
||||
before do
|
||||
@other_topic = Fabricate(:topic, user: @user)
|
||||
@other_post = @other_topic.posts.create(user: @user, raw: "some content")
|
||||
|
||||
@url = "http://#{test_uri.host}/t/topic-slug/#{@other_topic.id}"
|
||||
|
||||
@topic.posts.create(user: @user, raw: 'initial post')
|
||||
@post = @topic.posts.create(user: @user, raw: "Link to another topic: #{@url}")
|
||||
|
||||
TopicLink.extract_from(@post)
|
||||
|
||||
@link = @topic.topic_links.first
|
||||
end
|
||||
|
||||
it 'extracted the link' do
|
||||
@link.should be_present
|
||||
end
|
||||
|
||||
it 'is set to internal' do
|
||||
@link.should be_internal
|
||||
end
|
||||
|
||||
it 'has the correct url' do
|
||||
@link.url.should == @url
|
||||
end
|
||||
|
||||
it 'has the extracted domain' do
|
||||
@link.domain.should == test_uri.host
|
||||
end
|
||||
|
||||
it 'should have the id of the linked forum' do
|
||||
@link.link_topic_id == @other_topic.id
|
||||
end
|
||||
|
||||
it 'should not be the reflection' do
|
||||
@link.should_not be_reflection
|
||||
end
|
||||
|
||||
describe 'reflection in the other topic' do
|
||||
|
||||
context 'topic link' do
|
||||
before do
|
||||
@reflection = @other_topic.topic_links.first
|
||||
@other_topic = Fabricate(:topic, user: @user)
|
||||
@other_post = @other_topic.posts.create(user: @user, raw: "some content")
|
||||
|
||||
@url = "http://#{test_uri.host}/t/topic-slug/#{@other_topic.id}"
|
||||
|
||||
@topic.posts.create(user: @user, raw: 'initial post')
|
||||
@post = @topic.posts.create(user: @user, raw: "Link to another topic: #{@url}")
|
||||
|
||||
TopicLink.extract_from(@post)
|
||||
|
||||
@link = @topic.topic_links.first
|
||||
end
|
||||
|
||||
it 'exists' do
|
||||
@reflection.should be_present
|
||||
it 'extracted the link' do
|
||||
@link.should be_present
|
||||
end
|
||||
|
||||
it 'is a reflection' do
|
||||
@reflection.should be_reflection
|
||||
end
|
||||
|
||||
it 'has a post_id' do
|
||||
@reflection.post_id.should be_present
|
||||
end
|
||||
|
||||
it 'has the correct host' do
|
||||
@reflection.domain.should == test_uri.host
|
||||
it 'is set to internal' do
|
||||
@link.should be_internal
|
||||
end
|
||||
|
||||
it 'has the correct url' do
|
||||
@reflection.url.should == "http://#{test_uri.host}/t/unique-topic-name/#{@topic.id}/#{@post.post_number}"
|
||||
@link.url.should == @url
|
||||
end
|
||||
|
||||
it 'links to the original forum topic' do
|
||||
@reflection.link_topic_id.should == @topic.id
|
||||
it 'has the extracted domain' do
|
||||
@link.domain.should == test_uri.host
|
||||
end
|
||||
|
||||
it 'links to the original post' do
|
||||
@reflection.link_post_id.should == @post.id
|
||||
it 'should have the id of the linked forum' do
|
||||
@link.link_topic_id == @other_topic.id
|
||||
end
|
||||
|
||||
it 'has the user id of the original link' do
|
||||
@reflection.user_id.should == @link.user_id
|
||||
it 'should not be the reflection' do
|
||||
@link.should_not be_reflection
|
||||
end
|
||||
|
||||
describe 'reflection in the other topic' do
|
||||
|
||||
before do
|
||||
@reflection = @other_topic.topic_links.first
|
||||
end
|
||||
|
||||
it 'exists' do
|
||||
@reflection.should be_present
|
||||
end
|
||||
|
||||
it 'is a reflection' do
|
||||
@reflection.should be_reflection
|
||||
end
|
||||
|
||||
it 'has a post_id' do
|
||||
@reflection.post_id.should be_present
|
||||
end
|
||||
|
||||
it 'has the correct host' do
|
||||
@reflection.domain.should == test_uri.host
|
||||
end
|
||||
|
||||
it 'has the correct url' do
|
||||
@reflection.url.should == "http://#{test_uri.host}/t/unique-topic-name/#{@topic.id}/#{@post.post_number}"
|
||||
end
|
||||
|
||||
it 'links to the original forum topic' do
|
||||
@reflection.link_topic_id.should == @topic.id
|
||||
end
|
||||
|
||||
it 'links to the original post' do
|
||||
@reflection.link_post_id.should == @post.id
|
||||
end
|
||||
|
||||
it 'has the user id of the original link' do
|
||||
@reflection.user_id.should == @link.user_id
|
||||
end
|
||||
end
|
||||
|
||||
context 'removing a link' do
|
||||
|
||||
before do
|
||||
@post.revise(@post.user, "no more linkies")
|
||||
TopicLink.extract_from(@post)
|
||||
end
|
||||
|
||||
it 'should remove the link' do
|
||||
@topic.topic_links.where(post_id: @post.id).should be_blank
|
||||
end
|
||||
|
||||
it 'should remove the reflected link' do
|
||||
@reflection = @other_topic.topic_links.should be_blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "link to a user on discourse" do
|
||||
let(:post) { @topic.posts.create(user: @user, raw: "<a href='/users/#{@user.username_lower}'>user</a>") }
|
||||
before do
|
||||
TopicLink.extract_from(post)
|
||||
end
|
||||
|
||||
it 'does not extract a link' do
|
||||
@topic.topic_links.should be_blank
|
||||
end
|
||||
end
|
||||
|
||||
context 'removing a link' do
|
||||
context "link to a discourse resource like a FAQ" do
|
||||
let(:post) { @topic.posts.create(user: @user, raw: "<a href='/faq'>faq link here</a>") }
|
||||
before do
|
||||
TopicLink.extract_from(post)
|
||||
end
|
||||
|
||||
it 'does not extract a link' do
|
||||
@topic.topic_links.should be_present
|
||||
end
|
||||
end
|
||||
|
||||
context "@mention links" do
|
||||
let(:post) { @topic.posts.create(user: @user, raw: "Hey @#{@user.username_lower}") }
|
||||
|
||||
before do
|
||||
@post.revise(@post.user, "no more linkies")
|
||||
TopicLink.extract_from(@post)
|
||||
TopicLink.extract_from(post)
|
||||
end
|
||||
|
||||
it 'should remove the link' do
|
||||
@topic.topic_links.where(post_id: @post.id).should be_blank
|
||||
it 'does not extract a link' do
|
||||
@topic.topic_links.should be_blank
|
||||
end
|
||||
|
||||
it 'should remove the reflected link' do
|
||||
@reflection = @other_topic.topic_links.should be_blank
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe 'internal link from pm' do
|
||||
|
|
|
@ -617,4 +617,30 @@ describe User do
|
|||
it { should_not be_active }
|
||||
end
|
||||
|
||||
describe 'email_confirmed?' do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
context 'when email has not been confirmed yet' do
|
||||
it 'should return false' do
|
||||
user.email_confirmed?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when email has been confirmed' do
|
||||
it 'should return true' do
|
||||
token = user.email_tokens.where(email: user.email).first
|
||||
EmailToken.confirm(token.token)
|
||||
user.email_confirmed?.should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has no email tokens for some reason' do
|
||||
it 'should return false' do
|
||||
user.email_tokens.each {|t| t.destroy}
|
||||
user.reload
|
||||
user.email_confirmed?.should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue