mirror of
https://github.com/discourse/discourse.git
synced 2025-06-17 04:22:29 +08:00
Merge branch 'master' of github.com:discourse/discourse
This commit is contained in:
BIN
app/assets/images/default-favicon.ico
Normal file
BIN
app/assets/images/default-favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
@ -85,6 +85,14 @@ Discourse = Ember.Application.createWithMixins({
|
|||||||
}, 200);
|
}, 200);
|
||||||
}.observes('title', 'hasFocus', 'notifyCount'),
|
}.observes('title', 'hasFocus', 'notifyCount'),
|
||||||
|
|
||||||
|
faviconChanged: function() {
|
||||||
|
if(Discourse.SiteSettings.dynamic_favicon) {
|
||||||
|
$.faviconNotify(
|
||||||
|
Discourse.SiteSettings.favicon_url, this.get('notifyCount')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}.observes('notifyCount'),
|
||||||
|
|
||||||
// The classes of buttons to show on a post
|
// The classes of buttons to show on a post
|
||||||
postButtons: function() {
|
postButtons: function() {
|
||||||
return Discourse.SiteSettings.post_menu.split("|").map(function(i) {
|
return Discourse.SiteSettings.post_menu.split("|").map(function(i) {
|
||||||
|
224
app/assets/javascripts/external/jquery.faviconNotify.js
vendored
Normal file
224
app/assets/javascripts/external/jquery.faviconNotify.js
vendored
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
/**
|
||||||
|
* jQuery Favicon Notify
|
||||||
|
*
|
||||||
|
* Updates the favicon to notify the user of changes. In the original tests I
|
||||||
|
* had an embedded font collection to allow any charachers - I decided that the
|
||||||
|
* ~130Kb and added complexity was overkill. As such it now uses a manual glyph
|
||||||
|
* set meaning that only numerical notifications are possible.
|
||||||
|
*
|
||||||
|
* Dual licensed under the MIT and GPL licenses:
|
||||||
|
*
|
||||||
|
* http://www.opensource.org/licenses/mit-license.php
|
||||||
|
* http://www.gnu.org/licenses/gpl.html
|
||||||
|
*
|
||||||
|
* @author David King
|
||||||
|
* @copyright Copyright (c) 2011 +
|
||||||
|
* @url oodavid.com
|
||||||
|
*/
|
||||||
|
(function($){
|
||||||
|
var canvas;
|
||||||
|
var bg = '#000000';
|
||||||
|
var fg = '#FFFFFF';
|
||||||
|
var pos = 'br';
|
||||||
|
$.faviconNotify = function(icon, num, myPos, myBg, myFg){
|
||||||
|
// Default the positions
|
||||||
|
myPos = myPos || pos;
|
||||||
|
myFg = myFg || fg;
|
||||||
|
myBg = myBg || bg;
|
||||||
|
// Create a canvas if we need one
|
||||||
|
canvas = canvas || $('<canvas />')[0];
|
||||||
|
if(canvas.getContext){
|
||||||
|
// Load the icon
|
||||||
|
$('<img />').load(function(e){
|
||||||
|
// Load the icon into the canvas
|
||||||
|
canvas.height = canvas.width = 16;
|
||||||
|
var ctx = canvas.getContext('2d');
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
ctx.drawImage(this, 0, 0);
|
||||||
|
// We gots num?
|
||||||
|
if(num !== undefined){
|
||||||
|
num = parseFloat(num, 10);
|
||||||
|
// Convert the num into a glyphs array
|
||||||
|
var myGlyphs = [];
|
||||||
|
if(num > 99){
|
||||||
|
myGlyphs.push(glyphs['LOTS']);
|
||||||
|
} else {
|
||||||
|
num = num.toString().split('');
|
||||||
|
$.each(num, function(k,v){
|
||||||
|
myGlyphs.push(glyphs[v]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Merge the glyphs together
|
||||||
|
var combined = [];
|
||||||
|
var glyphHeight = myGlyphs[0].length;
|
||||||
|
$.each(myGlyphs, function(k,v){
|
||||||
|
for(y=0; y<glyphHeight; y++){
|
||||||
|
// First pass?
|
||||||
|
if(combined[y] === undefined) {
|
||||||
|
combined[y] = v[y];
|
||||||
|
} else {
|
||||||
|
// Merge the glyph parts, careful of the boundaries
|
||||||
|
var l = combined[y].length;
|
||||||
|
if(combined[y][(l-1)] == ' '){
|
||||||
|
combined[y] = combined[y].substring(0, (l-1)) + v[y];
|
||||||
|
} else {
|
||||||
|
combined[y] += v[y].substring(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Figure out our starting position
|
||||||
|
var glyphWidth = combined[0].length;
|
||||||
|
var x = (myPos.indexOf('l') != -1) ? 0 : (16 - glyphWidth);
|
||||||
|
var y = (myPos.indexOf('t') != -1) ? 0 : (16 - glyphHeight);
|
||||||
|
// Draw them pixels!
|
||||||
|
for(dX=0; dX<glyphWidth; dX++){
|
||||||
|
for(dY=0; dY<glyphHeight; dY++){
|
||||||
|
var pixel = combined[dY][dX];
|
||||||
|
if(pixel != ' '){
|
||||||
|
ctx.fillStyle = (pixel == '@') ? myFg : myBg;
|
||||||
|
ctx.fillRect((x+dX), (y+dY), 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Update the favicon
|
||||||
|
$('link[rel$=icon]').remove();
|
||||||
|
$('head').append($('<link rel="shortcut icon" type="image/x-icon"/>').attr('href', canvas.toDataURL('image/png')));
|
||||||
|
}).attr('src', icon)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var glyphs = {
|
||||||
|
'0': [
|
||||||
|
' --- ',
|
||||||
|
' -@@@- ',
|
||||||
|
'-@---@-',
|
||||||
|
'-@- -@-',
|
||||||
|
'-@- -@-',
|
||||||
|
'-@- -@-',
|
||||||
|
'-@---@-',
|
||||||
|
' -@@@- ',
|
||||||
|
' --- ' ],
|
||||||
|
'1': [
|
||||||
|
' - ',
|
||||||
|
' -@- ',
|
||||||
|
'-@@- ',
|
||||||
|
' -@- ',
|
||||||
|
' -@- ',
|
||||||
|
' -@- ',
|
||||||
|
' -@- ',
|
||||||
|
'-@@@-',
|
||||||
|
' --- ' ],
|
||||||
|
'2': [
|
||||||
|
' --- ',
|
||||||
|
' -@@@- ',
|
||||||
|
'-@---@-',
|
||||||
|
' - --@-',
|
||||||
|
' -@@- ',
|
||||||
|
' -@-- ',
|
||||||
|
'-@---- ',
|
||||||
|
'-@@@@@-',
|
||||||
|
' ----- ' ],
|
||||||
|
'3': [
|
||||||
|
' --- ',
|
||||||
|
' -@@@- ',
|
||||||
|
'-@---@-',
|
||||||
|
' - --@-',
|
||||||
|
' -@@- ',
|
||||||
|
' - --@-',
|
||||||
|
'-@---@-',
|
||||||
|
' -@@@- ',
|
||||||
|
' --- ' ],
|
||||||
|
'4': [
|
||||||
|
' -- ',
|
||||||
|
' -@@-',
|
||||||
|
' -@-@-',
|
||||||
|
' -@--@-',
|
||||||
|
'-@---@-',
|
||||||
|
'-@@@@@-',
|
||||||
|
' ----@-',
|
||||||
|
' -@-',
|
||||||
|
' - ' ],
|
||||||
|
'5': [
|
||||||
|
' ----- ',
|
||||||
|
'-@@@@@-',
|
||||||
|
'-@---- ',
|
||||||
|
'-@--- ',
|
||||||
|
'-@@@@- ',
|
||||||
|
' ----@-',
|
||||||
|
'-@---@-',
|
||||||
|
' -@@@- ',
|
||||||
|
' --- ' ],
|
||||||
|
'6': [
|
||||||
|
' --- ',
|
||||||
|
' -@@@- ',
|
||||||
|
'-@---@-',
|
||||||
|
'-@---- ',
|
||||||
|
'-@@@@- ',
|
||||||
|
'-@---@-',
|
||||||
|
'-@---@-',
|
||||||
|
' -@@@- ',
|
||||||
|
' --- ' ],
|
||||||
|
'7': [
|
||||||
|
' ----- ',
|
||||||
|
'-@@@@@-',
|
||||||
|
' ----@-',
|
||||||
|
' -@- ',
|
||||||
|
' -@- ',
|
||||||
|
' -@- ',
|
||||||
|
' -@- ',
|
||||||
|
' -@- ',
|
||||||
|
' - ' ],
|
||||||
|
'8': [
|
||||||
|
' --- ',
|
||||||
|
' -@@@- ',
|
||||||
|
'-@---@-',
|
||||||
|
'-@---@-',
|
||||||
|
' -@@@- ',
|
||||||
|
'-@---@-',
|
||||||
|
'-@---@-',
|
||||||
|
' -@@@- ',
|
||||||
|
' --- ' ],
|
||||||
|
'9': [
|
||||||
|
' --- ',
|
||||||
|
' -@@@- ',
|
||||||
|
'-@---@-',
|
||||||
|
'-@---@-',
|
||||||
|
' -@@@@-',
|
||||||
|
' ----@-',
|
||||||
|
'-@---@-',
|
||||||
|
' -@@@- ',
|
||||||
|
' --- ' ],
|
||||||
|
'!': [
|
||||||
|
' - ',
|
||||||
|
'-@-',
|
||||||
|
'-@-',
|
||||||
|
'-@-',
|
||||||
|
'-@-',
|
||||||
|
'-@-',
|
||||||
|
' - ',
|
||||||
|
'-@-',
|
||||||
|
' - ' ],
|
||||||
|
'.': [
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' ',
|
||||||
|
' - ',
|
||||||
|
'-@-',
|
||||||
|
' - ' ],
|
||||||
|
'LOTS': [
|
||||||
|
' - -- --- -- ',
|
||||||
|
'-@- -@@-@@@--@@-',
|
||||||
|
'-@--@--@-@--@- ',
|
||||||
|
'-@--@--@-@--@- ',
|
||||||
|
'-@--@--@-@- -@- ',
|
||||||
|
'-@--@--@-@- -@-',
|
||||||
|
'-@--@--@-@----@-',
|
||||||
|
'-@@@-@@--@-@@@- ',
|
||||||
|
' --- -- - --- '
|
||||||
|
]
|
||||||
|
};
|
||||||
|
})(jQuery);
|
@ -30,20 +30,16 @@ class Post < ActiveRecord::Base
|
|||||||
|
|
||||||
validates_presence_of :raw, :user_id, :topic_id
|
validates_presence_of :raw, :user_id, :topic_id
|
||||||
validates :raw, stripped_length: { in: -> { SiteSetting.post_length } }
|
validates :raw, stripped_length: { in: -> { SiteSetting.post_length } }
|
||||||
validate :raw_quality
|
validates_with PostValidator
|
||||||
validate :max_mention_validator
|
|
||||||
validate :max_images_validator
|
|
||||||
validate :max_links_validator
|
|
||||||
validate :unique_post_validator
|
|
||||||
|
|
||||||
# We can pass a hash of image sizes when saving to prevent crawling those images
|
# We can pass a hash of image sizes when saving to prevent crawling those images
|
||||||
attr_accessor :image_sizes, :quoted_post_numbers, :no_bump, :invalidate_oneboxes
|
attr_accessor :image_sizes, :quoted_post_numbers, :no_bump, :invalidate_oneboxes
|
||||||
|
|
||||||
SHORT_POST_CHARS = 1200
|
SHORT_POST_CHARS = 1200
|
||||||
|
|
||||||
scope :by_newest, order('created_at desc, id desc')
|
scope :by_newest, -> { order('created_at desc, id desc') }
|
||||||
scope :by_post_number, order('post_number ASC')
|
scope :by_post_number, -> { order('post_number ASC') }
|
||||||
scope :with_user, includes(:user)
|
scope :with_user, -> { includes(:user) }
|
||||||
scope :public_posts, -> { joins(:topic).where('topics.archetype <> ?', Archetype.private_message) }
|
scope :public_posts, -> { joins(:topic).where('topics.archetype <> ?', Archetype.private_message) }
|
||||||
scope :private_posts, -> { joins(:topic).where('topics.archetype = ?', Archetype.private_message) }
|
scope :private_posts, -> { joins(:topic).where('topics.archetype = ?', Archetype.private_message) }
|
||||||
scope :with_topic_subtype, ->(subtype) { joins(:topic).where('topics.subtype = ?', subtype) }
|
scope :with_topic_subtype, ->(subtype) { joins(:topic).where('topics.subtype = ?', subtype) }
|
||||||
@ -61,24 +57,6 @@ class Post < ActiveRecord::Base
|
|||||||
update_flagged_posts_count
|
update_flagged_posts_count
|
||||||
end
|
end
|
||||||
|
|
||||||
def raw_quality
|
|
||||||
sentinel = TextSentinel.body_sentinel(raw)
|
|
||||||
errors.add(:raw, I18n.t(:is_invalid)) unless sentinel.valid?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Stop us from posting the same thing too quickly
|
|
||||||
def unique_post_validator
|
|
||||||
return if SiteSetting.unique_posts_mins == 0
|
|
||||||
return if acting_user.admin? || acting_user.moderator?
|
|
||||||
|
|
||||||
# If the post is empty, default to the validates_presence_of
|
|
||||||
return if raw.blank?
|
|
||||||
|
|
||||||
if $redis.exists(unique_post_key)
|
|
||||||
errors.add(:raw, I18n.t(:just_posted_that))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# The key we use in redis to ensure unique posts
|
# The key we use in redis to ensure unique posts
|
||||||
def unique_post_key
|
def unique_post_key
|
||||||
"post-#{user_id}:#{raw_hash}"
|
"post-#{user_id}:#{raw_hash}"
|
||||||
@ -124,25 +102,6 @@ class Post < ActiveRecord::Base
|
|||||||
@acting_user = pu
|
@acting_user = pu
|
||||||
end
|
end
|
||||||
|
|
||||||
# Ensure maximum amount of mentions in a post
|
|
||||||
def max_mention_validator
|
|
||||||
if acting_user_is_trusted?
|
|
||||||
add_error_if_count_exceeded(:too_many_mentions, raw_mentions.size, SiteSetting.max_mentions_per_post)
|
|
||||||
else
|
|
||||||
add_error_if_count_exceeded(:too_many_mentions_newuser, raw_mentions.size, SiteSetting.newuser_max_mentions_per_post)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Ensure new users can not put too many images in a post
|
|
||||||
def max_images_validator
|
|
||||||
add_error_if_count_exceeded(:too_many_images, image_count, SiteSetting.newuser_max_images) unless acting_user_is_trusted?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Ensure new users can not put too many links in a post
|
|
||||||
def max_links_validator
|
|
||||||
add_error_if_count_exceeded(:too_many_links, link_count, SiteSetting.newuser_max_links) unless acting_user_is_trusted?
|
|
||||||
end
|
|
||||||
|
|
||||||
def total_hosts_usage
|
def total_hosts_usage
|
||||||
hosts = linked_hosts.clone
|
hosts = linked_hosts.clone
|
||||||
|
|
||||||
@ -291,37 +250,14 @@ class Post < ActiveRecord::Base
|
|||||||
PostRevisor.new(self).revise!(updated_by, new_raw, opts)
|
PostRevisor.new(self).revise!(updated_by, new_raw, opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# TODO: move into PostCreator
|
|
||||||
# Various callbacks
|
|
||||||
before_create do
|
before_create do
|
||||||
if reply_to_post_number.present?
|
PostCreator.before_create_tasks(self)
|
||||||
self.reply_to_user_id ||= Post.select(:user_id).where(topic_id: topic_id, post_number: reply_to_post_number).first.try(:user_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
self.post_number ||= Topic.next_post_number(topic_id, reply_to_post_number.present?)
|
|
||||||
self.cooked ||= cook(raw, topic_id: topic_id)
|
|
||||||
self.sort_order = post_number
|
|
||||||
DiscourseEvent.trigger(:before_create_post, self)
|
|
||||||
self.last_version_at ||= Time.now
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Move some of this into an asynchronous job?
|
# TODO: Move some of this into an asynchronous job?
|
||||||
# TODO: Move into PostCreator
|
# TODO: Move into PostCreator
|
||||||
after_create do
|
after_create do
|
||||||
|
PostCreator.after_create_tasks(self)
|
||||||
Rails.logger.info (">" * 30) + "#{no_bump} #{created_at}"
|
|
||||||
# Update attributes on the topic - featured users and last posted.
|
|
||||||
attrs = {last_posted_at: created_at, last_post_user_id: user_id}
|
|
||||||
attrs[:bumped_at] = created_at unless no_bump
|
|
||||||
topic.update_attributes(attrs)
|
|
||||||
|
|
||||||
# Update topic user data
|
|
||||||
TopicUser.change(user,
|
|
||||||
topic.id,
|
|
||||||
posted: true,
|
|
||||||
last_read_post_number: post_number,
|
|
||||||
seen_post_count: post_number)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# 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
|
||||||
@ -401,13 +337,7 @@ class Post < ActiveRecord::Base
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def acting_user_is_trusted?
|
|
||||||
acting_user.present? && acting_user.has_trust_level?(:basic)
|
|
||||||
end
|
|
||||||
|
|
||||||
def add_error_if_count_exceeded(key_for_translation, current_count, max_count)
|
|
||||||
errors.add(:base, I18n.t(key_for_translation, count: max_count)) if current_count > max_count
|
|
||||||
end
|
|
||||||
|
|
||||||
def parse_quote_into_arguments(quote)
|
def parse_quote_into_arguments(quote)
|
||||||
return {} unless quote.present?
|
return {} unless quote.present?
|
||||||
|
@ -79,7 +79,8 @@ class SiteSetting < ActiveRecord::Base
|
|||||||
setting(:invite_expiry_days, 14)
|
setting(:invite_expiry_days, 14)
|
||||||
setting(:active_user_rate_limit_secs, 60)
|
setting(:active_user_rate_limit_secs, 60)
|
||||||
setting(:previous_visit_timeout_hours, 1)
|
setting(:previous_visit_timeout_hours, 1)
|
||||||
setting(:favicon_url, '/assets/default-favicon.png')
|
client_setting(:favicon_url, '/assets/default-favicon.ico')
|
||||||
|
client_setting(:dynamic_favicon, false)
|
||||||
setting(:apple_touch_icon_url, '/assets/default-apple-touch-icon.png')
|
setting(:apple_touch_icon_url, '/assets/default-apple-touch-icon.png')
|
||||||
|
|
||||||
setting(:ninja_edit_window, 5.minutes.to_i)
|
setting(:ninja_edit_window, 5.minutes.to_i)
|
||||||
|
@ -460,6 +460,7 @@ en:
|
|||||||
logo_url: "The logo for your site eg: http://example.com/logo.png"
|
logo_url: "The logo for your site eg: http://example.com/logo.png"
|
||||||
logo_small_url: "The small logo for your site used when scrolling down on topics eg: http://example.com/logo-small.png"
|
logo_small_url: "The small logo for your site used when scrolling down on topics eg: http://example.com/logo-small.png"
|
||||||
favicon_url: "A favicon for your site, see http://en.wikipedia.org/wiki/Favicon"
|
favicon_url: "A favicon for your site, see http://en.wikipedia.org/wiki/Favicon"
|
||||||
|
dynamic_favicon: "Show incoming message notifications on favicon"
|
||||||
apple_touch_icon_url: "Icon used for Apple touch devices. Recommended size is 144px by 144px."
|
apple_touch_icon_url: "Icon used for Apple touch devices. Recommended size is 144px by 144px."
|
||||||
|
|
||||||
notification_email: "The return email address used when sending system emails such as notifying users of lost passwords, new accounts etc"
|
notification_email: "The return email address used when sending system emails such as notifying users of lost passwords, new accounts etc"
|
||||||
|
26
lib/oneboxer/github_pullrequest_onebox.rb
Normal file
26
lib/oneboxer/github_pullrequest_onebox.rb
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
require_dependency 'oneboxer/handlebars_onebox'
|
||||||
|
|
||||||
|
module Oneboxer
|
||||||
|
class GithubPullrequestOnebox < HandlebarsOnebox
|
||||||
|
|
||||||
|
matcher /^https?:\/\/(?:www\.)?github\.com\/[^\/]+\/[^\/]+\/pull\/.+/
|
||||||
|
favicon 'github.png'
|
||||||
|
|
||||||
|
def translate_url
|
||||||
|
@url.match(
|
||||||
|
/github\.com\/(?<owner>[^\/]+)\/(?<repo>[^\/]+)\/pull\/(?<number>[^\/]+)/mi
|
||||||
|
) do |match|
|
||||||
|
"https://api.github.com/repos/#{match[:owner]}/#{match[:repo]}/pulls/#{match[:number]}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def parse(data)
|
||||||
|
result = JSON.parse(data)
|
||||||
|
|
||||||
|
result['created_at'] =
|
||||||
|
Time.parse(result['created_at']).strftime("%I:%M%p - %d %b %y")
|
||||||
|
|
||||||
|
result
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
39
lib/oneboxer/templates/github_pullrequest_onebox.hbrs
Normal file
39
lib/oneboxer/templates/github_pullrequest_onebox.hbrs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<div class="onebox-result">
|
||||||
|
{{#host}}
|
||||||
|
<div class="source">
|
||||||
|
<div class="info">
|
||||||
|
<a href="{{html_url}}" class="track-link" target="_blank">
|
||||||
|
{{#favicon}}
|
||||||
|
<img class="favicon" src="{{favicon}}">
|
||||||
|
{{/favicon}}
|
||||||
|
{{host}}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/host}}
|
||||||
|
|
||||||
|
<div class="onebox-result-body">
|
||||||
|
{{#user.avatar_url}}
|
||||||
|
<a href="{{user.html_url}}" target="_blank">
|
||||||
|
<img alt="{{user.login}}" src="{{user.avatar_url}}">
|
||||||
|
</a>
|
||||||
|
{{/user.avatar_url}}
|
||||||
|
|
||||||
|
<h4>
|
||||||
|
<a href="{{html_url}}" target="_blank">{{title}}</a>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div class="date">
|
||||||
|
by <a href="{{user.html_url}}" target="_blank">{{user.login}}</a>
|
||||||
|
on <a href="{{html_url}}" target="_blank">{{created_at}}</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="github-commit-stats">
|
||||||
|
<strong>{{commits}} commits</strong>
|
||||||
|
changed <strong>{{changed_files}} files</strong>
|
||||||
|
with <strong>{{additions}} additions</strong>
|
||||||
|
and <strong>{{deletions}} deletions</strong>.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="clearfix"></div>
|
||||||
|
</div>
|
@ -73,12 +73,37 @@ class PostCreator
|
|||||||
@post
|
@post
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
# Shortcut
|
|
||||||
def self.create(user, opts)
|
def self.create(user, opts)
|
||||||
PostCreator.new(user, opts).create
|
PostCreator.new(user, opts).create
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.before_create_tasks(post)
|
||||||
|
if post.reply_to_post_number.present?
|
||||||
|
post.reply_to_user_id ||= Post.select(:user_id).where(topic_id: post.topic_id, post_number: post.reply_to_post_number).first.try(:user_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
post.post_number ||= Topic.next_post_number(post.topic_id, post.reply_to_post_number.present?)
|
||||||
|
post.cooked ||= post.cook(post.raw, topic_id: post.topic_id)
|
||||||
|
post.sort_order = post.post_number
|
||||||
|
DiscourseEvent.trigger(:before_create_post, post)
|
||||||
|
post.last_version_at ||= Time.now
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.after_create_tasks(post)
|
||||||
|
Rails.logger.info (">" * 30) + "#{post.no_bump} #{post.created_at}"
|
||||||
|
# Update attributes on the topic - featured users and last posted.
|
||||||
|
attrs = {last_posted_at: post.created_at, last_post_user_id: post.user_id}
|
||||||
|
attrs[:bumped_at] = post.created_at unless post.no_bump
|
||||||
|
post.topic.update_attributes(attrs)
|
||||||
|
|
||||||
|
# Update topic user data
|
||||||
|
TopicUser.change(post.user,
|
||||||
|
post.topic.id,
|
||||||
|
posted: true,
|
||||||
|
last_read_post_number: post.post_number,
|
||||||
|
seen_post_count: post.post_number)
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def secure_group_ids(topic)
|
def secure_group_ids(topic)
|
||||||
|
56
lib/validators/post_validator.rb
Normal file
56
lib/validators/post_validator.rb
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
class PostValidator < ActiveModel::Validator
|
||||||
|
def validate(record)
|
||||||
|
raw_quality(record)
|
||||||
|
max_mention_validator(record)
|
||||||
|
max_images_validator(record)
|
||||||
|
max_links_validator(record)
|
||||||
|
unique_post_validator(record)
|
||||||
|
end
|
||||||
|
|
||||||
|
def raw_quality(post)
|
||||||
|
sentinel = TextSentinel.body_sentinel(post.raw)
|
||||||
|
post.errors.add(:raw, I18n.t(:is_invalid)) unless sentinel.valid?
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ensure maximum amount of mentions in a post
|
||||||
|
def max_mention_validator(post)
|
||||||
|
if acting_user_is_trusted?(post)
|
||||||
|
add_error_if_count_exceeded(post, :too_many_mentions, post.raw_mentions.size, SiteSetting.max_mentions_per_post)
|
||||||
|
else
|
||||||
|
add_error_if_count_exceeded(post, :too_many_mentions_newuser, post.raw_mentions.size, SiteSetting.newuser_max_mentions_per_post)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ensure new users can not put too many images in a post
|
||||||
|
def max_images_validator(post)
|
||||||
|
add_error_if_count_exceeded(post, :too_many_images, post.image_count, SiteSetting.newuser_max_images) unless acting_user_is_trusted?(post)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Ensure new users can not put too many links in a post
|
||||||
|
def max_links_validator(post)
|
||||||
|
add_error_if_count_exceeded(post, :too_many_links, post.link_count, SiteSetting.newuser_max_links) unless acting_user_is_trusted?(post)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Stop us from posting the same thing too quickly
|
||||||
|
def unique_post_validator(post)
|
||||||
|
return if SiteSetting.unique_posts_mins == 0
|
||||||
|
return if post.acting_user.admin? || post.acting_user.moderator?
|
||||||
|
|
||||||
|
# If the post is empty, default to the validates_presence_of
|
||||||
|
return if post.raw.blank?
|
||||||
|
|
||||||
|
if $redis.exists(post.unique_post_key)
|
||||||
|
post.errors.add(:raw, I18n.t(:just_posted_that))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def acting_user_is_trusted?(post)
|
||||||
|
post.acting_user.present? && post.acting_user.has_trust_level?(:basic)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_error_if_count_exceeded(post, key_for_translation, current_count, max_count)
|
||||||
|
post.errors.add(:base, I18n.t(key_for_translation, count: max_count)) if current_count > max_count
|
||||||
|
end
|
||||||
|
end
|
17
spec/components/oneboxer/github_pullrequest_onebox_spec.rb
Normal file
17
spec/components/oneboxer/github_pullrequest_onebox_spec.rb
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
require 'oneboxer'
|
||||||
|
require 'oneboxer/github_pullrequest_onebox'
|
||||||
|
|
||||||
|
describe Oneboxer::GithubPullrequestOnebox do
|
||||||
|
describe '#translate_url' do
|
||||||
|
it 'returns the api url for the given pull request' do
|
||||||
|
onebox = described_class.new(
|
||||||
|
'https://github.com/discourse/discourse/pull/988'
|
||||||
|
)
|
||||||
|
expect(onebox.translate_url).to eq(
|
||||||
|
'https://api.github.com/repos/discourse/discourse/pulls/988'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
Reference in New Issue
Block a user