Version bump

This commit is contained in:
Neil Lalonde 2016-05-10 10:52:27 -04:00
commit 380891875e
150 changed files with 1201 additions and 887 deletions

View File

@ -27,7 +27,7 @@ rvm:
- 2.0.0
- 2.1
- 2.2
- 2.3.0
- 2.3.1
services:
- redis-server

View File

@ -65,7 +65,7 @@ GEM
byebug (8.2.1)
certified (1.0.0)
coderay (1.1.0)
concurrent-ruby (1.0.1)
concurrent-ruby (1.0.2)
connection_pool (2.2.0)
crass (1.0.2)
daemons (1.2.3)
@ -92,7 +92,7 @@ GEM
railties (>= 3.1)
ember-source (1.12.2)
erubis (2.7.0)
eventmachine (1.0.8)
eventmachine (1.2.0.1)
excon (0.45.4)
execjs (2.6.0)
exifr (1.2.4)
@ -149,7 +149,7 @@ GEM
thor (~> 0.15)
libv8 (3.16.14.13)
listen (0.7.3)
logster (1.2.2)
logster (1.2.3)
loofah (2.0.3)
nokogiri (>= 1.5.9)
lru_redux (1.1.0)
@ -282,7 +282,7 @@ GEM
ffi (>= 1.0.6)
msgpack (>= 0.4.3)
trollop (>= 1.16.2)
redis (3.2.2)
redis (3.3.0)
redis-namespace (1.5.2)
redis (~> 3.0, >= 3.0.4)
ref (2.0.0)
@ -344,7 +344,7 @@ GEM
shoulda-context (1.2.1)
shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
sidekiq (4.0.2)
sidekiq (4.1.2)
concurrent-ruby (~> 1.0)
connection_pool (~> 2.2, >= 2.2.0)
redis (~> 3.2, >= 3.2.1)
@ -501,4 +501,4 @@ DEPENDENCIES
unicorn
BUNDLED WITH
1.11.2
1.12.3

View File

@ -42,7 +42,7 @@ Discourse is built for the *next* 10 years of the Internet, so our requirements
| -------- | ------- | ----------- |
| Safari 6.1+| iPad 2+ | iOS 7+ |
| Google Chrome 23+ | Android 4.3+ | Android 4.3+ |
| Internet Explorer 10+ | Windows 8 | Windows Phone 8 |
| Internet Explorer 11+ | Windows 8 | Windows Phone 8 |
| Firefox 16+ | |
## Built With

View File

