Version bump
This commit is contained in:
commit
d330f9b62f
4
Gemfile
4
Gemfile
@ -52,7 +52,7 @@ gem 'ember-source', '1.12.2'
|
||||
gem 'barber'
|
||||
gem 'babel-transpiler'
|
||||
|
||||
gem 'message_bus', '2.0.0.beta.4'
|
||||
gem 'message_bus', '2.0.0.beta.5'
|
||||
|
||||
gem 'rails_multisite'
|
||||
|
||||
@ -66,7 +66,7 @@ gem 'aws-sdk', require: false
|
||||
gem 'excon', require: false
|
||||
gem 'unf', require: false
|
||||
|
||||
gem 'email_reply_trimmer', '0.0.8'
|
||||
gem 'email_reply_trimmer', '0.1.1'
|
||||
|
||||
# note: for image_optim to correctly work you need to follow
|
||||
# https://github.com/toy/image_optim
|
||||
|
||||
@ -76,7 +76,7 @@ GEM
|
||||
docile (1.1.5)
|
||||
domain_name (0.5.25)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
email_reply_trimmer (0.0.8)
|
||||
email_reply_trimmer (0.1.1)
|
||||
ember-data-source (1.0.0.beta.16.1)
|
||||
ember-source (~> 1.8)
|
||||
ember-handlebars-template (0.1.5)
|
||||
@ -157,7 +157,7 @@ GEM
|
||||
mail (2.6.3)
|
||||
mime-types (>= 1.16, < 3)
|
||||
memory_profiler (0.9.6)
|
||||
message_bus (2.0.0.beta.4)
|
||||
message_bus (2.0.0.beta.5)
|
||||
rack (>= 1.1.3)
|
||||
metaclass (0.0.4)
|
||||
method_source (0.8.2)
|
||||
@ -414,7 +414,7 @@ DEPENDENCIES
|
||||
byebug
|
||||
certified
|
||||
discourse-qunit-rails
|
||||
email_reply_trimmer (= 0.0.8)
|
||||
email_reply_trimmer (= 0.1.1)
|
||||
ember-rails
|
||||
ember-source (= 1.12.2)
|
||||
excon
|
||||
@ -438,7 +438,7 @@ DEPENDENCIES
|
||||
lru_redux
|
||||
mail
|
||||
memory_profiler
|
||||
message_bus (= 2.0.0.beta.4)
|
||||
message_bus (= 2.0.0.beta.5)
|
||||
mime-types
|
||||
minitest
|
||||
mocha
|
||||
|
||||
@ -96,3 +96,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{#if model.rejection_message}}
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="control-group">
|
||||
<label>{{i18n "admin.email.incoming_emails.modal.rejection_message"}}</label>
|
||||
<div class="controls">
|
||||
{{textarea value=model.rejection_message}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/if}}
|
||||
|
||||
@ -15,22 +15,44 @@ export default Ember.Component.extend({
|
||||
this._initFromTopicList(this.get('topicList'));
|
||||
}.observes('topicList.@each'),
|
||||
|
||||
_initFromTopicList: function(topicList) {
|
||||
_initFromTopicList(topicList) {
|
||||
if (topicList !== null) {
|
||||
this.set('topics', topicList.get('topics'));
|
||||
this.rerender();
|
||||
}
|
||||
},
|
||||
|
||||
init: function() {
|
||||
init() {
|
||||
this._super();
|
||||
var topicList = this.get('topicList');
|
||||
const topicList = this.get('topicList');
|
||||
if (topicList) {
|
||||
this._initFromTopicList(topicList);
|
||||
} else {
|
||||
// Without a topic list, we assume it's loaded always.
|
||||
this.set('loaded', true);
|
||||
}
|
||||
},
|
||||
|
||||
click(e) {
|
||||
// Mobile basic-topic-list doesn't use the `topic-list-item` view so
|
||||
// the event for the topic entrance is never wired up.
|
||||
if (!this.site.mobileView) { return; }
|
||||
|
||||
let target = $(e.target);
|
||||
|
||||
if (target.hasClass('posts-map')) {
|
||||
const topicId = target.closest('tr').attr('data-topic-id');
|
||||
if (topicId) {
|
||||
if (target.prop('tagName') !== 'A') {
|
||||
target = target.find('a');
|
||||
}
|
||||
|
||||
const topic = this.get('topics').findProperty('id', parseInt(topicId));
|
||||
this.sendAction('postsAction', {topic, position: target.offset()});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { on, observes } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
tagName: 'select',
|
||||
attributeBindings: ['tabindex'],
|
||||
@ -5,16 +7,6 @@ export default Ember.Component.extend({
|
||||
valueAttribute: 'id',
|
||||
nameProperty: 'name',
|
||||
|
||||
_buildData(o) {
|
||||
let result = "";
|
||||
if (this.resultAttributes) {
|
||||
this.resultAttributes.forEach(function(a) {
|
||||
result += "data-" + a + "=\"" + o.get(a) + "\" ";
|
||||
});
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
render(buffer) {
|
||||
const nameProperty = this.get('nameProperty');
|
||||
const none = this.get('none');
|
||||
@ -23,27 +15,27 @@ export default Ember.Component.extend({
|
||||
if (typeof none === "string") {
|
||||
buffer.push('<option value="">' + I18n.t(none) + "</option>");
|
||||
} else if (typeof none === "object") {
|
||||
buffer.push("<option value=\"\" " + this._buildData(none) + ">" + Em.get(none, nameProperty) + "</option>");
|
||||
buffer.push("<option value=\"\">" + Em.get(none, nameProperty) + "</option>");
|
||||
}
|
||||
|
||||
let selected = this.get('value');
|
||||
if (!Em.isNone(selected)) { selected = selected.toString(); }
|
||||
|
||||
if (this.get('content')) {
|
||||
const self = this;
|
||||
this.get('content').forEach(function(o) {
|
||||
let val = o[self.get('valueAttribute')];
|
||||
this.get('content').forEach(o => {
|
||||
let val = o[this.get('valueAttribute')];
|
||||
if (typeof val === "undefined") { val = o; }
|
||||
if (!Em.isNone(val)) { val = val.toString(); }
|
||||
|
||||
const selectedText = (val === selected) ? "selected" : "";
|
||||
const name = Ember.get(o, nameProperty) || o;
|
||||
buffer.push("<option " + selectedText + " value=\"" + val + "\" " + self._buildData(o) + ">" + Handlebars.Utils.escapeExpression(name) + "</option>");
|
||||
const name = Handlebars.Utils.escapeExpression(Ember.get(o, nameProperty) || o);
|
||||
buffer.push(`<option ${selectedText} value="${val}">${name}</option>`);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
valueChanged: function() {
|
||||
@observes('value')
|
||||
valueChanged() {
|
||||
const $combo = this.$(),
|
||||
val = this.get('value');
|
||||
|
||||
@ -52,19 +44,19 @@ export default Ember.Component.extend({
|
||||
} else {
|
||||
$combo.select2('val', null);
|
||||
}
|
||||
}.observes('value'),
|
||||
},
|
||||
|
||||
_rerenderOnChange: function() {
|
||||
@observes('content.@each')
|
||||
_rerenderOnChange() {
|
||||
this.rerender();
|
||||
}.observes('content.@each'),
|
||||
},
|
||||
|
||||
_initializeCombo: function() {
|
||||
@on('didInsertElement')
|
||||
_initializeCombo() {
|
||||
|
||||
// Workaround for https://github.com/emberjs/ember.js/issues/9813
|
||||
// Can be removed when fixed. Without it, the wrong option is selected
|
||||
this.$('option').each(function(i, o) {
|
||||
o.selected = !!$(o).attr('selected');
|
||||
});
|
||||
this.$('option').each((i, o) => o.selected = !!$(o).attr('selected'));
|
||||
|
||||
// observer for item names changing (optional)
|
||||
if (this.get('nameChanges')) {
|
||||
@ -84,10 +76,11 @@ export default Ember.Component.extend({
|
||||
this.set('value', val);
|
||||
});
|
||||
$elem.trigger('change');
|
||||
}.on('didInsertElement'),
|
||||
},
|
||||
|
||||
_destroyDropdown: function() {
|
||||
@on('willDestroyElement')
|
||||
_destroyDropdown() {
|
||||
this.$().select2('destroy');
|
||||
}.on('willDestroyElement')
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -37,6 +37,7 @@ class Toolbar {
|
||||
];
|
||||
|
||||
this.addButton({
|
||||
trimLeading: true,
|
||||
id: 'bold',
|
||||
group: 'fontStyles',
|
||||
shortcut: 'B',
|
||||
@ -44,6 +45,7 @@ class Toolbar {
|
||||
});
|
||||
|
||||
this.addButton({
|
||||
trimLeading: true,
|
||||
id: 'italic',
|
||||
group: 'fontStyles',
|
||||
shortcut: 'I',
|
||||
@ -134,7 +136,8 @@ class Toolbar {
|
||||
className: button.className || button.id,
|
||||
icon: button.icon || button.id,
|
||||
action: button.action || 'toolbarButton',
|
||||
perform: button.perform || Ember.K
|
||||
perform: button.perform || Ember.K,
|
||||
trimLeading: button.trimLeading
|
||||
};
|
||||
|
||||
if (button.sendAction) {
|
||||
@ -355,19 +358,26 @@ export default Ember.Component.extend({
|
||||
});
|
||||
},
|
||||
|
||||
_getSelected() {
|
||||
_getSelected(trimLeading) {
|
||||
if (!this.get('ready')) { return; }
|
||||
|
||||
const textarea = this.$('textarea.d-editor-input')[0];
|
||||
const value = textarea.value;
|
||||
const start = textarea.selectionStart;
|
||||
var start = textarea.selectionStart;
|
||||
let end = textarea.selectionEnd;
|
||||
|
||||
// Windows selects the space after a word when you double click
|
||||
// trim trailing spaces cause **test ** would be invalid
|
||||
while (end > start && /\s/.test(value.charAt(end-1))) {
|
||||
end--;
|
||||
}
|
||||
|
||||
if (trimLeading) {
|
||||
// trim leading spaces cause ** test** would be invalid
|
||||
while(end > start && /\s/.test(value.charAt(start))) {
|
||||
start++;
|
||||
}
|
||||
}
|
||||
|
||||
const selVal = value.substring(start, end);
|
||||
const pre = value.slice(0, start);
|
||||
const post = value.slice(end);
|
||||
@ -487,7 +497,7 @@ export default Ember.Component.extend({
|
||||
|
||||
actions: {
|
||||
toolbarButton(button) {
|
||||
const selected = this._getSelected();
|
||||
const selected = this._getSelected(button.trimLeading);
|
||||
const toolbarEvent = {
|
||||
selected,
|
||||
applySurround: (head, tail, exampleKey) => this._applySurround(selected, head, tail, exampleKey),
|
||||
@ -516,18 +526,26 @@ export default Ember.Component.extend({
|
||||
const link = this.get('link');
|
||||
const sel = this._lastSel;
|
||||
|
||||
const autoHttp = function(l){
|
||||
if (l.indexOf("://") === -1) {
|
||||
return "http://" + l;
|
||||
} else {
|
||||
return l;
|
||||
}
|
||||
};
|
||||
|
||||
if (Ember.isEmpty(link)) { return; }
|
||||
const m = / "([^"]+)"/.exec(link);
|
||||
if (m && m.length === 2) {
|
||||
const description = m[1];
|
||||
const remaining = link.replace(m[0], '');
|
||||
this._addText(sel, `[${description}](${remaining})`);
|
||||
this._addText(sel, `[${description}](${autoHttp(remaining)})`);
|
||||
} else {
|
||||
if (sel.value) {
|
||||
this._addText(sel, `[${sel.value}](${link})`);
|
||||
this._addText(sel, `[${sel.value}](${autoHttp(link)})`);
|
||||
} else {
|
||||
const desc = I18n.t('composer.link_description');
|
||||
this._addText(sel, `[${desc}](${link})`);
|
||||
this._addText(sel, `[${desc}](${autoHttp(link)})`);
|
||||
this._selectText(sel.start + 1, desc.length);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { iconHTML } from 'discourse/helpers/fa-icon';
|
||||
import Combobox from 'discourse/components/combo-box';
|
||||
import { on, observes } from 'ember-addons/ember-computed-decorators';
|
||||
|
||||
@ -11,20 +12,26 @@ export default Combobox.extend({
|
||||
const details = topic.get('details');
|
||||
|
||||
if (details.get('can_invite_to')) {
|
||||
content.push({ id: 'invite', name: I18n.t('topic.invite_reply.title') });
|
||||
content.push({ id: 'invite', icon: 'users', name: I18n.t('topic.invite_reply.title') });
|
||||
}
|
||||
|
||||
if (topic.get('bookmarked')) {
|
||||
content.push({ id: 'bookmark', name: I18n.t('bookmarked.clear_bookmarks') });
|
||||
content.push({ id: 'bookmark', icon: 'bookmark', name: I18n.t('bookmarked.clear_bookmarks') });
|
||||
} else {
|
||||
content.push({ id: 'bookmark', name: I18n.t('bookmarked.title') });
|
||||
content.push({ id: 'bookmark', icon: 'bookmark', name: I18n.t('bookmarked.title') });
|
||||
}
|
||||
content.push({ id: 'share', name: I18n.t('topic.share.title') });
|
||||
content.push({ id: 'share', icon: 'link', name: I18n.t('topic.share.title') });
|
||||
|
||||
if (details.get('can_flag_topic')) {
|
||||
content.push({ id: 'flag', name: I18n.t('topic.flag_topic.title') });
|
||||
content.push({ id: 'flag', icon: 'flag', name: I18n.t('topic.flag_topic.title') });
|
||||
}
|
||||
|
||||
this.comboTemplate = (item) => {
|
||||
const contentItem = content.findProperty('id', item.id);
|
||||
if (!contentItem) { return item.text; }
|
||||
return `${iconHTML(contentItem.icon)} ${item.text}`;
|
||||
};
|
||||
|
||||
this.set('content', content);
|
||||
},
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ export default Ember.Component.extend({
|
||||
return this.get('order') === "op_likes";
|
||||
}.property('order'),
|
||||
|
||||
click: function(e){
|
||||
click(e) {
|
||||
var self = this;
|
||||
var on = function(sel, callback){
|
||||
var target = $(e.target).closest(sel);
|
||||
|
||||
@ -5,8 +5,6 @@ export default Ember.Component.extend({
|
||||
layoutName: fmt('field.field_type', 'components/user-fields/%@'),
|
||||
|
||||
noneLabel: function() {
|
||||
if (!this.get('field.required')) {
|
||||
return 'user_fields.none';
|
||||
}
|
||||
}.property('field.required')
|
||||
return 'user_fields.none';
|
||||
}.property()
|
||||
});
|
||||
|
||||
@ -141,7 +141,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
fetchUserDetails() {
|
||||
if (Discourse.User.currentProp('staff') && this.get('model.username')) {
|
||||
const AdminUser = require('admin/models/admin-user').default;
|
||||
AdminUser.find(this.get('model.id')).then(user => this.set('userDetails', user));
|
||||
AdminUser.find(this.get('model.user_id')).then(user => this.set('userDetails', user));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,11 @@ var Tab = Em.Object.extend({
|
||||
@computed('name')
|
||||
location(name) {
|
||||
return 'group.' + name;
|
||||
},
|
||||
|
||||
@computed('name')
|
||||
message(name) {
|
||||
return I18n.t('groups.' + name);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -29,6 +29,25 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
Discourse.Post.showRevision(postId, postVersion).then(() => this.refresh(postId, postVersion));
|
||||
},
|
||||
|
||||
revert(post, postVersion) {
|
||||
post.revertToRevision(postVersion).then((result) => {
|
||||
this.refresh(post.get('id'), postVersion);
|
||||
if (result.topic) {
|
||||
post.set('topic.slug', result.topic.slug);
|
||||
post.set('topic.title', result.topic.title);
|
||||
post.set('topic.fancy_title', result.topic.fancy_title);
|
||||
}
|
||||
if (result.category_id) {
|
||||
post.set('topic.category', Discourse.Category.findById(result.category_id));
|
||||
}
|
||||
this.send("closeModal");
|
||||
}).catch(function(e) {
|
||||
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors && e.jqXHR.responseJSON.errors[0]) {
|
||||
bootbox.alert(e.jqXHR.responseJSON.errors[0]);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@computed('model.created_at')
|
||||
createdAtDate(createdAt) {
|
||||
return moment(createdAt).format("LLLL");
|
||||
@ -69,6 +88,11 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
return !prevHidden && this.currentUser && this.currentUser.get('staff');
|
||||
},
|
||||
|
||||
@computed()
|
||||
displayRevert() {
|
||||
return this.currentUser && this.currentUser.get('staff');
|
||||
},
|
||||
|
||||
isEitherRevisionHidden: Ember.computed.or("model.previous_hidden", "model.current_hidden"),
|
||||
|
||||
@computed('model.previous_hidden', 'model.current_hidden', 'displayingInline')
|
||||
@ -142,6 +166,8 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
hideVersion() { this.hide(this.get("model.post_id"), this.get("model.current_revision")); },
|
||||
showVersion() { this.show(this.get("model.post_id"), this.get("model.current_revision")); },
|
||||
|
||||
revertToVersion() { this.revert(this.get("post"), this.get("model.current_revision")); },
|
||||
|
||||
displayInline() { this.set("viewMode", "inline"); },
|
||||
displaySideBySide() { this.set("viewMode", "side_by_side"); },
|
||||
displaySideBySideMarkdown() { this.set("viewMode", "side_by_side_markdown"); }
|
||||
|
||||
@ -170,7 +170,7 @@ export default Ember.Controller.extend(ModalFunctionality, {
|
||||
userInvitedController.set('totalInvites', invite_model.invites.length);
|
||||
});
|
||||
} else if (this.get('isMessage') && result && result.user) {
|
||||
this.get('model.details.allowed_users').pushObject(result.user);
|
||||
this.get('model.details.allowed_users').pushObject(Ember.Object.create(result.user));
|
||||
}
|
||||
}).catch(function(e) {
|
||||
if (e.jqXHR.responseJSON && e.jqXHR.responseJSON.errors) {
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
**/
|
||||
Discourse.Dialect.inlineRegexp({
|
||||
start: '#',
|
||||
matcher: /^#([\w-:]{1,50})/i,
|
||||
matcher: /^#([\w-:]{1,101})/i,
|
||||
spaceOrTagBoundary: true,
|
||||
|
||||
emitter: function(matches) {
|
||||
|
||||
@ -110,7 +110,7 @@ function buildConnectorCache() {
|
||||
_connectorCache[outletName].removeObject(viewClass);
|
||||
} else {
|
||||
if (!/\.raw$/.test(uniqueName)) {
|
||||
viewClass = Em.View.extend({ classNames: [outletName + '-outlet', uniqueName] });
|
||||
viewClass = Ember.View.extend({ classNames: [outletName + '-outlet', uniqueName] });
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,8 +172,11 @@ Ember.HTMLBars._registerHelper('plugin-outlet', function(params, hash, options,
|
||||
// just shove it in.
|
||||
const viewClass = (childViews.length > 1) ? Ember.ContainerView : childViews[0];
|
||||
|
||||
const newHash = $.extend({}, viewInjections(env.data.view.container));
|
||||
if (hash.tagName) { newHash.tagName = hash.tagName; }
|
||||
|
||||
delete options.fn; // we don't need the default template since we have a connector
|
||||
env.helpers.view.helperFunction.call(this, [viewClass], viewInjections(env.data.view.container), options, env);
|
||||
env.helpers.view.helperFunction.call(this, [viewClass], newHash, options, env);
|
||||
|
||||
const cvs = env.data.view._childViews;
|
||||
if (childViews.length > 1 && cvs && cvs.length) {
|
||||
|
||||
@ -9,9 +9,10 @@ export default {
|
||||
const siteSettings = container.lookup('site-settings:main');
|
||||
const messageBus = container.lookup('message-bus:main');
|
||||
const keyValueStore = container.lookup('key-value-store:main');
|
||||
const currentUser = container.lookup('current-user:main');
|
||||
LogsNotice.reopenClass(Singleton, {
|
||||
createCurrent() {
|
||||
return this.create({ messageBus, keyValueStore, siteSettings});
|
||||
return this.create({ messageBus, keyValueStore, siteSettings, currentUser });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -12,7 +12,20 @@ export default {
|
||||
siteSettings = container.lookup('site-settings:main');
|
||||
|
||||
messageBus.alwaysLongPoll = Discourse.Environment === "development";
|
||||
messageBus.start();
|
||||
|
||||
// we do not want to start anything till document is complete
|
||||
messageBus.stop();
|
||||
// jQuery ready is called on "interactive" we want "complete"
|
||||
// Possibly change to document.addEventListener('readystatechange',...
|
||||
// but would only stop a handful of interval, message bus being delayed by
|
||||
// 500ms on load is fine. stuff that needs to catch up correctly should
|
||||
// pass in a position
|
||||
const interval = setInterval(()=>{
|
||||
if (document.readyState === "complete") {
|
||||
clearInterval(interval);
|
||||
messageBus.start();
|
||||
}
|
||||
},500);
|
||||
|
||||
messageBus.callbackInterval = siteSettings.anon_polling_interval;
|
||||
messageBus.backgroundCallbackInterval = siteSettings.background_polling_interval;
|
||||
|
||||
@ -169,7 +169,15 @@ Discourse.Dialect.addPreProcessor(function(text) {
|
||||
|
||||
var m;
|
||||
while ((m = _unicodeRegexp.exec(text)) !== null) {
|
||||
text = text.replace(m[0], ":" + _unicodeReplacements[m[0]] + ":");
|
||||
|
||||
var replacement = ":" + _unicodeReplacements[m[0]] + ":";
|
||||
|
||||
var before = text.charAt(m.index-1);
|
||||
if (!/\B/.test(before)) {
|
||||
replacement = "\u200b" + replacement;
|
||||
}
|
||||
|
||||
text = text.replace(m[0], replacement);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ export default function highlightSyntax($elem) {
|
||||
if (!path) { return; }
|
||||
|
||||
$(selector, $elem).each(function(i, e) {
|
||||
$(e).removeClass('lang-auto');
|
||||
loadScript(path).then(() => hljs.highlightBlock(e));
|
||||
});
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import { addButton } from 'discourse/widgets/post-menu';
|
||||
import { includeAttributes } from 'discourse/lib/transform-post';
|
||||
import { addToolbarCallback } from 'discourse/components/d-editor';
|
||||
import { addWidgetCleanCallback } from 'discourse/components/mount-widget';
|
||||
import { decorateWidget, changeSetting } from 'discourse/widgets/widget';
|
||||
import { createWidget, decorateWidget, changeSetting } from 'discourse/widgets/widget';
|
||||
import { onPageChange } from 'discourse/lib/page-tracker';
|
||||
import { preventCloak } from 'discourse/widgets/post-stream';
|
||||
|
||||
@ -89,6 +89,8 @@ class PluginApi {
|
||||
const src = Discourse.Emoji.urlFor(emoji);
|
||||
return dec.h('img', { className: 'emoji', attributes: { src } });
|
||||
});
|
||||
|
||||
iconBody = result.emoji.split('|').map(name => dec.attach('emoji', { name }));
|
||||
}
|
||||
|
||||
if (result.text) {
|
||||
@ -268,11 +270,20 @@ class PluginApi {
|
||||
preventCloak(postId) {
|
||||
preventCloak(postId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the widget creating ability to plugins. Plugins can
|
||||
* register their own plugins and attach them with decorators.
|
||||
* See `createWidget` in `discourse/widgets/widget` for more info.
|
||||
**/
|
||||
createWidget(name, args) {
|
||||
return createWidget(name, args);
|
||||
}
|
||||
}
|
||||
|
||||
let _pluginv01;
|
||||
function getPluginApi(version) {
|
||||
if (version === "0.1") {
|
||||
if (version === "0.1" || version === "0.2") {
|
||||
if (!_pluginv01) {
|
||||
_pluginv01 = new PluginApi(version, Discourse.__container__);
|
||||
}
|
||||
|
||||
@ -11,9 +11,7 @@ const DiscourseURL = Ember.Object.createWithMixins({
|
||||
return _jumpScheduled;
|
||||
},
|
||||
|
||||
/**
|
||||
Jumps to a particular post in the stream
|
||||
**/
|
||||
// Jumps to a particular post in the stream
|
||||
jumpToPost(postNumber, opts) {
|
||||
const holderId = `#post_${postNumber}`;
|
||||
const offset = () => {
|
||||
|
||||
@ -286,6 +286,10 @@ Category.reopenClass({
|
||||
return Discourse.ajax(`/c/${id}/show.json`);
|
||||
},
|
||||
|
||||
reloadBySlug(slug, parentSlug) {
|
||||
return parentSlug ? Discourse.ajax(`/c/${parentSlug}/${slug}/find_by_slug.json`) : Discourse.ajax(`/c/${slug}/find_by_slug.json`);
|
||||
},
|
||||
|
||||
search(term, opts) {
|
||||
var limit = 5;
|
||||
|
||||
|
||||
@ -130,6 +130,7 @@ const Group = Discourse.Model.extend({
|
||||
return Discourse.ajax(`/groups/${this.get('name')}/${type}.json`, { data: data }).then(posts => {
|
||||
return posts.map(p => {
|
||||
p.user = Discourse.User.create(p.user);
|
||||
p.topic = Discourse.Topic.create(p.topic);
|
||||
return Em.Object.create(p);
|
||||
});
|
||||
});
|
||||
|
||||
@ -271,6 +271,10 @@ const Post = RestModel.extend({
|
||||
json = Post.munge(json);
|
||||
this.set('actions_summary', json.actions_summary);
|
||||
}
|
||||
},
|
||||
|
||||
revertToRevision(version) {
|
||||
return Discourse.ajax(`/posts/${this.get('id')}/revisions/${version}/revert`, { type: 'PUT' });
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@ -2,6 +2,8 @@ import { filterQueryParams, findTopicList } from 'discourse/routes/build-topic-r
|
||||
import { queryParams } from 'discourse/controllers/discovery-sortable';
|
||||
import TopicList from 'discourse/models/topic-list';
|
||||
import PermissionType from 'discourse/models/permission-type';
|
||||
import CategoryList from 'discourse/models/category-list';
|
||||
import Category from 'discourse/models/category';
|
||||
|
||||
// A helper function to create a category route with parameters
|
||||
export default (filter, params) => {
|
||||
@ -9,7 +11,19 @@ export default (filter, params) => {
|
||||
queryParams,
|
||||
|
||||
model(modelParams) {
|
||||
return { category: Discourse.Category.findBySlug(modelParams.slug, modelParams.parentSlug) };
|
||||
const category = Category.findBySlug(modelParams.slug, modelParams.parentSlug);
|
||||
if (!category) {
|
||||
return Category.reloadBySlug(modelParams.slug, modelParams.parentSlug).then((atts) => {
|
||||
if (modelParams.parentSlug) {
|
||||
atts.category.parentCategory = Category.findBySlug(modelParams.parentSlug);
|
||||
}
|
||||
const record = this.store.createRecord('category', atts.category);
|
||||
record.setupGroupsAndPermissions();
|
||||
this.site.updateCategory(record);
|
||||
return { category: Category.findBySlug(modelParams.slug, modelParams.parentSlug) };
|
||||
});
|
||||
};
|
||||
return { category };
|
||||
},
|
||||
|
||||
afterModel(model, transition) {
|
||||
@ -38,7 +52,6 @@ export default (filter, params) => {
|
||||
_createSubcategoryList(category) {
|
||||
this._categoryList = null;
|
||||
if (Em.isNone(category.get('parentCategory')) && Discourse.SiteSettings.show_subcategory_list) {
|
||||
const CategoryList = require('discourse/models/category-list').default;
|
||||
return CategoryList.listForParent(this.store, category).then(list => this._categoryList = list);
|
||||
}
|
||||
|
||||
|
||||
@ -73,6 +73,7 @@ const TopicRoute = Discourse.Route.extend({
|
||||
showHistory(model) {
|
||||
showModal('history', { model });
|
||||
this.controllerFor('history').refresh(model.get("id"), "latest");
|
||||
this.controllerFor('history').set('post', model);
|
||||
this.controllerFor('modal').set('modalClass', 'history-modal');
|
||||
},
|
||||
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
{{#conditional-loading-spinner condition=loading}}
|
||||
{{#if topics}}
|
||||
{{topic-list
|
||||
showParticipants=showParticipants
|
||||
showPosters=showPosters
|
||||
hideCategory=hideCategory
|
||||
topics=topics
|
||||
expandExcerpts=expandExcerpts
|
||||
bulkSelectEnabled=bulkSelectEnabled
|
||||
canBulkSelect=canBulkSelect
|
||||
selected=selected
|
||||
}}
|
||||
{{topic-list showParticipants=showParticipants
|
||||
showPosters=showPosters
|
||||
hideCategory=hideCategory
|
||||
topics=topics
|
||||
expandExcerpts=expandExcerpts
|
||||
bulkSelectEnabled=bulkSelectEnabled
|
||||
canBulkSelect=canBulkSelect
|
||||
selected=selected}}
|
||||
{{else}}
|
||||
{{#unless loadingMore}}
|
||||
<div class='alert alert-info'>
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<a href="{{unbound post.user.userUrl}}" data-user-card="{{unbound post.user.username}}" class='avatar-link'><div class='avatar-wrapper'>{{avatar post.user imageSize="large" extraClasses="actor" ignoreTitle="true"}}</div></a>
|
||||
<span class='time'>{{format-date post.created_at leaveAgo="true"}}</span>
|
||||
<span class="title">
|
||||
<a href="{{unbound post.url}}">{{unbound post.title}}</a>
|
||||
{{topic-link post.topic}}
|
||||
</span>
|
||||
<span class="category">{{category-link post.category}}</span>
|
||||
<div class="group-member-info">
|
||||
|
||||
@ -1,19 +1,18 @@
|
||||
{{#unless skipHeader}}
|
||||
<thead>
|
||||
{{raw "topic-list-header"
|
||||
currentUser=currentUser
|
||||
canBulkSelect=canBulkSelect
|
||||
toggleInTitle=toggleInTitle
|
||||
hideCategory=hideCategory
|
||||
showPosters=showPosters
|
||||
showLikes=showLikes
|
||||
showOpLikes=showOpLikes
|
||||
showParticipants=showParticipants
|
||||
order=order
|
||||
ascending=ascending
|
||||
sortable=sortable
|
||||
bulkSelectEnabled=bulkSelectEnabled}}
|
||||
</thead>
|
||||
<thead>
|
||||
{{raw "topic-list-header" currentUser=currentUser
|
||||
canBulkSelect=canBulkSelect
|
||||
toggleInTitle=toggleInTitle
|
||||
hideCategory=hideCategory
|
||||
showPosters=showPosters
|
||||
showLikes=showLikes
|
||||
showOpLikes=showOpLikes
|
||||
showParticipants=showParticipants
|
||||
order=order
|
||||
ascending=ascending
|
||||
sortable=sortable
|
||||
bulkSelectEnabled=bulkSelectEnabled}}
|
||||
</thead>
|
||||
{{/unless}}
|
||||
<tbody>
|
||||
{{each topic in topics itemView="topic-list-item"}}
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
<ul class='action-list nav-stacked'>
|
||||
{{#each tabs as |tab|}}
|
||||
<li class="{{if tab.active 'active'}}">
|
||||
{{#link-to tab.location model}}
|
||||
{{tab.name}}
|
||||
{{#link-to tab.location model title=tab.message}}
|
||||
{{tab.message}}
|
||||
{{#if tab.count}}<span class='count'>({{tab.count}})</span>{{/if}}
|
||||
{{/link-to}}
|
||||
</li>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{{#if controller.bulkSelectEnabled}}
|
||||
<td class='star'>
|
||||
<input type="checkbox" class="bulk-select">
|
||||
</td>
|
||||
<td class='star'>
|
||||
<input type="checkbox" class="bulk-select">
|
||||
</td>
|
||||
{{/if}}
|
||||
|
||||
<td class='main-link clearfix' colspan="{{titleColSpan}}">
|
||||
@ -36,18 +36,18 @@
|
||||
{{#if controller.showLikes}}
|
||||
<td class="num likes">
|
||||
{{#if hasLikes}}
|
||||
<a href='{{topic.summaryUrl}}'>
|
||||
{{number topic.like_count}} <i class='fa fa-heart'></i></td>
|
||||
</a>
|
||||
<a href='{{topic.summaryUrl}}'>
|
||||
{{number topic.like_count}} <i class='fa fa-heart'></i></td>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if controller.showOpLikes}}
|
||||
<td class="num likes">
|
||||
{{#if hasOpLikes}}
|
||||
<a href='{{topic.summaryUrl}}'>
|
||||
{{number topic.op_like_count}} <i class='fa fa-heart'></i></td>
|
||||
</a>
|
||||
<a href='{{topic.summaryUrl}}'>
|
||||
{{number topic.op_like_count}} <i class='fa fa-heart'></i></td>
|
||||
</a>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
<table class="topic-list">
|
||||
<tbody>
|
||||
{{#each t in topics}}
|
||||
<tr {{bind-attr class="t.archived"}}>
|
||||
<tr {{bind-attr class="t.archived"}} data-topic-id={{t.id}}>
|
||||
<td>
|
||||
<div class='main-link'>
|
||||
{{topic-status topic=t}}
|
||||
|
||||
@ -10,12 +10,6 @@
|
||||
</div>
|
||||
{{d-button action="loadNextVersion" icon="forward" title="post.revisions.controls.next" disabled=loadNextDisabled}}
|
||||
{{d-button action="loadLastVersion" icon="fast-forward" title="post.revisions.controls.last" disabled=loadLastDisabled}}
|
||||
{{#if displayHide}}
|
||||
{{d-button action="hideVersion" icon="trash-o" title="post.revisions.controls.hide" class="btn-danger" disabled=loading}}
|
||||
{{/if}}
|
||||
{{#if displayShow}}
|
||||
{{d-button action="showVersion" icon="undo" title="post.revisions.controls.show" disabled=loading}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div id="display-modes">
|
||||
{{d-button action="displayInline" label="post.revisions.displays.inline.button" title="post.revisions.displays.inline.title" class=inlineClass}}
|
||||
@ -85,5 +79,15 @@
|
||||
<div class="row">
|
||||
{{{bodyDiff}}}
|
||||
</div>
|
||||
|
||||
{{#if displayRevert}}
|
||||
{{d-button action="revertToVersion" icon="undo" label="post.revisions.controls.revert" class="btn-danger" disabled=loading}}
|
||||
{{/if}}
|
||||
{{#if displayHide}}
|
||||
{{d-button action="hideVersion" icon="eye-slash" label="post.revisions.controls.hide" class="btn-danger" disabled=loading}}
|
||||
{{/if}}
|
||||
{{#if displayShow}}
|
||||
{{d-button action="showVersion" icon="eye" label="post.revisions.controls.show" disabled=loading}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -137,13 +137,12 @@
|
||||
<h3>{{{view.suggestedTitle}}}</h3>
|
||||
<div class="topics">
|
||||
{{#if model.isPrivateMessage}}
|
||||
{{basic-topic-list
|
||||
hideCategory="true"
|
||||
showPosters="true"
|
||||
topics=model.details.suggested_topics
|
||||
postsAction="showTopicEntrance"}}
|
||||
{{basic-topic-list hideCategory="true"
|
||||
showPosters="true"
|
||||
topics=model.details.suggested_topics
|
||||
postsAction="showTopicEntrance"}}
|
||||
{{else}}
|
||||
{{basic-topic-list topics=model.details.suggested_topics postsAction="showTopicEntrance"}}
|
||||
{{basic-topic-list topics=model.details.suggested_topics postsAction="showTopicEntrance"}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<h3>{{{view.browseMoreMessage}}}</h3>
|
||||
|
||||
@ -183,7 +183,9 @@
|
||||
{{preference-checkbox labelKey="user.email_in_reply_to" checked=model.user_option.email_in_reply_to}}
|
||||
{{preference-checkbox labelKey="user.email_private_messages" checked=model.user_option.email_private_messages}}
|
||||
{{preference-checkbox labelKey="user.email_direct" checked=model.user_option.email_direct}}
|
||||
<span class="pref-mailing-list-mode">{{preference-checkbox labelKey="user.mailing_list_mode" checked=model.user_option.mailing_list_mode}}</span>
|
||||
{{#unless siteSettings.disable_mailing_list_mode}}
|
||||
{{preference-checkbox labelKey="user.mailing_list_mode" checked=model.user_option.mailing_list_mode}}
|
||||
{{/unless}}
|
||||
{{preference-checkbox labelKey="user.email_always" checked=model.user_option.email_always}}
|
||||
{{#unless model.user_option.email_always}}
|
||||
<div class='instructions'>
|
||||
|
||||
@ -46,11 +46,14 @@
|
||||
{{#if currentUser.staff}}
|
||||
<li><a href={{model.adminPath}} class="btn">{{fa-icon "wrench"}}{{i18n 'admin.user.show_admin_profile'}}</a></li>
|
||||
{{/if}}
|
||||
{{plugin-outlet "user-profile-controls" tagName="li"}}
|
||||
|
||||
{{#if collapsedInfo}}
|
||||
{{#if viewingSelf}}
|
||||
<li><a {{action "expandProfile"}} href class="btn">{{fa-icon "angle-double-down"}}{{i18n 'user.expand_profile'}}</a></li>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
|
||||
@ -22,6 +22,20 @@ class DecoratorHelper {
|
||||
**/
|
||||
// h() is attached via `prototype` below
|
||||
|
||||
/**
|
||||
* Attach another widget inside this one.
|
||||
*
|
||||
* ```
|
||||
* return helper.attach('widget-name');
|
||||
* ```
|
||||
*/
|
||||
attach(name, attrs, state) {
|
||||
attrs = attrs || this.widget.attrs;
|
||||
state = state || this.widget.state;
|
||||
|
||||
return this.widget.attach(name, attrs, state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the model associated with this widget. When decorating
|
||||
* posts this will normally be the post.
|
||||
|
||||
9
app/assets/javascripts/discourse/widgets/emoji.js.es6
Normal file
9
app/assets/javascripts/discourse/widgets/emoji.js.es6
Normal file
@ -0,0 +1,9 @@
|
||||
import { createWidget } from 'discourse/widgets/widget';
|
||||
|
||||
export default createWidget('emoji', {
|
||||
tagName: 'img.emoji',
|
||||
|
||||
buildAttributes(attrs) {
|
||||
return { src: Discourse.Emoji.urlFor(attrs.name) };
|
||||
},
|
||||
});
|
||||
@ -21,27 +21,28 @@ export default createWidget('post-gutter', {
|
||||
|
||||
const seenTitles = {};
|
||||
|
||||
|
||||
let i = 0;
|
||||
while (i < links.length && result.length < toShow) {
|
||||
const l = links[i++];
|
||||
|
||||
let titleCount = 0;
|
||||
links.forEach(function(l) {
|
||||
let title = l.title;
|
||||
if (title && !seenTitles[title]) {
|
||||
seenTitles[title] = true;
|
||||
const linkBody = [new RawHtml({ html: `<span>${Discourse.Emoji.unescape(title)}</span>` })];
|
||||
if (l.clicks) {
|
||||
linkBody.push(h('span.badge.badge-notification.clicks', l.clicks.toString()));
|
||||
}
|
||||
titleCount++;
|
||||
if (result.length < toShow) {
|
||||
const linkBody = [new RawHtml({html: `<span>${Discourse.Emoji.unescape(title)}</span>`})];
|
||||
if (l.clicks) {
|
||||
linkBody.push(h('span.badge.badge-notification.clicks', l.clicks.toString()));
|
||||
}
|
||||
|
||||
const className = l.reflection ? 'inbound' : 'outbound';
|
||||
const link = h('a.track-link', { className, attributes: { href: l.url } }, linkBody);
|
||||
result.push(h('li', link));
|
||||
const className = l.reflection ? 'inbound' : 'outbound';
|
||||
const link = h('a.track-link', {className, attributes: {href: l.url}}, linkBody);
|
||||
result.push(h('li', link));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (state.collapsed) {
|
||||
const remaining = links.length - MAX_GUTTER_LINKS;
|
||||
const remaining = titleCount - MAX_GUTTER_LINKS;
|
||||
|
||||
if (remaining > 0) {
|
||||
result.push(h('li', h('a.toggle-more', I18n.t('post.more_links', {count: remaining}))));
|
||||
}
|
||||
|
||||
@ -348,12 +348,20 @@ createWidget('post-article', {
|
||||
return rows;
|
||||
},
|
||||
|
||||
_getTopicUrl() {
|
||||
const post = this.findAncestorModel();
|
||||
return post ? post.get('topic.url') : null;
|
||||
},
|
||||
|
||||
toggleReplyAbove() {
|
||||
const replyPostNumber = this.attrs.reply_to_post_number;
|
||||
|
||||
// jump directly on mobile
|
||||
if (this.attrs.mobileView) {
|
||||
DiscourseURL.jumpToPost(replyPostNumber);
|
||||
const topicUrl = this._getTopicUrl();
|
||||
if (topicUrl) {
|
||||
DiscourseURL.routeTo(`${topicUrl}/${replyPostNumber}`);
|
||||
}
|
||||
return Ember.RSVP.Promise.resolve();
|
||||
}
|
||||
|
||||
@ -361,8 +369,7 @@ createWidget('post-article', {
|
||||
this.state.repliesAbove = [];
|
||||
return Ember.RSVP.Promise.resolve();
|
||||
} else {
|
||||
const post = this.findAncestorModel();
|
||||
const topicUrl = post ? post.get('topic.url') : null;
|
||||
const topicUrl = this._getTopicUrl();
|
||||
return this.store.find('post-reply-history', { postId: this.attrs.id }).then(posts => {
|
||||
this.state.repliesAbove = posts.map((p) => {
|
||||
p.shareUrl = `${topicUrl}/${p.post_number}`;
|
||||
|
||||
@ -56,15 +56,6 @@ export default createWidget('poster-name', {
|
||||
contents.push(h('span.user-title', titleContents));
|
||||
}
|
||||
|
||||
// const cfs = attrs.userCustomFields;
|
||||
// if (cfs) {
|
||||
// _callbacks.forEach(cb => {
|
||||
// const result = cb(cfs, attrs);
|
||||
// if (result) {
|
||||
//
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
return contents;
|
||||
}
|
||||
});
|
||||
|
||||
@ -185,8 +185,8 @@ export default createWidget('topic-map', {
|
||||
tagName: 'div.topic-map',
|
||||
buildKey: attrs => `topic-map-${attrs.id}`,
|
||||
|
||||
defaultState() {
|
||||
return { collapsed: true };
|
||||
defaultState(attrs) {
|
||||
return { collapsed: !attrs.hasTopicSummary };
|
||||
},
|
||||
|
||||
html(attrs, state) {
|
||||
|
||||
@ -140,7 +140,7 @@ export default class Widget {
|
||||
if (prev && prev.state) {
|
||||
this.state = prev.state;
|
||||
} else {
|
||||
this.state = this.defaultState();
|
||||
this.state = this.defaultState(this.attrs, this.state);
|
||||
}
|
||||
|
||||
// Sometimes we pass state down from the parent
|
||||
|
||||
@ -11,16 +11,12 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-template_comment,
|
||||
.diff .hljs-header,
|
||||
.hljs-javadoc {
|
||||
.hljs-doctag {
|
||||
color: dark-light-choose(#998, #bba);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.css .rule .hljs-keyword,
|
||||
.hljs-winutils,
|
||||
.javascript .hljs-title,
|
||||
.nginx .hljs-title,
|
||||
.hljs-subst,
|
||||
@ -31,22 +27,20 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
|
||||
}
|
||||
|
||||
.hljs-number,
|
||||
.hljs-hexcolor,
|
||||
.ruby .hljs-constant {
|
||||
color: dark-light-choose(#099, #aff);
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-tag .hljs-value,
|
||||
.hljs-phpdoc,
|
||||
.hljs-tag .hljs-string,
|
||||
.tex .hljs-formula {
|
||||
color: dark-light-choose(#d14, #f99);
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-id,
|
||||
.hljs-name,
|
||||
.coffeescript .hljs-params,
|
||||
.scss .hljs-preprocessor {
|
||||
.scss .hljs-meta {
|
||||
color: #900;
|
||||
font-weight: bold;
|
||||
}
|
||||
@ -68,13 +62,13 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
|
||||
|
||||
.hljs-tag,
|
||||
.hljs-tag .hljs-title,
|
||||
.hljs-rules .hljs-property,
|
||||
.django .hljs-tag .hljs-keyword {
|
||||
color: dark-light-choose(#000080, #99f);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.hljs-attribute,
|
||||
.css .hljs-keyword,
|
||||
.hljs-variable,
|
||||
.lisp .hljs-body {
|
||||
color: dark-light-choose(#008080, #0ee);
|
||||
@ -94,16 +88,12 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
|
||||
|
||||
.hljs-built_in,
|
||||
.lisp .hljs-title,
|
||||
.clojure .hljs-built_in {
|
||||
.clojure .hljs-built_in,
|
||||
.hljs-builtin-name {
|
||||
color: #0086b3;
|
||||
}
|
||||
|
||||
.hljs-preprocessor,
|
||||
.hljs-pragma,
|
||||
.hljs-pi,
|
||||
.hljs-doctype,
|
||||
.hljs-shebang,
|
||||
.hljs-cdata {
|
||||
.meta {
|
||||
color: #999;
|
||||
font-weight: bold;
|
||||
}
|
||||
@ -116,11 +106,7 @@ github.com style (c) Vasily Polovnyov <vast@whiteants.net>
|
||||
background: #dfd;
|
||||
}
|
||||
|
||||
.diff .hljs-change {
|
||||
background: #0086b3;
|
||||
}
|
||||
|
||||
.hljs-chunk {
|
||||
.diff .hljs-meta {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
|
||||
@ -260,6 +260,7 @@
|
||||
#bulk-select {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
top: 130px;
|
||||
padding: 5px;
|
||||
background-color: $secondary;
|
||||
z-index: 99999;
|
||||
|
||||
@ -100,7 +100,6 @@ button {
|
||||
bottom: 0;
|
||||
left: 135px;
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
|
||||
4
app/assets/stylesheets/vendor/normalize.scss
vendored
4
app/assets/stylesheets/vendor/normalize.scss
vendored
@ -131,7 +131,7 @@ dfn {
|
||||
*/
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
font-size: 1.6em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
@ -422,4 +422,4 @@ table {
|
||||
td,
|
||||
th {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,22 @@
|
||||
require_dependency 'rate_limiter'
|
||||
|
||||
class AboutController < ApplicationController
|
||||
skip_before_filter :check_xhr, only: [:show]
|
||||
skip_before_filter :check_xhr, only: [:index]
|
||||
before_filter :ensure_logged_in, only: [:live_post_counts]
|
||||
|
||||
def index
|
||||
@about = About.new
|
||||
render_serialized(@about, AboutSerializer)
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
# @list = list
|
||||
# store_preloaded(list.preload_key, MultiJson.dump(TopicListSerializer.new(list, scope: guardian)))
|
||||
render :index
|
||||
end
|
||||
format.json do
|
||||
render_serialized(@about, AboutSerializer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def live_post_counts
|
||||
|
||||
@ -47,7 +47,7 @@ class Admin::EmailController < Admin::AdminController
|
||||
|
||||
def handle_mail
|
||||
params.require(:email)
|
||||
Email::Receiver.new(params[:email]).process
|
||||
Email::Receiver.new(params[:email]).process!
|
||||
render text: "email was processed"
|
||||
end
|
||||
|
||||
|
||||
@ -20,7 +20,8 @@ class Admin::EmailTemplatesController < Admin::AdminController
|
||||
"system_messages.unblocked", "system_messages.user_automatically_blocked",
|
||||
"system_messages.welcome_invite", "system_messages.welcome_user", "test_mailer",
|
||||
"user_notifications.account_created", "user_notifications.admin_login",
|
||||
"user_notifications.authorize_email", "user_notifications.forgot_password",
|
||||
"user_notifications.confirm_new_email", "user_notifications.confirm_old_email",
|
||||
"user_notifications.notify_old_email", "user_notifications.forgot_password",
|
||||
"user_notifications.set_password", "user_notifications.signup",
|
||||
"user_notifications.signup_after_approval",
|
||||
"user_notifications.user_invited_to_private_message_pm",
|
||||
|
||||
@ -8,7 +8,7 @@ class Admin::UserFieldsController < Admin::AdminController
|
||||
field = UserField.new(params.require(:user_field).permit(*Admin::UserFieldsController.columns))
|
||||
|
||||
field.position = (UserField.maximum(:position) || 0) + 1
|
||||
field.required = params[:required] == "true"
|
||||
field.required = params[:user_field][:required] == "true"
|
||||
update_options(field)
|
||||
|
||||
json_result(field, serializer: UserFieldSerializer) do
|
||||
|
||||
@ -75,9 +75,13 @@ class ApplicationController < ActionController::Base
|
||||
render 'default/empty'
|
||||
end
|
||||
|
||||
def render_rate_limit_error(e)
|
||||
render_json_error e.description, type: :rate_limit, status: 429
|
||||
end
|
||||
|
||||
# If they hit the rate limiter
|
||||
rescue_from RateLimiter::LimitExceeded do |e|
|
||||
render_json_error e.description, type: :rate_limit, status: 429
|
||||
render_rate_limit_error(e)
|
||||
end
|
||||
|
||||
rescue_from PG::ReadOnlySqlTransaction do |e|
|
||||
@ -165,7 +169,7 @@ class ApplicationController < ActionController::Base
|
||||
|
||||
def set_locale
|
||||
if !current_user
|
||||
if SiteSetting.allow_user_locale
|
||||
if SiteSetting.set_locale_from_accept_language_header
|
||||
I18n.locale = locale_from_header
|
||||
else
|
||||
I18n.locale = SiteSetting.default_locale
|
||||
|
||||
@ -2,7 +2,7 @@ require_dependency 'category_serializer'
|
||||
|
||||
class CategoriesController < ApplicationController
|
||||
|
||||
before_filter :ensure_logged_in, except: [:index, :show, :redirect]
|
||||
before_filter :ensure_logged_in, except: [:index, :show, :redirect, :find_by_slug]
|
||||
before_filter :fetch_category, only: [:show, :update, :destroy]
|
||||
before_filter :initialize_staff_action_logger, only: [:create, :update, :destroy]
|
||||
skip_before_filter :check_xhr, only: [:index, :redirect]
|
||||
@ -102,19 +102,16 @@ class CategoriesController < ApplicationController
|
||||
json_result(@category, serializer: CategorySerializer) do |cat|
|
||||
|
||||
cat.move_to(category_params[:position].to_i) if category_params[:position]
|
||||
category_params.delete(:position)
|
||||
|
||||
if category_params.key? :email_in and category_params[:email_in].length == 0
|
||||
# properly null the value so the database constrain doesn't catch us
|
||||
# properly null the value so the database constraint doesn't catch us
|
||||
if category_params.has_key?(:email_in) && category_params[:email_in].blank?
|
||||
category_params[:email_in] = nil
|
||||
elsif category_params.key? :email_in and existing_category = Category.find_by(email_in: category_params[:email_in]) and existing_category.id != @category.id
|
||||
# check if email_in address is already in use for other category
|
||||
return render_json_error I18n.t('category.errors.email_in_already_exist', {email_in: category_params[:email_in], category_name: existing_category.name})
|
||||
end
|
||||
|
||||
category_params.delete(:position)
|
||||
old_permissions = Category.find(@category.id).permissions_params
|
||||
old_permissions = cat.permissions_params
|
||||
|
||||
if result = cat.update_attributes(category_params)
|
||||
if result = cat.update(category_params)
|
||||
Scheduler::Defer.later "Log staff action change category settings" do
|
||||
@staff_action_logger.log_category_settings_change(@category, category_params, old_permissions)
|
||||
end
|
||||
@ -156,6 +153,15 @@ class CategoriesController < ApplicationController
|
||||
render json: success_json
|
||||
end
|
||||
|
||||
def find_by_slug
|
||||
params.require(:category_slug)
|
||||
@category = Category.find_by_slug(params[:category_slug], params[:parent_category_slug])
|
||||
guardian.ensure_can_see!(@category)
|
||||
|
||||
@category.permission = CategoryGroup.permission_types[:full] if Category.topic_create_allowed(guardian).where(id: @category.id).exists?
|
||||
render_serialized(@category, CategorySerializer)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def required_param_keys
|
||||
|
||||
@ -23,6 +23,15 @@ class PostActionsController < ApplicationController
|
||||
@post.reload
|
||||
render_post_json(@post, _add_raw = false)
|
||||
end
|
||||
rescue RateLimiter::LimitExceeded => e
|
||||
# Special case: if we hit the create like rate limit, record it in user history
|
||||
# so we can award a badge
|
||||
if e.type == "create_like"
|
||||
UserHistory.create!(action: UserHistory.actions[:rate_limited_like],
|
||||
target_user_id: current_user.id,
|
||||
post_id: @post.id)
|
||||
end
|
||||
render_rate_limit_error(e)
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
||||
@ -282,6 +282,55 @@ class PostsController < ApplicationController
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
def revert
|
||||
raise Discourse::NotFound unless guardian.is_staff?
|
||||
|
||||
post_id = params[:id] || params[:post_id]
|
||||
revision = params[:revision].to_i
|
||||
raise Discourse::InvalidParameters.new(:revision) if revision < 2
|
||||
|
||||
post_revision = PostRevision.find_by(post_id: post_id, number: revision)
|
||||
raise Discourse::NotFound unless post_revision
|
||||
|
||||
post = find_post_from_params
|
||||
raise Discourse::NotFound if post.blank?
|
||||
|
||||
post_revision.post = post
|
||||
guardian.ensure_can_see!(post_revision)
|
||||
guardian.ensure_can_edit!(post)
|
||||
return render_json_error(I18n.t('revert_version_same')) if post_revision.modifications["raw"].blank? && post_revision.modifications["title"].blank? && post_revision.modifications["category_id"].blank?
|
||||
|
||||
topic = Topic.with_deleted.find(post.topic_id)
|
||||
|
||||
changes = {}
|
||||
changes[:raw] = post_revision.modifications["raw"][0] if post_revision.modifications["raw"].present? && post_revision.modifications["raw"][0] != post.raw
|
||||
if post.is_first_post?
|
||||
changes[:title] = post_revision.modifications["title"][0] if post_revision.modifications["title"].present? && post_revision.modifications["title"][0] != topic.title
|
||||
changes[:category_id] = post_revision.modifications["category_id"][0] if post_revision.modifications["category_id"].present? && post_revision.modifications["category_id"][0] != topic.category.id
|
||||
end
|
||||
return render_json_error(I18n.t('revert_version_same')) unless changes.length > 0
|
||||
changes[:edit_reason] = "reverted to version ##{post_revision.number.to_i - 1}"
|
||||
|
||||
revisor = PostRevisor.new(post, topic)
|
||||
revisor.revise!(current_user, changes)
|
||||
|
||||
return render_json_error(post) if post.errors.present?
|
||||
return render_json_error(topic) if topic.errors.present?
|
||||
|
||||
post_serializer = PostSerializer.new(post, scope: guardian, root: false)
|
||||
post_serializer.draft_sequence = DraftSequence.current(current_user, topic.draft_key)
|
||||
link_counts = TopicLink.counts_for(guardian, topic, [post])
|
||||
post_serializer.single_post_link_counts = link_counts[post.id] if link_counts.present?
|
||||
|
||||
result = { post: post_serializer.as_json }
|
||||
if post.is_first_post?
|
||||
result[:topic] = BasicTopicSerializer.new(topic, scope: guardian, root: false).as_json if post_revision.modifications["title"].present?
|
||||
result[:category_id] = post_revision.modifications["category_id"][0] if post_revision.modifications["category_id"].present?
|
||||
end
|
||||
|
||||
render_json_dump(result)
|
||||
end
|
||||
|
||||
def bookmark
|
||||
post = find_post_from_params
|
||||
|
||||
|
||||
@ -5,9 +5,9 @@ require_dependency 'rate_limiter'
|
||||
class UsersController < ApplicationController
|
||||
|
||||
skip_before_filter :authorize_mini_profiler, only: [:avatar]
|
||||
skip_before_filter :check_xhr, only: [:show, :password_reset, :update, :account_created, :activate_account, :perform_account_activation, :authorize_email, :user_preferences_redirect, :avatar, :my_redirect, :toggle_anon, :admin_login]
|
||||
skip_before_filter :check_xhr, only: [:show, :password_reset, :update, :account_created, :activate_account, :perform_account_activation, :user_preferences_redirect, :avatar, :my_redirect, :toggle_anon, :admin_login]
|
||||
|
||||
before_filter :ensure_logged_in, only: [:username, :update, :change_email, :user_preferences_redirect, :upload_user_image, :pick_avatar, :destroy_user_image, :destroy, :check_emails]
|
||||
before_filter :ensure_logged_in, only: [:username, :update, :user_preferences_redirect, :upload_user_image, :pick_avatar, :destroy_user_image, :destroy, :check_emails]
|
||||
before_filter :respond_to_suspicious_request, only: [:create]
|
||||
|
||||
# we need to allow account creation with bad CSRF tokens, if people are caching, the CSRF token on the
|
||||
@ -21,7 +21,6 @@ class UsersController < ApplicationController
|
||||
:activate_account,
|
||||
:perform_account_activation,
|
||||
:send_activation_email,
|
||||
:authorize_email,
|
||||
:password_reset,
|
||||
:confirm_email_token,
|
||||
:admin_login]
|
||||
@ -471,45 +470,6 @@ class UsersController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def change_email
|
||||
params.require(:email)
|
||||
user = fetch_user_from_params
|
||||
guardian.ensure_can_edit_email!(user)
|
||||
lower_email = Email.downcase(params[:email]).strip
|
||||
|
||||
RateLimiter.new(user, "change-email-hr-#{request.remote_ip}", 6, 1.hour).performed!
|
||||
RateLimiter.new(user, "change-email-min-#{request.remote_ip}", 3, 1.minute).performed!
|
||||
|
||||
EmailValidator.new(attributes: :email).validate_each(user, :email, lower_email)
|
||||
return render_json_error(user.errors.full_messages) if user.errors[:email].present?
|
||||
|
||||
# Raise an error if the email is already in use
|
||||
return render_json_error(I18n.t('change_email.error')) if User.find_by_email(lower_email)
|
||||
|
||||
email_token = user.email_tokens.create(email: lower_email)
|
||||
Jobs.enqueue(
|
||||
:user_email,
|
||||
to_address: lower_email,
|
||||
type: :authorize_email,
|
||||
user_id: user.id,
|
||||
email_token: email_token.token
|
||||
)
|
||||
|
||||
render nothing: true
|
||||
rescue RateLimiter::LimitExceeded
|
||||
render_json_error(I18n.t("rate_limiter.slow_down"))
|
||||
end
|
||||
|
||||
def authorize_email
|
||||
expires_now()
|
||||
if @user = EmailToken.confirm(params[:token])
|
||||
log_on_user(@user)
|
||||
else
|
||||
flash[:error] = I18n.t('change_email.error')
|
||||
end
|
||||
render layout: 'no_ember'
|
||||
end
|
||||
|
||||
def account_created
|
||||
@message = session['user_created_message'] || I18n.t('activation.missing_session')
|
||||
expires_now
|
||||
|
||||
46
app/controllers/users_email_controller.rb
Normal file
46
app/controllers/users_email_controller.rb
Normal file
@ -0,0 +1,46 @@
|
||||
require_dependency 'rate_limiter'
|
||||
require_dependency 'email_validator'
|
||||
require_dependency 'email_updater'
|
||||
|
||||
class UsersEmailController < ApplicationController
|
||||
|
||||
before_filter :ensure_logged_in, only: [:index, :update]
|
||||
|
||||
skip_before_filter :check_xhr, only: [:confirm]
|
||||
skip_before_filter :redirect_to_login_if_required, only: [:confirm]
|
||||
|
||||
def index
|
||||
end
|
||||
|
||||
def update
|
||||
params.require(:email)
|
||||
user = fetch_user_from_params
|
||||
|
||||
RateLimiter.new(user, "change-email-hr-#{request.remote_ip}", 6, 1.hour).performed!
|
||||
RateLimiter.new(user, "change-email-min-#{request.remote_ip}", 3, 1.minute).performed!
|
||||
|
||||
updater = EmailUpdater.new(guardian, user)
|
||||
updater.change_to(params[:email])
|
||||
|
||||
if updater.errors.present?
|
||||
return render_json_error(updater.errors.full_messages)
|
||||
end
|
||||
|
||||
render nothing: true
|
||||
rescue RateLimiter::LimitExceeded
|
||||
render_json_error(I18n.t("rate_limiter.slow_down"))
|
||||
end
|
||||
|
||||
def confirm
|
||||
expires_now
|
||||
updater = EmailUpdater.new
|
||||
@update_result = updater.confirm(params[:token])
|
||||
|
||||
# Log in the user if the process is complete (and they're not logged in)
|
||||
log_on_user(updater.user) if @update_result == :complete
|
||||
|
||||
render layout: 'no_ember'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -169,6 +169,10 @@ module ApplicationHelper
|
||||
MobileDetection.resolve_mobile_view!(request.user_agent,params,session)
|
||||
end
|
||||
|
||||
def include_crawler_content?
|
||||
controller.try(:use_crawler_layout?) || !mobile_view?
|
||||
end
|
||||
|
||||
def mobile_device?
|
||||
MobileDetection.mobile_device?(request.user_agent)
|
||||
end
|
||||
|
||||
@ -109,6 +109,10 @@ module Jobs
|
||||
email_args[:email_token] = email_token
|
||||
end
|
||||
|
||||
if type == 'notify_old_email'
|
||||
email_args[:new_email] = user.email
|
||||
end
|
||||
|
||||
message = UserNotifications.send(type, user, email_args)
|
||||
|
||||
# Update the to address if we have a custom one
|
||||
|
||||
@ -14,6 +14,7 @@ module Jobs
|
||||
Topic.ensure_consistency!
|
||||
Badge.ensure_consistency!
|
||||
CategoryUser.ensure_consistency!
|
||||
UserOption.ensure_consistency!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -23,9 +23,14 @@ module Jobs
|
||||
def process_popmail(popmail)
|
||||
begin
|
||||
mail_string = popmail.pop
|
||||
Email::Receiver.new(mail_string).process
|
||||
receiver = Email::Receiver.new(mail_string)
|
||||
receiver.process!
|
||||
rescue => e
|
||||
handle_failure(mail_string, e)
|
||||
rejection_message = handle_failure(mail_string, e)
|
||||
if rejection_message.present? && receiver && receiver.incoming_email
|
||||
receiver.incoming_email.rejection_message = rejection_message.body.to_s
|
||||
receiver.incoming_email.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -35,7 +40,6 @@ module Jobs
|
||||
message_template = case e
|
||||
when Email::Receiver::EmptyEmailError then :email_reject_empty
|
||||
when Email::Receiver::NoBodyDetectedError then :email_reject_empty
|
||||
when Email::Receiver::NoMessageIdError then :email_reject_no_message_id
|
||||
when Email::Receiver::AutoGeneratedEmailError then :email_reject_auto_generated
|
||||
when Email::Receiver::InactiveUserError then :email_reject_inactive_user
|
||||
when Email::Receiver::BlockedUserError then :email_reject_blocked_user
|
||||
@ -49,6 +53,7 @@ module Jobs
|
||||
when ActiveRecord::Rollback then :email_reject_invalid_post
|
||||
when Email::Receiver::InvalidPostAction then :email_reject_invalid_post_action
|
||||
when Discourse::InvalidAccess then :email_reject_invalid_access
|
||||
when RateLimiter::LimitExceeded then :email_reject_rate_limit_specified
|
||||
end
|
||||
|
||||
template_args = {}
|
||||
@ -59,6 +64,10 @@ module Jobs
|
||||
template_args[:post_error] = e.message
|
||||
end
|
||||
|
||||
if message_template == :email_reject_rate_limit_specified
|
||||
template_args[:rate_limit_description] = e.description
|
||||
end
|
||||
|
||||
if message_template
|
||||
# inform the user about the rejection
|
||||
message = Mail::Message.new(mail_string)
|
||||
@ -68,7 +77,10 @@ module Jobs
|
||||
|
||||
client_message = RejectionMailer.send_rejection(message_template, message.from, template_args)
|
||||
Email::Sender.new(client_message, message_template).send
|
||||
|
||||
client_message
|
||||
else
|
||||
mark_as_errored!
|
||||
Discourse.handle_job_exception(e, error_context(@args, "Unrecognized error type when processing incoming email", mail: mail_string))
|
||||
end
|
||||
end
|
||||
@ -83,8 +95,21 @@ module Jobs
|
||||
end
|
||||
end
|
||||
rescue Net::POPAuthenticationError => e
|
||||
mark_as_errored!
|
||||
Discourse.handle_job_exception(e, error_context(@args, "Signing in to poll incoming email"))
|
||||
end
|
||||
|
||||
POLL_MAILBOX_ERRORS_KEY ||= "poll_mailbox_errors".freeze
|
||||
|
||||
def self.errors_in_past_24_hours
|
||||
$redis.zremrangebyscore(POLL_MAILBOX_ERRORS_KEY, 0, 24.hours.ago.to_i)
|
||||
$redis.zcard(POLL_MAILBOX_ERRORS_KEY).to_i
|
||||
end
|
||||
|
||||
def mark_as_errored!
|
||||
now = Time.now.to_i
|
||||
$redis.zadd(POLL_MAILBOX_ERRORS_KEY, now, now.to_s)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
@ -23,9 +23,23 @@ class UserNotifications < ActionMailer::Base
|
||||
new_user_tips: I18n.t('system_messages.usage_tips.text_body_template', base_url: Discourse.base_url, locale: locale))
|
||||
end
|
||||
|
||||
def authorize_email(user, opts={})
|
||||
def notify_old_email(user, opts={})
|
||||
build_email(user.email,
|
||||
template: "user_notifications.authorize_email",
|
||||
template: "user_notifications.notify_old_email",
|
||||
locale: user_locale(user),
|
||||
new_email: opts[:new_email])
|
||||
end
|
||||
|
||||
def confirm_old_email(user, opts={})
|
||||
build_email(user.email,
|
||||
template: "user_notifications.confirm_old_email",
|
||||
locale: user_locale(user),
|
||||
email_token: opts[:email_token])
|
||||
end
|
||||
|
||||
def confirm_new_email(user, opts={})
|
||||
build_email(user.email,
|
||||
template: "user_notifications.confirm_new_email",
|
||||
locale: user_locale(user),
|
||||
email_token: opts[:email_token])
|
||||
end
|
||||
|
||||
@ -63,7 +63,7 @@ class AdminDashboardData
|
||||
:send_consumer_email_check, :title_check,
|
||||
:site_description_check, :site_contact_username_check,
|
||||
:notification_email_check, :subfolder_ends_in_slash_check,
|
||||
:pop3_polling_configuration
|
||||
:pop3_polling_configuration, :email_polling_errored_recently
|
||||
|
||||
add_problem_check do
|
||||
sidekiq_check || queue_size_check
|
||||
@ -210,4 +210,9 @@ class AdminDashboardData
|
||||
POP3PollingEnabledSettingValidator.new.error_message if SiteSetting.pop3_polling_enabled
|
||||
end
|
||||
|
||||
def email_polling_errored_recently
|
||||
errors = Jobs::PollMailbox.errors_in_past_24_hours
|
||||
I18n.t('dashboard.email_polling_errored_recently', count: errors) if errors > 0
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@ -20,13 +20,27 @@ class Badge < ActiveRecord::Base
|
||||
GoodShare = 22
|
||||
GreatShare = 23
|
||||
OneYearAnniversary = 24
|
||||
|
||||
Promoter = 25
|
||||
Campaigner = 26
|
||||
Champion = 27
|
||||
|
||||
PopularLink = 28
|
||||
HotLink = 29
|
||||
FamousLink = 30
|
||||
|
||||
Appreciated = 36
|
||||
Respected = 37
|
||||
Admired = 31
|
||||
|
||||
OutOfLove = 33
|
||||
HigherLove = 34
|
||||
CrazyInLove = 35
|
||||
|
||||
ThankYou = 38
|
||||
GivesBack = 32
|
||||
Empathetic = 39
|
||||
|
||||
# other consts
|
||||
AutobiographerMinBioLength = 10
|
||||
|
||||
@ -233,7 +247,7 @@ SQL
|
||||
|
||||
def self.sharing_badge(count)
|
||||
<<SQL
|
||||
SELECT views.user_id, i2.post_id, i2.created_at granted_at
|
||||
SELECT views.user_id, i2.post_id, current_timestamp granted_at
|
||||
FROM
|
||||
(
|
||||
SELECT i.user_id, MIN(i.id) i_id
|
||||
@ -249,7 +263,7 @@ SQL
|
||||
|
||||
def self.linking_badge(count)
|
||||
<<-SQL
|
||||
SELECT tl.user_id, post_id, MIN(tl.created_at) granted_at
|
||||
SELECT tl.user_id, post_id, current_timestamp granted_at
|
||||
FROM topic_links tl
|
||||
JOIN posts p ON p.id = post_id AND p.deleted_at IS NULL
|
||||
JOIN topics t ON t.id = p.topic_id AND t.deleted_at IS NULL AND t.archetype <> 'private_message'
|
||||
@ -259,6 +273,40 @@ SQL
|
||||
SQL
|
||||
end
|
||||
|
||||
def self.liked_posts(post_count, like_count)
|
||||
<<-SQL
|
||||
SELECT p.user_id, current_timestamp AS granted_at
|
||||
FROM posts AS p
|
||||
WHERE p.like_count >= #{like_count}
|
||||
AND (:backfill OR p.user_id IN (:user_ids))
|
||||
GROUP BY p.user_id
|
||||
HAVING count(*) > #{post_count}
|
||||
SQL
|
||||
end
|
||||
|
||||
def self.like_rate_limit(count)
|
||||
<<-SQL
|
||||
SELECT uh.target_user_id AS user_id, MAX(uh.created_at) AS granted_at
|
||||
FROM user_histories AS uh
|
||||
WHERE uh.action = #{UserHistory.actions[:rate_limited_like]}
|
||||
AND (:backfill OR uh.target_user_id IN (:user_ids))
|
||||
GROUP BY uh.target_user_id
|
||||
HAVING COUNT(*) >= #{count}
|
||||
SQL
|
||||
end
|
||||
|
||||
def self.liked_back(min_posts, ratio)
|
||||
<<-SQL
|
||||
SELECT p.user_id, current_timestamp AS granted_at
|
||||
FROM posts AS p
|
||||
INNER JOIN user_stats AS us ON us.user_id = p.user_id
|
||||
WHERE p.like_count > 0
|
||||
AND (:backfill OR p.user_id IN (:user_ids))
|
||||
GROUP BY p.user_id, us.likes_given
|
||||
HAVING count(*) > #{min_posts}
|
||||
AND (us.likes_given / count(*)::float) > #{ratio}
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
||||
belongs_to :badge_type
|
||||
@ -280,7 +328,6 @@ SQL
|
||||
[:badge_type_id, :multiple_grant, :target_posts, :show_posts, :query, :trigger, :auto_revoke, :listable]
|
||||
end
|
||||
|
||||
|
||||
def self.trust_level_badge_ids
|
||||
(1..4).to_a
|
||||
end
|
||||
|
||||
@ -316,8 +316,12 @@ SQL
|
||||
def email_in_validator
|
||||
return if self.email_in.blank?
|
||||
email_in.split("|").each do |email|
|
||||
unless Email.is_valid?(email)
|
||||
self.errors.add(:base, I18n.t('category.errors.invalid_email_in', email_in: email))
|
||||
if !Email.is_valid?(email)
|
||||
self.errors.add(:base, I18n.t('category.errors.invalid_email_in', email: email))
|
||||
elsif group = Group.find_by_email(email)
|
||||
self.errors.add(:base, I18n.t('category.errors.email_already_used_in_group', email: email, group_name: group.name))
|
||||
elsif category = Category.where.not(id: self.id).find_by_email(email)
|
||||
self.errors.add(:base, I18n.t('category.errors.email_already_used_in_category', email: email, category_name: category.name))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -391,7 +395,7 @@ SQL
|
||||
end
|
||||
|
||||
def self.find_by_email(email)
|
||||
self.where("email_in LIKE ?", "%#{Email.downcase(email)}%").first
|
||||
self.where("string_to_array(email_in, '|') @> ARRAY[?]", Email.downcase(email)).first
|
||||
end
|
||||
|
||||
def has_children?
|
||||
@ -446,6 +450,15 @@ SQL
|
||||
def publish_discourse_stylesheet
|
||||
DiscourseStylesheets.cache.clear
|
||||
end
|
||||
|
||||
def self.find_by_slug(category_slug, parent_category_slug=nil)
|
||||
if parent_category_slug
|
||||
parent_category_id = self.where(slug: parent_category_slug, parent_category_id: nil).pluck(:id).first
|
||||
self.where(slug: category_slug, parent_category_id: parent_category_id).first
|
||||
else
|
||||
self.where(slug: category_slug, parent_category_id: nil).first
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
||||
@ -91,7 +91,7 @@ class DirectoryItem < ActiveRecord::Base
|
||||
UPDATE directory_items di SET
|
||||
likes_received = x.likes_received,
|
||||
likes_given = x.likes_given,
|
||||
topics_entered = x.likes_given,
|
||||
topics_entered = x.topics_entered,
|
||||
days_visited = x.days_visited,
|
||||
posts_read = x.posts_read,
|
||||
topic_count = x.topic_count,
|
||||
@ -102,7 +102,7 @@ class DirectoryItem < ActiveRecord::Base
|
||||
di.period_type = :period_type AND (
|
||||
di.likes_received <> x.likes_received OR
|
||||
di.likes_given <> x.likes_given OR
|
||||
di.topics_entered <> x.likes_given OR
|
||||
di.topics_entered <> x.topics_entered OR
|
||||
di.days_visited <> x.days_visited OR
|
||||
di.posts_read <> x.posts_read OR
|
||||
di.topic_count <> x.topic_count OR
|
||||
|
||||
9
app/models/email_change_request.rb
Normal file
9
app/models/email_change_request.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class EmailChangeRequest < ActiveRecord::Base
|
||||
belongs_to :old_email_token, class_name: 'EmailToken'
|
||||
belongs_to :new_email_token, class_name: 'EmailToken'
|
||||
|
||||
def self.states
|
||||
@states ||= Enum.new(authorizing_old: 1, authorizing_new: 2, complete: 3)
|
||||
end
|
||||
|
||||
end
|
||||
@ -41,28 +41,41 @@ class EmailToken < ActiveRecord::Base
|
||||
return token.present? && token =~ /[a-f0-9]{#{token.length/2}}/i
|
||||
end
|
||||
|
||||
def self.confirm(token)
|
||||
return unless valid_token_format?(token)
|
||||
def self.atomic_confirm(token)
|
||||
failure = { success: false }
|
||||
return failure unless valid_token_format?(token)
|
||||
|
||||
email_token = confirmable(token)
|
||||
return if email_token.blank?
|
||||
return failure if email_token.blank?
|
||||
|
||||
user = email_token.user
|
||||
failure[:user] = user
|
||||
row_count = EmailToken.where(id: email_token.id, expired: false).update_all 'confirmed = true'
|
||||
if row_count == 1
|
||||
return { success: true, user: user, email_token: email_token }
|
||||
end
|
||||
|
||||
return failure
|
||||
end
|
||||
|
||||
def self.confirm(token)
|
||||
User.transaction do
|
||||
row_count = EmailToken.where(id: email_token.id, expired: false).update_all 'confirmed = true'
|
||||
if row_count == 1
|
||||
result = atomic_confirm(token)
|
||||
user = result[:user]
|
||||
if result[:success]
|
||||
# If we are activating the user, send the welcome message
|
||||
user.send_welcome_message = !user.active?
|
||||
|
||||
user.active = true
|
||||
user.email = email_token.email
|
||||
user.email = result[:email_token].email
|
||||
user.save!
|
||||
end
|
||||
end
|
||||
|
||||
# redeem invite, if available
|
||||
return User.find_by(email: Email.downcase(user.email)) if Invite.redeem_from_email(user.email).present?
|
||||
user
|
||||
if user
|
||||
return User.find_by(email: Email.downcase(user.email)) if Invite.redeem_from_email(user.email).present?
|
||||
user
|
||||
end
|
||||
end
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
# If the user's email is already taken, just return nil (failure)
|
||||
end
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
class Emoji
|
||||
# update this to clear the cache
|
||||
EMOJI_VERSION = "v2"
|
||||
|
||||
include ActiveModel::SerializerSupport
|
||||
|
||||
attr_reader :path
|
||||
@ -20,19 +23,19 @@ class Emoji
|
||||
end
|
||||
|
||||
def self.all
|
||||
Discourse.cache.fetch("all_emojis:v2") { standard | custom }
|
||||
Discourse.cache.fetch("all_emojis:#{EMOJI_VERSION}") { standard | custom }
|
||||
end
|
||||
|
||||
def self.standard
|
||||
Discourse.cache.fetch("standard_emojis:v2") { load_standard }
|
||||
Discourse.cache.fetch("standard_emojis:#{EMOJI_VERSION}") { load_standard }
|
||||
end
|
||||
|
||||
def self.aliases
|
||||
Discourse.cache.fetch("aliases_emojis:v2") { load_aliases }
|
||||
Discourse.cache.fetch("aliases_emojis:#{EMOJI_VERSION}") { load_aliases }
|
||||
end
|
||||
|
||||
def self.custom
|
||||
Discourse.cache.fetch("custom_emojis:v2") { load_custom }
|
||||
Discourse.cache.fetch("custom_emojis:#{EMOJI_VERSION}") { load_custom }
|
||||
end
|
||||
|
||||
def self.exists?(name)
|
||||
@ -76,10 +79,10 @@ class Emoji
|
||||
end
|
||||
|
||||
def self.clear_cache
|
||||
Discourse.cache.delete("custom_emojis")
|
||||
Discourse.cache.delete("standard_emojis")
|
||||
Discourse.cache.delete("aliases_emojis")
|
||||
Discourse.cache.delete("all_emojis")
|
||||
Discourse.cache.delete("custom_emojis:#{EMOJI_VERSION}")
|
||||
Discourse.cache.delete("standard_emojis:#{EMOJI_VERSION}")
|
||||
Discourse.cache.delete("aliases_emojis:#{EMOJI_VERSION}")
|
||||
Discourse.cache.delete("all_emojis:#{EMOJI_VERSION}")
|
||||
end
|
||||
|
||||
def self.db_file
|
||||
@ -128,8 +131,8 @@ class Emoji
|
||||
@unicode_replacements = {}
|
||||
db['emojis'].each do |e|
|
||||
hex = e['code'].hex
|
||||
# Don't replace digits or letters
|
||||
if hex > 128
|
||||
# Don't replace digits, letters and some symbols
|
||||
if hex > 255 && e['name'] != 'tm'
|
||||
@unicode_replacements[[hex].pack('U')] = e['name']
|
||||
end
|
||||
end
|
||||
|
||||
@ -43,6 +43,8 @@ class GlobalSetting
|
||||
c = {}
|
||||
c[:host] = redis_host if redis_host
|
||||
c[:port] = redis_port if redis_port
|
||||
c[:slave_host] = redis_slave_host if redis_slave_host
|
||||
c[:slave_port] = redis_slave_port if redis_slave_port
|
||||
c[:password] = redis_password if redis_password.present?
|
||||
c[:db] = redis_db if redis_db != 0
|
||||
c[:db] = 1 if Rails.env == "test"
|
||||
@ -52,6 +54,7 @@ class GlobalSetting
|
||||
{host: host, port: port}
|
||||
end.to_a
|
||||
end
|
||||
c[:connector] = DiscourseRedis::Connector
|
||||
c.freeze
|
||||
end
|
||||
end
|
||||
|
||||
@ -82,8 +82,12 @@ class Group < ActiveRecord::Base
|
||||
def incoming_email_validator
|
||||
return if self.automatic || self.incoming_email.blank?
|
||||
incoming_email.split("|").each do |email|
|
||||
unless Email.is_valid?(email)
|
||||
self.errors.add(:base, I18n.t('groups.errors.invalid_incoming_email', incoming_email: email))
|
||||
if !Email.is_valid?(email)
|
||||
self.errors.add(:base, I18n.t('groups.errors.invalid_incoming_email', email: email))
|
||||
elsif group = Group.where.not(id: self.id).find_by_email(email)
|
||||
self.errors.add(:base, I18n.t('groups.errors.email_already_used_in_group', email: email, group_name: group.name))
|
||||
elsif category = Category.find_by_email(email)
|
||||
self.errors.add(:base, I18n.t('groups.errors.email_already_used_in_category', email: email, category_name: category.name))
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -334,7 +338,7 @@ class Group < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def self.find_by_email(email)
|
||||
self.where("incoming_email LIKE ?", "%#{Email.downcase(email)}%").first
|
||||
self.where("string_to_array(incoming_email, '|') @> ARRAY[?]", Email.downcase(email)).first
|
||||
end
|
||||
|
||||
def bulk_add(user_ids)
|
||||
|
||||
@ -108,6 +108,7 @@ class OptimizedImage < ActiveRecord::Base
|
||||
-interpolate bicubic
|
||||
-unsharp 2x0.5+0.7+0
|
||||
-quality 98
|
||||
-profile #{File.join(Rails.root, 'vendor', 'data', 'RT_sRGB.icm')}
|
||||
#{to}
|
||||
}
|
||||
end
|
||||
@ -130,6 +131,7 @@ class OptimizedImage < ActiveRecord::Base
|
||||
-gravity center
|
||||
-background transparent
|
||||
-resize #{dimensions}#{!!opts[:force_aspect_ratio] ? "\\!" : "\\>"}
|
||||
-profile #{File.join(Rails.root, 'vendor', 'data', 'RT_sRGB.icm')}
|
||||
#{to}
|
||||
}
|
||||
end
|
||||
|
||||
@ -222,6 +222,10 @@ class Post < ActiveRecord::Base
|
||||
@acting_user = pu
|
||||
end
|
||||
|
||||
def last_editor
|
||||
self.last_editor_id ? (User.find_by_id(self.last_editor_id) || user) : user
|
||||
end
|
||||
|
||||
def whitelisted_spam_hosts
|
||||
|
||||
hosts = SiteSetting
|
||||
@ -435,7 +439,12 @@ class Post < ActiveRecord::Base
|
||||
new_user: new_user.username_lower
|
||||
)
|
||||
|
||||
revise(actor, { raw: self.raw, user_id: new_user.id, edit_reason: edit_reason })
|
||||
revise(actor, {raw: self.raw, user_id: new_user.id, edit_reason: edit_reason}, bypass_bump: true)
|
||||
|
||||
if post_number == topic.highest_post_number
|
||||
topic.update_columns(last_post_user_id: new_user.id)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
before_create do
|
||||
|
||||
@ -113,6 +113,8 @@ class PostMover
|
||||
def update_statistics
|
||||
destination_topic.update_statistics
|
||||
original_topic.update_statistics
|
||||
TopicUser.update_post_action_cache(topic_id: original_topic.id, post_action_type: :bookmark)
|
||||
TopicUser.update_post_action_cache(topic_id: destination_topic.id, post_action_type: :bookmark)
|
||||
end
|
||||
|
||||
def update_user_actions
|
||||
|
||||
@ -105,6 +105,10 @@ class SiteSetting < ActiveRecord::Base
|
||||
nil
|
||||
end
|
||||
|
||||
def self.email_polling_enabled?
|
||||
SiteSetting.manual_polling_enabled? || SiteSetting.pop3_polling_enabled?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
||||
@ -36,6 +36,7 @@ class User < ActiveRecord::Base
|
||||
has_many :uploads
|
||||
has_many :warnings
|
||||
has_many :user_archived_messages, dependent: :destroy
|
||||
has_many :email_change_requests, dependent: :destroy
|
||||
|
||||
|
||||
has_one :user_option, dependent: :destroy
|
||||
@ -147,6 +148,33 @@ class User < ActiveRecord::Base
|
||||
User.where(username_lower: lower).blank? && !SiteSetting.reserved_usernames.split("|").include?(username)
|
||||
end
|
||||
|
||||
def self.plugin_staff_user_custom_fields
|
||||
@plugin_staff_user_custom_fields ||= {}
|
||||
end
|
||||
|
||||
def self.register_plugin_staff_custom_field(custom_field_name, plugin)
|
||||
plugin_staff_user_custom_fields[custom_field_name] = plugin
|
||||
end
|
||||
|
||||
def self.whitelisted_user_custom_fields(guardian)
|
||||
fields = []
|
||||
|
||||
if SiteSetting.public_user_custom_fields.present?
|
||||
fields += SiteSetting.public_user_custom_fields.split('|')
|
||||
end
|
||||
|
||||
if guardian.is_staff?
|
||||
if SiteSetting.staff_user_custom_fields.present?
|
||||
fields += SiteSetting.staff_user_custom_fields.split('|')
|
||||
end
|
||||
plugin_staff_user_custom_fields.each do |k, v|
|
||||
fields << k if v.enabled?
|
||||
end
|
||||
end
|
||||
|
||||
fields.uniq
|
||||
end
|
||||
|
||||
def effective_locale
|
||||
if SiteSetting.allow_user_locale && self.locale.present?
|
||||
self.locale
|
||||
|
||||
@ -51,7 +51,8 @@ class UserHistory < ActiveRecord::Base
|
||||
revoke_admin: 33,
|
||||
grant_moderation: 34,
|
||||
revoke_moderation: 35,
|
||||
backup_operation: 36)
|
||||
backup_operation: 36,
|
||||
rate_limited_like: 37)
|
||||
end
|
||||
|
||||
# Staff actions is a subset of all actions, used to audit actions taken by staff users.
|
||||
|
||||
@ -5,6 +5,14 @@ class UserOption < ActiveRecord::Base
|
||||
|
||||
after_save :update_tracked_topics
|
||||
|
||||
def self.ensure_consistency!
|
||||
exec_sql("SELECT u.id FROM users u
|
||||
LEFT JOIN user_options o ON o.user_id = u.id
|
||||
WHERE o.user_id IS NULL").values.each do |id,_|
|
||||
UserOption.create(user_id: id.to_i)
|
||||
end
|
||||
end
|
||||
|
||||
def self.previous_replies_type
|
||||
@previous_replies_type ||= Enum.new(always: 0, unless_emailed: 1, never: 2)
|
||||
end
|
||||
@ -44,6 +52,11 @@ class UserOption < ActiveRecord::Base
|
||||
true
|
||||
end
|
||||
|
||||
def mailing_list_mode
|
||||
return false if SiteSetting.disable_mailing_list_mode
|
||||
super
|
||||
end
|
||||
|
||||
def update_tracked_topics
|
||||
return unless auto_track_topics_after_msecs_changed?
|
||||
TrackedTopicsUpdater.new(id, auto_track_topics_after_msecs).call
|
||||
|
||||
@ -6,6 +6,7 @@ class GroupPostSerializer < ApplicationSerializer
|
||||
:url,
|
||||
:user_title,
|
||||
:user_long_name,
|
||||
:topic,
|
||||
:category
|
||||
|
||||
has_one :user, serializer: BasicUserSerializer, embed: :objects
|
||||
@ -26,8 +27,11 @@ class GroupPostSerializer < ApplicationSerializer
|
||||
SiteSetting.enable_names?
|
||||
end
|
||||
|
||||
def topic
|
||||
object.topic
|
||||
end
|
||||
|
||||
def category
|
||||
object.topic.category
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ class IncomingEmailDetailsSerializer < ApplicationSerializer
|
||||
|
||||
attributes :error,
|
||||
:error_description,
|
||||
:rejection_message,
|
||||
:return_path,
|
||||
:date,
|
||||
:from,
|
||||
|
||||
@ -318,15 +318,10 @@ class UserSerializer < BasicUserSerializer
|
||||
end
|
||||
|
||||
def custom_fields
|
||||
fields = nil
|
||||
fields = User.whitelisted_user_custom_fields(scope)
|
||||
|
||||
if scope.can_edit?(object)
|
||||
fields = DiscoursePluginRegistry.serialized_current_user_fields.to_a
|
||||
end
|
||||
|
||||
if SiteSetting.public_user_custom_fields.present?
|
||||
fields ||= []
|
||||
fields += SiteSetting.public_user_custom_fields.split('|')
|
||||
fields += DiscoursePluginRegistry.serialized_current_user_fields.to_a
|
||||
end
|
||||
|
||||
if fields.present?
|
||||
|
||||
@ -38,14 +38,23 @@ class PostAlerter
|
||||
# mentions (users/groups)
|
||||
mentioned_groups, mentioned_users = extract_mentions(post)
|
||||
|
||||
expand_group_mentions(mentioned_groups, post) do |group, users|
|
||||
notify_users(users - notified, :group_mentioned, post, group: group)
|
||||
notified += users
|
||||
end
|
||||
if mentioned_groups || mentioned_users
|
||||
mentioned_opts = {}
|
||||
if post.last_editor_id != post.user_id
|
||||
# Mention comes from an edit by someone else, so notification should say who added the mention.
|
||||
editor = post.last_editor
|
||||
mentioned_opts = {user_id: editor.id, original_username: editor.username, display_username: editor.username}
|
||||
end
|
||||
|
||||
if mentioned_users
|
||||
notify_users(mentioned_users - notified, :mentioned, post)
|
||||
notified += mentioned_users
|
||||
expand_group_mentions(mentioned_groups, post) do |group, users|
|
||||
notify_users(users - notified, :group_mentioned, post, mentioned_opts.merge({group: group}))
|
||||
notified += users
|
||||
end
|
||||
|
||||
if mentioned_users
|
||||
notify_users(mentioned_users - notified, :mentioned, post, mentioned_opts)
|
||||
notified += mentioned_users
|
||||
end
|
||||
end
|
||||
|
||||
# replies
|
||||
@ -208,15 +217,19 @@ class PostAlerter
|
||||
end
|
||||
|
||||
def should_notify_previous?(user, notification, opts)
|
||||
type = notification.notification_type
|
||||
if type == Notification.types[:edited]
|
||||
return should_notify_edit?(notification, opts)
|
||||
elsif type == Notification.types[:liked]
|
||||
return should_notify_like?(user, notification)
|
||||
case notification.notification_type
|
||||
when Notification.types[:edited] then should_notify_edit?(notification, opts)
|
||||
when Notification.types[:liked] then should_notify_like?(user, notification)
|
||||
else false
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
COLLAPSED_NOTIFICATION_TYPES ||= [
|
||||
Notification.types[:replied],
|
||||
Notification.types[:quoted],
|
||||
Notification.types[:posted],
|
||||
]
|
||||
|
||||
def create_notification(user, type, post, opts=nil)
|
||||
return if user.blank?
|
||||
return if user.id == Discourse::SYSTEM_USER_ID
|
||||
@ -228,7 +241,7 @@ class PostAlerter
|
||||
# Make sure the user can see the post
|
||||
return unless Guardian.new(user).can_see?(post)
|
||||
|
||||
notifier_id = opts[:user_id] || post.user_id
|
||||
notifier_id = opts[:user_id] || post.user_id # xxxxx look at revision history
|
||||
|
||||
# apply muting here
|
||||
return if notifier_id && MutedUser.where(user_id: user.id, muted_user_id: notifier_id)
|
||||
@ -268,9 +281,10 @@ class PostAlerter
|
||||
|
||||
collapsed = false
|
||||
|
||||
if type == Notification.types[:replied] || type == Notification.types[:posted]
|
||||
destroy_notifications(user, Notification.types[:replied], post.topic)
|
||||
destroy_notifications(user, Notification.types[:posted], post.topic)
|
||||
if COLLAPSED_NOTIFICATION_TYPES.include?(type)
|
||||
COLLAPSED_NOTIFICATION_TYPES.each do |t|
|
||||
destroy_notifications(user, t, post.topic)
|
||||
end
|
||||
collapsed = true
|
||||
end
|
||||
|
||||
@ -280,7 +294,7 @@ class PostAlerter
|
||||
end
|
||||
|
||||
original_post = post
|
||||
original_username = opts[:display_username] || post.username
|
||||
original_username = opts[:display_username] || post.username # xxxxx need something here too
|
||||
|
||||
if collapsed
|
||||
post = first_unread_post(user, post.topic) || post
|
||||
|
||||
100
app/views/about/index.html.erb
Normal file
100
app/views/about/index.html.erb
Normal file
@ -0,0 +1,100 @@
|
||||
<% content_for :title do %><%=t "about" %><% end %>
|
||||
|
||||
<section itemscope itemtype="https://schema.org/AboutPage">
|
||||
<h1 itemprop="name">
|
||||
<%=t "js.about.title", {title: @about.title} %>
|
||||
</h1>
|
||||
|
||||
<div itemprop="text">
|
||||
<%= @about.description %>
|
||||
</div>
|
||||
|
||||
<h2><%=t "js.about.our_admins" %></h2>
|
||||
|
||||
<div class='admins-list' itemscope itemtype='http://schema.org/ItemList'>
|
||||
<% @about.admins.each do |user| %>
|
||||
<div itemprop='itemListElement' itemscope itemtype='http://schema.org/ListItem'>
|
||||
<meta itemprop='url' content='<%= user_path(user) %>'>
|
||||
<a href='<%= user_path(user) %>' itemprop='item'>
|
||||
<span itemprop='image'>
|
||||
<img width="45" height="45" class="avatar" src="<%= user.small_avatar_url %>">
|
||||
</span>
|
||||
<span itemprop='name'>
|
||||
<%= user.username %>
|
||||
<% if user.name.present? %>
|
||||
- <%= user.name %>
|
||||
<% end %>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if @about.moderators.count > 0 %>
|
||||
<h2><%=t "js.about.our_moderators" %></h2>
|
||||
<div class='moderators-list' itemscope itemtype='http://schema.org/ItemList'>
|
||||
<% @about.moderators.each do |user| %>
|
||||
<div itemprop='itemListElement' itemscope itemtype='http://schema.org/ListItem'>
|
||||
<meta itemprop='url' content='<%= user_path(user) %>'>
|
||||
<a href='<%= user_path(user) %>' itemprop='item'>
|
||||
<span itemprop='image'>
|
||||
<img width="45" height="45" class="avatar" src="<%= user.small_avatar_url %>">
|
||||
</span>
|
||||
<span itemprop='name'>
|
||||
<%= user.username %>
|
||||
<% if user.name.present? %>
|
||||
- <%= user.name %>
|
||||
<% end %>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<section class='about stats'>
|
||||
<h2><%=t 'js.about.stats' %></h2>
|
||||
|
||||
<table class='table'>
|
||||
<tr>
|
||||
<th> </th>
|
||||
<th><%=t 'js.about.stat.all_time' %></th>
|
||||
<th><%=t 'js.about.stat.last_7_days' %></th>
|
||||
<th><%=t 'js.about.stat.last_30_days' %></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class='title'><%=t 'js.about.topic_count' %></td>
|
||||
<td><%= @about.stats[:topic_count] %></td>
|
||||
<td><%= @about.stats[:topics_7_days] %></td>
|
||||
<td><%= @about.stats[:topics_30_days] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%=t 'js.about.post_count' %></td>
|
||||
<td><%= @about.stats[:post_count] %></td>
|
||||
<td><%= @about.stats[:posts_7_days] %></td>
|
||||
<td><%= @about.stats[:posts_30_days] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%=t 'js.about.user_count' %></td>
|
||||
<td><%= @about.stats[:user_count] %></td>
|
||||
<td><%= @about.stats[:users_7_days] %></td>
|
||||
<td><%= @about.stats[:users_30_days] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%=t 'js.about.active_user_count' %></td>
|
||||
<td>—</td>
|
||||
<td><%= @about.stats[:active_users_7_days] %></td>
|
||||
<td><%= @about.stats[:active_users_30_days] %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%=t 'js.about.like_count' %></td>
|
||||
<td><%= @about.stats[:like_count] %></td>
|
||||
<td><%= @about.stats[:likes_7_days] %></td>
|
||||
<td><%= @about.stats[:likes_30_days] %></td>
|
||||
</tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
</section>
|
||||
<br/>
|
||||
<br/>
|
||||
@ -11,7 +11,39 @@
|
||||
<%= render_google_universal_analytics_code %>
|
||||
<%= yield :head %>
|
||||
<style>
|
||||
img { max-width: 100%; width: auto; height: auto; }
|
||||
header img {
|
||||
max-width: 90% !important;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
img { max-width: 100%; width: auto; height: auto; }
|
||||
|
||||
#main-outlet > div {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
footer nav a {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
footer nav {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: 98%;
|
||||
padding-left: 1%;
|
||||
}
|
||||
.topic-list > div > a {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.topic-list > div .posts {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.topic-list > div {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -19,7 +51,7 @@
|
||||
<%= SiteCustomization.custom_header(session[:preview_style], mobile_view? ? :mobile : :desktop) %>
|
||||
<%- end %>
|
||||
<header>
|
||||
<a href="<%= path "/" %>"><img src="<%=SiteSetting.logo_url%>" alt="<%=SiteSetting.title%>" id="site-logo"></a>
|
||||
<a href="<%= path "/" %>"><img src="<%=SiteSetting.logo_url%>" alt="<%=SiteSetting.title%>" id="site-logo" style="max-width: 400px;"></a>
|
||||
</header>
|
||||
<div id="main-outlet" class="wrap">
|
||||
<%= yield %>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<%- unless mobile_view? %>
|
||||
<%- if include_crawler_content? %>
|
||||
|
||||
<% if @category %>
|
||||
<h1>
|
||||
@ -25,9 +25,9 @@
|
||||
</a>
|
||||
<%= page_links(t) %>
|
||||
<% if (!@category || @category.has_children?) && t.category %>
|
||||
<span>[<a href='<%= t.category.url %>'><%= t.category.name %></a>]</span>
|
||||
<span class='category'>[<a href='<%= t.category.url %>'><%= t.category.name %></a>]</span>
|
||||
<% end %>
|
||||
<span title='<%= t 'posts' %>'>(<a href="<%=t.last_post_url%>"><%= t.posts_count %></a>)</span>
|
||||
<span class='posts' title='<%= t 'posts' %>'>(<a href="<%=t.last_post_url%>"><%= t.posts_count %></a>)</span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
<%= server_plugin_outlet "topic_header" %>
|
||||
<hr>
|
||||
|
||||
<%- unless mobile_view? %>
|
||||
<%- if include_crawler_content? %>
|
||||
|
||||
<% @topic_view.posts.each do |post| %>
|
||||
<div itemscope itemtype='http://schema.org/Article'>
|
||||
|
||||
@ -58,7 +58,7 @@
|
||||
<div>
|
||||
<%- @new_by_category.first(10).each do |c| %>
|
||||
<span style='white-space: nowrap'>
|
||||
<a href='<%= Discourse.base_url %><%= c[0].url %>' style='color: #<%= @anchor_color %>'><%= c[0].name %></b> <span style='color: #777; margin: 0 10px 0 5px; font-size: 0.9em;'> <%= c[1] %></span>
|
||||
<a href='<%= Discourse.base_url %><%= c[0].url %>' style='color: #<%= @anchor_color %>'><%= c[0].name %></b> <span style='color: #777; margin: 0 10px 0 5px; font-size: 0.9em;'> <%= c[1] %></span></a>
|
||||
</span>
|
||||
<%- end %>
|
||||
</div>
|
||||
@ -71,7 +71,7 @@
|
||||
</table>
|
||||
|
||||
<div class='footer'>
|
||||
<%=raw(t :'user_notifications.digest.unsubscribe',
|
||||
<%=raw(t 'user_notifications.digest.unsubscribe',
|
||||
site_link: html_site_link(@anchor_color),
|
||||
unsubscribe_link: link_to(t('user_notifications.digest.click_here'), email_unsubscribe_url(host: Discourse.base_url, key: @unsubscribe_key), {:style=>'color: #' + @anchor_color })) %>
|
||||
</div>
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
<div id="simple-container">
|
||||
<%if flash[:error]%>
|
||||
<div class='alert alert-error'>
|
||||
<%=flash[:error]%>
|
||||
</div>
|
||||
<%else%>
|
||||
<h2><%= t 'change_email.confirmed' %></h2>
|
||||
<br>
|
||||
<a class="btn" href="/"><%= t('change_email.please_continue', site_name: SiteSetting.title) %></a>
|
||||
<%= render partial: 'auto_redirect_home' %>
|
||||
<%end%>
|
||||
</div>
|
||||
15
app/views/users_email/confirm.html.erb
Normal file
15
app/views/users_email/confirm.html.erb
Normal file
@ -0,0 +1,15 @@
|
||||
<div id="simple-container">
|
||||
<% if @update_result == :authorizing_new %>
|
||||
<h2><%= t 'change_email.authorizing_old.title' %></h2>
|
||||
<br>
|
||||
<p><%= t 'change_email.authorizing_old.description' %></p>
|
||||
<% elsif @update_result == :complete %>
|
||||
<h2><%= t 'change_email.confirmed' %></h2>
|
||||
<br>
|
||||
<a class="btn" href="/"><%= t('change_email.please_continue', site_name: SiteSetting.title) %></a>
|
||||
<% else %>
|
||||
<div class='alert alert-error'>
|
||||
<%=t 'change_email.error' %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@ -147,6 +147,7 @@ module Discourse
|
||||
|
||||
require 'discourse_redis'
|
||||
require 'logster/redis_store'
|
||||
require 'freedom_patches/redis'
|
||||
# Use redis for our cache
|
||||
config.cache_store = DiscourseRedis.new_redis_store
|
||||
$redis = DiscourseRedis.new
|
||||
|
||||
@ -95,6 +95,12 @@ redis_host = localhost
|
||||
# redis server port
|
||||
redis_port = 6379
|
||||
|
||||
# redis slave server address
|
||||
redis_slave_host =
|
||||
|
||||
# redis slave server port
|
||||
redis_slave_port = 6379
|
||||
|
||||
# redis database
|
||||
redis_db = 0
|
||||
|
||||
|
||||
@ -42,11 +42,12 @@ if defined?(Rack::MiniProfiler)
|
||||
/^\/favicon\/proxied/
|
||||
]
|
||||
|
||||
# For our app, let's just show mini profiler always, polling is chatty so nuke that
|
||||
# we DO NOT WANT mini-profiler loading on anything but real desktops and laptops
|
||||
# so let's rule out all handheld, tablet, and mobile devices
|
||||
Rack::MiniProfiler.config.pre_authorize_cb = lambda do |env|
|
||||
path = env['PATH_INFO']
|
||||
|
||||
(env['HTTP_USER_AGENT'] !~ /iPad|iPhone|Nexus 7|Android/) &&
|
||||
(env['HTTP_USER_AGENT'] !~ /iPad|iPhone|Android/) &&
|
||||
!skip.any?{|re| re =~ path}
|
||||
end
|
||||
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
# Be sure to restart your server when you modify this file.
|
||||
|
||||
Discourse::Application.config.session_store :cookie_store, key: '_forum_session'
|
||||
Discourse::Application.config.session_store(
|
||||
:cookie_store,
|
||||
key: '_forum_session',
|
||||
path: (Rails.application.config.relative_url_root.nil?) ? '/' : Rails.application.config.relative_url_root
|
||||
)
|
||||
|
||||
# Use the database for sessions instead of the cookie-based default,
|
||||
# which shouldn't be used to store highly confidential information
|
||||
|
||||
@ -364,7 +364,10 @@ en:
|
||||
one: "group"
|
||||
other: "groups"
|
||||
members: "Members"
|
||||
topics: "Topics"
|
||||
posts: "Posts"
|
||||
mentions: "Mentions"
|
||||
messages: "Messages"
|
||||
alias_levels:
|
||||
title: "Who can message and @mention this group?"
|
||||
nobody: "Nobody"
|
||||
@ -1015,7 +1018,7 @@ en:
|
||||
group_mentioned: "<i title='group mentioned' class='fa fa-at'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
quoted: "<i title='quoted' class='fa fa-quote-right'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
replied: "<i title='replied' class='fa fa-reply'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
posted: "<i title='replied' class='fa fa-reply'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
posted: "<i title='posted' class='fa fa-reply'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
edited: "<i title='edited' class='fa fa-pencil'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
liked: "<i title='liked' class='fa fa-heart'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
liked_2: "<i title='liked' class='fa fa-heart'></i><p><span>{{username}}, {{username2}}</span> {{description}}</p>"
|
||||
@ -1636,6 +1639,7 @@ en:
|
||||
last: "Last revision"
|
||||
hide: "Hide revision"
|
||||
show: "Show revision"
|
||||
revert: "Revert to this revision"
|
||||
comparing_previous_to_current_out_of_total: "<strong>{{previous}}</strong> <i class='fa fa-arrows-h'></i> <strong>{{current}}</strong> / {{total}}"
|
||||
displays:
|
||||
inline:
|
||||
@ -2296,6 +2300,7 @@ en:
|
||||
cc: "Cc"
|
||||
subject: "Subject"
|
||||
body: "Body"
|
||||
rejection_message: "Rejection Mail"
|
||||
filters:
|
||||
from_placeholder: "from@example.com"
|
||||
to_placeholder: "to@example.com"
|
||||
@ -2959,6 +2964,33 @@ en:
|
||||
famous_link:
|
||||
name: Famous Link
|
||||
description: Posted an external link with at least 1000 clicks
|
||||
appreciated:
|
||||
name: Appreciated
|
||||
description: Has received at least 1 like on 20 posts
|
||||
respected:
|
||||
name: Respected
|
||||
description: Has received at least 2 likes on 100 posts
|
||||
admired:
|
||||
name: Admired
|
||||
description: Has received at least 5 likes on 300 posts
|
||||
out_of_love:
|
||||
name: Out of Love
|
||||
description: Used the maximum amount of likes in a day
|
||||
higher_love:
|
||||
name: Higher Love
|
||||
description: Used the maximum amount of likes in a day 5 times
|
||||
crazy_in_love:
|
||||
name: Crazy in Love
|
||||
description: Used the maximum amount of likes in a day 20 times
|
||||
thank_you:
|
||||
name: Thank You
|
||||
description: Has at least 6 liked posts and a high like ratio
|
||||
gives_back:
|
||||
name: Gives Back
|
||||
description: Has at least 100 liked posts and a very high like ratio
|
||||
empathetic:
|
||||
name: Empathetic
|
||||
description: Has at least 500 liked posts and a superlative like ratio
|
||||
|
||||
google_search: |
|
||||
<h3>Search with Google</h3>
|
||||
|
||||
@ -179,6 +179,8 @@ es:
|
||||
more: "Más"
|
||||
less: "Menos"
|
||||
never: "nunca"
|
||||
every_30_minutes: "cada 30 minutos"
|
||||
every_hour: "cada hora"
|
||||
daily: "cada día"
|
||||
weekly: "cada semana"
|
||||
every_two_weeks: "cada dos semanas"
|
||||
@ -269,7 +271,7 @@ es:
|
||||
one: "Este tema tiene <b>1</b> post esperando aprobación"
|
||||
other: "Este tema tiene <b>{{count}}</b> posts esperando aprobación"
|
||||
confirm: "Guardar Cambios"
|
||||
delete_prompt: "¿Seguro que quieres eliminar a <b>%{username}</b>? Esto eliminará todos sus posts y bloqueará su email y dirección IP."
|
||||
delete_prompt: "¿Seguro que quieres eliminar a <b>%{username}</b>? Se eliminarán todos sus posts y se bloqueará su email y dirección IP."
|
||||
approval:
|
||||
title: "El Post Necesita Aprobación"
|
||||
description: "Hemos recibido tu nuevo post pero necesita ser aprobado por un moderador antes de aparecer. Por favor, ten paciencia."
|
||||
@ -326,7 +328,10 @@ es:
|
||||
one: "grupo"
|
||||
other: "grupos"
|
||||
members: "Miembros"
|
||||
topics: "Temas"
|
||||
posts: "Posts"
|
||||
mentions: "Menciones"
|
||||
messages: "Mensajes"
|
||||
alias_levels:
|
||||
title: "¿Quién puede emviar mensajes y @mencionar a este grupo?"
|
||||
nobody: "Nadie"
|
||||
@ -433,9 +438,7 @@ es:
|
||||
perm_denied_btn: "Permiso denegado"
|
||||
perm_denied_expl: "Has denegado los permisos para las notificaciones en tu navegador web. Configura tu navegador para permitir notificaciones. "
|
||||
disable: "Desactivar notificaciones"
|
||||
currently_enabled: "(activadas actualmente)"
|
||||
enable: "Activar notificaciones"
|
||||
currently_disabled: "(desactivadas actualmente)"
|
||||
each_browser_note: "Nota: Tendrás que cambiar esta opción para cada navegador que uses."
|
||||
dismiss_notifications: "Marcador todos como leídos"
|
||||
dismiss_notifications_tooltip: "Marcar todas las notificaciones no leídas como leídas"
|
||||
@ -470,7 +473,7 @@ es:
|
||||
muted_users: "Silenciados"
|
||||
muted_users_instructions: "Omite todas las notificaciones de estos usuarios."
|
||||
muted_topics_link: "Mostrar temas silenciados"
|
||||
automatically_unpin_topics: "Quitar destacado automáticamente cuando el usuario llega al final del tema."
|
||||
automatically_unpin_topics: "Dejar de destacar temas automáticamente cuando los leo por completo."
|
||||
staff_counters:
|
||||
flags_given: "reportes útiles"
|
||||
flagged_posts: "posts reportados"
|
||||
@ -571,12 +574,26 @@ es:
|
||||
title: "Distintivo de Tarjeta de Usuario"
|
||||
website: "Sitio Web"
|
||||
email_settings: "E-mail"
|
||||
like_notification_frequency:
|
||||
title: "Notificar cuando me dan Me gusta"
|
||||
always: "Con cada Me gusta que reciban mis posts"
|
||||
first_time_and_daily: "Al primer Me gusta que reciben mis posts y luego diariamente si reciben más"
|
||||
first_time: "Al primer Me gusta que reciben mi posts"
|
||||
never: "Nunca"
|
||||
email_previous_replies:
|
||||
title: "Incluir respuestas previas al pie de los emails"
|
||||
unless_emailed: "a menos que se hayan enviado previamente"
|
||||
always: "siempre"
|
||||
never: "nunca"
|
||||
email_digests:
|
||||
title: "Cuando no visite la página, enviarme un correo con las últimas novedades."
|
||||
every_30_minutes: "cada 30 minutos"
|
||||
every_hour: "cada hora"
|
||||
daily: "diariamente"
|
||||
every_three_days: "cada tres días"
|
||||
weekly: "semanalmente"
|
||||
every_two_weeks: "cada dos semanas"
|
||||
email_in_reply_to: "Incluir un extracto del post al que se responde en los emails"
|
||||
email_direct: "Envíame un email cuando alguien me cite, responda a mis posts, mencione mi @usuario o me invite a un tema"
|
||||
email_private_messages: "Notifícame por email cuando alguien me envíe un mensaje"
|
||||
email_always: "Quiero recibir notificaciones por email incluso cuando esté de forma activa por el sitio"
|
||||
@ -703,9 +720,11 @@ es:
|
||||
read_only_mode:
|
||||
enabled: "Este sitio está en modo solo-lectura. Puedes continuar navegando pero algunas acciones como responder o dar \"me gusta\" no están disponibles por ahora."
|
||||
login_disabled: "Iniciar sesión está desactivado mientras el foro esté en modo solo lectura."
|
||||
logout_disabled: "Cerrar sesión está desactivado mientras el sitio se encuentre en modo de sólo lectura."
|
||||
too_few_topics_and_posts_notice: "¡Vamos a <a href='http://blog.discourse.org/2014/08/building-a-discourse-community/'>dar por comenzada la comunidad!</a> Hay <strong>%{currentTopics} / %{requiredTopics}</strong> temas y <strong>%{currentPosts} / %{requiredPosts}</strong> mensajes. Los nuevos visitantes necesitan algo que leer y a lo que responder."
|
||||
too_few_topics_notice: "¡Vamos a <a href='http://blog.discourse.org/2014/08/building-a-discourse-community/'>dar por comenzada la comunidad!</a> Hay <strong>%{currentTopics} / %{requiredTopics}</strong> temas. Los nuevos visitantes necesitan algo que leer y a lo que responder."
|
||||
too_few_posts_notice: "¡Vamos a <a href='http://blog.discourse.org/2014/08/building-a-discourse-community/'>dar por empezada la comunidad!</a> Hay <strong>%{currentPosts} / %{requiredPosts}</strong> mensajes. Los nuevos visitantes necesitan algo que leer y a lo que responder."
|
||||
logs_error_rate_exceeded_notice: "%{timestamp}: La tasa actual de <a href='%{url}' target='_blank'>%{rate} errors/%{duration}</a> ha excedido el límite establecido en las opciones del sitio de %{siteSettingLimit} errors/%{duration}."
|
||||
learn_more: "saber más..."
|
||||
year: 'año'
|
||||
year_desc: 'temas creados en los últimos 365 días'
|
||||
@ -798,6 +817,9 @@ es:
|
||||
twitter:
|
||||
title: "con Twitter"
|
||||
message: "Autenticando con Twitter (asegúrate de desactivar cualquier bloqueador de pop ups)"
|
||||
instagram:
|
||||
title: "con Instagram"
|
||||
message: "Autenticando con Instagram (asegúrate que los bloqueadores de pop up no están activados)"
|
||||
facebook:
|
||||
title: "con Facebook"
|
||||
message: "Autenticando con Facebook (asegúrate de desactivar cualquier bloqueador de pop ups)"
|
||||
@ -903,9 +925,13 @@ es:
|
||||
group_mentioned: "<i title='group mentioned' class='fa fa-at'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
quoted: "<i title='quoted' class='fa fa-quote-right'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
replied: "<i title='replied' class='fa fa-reply'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
posted: "<i title='replied' class='fa fa-reply'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
posted: "<i title='posted' class='fa fa-reply'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
edited: "<i title='edited' class='fa fa-pencil'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
liked: "<i title='liked' class='fa fa-heart'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
liked_2: "<i title='liked' class='fa fa-heart'></i><p><span>{{username}}, {{username2}}</span> {{description}}</p>"
|
||||
liked_many:
|
||||
one: "<i title='liked' class='fa fa-heart'></i><p><span>{{username}}, {{username2}} y otro</span> {{description}}</p>"
|
||||
other: "<i title='liked' class='fa fa-heart'></i><p><span>{{username}}, {{username2}} y {{count}} otros</span> {{description}}</p>"
|
||||
private_message: "<i title='private message' class='fa fa-envelope-o'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
invited_to_private_message: "<i title='private message' class='fa fa-envelope-o'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
invited_to_topic: "<i title='invited to topic' class='fa fa-hand-o-right'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
@ -1013,8 +1039,8 @@ es:
|
||||
top: "No hay temas en el top más vistos."
|
||||
search: "No hay resultados de búsqueda."
|
||||
educate:
|
||||
new: '<p>Tus nuevos temas aparecerán aquí.</p><p>Por defecto, los temas son considerados nuevos y mostrarán un indicador: <span class="badge new-topic badge-notification" style="vertical-align:middle;line-height:inherit;">nuevo</span> si son creados en los 2 últimos días.</p><p>Puedes cambiar esto en tus <a href="%{userPrefsUrl}">preferencias</a>.</p>'
|
||||
unread: '<p>Tus temas sin leer aparecerán aquí.</p><p>Por defecto, los temas son considerados no leídos y mostrán contadores de post sin leer <span class="badge new-posts badge-notification">1</span> si:</p><ul><li>Creaste el tema</li><li>Respondiste al tema</li><li>Leíste el tema durante más de 4 minutos</li></ul><p>O si has establecido específicamente el tema a Seguir o Vigilar en el control de notificaciones al pie de cada tema.</p><p>Puedes cambiar esto en tus <a href="%{userPrefsUrl}">preferencias</a>.</p>'
|
||||
new: '<p>Tus temas nuevos aparecen aquí.</p><p>Por defecto, los temas se consideran nuevos y mostrarán un indicador <span class="badge new-topic badge-notification" style="vertical-align:middle;line-height:inherit;">nuevo</span> si fueron creados en los últimos 2 días.</p><p>Dirígite a <a href="%{userPrefsUrl}">preferencias</a> para cambiar esto.</p>'
|
||||
unread: '<p>Tus temas sin leer aparecen aquí.</p><p>Por defecto, los temas son considerados sin leer y mostrarán contadores de posts sin leer <span class="badge new-posts badge-notification">1</span> si:</p><ul><li>Creaste el tema</li><li>Respondiste al tema</li><li>Leíste el tema por más de 4 minutos</li></ul><p>O si has establecido específicamente el tema como Siguiendo o Vigilando a través del control de notificaciones al pie de cada tema.</p><p>Visita tus <a href="%{userPrefsUrl}">preferencias</a> para cambiar esto.</p>'
|
||||
bottom:
|
||||
latest: "No hay más temas recientes para leer."
|
||||
hot: "No hay más temas candentes."
|
||||
@ -1379,17 +1405,14 @@ es:
|
||||
like: "Deshacer Me gusta"
|
||||
vote: "Deshacer voto"
|
||||
people:
|
||||
off_topic: "{{icons}} reportó esto como off-topic"
|
||||
spam: "{{icons}} reportó esto como spam"
|
||||
spam_with_url: "{{icons}} reportó <a href='{{postUrl}}'>esto como spam</a>"
|
||||
inappropriate: "{{icons}} flagged reportó esto como inapropiado"
|
||||
notify_moderators: "{{icons}} ha notificado a los moderadores"
|
||||
notify_moderators_with_url: "{{icons}} <a href='{{postUrl}}'>moderadores notificados</a>"
|
||||
notify_user: "{{icons}} ha enviado un mensaje"
|
||||
notify_user_with_url: "{{icons}} ha enviado un <a href='{{postUrl}}'>mensaje</a>"
|
||||
bookmark: "{{icons}} ha marcado esto"
|
||||
like: "{{icons}} les gusta esto"
|
||||
vote: "{{icons}} ha votado esto"
|
||||
off_topic: "reportó esto como off-topic"
|
||||
spam: "reportó esto como spam"
|
||||
inappropriate: "reportó esto como inapropiado"
|
||||
notify_moderators: "notificó a moderadores"
|
||||
notify_user: "envió un mensaje"
|
||||
bookmark: "guardó esto en marcadores"
|
||||
like: "le gustó esto"
|
||||
vote: "votó por esto"
|
||||
by_you:
|
||||
off_topic: "Has reportado esto como off-topic"
|
||||
spam: "Has reportado esto como Spam"
|
||||
@ -1461,6 +1484,7 @@ es:
|
||||
last: "Última revisión"
|
||||
hide: "Ocultar revisión."
|
||||
show: "Mostrar revisión."
|
||||
revert: "Volver a esta revisión"
|
||||
comparing_previous_to_current_out_of_total: "<strong>{{previous}}</strong> <i class='fa fa-arrows-h'></i> <strong>{{current}}</strong> / {{total}}"
|
||||
displays:
|
||||
inline:
|
||||
@ -1541,7 +1565,6 @@ es:
|
||||
description: "No serás notificado de ningún tema en estas categorías, y no aparecerán en la página de mensajes recientes."
|
||||
flagging:
|
||||
title: '¡Gracias por ayudar a mantener una comunidad civilizada!'
|
||||
private_reminder: 'los reportes son privados, son visibles <b>únicamente</b> por los administradores'
|
||||
action: 'Reportar post'
|
||||
take_action: "Tomar medidas"
|
||||
notify_action: 'Mensaje'
|
||||
@ -1553,7 +1576,7 @@ es:
|
||||
submit_tooltip: "Enviar el reporte privado"
|
||||
take_action_tooltip: "Alcanzar el umbral de reportes inmediatamente, en vez de esperar a más reportes de la comunidad"
|
||||
cant: "Lo sentimos, no puedes reportar este post en este momento."
|
||||
notify_staff: 'Notificar al Staff'
|
||||
notify_staff: 'Notificar a los administradores de forma privada'
|
||||
formatted_name:
|
||||
off_topic: "Está fuera de lugar"
|
||||
inappropriate: "Es inapropiado"
|
||||
@ -1934,11 +1957,11 @@ es:
|
||||
is_disabled: "Restaurar está deshabilitado en la configuración del sitio."
|
||||
label: "Restaurar"
|
||||
title: "Restaurar la copia de seguridad"
|
||||
confirm: "¿Estás seguro que quieres restaurar esta copia de seguridad?"
|
||||
confirm: "¿Seguro que quieres restaurar esta copia de seguridad?"
|
||||
rollback:
|
||||
label: "Revertir"
|
||||
title: "Regresar la base de datos al estado funcional anterior"
|
||||
confirm: "¿Estás seguro que quieres regresar la base de datos al estado funcional anterior?"
|
||||
confirm: "¿Seguro que quieres retornar la base de datos al estado funcional previo?"
|
||||
export_csv:
|
||||
user_archive_confirm: "¿Seguro que quieres descargar todos tus posts?"
|
||||
success: "Exportación iniciada, se te notificará a través de un mensaje cuando el proceso se haya completado."
|
||||
@ -2041,9 +2064,6 @@ es:
|
||||
love:
|
||||
name: 'me gusta'
|
||||
description: "El color del botón de \"me gusta\""
|
||||
wiki:
|
||||
name: 'wiki'
|
||||
description: "Color base usado para el fondo en los posts del wiki."
|
||||
email:
|
||||
title: "Emails"
|
||||
settings: "Ajustes"
|
||||
@ -2080,6 +2100,20 @@ es:
|
||||
subject: "Asunto"
|
||||
error: "Error"
|
||||
none: "No se encontraron emails entrantes."
|
||||
modal:
|
||||
title: "Detalles de emails entrantes"
|
||||
error: "Error"
|
||||
return_path: "Ruta de retorno"
|
||||
message_id: "Id del mensaje"
|
||||
in_reply_to: "En respuesta a"
|
||||
references: "Referencias"
|
||||
date: "Fecha"
|
||||
from: "De"
|
||||
to: "Para"
|
||||
cc: "Cc"
|
||||
subject: "Asunto"
|
||||
body: "Cuerpo"
|
||||
rejection_message: "Correo de rechazo"
|
||||
filters:
|
||||
from_placeholder: "from@example.com"
|
||||
to_placeholder: "to@example.com"
|
||||
@ -2155,6 +2189,7 @@ es:
|
||||
revoke_admin: "revocar administración"
|
||||
grant_moderation: "conceder moderación"
|
||||
revoke_moderation: "revocar moderación"
|
||||
backup_operation: "operación de copia de seguridad de respaldo"
|
||||
screened_emails:
|
||||
title: "Correos bloqueados"
|
||||
description: "Cuando alguien trata de crear una cuenta nueva, los siguientes correos serán revisados y el registro será bloqueado, o alguna otra acción será realizada."
|
||||
|
||||
@ -179,6 +179,8 @@ fi:
|
||||
more: "Lisää"
|
||||
less: "Vähemmän"
|
||||
never: "ei koskaan"
|
||||
every_30_minutes: "puolen tunnin välein"
|
||||
every_hour: "tunnin välein"
|
||||
daily: "päivittäin"
|
||||
weekly: "viikottain"
|
||||
every_two_weeks: "kahden viikon välein"
|
||||
@ -326,7 +328,10 @@ fi:
|
||||
one: "ryhmä"
|
||||
other: "ryhmät"
|
||||
members: "Jäsenet"
|
||||
topics: "Ketjut"
|
||||
posts: "Viestit"
|
||||
mentions: "Viittaukset"
|
||||
messages: "Viestit"
|
||||
alias_levels:
|
||||
title: "Ketkä voivat lähettää viestejä tälle ryhmälle tai @viitata siihen?"
|
||||
nobody: "Ei kukaan"
|
||||
@ -468,7 +473,7 @@ fi:
|
||||
muted_users: "Vaimennetut"
|
||||
muted_users_instructions: "Älä näytä ilmoituksia näiltä käyttäjiltä"
|
||||
muted_topics_link: "Näytä vaimennetut ketjut"
|
||||
automatically_unpin_topics: "Poista ketjun kiinnitys automaattisesti, jos selaan keskustelun loppuun"
|
||||
automatically_unpin_topics: "Poista kiinnitetyn ketjun kiinnitys automaattisesti, selattuani sen loppuun."
|
||||
staff_counters:
|
||||
flags_given: "hyödyllistä liputusta"
|
||||
flagged_posts: "liputettuja viestejä"
|
||||
@ -569,6 +574,12 @@ fi:
|
||||
title: "Käyttäjäkortin tunnus"
|
||||
website: "Nettisivu"
|
||||
email_settings: "Sähköposti"
|
||||
like_notification_frequency:
|
||||
title: "Ilmoita, kun viestistäni tykätään"
|
||||
always: "Aina"
|
||||
first_time_and_daily: "Ensimmäistä kertaa ja päivittäin"
|
||||
first_time: "Ensimmäistä kertaa"
|
||||
never: "Ei koskaan"
|
||||
email_previous_replies:
|
||||
title: "Liitä aiemmat vastaukset mukaan sähköpostin alaosaan"
|
||||
unless_emailed: "ellei aiemmin lähetetty"
|
||||
@ -576,6 +587,8 @@ fi:
|
||||
never: "ei koskaan"
|
||||
email_digests:
|
||||
title: "Lähetä tiivistelmä uusista viesteistä sähköpostilla, jos en käy sivustolla "
|
||||
every_30_minutes: "puolen tunnin välein"
|
||||
every_hour: "tunneittain"
|
||||
daily: "päivittäin"
|
||||
every_three_days: "joka kolmas päivä"
|
||||
weekly: "viikottain"
|
||||
@ -651,7 +664,7 @@ fi:
|
||||
summary:
|
||||
title: "Yhteenveto"
|
||||
stats: "Tilastot"
|
||||
topic_count: "Avattuja ketjuja"
|
||||
topic_count: "Luotuja ketjuja"
|
||||
post_count: "Kirjoitettuja viestejä"
|
||||
likes_given: "Annettuja tykkäyksiä"
|
||||
likes_received: "Saatuja tykkäyksiä"
|
||||
@ -706,10 +719,12 @@ fi:
|
||||
refresh: "Lataa sivu uudelleen"
|
||||
read_only_mode:
|
||||
enabled: "Sivusto on vain luku -tilassa. Voit jatkaa selailua, mutta vastaaminen, tykkääminen ja muita toimintoja on toistaiseksi poissa käytöstä."
|
||||
login_disabled: "Kirjautuminen ei ole käytössä sivuston ollessa vain luku -tilassa."
|
||||
login_disabled: "Et voi kirjautua sisään, kun sivusto on vain luku -tilassa."
|
||||
logout_disabled: "Et voi kirjautua ulos, kun sivusto on vain luku -tilassa."
|
||||
too_few_topics_and_posts_notice: "Laitetaanpa <a href='http://blog.discourse.org/2014/08/building-a-discourse-community/'>keskustelu alulle!</a> Tällä hetkellä palstalla on <strong>%{currentTopics} / %{requiredTopics}</strong> ketjua ja <strong>%{currentPosts} / %{requiredPosts}</strong> viestiä. Uudet kävijät tarvitsevat keskusteluita, joita lukea ja joihin vastata."
|
||||
too_few_topics_notice: "Laitetaanpa <a href='http://blog.discourse.org/2014/08/building-a-discourse-community/'>keskustelu alulle!</a> Tällä hetkellä palstalla on <strong>%{currentTopics} / %{requiredTopics}</strong> ketjua. Uudet kävijät tarvitsevat keskusteluita, joita lukea ja joihin vastata."
|
||||
too_few_posts_notice: "Laitetaanpa <a href='http://blog.discourse.org/2014/08/building-a-discourse-community/'>keskustelu alulle!</a> Tällä hetkellä palstalla on <strong>%{currentPosts} / %{requiredPosts}</strong> viestiä. Uudet kävijät tarvitsevat keskusteluita, joita lukea ja joihin vastata."
|
||||
logs_error_rate_exceeded_notice: "%{timestamp}: Current rate of <a href='%{url}' target='_blank'>%{rate} errors/%{duration}</a> has exceeded site settings's limit of %{siteSettingLimit} errors/%{duration}."
|
||||
learn_more: "opi lisää..."
|
||||
year: 'vuosi'
|
||||
year_desc: 'viimeisen 365 päivän aikana luodut ketjut'
|
||||
@ -802,6 +817,9 @@ fi:
|
||||
twitter:
|
||||
title: "Twitterillä"
|
||||
message: "Todennetaan Twitterin kautta (varmista, että ponnahdusikkunoiden esto ei ole päällä)"
|
||||
instagram:
|
||||
title: "Instagramilla"
|
||||
message: "Todennetaan Instagramin kautta (varmista, että ponnahdusikkunoiden esto ei ole päällä)"
|
||||
facebook:
|
||||
title: "Facebookilla"
|
||||
message: "Todennetaan Facebookin kautta (varmista, että ponnahdusikkunoiden esto ei ole päällä)"
|
||||
@ -907,9 +925,13 @@ fi:
|
||||
group_mentioned: "<i title='group mentioned' class='fa fa-at'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
quoted: "<i title='lainasi' class='fa fa-quote-right'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
replied: "<i title='vastasi' class='fa fa-reply'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
posted: "<i title='vastasi' class='fa fa-reply'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
posted: "<i title='kirjoitti' class='fa fa-reply'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
edited: "<i title='muokkasi' class='fa fa-pencil'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
liked: "<i title='tykkäsi' class='fa fa-heart'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
liked_2: "<i title='tykkäsi' class='fa fa-heart'></i><p><span>{{username}}, {{username2}}</span> {{description}}</p>"
|
||||
liked_many:
|
||||
one: "<i title='tykkäsi' class='fa fa-heart'></i><p><span>{{username}}, {{username2}} ja 1 muu</span> {{description}}</p>"
|
||||
other: "<i title='tykkäsi' class='fa fa-heart'></i><p><span>{{username}}, {{username2}} ja {{count}} muuta</span> {{description}}</p>"
|
||||
private_message: "<i title='yksityisviesti' class='fa fa-envelope-o'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
invited_to_private_message: "<i title='yksityisviesti' class='fa fa-envelope-o'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
invited_to_topic: "<i title='kutsui ketjuun' class='fa fa-hand-o-right'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
@ -1016,6 +1038,9 @@ fi:
|
||||
category: "Alueella {{category}} ei ole ketjua."
|
||||
top: "Huippuketjuja ei ole."
|
||||
search: "Hakutuloksia ei löytynyt."
|
||||
educate:
|
||||
new: '<p>Uuden ketjut näytetään tässä.</p><p>Ketjut tulkitaan uusiksi ja niiden yhteydessä näytetään <span class="badge new-topic badge-notification" style="vertical-align:middle;line-height:inherit;">uusi</span>-merkki, kun ne on luotu edellisen kahden päivän aikana.</p><p>Voit muuttaa tätä <a href="%{userPrefsUrl}">käyttäjäasetuksistasi</a>.</p>'
|
||||
unread: '<p>Lukemattomia viestejä sisältävät ketjut näytetään tässä.</p><p>Ketjun yhteydessä näytetään lukemattomien viestien lukumäärä <span class="badge new-posts badge-notification">1</span> jos olet:</p><ul><li>luonut ketjun</li><li>vastannut ketjuun</li><li>lukenut ketjua pidempään, kuin 4 minuuttia</li></ul><p>tai, jos olet erikseen merkannut ketjun seurattavaksi tai tarkkailtavaksi ketjun lopusta löytyvästä painikkeesta.</p><p>Voit muuttaa tätä <a href="%{userPrefsUrl}">käyttäjäasetuksistasi</a>.</p>'
|
||||
bottom:
|
||||
latest: "Tuoreimpia ketjuja ei ole enempää."
|
||||
hot: "Kuumia ketjuja ei ole enempää."
|
||||
@ -1379,6 +1404,15 @@ fi:
|
||||
bookmark: "Peru kirjanmerkki"
|
||||
like: "Peru tykkäys"
|
||||
vote: "Peru ääni"
|
||||
people:
|
||||
off_topic: "liputti tämän asiaan kuulumattomaksi"
|
||||
spam: "liputti tämän roskapostiksi"
|
||||
inappropriate: "liputti tämän asiattomaksi"
|
||||
notify_moderators: "ilmoitti valvojille"
|
||||
notify_user: "lähetti viestin"
|
||||
bookmark: "lisäsi tämän kirjanmerkkeihin"
|
||||
like: "tykkäsi tästä"
|
||||
vote: "äänesti tätä"
|
||||
by_you:
|
||||
off_topic: "Liputit tämän asiaankuulumattomaksi"
|
||||
spam: "Liputit tämän roskapostiksi"
|
||||
@ -1450,6 +1484,7 @@ fi:
|
||||
last: "Viimeinen revisio"
|
||||
hide: "Piilota revisio"
|
||||
show: "Näytä revisio"
|
||||
revert: "Palaa tähän revisioon"
|
||||
comparing_previous_to_current_out_of_total: "<strong>{{previous}}</strong> <i class='fa fa-arrows-h'></i> <strong>{{current}}</strong> / {{total}}"
|
||||
displays:
|
||||
inline:
|
||||
@ -1530,7 +1565,6 @@ fi:
|
||||
description: "Et saa ilmoituksia uusista ketjuista näillä alueilla, eivätkä ne näy tuoreimmissa."
|
||||
flagging:
|
||||
title: 'Kiitos avustasi yhteisön hyväksi!'
|
||||
private_reminder: 'liput ovat yksityisiä, ne näkyvät <b>ainoastaan</b> henkilökunnalle'
|
||||
action: 'Liputa viesti'
|
||||
take_action: "Ryhdy toimiin"
|
||||
notify_action: 'Viesti'
|
||||
@ -1542,7 +1576,7 @@ fi:
|
||||
submit_tooltip: "Toimita lippu"
|
||||
take_action_tooltip: "Saavuta liputusraja välittömästi, ennemmin kuin odota muidenkin käyttäjien liputuksia."
|
||||
cant: "Pahoittelut, et pysty liputtamaan tätä viestiä tällä hetkellä."
|
||||
notify_staff: 'Ilmoita ylläpitäjille'
|
||||
notify_staff: 'Ilmoita ylläpidolle yksityisesti'
|
||||
formatted_name:
|
||||
off_topic: "Se on asiaankuulumaton"
|
||||
inappropriate: "Se on asiaton"
|
||||
@ -1923,9 +1957,11 @@ fi:
|
||||
is_disabled: "Palautus on estetty sivuston asetuksissa."
|
||||
label: "Palauta"
|
||||
title: "Palauta varmuuskopio"
|
||||
confirm: "Oletko varma, että haluat palauttaa tämän varmuuskopion?"
|
||||
rollback:
|
||||
label: "Palauta"
|
||||
title: "Palauta tietokanta edelliseen toimivaan tilaan"
|
||||
confirm: "Oletko varma, että haluat palauttaa tietokannan edelliseen toimivaan tilaan?"
|
||||
export_csv:
|
||||
user_archive_confirm: "Oletko varma, että haluat ladata viestisi?"
|
||||
success: "Vienti on käynnissä. Saat ilmoituksen viestillä, kun prosessi on valmis."
|
||||
@ -2065,12 +2101,19 @@ fi:
|
||||
error: "Virhe"
|
||||
none: "Uusia sähköpostiviestejä ei löydetty."
|
||||
modal:
|
||||
title: "Saapuvan sähköpostin tiedot"
|
||||
error: "Virhe"
|
||||
return_path: "Paluupolku"
|
||||
message_id: "Viestin ID"
|
||||
in_reply_to: "Vastauksena"
|
||||
references: "Viittaukset"
|
||||
date: "Päivämäärä"
|
||||
from: "Lähettäjä"
|
||||
to: "Vastaanottaja"
|
||||
cc: "Kopio"
|
||||
subject: "Otsikko"
|
||||
body: "Leipäteksti"
|
||||
rejection_message: "Hylkäysviesti"
|
||||
filters:
|
||||
from_placeholder: "from@example.com"
|
||||
to_placeholder: "to@example.com"
|
||||
@ -2248,6 +2291,7 @@ fi:
|
||||
moderator: "Valvoja?"
|
||||
admin: "Ylläpitäjä?"
|
||||
blocked: "Estetty?"
|
||||
staged: "Luotu?"
|
||||
show_admin_profile: "Ylläpito"
|
||||
edit_title: "Muokkaa nimikettä"
|
||||
save_title: "Tallenna nimike"
|
||||
@ -2317,6 +2361,7 @@ fi:
|
||||
deactivate_explanation: "Käytöstä poistetun käyttäjän täytyy uudelleen vahvistaa sähköpostiosoitteensa."
|
||||
suspended_explanation: "Hyllytetty käyttäjä ei voi kirjautua sisään."
|
||||
block_explanation: "Estetty käyttäjä ei voi luoda viestejä tai ketjuja."
|
||||
stage_explanation: "Automaattisesti luotu käyttäjä voi vastata sähköpostilla vain erikseen määritettyihin ketjuihin."
|
||||
trust_level_change_failed: "Käyttäjän luottamustason vaihtamisessa tapahtui virhe."
|
||||
suspend_modal_title: "Hyllytä käyttäjä"
|
||||
trust_level_2_users: "Käyttäjät luottamustasolla 2"
|
||||
|
||||
@ -179,6 +179,8 @@ fr:
|
||||
more: "Plus"
|
||||
less: "Moins"
|
||||
never: "jamais"
|
||||
every_30_minutes: "toutes les 30 minutes"
|
||||
every_hour: "chaque heure"
|
||||
daily: "quotidiennes"
|
||||
weekly: "hebdomadaires"
|
||||
every_two_weeks: "bi-mensuelles"
|
||||
@ -326,7 +328,10 @@ fr:
|
||||
one: "groupe"
|
||||
other: "groupes"
|
||||
members: "Membres"
|
||||
topics: "Sujets"
|
||||
posts: "Messages"
|
||||
mentions: "Mentions"
|
||||
messages: "Messages"
|
||||
alias_levels:
|
||||
title: "Qui peut envoyer un message et @notifier ce groupe ?"
|
||||
nobody: "Personne"
|
||||
@ -468,7 +473,7 @@ fr:
|
||||
muted_users: "Silencieux"
|
||||
muted_users_instructions: "Cacher toutes les notifications de ces utilisateurs."
|
||||
muted_topics_link: "Afficher les sujets en sourdine"
|
||||
automatically_unpin_topics: "Automatiquement désépingler les sujets lorsque vous atteignez le bas."
|
||||
automatically_unpin_topics: "Desépingler automatiquement quand j'arrive à la fin."
|
||||
staff_counters:
|
||||
flags_given: "signalements utiles"
|
||||
flagged_posts: "messages signalés"
|
||||
@ -569,6 +574,12 @@ fr:
|
||||
title: "Badge pour la carte de l'utilisateur"
|
||||
website: "Site internet"
|
||||
email_settings: "Courriel"
|
||||
like_notification_frequency:
|
||||
title: "Notifier lors d'un J'aime"
|
||||
always: "Toujours"
|
||||
first_time_and_daily: "La première fois qu'un message est aimé, et quotidiennement"
|
||||
first_time: "La première fois qu'un message est aimé"
|
||||
never: "Jamais"
|
||||
email_previous_replies:
|
||||
title: "Inclure les réponses précédentes en bas des courriels"
|
||||
unless_emailed: "sauf si déjà envoyé"
|
||||
@ -576,6 +587,8 @@ fr:
|
||||
never: "jamais"
|
||||
email_digests:
|
||||
title: "Quand je ne visite pas ce site, m'envoyer un résumé des nouveautés par courriel:"
|
||||
every_30_minutes: "toutes les 30 minutes"
|
||||
every_hour: "toutes les heures"
|
||||
daily: "quotidien"
|
||||
every_three_days: "tous les trois jours"
|
||||
weekly: "hebdomadaire"
|
||||
@ -707,9 +720,11 @@ fr:
|
||||
read_only_mode:
|
||||
enabled: "Le site est en mode lecture seule. Vous pouvez continer à naviguer, mais les réponses, J'aime et autre interactions sont désactivées pour l'instant."
|
||||
login_disabled: "Impossible de se connecté quand le site est en mode lecture seule."
|
||||
logout_disabled: "Impossible de se deconnecter quand le site est en mode lecture seule."
|
||||
too_few_topics_and_posts_notice: "<a href='http://blog.discourse.org/2014/08/building-a-discourse-community/'>Démarrons cette discussion!</a> Il y a actuellement <strong>%{currentTopics} / %{requiredTopics}</strong> sujets et <strong>%{currentPosts} / %{requiredPosts}</strong> messages. Les nouveaux visiteurs ont besoin de quelques conversations pour lire et répondre."
|
||||
too_few_topics_notice: "<a href='http://blog.discourse.org/2014/08/building-a-discourse-community/'>Démarrons cette discussion !</a> Il y a actuellement <strong>%{currentTopics} / %{requiredTopics}</strong> sujets. Les nouveaux visiteurs ont besoin de quelques conversations à lire et répondre."
|
||||
too_few_posts_notice: "<a href='http://blog.discourse.org/2014/08/building-a-discourse-community/'>Démarrons cette discussion !</a> Il y a actuellement <strong>%{currentPosts} / %{requiredPosts}</strong> messages. Les nouveaux visiteurs ont besoin de quelques conversations à lire et répondre."
|
||||
logs_error_rate_exceeded_notice: "%{timestamp}: Le taux actuel de <a href='%{url}' target='_blank'>%{rate} erreurs/%{duration}</a> a dépassé la limite des paramètres du site de %{siteSettingLimit} erreurs/%{duration}."
|
||||
learn_more: "en savoir plus…"
|
||||
year: 'an'
|
||||
year_desc: 'sujets créés durant les 365 derniers jours'
|
||||
@ -802,6 +817,9 @@ fr:
|
||||
twitter:
|
||||
title: "via Twitter"
|
||||
message: "Authentification via Twitter (assurez-vous que les popups ne soient pas bloquées)"
|
||||
instagram:
|
||||
title: "avec Instagram"
|
||||
message: "Authentification via Instagtram (assurez-vous que les popups ne soient pas bloquées)"
|
||||
facebook:
|
||||
title: "via Facebook"
|
||||
message: "Authentification via Facebook (assurez-vous que les popups ne soient pas bloquées)"
|
||||
@ -907,9 +925,13 @@ fr:
|
||||
group_mentioned: "<i title='group mentioned' class='fa fa-at'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
quoted: "<i title='cité' class='fa fa-quote-right'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
replied: "<i title='avec réponse' class='fa fa-reply'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
posted: "<i title='avec réponse' class='fa fa-reply'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
posted: "<i title='posted' class='fa fa-reply'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
edited: "<i title='édité' class='fa fa-pencil'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
liked: "<i title='aimé' class='fa fa-heart'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
liked_2: "<i title='liked' class='fa fa-heart'></i><p><span>{{username}}, {{username2}}</span> {{description}}</p>"
|
||||
liked_many:
|
||||
one: "<i title='liked' class='fa fa-heart'></i><p><span>{{username}}, {{username2}} et 1 autre</span> {{description}}</p>"
|
||||
other: "<i title='liked' class='fa fa-heart'></i><p><span>{{username}}, {{username2}} et {{count}} autres</span> {{description}}</p>"
|
||||
private_message: "<i title='message privé' class='fa fa-envelope-o'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
invited_to_private_message: "<i title='message privé' class='fa fa-envelope-o'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
invited_to_topic: "<i title='invité' class='fa fa-hand-o-right'></i><p><span>{{username}}</span> {{description}}</p>"
|
||||
@ -1462,6 +1484,7 @@ fr:
|
||||
last: "Dernière révision"
|
||||
hide: "Masquer la révision"
|
||||
show: "Afficher la révision"
|
||||
revert: "Revenir à cette révision"
|
||||
comparing_previous_to_current_out_of_total: "<strong>{{previous}}</strong> <i class='fa fa-arrows-h'></i> <strong>{{current}}</strong> / {{total}}"
|
||||
displays:
|
||||
inline:
|
||||
@ -1542,7 +1565,6 @@ fr:
|
||||
description: "Vous ne serez jamais notifié de rien concernant les nouveaux sujets dans ces catégories, et elles n'apparaîtront pas dans les dernières catégories."
|
||||
flagging:
|
||||
title: 'Merci de nous aider à garder notre communauté aimable !'
|
||||
private_reminder: 'les signalements sont privés, <b>seulement</b> visible aux modérateurs'
|
||||
action: 'Signaler ce message'
|
||||
take_action: "Signaler"
|
||||
notify_action: 'Message'
|
||||
@ -1554,7 +1576,7 @@ fr:
|
||||
submit_tooltip: "Soumettre le signalement privé"
|
||||
take_action_tooltip: "Atteindre le seuil de signalement immédiatement, plutôt que d'attendre plus de signalement de la communauté."
|
||||
cant: "Désolé, vous ne pouvez pas signaler ce message pour le moment"
|
||||
notify_staff: 'Notifier les responsables'
|
||||
notify_staff: 'Notifier les responsables de manière privée'
|
||||
formatted_name:
|
||||
off_topic: "C'est hors-sujet"
|
||||
inappropriate: "C'est inapproprié"
|
||||
@ -2087,10 +2109,11 @@ fr:
|
||||
references: "References"
|
||||
date: "Date"
|
||||
from: "From"
|
||||
to: "à"
|
||||
to: "To"
|
||||
cc: "Cc"
|
||||
subject: "Objet"
|
||||
body: "Corps"
|
||||
subject: "Subject"
|
||||
body: "Body"
|
||||
rejection_message: "Courriel de refus"
|
||||
filters:
|
||||
from_placeholder: "from@example.com"
|
||||
to_placeholder: "to@example.com"
|
||||
|
||||
@ -713,7 +713,7 @@ it:
|
||||
summary:
|
||||
enabled_description: "Stai visualizzando un riepilogo dell'argomento: è la comunità a determinare quali sono i messaggi più interessanti."
|
||||
description: "Ci sono <b>{{replyCount}}</b> risposte."
|
||||
description_time: "Ci sono <b>{{replyCount}}</b> risposte con un tempo stimato di lettura di <b>{{readingTime}} minuti</b>."
|
||||
description_time: "Ci sono <b>{{replyCount}}</b> risposte con un tempo stimato di lettura di <b>{{readingTime}} minuti</b>."
|
||||
enable: 'Riassumi Questo Argomento'
|
||||
disable: 'Mostra Tutti i Messaggi'
|
||||
deleted_filter:
|
||||
|
||||
@ -159,6 +159,8 @@ ko:
|
||||
more: "더"
|
||||
less: "덜"
|
||||
never: "전혀"
|
||||
every_30_minutes: "매 30분 마다"
|
||||
every_hour: "매 한시간 마다"
|
||||
daily: "매일"
|
||||
weekly: "매주"
|
||||
every_two_weeks: "격주"
|
||||
@ -545,6 +547,7 @@ ko:
|
||||
never: "절대"
|
||||
email_digests:
|
||||
title: "사이트 방문이 없을 경우, 새로운 글을 요약하여 메일로 보냄"
|
||||
every_30_minutes: "매 30분 마다"
|
||||
daily: "매일"
|
||||
every_three_days: "매 3일마다"
|
||||
weekly: "매주"
|
||||
|
||||
@ -179,6 +179,8 @@ nl:
|
||||
more: "Meer"
|
||||
less: "Minder"
|
||||
never: "nooit"
|
||||
every_30_minutes: "elke dertig minuten"
|
||||
every_hour: "elk uur"
|
||||
daily: "dagelijks"
|
||||
weekly: "wekelijks"
|
||||
every_two_weeks: "elke twee weken"
|
||||
@ -468,7 +470,7 @@ nl:
|
||||
muted_users: "Negeren"
|
||||
muted_users_instructions: "Negeer alle meldingen van deze leden."
|
||||
muted_topics_link: "Toon gedempte topics."
|
||||
automatically_unpin_topics: "ontspelt onderwerp automatische wanneer de bodem is bereikt."
|
||||
automatically_unpin_topics: "Topics automatisch lospinnen als ik het laatste bericht bereik."
|
||||
staff_counters:
|
||||
flags_given: "behulpzame markeringen"
|
||||
flagged_posts: "gemarkeerde berichten"
|
||||
@ -569,6 +571,11 @@ nl:
|
||||
title: "Badge van gebruikersprofiel"
|
||||
website: "Website"
|
||||
email_settings: "E-mail"
|
||||
like_notification_frequency:
|
||||
always: "Altijd"
|
||||
first_time_and_daily: "De eerste keer dat iemand een bericht leuk vond en dagelijks"
|
||||
first_time: "De eerste keer dat iemand een bericht leuk vond"
|
||||
never: "Nooit"
|
||||
email_previous_replies:
|
||||
title: "Voeg de vorige reacties bij onderaan de emails"
|
||||
unless_emailed: "tenzij eerder verzonden"
|
||||
@ -576,6 +583,8 @@ nl:
|
||||
never: "nooit"
|
||||
email_digests:
|
||||
title: "Stuur me een mail met de laatste updates wanneer ik de site niet bezoek:"
|
||||
every_30_minutes: "elke dertig minuten"
|
||||
every_hour: "elk uur"
|
||||
daily: "dagelijks"
|
||||
every_three_days: "elke drie dagen"
|
||||
weekly: "wekelijks"
|
||||
@ -707,6 +716,7 @@ nl:
|
||||
read_only_mode:
|
||||
enabled: "De site is in alleen lezen modus. Interactie is niet mogelijk."
|
||||
login_disabled: "Zolang de site in read-only modus is, kan er niet ingelogd worden."
|
||||
logout_disabled: "Uitloggen is uitgeschakeld als de site op alleen lezen staat."
|
||||
too_few_topics_and_posts_notice: "Laten <a href='http://blog.discourse.org/2014/08/building-a-discourse-community/'>we de discussie starten!</a> Er zijn al <strong>%{currentTopics} / %{requiredTopics}</strong> topics en <strong>%{currentPosts} / %{requiredPosts}</strong> berichten. Nieuwe bezoekers hebben conversaties nodig om te lezen en reageren."
|
||||
too_few_topics_notice: "Laten <a href='http://blog.discourse.org/2014/08/building-a-discourse-community/'>we de discussie starten!</a> Er zijn al <strong>%{currentTopics} / %{requiredTopics}</strong> topics en <strong>%{currentPosts} / %{requiredPosts}</strong> berichten. Nieuwe bezoekers hebben conversaties nodig om te lezen en reageren."
|
||||
too_few_posts_notice: "Laten <a href='http://blog.discourse.org/2014/08/building-a-discourse-community/'>we de discussie starten!</a>. Er zijn al <strong>%{currentPosts} / %{requiredPosts}</strong> posts Nieuwe bezoekers hebben conversaties nodig om te lezen en reageren."
|
||||
@ -802,6 +812,9 @@ nl:
|
||||
twitter:
|
||||
title: "met Twitter"
|
||||
message: "Inloggen met een Twitteraccount (zorg ervoor dat je popup blocker uit staat)"
|
||||
instagram:
|
||||
title: "met Instagram"
|
||||
message: "Inloggen met een Instagram-account (zorg ervoor dat je pop-upblocker uitstaat)."
|
||||
facebook:
|
||||
title: "met Facebook"
|
||||
message: "Inloggen met een Facebookaccount (zorg ervoor dat je popup blocker uit staat)"
|
||||
@ -1542,7 +1555,6 @@ nl:
|
||||
description: "Je zult nooit op de hoogte worden gebracht over nieuwe topics in deze categorie, en ze zullen niet verschijnen in Nieuwste."
|
||||
flagging:
|
||||
title: 'Bedankt voor het helpen beleefd houden van onze gemeenschap!'
|
||||
private_reminder: 'vlaggen zijn privé, <b>alleen</b> zichtbaar voor de staf'
|
||||
action: 'Meld bericht'
|
||||
take_action: "Onderneem actie"
|
||||
notify_action: 'Bericht'
|
||||
@ -1554,7 +1566,6 @@ nl:
|
||||
submit_tooltip: "Verstuur de privé markering"
|
||||
take_action_tooltip: "Bereik de vlag drempel direct, in plaats van het wachten op meer gemeenschapsvlaggen"
|
||||
cant: "Sorry, je kan dit bericht momenteel niet melden."
|
||||
notify_staff: 'Licht de staf in'
|
||||
formatted_name:
|
||||
off_topic: "Het is off topic"
|
||||
inappropriate: "Het is ongepast"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user