Merge branch 'master' into fix_whisper
This commit is contained in:
commit
9b885c039a
|
@ -55,4 +55,4 @@ install:
|
||||||
- bash -c "if [ '$RAILS_MASTER' == '1' ]; then bundle update --retry=3 --jobs=3 arel rails rails-observers seed-fu; fi"
|
- bash -c "if [ '$RAILS_MASTER' == '1' ]; then bundle update --retry=3 --jobs=3 arel rails rails-observers seed-fu; fi"
|
||||||
- bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3; fi"
|
- bash -c "if [ '$RAILS_MASTER' == '0' ]; then bundle install --without development --deployment --retry=3 --jobs=3; fi"
|
||||||
|
|
||||||
script: 'bundle exec rspec && bundle exec rake plugin:spec && bundle exec rake qunit:test'
|
script: "bundle exec rspec && bundle exec rake plugin:spec && bundle exec rake qunit:test['200000']"
|
||||||
|
|
|
@ -212,7 +212,7 @@ GEM
|
||||||
omniauth-twitter (1.2.1)
|
omniauth-twitter (1.2.1)
|
||||||
json (~> 1.3)
|
json (~> 1.3)
|
||||||
omniauth-oauth (~> 1.1)
|
omniauth-oauth (~> 1.1)
|
||||||
onebox (1.6.1)
|
onebox (1.6.2)
|
||||||
htmlentities (~> 4.3.4)
|
htmlentities (~> 4.3.4)
|
||||||
moneta (~> 0.8)
|
moneta (~> 0.8)
|
||||||
multi_json (~> 1.11)
|
multi_json (~> 1.11)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import debounce from 'discourse/lib/debounce';
|
import debounce from 'discourse/lib/debounce';
|
||||||
|
|
||||||
export default Ember.Controller.extend({
|
export default Ember.Controller.extend({
|
||||||
|
queryParams: ["filter"],
|
||||||
filter: null,
|
filter: null,
|
||||||
onlyOverridden: false,
|
onlyOverridden: false,
|
||||||
filtered: Ember.computed.notEmpty('filter'),
|
filtered: Ember.computed.notEmpty('filter'),
|
||||||
|
|
|
@ -6,12 +6,8 @@ export default Ember.Controller.extend({
|
||||||
fieldTypes: null,
|
fieldTypes: null,
|
||||||
createDisabled: Em.computed.gte('model.length', MAX_FIELDS),
|
createDisabled: Em.computed.gte('model.length', MAX_FIELDS),
|
||||||
|
|
||||||
arrangedContent: function() {
|
fieldSortOrder: ['position'],
|
||||||
return Ember.ArrayProxy.extend(Ember.SortableMixin).create({
|
sortedFields: Ember.computed.sort('model', 'fieldSortOrder'),
|
||||||
sortProperties: ['position'],
|
|
||||||
content: this.get('model')
|
|
||||||
});
|
|
||||||
}.property('model'),
|
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
createField() {
|
createField() {
|
||||||
|
@ -20,9 +16,9 @@ export default Ember.Controller.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
moveUp(f) {
|
moveUp(f) {
|
||||||
const idx = this.get('arrangedContent').indexOf(f);
|
const idx = this.get('sortedFields').indexOf(f);
|
||||||
if (idx) {
|
if (idx) {
|
||||||
const prev = this.get('arrangedContent').objectAt(idx-1);
|
const prev = this.get('sortedFields').objectAt(idx-1);
|
||||||
const prevPos = prev.get('position');
|
const prevPos = prev.get('position');
|
||||||
|
|
||||||
prev.update({ position: f.get('position') });
|
prev.update({ position: f.get('position') });
|
||||||
|
@ -31,9 +27,9 @@ export default Ember.Controller.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
moveDown(f) {
|
moveDown(f) {
|
||||||
const idx = this.get('arrangedContent').indexOf(f);
|
const idx = this.get('sortedFields').indexOf(f);
|
||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
const next = this.get('arrangedContent').objectAt(idx+1);
|
const next = this.get('sortedFields').objectAt(idx+1);
|
||||||
const nextPos = next.get('position');
|
const nextPos = next.get('position');
|
||||||
|
|
||||||
next.update({ position: f.get('position') });
|
next.update({ position: f.get('position') });
|
||||||
|
|
|
@ -83,6 +83,8 @@ export default function() {
|
||||||
this.route('show', { path: '/:badge_id' });
|
this.route('show', { path: '/:badge_id' });
|
||||||
});
|
});
|
||||||
|
|
||||||
this.route('adminPlugins', { path: '/plugins', resetNamespace: true });
|
this.route('adminPlugins', { path: '/plugins', resetNamespace: true }, function() {
|
||||||
|
this.route('index', { path: '/' });
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
<p class="desc">{{i18n 'admin.user_fields.help'}}</p>
|
<p class="desc">{{i18n 'admin.user_fields.help'}}</p>
|
||||||
|
|
||||||
{{#if model}}
|
{{#if model}}
|
||||||
{{#each arrangedContent as |uf|}}
|
{{#each sortedFields as |uf|}}
|
||||||
{{admin-user-field-item userField=uf
|
{{admin-user-field-item userField=uf
|
||||||
fieldTypes=fieldTypes
|
fieldTypes=fieldTypes
|
||||||
firstField=arrangedContent.firstObject
|
firstField=sortedFields.firstObject
|
||||||
lastField=arrangedContent.lastObject
|
lastField=sortedFields.lastObject
|
||||||
destroyAction="destroy"
|
destroyAction="destroy"
|
||||||
moveUpAction="moveUp"
|
moveUpAction="moveUp"
|
||||||
moveDownAction="moveDown"}}
|
moveDownAction="moveDown"}}
|
||||||
|
|
|
@ -135,7 +135,7 @@ export function buildResolver(baseName) {
|
||||||
},
|
},
|
||||||
|
|
||||||
findPluginTemplate(parsedName) {
|
findPluginTemplate(parsedName) {
|
||||||
var pluginParsedName = this.parseName(parsedName.fullName.replace("template:", "template:javascripts/"));
|
const pluginParsedName = this.parseName(parsedName.fullName.replace("template:", "template:javascripts/"));
|
||||||
return this.findTemplate(pluginParsedName);
|
return this.findTemplate(pluginParsedName);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ export default Ember.Component.extend({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.appEvents.trigger('modal:body-shown', this.getProperties('title'));
|
this.appEvents.trigger('modal:body-shown', this.getProperties('title', 'rawTitle'));
|
||||||
},
|
},
|
||||||
|
|
||||||
_flash(msg) {
|
_flash(msg) {
|
||||||
|
|
|
@ -19,6 +19,8 @@ export default Ember.Component.extend({
|
||||||
this.appEvents.on('modal:body-shown', data => {
|
this.appEvents.on('modal:body-shown', data => {
|
||||||
if (data.title) {
|
if (data.title) {
|
||||||
this.set('title', I18n.t(data.title));
|
this.set('title', I18n.t(data.title));
|
||||||
|
} else if (data.rawTitle) {
|
||||||
|
this.set('title', data.rawTitle);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -65,7 +65,7 @@ export default Ember.Component.extend({
|
||||||
|
|
||||||
const prevEvent = this.get('prevEvent');
|
const prevEvent = this.get('prevEvent');
|
||||||
if (prevEvent) {
|
if (prevEvent) {
|
||||||
this._topicScrolled(prevEvent);
|
Ember.run.scheduleOnce('afterRender', this, this._topicScrolled, prevEvent);
|
||||||
} else {
|
} else {
|
||||||
Ember.run.scheduleOnce('afterRender', this, this._updateProgressBar);
|
Ember.run.scheduleOnce('afterRender', this, this._updateProgressBar);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,8 +27,6 @@ export default Ember.Component.extend(bufferedRender({
|
||||||
}.property('disableActions'),
|
}.property('disableActions'),
|
||||||
|
|
||||||
buildBuffer(buffer) {
|
buildBuffer(buffer) {
|
||||||
const self = this;
|
|
||||||
|
|
||||||
const renderIcon = function(name, key, actionable) {
|
const renderIcon = function(name, key, actionable) {
|
||||||
const title = escapeExpression(I18n.t(`topic_statuses.${key}.help`)),
|
const title = escapeExpression(I18n.t(`topic_statuses.${key}.help`)),
|
||||||
startTag = actionable ? "a href" : "span",
|
startTag = actionable ? "a href" : "span",
|
||||||
|
@ -39,8 +37,8 @@ export default Ember.Component.extend(bufferedRender({
|
||||||
buffer.push(`<${startTag} title='${title}' class='topic-status'>${icon}</${endTag}>`);
|
buffer.push(`<${startTag} title='${title}' class='topic-status'>${icon}</${endTag}>`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderIconIf = function(conditionProp, name, key, actionable) {
|
const renderIconIf = (conditionProp, name, key, actionable) => {
|
||||||
if (!self.get(conditionProp)) { return; }
|
if (!this.get(conditionProp)) { return; }
|
||||||
renderIcon(name, key, actionable);
|
renderIcon(name, key, actionable);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ export default DiscoveryController.extend({
|
||||||
return Discourse.User.currentProp('staff');
|
return Discourse.User.currentProp('staff');
|
||||||
},
|
},
|
||||||
|
|
||||||
@computed("model.categories.@each.featuredTopics.length")
|
@computed("model.categories.[].featuredTopics.length")
|
||||||
latestTopicOnly() {
|
latestTopicOnly() {
|
||||||
return this.get("model.categories").find(c => c.get("featuredTopics.length") > 1) === undefined;
|
return this.get("model.categories").find(c => c.get("featuredTopics.length") > 1) === undefined;
|
||||||
},
|
},
|
||||||
|
|
|
@ -157,9 +157,7 @@ export default Ember.Controller.extend(CanCheckEmails, {
|
||||||
|
|
||||||
// Cook the bio for preview
|
// Cook the bio for preview
|
||||||
model.set('name', this.get('newNameInput'));
|
model.set('name', this.get('newNameInput'));
|
||||||
var options = {};
|
return model.save().then(() => {
|
||||||
|
|
||||||
return model.save(options).then(() => {
|
|
||||||
if (Discourse.User.currentProp('id') === model.get('id')) {
|
if (Discourse.User.currentProp('id') === model.get('id')) {
|
||||||
Discourse.User.currentProp('name', model.get('name'));
|
Discourse.User.currentProp('name', model.get('name'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,6 @@ import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
import { on, default as computed } from "ember-addons/ember-computed-decorators";
|
import { on, default as computed } from "ember-addons/ember-computed-decorators";
|
||||||
import Ember from 'ember';
|
import Ember from 'ember';
|
||||||
|
|
||||||
const SortableArrayProxy = Ember.ArrayProxy.extend(Ember.SortableMixin);
|
|
||||||
|
|
||||||
export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, {
|
export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, {
|
||||||
|
|
||||||
@on('init')
|
@on('init')
|
||||||
|
@ -20,12 +18,8 @@ export default Ember.Controller.extend(ModalFunctionality, Ember.Evented, {
|
||||||
return categories.map(c => bufProxy.create({ content: c }));
|
return categories.map(c => bufProxy.create({ content: c }));
|
||||||
},
|
},
|
||||||
|
|
||||||
categoriesOrdered: function() {
|
categoriesSorting: ['position'],
|
||||||
return SortableArrayProxy.create({
|
categoriesOrdered: Ember.computed.sort('categoriesBuffered', 'categoriesSorting'),
|
||||||
sortProperties: ['content.position'],
|
|
||||||
content: this.get('categoriesBuffered')
|
|
||||||
});
|
|
||||||
}.property('categoriesBuffered'),
|
|
||||||
|
|
||||||
showFixIndices: function() {
|
showFixIndices: function() {
|
||||||
const cats = this.get('categoriesOrdered');
|
const cats = this.get('categoriesOrdered');
|
||||||
|
|
|
@ -234,7 +234,7 @@ export function uploadLocation(url) {
|
||||||
} else {
|
} else {
|
||||||
var protocol = window.location.protocol + '//',
|
var protocol = window.location.protocol + '//',
|
||||||
hostname = window.location.hostname,
|
hostname = window.location.hostname,
|
||||||
port = ':' + window.location.port;
|
port = window.location.port ? ':' + window.location.port : '';
|
||||||
return protocol + hostname + port + url;
|
return protocol + hostname + port + url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,10 +146,9 @@ const ApplicationRoute = Discourse.Route.extend(OpenComposer, {
|
||||||
},
|
},
|
||||||
|
|
||||||
changeBulkTemplate(w) {
|
changeBulkTemplate(w) {
|
||||||
const controllerName = w.replace('modal/', ''),
|
const controllerName = w.replace('modal/', '');
|
||||||
factory = getOwner(this).lookupFactory('controller:' + controllerName);
|
const controller = getOwner(this).lookup('controller:' + controllerName);
|
||||||
|
this.render(w, {into: 'modal/topic-bulk-actions', outlet: 'bulkOutlet', controller: controller ? controllerName : 'topic-bulk-actions'});
|
||||||
this.render(w, {into: 'modal/topic-bulk-actions', outlet: 'bulkOutlet', controller: factory ? controllerName : 'topic-bulk-actions'});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createNewTopicViaParams(title, body, category_id, category, tags) {
|
createNewTopicViaParams(title, body, category_id, category, tags) {
|
||||||
|
|
|
@ -3,5 +3,5 @@ import UserAction from "discourse/models/user-action";
|
||||||
|
|
||||||
export default UserActivityStreamRoute.extend({
|
export default UserActivityStreamRoute.extend({
|
||||||
userActionType: UserAction.TYPES["likes_given"],
|
userActionType: UserAction.TYPES["likes_given"],
|
||||||
noContentHelpKey: 'no_likes_given'
|
noContentHelpKey: 'user_activity.no_likes_given'
|
||||||
});
|
});
|
||||||
|
|
|
@ -136,7 +136,7 @@ $tag-color: scale-color($primary, $lightness: 40%);
|
||||||
top: -0.1em;
|
top: -0.1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
header .discourse-tag {color: $tag-color !important; }
|
header .discourse-tag {color: $tag-color }
|
||||||
|
|
||||||
.list-tags {
|
.list-tags {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
|
|
@ -130,13 +130,15 @@ class UserNotifications < ActionMailer::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
# Now fetch some topics and posts to show
|
# Now fetch some topics and posts to show
|
||||||
topics_for_digest = Topic.for_digest(user, min_date, limit: SiteSetting.digest_topics + 3, top_order: true).to_a
|
topics_for_digest = Topic.for_digest(user, min_date, limit: SiteSetting.digest_topics + SiteSetting.digest_other_topics, top_order: true).to_a
|
||||||
|
|
||||||
@popular_topics = topics_for_digest[0,SiteSetting.digest_topics]
|
@popular_topics = topics_for_digest[0,SiteSetting.digest_topics]
|
||||||
@other_new_for_you = topics_for_digest.size > SiteSetting.digest_topics ? topics_for_digest[SiteSetting.digest_topics..-1] : []
|
@other_new_for_you = topics_for_digest.size > SiteSetting.digest_topics ? topics_for_digest[SiteSetting.digest_topics..-1] : []
|
||||||
|
|
||||||
@popular_posts = if SiteSetting.digest_posts > 0
|
@popular_posts = if SiteSetting.digest_posts > 0
|
||||||
Post.for_mailing_list(user, min_date)
|
Post.for_mailing_list(user, min_date)
|
||||||
|
.where('posts.post_type = ?', Post.types[:regular])
|
||||||
|
.where('posts.deleted_at IS NULL AND posts.hidden = false AND posts.user_deleted = false')
|
||||||
.where("posts.post_number > ? AND posts.score > ?", 1, 5.0)
|
.where("posts.post_number > ? AND posts.score > ?", 1, 5.0)
|
||||||
.order("posts.score DESC")
|
.order("posts.score DESC")
|
||||||
.limit(SiteSetting.digest_posts)
|
.limit(SiteSetting.digest_posts)
|
||||||
|
|
|
@ -20,7 +20,7 @@ class QuotedPost < ActiveRecord::Base
|
||||||
next if uniq[[topic_id,post_number]]
|
next if uniq[[topic_id,post_number]]
|
||||||
uniq[[topic_id,post_number]] = true
|
uniq[[topic_id,post_number]] = true
|
||||||
|
|
||||||
|
begin
|
||||||
# It would be so much nicer if we used post_id in quotes
|
# It would be so much nicer if we used post_id in quotes
|
||||||
results = exec_sql "INSERT INTO quoted_posts(post_id, quoted_post_id, created_at, updated_at)
|
results = exec_sql "INSERT INTO quoted_posts(post_id, quoted_post_id, created_at, updated_at)
|
||||||
SELECT :post_id, p.id, current_timestamp, current_timestamp
|
SELECT :post_id, p.id, current_timestamp, current_timestamp
|
||||||
|
@ -33,9 +33,9 @@ class QuotedPost < ActiveRecord::Base
|
||||||
", post_id: post.id, post_number: post_number, topic_id: topic_id
|
", post_id: post.id, post_number: post_number, topic_id: topic_id
|
||||||
|
|
||||||
results = results.to_a
|
results = results.to_a
|
||||||
|
ids << results[0]["quoted_post_id"].to_i if results.length > 0
|
||||||
if results.length > 0
|
rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation
|
||||||
ids << results[0]["quoted_post_id"].to_i
|
# it's fine
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ class TopicLink < ActiveRecord::Base
|
||||||
def self.topic_map(guardian, topic_id)
|
def self.topic_map(guardian, topic_id)
|
||||||
|
|
||||||
# Sam: complicated reports are really hard in AR
|
# Sam: complicated reports are really hard in AR
|
||||||
builder = SqlBuilder.new <<SQL
|
builder = SqlBuilder.new <<-SQL
|
||||||
SELECT ftl.url,
|
SELECT ftl.url,
|
||||||
COALESCE(ft.title, ftl.title) AS title,
|
COALESCE(ft.title, ftl.title) AS title,
|
||||||
ftl.link_topic_id,
|
ftl.link_topic_id,
|
||||||
|
@ -163,11 +163,8 @@ SQL
|
||||||
|
|
||||||
added_urls << url
|
added_urls << url
|
||||||
|
|
||||||
topic_link = TopicLink.find_by(topic_id: post.topic_id,
|
unless TopicLink.exists?(topic_id: post.topic_id, post_id: post.id, url: url)
|
||||||
post_id: post.id,
|
begin
|
||||||
url: url)
|
|
||||||
|
|
||||||
unless topic_link
|
|
||||||
TopicLink.create!(post_id: post.id,
|
TopicLink.create!(post_id: post.id,
|
||||||
user_id: post.user_id,
|
user_id: post.user_id,
|
||||||
topic_id: post.topic_id,
|
topic_id: post.topic_id,
|
||||||
|
@ -177,6 +174,9 @@ SQL
|
||||||
link_topic_id: topic_id,
|
link_topic_id: topic_id,
|
||||||
link_post_id: reflected_post.try(:id),
|
link_post_id: reflected_post.try(:id),
|
||||||
quote: link.is_quote)
|
quote: link.is_quote)
|
||||||
|
rescue ActiveRecord::RecordNotUnique, PG::UniqueViolation
|
||||||
|
# it's fine
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Create the reflection if we can
|
# Create the reflection if we can
|
||||||
|
@ -184,17 +184,14 @@ SQL
|
||||||
topic = Topic.find_by(id: topic_id)
|
topic = Topic.find_by(id: topic_id)
|
||||||
|
|
||||||
if topic && post.topic && post.topic.archetype != 'private_message' && topic.archetype != 'private_message'
|
if topic && post.topic && post.topic.archetype != 'private_message' && topic.archetype != 'private_message'
|
||||||
|
|
||||||
prefix = Discourse.base_url_no_prefix
|
prefix = Discourse.base_url_no_prefix
|
||||||
|
|
||||||
reflected_url = "#{prefix}#{post.topic.relative_url(post.post_number)}"
|
reflected_url = "#{prefix}#{post.topic.relative_url(post.post_number)}"
|
||||||
|
|
||||||
tl = TopicLink.find_by(topic_id: topic_id,
|
tl = TopicLink.find_by(topic_id: topic_id,
|
||||||
post_id: reflected_post.try(:id),
|
post_id: reflected_post.try(:id),
|
||||||
url: reflected_url)
|
url: reflected_url)
|
||||||
|
|
||||||
unless tl
|
unless tl
|
||||||
tl = TopicLink.create!(user_id: post.user_id,
|
tl = TopicLink.create(user_id: post.user_id,
|
||||||
topic_id: topic_id,
|
topic_id: topic_id,
|
||||||
post_id: reflected_post.try(:id),
|
post_id: reflected_post.try(:id),
|
||||||
url: reflected_url,
|
url: reflected_url,
|
||||||
|
@ -206,7 +203,7 @@ SQL
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
reflected_ids << tl.try(:id)
|
reflected_ids << tl.id if tl.persisted?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -129,13 +129,15 @@ body, table, td, th, h1, h2, h3 {font-family: Helvetica, Arial, sans-serif !impo
|
||||||
<img src="<%= t.user.small_avatar_url -%>" style="border-radius:50%;clear:both;display:block;float:none;height:50px;width:50px;margin:0;max-width:100%;outline:0;text-align:center;text-decoration:none;" align="center">
|
<img src="<%= t.user.small_avatar_url -%>" style="border-radius:50%;clear:both;display:block;float:none;height:50px;width:50px;margin:0;max-width:100%;outline:0;text-align:center;text-decoration:none;" align="center">
|
||||||
</td>
|
</td>
|
||||||
<td style="color:#0a0a0a;padding:0 16px 0 8px;text-align:left;">
|
<td style="color:#0a0a0a;padding:0 16px 0 8px;text-align:left;">
|
||||||
<h6 style="color:inherit;font-size:18px;font-weight:400;line-height:1.3;margin:0;padding:0;word-wrap:normal;"><%= t.user.try(:username) -%></h6>
|
<% if t.user %>
|
||||||
<% if t.user.try(:name).present? %>
|
<h6 style="color:inherit;font-size:18px;font-weight:400;line-height:1.3;margin:0;padding:0;word-wrap:normal;"><%= t.user.username -%></h6>
|
||||||
|
<% if SiteSetting.enable_names? && t.user.name.present? && t.user.name.downcase != t.user.username.downcase %>
|
||||||
<p style="color:#8f8f8f;line-height:1.3;margin:0 0 16px 0;padding:0;"><%= t.user.name -%></p>
|
<p style="color:#8f8f8f;line-height:1.3;margin:0 0 16px 0;padding:0;"><%= t.user.name -%></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<%- if show_image_with_url(t.image_url) -%>
|
<%- if show_image_with_url(t.image_url) -%>
|
||||||
<td style="margin:0;padding:0 16px 8px 8px;text-align:right;" align="right">
|
<td style="margin:0;padding:0 16px 0 8px;text-align:right;" align="right">
|
||||||
<img src="<%= url_for_email(t.image_url) -%>" height="64" style="margin:auto;max-height:64px;max-width:100%;outline:0;text-align:right;text-decoration:none;">
|
<img src="<%= url_for_email(t.image_url) -%>" height="64" style="margin:auto;max-height:64px;max-width:100%;outline:0;text-align:right;text-decoration:none;">
|
||||||
</td>
|
</td>
|
||||||
<%- end -%>
|
<%- end -%>
|
||||||
|
@ -147,7 +149,7 @@ body, table, td, th, h1, h2, h3 {font-family: Helvetica, Arial, sans-serif !impo
|
||||||
<table style="border-bottom:1px solid #f3f3f3;padding:0;text-align:left;vertical-align:top;width:100%">
|
<table style="border-bottom:1px solid #f3f3f3;padding:0;text-align:left;vertical-align:top;width:100%">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="color:#0a0a0a;line-height:1.3;padding:0 16px 16px 16px;text-align:left;width:100%;font-weight:normal;">
|
<td style="color:#0a0a0a;line-height:1.3;padding:0 16px 0 16px;text-align:left;width:100%;font-weight:normal;">
|
||||||
<%= email_excerpt(t.first_post.cooked) %>
|
<%= email_excerpt(t.first_post.cooked) %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -219,7 +221,7 @@ body, table, td, th, h1, h2, h3 {font-family: Helvetica, Arial, sans-serif !impo
|
||||||
<table style="background-color: #f3f3f3; width: 100%;">
|
<table style="background-color: #f3f3f3; width: 100%;">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h1 style="color:#0a0a0a;font-size:28px;line-height:1.3;text-align:center;">
|
<h1 style="color:#0a0a0a;font-size:28px;line-height:1.3;text-align:center;font-family:Helvetica,Arial,sans-serif;">
|
||||||
<%=t 'user_notifications.digest.popular_posts' %>
|
<%=t 'user_notifications.digest.popular_posts' %>
|
||||||
</h1>
|
</h1>
|
||||||
</td>
|
</td>
|
||||||
|
@ -261,8 +263,12 @@ body, table, td, th, h1, h2, h3 {font-family: Helvetica, Arial, sans-serif !impo
|
||||||
<img src="<%= post.user.small_avatar_url -%>" style="border-radius:50%;clear:both;display:block;height:50px;width:50px;outline:0;">
|
<img src="<%= post.user.small_avatar_url -%>" style="border-radius:50%;clear:both;display:block;height:50px;width:50px;outline:0;">
|
||||||
</td>
|
</td>
|
||||||
<td style="color:#0a0a0a;line-height:1.3;padding:0 8px 8px 8px;vertical-align:top;">
|
<td style="color:#0a0a0a;line-height:1.3;padding:0 8px 8px 8px;vertical-align:top;">
|
||||||
|
<% if post.user %>
|
||||||
<h6 style="color:inherit;font-size:16px;font-weight:400;margin:0;padding:0;text-align:left;word-wrap:normal;"><%= post.user.username -%></h6>
|
<h6 style="color:inherit;font-size:16px;font-weight:400;margin:0;padding:0;text-align:left;word-wrap:normal;"><%= post.user.username -%></h6>
|
||||||
|
<% if SiteSetting.enable_names? && post.user.name && post.user.name.downcase != post.user.username %>
|
||||||
<p style="color:#8f8f8f;font-size:16px;line-height:1.3;margin:0;padding:0;text-align:left;"><%= post.user.name -%></p>
|
<p style="color:#8f8f8f;font-size:16px;line-height:1.3;margin:0;padding:0;text-align:left;"><%= post.user.name -%></p>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td style="color:#0a0a0a;line-height:1.3;padding:0 8px 8px 8px;text-align:right;">
|
<td style="color:#0a0a0a;line-height:1.3;padding:0 8px 8px 8px;text-align:right;">
|
||||||
<p style="color:#8f8f8f;line-height:1.3;margin:0 0 10px 0;padding:0;text-align:right;">
|
<p style="color:#8f8f8f;line-height:1.3;margin:0 0 10px 0;padding:0;text-align:right;">
|
||||||
|
@ -279,23 +285,33 @@ body, table, td, th, h1, h2, h3 {font-family: Helvetica, Arial, sans-serif !impo
|
||||||
|
|
||||||
<div style="background-color:#f3f3f3">
|
<div style="background-color:#f3f3f3">
|
||||||
<table class="spacer" style="padding:0;text-align:left;vertical-align:top;width:100%">
|
<table class="spacer" style="padding:0;text-align:left;vertical-align:top;width:100%">
|
||||||
<tbody><tr><td height="40px" style="-moz-hyphens:auto;-webkit-hyphens:auto;border-collapse:collapse!important;color:#0a0a0a;font-size:40px;font-weight:400;hyphens:auto;line-height:40px;margin:0;mso-line-height-rule:exactly;padding:0;text-align:left;vertical-align:top;word-wrap:normal"> </td></tr></tbody>
|
<tbody><tr><td height="40" style="-moz-hyphens:auto;-webkit-hyphens:auto;border-collapse:collapse!important;color:#0a0a0a;font-size:40px;font-weight:400;hyphens:auto;line-height:40px;margin:0;mso-line-height-rule:exactly;padding:0;text-align:left;vertical-align:top;word-wrap:normal"> </td></tr></tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<!-- End of Popular Post -->
|
<!-- End of Popular Post -->
|
||||||
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td class="side-spacer" style="width:5%;padding:0;"> </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<% if @other_new_for_you.present? %>
|
<% if @other_new_for_you.present? %>
|
||||||
<center style="color:#0a0a0a;font-size:28px;font-weight:400;margin-bottom: 8px;"><%=t 'user_notifications.digest.more_new' %></center>
|
<center style="color:#0a0a0a;font-size:28px;font-weight:400;margin-bottom: 8px;font-family:Helvetica,Arial,sans-serif;"><%=t 'user_notifications.digest.more_new' %></center>
|
||||||
|
|
||||||
|
|
||||||
<%= digest_custom_html("above_popular_topics") %>
|
<%= digest_custom_html("above_popular_topics") %>
|
||||||
|
|
||||||
|
<table class="body" style="width:100%;background:#f3f3f3;border-spacing:0;border-collapse:collapse!important;font-family:Helvetica,Arial,sans-serif;font-size:16px;font-weight:200;line-height:1.3;padding:0;text-align:left;vertical-align:top;">
|
||||||
|
<tr>
|
||||||
|
<td class="side-spacer" style="width:5%;padding:0;"> </td>
|
||||||
|
<td align="center" valign="top" style="width:90%;border-collapse:collapse!important;margin:0;padding:0;">
|
||||||
|
|
||||||
<table style="padding:0;text-align:left;vertical-align:top;width:100%">
|
<table style="padding:0;text-align:left;vertical-align:top;width:100%">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
||||||
|
@ -342,6 +358,11 @@ body, table, td, th, h1, h2, h3 {font-family: Helvetica, Arial, sans-serif !impo
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td class="side-spacer" style="width:5%;padding:0;"> </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
<%= digest_custom_html("below_popular_topics") %>
|
<%= digest_custom_html("below_popular_topics") %>
|
||||||
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -87,5 +87,6 @@ RailsMultisite::ConnectionManagement.each_connection do
|
||||||
end
|
end
|
||||||
|
|
||||||
if Rails.configuration.multisite
|
if Rails.configuration.multisite
|
||||||
Rails.logger.instance_variable_get(:@chained).first.formatter = RailsMultisite::Formatter.new
|
chained = Rails.logger.instance_variable_get(:@chained)
|
||||||
|
chained && chained.first.formatter = RailsMultisite::Formatter.new
|
||||||
end
|
end
|
||||||
|
|
|
@ -1286,8 +1286,9 @@ en:
|
||||||
allow_animated_thumbnails: "Generates animated thumbnails of animated gifs."
|
allow_animated_thumbnails: "Generates animated thumbnails of animated gifs."
|
||||||
default_avatars: "URLs to avatars that will be used by default for new users until they change them."
|
default_avatars: "URLs to avatars that will be used by default for new users until they change them."
|
||||||
automatically_download_gravatars: "Download Gravatars for users upon account creation or email change."
|
automatically_download_gravatars: "Download Gravatars for users upon account creation or email change."
|
||||||
digest_topics: "The maximum number of topics to display in the email summary."
|
digest_topics: "The maximum number of popular topics to display in the email summary."
|
||||||
digest_posts: "The maximum number of popular posts to display in the email summary."
|
digest_posts: "The maximum number of popular posts to display in the email summary."
|
||||||
|
digest_other_topics: "The maximum number of topics to show in the 'New in topics and categories you follow' section of the email summary."
|
||||||
digest_min_excerpt_length: "Minimum post excerpt in the email summary, in characters."
|
digest_min_excerpt_length: "Minimum post excerpt in the email summary, in characters."
|
||||||
delete_digest_email_after_days: "Suppress summary emails for users not seen on the site for more than (n) days."
|
delete_digest_email_after_days: "Suppress summary emails for users not seen on the site for more than (n) days."
|
||||||
digest_suppress_categories: "Suppress these categories from summary emails."
|
digest_suppress_categories: "Suppress these categories from summary emails."
|
||||||
|
|
|
@ -588,6 +588,7 @@ email:
|
||||||
default: 5
|
default: 5
|
||||||
min: 1
|
min: 1
|
||||||
digest_posts: 3
|
digest_posts: 3
|
||||||
|
digest_other_topics: 5
|
||||||
delete_digest_email_after_days: 365
|
delete_digest_email_after_days: 365
|
||||||
digest_suppress_categories:
|
digest_suppress_categories:
|
||||||
type: category_list
|
type: category_list
|
||||||
|
@ -669,7 +670,7 @@ email:
|
||||||
reset_bounce_score_after_days: 30
|
reset_bounce_score_after_days: 30
|
||||||
attachment_content_type_blacklist:
|
attachment_content_type_blacklist:
|
||||||
type: list
|
type: list
|
||||||
default: "pkcs7"
|
default: "pkcs7|x-vcard"
|
||||||
attachment_filename_blacklist:
|
attachment_filename_blacklist:
|
||||||
type: list
|
type: list
|
||||||
default: "smime.p7s|signature.asc"
|
default: "smime.p7s|signature.asc"
|
||||||
|
|
|
@ -6,23 +6,42 @@ class PostgreSQLFallbackHandler
|
||||||
include Singleton
|
include Singleton
|
||||||
|
|
||||||
def initialize
|
def initialize
|
||||||
@master = {}
|
@masters_down = {}
|
||||||
@running = {}
|
@mutex = Mutex.new
|
||||||
@mutex = {}
|
|
||||||
@last_check = {}
|
|
||||||
|
|
||||||
setup!
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def verify_master
|
def verify_master
|
||||||
@mutex[namespace].synchronize do
|
synchronize { return if @thread && @thread.alive? }
|
||||||
return if running || recently_checked?
|
|
||||||
@running[namespace] = true
|
@thread = Thread.new do
|
||||||
|
while true do
|
||||||
|
begin
|
||||||
|
thread = Thread.new { initiate_fallback_to_master }
|
||||||
|
thread.join
|
||||||
|
break if synchronize { @masters_down.empty? }
|
||||||
|
sleep 10
|
||||||
|
ensure
|
||||||
|
thread.kill
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
current_namespace = namespace
|
def master_down?
|
||||||
Thread.new do
|
synchronize { @masters_down[namespace] }
|
||||||
RailsMultisite::ConnectionManagement.with_connection(current_namespace) do
|
end
|
||||||
|
|
||||||
|
def master_down=(args)
|
||||||
|
synchronize { @masters_down[namespace] = args }
|
||||||
|
end
|
||||||
|
|
||||||
|
def master_up(namespace)
|
||||||
|
synchronize { @masters_down.delete(namespace) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def initiate_fallback_to_master
|
||||||
|
@masters_down.keys.each do |key|
|
||||||
|
RailsMultisite::ConnectionManagement.with_connection(key) do
|
||||||
begin
|
begin
|
||||||
logger.warn "#{log_prefix}: Checking master server..."
|
logger.warn "#{log_prefix}: Checking master server..."
|
||||||
connection = ActiveRecord::Base.postgresql_connection(config)
|
connection = ActiveRecord::Base.postgresql_connection(config)
|
||||||
|
@ -32,54 +51,19 @@ class PostgreSQLFallbackHandler
|
||||||
ActiveRecord::Base.clear_all_connections!
|
ActiveRecord::Base.clear_all_connections!
|
||||||
logger.warn "#{log_prefix}: Master server is active. Reconnecting..."
|
logger.warn "#{log_prefix}: Master server is active. Reconnecting..."
|
||||||
|
|
||||||
if namespace == RailsMultisite::ConnectionManagement::DEFAULT
|
self.master_up(key)
|
||||||
ActiveRecord::Base.establish_connection(config)
|
|
||||||
else
|
|
||||||
RailsMultisite::ConnectionManagement.establish_connection(db: namespace)
|
|
||||||
end
|
|
||||||
|
|
||||||
Discourse.disable_readonly_mode
|
Discourse.disable_readonly_mode
|
||||||
self.master = true
|
|
||||||
end
|
end
|
||||||
rescue => e
|
rescue => e
|
||||||
if e.message.include?("could not connect to server")
|
|
||||||
logger.warn "#{log_prefix}: Connection to master PostgreSQL server failed with '#{e.message}'"
|
logger.warn "#{log_prefix}: Connection to master PostgreSQL server failed with '#{e.message}'"
|
||||||
else
|
|
||||||
raise e
|
|
||||||
end
|
|
||||||
ensure
|
|
||||||
@mutex[namespace].synchronize do
|
|
||||||
@last_check[namespace] = Time.zone.now
|
|
||||||
@running[namespace] = false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def master
|
# Use for testing
|
||||||
@master[namespace]
|
|
||||||
end
|
|
||||||
|
|
||||||
def master=(args)
|
|
||||||
@master[namespace] = args
|
|
||||||
end
|
|
||||||
|
|
||||||
def running
|
|
||||||
@running[namespace]
|
|
||||||
end
|
|
||||||
|
|
||||||
def setup!
|
def setup!
|
||||||
RailsMultisite::ConnectionManagement.all_dbs.each do |db|
|
@masters_down = {}
|
||||||
@master[db] = true
|
|
||||||
@running[db] = false
|
|
||||||
@mutex[db] = Mutex.new
|
|
||||||
@last_check[db] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def verify?
|
|
||||||
!master && !running
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -96,17 +80,13 @@ class PostgreSQLFallbackHandler
|
||||||
"#{self.class} [#{namespace}]"
|
"#{self.class} [#{namespace}]"
|
||||||
end
|
end
|
||||||
|
|
||||||
def recently_checked?
|
|
||||||
if @last_check[namespace]
|
|
||||||
Time.zone.now <= (@last_check[namespace] + 5.seconds)
|
|
||||||
else
|
|
||||||
false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def namespace
|
def namespace
|
||||||
RailsMultisite::ConnectionManagement.current_db
|
RailsMultisite::ConnectionManagement.current_db
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def synchronize
|
||||||
|
@mutex.synchronize { yield }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module ActiveRecord
|
module ActiveRecord
|
||||||
|
@ -115,7 +95,9 @@ module ActiveRecord
|
||||||
fallback_handler = ::PostgreSQLFallbackHandler.instance
|
fallback_handler = ::PostgreSQLFallbackHandler.instance
|
||||||
config = config.symbolize_keys
|
config = config.symbolize_keys
|
||||||
|
|
||||||
if fallback_handler.verify?
|
if fallback_handler.master_down?
|
||||||
|
fallback_handler.verify_master
|
||||||
|
|
||||||
connection = postgresql_connection(config.dup.merge({
|
connection = postgresql_connection(config.dup.merge({
|
||||||
host: config[:replica_host], port: config[:replica_port]
|
host: config[:replica_host], port: config[:replica_port]
|
||||||
}))
|
}))
|
||||||
|
@ -126,7 +108,8 @@ module ActiveRecord
|
||||||
begin
|
begin
|
||||||
connection = postgresql_connection(config)
|
connection = postgresql_connection(config)
|
||||||
rescue PG::ConnectionBad => e
|
rescue PG::ConnectionBad => e
|
||||||
fallback_handler.master = false
|
fallback_handler.master_down = true
|
||||||
|
fallback_handler.verify_master
|
||||||
raise e
|
raise e
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -141,20 +124,4 @@ module ActiveRecord
|
||||||
raise "Replica database server is not in recovery mode." if value == 'f'
|
raise "Replica database server is not in recovery mode." if value == 'f'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
module ConnectionAdapters
|
|
||||||
class PostgreSQLAdapter
|
|
||||||
set_callback :checkout, :before, :switch_back?
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def fallback_handler
|
|
||||||
@fallback_handler ||= ::PostgreSQLFallbackHandler.instance
|
|
||||||
end
|
|
||||||
|
|
||||||
def switch_back?
|
|
||||||
fallback_handler.verify_master if fallback_handler.verify?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -228,6 +228,7 @@ module Discourse
|
||||||
|
|
||||||
def self.keep_readonly_mode
|
def self.keep_readonly_mode
|
||||||
# extend the expiry by 1 minute every 30 seconds
|
# extend the expiry by 1 minute every 30 seconds
|
||||||
|
unless Rails.env.test?
|
||||||
Thread.new do
|
Thread.new do
|
||||||
while readonly_mode?
|
while readonly_mode?
|
||||||
$redis.expire(READONLY_MODE_KEY, READONLY_MODE_KEY_TTL)
|
$redis.expire(READONLY_MODE_KEY, READONLY_MODE_KEY_TTL)
|
||||||
|
@ -235,6 +236,7 @@ module Discourse
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.disable_readonly_mode(user_enabled: false)
|
def self.disable_readonly_mode(user_enabled: false)
|
||||||
key = user_enabled ? USER_READONLY_MODE_KEY : READONLY_MODE_KEY
|
key = user_enabled ? USER_READONLY_MODE_KEY : READONLY_MODE_KEY
|
||||||
|
|
|
@ -244,14 +244,21 @@ module Email
|
||||||
address_field.decoded
|
address_field.decoded
|
||||||
from_address = address_field.address
|
from_address = address_field.address
|
||||||
from_display_name = address_field.display_name.try(:to_s)
|
from_display_name = address_field.display_name.try(:to_s)
|
||||||
return [from_address.downcase, from_display_name] if from_address["@"]
|
return [from_address&.downcase, from_display_name&.strip] if from_address["@"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if mail.from[/<[^>]+>/]
|
||||||
from_address = mail.from[/<([^>]+)>/, 1]
|
from_address = mail.from[/<([^>]+)>/, 1]
|
||||||
from_display_name = mail.from[/^([^<]+)/, 1]
|
from_display_name = mail.from[/^([^<]+)/, 1]
|
||||||
|
end
|
||||||
|
|
||||||
[from_address.downcase, from_display_name]
|
if (from_address.blank? || !from_address["@"]) && mail.from[/\[mailto:[^\]]+\]/]
|
||||||
|
from_address = mail.from[/\[mailto:([^\]]+)\]/, 1]
|
||||||
|
from_display_name = mail.from[/^([^\[]+)/, 1]
|
||||||
|
end
|
||||||
|
|
||||||
|
[from_address&.downcase, from_display_name&.strip]
|
||||||
end
|
end
|
||||||
|
|
||||||
def subject
|
def subject
|
||||||
|
@ -376,6 +383,9 @@ module Email
|
||||||
def process_forwarded_email(destination, user)
|
def process_forwarded_email(destination, user)
|
||||||
embedded = Mail.new(@embedded_email_raw)
|
embedded = Mail.new(@embedded_email_raw)
|
||||||
email, display_name = parse_from_field(embedded)
|
email, display_name = parse_from_field(embedded)
|
||||||
|
|
||||||
|
return false if email.blank? || !email["@"]
|
||||||
|
|
||||||
embedded_user = find_or_create_user(email, display_name)
|
embedded_user = find_or_create_user(email, display_name)
|
||||||
raw = try_to_encode(embedded.decoded, "UTF-8").presence || embedded.to_s
|
raw = try_to_encode(embedded.decoded, "UTF-8").presence || embedded.to_s
|
||||||
title = embedded.subject.presence || subject
|
title = embedded.subject.presence || subject
|
||||||
|
@ -387,6 +397,7 @@ module Email
|
||||||
raw: raw,
|
raw: raw,
|
||||||
title: title,
|
title: title,
|
||||||
archetype: Archetype.private_message,
|
archetype: Archetype.private_message,
|
||||||
|
target_usernames: [user.username],
|
||||||
target_group_names: [group.name],
|
target_group_names: [group.name],
|
||||||
is_group_message: true,
|
is_group_message: true,
|
||||||
skip_validations: true,
|
skip_validations: true,
|
||||||
|
@ -409,11 +420,14 @@ module Email
|
||||||
end
|
end
|
||||||
|
|
||||||
if post && post.topic && @before_embedded.present?
|
if post && post.topic && @before_embedded.present?
|
||||||
|
post_type = Post.types[:regular]
|
||||||
|
post_type = Post.types[:whisper] if post.topic.private_message? && group.usernames[user.username]
|
||||||
|
|
||||||
create_reply(user: user,
|
create_reply(user: user,
|
||||||
raw: @before_embedded,
|
raw: @before_embedded,
|
||||||
post: post,
|
post: post,
|
||||||
topic: post.topic,
|
topic: post.topic,
|
||||||
post_type: Post.types[:whisper])
|
post_type: post_type)
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
true
|
||||||
|
|
|
@ -33,7 +33,7 @@ class SystemMessage
|
||||||
|
|
||||||
post = creator.create
|
post = creator.create
|
||||||
if creator.errors.present?
|
if creator.errors.present?
|
||||||
raise StandardError, creator.errors.to_s
|
raise StandardError, creator.errors.full_messages.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
UserArchivedMessage.create!(user: Discourse.site_contact_user, topic: post.topic)
|
UserArchivedMessage.create!(user: Discourse.site_contact_user, topic: post.topic)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
desc "Runs the qunit test suite"
|
desc "Runs the qunit test suite"
|
||||||
|
|
||||||
task "qunit:test" => :environment do
|
task "qunit:test", [:timeout] => :environment do |_, args|
|
||||||
|
|
||||||
require "rack"
|
require "rack"
|
||||||
require "socket"
|
require "socket"
|
||||||
|
@ -35,7 +35,7 @@ task "qunit:test" => :environment do
|
||||||
begin
|
begin
|
||||||
success = true
|
success = true
|
||||||
test_path = "#{Rails.root}/vendor/assets/javascripts"
|
test_path = "#{Rails.root}/vendor/assets/javascripts"
|
||||||
cmd = "phantomjs #{test_path}/run-qunit.js http://localhost:#{port}/qunit"
|
cmd = "phantomjs #{test_path}/run-qunit.js http://localhost:#{port}/qunit #{args[:timeout]}"
|
||||||
|
|
||||||
options = {}
|
options = {}
|
||||||
|
|
||||||
|
|
|
@ -89,7 +89,7 @@ function initializePolls(api) {
|
||||||
);
|
);
|
||||||
|
|
||||||
$poll.replaceWith($div);
|
$poll.replaceWith($div);
|
||||||
Em.run.schedule('afterRender', () => pollComponent.renderer.replaceIn(pollComponent, $div[0]));
|
Em.run.schedule('afterRender', () => pollComponent.renderer.appendTo(pollComponent, $div[0]));
|
||||||
postPollViews[pollId] = pollComponent;
|
postPollViews[pollId] = pollComponent;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -48,8 +48,9 @@ describe ActiveRecord::ConnectionHandling do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should failover to a replica server' do
|
it 'should failover to a replica server' do
|
||||||
|
current_threads = Thread.list
|
||||||
|
|
||||||
RailsMultisite::ConnectionManagement.stubs(:all_dbs).returns(['default', multisite_db])
|
RailsMultisite::ConnectionManagement.stubs(:all_dbs).returns(['default', multisite_db])
|
||||||
::PostgreSQLFallbackHandler.instance.setup!
|
|
||||||
|
|
||||||
[config, multisite_config].each do |configuration|
|
[config, multisite_config].each do |configuration|
|
||||||
ActiveRecord::Base.expects(:postgresql_connection).with(configuration).raises(PG::ConnectionBad)
|
ActiveRecord::Base.expects(:postgresql_connection).with(configuration).raises(PG::ConnectionBad)
|
||||||
|
@ -60,7 +61,7 @@ describe ActiveRecord::ConnectionHandling do
|
||||||
})).returns(@replica_connection)
|
})).returns(@replica_connection)
|
||||||
end
|
end
|
||||||
|
|
||||||
expect(postgresql_fallback_handler.master).to eq(true)
|
expect(postgresql_fallback_handler.master_down?).to eq(nil)
|
||||||
|
|
||||||
expect { ActiveRecord::Base.postgresql_fallback_connection(config) }
|
expect { ActiveRecord::Base.postgresql_fallback_connection(config) }
|
||||||
.to raise_error(PG::ConnectionBad)
|
.to raise_error(PG::ConnectionBad)
|
||||||
|
@ -68,10 +69,10 @@ describe ActiveRecord::ConnectionHandling do
|
||||||
expect{ ActiveRecord::Base.postgresql_fallback_connection(config) }
|
expect{ ActiveRecord::Base.postgresql_fallback_connection(config) }
|
||||||
.to change{ Discourse.readonly_mode? }.from(false).to(true)
|
.to change{ Discourse.readonly_mode? }.from(false).to(true)
|
||||||
|
|
||||||
expect(postgresql_fallback_handler.master).to eq(false)
|
expect(postgresql_fallback_handler.master_down?).to eq(true)
|
||||||
|
|
||||||
with_multisite_db(multisite_db) do
|
with_multisite_db(multisite_db) do
|
||||||
expect(postgresql_fallback_handler.master).to eq(true)
|
expect(postgresql_fallback_handler.master_down?).to eq(nil)
|
||||||
|
|
||||||
expect { ActiveRecord::Base.postgresql_fallback_connection(multisite_config) }
|
expect { ActiveRecord::Base.postgresql_fallback_connection(multisite_config) }
|
||||||
.to raise_error(PG::ConnectionBad)
|
.to raise_error(PG::ConnectionBad)
|
||||||
|
@ -79,30 +80,18 @@ describe ActiveRecord::ConnectionHandling do
|
||||||
expect{ ActiveRecord::Base.postgresql_fallback_connection(multisite_config) }
|
expect{ ActiveRecord::Base.postgresql_fallback_connection(multisite_config) }
|
||||||
.to change{ Discourse.readonly_mode? }.from(false).to(true)
|
.to change{ Discourse.readonly_mode? }.from(false).to(true)
|
||||||
|
|
||||||
expect(postgresql_fallback_handler.master).to eq(false)
|
expect(postgresql_fallback_handler.master_down?).to eq(true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
postgresql_fallback_handler.master_up(multisite_db)
|
||||||
|
|
||||||
ActiveRecord::Base.unstub(:postgresql_connection)
|
ActiveRecord::Base.unstub(:postgresql_connection)
|
||||||
|
|
||||||
current_threads = Thread.list
|
postgresql_fallback_handler.initiate_fallback_to_master
|
||||||
|
|
||||||
expect{ ActiveRecord::Base.connection_pool.checkout }
|
|
||||||
.to change{ Thread.list.size }.by(1)
|
|
||||||
|
|
||||||
# Ensure that we don't try to connect back to the replica when a thread
|
|
||||||
# is running
|
|
||||||
begin
|
|
||||||
ActiveRecord::Base.postgresql_fallback_connection(config)
|
|
||||||
rescue PG::ConnectionBad => e
|
|
||||||
# This is expected if the thread finishes before the above is called.
|
|
||||||
end
|
|
||||||
|
|
||||||
# Wait for the thread to finish execution
|
|
||||||
(Thread.list - current_threads).each(&:join)
|
|
||||||
|
|
||||||
expect(Discourse.readonly_mode?).to eq(false)
|
expect(Discourse.readonly_mode?).to eq(false)
|
||||||
|
|
||||||
expect(PostgreSQLFallbackHandler.instance.master).to eq(true)
|
expect(postgresql_fallback_handler.master_down?).to eq(nil)
|
||||||
|
|
||||||
expect(ActiveRecord::Base.connection_pool.connections.count).to eq(0)
|
expect(ActiveRecord::Base.connection_pool.connections.count).to eq(0)
|
||||||
|
|
||||||
|
|
|
@ -383,6 +383,39 @@ describe Email::Receiver do
|
||||||
expect(Post.last.raw).to match(/discourse\.rb/)
|
expect(Post.last.raw).to match(/discourse\.rb/)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "handles forwarded emails" do
|
||||||
|
SiteSetting.enable_forwarded_emails = true
|
||||||
|
expect { process(:forwarded_email_1) }.to change(Topic, :count)
|
||||||
|
|
||||||
|
forwarded_post, last_post = *Post.last(2)
|
||||||
|
|
||||||
|
expect(forwarded_post.user.email).to eq("some@one.com")
|
||||||
|
expect(last_post.user.email).to eq("ba@bar.com")
|
||||||
|
|
||||||
|
expect(forwarded_post.raw).to match(/XoXo/)
|
||||||
|
expect(last_post.raw).to match(/can you have a look at this email below/)
|
||||||
|
|
||||||
|
expect(last_post.post_type).to eq(Post.types[:regular])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "handles weirdly forwarded emails" do
|
||||||
|
group.add(Fabricate(:user, email: "ba@bar.com"))
|
||||||
|
group.save
|
||||||
|
|
||||||
|
SiteSetting.enable_forwarded_emails = true
|
||||||
|
expect { process(:forwarded_email_2) }.to change(Topic, :count)
|
||||||
|
|
||||||
|
forwarded_post, last_post = *Post.last(2)
|
||||||
|
|
||||||
|
expect(forwarded_post.user.email).to eq("some@one.com")
|
||||||
|
expect(last_post.user.email).to eq("ba@bar.com")
|
||||||
|
|
||||||
|
expect(forwarded_post.raw).to match(/XoXo/)
|
||||||
|
expect(last_post.raw).to match(/can you have a look at this email below/)
|
||||||
|
|
||||||
|
expect(last_post.post_type).to eq(Post.types[:whisper])
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "new topic in a category" do
|
context "new topic in a category" do
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
Message-ID: <58@foo.bar.mail>
|
||||||
|
From: Ba Bar <ba@bar.com>
|
||||||
|
To: Team <team@bar.com>
|
||||||
|
Date: Mon, 1 Dec 2016 13:37:42 +0100
|
||||||
|
Subject: FW: Discoursing much?
|
||||||
|
|
||||||
|
@team, can you have a look at this email below?
|
||||||
|
|
||||||
|
From: Some One <some@one.com>
|
||||||
|
To: Ba Bar <ba@bar.com>
|
||||||
|
Date: Mon, 1 Dec 2016 00:13:37 +0100
|
||||||
|
Subject: Discoursing much?
|
||||||
|
|
||||||
|
Hello Ba Bar,
|
||||||
|
|
||||||
|
Discoursing much today?
|
||||||
|
|
||||||
|
XoXo
|
|
@ -0,0 +1,18 @@
|
||||||
|
Message-ID: <59@foo.bar.mail>
|
||||||
|
From: Ba Bar <ba@bar.com>
|
||||||
|
To: Team <team@bar.com>
|
||||||
|
Date: Mon, 1 Dec 2016 13:37:42 +0100
|
||||||
|
Subject: Re: Discoursing much?
|
||||||
|
|
||||||
|
@team, can you have a look at this email below?
|
||||||
|
|
||||||
|
From: Some One [mailto:some@one.com]
|
||||||
|
To: Ba Bar <ba@bar.com>
|
||||||
|
Date: Mon, 1 Dec 2016 00:13:37 +0100
|
||||||
|
Subject: Discoursing much?
|
||||||
|
|
||||||
|
Hello Ba Bar,
|
||||||
|
|
||||||
|
Discoursing much today?
|
||||||
|
|
||||||
|
XoXo
|
|
@ -155,8 +155,7 @@ describe UserNotifications do
|
||||||
context "with new topics" do
|
context "with new topics" do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
Topic.stubs(:for_digest).returns([Fabricate(:topic, user: Fabricate(:coding_horror))])
|
Fabricate(:topic, user: Fabricate(:coding_horror))
|
||||||
Topic.stubs(:new_since_last_seen).returns(Topic.none)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "works" do
|
it "works" do
|
||||||
|
@ -184,6 +183,28 @@ describe UserNotifications do
|
||||||
expect(html).to_not include deleted.title
|
expect(html).to_not include deleted.title
|
||||||
expect(html).to_not include post.raw
|
expect(html).to_not include post.raw
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "excludes whispers and other post types that don't belong" do
|
||||||
|
t = Fabricate(:topic, user: Fabricate(:user), title: "Who likes the same stuff I like?")
|
||||||
|
whisper = Fabricate(:post, topic: t, score: 100.0, post_number: 2, raw: "You like weird stuff", post_type: Post.types[:whisper])
|
||||||
|
mod_action = Fabricate(:post, topic: t, score: 100.0, post_number: 3, raw: "This topic unlisted", post_type: Post.types[:moderator_action])
|
||||||
|
small_action = Fabricate(:post, topic: t, score: 100.0, post_number: 4, raw: "A small action", post_type: Post.types[:small_action])
|
||||||
|
html = subject.html_part.body.to_s
|
||||||
|
expect(html).to_not include whisper.raw
|
||||||
|
expect(html).to_not include mod_action.raw
|
||||||
|
expect(html).to_not include small_action.raw
|
||||||
|
end
|
||||||
|
|
||||||
|
it "excludes deleted and hidden posts" do
|
||||||
|
t = Fabricate(:topic, user: Fabricate(:user), title: "Post objectionable stuff here")
|
||||||
|
deleted = Fabricate(:post, topic: t, score: 100.0, post_number: 2, raw: "This post is uncalled for", deleted_at: 5.minutes.ago)
|
||||||
|
hidden = Fabricate(:post, topic: t, score: 100.0, post_number: 3, raw: "Try to find this post", hidden: true, hidden_at: 5.minutes.ago, hidden_reason_id: Post.hidden_reasons[:flagged_by_tl3_user])
|
||||||
|
user_deleted = Fabricate(:post, topic: t, score: 100.0, post_number: 4, raw: "I regret this post", user_deleted: true)
|
||||||
|
html = subject.html_part.body.to_s
|
||||||
|
expect(html).to_not include deleted.raw
|
||||||
|
expect(html).to_not include hidden.raw
|
||||||
|
expect(html).to_not include user_deleted.raw
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,8 +15,14 @@ describe QuotedPost do
|
||||||
post1 = Fabricate(:post)
|
post1 = Fabricate(:post)
|
||||||
post2 = Fabricate(:post)
|
post2 = Fabricate(:post)
|
||||||
|
|
||||||
post2.cooked = <<HTML
|
post2.cooked = <<-HTML
|
||||||
<aside class="quote" data-post="#{post1.post_number}" data-topic="#{post1.topic_id}"><div class="title"><div class="quote-controls"></div><img width="20" height="20" src="/user_avatar/meta.discourse.org/techapj/20/3281.png" class="avatar">techAPJ:</div><blockquote><p>When the user will v</p></blockquote></aside>
|
<aside class="quote" data-post="#{post1.post_number}" data-topic="#{post1.topic_id}">
|
||||||
|
<div class="title">
|
||||||
|
<div class="quote-controls"></div>
|
||||||
|
<img width="20" height="20" src="/user_avatar/meta.discourse.org/techapj/20/3281.png" class="avatar">techAPJ:
|
||||||
|
</div>
|
||||||
|
<blockquote><p>When the user will v</p></blockquote>
|
||||||
|
</aside>
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
QuotedPost.create!(post_id: post2.id, quoted_post_id: 999)
|
QuotedPost.create!(post_id: post2.id, quoted_post_id: 999)
|
||||||
|
|
|
@ -36,7 +36,7 @@ page.open(args[0], function(status) {
|
||||||
} else {
|
} else {
|
||||||
page.evaluate(logQUnit);
|
page.evaluate(logQUnit);
|
||||||
|
|
||||||
var timeout = parseInt(args[1] || 130000, 10),
|
var timeout = parseInt(args[1] || 200000, 10),
|
||||||
start = Date.now();
|
start = Date.now();
|
||||||
|
|
||||||
var interval = setInterval(function() {
|
var interval = setInterval(function() {
|
||||||
|
|
Loading…
Reference in New Issue