mirror of
https://github.com/discourse/discourse.git
synced 2025-09-06 10:50:21 +08:00
Merge branch 'master' of github.com:discourse/discourse
This commit is contained in:
commit
ad88487a32
27 changed files with 443 additions and 293 deletions
|
@ -10,7 +10,7 @@ on Discourse with:
|
||||||
1. Install VirtualBox: https://www.virtualbox.org/wiki/Downloads
|
1. Install VirtualBox: https://www.virtualbox.org/wiki/Downloads
|
||||||
2. Install Vagrant: https://www.vagrantup.com/
|
2. Install Vagrant: https://www.vagrantup.com/
|
||||||
3. Open a terminal
|
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`
|
5. Enter the project directory: `cd discourse`
|
||||||
|
|
||||||
### Using Vagrant
|
### 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!
|
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
|
### Guard + Rspec
|
||||||
|
|
||||||
|
@ -71,6 +71,12 @@ Wait a minute while it runs all our unit tests. Once it has completed, live relo
|
||||||
|
|
||||||
### Sending Email
|
### 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
|
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:
|
To start mailcatcher, run the following command in the vagrant image:
|
||||||
|
|
58
Gemfile.lock
58
Gemfile.lock
|
@ -63,34 +63,34 @@ PATH
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actionmailer (3.2.11)
|
actionmailer (3.2.12)
|
||||||
actionpack (= 3.2.11)
|
actionpack (= 3.2.12)
|
||||||
mail (~> 2.4.4)
|
mail (~> 2.4.4)
|
||||||
actionpack (3.2.11)
|
actionpack (3.2.12)
|
||||||
activemodel (= 3.2.11)
|
activemodel (= 3.2.12)
|
||||||
activesupport (= 3.2.11)
|
activesupport (= 3.2.12)
|
||||||
builder (~> 3.0.0)
|
builder (~> 3.0.0)
|
||||||
erubis (~> 2.7.0)
|
erubis (~> 2.7.0)
|
||||||
journey (~> 1.0.4)
|
journey (~> 1.0.4)
|
||||||
rack (~> 1.4.0)
|
rack (~> 1.4.5)
|
||||||
rack-cache (~> 1.2)
|
rack-cache (~> 1.2)
|
||||||
rack-test (~> 0.6.1)
|
rack-test (~> 0.6.1)
|
||||||
sprockets (~> 2.2.1)
|
sprockets (~> 2.2.1)
|
||||||
activemodel (3.2.11)
|
activemodel (3.2.12)
|
||||||
activesupport (= 3.2.11)
|
activesupport (= 3.2.12)
|
||||||
builder (~> 3.0.0)
|
builder (~> 3.0.0)
|
||||||
activerecord (3.2.11)
|
activerecord (3.2.12)
|
||||||
activemodel (= 3.2.11)
|
activemodel (= 3.2.12)
|
||||||
activesupport (= 3.2.11)
|
activesupport (= 3.2.12)
|
||||||
arel (~> 3.0.2)
|
arel (~> 3.0.2)
|
||||||
tzinfo (~> 0.3.29)
|
tzinfo (~> 0.3.29)
|
||||||
activerecord-postgres-hstore (0.7.1)
|
activerecord-postgres-hstore (0.7.1)
|
||||||
rails
|
rails
|
||||||
rake
|
rake
|
||||||
activeresource (3.2.11)
|
activeresource (3.2.12)
|
||||||
activemodel (= 3.2.11)
|
activemodel (= 3.2.12)
|
||||||
activesupport (= 3.2.11)
|
activesupport (= 3.2.12)
|
||||||
activesupport (3.2.11)
|
activesupport (3.2.12)
|
||||||
i18n (~> 0.6)
|
i18n (~> 0.6)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
acts_as_paranoid (0.4.1)
|
acts_as_paranoid (0.4.1)
|
||||||
|
@ -194,7 +194,7 @@ GEM
|
||||||
jquery-rails (2.2.0)
|
jquery-rails (2.2.0)
|
||||||
railties (>= 3.0, < 5.0)
|
railties (>= 3.0, < 5.0)
|
||||||
thor (>= 0.14, < 2.0)
|
thor (>= 0.14, < 2.0)
|
||||||
json (1.7.6)
|
json (1.7.7)
|
||||||
koala (1.6.0)
|
koala (1.6.0)
|
||||||
addressable (~> 2.2)
|
addressable (~> 2.2)
|
||||||
faraday (~> 0.8)
|
faraday (~> 0.8)
|
||||||
|
@ -208,10 +208,10 @@ GEM
|
||||||
treetop (~> 1.4.8)
|
treetop (~> 1.4.8)
|
||||||
metaclass (0.0.1)
|
metaclass (0.0.1)
|
||||||
method_source (0.8.1)
|
method_source (0.8.1)
|
||||||
mime-types (1.20.1)
|
mime-types (1.21)
|
||||||
mocha (0.10.5)
|
mocha (0.10.5)
|
||||||
metaclass (~> 0.0.1)
|
metaclass (~> 0.0.1)
|
||||||
multi_json (1.5.0)
|
multi_json (1.5.1)
|
||||||
multipart-post (1.1.5)
|
multipart-post (1.1.5)
|
||||||
mustache (0.99.4)
|
mustache (0.99.4)
|
||||||
net-scp (1.0.4)
|
net-scp (1.0.4)
|
||||||
|
@ -239,17 +239,17 @@ GEM
|
||||||
rack
|
rack
|
||||||
rack-test (0.6.2)
|
rack-test (0.6.2)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
rails (3.2.11)
|
rails (3.2.12)
|
||||||
actionmailer (= 3.2.11)
|
actionmailer (= 3.2.12)
|
||||||
actionpack (= 3.2.11)
|
actionpack (= 3.2.12)
|
||||||
activerecord (= 3.2.11)
|
activerecord (= 3.2.12)
|
||||||
activeresource (= 3.2.11)
|
activeresource (= 3.2.12)
|
||||||
activesupport (= 3.2.11)
|
activesupport (= 3.2.12)
|
||||||
bundler (~> 1.0)
|
bundler (~> 1.0)
|
||||||
railties (= 3.2.11)
|
railties (= 3.2.12)
|
||||||
railties (3.2.11)
|
railties (3.2.12)
|
||||||
actionpack (= 3.2.11)
|
actionpack (= 3.2.12)
|
||||||
activesupport (= 3.2.11)
|
activesupport (= 3.2.12)
|
||||||
rack-ssl (~> 1.3.2)
|
rack-ssl (~> 1.3.2)
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
rdoc (~> 3.4)
|
rdoc (~> 3.4)
|
||||||
|
@ -258,7 +258,7 @@ GEM
|
||||||
rb-fsevent (0.9.3)
|
rb-fsevent (0.9.3)
|
||||||
rb-inotify (0.8.8)
|
rb-inotify (0.8.8)
|
||||||
ffi (>= 0.5.0)
|
ffi (>= 0.5.0)
|
||||||
rdoc (3.12)
|
rdoc (3.12.1)
|
||||||
json (~> 1.4)
|
json (~> 1.4)
|
||||||
redis (3.0.2)
|
redis (3.0.2)
|
||||||
redis-actionpack (3.2.3)
|
redis-actionpack (3.2.3)
|
||||||
|
|
|
@ -84,9 +84,11 @@
|
||||||
ul = div.find('ul')
|
ul = div.find('ul')
|
||||||
selectedOption = 0
|
selectedOption = 0
|
||||||
markSelected()
|
markSelected()
|
||||||
|
|
||||||
ul.find('li').click ->
|
ul.find('li').click ->
|
||||||
selectedOption = ul.find('li').index(this)
|
selectedOption = ul.find('li').index(this)
|
||||||
completeTerm(autocompleteOptions[selectedOption])
|
completeTerm(autocompleteOptions[selectedOption])
|
||||||
|
false
|
||||||
|
|
||||||
pos = null
|
pos = null
|
||||||
if isInput
|
if isInput
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
<h3><i class='icon icon-lock'></i> {{i18n private_message_info.title}}</h3>
|
<h3><i class='icon icon-lock'></i> {{i18n private_message_info.title}}</h3>
|
||||||
<p>{{{i18n private_message_info.description}}}</p>
|
|
||||||
<div class='participants clearfix'>
|
<div class='participants clearfix'>
|
||||||
{{#each content.allowed_users}}
|
{{#each content.allowed_users}}
|
||||||
<div class='user'>
|
<div class='user'>
|
||||||
|
|
|
@ -38,6 +38,7 @@ window.Discourse.TopicFooterButtonsView = Ember.ContainerView.extend
|
||||||
|
|
||||||
@addObject Discourse.ButtonView.createWithMixins
|
@addObject Discourse.ButtonView.createWithMixins
|
||||||
classNames: ['btn', 'btn-primary', 'create']
|
classNames: ['btn', 'btn-primary', 'create']
|
||||||
|
attributeBindings: ['disabled']
|
||||||
text: (->
|
text: (->
|
||||||
archetype = @get('controller.content.archetype')
|
archetype = @get('controller.content.archetype')
|
||||||
return customTitle if customTitle = @get("parentView.replyButtonText#{archetype.capitalize()}")
|
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>")
|
renderIcon: (buffer) -> buffer.push("<i class='icon icon-plus'></i>")
|
||||||
click: -> @get('controller').reply()
|
click: -> @get('controller').reply()
|
||||||
helpKey: 'topic.reply.help'
|
helpKey: 'topic.reply.help'
|
||||||
|
disabled: !@get('controller.content.can_create_post')
|
||||||
|
|
||||||
unless topic.get('isPrivateMessage')
|
unless topic.get('isPrivateMessage')
|
||||||
@addObject Discourse.DropdownButtonView.createWithMixins
|
@addObject Discourse.DropdownButtonView.createWithMixins
|
||||||
|
|
|
@ -387,7 +387,7 @@ kbd {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
padding: 0;
|
padding: 0 1px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
position: absolute;
|
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
|
# for now do a simple remap, we may look at cleaner ways of doing the render
|
||||||
raise ActiveRecord::RecordNotFound
|
raise ActiveRecord::RecordNotFound
|
||||||
else
|
else
|
||||||
render file: 'public/404', layout: false, status: 404
|
render file: 'public/404', formats: [:html], layout: false, status: 404
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue_from Discourse::InvalidAccess do
|
rescue_from Discourse::InvalidAccess do
|
||||||
render file: 'public/403', layout: false, status: 403
|
render file: 'public/403', formats: [:html], layout: false, status: 403
|
||||||
end
|
end
|
||||||
|
|
||||||
def store_preloaded(key, json)
|
def store_preloaded(key, json)
|
||||||
|
|
|
@ -22,9 +22,14 @@ class SessionController < ApplicationController
|
||||||
|
|
||||||
# If their password is correct
|
# If their password is correct
|
||||||
if @user.confirm_password?(params[:password])
|
if @user.confirm_password?(params[:password])
|
||||||
|
if @user.email_confirmed?
|
||||||
log_on_user(@user)
|
log_on_user(@user)
|
||||||
render_serialized(@user, UserSerializer)
|
render_serialized(@user, UserSerializer)
|
||||||
return
|
return
|
||||||
|
else
|
||||||
|
render :json => {error: I18n.t("login.not_activated")}
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
7
app/helpers/common_helper.rb
Normal file
7
app/helpers/common_helper.rb
Normal file
|
@ -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
|
10
app/helpers/topics_helper.rb
Normal file
10
app/helpers/topics_helper.rb
Normal file
|
@ -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,6 +342,7 @@ class Post < ActiveRecord::Base
|
||||||
# This calculates the geometric mean of the post timings and stores it along with
|
# This calculates the geometric mean of the post timings and stores it along with
|
||||||
# each post.
|
# each post.
|
||||||
def self.calculate_avg_time
|
def self.calculate_avg_time
|
||||||
|
retry_lock_error do
|
||||||
exec_sql("UPDATE posts
|
exec_sql("UPDATE posts
|
||||||
SET avg_time = (x.gmean / 1000)
|
SET avg_time = (x.gmean / 1000)
|
||||||
FROM (SELECT post_timings.topic_id,
|
FROM (SELECT post_timings.topic_id,
|
||||||
|
@ -356,6 +357,7 @@ class Post < ActiveRecord::Base
|
||||||
WHERE x.topic_id = posts.topic_id
|
WHERE x.topic_id = posts.topic_id
|
||||||
AND x.post_number = posts.post_number")
|
AND x.post_number = posts.post_number")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
before_save do
|
before_save do
|
||||||
self.last_editor_id ||= self.user_id
|
self.last_editor_id ||= self.user_id
|
||||||
|
|
|
@ -47,6 +47,10 @@ class TopicLink < ActiveRecord::Base
|
||||||
internal = true
|
internal = true
|
||||||
|
|
||||||
route = Rails.application.routes.recognize_path(parsed.path)
|
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]
|
topic_id = route[:topic_id]
|
||||||
post_number = route[:post_number] || 1
|
post_number = route[:post_number] || 1
|
||||||
end
|
end
|
||||||
|
|
|
@ -380,6 +380,10 @@ class User < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def email_confirmed?
|
||||||
|
email_tokens.where(email: self.email, confirmed: true).present?
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
|
25
app/views/common/_discourse_javascript.html.erb
Normal file
25
app/views/common/_discourse_javascript.html.erb
Normal file
|
@ -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>
|
12
app/views/common/_discourse_stylesheet.html.erb
Normal file
12
app/views/common/_discourse_stylesheet.html.erb
Normal file
|
@ -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])%>
|
14
app/views/common/_google_analytics.html.erb
Normal file
14
app/views/common/_google_analytics.html.erb
Normal file
|
@ -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>
|
31
app/views/common/_special_font_face.html.erb
Normal file
31
app/views/common/_special_font_face.html.erb
Normal file
|
@ -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" %>
|
<%= 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%>
|
<%=csrf_meta_tags%>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -98,48 +61,8 @@
|
||||||
|
|
||||||
<footer id='bottom'></footer>
|
<footer id='bottom'></footer>
|
||||||
|
|
||||||
<%- if mini_profiler_enabled? %>
|
<%= render :partial => "common/discourse_javascript" %>
|
||||||
<%- Rack::MiniProfiler.step "application" do %>
|
<%= render_google_analytics_code %>
|
||||||
<%= 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%>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<h2>
|
<h2>
|
||||||
<a href="<%= @topic_view.topic.relative_url %>"><%= @topic_view.topic.title %></a>
|
<%= render_topic_title(@topic_view.topic) %>
|
||||||
|
|
||||||
</h2>
|
</h2>
|
||||||
<% (@post ? [@post] : @topic_view.posts).each do |post| %>
|
<% (@post ? [@post] : @topic_view.posts).each do |post| %>
|
||||||
<div class='creator'>
|
<div class='creator'>
|
||||||
|
@ -12,7 +13,7 @@
|
||||||
|
|
||||||
<% if @next_page%>
|
<% if @next_page%>
|
||||||
<p>
|
<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>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|
|
@ -596,7 +596,6 @@ en:
|
||||||
|
|
||||||
private_message_info:
|
private_message_info:
|
||||||
title: "Private Conversation"
|
title: "Private Conversation"
|
||||||
description: "Participants in this private conversation"
|
|
||||||
invite: "Invite Others..."
|
invite: "Invite Others..."
|
||||||
|
|
||||||
email: 'Email'
|
email: 'Email'
|
||||||
|
@ -1039,6 +1038,7 @@ en:
|
||||||
wait_approval: "Thanks for signing up. We will notify you when your account has been approved."
|
wait_approval: "Thanks for signing up. We will notify you when your account has been approved."
|
||||||
active: "Your account is active and ready."
|
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."
|
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}"
|
errors: "Failed to create account: %{errors}"
|
||||||
not_available: "Not available. Try %{suggestion}?"
|
not_available: "Not available. Try %{suggestion}?"
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,9 @@ module CurrentUser
|
||||||
@not_logged_in = session[:current_user_id].blank?
|
@not_logged_in = session[:current_user_id].blank?
|
||||||
if @current_user
|
if @current_user
|
||||||
@current_user.update_last_seen!
|
@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
|
@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
|
||||||
end
|
end
|
||||||
@current_user
|
@current_user
|
||||||
|
|
|
@ -9,10 +9,21 @@ class DiscoursePluginRegistry
|
||||||
attr_accessor :stylesheets
|
attr_accessor :stylesheets
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_js(filename, options={})
|
# Default accessor values
|
||||||
self.class.javascripts ||= Set.new
|
#
|
||||||
self.class.server_side_javascripts ||= Set.new
|
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.
|
# If we have a server side option, add that too.
|
||||||
self.class.server_side_javascripts << options[:server_side] if options[:server_side].present?
|
self.class.server_side_javascripts << options[:server_side] if options[:server_side].present?
|
||||||
|
|
||||||
|
@ -20,12 +31,11 @@ class DiscoursePluginRegistry
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_css(filename)
|
def register_css(filename)
|
||||||
self.class.stylesheets ||= Set.new
|
|
||||||
self.class.stylesheets << filename
|
self.class.stylesheets << filename
|
||||||
end
|
end
|
||||||
|
|
||||||
def stylesheets
|
def stylesheets
|
||||||
self.class.stylesheets || Set.new
|
self.class.stylesheets
|
||||||
end
|
end
|
||||||
|
|
||||||
def register_archetype(name, options={})
|
def register_archetype(name, options={})
|
||||||
|
@ -33,17 +43,17 @@ class DiscoursePluginRegistry
|
||||||
end
|
end
|
||||||
|
|
||||||
def server_side_javascripts
|
def server_side_javascripts
|
||||||
self.class.javascripts || Set.new
|
self.class.javascripts
|
||||||
end
|
end
|
||||||
|
|
||||||
def javascripts
|
def javascripts
|
||||||
self.class.javascripts || Set.new
|
self.class.javascripts
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.clear
|
def self.clear
|
||||||
self.stylesheets = Set.new
|
self.stylesheets = nil
|
||||||
self.server_side_javascripts = Set.new
|
self.server_side_javascripts = nil
|
||||||
self.javascripts = Set.new
|
self.javascripts = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.setup(plugin_class)
|
def self.setup(plugin_class)
|
||||||
|
@ -52,6 +62,4 @@ class DiscoursePluginRegistry
|
||||||
plugin.setup
|
plugin.setup
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,6 +15,24 @@ class ActiveRecord::Base
|
||||||
ActiveRecord::Base.exec_sql(*args)
|
ActiveRecord::Base.exec_sql(*args)
|
||||||
end
|
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
|
# Support for psql. If we want to support multiple RDBMs in the future we can
|
||||||
# split this.
|
# split this.
|
||||||
def exec_sql_row_count(*args)
|
def exec_sql_row_count(*args)
|
||||||
|
|
|
@ -5,6 +5,27 @@ describe DiscoursePluginRegistry do
|
||||||
|
|
||||||
let(:registry) { DiscoursePluginRegistry.new }
|
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
|
context '.register_css' do
|
||||||
before do
|
before do
|
||||||
registry.register_css('hello.css')
|
registry.register_css('hello.css')
|
||||||
|
|
|
@ -6,17 +6,21 @@ describe SessionController do
|
||||||
|
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
context 'when email is confirmed' do
|
||||||
|
before do
|
||||||
|
token = user.email_tokens.where(email: user.email).first
|
||||||
|
EmailToken.confirm(token.token)
|
||||||
|
end
|
||||||
|
|
||||||
it "raises an error when the login isn't present" do
|
it "raises an error when the login isn't present" do
|
||||||
lambda { xhr :post, :create }.should raise_error(Discourse::InvalidParameters)
|
lambda { xhr :post, :create }.should raise_error(Discourse::InvalidParameters)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'invalid password' do
|
describe 'invalid password' do
|
||||||
|
|
||||||
it "should return an error with an invalid password" do
|
it "should return an error with an invalid password" do
|
||||||
xhr :post, :create, login: user.username, password: 'sssss'
|
xhr :post, :create, login: user.username, password: 'sssss'
|
||||||
::JSON.parse(response.body)['error'].should be_present
|
::JSON.parse(response.body)['error'].should be_present
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'success by username' do
|
describe 'success by username' do
|
||||||
|
@ -72,11 +76,23 @@ describe SessionController do
|
||||||
it "doesn't log in the user" do
|
it "doesn't log in the user" do
|
||||||
session[:current_user_id].should be_blank
|
session[:current_user_id].should be_blank
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when email has not been confirmed' do
|
||||||
|
before do
|
||||||
|
xhr :post, :create, login: user.email, password: 'myawesomepassword'
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
describe '.destroy' do
|
describe '.destroy' do
|
||||||
|
|
|
@ -44,31 +44,9 @@ describe TopicLink do
|
||||||
|
|
||||||
end
|
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
|
describe 'internal links' do
|
||||||
|
|
||||||
|
context 'topic link' do
|
||||||
before do
|
before do
|
||||||
@other_topic = Fabricate(:topic, user: @user)
|
@other_topic = Fabricate(:topic, user: @user)
|
||||||
@other_post = @other_topic.posts.create(user: @user, raw: "some content")
|
@other_post = @other_topic.posts.create(user: @user, raw: "some content")
|
||||||
|
@ -165,6 +143,42 @@ describe TopicLink do
|
||||||
|
|
||||||
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 "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
|
||||||
|
TopicLink.extract_from(post)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not extract a link' do
|
||||||
|
@topic.topic_links.should be_blank
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
describe 'internal link from pm' do
|
describe 'internal link from pm' do
|
||||||
before do
|
before do
|
||||||
@pm = Fabricate(:topic, user: @user, archetype: 'private_message')
|
@pm = Fabricate(:topic, user: @user, archetype: 'private_message')
|
||||||
|
|
|
@ -617,4 +617,30 @@ describe User do
|
||||||
it { should_not be_active }
|
it { should_not be_active }
|
||||||
end
|
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
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue