Merge branch 'master' of github.com:discourse/discourse

This commit is contained in:
Neil Lalonde 2015-01-29 17:39:52 -05:00
commit 67b262b93e
14 changed files with 110 additions and 43 deletions

View file

@ -24,6 +24,12 @@ window.Discourse = Ember.Application.createWithMixins(Discourse.Ajax, {
return u + url;
},
getURLWithCDN: function(url) {
url = this.getURL(url);
if (Discourse.CDN) { url = Discourse.CDN + url; }
return url;
},
Resolver: DiscourseResolver,
_titleChanged: function() {

View file

@ -1,4 +1,5 @@
var esc = Handlebars.Utils.escapeExpression;
Discourse.BBCode.register('quote', {noWrap: true, singlePara: true}, function(contents, bbParams, options) {
var params = {'class': 'quote'},
username = null;

View file

@ -72,10 +72,9 @@ Discourse.User = Discourse.Model.extend({
@type {String}
**/
profileBackground: function() {
var background = this.get('profile_background');
if(Em.isEmpty(background) || !Discourse.SiteSettings.allow_profile_backgrounds) { return; }
return 'background-image: url(' + background + ')';
var url = this.get('profile_background');
if (Em.isEmpty(url) || !Discourse.SiteSettings.allow_profile_backgrounds) { return; }
return 'background-image: url(' + Discourse.getURLWithCDN(url) + ')';
}.property('profile_background'),
/**
@ -442,6 +441,7 @@ Discourse.User.reopenClass(Discourse.Singleton, {
avatarTemplate: function(username, uploadedAvatarId) {
var url;
if (uploadedAvatarId) {
url = "/user_avatar/" +
Discourse.BaseUrl +
@ -456,11 +456,7 @@ Discourse.User.reopenClass(Discourse.Singleton, {
Discourse.LetterAvatarVersion + ".png";
}
url = Discourse.getURL(url);
if (Discourse.CDN) {
url = Discourse.CDN + url;
}
return url;
return Discourse.getURLWithCDN(url);
},
/**

View file

@ -11,6 +11,7 @@ export default Discourse.View.extend(CleansUp, {
addBackground: function() {
var url = this.get('controller.user.card_background');
if (!this.get('allowBackgrounds')) { return; }
var $this = this.$();
@ -19,7 +20,7 @@ export default Discourse.View.extend(CleansUp, {
if (Ember.isEmpty(url)) {
$this.css('background-image', '').addClass('no-bg');
} else {
$this.css('background-image', "url(" + url + ")").removeClass('no-bg');
$this.css('background-image', "url(" + Discourse.getURLWithCDN(url) + ")").removeClass('no-bg');
}
}.observes('controller.user.card_background'),

View file

@ -155,7 +155,7 @@ class ApplicationController < ActionController::Base
# If we are rendering HTML, preload the session data
def preload_json
# We don't preload JSON on xhr or JSON request
return if request.xhr?
return if request.xhr? || request.format.json?
preload_anonymous_data

View file

@ -5,7 +5,7 @@ require_dependency 'distributed_memoizer'
class PostsController < ApplicationController
# Need to be logged in for all actions here
before_filter :ensure_logged_in, except: [:show, :replies, :by_number, :short_link, :reply_history, :revisions, :latest_revision, :expand_embed, :markdown, :raw, :cooked]
before_filter :ensure_logged_in, except: [:show, :replies, :by_number, :short_link, :reply_history, :revisions, :latest_revision, :expand_embed, :markdown_id, :markdown_num, :cooked, :latest]
skip_before_filter :check_xhr, only: [:markdown_id, :markdown_num, :short_link]
@ -25,6 +25,33 @@ class PostsController < ApplicationController
end
end
def latest
params.permit(:before)
last_post_id = params[:before].to_i
last_post_id = Post.last.id if last_post_id <= 0
# last 50 post IDs only, to avoid counting deleted posts in security check
posts = Post.order(created_at: :desc)
.where('posts.id <= ?', last_post_id)
.where('posts.id > ?', last_post_id - 50)
.includes(topic: :category)
.includes(:user)
.limit(50)
# Remove posts the user doesn't have permission to see
# This isn't leaking any information we weren't already through the post ID numbers
posts = posts.reject { |post| !guardian.can_see?(post) }
counts = PostAction.counts_for(posts, current_user)
render_json_dump(serialize_data(posts,
PostSerializer,
scope: guardian,
root: 'latest_posts',
add_raw: true,
all_post_actions: counts)
)
end
def cooked
post = find_post_from_params
render json: {cooked: post.cooked}

View file

@ -78,17 +78,6 @@ module HasCustomFields
!@custom_fields || @custom_fields_orig == @custom_fields
end
protected
def refresh_custom_fields_from_db
target = Hash.new
_custom_fields.pluck(:name,:value).each do |key, value|
self.class.append_custom_field(target, key, value)
end
@custom_fields_orig = target
@custom_fields = @custom_fields_orig.dup
end
def save_custom_fields
if !custom_fields_clean?
dup = @custom_fields.dup
@ -134,4 +123,16 @@ module HasCustomFields
refresh_custom_fields_from_db
end
end
protected
def refresh_custom_fields_from_db
target = Hash.new
_custom_fields.pluck(:name,:value).each do |key, value|
self.class.append_custom_field(target, key, value)
end
@custom_fields_orig = target
@custom_fields = @custom_fields_orig.dup
end
end

View file

@ -62,7 +62,7 @@ class User < ActiveRecord::Base
delegate :last_sent_email_address, :to => :email_logs
before_validation :downcase_email
before_validation :strip_downcase_email
validates_presence_of :username
validate :username_validator
@ -764,8 +764,11 @@ class User < ActiveRecord::Base
self.username_lower = username.downcase
end
def downcase_email
self.email = self.email.downcase if self.email
def strip_downcase_email
if self.email
self.email = self.email.strip
self.email = self.email.downcase
end
end
def username_validator

View file

@ -1,12 +1,17 @@
class PostSerializer < BasicPostSerializer
# To pass in additional information we might need
attr_accessor :topic_view,
INSTANCE_VARS = [:topic_view,
:parent_post,
:add_raw,
:single_post_link_counts,
:draft_sequence,
:post_actions
:post_actions,
:all_post_actions]
INSTANCE_VARS.each do |v|
self.send(:attr_accessor, v)
end
attributes :post_number,
:post_type,
@ -54,6 +59,15 @@ class PostSerializer < BasicPostSerializer
:static_doc,
:via_email
def initialize(object, opts)
super(object, opts)
PostSerializer::INSTANCE_VARS.each do |name|
if opts.include? name
self.send("#{name}=", opts[name])
end
end
end
def topic_slug
object.try(:topic).try(:slug)
end
@ -155,6 +169,13 @@ class PostSerializer < BasicPostSerializer
scope.is_staff? && object.deleted_by.present?
end
# Helper function to decide between #post_actions and @all_post_actions
def actions
return post_actions if post_actions.present?
return all_post_actions[object.id] if all_post_actions.present?
nil
end
# Summary of the actions taken on this post
def actions_summary
result = []
@ -168,7 +189,7 @@ class PostSerializer < BasicPostSerializer
id: id,
count: count,
hidden: (sym == :vote),
can_act: scope.post_can_act?(object, sym, taken_actions: post_actions)
can_act: scope.post_can_act?(object, sym, taken_actions: actions)
}
if sym == :notify_user && scope.current_user.present? && scope.current_user == object.user
@ -183,9 +204,9 @@ class PostSerializer < BasicPostSerializer
active_flags[id].count > 0
end
if post_actions.present? && post_actions.has_key?(id)
if actions.present? && actions.has_key?(id)
action_summary[:acted] = true
action_summary[:can_undo] = scope.can_delete?(post_actions[id])
action_summary[:can_undo] = scope.can_delete?(actions[id])
end
# only show public data
@ -226,7 +247,7 @@ class PostSerializer < BasicPostSerializer
end
def include_bookmarked?
post_actions.present? && post_actions.keys.include?(PostActionType.types[:bookmark])
actions.present? && actions.keys.include?(PostActionType.types[:bookmark])
end
def include_display_username?

View file

@ -1013,7 +1013,7 @@ en:
disable_edit_notifications: "Disables edit notifications by the system user when 'download_remote_images_to_local' is active."
enable_names: "Allow showing user full names. Disable to hide full names."
enable_names: "Show the user's full name on their profile, user card, and emails. Disable to hide full name everywhere."
display_name_on_posts: "Show a user's full name on their posts in addition to their @username."
invites_per_page: "Default invites shown on the user page."
short_progress_text_threshold: "After the number of posts in a topic goes above this number, the progress bar will only show the current post number. If you change the progress bar's width, you may need to change this value."

View file

@ -269,6 +269,7 @@ Discourse::Application.routes.draw do
get "uploads/:site/:sha" => "uploads#show", constraints: { site: /\w+/, sha: /[a-z0-9]{40}/}
post "uploads" => "uploads#create"
get "posts" => "posts#latest"
get "posts/by_number/:topic_id/:post_number" => "posts#by_number"
get "posts/:id/reply-history" => "posts#reply_history"
get "posts/:username/deleted" => "posts#deleted_posts", constraints: {username: USERNAME_ROUTE_FORMAT}

View file

@ -1,11 +1,13 @@
require 'v8'
require 'nokogiri'
require_dependency 'url_helper'
require_dependency 'excerpt_parser'
require_dependency 'post'
module PrettyText
class Helpers
include UrlHelper
def t(key, opts)
key = "js." + key
@ -21,15 +23,15 @@ module PrettyText
# function here are available to v8
def avatar_template(username)
return "" unless username
user = User.find_by(username_lower: username.downcase)
user.avatar_template if user.present?
return "" unless user.present?
schemaless absolute user.avatar_template
end
def is_username_valid(username)
return false unless username
username = username.downcase
return User.exec_sql('SELECT 1 FROM users WHERE username_lower = ?', username).values.length == 1
User.exec_sql('SELECT 1 FROM users WHERE username_lower = ?', username).values.length == 1
end
end
@ -128,7 +130,9 @@ module PrettyText
context.eval("Discourse.SiteSettings = #{SiteSetting.client_settings_json};")
context.eval("Discourse.CDN = '#{Rails.configuration.action_controller.asset_host}';")
context.eval("Discourse.BaseUrl = 'http://#{RailsMultisite::ConnectionManagement.current_hostname}';")
context.eval("Discourse.getURL = function(url) {return '#{Discourse::base_uri}' + url};")
context.eval("Discourse.getURL = function(url) { return '#{Discourse::base_uri}' + url };")
context.eval("Discourse.getURLWithCDN = function(url) { url = Discourse.getURL(url); if (Discourse.CDN) { url = Discourse.CDN + url; } return url; };")
end
def self.markdown(text, opts=nil)

View file

@ -12,20 +12,20 @@ describe PrettyText do
before(:each) do
eviltrout = User.new
eviltrout.stubs(:avatar_template).returns("http://test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/{size}.png")
eviltrout.stubs(:avatar_template).returns("//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/{size}.png")
User.expects(:find_by).with(username_lower: "eviltrout").returns(eviltrout)
end
it "produces a quote even with new lines in it" do
expect(PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd\n[/quote]")).to match_html "<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"http://test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
expect(PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd\n[/quote]")).to match_html "<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
end
it "should produce a quote" do
expect(PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd[/quote]")).to match_html "<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"http://test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
expect(PrettyText.cook("[quote=\"EvilTrout, post:123, topic:456, full:true\"]ddd[/quote]")).to match_html "<aside class=\"quote\" data-post=\"123\" data-topic=\"456\" data-full=\"true\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
end
it "trims spaces on quote params" do
expect(PrettyText.cook("[quote=\"EvilTrout, post:555, topic: 666\"]ddd[/quote]")).to match_html "<aside class=\"quote\" data-post=\"555\" data-topic=\"666\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"http://test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
expect(PrettyText.cook("[quote=\"EvilTrout, post:555, topic: 666\"]ddd[/quote]")).to match_html "<aside class=\"quote\" data-post=\"555\" data-topic=\"666\"><div class=\"title\">\n<div class=\"quote-controls\"></div>\n<img width=\"20\" height=\"20\" src=\"//test.localhost/uploads/default/avatars/42d/57c/46ce7ee487/40.png\" class=\"avatar\">EvilTrout:</div>\n<blockquote><p>ddd</p></blockquote></aside>"
end
end

View file

@ -261,8 +261,14 @@ describe User do
it "downcases email addresses" do
user = Fabricate.build(:user, email: 'Fancy.Caps.4.U@gmail.com')
user.save
expect(user.reload.email).to eq('fancy.caps.4.u@gmail.com')
user.valid?
expect(user.email).to eq('fancy.caps.4.u@gmail.com')
end
it "strips whitespace from email addresses" do
user = Fabricate.build(:user, email: ' example@gmail.com ')
user.valid?
expect(user.email).to eq('example@gmail.com')
end
end