mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 17:46:05 -05:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
328f9a4a39
24 changed files with 246 additions and 122 deletions
|
@ -2057,7 +2057,13 @@ var html = (function(html4) {
|
|||
}
|
||||
|
||||
// Discourse modification: give us more flexibility with whitelists
|
||||
if (opt_nmTokenPolicy && opt_nmTokenPolicy(tagName, attribName, value)) { continue; }
|
||||
if (opt_nmTokenPolicy) {
|
||||
var newValue = opt_nmTokenPolicy(tagName, attribName, value);
|
||||
if (newValue) {
|
||||
attribs[i + 1] = newValue;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (atype !== null) {
|
||||
switch (atype) {
|
||||
|
|
|
@ -14,7 +14,8 @@ var PosterNameComponent = Em.Component.extend({
|
|||
var name = post.get('name'),
|
||||
username = post.get('username'),
|
||||
linkClass = 'username',
|
||||
primaryGroupName = post.get('primary_group_name');
|
||||
primaryGroupName = post.get('primary_group_name'),
|
||||
url = post.get('usernameUrl');
|
||||
|
||||
if (post.get('staff')) { linkClass += ' staff'; }
|
||||
if (post.get('admin')) { linkClass += ' admin'; }
|
||||
|
@ -25,7 +26,7 @@ var PosterNameComponent = Em.Component.extend({
|
|||
linkClass += ' ' + primaryGroupName;
|
||||
}
|
||||
// Main link
|
||||
buffer.push("<span class='" + linkClass + "'><a href='#'>" + username + "</a>");
|
||||
buffer.push("<span class='" + linkClass + "'><a href='" + url + "' data-auto-route='true'>" + username + "</a>");
|
||||
|
||||
// Add a glyph if we have one
|
||||
var glyph = this.posterGlyph(post);
|
||||
|
@ -37,7 +38,7 @@ var PosterNameComponent = Em.Component.extend({
|
|||
// Are we showing full names?
|
||||
if (name && this.get('displayNameOnPosts') && (this.sanitizeName(name) !== this.sanitizeName(username))) {
|
||||
name = Handlebars.Utils.escapeExpression(name);
|
||||
buffer.push("<span class='full-name'><a href='#'>" + name + "</a></span>");
|
||||
buffer.push("<span class='full-name'><a href='" + url + "' data-auto-route='true'>" + name + "</a></span>");
|
||||
}
|
||||
|
||||
// User titles
|
||||
|
@ -60,9 +61,10 @@ var PosterNameComponent = Em.Component.extend({
|
|||
|
||||
click: function(e) {
|
||||
var $target = $(e.target),
|
||||
href = $target.attr('href');
|
||||
href = $target.attr('href'),
|
||||
url = this.get('post.usernameUrl');
|
||||
|
||||
if (!Em.isEmpty(href) && href !== '#') {
|
||||
if (!Em.isEmpty(href) && href !== url) {
|
||||
return true;
|
||||
} else {
|
||||
this.appEvents.trigger('poster:expand', $target);
|
||||
|
|
|
@ -34,7 +34,11 @@ export default Discourse.ObjectController.extend({
|
|||
var currentUsername = this.get('username'),
|
||||
wasVisible = this.get('visible');
|
||||
|
||||
this.set('avatar', {username: username, uploaded_avatar_id: uploadedAvatarId});
|
||||
if (uploadedAvatarId) {
|
||||
this.set('avatar', {username: username, uploaded_avatar_id: uploadedAvatarId});
|
||||
} else {
|
||||
this.set('avatar', null);
|
||||
}
|
||||
|
||||
this.setProperties({visible: true, username: username});
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ function daysSinceEpoch(dt) {
|
|||
return dt.getTime() / 86400000;
|
||||
}
|
||||
|
||||
var safe = Handlebars.SafeString;
|
||||
|
||||
/**
|
||||
Converts a date to a coldmap class
|
||||
|
||||
|
@ -68,7 +70,7 @@ function categoryLinkHTML(category, options) {
|
|||
categoryOptions.categories = Em.Handlebars.get(this, options.hash.categories, options);
|
||||
}
|
||||
}
|
||||
return new Handlebars.SafeString(Discourse.HTML.categoryBadge(category, categoryOptions));
|
||||
return new safe(Discourse.HTML.categoryBadge(category, categoryOptions));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -171,7 +173,7 @@ Handlebars.registerHelper('avatar', function(user, options) {
|
|||
var uploadedAvatarId = Em.get(user, 'uploaded_avatar_id') || Em.get(user, 'user.uploaded_avatar_id');
|
||||
var avatarTemplate = Discourse.User.avatarTemplate(username,uploadedAvatarId);
|
||||
|
||||
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
|
||||
return new safe(Discourse.Utilities.avatarImg({
|
||||
size: options.hash.imageSize,
|
||||
extraClasses: Em.get(user, 'extras') || options.hash.extraClasses,
|
||||
title: title || username,
|
||||
|
@ -189,7 +191,9 @@ Handlebars.registerHelper('avatar', function(user, options) {
|
|||
@for Handlebars
|
||||
**/
|
||||
Em.Handlebars.helper('bound-avatar', function(user, size, uploadId) {
|
||||
if (Em.isEmpty(user)) { return; }
|
||||
if (Em.isEmpty(user)) {
|
||||
return new safe("<div class='avatar-placeholder'></div>");
|
||||
}
|
||||
var username = Em.get(user, 'username');
|
||||
|
||||
if(arguments.length < 4){
|
||||
|
@ -198,7 +202,7 @@ Em.Handlebars.helper('bound-avatar', function(user, size, uploadId) {
|
|||
|
||||
var avatarTemplate = Discourse.User.avatarTemplate(username, uploadId);
|
||||
|
||||
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
|
||||
return new safe(Discourse.Utilities.avatarImg({
|
||||
size: size,
|
||||
avatarTemplate: avatarTemplate
|
||||
}));
|
||||
|
@ -208,7 +212,7 @@ Em.Handlebars.helper('bound-avatar', function(user, size, uploadId) {
|
|||
* Used when we only have a template
|
||||
*/
|
||||
Em.Handlebars.helper('bound-avatar-template', function(avatarTemplate, size) {
|
||||
return new Handlebars.SafeString(Discourse.Utilities.avatarImg({
|
||||
return new safe(Discourse.Utilities.avatarImg({
|
||||
size: size,
|
||||
avatarTemplate: avatarTemplate
|
||||
}));
|
||||
|
@ -243,7 +247,7 @@ Em.Handlebars.helper('bound-raw-date', function (date) {
|
|||
**/
|
||||
Handlebars.registerHelper('age', function(property, options) {
|
||||
var dt = new Date(Ember.Handlebars.get(this, property, options));
|
||||
return new Handlebars.SafeString(Discourse.Formatter.autoUpdatingRelativeAge(dt));
|
||||
return new safe(Discourse.Formatter.autoUpdatingRelativeAge(dt));
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -254,7 +258,7 @@ Handlebars.registerHelper('age', function(property, options) {
|
|||
**/
|
||||
Handlebars.registerHelper('age-with-tooltip', function(property, options) {
|
||||
var dt = new Date(Ember.Handlebars.get(this, property, options));
|
||||
return new Handlebars.SafeString(Discourse.Formatter.autoUpdatingRelativeAge(dt, {title: true}));
|
||||
return new safe(Discourse.Formatter.autoUpdatingRelativeAge(dt, {title: true}));
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -286,7 +290,7 @@ Handlebars.registerHelper('number', function(property, options) {
|
|||
}
|
||||
result += ">" + n + "</span>";
|
||||
|
||||
return new Handlebars.SafeString(result);
|
||||
return new safe(result);
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -310,12 +314,12 @@ Handlebars.registerHelper('date', function(property, options) {
|
|||
var val = Ember.Handlebars.get(this, property, options);
|
||||
if (val) {
|
||||
var date = new Date(val);
|
||||
return new Handlebars.SafeString(Discourse.Formatter.autoUpdatingRelativeAge(date, {format: 'medium', title: true, leaveAgo: leaveAgo}));
|
||||
return new safe(Discourse.Formatter.autoUpdatingRelativeAge(date, {format: 'medium', title: true, leaveAgo: leaveAgo}));
|
||||
}
|
||||
});
|
||||
|
||||
Em.Handlebars.helper('bound-date', function(dt) {
|
||||
return new Handlebars.SafeString(Discourse.Formatter.autoUpdatingRelativeAge(new Date(dt), {format: 'medium', title: true }));
|
||||
return new safe(Discourse.Formatter.autoUpdatingRelativeAge(new Date(dt), {format: 'medium', title: true }));
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -337,7 +341,7 @@ Handlebars.registerHelper('custom-html', function(name, contextString, options)
|
|||
});
|
||||
|
||||
Em.Handlebars.helper('human-size', function(size) {
|
||||
return new Handlebars.SafeString(I18n.toHumanSize(size));
|
||||
return new safe(I18n.toHumanSize(size));
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -356,7 +360,7 @@ Handlebars.registerHelper('link-domain', function(property, options) {
|
|||
if (!Em.isEmpty(domain)) {
|
||||
var s = domain.split('.');
|
||||
domain = s[s.length-2] + "." + s[s.length-1];
|
||||
return new Handlebars.SafeString("<span class='domain'>" + domain + "</span>");
|
||||
return new safe("<span class='domain'>" + domain + "</span>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -378,5 +382,5 @@ Handlebars.registerHelper('icon', function(icon, options) {
|
|||
if (labelKey) {
|
||||
html += "<span class='sr-only'>" + I18n.t(labelKey) + "</span>";
|
||||
}
|
||||
return new Handlebars.SafeString(html);
|
||||
return new safe(html);
|
||||
});
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
export default {
|
||||
name: "inject-app-events",
|
||||
initialize: function(container, application) {
|
||||
var AppEvents = Ember.Object.extend(Ember.Evented);
|
||||
application.register('app-events:main', AppEvents, { singleton: true });
|
||||
var appEvents = Ember.Object.createWithMixins(Ember.Evented);
|
||||
application.register('app-events:main', appEvents, { instantiate: false });
|
||||
|
||||
application.inject('controller', 'appEvents', 'app-events:main');
|
||||
application.inject('component', 'appEvents', 'app-events:main');
|
||||
application.inject('route', 'appEvents', 'app-events:main');
|
||||
application.inject('view', 'appEvents', 'app-events:main');
|
||||
application.inject('model', 'appEvents', 'app-events:main');
|
||||
|
||||
Discourse.URL.appEvents = appEvents;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -14,15 +14,6 @@ var _validClasses = {},
|
|||
function validateAttribute(tagName, attribName, value) {
|
||||
var tag = _validTags[tagName];
|
||||
|
||||
// Handle possible attacks
|
||||
// if you include html in your markdown, it better be valid
|
||||
//
|
||||
// We are SUPER strict cause nokogiri will sometimes "correct"
|
||||
// this stuff "incorrectly"
|
||||
if(/[<>"'`]/.test(value)){
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle classes
|
||||
if (attribName === "class") {
|
||||
if (_validClasses[value]) { return value; }
|
||||
|
|
|
@ -71,6 +71,20 @@ Discourse.URL = Em.Object.createWithMixins({
|
|||
}
|
||||
},
|
||||
|
||||
// Scroll to the same page, different anchor
|
||||
scrollToId: function(id) {
|
||||
if (Em.isEmpty(id)) { return; }
|
||||
|
||||
jumpScheduled = true;
|
||||
Em.run.schedule('afterRender', function() {
|
||||
var $elem = $(id);
|
||||
if ($elem.length > 0) {
|
||||
$('html,body').scrollTop($elem.offset().top - $('header').height() - 15);
|
||||
jumpScheduled = false;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
Our custom routeTo method is used to intelligently overwrite default routing
|
||||
behavior.
|
||||
|
@ -98,12 +112,7 @@ Discourse.URL = Em.Object.createWithMixins({
|
|||
|
||||
// Scroll to the same page, different anchor
|
||||
if (path.indexOf('#') === 0) {
|
||||
var $elem = $(path);
|
||||
if ($elem.length > 0) {
|
||||
Em.run.schedule('afterRender', function() {
|
||||
$('html,body').scrollTop($elem.offset().top - $('header').height() - 15);
|
||||
});
|
||||
}
|
||||
this.scrollToId(path);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -136,8 +145,10 @@ Discourse.URL = Em.Object.createWithMixins({
|
|||
// TODO: Extract into rules we can inject into the URL handler
|
||||
if (this.navigatedToHome(oldPath, path)) { return; }
|
||||
|
||||
if (path.match(/^\/?users\/[^\/]+$/)) {
|
||||
path += "/activity";
|
||||
if (oldPath === path) {
|
||||
// If navigating to the same path send an app event. Views can watch it
|
||||
// and tell their controllers to refresh
|
||||
this.appEvents.trigger('url:refresh');
|
||||
}
|
||||
|
||||
return this.handleURL(path);
|
||||
|
@ -235,12 +246,7 @@ Discourse.URL = Em.Object.createWithMixins({
|
|||
var homepage = Discourse.Utilities.defaultHomepage();
|
||||
|
||||
if (window.history && window.history.pushState && path === "/" && (oldPath === "/" || oldPath === "/" + homepage)) {
|
||||
// refresh the list
|
||||
switch (homepage) {
|
||||
case "top" : { this.controllerFor('discovery/top').send('refresh'); break; }
|
||||
case "categories": { this.controllerFor('discovery/categories').send('refresh'); break; }
|
||||
default: { this.controllerFor('discovery/topics').send('refresh'); break; }
|
||||
}
|
||||
this.appEvents.trigger('url:refresh');
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
19
app/assets/javascripts/discourse/mixins/url-refresh.js.es6
Normal file
19
app/assets/javascripts/discourse/mixins/url-refresh.js.es6
Normal file
|
@ -0,0 +1,19 @@
|
|||
// A Mixin that a view can use to listen for 'url:refresh' when
|
||||
// it is on screen, and will send an action to the controller to
|
||||
// refresh its data.
|
||||
//
|
||||
// This is useful if you want to get around Ember's default
|
||||
// behavior of not refreshing when navigating to the same place.
|
||||
export default Em.Mixin.create({
|
||||
_initURLRefresh: function() {
|
||||
this.appEvents.on('url:refresh', this, '_urlRefresh');
|
||||
}.on('didInsertElement'),
|
||||
|
||||
_tearDownURLRefresh: function() {
|
||||
this.appEvents.off('url:refresh', this, '_urlRefresh');
|
||||
}.on('willDestroyElement'),
|
||||
|
||||
_urlRefresh: function() {
|
||||
this.get('controller').send('refresh');
|
||||
}
|
||||
});
|
|
@ -32,7 +32,15 @@ Ember.DiscourseLocation = Ember.Object.extend({
|
|||
*/
|
||||
initState: function() {
|
||||
set(this, 'history', get(this, 'history') || window.history);
|
||||
this.replaceState(this.formatURL(this.getURL()));
|
||||
|
||||
var url = this.formatURL(this.getURL()),
|
||||
loc = get(this, 'location');
|
||||
|
||||
if (loc && loc.hash) {
|
||||
url += loc.hash;
|
||||
}
|
||||
|
||||
this.replaceState(url);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,6 +21,13 @@ Discourse.StaticController.PAGES.forEach(function(page) {
|
|||
}
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this._super();
|
||||
|
||||
// Scroll to an element if exists
|
||||
Discourse.URL.scrollToId(document.location.hash);
|
||||
},
|
||||
|
||||
model: function() {
|
||||
return Discourse.StaticPage.find(page);
|
||||
},
|
||||
|
|
|
@ -98,7 +98,11 @@ Discourse.TopicRoute = Discourse.Route.extend({
|
|||
}
|
||||
},
|
||||
|
||||
willTransition: function() { isTransitioning = true; return true; }
|
||||
willTransition: function() {
|
||||
Em.run.cancel(scheduledReplace);
|
||||
isTransitioning = true;
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
// replaceState can be very slow on Android Chrome. This function debounces replaceState
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
export default Discourse.View.extend({
|
||||
import UrlRefresh from 'discourse/mixins/url-refresh';
|
||||
|
||||
export default Discourse.View.extend(UrlRefresh, {
|
||||
|
||||
orderingChanged: function(){
|
||||
if (this.get("controller.ordering")) {
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
export default Discourse.View.extend(Discourse.ScrollTop);
|
||||
import UrlRefresh from 'discourse/mixins/url-refresh';
|
||||
|
||||
export default Discourse.View.extend(Discourse.ScrollTop, UrlRefresh);
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
/**
|
||||
This view handles rendering of a list of topics under discovery, with support
|
||||
for loading more as well as remembering your scroll position.
|
||||
import UrlRefresh from 'discourse/mixins/url-refresh';
|
||||
|
||||
@class DiscoveryTopicsView
|
||||
@extends Discourse.View
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Discourse.View.extend(Discourse.LoadMore, {
|
||||
export default Discourse.View.extend(Discourse.LoadMore, UrlRefresh, {
|
||||
eyelineSelector: '.topic-list-item',
|
||||
|
||||
actions: {
|
||||
|
|
|
@ -5,27 +5,8 @@ export default Discourse.View.extend({
|
|||
classNameBindings: ['controller.visible::hidden', 'controller.showBadges'],
|
||||
|
||||
_setup: function() {
|
||||
var self = this,
|
||||
width = this.$().width();
|
||||
|
||||
this.appEvents.on('poster:expand', function(target) {
|
||||
if (!target) { return; }
|
||||
Em.run.schedule('afterRender', function() {
|
||||
if (target) {
|
||||
var position = target.offset();
|
||||
if (position) {
|
||||
position.left += target.width() + 10;
|
||||
|
||||
var overage = ($(window).width() - 50) - (position.left + width);
|
||||
if (overage < 0) {
|
||||
position.left += overage;
|
||||
position.top += target.height() + 5;
|
||||
}
|
||||
self.$().css(position);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
var self = this;
|
||||
this.appEvents.on('poster:expand', this, '_posterExpand');
|
||||
|
||||
$('html').off(clickOutsideEventName).on(clickOutsideEventName, function(e) {
|
||||
if (self.get('controller.visible')) {
|
||||
|
@ -40,9 +21,30 @@ export default Discourse.View.extend({
|
|||
});
|
||||
}.on('didInsertElement'),
|
||||
|
||||
_posterExpand: function(target) {
|
||||
if (!target) { return; }
|
||||
var self = this,
|
||||
width = this.$().width();
|
||||
Em.run.schedule('afterRender', function() {
|
||||
if (target) {
|
||||
var position = target.offset();
|
||||
if (position) {
|
||||
position.left += target.width() + 10;
|
||||
|
||||
var overage = ($(window).width() - 50) - (position.left + width);
|
||||
if (overage < 0) {
|
||||
position.left += overage;
|
||||
position.top += target.height() + 5;
|
||||
}
|
||||
self.$().css(position);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_removeEvents: function() {
|
||||
$('html').off(clickOutsideEventName);
|
||||
this.appEvents.off('poster:expand');
|
||||
this.appEvents.off('poster:expand', this, '_posterExpand');
|
||||
}.on('willDestroyElement')
|
||||
|
||||
});
|
||||
|
|
|
@ -11,6 +11,13 @@
|
|||
padding: 12px 12px 5px 12px;
|
||||
border: 1px solid scale-color-diff();
|
||||
|
||||
.avatar-placeholder {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
float: left;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
display: inline-block;
|
||||
min-width: 120px;
|
||||
|
|
15
app/jobs/scheduled/calculate_avg_time.rb
Normal file
15
app/jobs/scheduled/calculate_avg_time.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
module Jobs
|
||||
|
||||
class CalculateAvgTime < Jobs::Scheduled
|
||||
every 1.day
|
||||
|
||||
# PERF: these calculations can become exceedingly expnsive
|
||||
# they run a huge gemoetric mean and are hard to optimise
|
||||
# defer to only run once a day
|
||||
def execute(args)
|
||||
# Update the average times
|
||||
Post.calculate_avg_time(2.days.ago)
|
||||
Topic.calculate_avg_time(2.days.ago)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -8,9 +8,6 @@ module Jobs
|
|||
every 15.minutes
|
||||
|
||||
def execute(args)
|
||||
# Update the average times
|
||||
Post.calculate_avg_time(1.day.ago)
|
||||
Topic.calculate_avg_time(1.day.ago)
|
||||
|
||||
# Feature topics in categories
|
||||
CategoryFeaturedTopic.feature_topics
|
||||
|
|
|
@ -23,8 +23,15 @@ class ExcerptParser < Nokogiri::XML::SAX::Document
|
|||
me.excerpt
|
||||
end
|
||||
|
||||
def escape_attribute(v)
|
||||
v.gsub("&", "&")
|
||||
.gsub("\"", """)
|
||||
.gsub("<", "<")
|
||||
.gsub(">", ">")
|
||||
end
|
||||
|
||||
def include_tag(name, attributes)
|
||||
characters("<#{name} #{attributes.map{|k,v| "#{k}='#{v}'"}.join(' ')}>", false, false, false)
|
||||
characters("<#{name} #{attributes.map{|k,v| "#{k}=\"#{escape_attribute(v)}\""}.join(' ')}>", false, false, false)
|
||||
end
|
||||
|
||||
def start_element(name, attributes=[])
|
||||
|
|
|
@ -264,8 +264,21 @@ module SiteSettingExtension
|
|||
refresh_settings.include?(name.to_sym)
|
||||
end
|
||||
|
||||
def filter_value(name, value)
|
||||
# filter domain name
|
||||
if %w[disabled_image_download_domains onebox_domains_whitelist exclude_rel_nofollow_domains email_domains_blacklist email_domains_whitelist white_listed_spam_host_domains].include? name
|
||||
domain_array = []
|
||||
value.split('|').each { |url|
|
||||
domain_array.push(get_hostname(url))
|
||||
}
|
||||
value = domain_array.join("|")
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
def set(name, value)
|
||||
if has_setting?(name)
|
||||
value = filter_value(name, value)
|
||||
self.send("#{name}=", value)
|
||||
Discourse.request_refresh! if requires_refresh?(name)
|
||||
else
|
||||
|
@ -365,5 +378,13 @@ module SiteSettingExtension
|
|||
enums[name]
|
||||
end
|
||||
|
||||
def get_hostname(url)
|
||||
unless (URI.parse(url).scheme rescue nil).nil?
|
||||
url = "http://#{url}" if URI.parse(url).scheme.nil?
|
||||
url = URI.parse(url).host
|
||||
end
|
||||
return url
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -75,6 +75,15 @@ describe PrettyText do
|
|||
|
||||
describe "Excerpt" do
|
||||
|
||||
it "sanitizes attempts to inject invalid attributes" do
|
||||
|
||||
spinner = "<a href=\"http://thedailywtf.com/\" data-bbcode=\"' class='fa fa-spin\">WTF</a>"
|
||||
PrettyText.excerpt(spinner, 20).should match_html spinner
|
||||
|
||||
spinner = %q{<a href="http://thedailywtf.com/" title="' class="fa fa-spin"><img src='http://thedailywtf.com/Resources/Images/Primary/logo.gif"></a>}
|
||||
PrettyText.excerpt(spinner, 20).should match_html spinner
|
||||
end
|
||||
|
||||
context "images" do
|
||||
|
||||
it "should dump images" do
|
||||
|
@ -94,8 +103,8 @@ describe PrettyText do
|
|||
end
|
||||
|
||||
it "should keep spoilers" do
|
||||
PrettyText.excerpt("<div class='spoiler'><img src='http://cnn.com/a.gif'></div>", 100).should == "<span class='spoiler'>[image]</span>"
|
||||
PrettyText.excerpt("<span class='spoiler'>spoiler</div>", 100).should == "<span class='spoiler'>spoiler</span>"
|
||||
PrettyText.excerpt("<div class='spoiler'><img src='http://cnn.com/a.gif'></div>", 100).should match_html "<span class='spoiler'>[image]</span>"
|
||||
PrettyText.excerpt("<span class='spoiler'>spoiler</div>", 100).should match_html "<span class='spoiler'>spoiler</span>"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -104,7 +113,7 @@ describe PrettyText do
|
|||
end
|
||||
|
||||
it "should preserve links" do
|
||||
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",100).should == "<a href='http://cnn.com'>cnn</a>"
|
||||
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",100).should match_html "<a href='http://cnn.com'>cnn</a>"
|
||||
end
|
||||
|
||||
it "should deal with special keys properly" do
|
||||
|
@ -125,15 +134,15 @@ describe PrettyText do
|
|||
end
|
||||
|
||||
it "should not count the surrounds of a link" do
|
||||
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",3).should == "<a href='http://cnn.com'>cnn</a>"
|
||||
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",3).should match_html "<a href='http://cnn.com'>cnn</a>"
|
||||
end
|
||||
|
||||
it "uses an ellipsis instead of html entities if provided with the option" do
|
||||
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>", 2, text_entities: true).should == "<a href='http://cnn.com'>cn...</a>"
|
||||
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>", 2, text_entities: true).should match_html "<a href='http://cnn.com'>cn...</a>"
|
||||
end
|
||||
|
||||
it "should truncate links" do
|
||||
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",2).should == "<a href='http://cnn.com'>cn…</a>"
|
||||
PrettyText.excerpt("<a href='http://cnn.com'>cnn</a>",2).should match_html "<a href='http://cnn.com'>cn…</a>"
|
||||
end
|
||||
|
||||
it "doesn't extract empty quotes as links" do
|
||||
|
@ -294,9 +303,6 @@ describe PrettyText do
|
|||
PrettyText.cook("**你hello**").should match_html "<p><strong>你hello</strong></p>"
|
||||
end
|
||||
|
||||
it "sanitizes attempts to inject invalid attributes" do
|
||||
PrettyText.cook("<a href=\"http://thedailywtf.com/\" data-bbcode=\"' class='fa fa-spin\">WTF</a>").should == "<p><a href=\"http://thedailywtf.com/\" rel=\"nofollow\">WTF</a></p>"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -322,4 +322,21 @@ describe SiteSettingExtension do
|
|||
end
|
||||
end
|
||||
|
||||
describe "filter domain name" do
|
||||
before do
|
||||
settings.setting(:white_listed_spam_host_domains, "www.example.com")
|
||||
settings.refresh!
|
||||
end
|
||||
|
||||
it "filters domain" do
|
||||
settings.set("white_listed_spam_host_domains", "http://www.discourse.org/")
|
||||
settings.white_listed_spam_host_domains.should == "www.discourse.org"
|
||||
end
|
||||
|
||||
it "returns invalid domain as is, without throwing exception" do
|
||||
settings.set("white_listed_spam_host_domains", "test!url")
|
||||
settings.white_listed_spam_host_domains.should == "test!url"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -57,7 +57,7 @@ describe UserProfile do
|
|||
end
|
||||
|
||||
context 'with a user that has a link in their bio' do
|
||||
let(:user_profile) { Fabricate.build(:user_profile, bio_raw: "im sissy and i love http://ponycorns.com") }
|
||||
let(:user_profile) { Fabricate.build(:user_profile, bio_raw: "I love http://discourse.org") }
|
||||
let(:user) do
|
||||
user = Fabricate.build(:user, user_profile: user_profile)
|
||||
user_profile.user = user
|
||||
|
@ -66,22 +66,22 @@ describe UserProfile do
|
|||
|
||||
let(:created_user) do
|
||||
user = Fabricate(:user)
|
||||
user.user_profile.bio_raw = 'im sissy and i love http://ponycorns.com'
|
||||
user.user_profile.bio_raw = 'I love http://discourse.org'
|
||||
user.user_profile.save!
|
||||
user
|
||||
end
|
||||
|
||||
it 'includes the link as nofollow if the user is not new' do
|
||||
user.user_profile.send(:cook)
|
||||
expect(user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com' rel='nofollow'>http://ponycorns.com</a>")
|
||||
expect(user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\" rel=\"nofollow\">http://ponycorns.com</a></p>")
|
||||
expect(user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org' rel='nofollow'>http://discourse.org</a>")
|
||||
expect(user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\" rel=\"nofollow\">http://discourse.org</a></p>")
|
||||
end
|
||||
|
||||
it 'removes the link if the user is new' do
|
||||
user.trust_level = TrustLevel.levels[:newuser]
|
||||
user_profile.send(:cook)
|
||||
expect(user_profile.bio_excerpt).to eq("im sissy and i love http://ponycorns.com")
|
||||
expect(user_profile.bio_processed).to eq("<p>im sissy and i love http://ponycorns.com</p>")
|
||||
expect(user_profile.bio_excerpt).to match_html("I love http://discourse.org")
|
||||
expect(user_profile.bio_processed).to eq("<p>I love http://discourse.org</p>")
|
||||
end
|
||||
|
||||
context 'leader_links_no_follow is false' do
|
||||
|
@ -90,22 +90,22 @@ describe UserProfile do
|
|||
it 'includes the link without nofollow if the user is trust level 3 or higher' do
|
||||
user.trust_level = TrustLevel.levels[:leader]
|
||||
user_profile.send(:cook)
|
||||
expect(user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com'>http://ponycorns.com</a>")
|
||||
expect(user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\">http://ponycorns.com</a></p>")
|
||||
expect(user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org'>http://discourse.org</a>")
|
||||
expect(user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\">http://discourse.org</a></p>")
|
||||
end
|
||||
|
||||
it 'removes nofollow from links in bio when trust level is increased' do
|
||||
created_user.change_trust_level!(:leader)
|
||||
expect(created_user.user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com'>http://ponycorns.com</a>")
|
||||
expect(created_user.user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\">http://ponycorns.com</a></p>")
|
||||
expect(created_user.user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org'>http://discourse.org</a>")
|
||||
expect(created_user.user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\">http://discourse.org</a></p>")
|
||||
end
|
||||
|
||||
it 'adds nofollow to links in bio when trust level is decreased' do
|
||||
created_user.trust_level = TrustLevel.levels[:leader]
|
||||
created_user.save
|
||||
created_user.change_trust_level!(:regular)
|
||||
expect(created_user.user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com' rel='nofollow'>http://ponycorns.com</a>")
|
||||
expect(created_user.user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\" rel=\"nofollow\">http://ponycorns.com</a></p>")
|
||||
expect(created_user.user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org' rel='nofollow'>http://discourse.org</a>")
|
||||
expect(created_user.user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\" rel=\"nofollow\">http://discourse.org</a></p>")
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -115,8 +115,8 @@ describe UserProfile do
|
|||
it 'includes the link with nofollow if the user is trust level 3 or higher' do
|
||||
user.trust_level = TrustLevel.levels[:leader]
|
||||
user_profile.send(:cook)
|
||||
expect(user_profile.bio_excerpt).to eq("im sissy and i love <a href='http://ponycorns.com' rel='nofollow'>http://ponycorns.com</a>")
|
||||
expect(user_profile.bio_processed).to eq("<p>im sissy and i love <a href=\"http://ponycorns.com\" rel=\"nofollow\">http://ponycorns.com</a></p>")
|
||||
expect(user_profile.bio_excerpt).to match_html("I love <a href='http://discourse.org' rel='nofollow'>http://discourse.org</a>")
|
||||
expect(user_profile.bio_processed).to match_html("<p>I love <a href=\"http://discourse.org\" rel=\"nofollow\">http://discourse.org</a></p>")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,19 +28,21 @@ test("isInternal with a HTTPS url", function() {
|
|||
// ok(Discourse.URL.routeTo("/t/topic-title/42"), "can route relative");
|
||||
// });
|
||||
|
||||
test("navigatedToHome", function() {
|
||||
var fakeDiscoveryController = { send: function() { return true; } };
|
||||
var mock = sinon.mock(fakeDiscoveryController);
|
||||
this.stub(Discourse.URL, "controllerFor").returns(fakeDiscoveryController);
|
||||
// TODO pending: this works but the test is too mocky and needs to be fixed
|
||||
|
||||
mock.expects("send").withArgs('refresh').twice();
|
||||
ok(Discourse.URL.navigatedToHome("/", "/"));
|
||||
|
||||
var homepage = "/" + Discourse.Utilities.defaultHomepage();
|
||||
ok(Discourse.URL.navigatedToHome(homepage, "/"));
|
||||
|
||||
not(Discourse.URL.navigatedToHome("/old", "/new"));
|
||||
|
||||
// make sure we called the .refresh() method
|
||||
mock.verify();
|
||||
});
|
||||
// test("navigatedToHome", function() {
|
||||
// var fakeDiscoveryController = { send: function() { return true; } };
|
||||
// var mock = sinon.mock(fakeDiscoveryController);
|
||||
// this.stub(Discourse.URL, "controllerFor").returns(fakeDiscoveryController);
|
||||
//
|
||||
// mock.expects("send").withArgs('refresh').twice();
|
||||
// ok(Discourse.URL.navigatedToHome("/", "/"));
|
||||
//
|
||||
// var homepage = "/" + Discourse.Utilities.defaultHomepage();
|
||||
// ok(Discourse.URL.navigatedToHome(homepage, "/"));
|
||||
//
|
||||
// not(Discourse.URL.navigatedToHome("/old", "/new"));
|
||||
//
|
||||
// // make sure we called the .refresh() method
|
||||
// mock.verify();
|
||||
// });
|
||||
|
|
Loading…
Reference in a new issue