@ -28,7 +28,7 @@ export default Ember.Controller.extend({
@computed('model.type')
showCategoryOptions(modelType) {
return !modelType.match(/_private_messages$/);
return !modelType.match(/_private_messages$/) && !modelType.match(/^page_view_/);
},
@computed('model.type')

View File

@ -1,3 +1,4 @@
import computed from 'ember-addons/ember-computed-decorators';
import { propertyNotEqual } from 'discourse/lib/computed';
import { popupAjaxError } from 'discourse/lib/ajax-error';
import ApiKey from 'admin/models/api-key';
@ -6,11 +7,42 @@ import TL3Requirements from 'admin/models/tl3-requirements';
const AdminUser = Discourse.User.extend({
customGroups: Em.computed.filter("groups", (g) => !g.automatic && Group.create(g)),
automaticGroups: Em.computed.filter("groups", (g) => g.automatic && Group.create(g)),
customGroups: Ember.computed.filter("groups", g => !g.automatic && Group.create(g)),
automaticGroups: Ember.computed.filter("groups", g => g.automatic && Group.create(g)),
canViewProfile: Ember.computed.or("active", "staged"),
@computed("bounce_score", "reset_bounce_score_after")
bounceScore(bounce_score, reset_bounce_score_after) {
if (bounce_score > 0) {
return `${bounce_score} - ${moment(reset_bounce_score_after).format('LL')}`;
} else {
return bounce_score;
}
},
@computed("bounce_score")
bounceScoreExplanation(bounce_score) {
if (bounce_score === 0) {
return I18n.t("admin.user.bounce_score_explanation.none");
} else if (bounce_score < Discourse.SiteSettings.bounce_score_threshold) {
return I18n.t("admin.user.bounce_score_explanation.some");
} else {
return I18n.t("admin.user.bounce_score_explanation.threshold_reached");
}
},
canResetBounceScore: Ember.computed.gt("bounce_score", 0),
resetBounceScore() {
return Discourse.ajax(`/admin/users/${this.get("id")}/reset_bounce_score`, {
type: 'POST'
}).then(() => this.setProperties({
"bounce_score": 0,
"reset_bounce_score_after": null
}));
},
generateApiKey() {
const self = this;
return Discourse.ajax("/admin/users/" + this.get('id') + "/generate_api_key", {

View File

@ -5,7 +5,7 @@
<th>{{i18n 'admin.api.user'}}</th>
<th>&nbsp;</th>
</tr>
{{#each k in model}}
{{#each model as |k|}}
<tr>
<td class='key'>{{k.key}}</td>
<td>

View File

@ -13,7 +13,7 @@
</div>
</th>
</tr>
{{#each backup in model}}
{{#each model as |backup|}}
<tr>
<td>{{backup.filename}}</td>
<td>{{human-size backup.size}}</td>

View File

@ -3,7 +3,7 @@
<div class='content-list span6'>
<h3>{{i18n 'admin.badges.title'}}</h3>
<ul>
{{#each badge in model}}
{{#each model as |badge|}}
<li>
{{#link-to 'adminBadges.show' badge.id}}
{{badge-button badge=badge}}

View File

@ -1,7 +1,7 @@
<div class='content-list span6'>
<h3>{{i18n 'admin.customize.colors.long_title'}}</h3>
<ul>
{{#each scheme in model}}
{{#each model as |scheme|}}
{{#unless scheme.is_base}}
<li><a {{action "selectColorScheme" scheme}} {{bind-attr class="scheme.selected:active"}}>{{scheme.description}}</a></li>
{{/unless}}
@ -50,7 +50,7 @@
</tr>
</thead>
<tbody>
{{#each c in colors}}
{{#each colors as |c|}}
<tr {{bind-attr class="c.changed c.valid:valid:invalid"}}>
<td class="name" {{bind-attr title="c.name"}}>
<b>{{c.translatedName}}</b>

View File

@ -19,7 +19,7 @@
</thead>
<tbody>
{{#unless loading}}
{{#each r in user_reports}}
{{#each user_reports as |r|}}
{{admin-report-trust-level-counts report=r}}
{{/each}}
{{/unless}}
@ -58,7 +58,7 @@
</thead>
<tbody>
{{#unless loading}}
{{#each r in global_reports}}
{{#each global_reports as |r|}}
{{admin-report-counts report=r}}
{{/each}}
{{/unless}}
@ -80,7 +80,7 @@
</thead>
<tbody>
{{#unless loading}}
{{#each r in page_view_reports}}
{{#each page_view_reports as |r|}}
{{admin-report-counts report=r}}
{{/each}}
{{/unless}}
@ -102,7 +102,7 @@
</thead>
<tbody>
{{#unless loading}}
{{#each r in private_message_reports}}
{{#each private_message_reports as |r|}}
{{admin-report-counts report=r}}
{{/each}}
{{/unless}}
@ -124,7 +124,7 @@
</thead>
<tbody>
{{#unless loading}}
{{#each r in mobile_reports}}
{{#each mobile_reports as |r|}}
{{admin-report-counts report=r}}
{{/each}}
{{/unless}}
@ -171,7 +171,7 @@
</thead>
<tbody>
{{#unless loading}}
{{#each r in http_reports}}
{{#each http_reports as |r|}}
{{admin-report-counts report=r}}
{{/each}}
{{/unless}}
@ -195,7 +195,7 @@
<p {{bind-attr class="loadingProblems:invisible"}}>
{{i18n 'admin.dashboard.problems_found'}}
<ul {{bind-attr class="loadingProblems:invisible"}}>
{{#each problem in problems}}
{{#each problems as |problem|}}
<li>{{{problem}}}</li>
{{/each}}
</ul>
@ -231,7 +231,7 @@
</tr>
</thead>
{{#unless loading}}
{{#each data in top_referred_topics.data}}
{{#each top_referred_topics.data as |data|}}
<tbody>
<tr>
<td class="title">
@ -259,7 +259,7 @@
</tr>
</thead>
{{#unless loading}}
{{#each s in top_traffic_sources.data}}
{{#each top_traffic_sources.data as |s|}}
<tbody>
<tr>
<td class="title">{{s.domain}}</td>
@ -282,7 +282,7 @@
</tr>
</thead>
{{#unless loading}}
{{#each r in top_referrers.data}}
{{#each top_referrers.data as |r|}}
<tbody>
<tr>
<td class="title">{{#link-to 'adminUser' r.user_id r.username}}{{unbound r.username}}{{/link-to}}</td>

View File

@ -6,7 +6,6 @@
<th>{{i18n 'admin.email.user'}}</th>
<th>{{i18n 'admin.email.to_address'}}</th>
<th>{{i18n 'admin.email.email_type'}}</th>
<th>{{i18n 'admin.email.skipped_reason'}}</th>
</tr>
</thead>
@ -15,10 +14,9 @@
<td>{{text-field value=filter.user placeholderKey="admin.email.logs.filters.user_placeholder"}}</td>
<td>{{text-field value=filter.address placeholderKey="admin.email.logs.filters.address_placeholder"}}</td>
<td>{{text-field value=filter.type placeholderKey="admin.email.logs.filters.type_placeholder"}}</td>
<td>{{text-field value=filter.skipped_reason placeholderKey="admin.email.logs.filters.skipped_reason_placeholder"}}</td>
</tr>
{{#each l in model}}
{{#each model as |l|}}
<tr>
<td>{{format-date l.created_at}}</td>
<td>
@ -31,16 +29,9 @@
</td>
<td><a href='mailto:{{unbound l.to_address}}'>{{l.to_address}}</a></td>
<td>{{l.email_type}}</td>
<td>
{{#if l.post_url}}
<a href="{{l.post_url}}">{{l.skipped_reason}}</a>
{{else}}
{{l.skipped_reason}}
{{/if}}
</td>
</tr>
{{else}}
<tr><td colspan="5">{{i18n 'admin.email.logs.none'}}</td></tr>
<tr><td colspan="4">{{i18n 'admin.email.logs.none'}}</td></tr>
{{/each}}
</table>

View File

@ -16,7 +16,7 @@
<td>{{text-field value=filter.subject placeholderKey="admin.email.incoming_emails.filters.subject_placeholder"}}</td>
</tr>
{{#each email in model}}
{{#each model as |email|}}
<tr>
<td class="time">{{format-date email.created_at}}</td>
<td class="username">
@ -32,10 +32,10 @@
</div>
</td>
<td class="addresses">
{{#each to in email.to_addresses}}
{{#each email.to_addresses as |to|}}
<p><a href="mailto:{{unbound to}}" title="TO">{{unbound to}}</a></p>
{{/each}}
{{#each cc in email.cc_addresses}}
{{#each email.cc_addresses as |cc|}}
<p><a href="mailto:{{unbound cc}}" title="CC">{{unbound cc}}</a></p>
{{/each}}
</td>

View File

@ -18,7 +18,7 @@
<td>{{text-field value=filter.error placeholderKey="admin.email.incoming_emails.filters.error_placeholder"}}</td>
</tr>
{{#each email in model}}
{{#each model as |email|}}
<tr>
<td class="time">{{format-date email.created_at}}</td>
<td class="username">
@ -38,10 +38,10 @@
</div>
</td>
<td class="addresses">
{{#each to in email.to_addresses}}
{{#each email.to_addresses as |to|}}
<p><a href="mailto:{{unbound to}}" title="TO">{{unbound to}}</a></p>
{{/each}}
{{#each cc in email.cc_addresses}}
{{#each email.cc_addresses as |cc|}}
<p><a href="mailto:{{unbound cc}}" title="CC">{{unbound cc}}</a></p>
{{/each}}
</td>

View File

@ -18,7 +18,7 @@
<td>{{text-field value=filter.reply_key placeholderKey="admin.email.logs.filters.reply_key_placeholder"}}</td>
</tr>
{{#each l in model}}
{{#each model as |l|}}
<tr>
<td>{{format-date l.created_at}}</td>
<td>

View File

@ -18,7 +18,7 @@
<td>{{text-field value=filter.skipped_reason placeholderKey="admin.email.logs.filters.skipped_reason_placeholder"}}</td>
</tr>
{{#each l in model}}
{{#each model as |l|}}
<tr>
<td>{{format-date l.created_at}}</td>
<td>

View File

@ -4,7 +4,7 @@
<td>{{delivery_method}}</td>
</tr>
{{#each s in model.settings}}
{{#each model.settings as |s|}}
<tr>
<th style='width: 25%'>{{s.name}}</th>
<td>{{s.value}}</td>

View File

@ -16,7 +16,7 @@
</tr>
</thead>
<tbody>
{{#each e in controller}}
{{#each controller as |e|}}
<tr>
<th><img class="emoji" src="{{unbound e.url}}" title="{{unbound e.name}}"></th>
<th>:{{e.name}}:</th>

View File

@ -42,7 +42,7 @@
<td class='flaggers'>
<table>
<tbody>
{{#each flagger in flaggedPost.flaggers}}
{{#each flaggedPost.flaggers as |flagger|}}
<tr>
<td class='avatar'>
{{#link-to 'adminUser' flagger.user}}
@ -67,7 +67,7 @@
{{#if adminOldFlagsView}}
<table>
<tbody>
{{#each flagger in flaggedPost.flaggers}}
{{#each flaggedPost.flaggers as |flagger|}}
<tr>
<td class='avatar'>
{{#link-to 'adminUser' flagger.disposedBy}}
@ -101,7 +101,7 @@
</tr>
{{/if}}
{{#each c in flaggedPost.conversations}}
{{#each flaggedPost.conversations as |c|}}
<tr class='message'>
<td></td>
<td colspan="3">

View File

@ -2,7 +2,7 @@
<div class='content-list span6'>
<h3>{{i18n 'admin.groups.edit'}}</h3>
<ul>
{{#each group in controller}}
{{#each controller as |group|}}
<li>
{{#link-to "adminGroup" group.type group.name}}{{group.name}}
{{#if group.userCountDisplay}}

View File

@ -34,7 +34,7 @@
{{i18n 'admin.badges.preview.sample'}}
</p>
<ul>
{{#each html in processed_sample}}
{{#each processed_sample as |html|}}
<li>{{{html}}}</li>
{{/each}}
</ul>

View File

@ -1,7 +1,7 @@
<div class="modal-body">
<div>
<ul class='badge-groupings'>
{{#each wc in workingCopy}}
{{#each workingCopy as |wc|}}
<li>
{{#if wc.editing}}
{{input value=wc.name}}

View File

@ -21,7 +21,7 @@
</tr>
</thead>
<tbody>
{{#each plugin in controller}}
{{#each controller as |plugin|}}
<tr>
<td>
{{#if plugin.url}}

View File

@ -2,7 +2,7 @@
<ul class="nav nav-stacked">
{{nav-item route='adminPlugins.index' label="admin.plugins.title"}}
{{#each route in adminRoutes}}
{{#each adminRoutes as |route|}}
{{nav-item route=route.full_location label=route.label}}
{{/each}}
</ul>

View File

@ -37,7 +37,7 @@
<th>{{model.yaxis}}</th>
</tr>
{{#each row in model.dataReversed}}
{{#each model.dataReversed as |row|}}
<tr>
<td>{{row.x}}</td>
<td>

View File

@ -14,7 +14,7 @@
<div class="admin-nav pull-left">
<ul class="nav nav-stacked">
{{#each category in controller}}
{{#each controller as |category|}}
{{#link-to 'adminSiteSettingsCategory' category.nameKey tagName='li' class=category.nameKey}}
{{#link-to 'adminSiteSettingsCategory' category.nameKey class=category.nameKey}}
{{category.name}}

View File

@ -128,7 +128,7 @@
{{#if userFields}}
<section class='details'>
{{#each uf in userFields}}
{{#each userFields as |uf|}}
<div class='display-row'>
<div class='field'>{{uf.name}}</div>
<div class='value'>
@ -350,7 +350,20 @@
<div class="display-row">
<div class='field'>{{i18n 'admin.user.staged'}}</div>
<div class='value'>{{model.staged}}</div>
<div class='controls'>{{i18n 'admin.user.stage_explanation'}}</div>
<div class='controls'>{{i18n 'admin.user.staged_explanation'}}</div>
</div>
<div class="display-row">
<div class='field'>{{i18n 'admin.user.bounce_score'}}</div>
<div class='value'>{{model.bounceScore}}</div>
<div class='controls'>
{{#if model.canResetBounceScore}}
<button class='btn' {{action "resetBounceScore" target="content"}} title={{i18n "admin.user.reset_bounce_score.title"}}>
{{i18n "admin.user.reset_bounce_score.label"}}
</button>
{{/if}}
{{model.bounceScoreExplanation}}
</div>
</div>
</section>

View File

@ -41,7 +41,7 @@
<th>&nbsp;</th>
</tr>
{{#each user in model}}
{{#each model as |user|}}
<tr {{bind-attr class="user.selected user.active::not-activated"}}>
{{#if controller.showApproval}}
<td>

View File

@ -0,0 +1,7 @@
import { propertyEqual } from 'discourse/lib/computed';
export default Ember.Component.extend({
tagName: 'tr',
classNameBindings: ['me'],
me: propertyEqual('item.user.id', 'currentUser.id')
});

View File

@ -0,0 +1,54 @@
import { MAX_MESSAGE_LENGTH } from 'discourse/models/post-action-type';
import computed from 'ember-addons/ember-computed-decorators';
export default Ember.Component.extend({
@computed('flag.name_key')
customPlaceholder(nameKey) {
return I18n.t("flagging.custom_placeholder_" + nameKey);
},
@computed('flag.name', 'flag.name_key', 'flag.is_custom_flag', 'username')
formattedName(name, nameKey, isCustomFlag, username) {
if (isCustomFlag) {
return name.replace("{{username}}", username);
} else {
return I18n.t("flagging.formatted_name." + nameKey);
}
},
@computed('flag', 'selectedFlag')
selected(flag, selectedFlag) {
return flag === selectedFlag;
},
showMessageInput: Em.computed.and('flag.is_custom_flag', 'selected'),
showDescription: Em.computed.not('showMessageInput'),
isNotifyUser: Em.computed.equal('flag.name_key', 'notify_user'),
@computed('message.length')
customMessageLengthClasses(messageLength) {
return (messageLength < Discourse.SiteSettings.min_private_message_post_length) ? "too-short" : "ok";
},
@computed('message.length')
customMessageLength(messageLength) {
const len = messageLength || 0;
const minLen = Discourse.SiteSettings.min_private_message_post_length;
if (len === 0) {
return I18n.t("flagging.custom_message.at_least", { n: minLen });
} else if (len < minLen) {
return I18n.t("flagging.custom_message.more", { n: minLen - len });
} else {
return I18n.t("flagging.custom_message.left", {
n: MAX_MESSAGE_LENGTH - len
});
}
},
actions: {
changePostActionType(at) {
this.sendAction('changePostActionType', at);
}
}
});

View File

@ -1,5 +1,5 @@
import { propertyEqual } from 'discourse/lib/computed';
import BufferedContent from 'discourse/mixins/buffered-content';
import { bufferedProperty } from 'discourse/mixins/buffered-content';
import { popupAjaxError } from 'discourse/lib/ajax-error';
function updateState(state, opts) {
@ -12,18 +12,14 @@ function updateState(state, opts) {
if (opts.deleteUser) { args.delete_user = true; }
post.update(args).then(() => {
this.get('controllers.queued-posts.model').removeObject(post);
this.sendAction('removePost', post);
// this.get('controllers.queued-posts.model').removeObject(post);
}).catch(popupAjaxError);
};
}
export default Ember.Controller.extend(BufferedContent, {
needs: ['queued-posts'],
post: Ember.computed.alias('model'),
currentlyEditing: Ember.computed.alias('controllers.queued-posts.editing'),
editing: propertyEqual('model', 'currentlyEditing'),
export default Ember.Component.extend(bufferedProperty('post'), {
editing: propertyEqual('post', 'currentlyEditing'),
_confirmDelete: updateState('rejected', {deleteUser: true}),
actions: {
@ -31,7 +27,7 @@ export default Ember.Controller.extend(BufferedContent, {
reject: updateState('rejected'),
deleteUser() {
bootbox.confirm(I18n.t('queue.delete_prompt', {username: this.get('model.user.username')}), (confirmed) => {
bootbox.confirm(I18n.t('queue.delete_prompt', {username: this.get('post.user.username')}), (confirmed) => {
if (confirmed) { this._confirmDelete(); }
});
},
@ -39,7 +35,7 @@ export default Ember.Controller.extend(BufferedContent, {
edit() {
// This is stupid but pagedown cannot be on the screen twice or it will break
this.set('currentlyEditing', null);
Ember.run.scheduleOnce('afterRender', () => this.set('currentlyEditing', this.get('model')));
Ember.run.scheduleOnce('afterRender', () => this.set('currentlyEditing', this.get('post')));
},
confirmEdit() {

View File

@ -1,34 +1,19 @@
import StringBuffer from 'discourse/mixins/string-buffer';
export default Ember.View.extend(StringBuffer, {
topic: Em.computed.alias("content"),
rerenderTriggers: ['controller.bulkSelectEnabled', 'topic.pinned'],
export default Ember.Component.extend(StringBuffer, {
rerenderTriggers: ['bulkSelectEnabled', 'topic.pinned'],
tagName: 'tr',
rawTemplate: 'list/topic-list-item.raw',
classNameBindings: ['controller.checked',
':topic-list-item',
'unboundClassNames',
'selected'],
classNameBindings: [':topic-list-item', 'unboundClassNames'],
attributeBindings: ['data-topic-id'],
'data-topic-id': Em.computed.alias('topic.id'),
actions: {
select() {
this.set('controller.selectedRow', this);
},
toggleBookmark() {
const self = this;
this.get('topic').toggleBookmark().finally(function() {
self.rerender();
});
this.get('topic').toggleBookmark().finally(() => this.rerender());
}
},
selected: function() {
return this.get('controller.selectedRow')===this;
}.property('controller.selectedRow'),
unboundClassNames: function() {
let classes = [];
const topic = this.get('topic');
@ -51,7 +36,7 @@ export default Ember.View.extend(StringBuffer, {
}.property(),
titleColSpan: function() {
return (!this.get('controller.hideCategory') &&
return (!this.get('hideCategory') &&
this.get('topic.isPinnedUncategorized') ? 2 : 1);
}.property("topic.isPinnedUncategorized"),
@ -70,11 +55,11 @@ export default Ember.View.extend(StringBuffer, {
return false;
}
if (this.get('controller.expandGloballyPinned') && this.get('topic.pinned_globally')) {
if (this.get('expandGloballyPinned') && this.get('topic.pinned_globally')) {
return true;
}
if (this.get('controller.expandAllPinned')) {
if (this.get('expandAllPinned')) {
return true;
}
@ -96,7 +81,7 @@ export default Ember.View.extend(StringBuffer, {
}
if (target.hasClass('bulk-select')) {
const selected = this.get('controller.selected');
const selected = this.get('selected');
const topic = this.get('topic');
if (target.is(':checked')) {

View File

@ -9,7 +9,7 @@ export default MountWidget.extend({
this.args = { notifications: this.get('notifications') };
},
@observes('notifications.length')
@observes('notifications.length', 'notifications.@each.read')
_triggerRefresh() {
this.queueRerender();
}

View File

@ -1,5 +0,0 @@
import { propertyEqual } from 'discourse/lib/computed';
export default Ember.Controller.extend({
me: propertyEqual('model.user.id', 'currentUser.id')
});

View File

@ -1,48 +0,0 @@
import { MAX_MESSAGE_LENGTH } from 'discourse/models/post-action-type';
// Supports logic for flags in the modal
export default Ember.Controller.extend({
needs: ['flag'],
message: Em.computed.alias('controllers.flag.message'),
isWarning: Em.computed.alias('controllers.flag.isWarning'),
customPlaceholder: function(){
return I18n.t("flagging.custom_placeholder_" + this.get('model.name_key'));
}.property('model.name_key'),
formattedName: function(){
if (this.get("model.is_custom_flag")) {
return this.get('model.name').replace("{{username}}", this.get('controllers.flag.model.username'));
} else {
return I18n.t("flagging.formatted_name." + this.get('model.name_key'));
}
}.property('model.name', 'model.name_key', 'model.is_custom_flag'),
selected: function() {
return this.get('model') === this.get('controllers.flag.selected');
}.property('controllers.flag.selected'),
showMessageInput: Em.computed.and('model.is_custom_flag', 'selected'),
showDescription: Em.computed.not('showMessageInput'),
isNotifyUser: Em.computed.equal('model.name_key', 'notify_user'),
customMessageLengthClasses: function() {
return (this.get('message.length') < Discourse.SiteSettings.min_private_message_post_length) ? "too-short" : "ok";
}.property('message.length'),
customMessageLength: function() {
var len = this.get('message.length') || 0;
var minLen = Discourse.SiteSettings.min_private_message_post_length;
if (len === 0) {
return I18n.t("flagging.custom_message.at_least", { n: minLen });
} else if (len < minLen) {
return I18n.t("flagging.custom_message.more", { n: minLen - len });
} else {
return I18n.t("flagging.custom_message.left", {
n: MAX_MESSAGE_LENGTH - len
});
}
}.property('message.length')
});

View File

@ -39,7 +39,6 @@ function parseName(fullName) {
}
export default Ember.DefaultResolver.extend({
parseName: parseName,
normalize(fullName) {

View File

@ -1,6 +1,6 @@
import Mobile from 'discourse/lib/mobile';
// Initializes the `Mobile` helper object.
// Initializes the `Mobile` helper object.
export default {
name: 'mobile',
after: 'inject-objects',
@ -16,4 +16,3 @@ export default {
app.registry.resolver.__resolver__.mobileView = Mobile.mobileView;
}
};

View File

@ -40,7 +40,7 @@
};
};
// #each .. in support
// #each .. in support (as format is transformed to this)
RawHandlebars.registerHelper('each', function(localName,inKeyword,contextName,options){
var list = Em.get(this, contextName);
var output = [];
@ -68,6 +68,21 @@
RawHandlebars.JavaScriptCompiler.prototype.compiler = RawHandlebars.JavaScriptCompiler;
RawHandlebars.JavaScriptCompiler.prototype.namespace = "Discourse.EmberCompatHandlebars";
function buildPath(blk, args) {
var result = { type: "PathExpression",
data: false,
depth: blk.path.depth,
loc: blk.path.loc };
// Server side precompile doesn't have jquery.extend
Object.keys(args).forEach(function (a) {
result[a] = args[a];
});
return result;
}
function replaceGet(ast) {
var visitor = new Handlebars.Visitor();
visitor.mutating = true;
@ -75,19 +90,25 @@
visitor.MustacheStatement = function(mustache) {
if (!(mustache.params.length || mustache.hash)) {
mustache.params[0] = mustache.path;
mustache.path = {
type: "PathExpression",
data: false,
depth: mustache.path.depth,
parts: ["get"],
original: "get",
loc: mustache.path.loc,
strict: true,
falsy: true
};
mustache.path = buildPath(mustache, { parts: ['get'], original: 'get', strict: true, falsy: true });
}
return Handlebars.Visitor.prototype.MustacheStatement.call(this, mustache);
};
// rewrite `each x as |y|` as each y in x`
// This allows us to use the same syntax in all templates
visitor.BlockStatement = function(block) {
if (block.path.original === 'each' && block.params.length === 1) {
var paramName = block.program.blockParams[0];
block.params = [ buildPath(block, { original: paramName }),
{ type: "CommentStatement", value: "in" },
block.params[0] ];
delete block.program.blockParams;
}
return Handlebars.Visitor.prototype.BlockStatement.call(this, block);
};
visitor.accept(ast);
}

View File

@ -294,10 +294,6 @@ export default {
$articles.removeClass('selected');
$article.addClass('selected');
if ($article.is('.topic-list-item')) {
this.sendToTopicListItemView('select');
}
if ($article.is('.topic-post')) {
$('a.tabLoc', $article).focus();
}

View File

@ -1,3 +1,5 @@
let mobileForced = false;
// An object that is responsible for logic related to mobile devices.
const Mobile = {
isMobileDevice: false,
@ -5,8 +7,10 @@ const Mobile = {
init() {
const $html = $('html');
this.isMobileDevice = $html.hasClass('mobile-device');
this.mobileView = $html.hasClass('mobile-view');
this.isMobileDevice = mobileForced || $html.hasClass('mobile-device');
this.mobileView = mobileForced || $html.hasClass('mobile-view');
if (mobileForced) { return; }
try{
if (window.location.search.match(/mobile_view=1/)){
@ -27,8 +31,8 @@ const Mobile = {
}
},
toggleMobileView: function() {
try{
toggleMobileView() {
try {
if (localStorage) {
localStorage.mobileView = !this.mobileView;
}
@ -38,11 +42,19 @@ const Mobile = {
this.reloadPage(!this.mobileView);
},
reloadPage: function(mobile) {
reloadPage(mobile) {
window.location.assign(window.location.pathname + '?mobile_view=' + (mobile ? '1' : '0'));
}
};
export function forceMobile() {
mobileForced = true;
}
export function resetMobile() {
mobileForced = false;
}
// Backwards compatibiltity, deprecated
Object.defineProperty(Discourse, 'Mobile', {
get: function() {

View File

@ -139,9 +139,7 @@ const DiscourseURL = Ember.Object.extend({
}
}
rewrites.forEach(function(rw) {
path = path.replace(rw.regexp, rw.replacement);
});
rewrites.forEach(rw => path = path.replace(rw.regexp, rw.replacement));
if (this.navigatedToPost(oldPath, path)) { return; }
// Schedule a DOM cleanup event

View File

@ -1,12 +1,13 @@
import buildCategoryRoute from 'discourse/routes/build-category-route';
import buildTopicRoute from 'discourse/routes/build-topic-route';
import DiscoverySortableController from 'discourse/controllers/discovery-sortable';
import TagsShowRoute from 'discourse/routes/tags-show';
export default {
after: 'inject-discourse-objects',
name: 'dynamic-route-builders',
initialize(container, app) {
initialize(registry, app) {
app.DiscoveryCategoryController = DiscoverySortableController.extend();
app.DiscoveryParentCategoryController = DiscoverySortableController.extend();
app.DiscoveryCategoryNoneController = DiscoverySortableController.extend();
@ -58,5 +59,14 @@ export default {
app[`DiscoveryTop${periodCapitalized}ParentCategoryRoute`] = buildCategoryRoute('top/' + period);
app[`DiscoveryTop${periodCapitalized}CategoryNoneRoute`] = buildCategoryRoute('top/' + period, {no_subcategories: true});
});
app["TagsShowCategoryRoute"] = TagsShowRoute.extend();
app["TagsShowParentCategoryRoute"] = TagsShowRoute.extend();
site.get('filters').forEach(function(filter) {
app["TagsShow" + filter.capitalize() + "Route"] = TagsShowRoute.extend({ filterMode: filter });
app["TagsShowCategory" + filter.capitalize() + "Route"] = TagsShowRoute.extend({ filterMode: filter });
app["TagsShowParentCategory" + filter.capitalize() + "Route"] = TagsShowRoute.extend({ filterMode: filter });
});
}
};

View File

@ -13,6 +13,10 @@ export default DiscourseRoute.extend({
},
actions: {
removePost(post) {
this.modelFor('queued-posts').removeObject(post);
},
refresh() {
this.modelFor('queued-posts').refresh();
}

View File

@ -7,6 +7,13 @@ export default Discourse.Route.extend({
return I18n.t("tagging.tags");
},
setupController(controller, model) {
this.controllerFor('tags.index').setProperties({
model,
sortProperties: this.siteSettings.tags_sort_alphabetically ? ['id'] : ['count:desc', 'id']
});
},
actions: {
didTransition() {
this.controllerFor("application").set("showFooter", true);

View File

@ -2,7 +2,7 @@
<h1>{{i18n 'badges.title'}}</h1>
<div class='badge-groups'>
{{#each bg in badgeGroups}}
{{#each badgeGroups as |bg|}}
<div class='badge-grouping'>
<div class='title'>
<h3>{{bg.badgeGrouping.displayName}}</h3>

View File

@ -1,6 +1,6 @@
<div class='autocomplete'>
<ul>
{{#each option in options}}
{{#each options as |option|}}
<li><a href>{{category-link option allowUncategorized="true" link="false"}}</a></li>
{{/each}}
</ul>

View File

@ -1,6 +1,6 @@
<div class='autocomplete'>
<ul>
{{#each option in options}}
{{#each options as |option|}}
<li>
{{#if option.model}}
<a href>{{category-link option.model allowUncategorized="true" link="false"}}</a>

View File

@ -8,7 +8,7 @@
{{#if view.noResults}}
<p>{{i18n 'choose_topic.none_found'}}</p>
{{else}}
{{#each t in view.topics}}
{{#each view.topics as |t|}}
<div class='controls'>
<label class='radio'>
<input type='radio' id="choose-topic-{{unbound t.id}}" name='choose_topic_id' {{action "chooseTopic" t target="view"}}>{{t.title}}

View File

@ -22,7 +22,7 @@
<div class='cat'><a {{bind-attr href=noCategoriesUrl}} data-drop-close="true" class='badge-category home'>{{i18n 'categories.no_subcategory'}}</a></div>
{{/if}}
{{#if renderCategories}}
{{#each c in categories}}<div class='cat'>{{category-link c allowUncategorized=true hideParent=subCategory}}</div>{{/each}}
{{#each categories as |c|}}<div class='cat'>{{category-link c allowUncategorized=true hideParent=subCategory}}</div>{{/each}}
{{/if}}
</section>
{{/if}}

View File

@ -0,0 +1,11 @@
<td>{{user-info user=item.user}}</td>
<td>{{number item.likes_received}}</td>
<td>{{number item.likes_given}}</td>
<td>{{number item.topic_count}}</td>
<td>{{number item.post_count}}</td>
<td>{{number item.topics_entered}}</td>
<td>{{number item.posts_read}}</td>
<td>{{number item.days_visited}}</td>
{{#if showTimeRead}}
<td><span class='time-read'>{{unbound item.time_read}}</span></td>
{{/if}}

View File

@ -0,0 +1,27 @@
{{#if isNotifyUser}}
<h3>{{formattedName}}</h3>
<div class='controls'>
<label class='radio'><input type='radio' id="radio_{{unbound flag.name_key}}" {{action "changePostActionType" flag}} name='post_action_type_index'> <span class='description'>{{{flag.description}}}</span></label>
{{#if showMessageInput}}
{{textarea name="message" class="flag-message" placeholder=customPlaceholder value=message}}
<div {{bind-attr class=":custom-message-length customMessageLengthClasses"}}>{{customMessageLength}}</div>
{{/if}}
</div>
{{#if staffFlagsAvailable}}
<hr>
<h3>{{i18n 'flagging.notify_staff'}}</h3>
{{/if}}
{{else}}
<div class='controls'>
<label class='radio'>
<input type='radio' id="radio_{{unbound flag.name_key}}" {{action "changePostActionType" flag}} name='post_action_type_index'> <strong>{{formattedName}}</strong>
{{#if showDescription}}
<div class='description'>{{{flag.description}}}</div>
{{/if}}
</label>
{{#if showMessageInput}}
{{textarea name="message" class="flag-message" placeholder=customPlaceholder value=message}}
<div {{bind-attr class=":custom-message-length customMessageLengthClasses"}}>{{customMessageLength}}</div>
{{/if}}
</div>
{{/if}}

View File

@ -60,7 +60,7 @@
</tr>
</thead>
<tbody>
{{#each a in other_accounts}}
{{#each other_accounts as |a|}}
<tr>
<td>{{#link-to "adminUser" a}}{{avatar a usernamePath="user.username" imageSize="small"}}&nbsp;{{a.username}}{{/link-to}}</td>
<td>{{a.trustLevel.id}}</td>

View File

@ -1,3 +1,3 @@
{{#each b in buttons}}
{{#each buttons as |b|}}
<button {{bind-attr class=":btn :btn-social b.name"}} {{action "externalLogin" b}}>{{b.title}}</button>
{{/each}}

View File

@ -3,7 +3,7 @@
<div id='period-popup' {{bind-attr class="showPeriods::hidden :period-popup"}}>
<ul>
{{#each p in site.periods}}
{{#each site.periods as |p|}}
<li><a href {{action "changePeriod" p}}>{{period-title p showDateRange=true}}</a></li>
{{/each}}
</ul>

View File

@ -0,0 +1,79 @@
<div class='queued-post'>
<div class='poster'>
{{#user-link user=post.user}}
{{avatar post.user imageSize="large"}}
{{/user-link}}
</div>
<div class='cooked'>
<div class='names'>
<span class="username">
{{#user-link user=post.user}}
{{post.user.username}}
{{/user-link}}
{{#if post.user.blocked}}
<i class='fa fa-ban' title='{{i18n "user.blocked_tooltip"}}'></i>
{{/if}}
</span>
</div>
<div class='post-info'>
<span class='post-date'>{{age-with-tooltip post.created_at}}</span>
</div>
<div class='clearfix'></div>
<span class='post-title'>
{{i18n "queue.topic"}}
{{#if post.topic}}
{{topic-link post.topic}}
{{else}}
{{post.post_options.title}}
{{/if}}
{{category-badge post.category}}
</span>
<div class='body'>
{{#if editing}}
{{d-editor value=buffered.raw}}
{{else}}
{{{cook-text post.raw}}}
{{/if}}
</div>
<div class='queue-controls'>
{{#if editing}}
{{d-button action="confirmEdit"
label="queue.confirm"
disabled=post.isSaving
class="btn-primary confirm"}}
{{d-button action="cancelEdit"
label="queue.cancel"
icon="times"
disabled=post.isSaving
class="btn-danger cancel"}}
{{else}}
{{d-button action="approve"
disabled=post.isSaving
label="queue.approve"
icon="check"
class="btn-primary approve"}}
{{d-button action="reject"
disabled=post.isSaving
label="queue.reject"
icon="times"
class="btn-danger reject"}}
{{#if post.can_delete_user}}
{{d-button action="deleteUser"
disabled=post.isSaving
label="queue.delete_user"
icon="trash"
class="btn-danger delete-user"}}
{{/if}}
{{d-button action="edit"
disabled=post.isSaving
label="queue.edit"
icon="pencil"
class="edit"}}
{{/if}}
</div>
</div>
<div class='clearfix'></div>
</div>

View File

@ -10,7 +10,7 @@
<section {{bind-attr class="expanded::hidden :category-dropdown-menu :chooser"}}>
<div class='cat'><a {{bind-attr href=allTagsUrl}} data-drop-close="true" class='badge-category home'>{{allTagsLabel}}</a></div>
{{#if renderTags}}
{{#each t in tags}}
{{#each tags as |t|}}
<div class='cat'>
{{tag-drop-link tagId=t category=currentCategory}}
</div>
@ -18,4 +18,4 @@
{{/if}}
</section>
{{/if}}
{{/if}}
{{/if}}

View File

@ -1,4 +1,4 @@
{{#each p in periods}}
{{#each periods as |p|}}
{{#d-button action="changePeriod" actionParam=p}}
{{period-title p}}
{{/d-button}}

View File

@ -4,7 +4,7 @@
{{bound-category-link topic.category hideParent=true}}
{{#if siteSettings.tagging_enabled}}
<div class="list-tags">
{{#each t in topic.tags}}
{{#each topic.tags as |t|}}
{{discourse-tag t}}
{{/each}}
</div>

View File

@ -15,5 +15,17 @@
</thead>
{{/unless}}
<tbody>
{{each topic in topics itemView="topic-list-item"}}
{{#each topics as |topic|}}
{{topic-list-item topic=topic
bulkSelectEnabled=bulkSelectEnabled
showTopicPostBadges=showTopicPostBadges
hideCategory=hideCategory
showPosters=showPosters
showParticipants=showParticipants
showLikes=showLikes
showOpLikes=showOpLikes
expandGloballyPinned=expandGloballyPinned
expandAllPinned=expandAllPinned
selected=selected}}
{{/each}}
</tbody>

View File

@ -9,7 +9,7 @@
</tr>
</thead>
<tbody>
{{#each c in model.categories}}
{{#each model.categories as |c|}}
<tr data-category_id='{{unbound c.id}}' {{bind-attr class="c.description_excerpt:has-description:no-description c.logo_url:has-logo:no-logo"}}>
<td class='category' style={{border-color c.color}}>
<div>
@ -41,7 +41,7 @@
<td class='stats' {{bind-attr title="c.topicStatsTitle"}}>
<table class="categoryStats">
<tbody>
{{#each s in c.topicCountStats}}
{{#each c.topicCountStats as |s|}}
<tr>
<td class="value">{{s.value}}</td>
<td class="unit"> / {{s.unit}}</td>

View File

@ -1,6 +1,6 @@
<div class='autocomplete'>
<ul>
{{#each option in options}}
{{#each options as |option|}}
<li>
<a href>
{{#if option.src}}

View File

@ -1,12 +1,12 @@
<div class='emoji-modal'>
<ul class='toolbar'>
{{#each item in toolbarItems}}<li><a title='{{item.title}}' {{#if item.selected}}class='selected'{{/if}} data-group-id='{{item.groupId}}'><img src='{{item.src}}' class='emoji'></a></li>{{/each}}
{{#each toolbarItems as |item|}}<li><a title='{{item.title}}' {{#if item.selected}}class='selected'{{/if}} data-group-id='{{item.groupId}}'><img src='{{item.src}}' class='emoji'></a></li>{{/each}}
</ul>
<div class='emoji-table-wrapper'>
<table class='emoji-page'>
{{#each row in rows}}
{{#each rows as |row|}}
<tr>
{{#each item in row}}
{{#each row as |item|}}
<td><a title='{{item.title}}'><img src='{{item.src}}' class='emoji'></a></td>
{{/each}}
</tr>

View File

@ -1,6 +1,6 @@
<div class='autocomplete'>
<ul>
{{#each option in options}}
{{#each options as |option|}}
<li><a href>{{option.name}}</a></li>
{{/each}}
</ul>

View File

@ -1,5 +1,5 @@
<td class='posters'>
{{#each poster in posters}}
{{#each posters as |poster|}}
<a href="{{poster.user.path}}" data-user-card="{{poster.user.username}}" class="{{poster.extras}}">{{avatar poster avatarTemplatePath="user.avatar_template" usernamePath="user.username" imageSize="small"}}</a>
{{/each}}
</td>

View File

@ -1,4 +1,4 @@
{{#if controller.bulkSelectEnabled}}
{{#if bulkSelectEnabled}}
<td class='star'>
<input type="checkbox" class="bulk-select">
</td>
@ -8,12 +8,12 @@
{{raw "topic-status" topic=topic}}
{{topic-link topic}}
{{plugin-outlet "topic-list-after-title"}}
{{#if controller.showTopicPostBadges}}
{{#if showTopicPostBadges}}
{{raw "topic-post-badges" unread=topic.unread newPosts=topic.displayNewPosts unseen=topic.unseen url=topic.lastUnreadUrl}}
{{/if}}
{{#if topic.tags}}
<div class='discourse-tags'>
{{#each tag in topic.visibleListTags}}
{{#each topic.visibleListTags as |tag|}}
{{discourse-tag tag}}
{{/each}}
</div>
@ -25,23 +25,23 @@
{{raw "list/action-list" topic=topic postNumbers=topic.liked_post_numbers className="likes" icon="heart"}}
</td>
{{#unless controller.hideCategory}}
{{#unless hideCategory}}
{{#unless topic.isPinnedUncategorized}}
{{raw "list/category-column" category=topic.category}}
{{/unless}}
{{/unless}}
{{#if controller.showPosters}}
{{#if showPosters}}
{{raw "list/posters-column" posters=topic.posters}}
{{/if}}
{{raw "list/posts-count-column" topic=topic}}
{{#if controller.showParticipants}}
{{#if showParticipants}}
{{raw "list/posters-column" posters=topic.participants}}
{{/if}}
{{#if controller.showLikes}}
{{#if showLikes}}
<td class="num likes">
{{#if hasLikes}}
<a href='{{topic.summaryUrl}}'>
@ -50,7 +50,7 @@
{{/if}}
{{/if}}
{{#if controller.showOpLikes}}
{{#if showOpLikes}}
<td class="num likes">
{{#if hasOpLikes}}
<a href='{{topic.summaryUrl}}'>

View File

@ -2,7 +2,7 @@
{{#if topics}}
<table class="topic-list">
<tbody>
{{#each t in topics}}
{{#each topics as |t|}}
<tr {{bind-attr class="t.archived"}} data-topic-id={{t.id}}>
<td>
<div class='main-link'>
@ -39,7 +39,7 @@
{{/unless}}
{{#if controller.showParticipants}}
<div class='participants'>
{{#each p in participants}}
{{#each participants as |p|}}
<a href="{{unbound p.user.path}}" class="{{unbound p.extras}}">{{avatar p usernamePath="user.username" imageSize="small"}}</a>
{{/each}}
</div>

View File

@ -0,0 +1,11 @@
{{user-info user=item.user}}
{{user-stat value=item.likes_received label="directory.likes_received" icon="heart"}}
{{user-stat value=item.likes_given label="directory.likes_given" icon="heart"}}
{{user-stat value=item.topic_count label="directory.topic_count"}}
{{user-stat value=item.post_count label="directory.post_count"}}
{{user-stat value=item.topics_entered label="directory.topics_entered"}}
{{user-stat value=item.posts_read label="directory.posts_read"}}
{{user-stat value=item.days_visited label="directory.days_visited"}}
{{#if showTimeRead}}
<div class='time-read'>{{unbound item.time_read}}</div>
{{/if}}

View File

@ -15,7 +15,7 @@
</tr>
{{/if}}
{{#each t in c.topics}}
{{#each c.topics as |t|}}
<tr {{bind-attr class="t.archived :category-topic-link"}}>
<td class='main-link'>
<div class='topic-inset'>
@ -46,7 +46,7 @@
<tr class="subcategories-list">
<td>
<div class='subcategories'>
{{#each subcategory in c.subcategories}}
{{#each c.subcategories as |subcategory|}}
{{category-link subcategory}}
{{/each}}
</div>

View File

@ -1,35 +1,35 @@
<td>
{{~#unless content.hasExcerpt}}
{{~#unless topic.hasExcerpt}}
<div class='pull-left'>
<a href="{{content.lastPostUrl}}">{{avatar content.lastPoster imageSize="large"}}</a>
<a href="{{topic.lastPostUrl}}">{{avatar topic.lastPoster imageSize="large"}}</a>
</div>
<div class='right'>
{{else}}
<div>
{{/unless~}}
<div class='main-link'>
{{raw "topic-status" topic=content}}
{{topic-link content}}
{{#if content.unseen}}
{{raw "topic-status" topic=topic}}
{{topic-link topic}}
{{#if topic.unseen}}
<span class="badge-notification new-topic"></span>
{{/if}}
{{raw "list/topic-excerpt" topic=content}}
{{raw "list/topic-excerpt" topic=topic}}
</div>
<div class='pull-right'>
{{raw "list/post-count-or-badges" topic=content postBadgesEnabled=controller.showTopicPostBadges}}
{{raw "list/post-count-or-badges" topic=topic postBadgesEnabled=controller.showTopicPostBadges}}
</div>
<div class="topic-item-stats clearfix">
{{#unless controller.hideCategory}}
<div class='category'>
{{category-link content.category}}
{{category-link topic.category}}
</div>
{{/unless}}
{{#if context.topic.tags}}
<div class='discourse-tags'>
{{#each tag in context.topic.visibleListTags}}
{{#each context.topic.visibleListTags as |tag|}}
{{discourse-tag tag}}
{{/each}}
</div>
@ -39,7 +39,7 @@
<div class="pull-right">
<div class='num activity last'>
<span class="age activity" title="{{content.bumpedAtTitle}}"><a href="{{content.lastPostUrl}}">{{format-date content.bumpedAt format="tiny" noTitle="true"}}</a></span>
<span class="age activity" title="{{topic.bumpedAtTitle}}"><a href="{{topic.lastPostUrl}}">{{format-date topic.bumpedAt format="tiny" noTitle="true"}}</a></span>
</div>
</div>

View File

@ -9,24 +9,9 @@
{{#conditional-loading-spinner condition=model.loading}}
{{#if model.length}}
<div class='total-rows'>{{i18n "directory.total_rows" count=model.totalRows}}</div>
{{#each ic in model itemController="directory-item"}}
<div class="user {{if ic.me 'me'}}">
{{#with ic.model as |it|}}
{{user-info user=it.user}}
{{user-stat value=it.likes_received label="directory.likes_received" icon="heart"}}
{{user-stat value=it.likes_given label="directory.likes_given" icon="heart"}}
{{user-stat value=it.topic_count label="directory.topic_count"}}
{{user-stat value=it.post_count label="directory.post_count"}}
{{user-stat value=it.topics_entered label="directory.topics_entered"}}
{{user-stat value=it.posts_read label="directory.posts_read"}}
{{user-stat value=it.days_visited label="directory.days_visited"}}
{{#if showTimeRead}}
<div class='time-read'>{{unbound it.time_read}}</div>
{{/if}}
{{/with}}
</div>
{{/each}}
{{#each model as |item|}}
{{directory-item tagName="div" class="user" item=item showTimeRead=showTimeRead}}
{{/each}}
{{conditional-loading-spinner condition=model.loadingMore}}
{{else}}

View File

@ -1,4 +1,4 @@
{{#each level in notificationLevels}}
{{#each notificationLevels as |level|}}
<div class='controls'>
<label class='radio'>
{{radio-button value=level.id name="notification_level" selection=controller.notificationLevelId}} <strong>{{unbound level.name}}</strong>

View File

@ -81,7 +81,7 @@
{{#if userFields}}
<div class='user-fields'>
{{#each f in userFields}}
{{#each userFields as |f|}}
{{user-field field=f.field value=f.value}}
{{/each}}
</div>

View File

@ -1,34 +1,13 @@
<div class="modal-body flag-modal">
<form>
{{#each f in flagsAvailable itemController="flag-action-type"}}
{{#if f.isNotifyUser}}
<h3>{{f.formattedName}}</h3>
<div class='controls'>
<label class='radio'><input type='radio' id="radio_{{unbound f.model.name_key}}" {{action "changePostActionType" f}} name='post_action_type_index'> <span class='description'>{{{f.model.description}}}</span></label>
{{#if f.showMessageInput}}
{{textarea name="message" class="flag-message" placeholder=f.customPlaceholder value=f.message}}
<div {{bind-attr class=":custom-message-length f.customMessageLengthClasses"}}>{{f.customMessageLength}}</div>
{{/if}}
</div>
{{#if staffFlagsAvailable}}
<hr>
<h3>{{i18n 'flagging.notify_staff'}}</h3>
{{/if}}
{{else}}
<div class='controls'>
<label class='radio'>
<input type='radio' id="radio_{{unbound f.model.name_key}}" {{action "changePostActionType" f}} name='post_action_type_index'> <strong>{{f.formattedName}}</strong>
{{#if f.showDescription}}
<div class='description'>{{{f.model.description}}}</div>
{{/if}}
</label>
{{#if f.showMessageInput}}
{{textarea name="message" class="flag-message" placeholder=f.customPlaceholder value=f.message}}
<div {{bind-attr class=":custom-message-length f.customMessageLengthClasses"}}>{{f.customMessageLength}}</div>
{{/if}}
</div>
{{/if}}
{{#each flagsAvailable as |f|}}
{{flag-action-type flag=f
message=message
isWarning=isWarning
selectedFlag=selected
username=model.username
changePostActionType="changePostActionType"}}
{{else}}
{{i18n 'flagging.cant'}}
{{/each}}

View File

@ -8,7 +8,7 @@
</div>
<div id='modal-alert'></div>
{{outlet "modalBody"}}
{{#each error in errors}}
{{#each errors as |error|}}
<div class="alert alert-error">
<button class="close" data-dismiss="alert">×</button>
{{error}}

View File

@ -1,85 +1,7 @@
<div class='container'>
<div class='queued-posts'>
{{#each ctrl in model itemController='queued-post'}}
<div class='queued-post'>
<div class='poster'>
{{#user-link user=ctrl.post.user}}
{{avatar ctrl.post.user imageSize="large"}}
{{/user-link}}
</div>
<div class='cooked'>
<div class='names'>
<span class="username">
{{#user-link user=ctrl.post.user}}
{{ctrl.post.user.username}}
{{/user-link}}
{{#if ctrl.post.user.blocked}}
<i class='fa fa-ban' title='{{i18n "user.blocked_tooltip"}}'></i>
{{/if}}
</span>
</div>
<div class='post-info'>
<span class='post-date'>{{age-with-tooltip ctrl.post.created_at}}</span>
</div>
<div class='clearfix'></div>
<span class='post-title'>
{{i18n "queue.topic"}}
{{#if ctrl.post.topic}}
{{topic-link ctrl.post.topic}}
{{else}}
{{ctrl.post.post_options.title}}
{{/if}}
{{category-badge ctrl.post.category}}
</span>
<div class='body'>
{{#if ctrl.editing}}
{{d-editor value=ctrl.buffered.raw}}
{{else}}
{{{cook-text ctrl.post.raw}}}
{{/if}}
</div>
<div class='queue-controls'>
{{#if ctrl.editing}}
{{d-button action="confirmEdit"
label="queue.confirm"
disabled=ctrl.post.isSaving
class="btn-primary confirm"}}
{{d-button action="cancelEdit"
label="queue.cancel"
icon="times"
disabled=ctrl.post.isSaving
class="btn-danger cancel"}}
{{else}}
{{d-button action="approve"
disabled=ctrl.post.isSaving
label="queue.approve"
icon="check"
class="btn-primary approve"}}
{{d-button action="reject"
disabled=ctrl.post.isSaving
label="queue.reject"
icon="times"
class="btn-danger reject"}}
{{#if ctrl.post.can_delete_user}}
{{d-button action="deleteUser"
disabled=ctrl.post.isSaving
label="queue.delete_user"
icon="trash"
class="btn-danger delete-user"}}
{{/if}}
{{d-button action="edit"
disabled=ctrl.post.isSaving
label="queue.edit"
icon="pencil"
class="edit"}}
{{/if}}
</div>
</div>
<div class='clearfix'></div>
</div>
{{#each model as |post|}}
{{queued-post post=post currentlyEditing=editing removePost="removePost"}}
{{else}}
<p>{{i18n "queue.none"}}</p>
{{/each}}

View File

@ -10,7 +10,7 @@
<div class="share-for-touch"><div class="overflow-ellipsis"><a></a></div></div>
</div>
{{#each s in sources}}
{{#each sources as |s|}}
{{share-source source=s title=view.title action="share"}}
{{/each}}

View File

@ -11,7 +11,7 @@
</div>
<div class='tag-list'>
{{#each tag in sortedTags}}
{{#each sortedTags as |tag|}}
<div class='tag-box'>
{{discourse-tag tag.id}} <span class='tag-count'>x {{tag.count}}</span>
</div>

View File

@ -1,7 +1,7 @@
{{~#if view.renderDiv ~}}
<div class='topic-statuses'>
{{/if ~}}
{{~#each status in view.statuses ~}}
{{~#each view.statuses as |status|~}}
{{~#if status.href ~}}
<a href='{{status.href}}' title='{{status.title}}' class='topic-status {{status.extraClasses}}'><i class='fa fa-{{status.icon}}'></i></a>
{{~else ~}}

View File

@ -86,7 +86,7 @@
{{#if publicUserFields}}
<div class="public-user-fields">
{{#each uf in publicUserFields}}
{{#each publicUserFields as |uf|}}
{{#if uf.value}}
<div class="public-user-field {{uf.field.dasherized_name}}">
<span class="user-field-name">{{uf.field.name}}:</span>
@ -99,7 +99,7 @@
{{#if showBadges}}
<div class="badge-section">
{{#each ub in user.featured_user_badges}}
{{#each user.featured_user_badges as |ub|}}
{{user-badge badge=ub.badge user=user}}
{{/each}}
{{#if showMoreBadges}}

View File

@ -45,7 +45,7 @@
<th colspan="6">{{i18n 'user.invited.sent'}}</th>
{{/if}}
</tr>
{{#each invite in model.invites}}
{{#each model.invites as |invite|}}
<tr>
{{#if invite.user}}
<td>

View File

@ -1,6 +1,6 @@
<div class='autocomplete'>
<ul>
{{#each user in options.users}}
{{#each options.users as |user|}}
<li>
<a href>
{{avatar user imageSize="tiny"}}
@ -10,7 +10,7 @@
</li>
{{/each}}
{{#if options.groups}}
{{#each group in options.groups}}
{{#each options.groups as |group|}}
<li>
<a href>
<i class='fa fa-users'></i>

View File

@ -1,5 +1,5 @@
{{#user-stream stream=model}}
{{#each p in model.content}}
{{#each model.content as |p|}}
<div {{bind-attr class=":item p.hidden p.deleted p.moderator_action"}}>
<div class="clearfix info">
<a href="{{unbound p.usernameUrl}}" class="avatar-link">

View File

@ -137,7 +137,7 @@
</div>
</div>
{{#each uf in userFields}}
{{#each userFields as |uf|}}
{{user-field field=uf.field value=uf.value}}
{{/each}}
<div class='clearfix'></div>

View File

@ -8,7 +8,9 @@
</li>
<li>{{user-stat value=model.posts_read_count label="user.summary.posts_read"}}</li>
<li>{{user-stat value=model.likes_given label="user.summary.likes_given"}}</li>
<li>{{user-stat value=model.bookmark_count label="user.summary.bookmark_count"}}</li>
{{#if model.bookmark_count}}
<li>{{user-stat value=model.bookmark_count label="user.summary.bookmark_count"}}</li>
{{/if}}
<li>{{user-stat value=model.topic_count label="user.summary.topic_count"}}</li>
<li>{{user-stat value=model.post_count label="user.summary.post_count"}}</li>
<li>{{user-stat value=model.likes_received label="user.summary.likes_received"}}</li>
@ -20,7 +22,7 @@
<h3 class='stats-title'>{{i18n "user.summary.top_replies"}}</h3>
{{#if model.replies.length}}
<ul>
{{#each reply in model.replies}}
{{#each model.replies as |reply|}}
<li>
<span class='topic-info'>
{{format-date reply.createdAt format="tiny" noTitle="true"}}
@ -45,7 +47,7 @@
<h3 class='stats-title'>{{i18n "user.summary.top_topics"}}</h3>
{{#if model.topics.length}}
<ul>
{{#each topic in model.topics}}
{{#each model.topics as |topic|}}
<li>
<span class='topic-info'>
{{format-date topic.createdAt format="tiny" noTitle="true"}}
@ -73,7 +75,7 @@
<h3 class='stats-title'>{{i18n "user.summary.top_links"}}</h3>
{{#if model.links.length}}
<ul>
{{#each link in model.links}}
{{#each model.links as |link|}}
<li>
<a class='domain'
href='{{unbound link.url}}'
@ -93,14 +95,51 @@
{{/if}}
</div>
<div class='top-sub-section likes-section pull-right'>
<h3 class='stats-title'>{{i18n "user.summary.most_replied_to_users"}}</h3>
{{#if model.most_replied_to_users.length}}
<ul>
{{#each user in model.most_replied_to_users}}
<li>
{{#user-info user=user}}
{{fa-icon "reply"}}
<span class='replies'>{{user.count}}</span>
{{/user-info}}
</li>
{{/each}}
</ul>
{{else}}
<p>{{i18n "user.summary.no_likes"}}</p>
{{/if}}
</div>
</div>
<div class='top-section'>
<div class='top-sub-section likes-section pull-left'>
<h3 class='stats-title'>{{i18n "user.summary.most_liked_by"}}</h3>
{{#if model.most_liked_by_users.length}}
<ul>
{{#each user in model.most_liked_by_users}}
{{#each model.most_liked_by_users as |user|}}
<li>
{{#user-info user=user}}
{{fa-icon "heart"}}
<span class='likes'>{{user.likes}}</span>
<span class='likes'>{{user.count}}</span>
{{/user-info}}
</li>
{{/each}}
</ul>
{{else}}
<p>{{i18n "user.summary.no_likes"}}</p>
{{/if}}
</div>
<div class='top-sub-section likes-section pull-right'>
<h3 class='stats-title'>{{i18n "user.summary.most_liked_users"}}</h3>
{{#if model.most_liked_users.length}}
<ul>
{{#each user in model.most_liked_users}}
<li>
{{#user-info user=user}}
{{fa-icon "heart"}}
<span class='likes'>{{user.count}}</span>
{{/user-info}}
</li>
{{/each}}
@ -113,7 +152,7 @@
<div class='top-section badges-section'>
<h3 class='stats-title'>{{i18n "user.summary.top_badges"}}</h3>
{{#each badge in model.badges}}
{{#each model.badges as |badge|}}
{{badge-card badge=badge count=badge.count navigateOnClick="true" username=user.username_lower}}
{{else}}
<p>{{i18n "user.summary.no_badges"}}</p>

View File

@ -88,7 +88,7 @@
{{#if publicUserFields}}
<div class="public-user-fields">
{{#each uf in publicUserFields}}
{{#each publicUserFields as |uf|}}
{{#if uf.value}}
<div class="public-user-field {{uf.field.dasherized_name}}">
<span class="user-field-name">{{uf.field.name}}</span>:
@ -139,7 +139,7 @@
{{#if model.displayGroups}}
<dt>{{i18n 'groups.title' count=model.displayGroups.length}}</dt>
<dd class='groups'>
{{#each group in model.displayGroups}}
{{#each model.displayGroups as |group|}}
<span>{{#link-to 'group' group class="group-link"}}{{group.name}}{{/link-to}}</span>
{{/each}}
</dd>

View File

@ -26,22 +26,8 @@
{{/if}}
</thead>
<tbody>
{{#each ic in model itemController="directory-item"}}
<tr class="{{if ic.me 'me'}}">
{{#with ic.model as |it|}}
<td>{{user-info user=it.user}}</td>
<td>{{number it.likes_received}}</td>
<td>{{number it.likes_given}}</td>
<td>{{number it.topic_count}}</td>
<td>{{number it.post_count}}</td>
<td>{{number it.topics_entered}}</td>
<td>{{number it.posts_read}}</td>
<td>{{number it.days_visited}}</td>
{{#if controller.parentController.showTimeRead}}
<td><span class='time-read'>{{unbound it.time_read}}</span></td>
{{/if}}
{{/with}}
</tr>
{{#each model as |item|}}
{{directory-item item=item showTimeRead=showTimeRead}}
{{/each}}
</tbody>
</table>

View File

@ -27,7 +27,7 @@ export default ContainerView.extend({
}
}
if (this.get('topic.details.can_invite_to')) {
if (!mobileView && this.get('topic.details.can_invite_to')) {
this.attachViewClass('invite-reply-button');
}

View File

@ -229,11 +229,6 @@ export default createWidget('header', {
togglePageSearch() {
const { state } = this;
if (state.searchVisible) {
this.toggleSearchMenu();
return false;
}
state.contextEnabled = false;
const currentPath = this.container.lookup('controller:application').get('currentPath');
@ -248,6 +243,11 @@ export default createWidget('header', {
this.container.lookup('controller:topic').get('model.postStream.stream.length'));
}
if (state.searchVisible) {
this.toggleSearchMenu();
return showSearch;
}
if (showSearch) {
state.contextEnabled = true;
this.toggleSearchMenu();

View File

@ -48,7 +48,13 @@ export default createWidget('home-logo', {
click(e) {
if (wantsNewWindow(e)) { return false; }
e.preventDefault();
const a = $(e.target).closest('a')[0];
if (a && a.host !== document.location.host) {
document.location = a.href;
}
DiscourseURL.routeTo(this.href());
return false;
}

View File

@ -393,11 +393,13 @@ td.flaggers td {
.setting-value {
float: left;
width: 53%;
padding-right: 20px;
.category-group {
width: 95%;
}
@media (max-width: $mobile-breakpoint) {
width: 100%;
padding-right: 0;
}
.select2-container {
width: 100% !important; // Needs !important to override hard-coded value
@ -413,14 +415,14 @@ td.flaggers td {
float: left;
}
.input-setting-string {
width: 404px;
@include medium-width { width: 314px; }
box-sizing: border-box;
height: 30px;
width: 100%;
@media (max-width: $mobile-breakpoint) {
width: 100%;
}
}
.input-setting-list {
width: 408px;
@media (max-width: $mobile-breakpoint) {
width: 100%;
}

View File

@ -23,7 +23,8 @@ class Admin::UsersController < Admin::AdminController
:primary_group,
:generate_api_key,
:revoke_api_key,
:anonymize]
:anonymize,
:reset_bounce_score]
def index
users = ::AdminUserIndexQuery.new(params).find_users
@ -355,6 +356,12 @@ class Admin::UsersController < Admin::AdminController
end
end
def reset_bounce_score
guardian.ensure_can_reset_bounce_score!(@user)
@user.user_stat.update_columns(bounce_score: 0, reset_bounce_score_after: nil)
render json: success_json
end
private
def fetch_user

View File

@ -471,6 +471,7 @@ class UsersController < ApplicationController
end
def account_created
@custom_body_class = "static-account-created"
@message = session['user_created_message'] || I18n.t('activation.missing_session')
expires_now
render layout: 'no_ember'

View File

@ -173,10 +173,9 @@ module Jobs
if exceptions.length > 0
exceptions.each do |exception_hash|
Discourse.handle_job_exception(exception_hash[:ex],
error_context(opts, exception_hash[:code], exception_hash[:other]))
Discourse.handle_job_exception(exception_hash[:ex], error_context(opts, exception_hash[:code], exception_hash[:other]))
end
raise HandledExceptionWrapper.new exceptions[0][:ex]
raise HandledExceptionWrapper.new(exceptions[0][:ex])
end
nil

View File

@ -2,7 +2,7 @@ module Jobs
class MigrateUploadScheme < Jobs::Onceoff
def execute(args)
def execute_onceoff(args)
return unless SiteSetting.migrate_to_new_scheme
# clean up failed uploads

View File

@ -48,7 +48,24 @@ module Jobs
@skip_context = { type: type, user_id: user_id, to_address: to_address, post_id: post_id }
end
NOTIFICATIONS_SENT_BY_MAILING_LIST ||= Set.new %w{posted replied mentioned group_mentioned quoted}
NOTIFICATIONS_SENT_BY_MAILING_LIST ||= Set.new %w{
posted
replied
mentioned
group_mentioned
quoted
}
CRITICAL_EMAIL_TYPES = Set.new %i{
account_created
admin_login
confirm_new_email
confirm_old_email
forgot_password
notify_old_email
signup
signup_after_approval
}
def message_for_email(user, post, type, notification,
notification_type=nil, notification_data_hash=nil,
@ -109,7 +126,7 @@ module Jobs
email_args[:email_token] = email_token
end
if type == 'notify_old_email'
if type == :notify_old_email
email_args[:new_email] = user.email
end
@ -117,7 +134,7 @@ module Jobs
return skip_message(I18n.t('email_log.exceeded_emails_limit'))
end
if (user.user_stat.try(:bounce_score) || 0) >= SiteSetting.bounce_score_threshold
if !CRITICAL_EMAIL_TYPES.include?(type) && user.user_stat.bounce_score >= SiteSetting.bounce_score_threshold
return skip_message(I18n.t('email_log.exceeded_bounces_limit'))
end
@ -133,6 +150,22 @@ module Jobs
[message, nil]
end
sidekiq_retry_in do |count, exception|
# retry in an hour when SMTP server is busy
# or use default sidekiq retry formula
case exception.wrapped
when Net::SMTPServerBusy
1.hour + (rand(30) * (count + 1))
else
Jobs::UserEmail.seconds_to_delay(count)
end
end
# extracted from sidekiq
def self.seconds_to_delay(count)
(count ** 4) + 15 + (rand(30) * (count + 1))
end
private
def skip_message(reason)

View File

@ -30,7 +30,7 @@ module Jobs
set_incoming_email_rejection_message(
receiver.incoming_email,
I18n.t("email.incoming.errors.bounced_email_report")
I18n.t("email.incoming.errors.bounced_email_error")
)
rescue Email::Receiver::AutoGeneratedEmailReplyError => e
log_email_process_failure(mail_string, e)

View File

@ -213,8 +213,8 @@ SQL
return unless opts[:message] && [:notify_moderators, :notify_user, :spam].include?(post_action_type)
title = I18n.t("post_action_types.#{post_action_type}.email_title", title: post.topic.title)
body = I18n.t("post_action_types.#{post_action_type}.email_body", message: opts[:message], link: "#{Discourse.base_url}#{post.url}")
title = I18n.t("post_action_types.#{post_action_type}.email_title", title: post.topic.title, locale: SiteSetting.default_locale)
body = I18n.t("post_action_types.#{post_action_type}.email_body", message: opts[:message], link: "#{Discourse.base_url}#{post.url}", locale: SiteSetting.default_locale)
warning = opts[:is_warning] if opts[:is_warning].present?
title = title.truncate(255, separator: /\s/)

View File

@ -66,11 +66,8 @@ class Report
ApplicationRequest.where(req_type: ApplicationRequest.req_types[filter])
end
filtered_results = data
filtered_results = data.filtered_results.where(category_id: report.category_id) if report.category_id
report.data = []
filtered_results.where('date >= ? AND date <= ?', report.start_date.to_date, report.end_date.to_date)
data.where('date >= ? AND date <= ?', report.start_date.to_date, report.end_date.to_date)
.order(date: :asc)
.group(:date)
.sum(:count)
@ -79,7 +76,7 @@ class Report
end
report.total = data.sum(:count)
report.prev30Days = filtered_results.where('date >= ? AND date <= ?',
report.prev30Days = data.where('date >= ? AND date <= ?',
(report.start_date - 31.days).to_date,
(report.end_date - 31.days).to_date )
.sum(:count)

View File

@ -46,7 +46,7 @@ class UserSummary
.limit(MAX_SUMMARY_RESULTS)
end
class LikedByUser < OpenStruct
class UserWithCount < OpenStruct
include ActiveModel::SerializerSupport
end
@ -65,20 +65,81 @@ class UserSummary
User.where(id: likers.keys)
.pluck(:id, :username, :name, :uploaded_avatar_id)
.map do |u|
LikedByUser.new(
UserWithCount.new(
id: u[0],
username: u[1],
name: u[2],
avatar_template: User.avatar_template(u[1], u[3]),
likes: likers[u[0].to_s]
count: likers[u[0].to_s]
)
end.sort_by { |u| -u[:likes] }
end.sort_by { |u| -u[:count] }
end
def most_liked_users
liked_users = {}
UserAction.joins(:target_topic, :target_post)
.where('topics.archetype <> ?', Archetype.private_message)
.where(action_type: UserAction::WAS_LIKED)
.where(acting_user_id: @user.id)
.group(:user_id)
.order('COUNT(*) DESC')
.limit(MAX_SUMMARY_RESULTS)
.pluck('user_actions.user_id, COUNT(*)')
.each { |l| liked_users[l[0].to_s] = l[1] }
User.where(id: liked_users.keys)
.pluck(:id, :username, :name, :uploaded_avatar_id)
.map do |u|
UserWithCount.new(
id: u[0],
username: u[1],
name: u[2],
avatar_template: User.avatar_template(u[1], u[3]),
count: liked_users[u[0].to_s]
)
end.sort_by { |u| -u[:count] }
end
REPLY_ACTIONS ||= [UserAction::RESPONSE, UserAction::QUOTE, UserAction::MENTION]
def most_replied_to_users
replied_users = {}
Post
.joins(:topic)
.joins('JOIN posts replies ON posts.topic_id = replies.topic_id AND posts.reply_to_post_number = replies.post_number')
.includes(:topic)
.secured(@guardian)
.merge(Topic.listable_topics.visible.secured(@guardian))
.where(user: @user)
.where('replies.user_id <> ?', @user.id)
.group('replies.user_id')
.order('COUNT(*) DESC')
.limit(MAX_SUMMARY_RESULTS)
.pluck('replies.user_id, COUNT(*)')
.each { |r| replied_users[r[0].to_s] = r[1] }
User.where(id: replied_users.keys)
.pluck(:id, :username, :name, :uploaded_avatar_id)
.map do |u|
UserWithCount.new(
id: u[0],
username: u[1],
name: u[2],
avatar_template: User.avatar_template(u[1], u[3]),
count: replied_users[u[0].to_s]
)
end.sort_by { |u| -u[:count] }
end
def badges
@user.featured_user_badges(MAX_BADGES)
end
def user_id
@user.id
end
def user_stat
@user.user_stat
end

View File

@ -20,7 +20,9 @@ class AdminDetailedUserSerializer < AdminUserSerializer
:primary_group_id,
:badge_count,
:warnings_received_count,
:user_fields
:user_fields,
:bounce_score,
:reset_bounce_score_after
has_one :approved_by, serializer: BasicUserSerializer, embed: :objects
has_one :api_key, serializer: ApiKeySerializer, embed: :objects
@ -76,4 +78,12 @@ class AdminDetailedUserSerializer < AdminUserSerializer
object.user_fields.present?
end
def bounce_score
object.user_stat.bounce_score
end
def reset_bounce_score_after
object.user_stat.reset_bounce_score_after
end
end

View File

@ -18,14 +18,16 @@ class UserSummarySerializer < ApplicationSerializer
end
end
class MostLikedByUserSerializer < BasicUserSerializer
attributes :likes, :name
class UserWithCountSerializer < BasicUserSerializer
attributes :count, :name
end
has_many :topics, serializer: TopicSerializer
has_many :replies, serializer: ReplySerializer, embed: :object
has_many :links, serializer: LinkSerializer, embed: :object
has_many :most_liked_by_users, serializer: MostLikedByUserSerializer, embed: :object
has_many :most_liked_by_users, serializer: UserWithCountSerializer, embed: :object
has_many :most_liked_users, serializer: UserWithCountSerializer, embed: :object
has_many :most_replied_to_users, serializer: UserWithCountSerializer, embed: :object
has_many :badges, serializer: UserBadgeSerializer, embed: :object
attributes :likes_given,
@ -41,6 +43,10 @@ class UserSummarySerializer < ApplicationSerializer
SiteSetting.enable_badges
end
def include_bookmark_count?
scope.authenticated? && object.user_id == scope.user.id
end
def time_read
AgeWords.age_words(object.time_read)
end

Some files were not shown because too many files have changed in this diff Show More