mirror of
https://github.com/discourse/discourse.git
synced 2026-08-26 21:27:16 -05:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -24,6 +24,8 @@ Discourse.AdminUserIndexController = Discourse.ObjectController.extend({
|
||||
return Discourse.SiteSettings.must_approve_users;
|
||||
}.property(),
|
||||
|
||||
primaryGroupDirty: Discourse.computed.propertyNotEqual('originalPrimaryGroupId', 'primary_group_id'),
|
||||
|
||||
actions: {
|
||||
toggleTitleEdit: function() {
|
||||
this.toggleProperty('editingTitle');
|
||||
@@ -44,6 +46,22 @@ Discourse.AdminUserIndexController = Discourse.ObjectController.extend({
|
||||
this.get('model').generateApiKey();
|
||||
},
|
||||
|
||||
savePrimaryGroup: function() {
|
||||
var self = this;
|
||||
Discourse.ajax("/admin/users/" + this.get('id') + "/primary_group", {
|
||||
type: 'PUT',
|
||||
data: {primary_group_id: this.get('primary_group_id')}
|
||||
}).then(function () {
|
||||
self.set('originalPrimaryGroupId', self.get('primary_group_id'));
|
||||
}).catch(function() {
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
});
|
||||
},
|
||||
|
||||
resetPrimaryGroup: function() {
|
||||
this.set('primary_group_id', this.get('originalPrimaryGroupId'));
|
||||
},
|
||||
|
||||
regenerateApiKey: function() {
|
||||
var self = this;
|
||||
bootbox.confirm(I18n.t("admin.api.confirm_regen"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
|
||||
|
||||
@@ -19,7 +19,7 @@ Discourse.AdminDashboard.reopenClass({
|
||||
@return {jqXHR} a jQuery Promise object
|
||||
**/
|
||||
find: function() {
|
||||
return Discourse.ajax("/admin/dashboard").then(function(json) {
|
||||
return Discourse.ajax("/admin/dashboard.json").then(function(json) {
|
||||
var model = Discourse.AdminDashboard.create(json);
|
||||
model.set('loaded', true);
|
||||
return model;
|
||||
@@ -34,7 +34,7 @@ Discourse.AdminDashboard.reopenClass({
|
||||
@return {jqXHR} a jQuery Promise object
|
||||
**/
|
||||
fetchProblems: function() {
|
||||
return Discourse.ajax("/admin/dashboard/problems", {
|
||||
return Discourse.ajax("/admin/dashboard/problems.json", {
|
||||
type: 'GET',
|
||||
dataType: 'json'
|
||||
}).then(function(json) {
|
||||
|
||||
@@ -388,7 +388,7 @@ Discourse.AdminUser.reopenClass({
|
||||
},
|
||||
|
||||
find: function(username) {
|
||||
return Discourse.ajax("/admin/users/" + username).then(function (result) {
|
||||
return Discourse.ajax("/admin/users/" + username + ".json").then(function (result) {
|
||||
result.loadedDetails = true;
|
||||
return Discourse.AdminUser.create(result);
|
||||
});
|
||||
|
||||
@@ -23,10 +23,16 @@ Discourse.AdminUserRoute = Discourse.Route.extend({
|
||||
afterModel: function(adminUser) {
|
||||
var controller = this.controllerFor('adminUser');
|
||||
|
||||
adminUser.loadDetails().then(function () {
|
||||
return adminUser.loadDetails().then(function () {
|
||||
adminUser.setOriginalTrustLevel();
|
||||
controller.set('model', adminUser);
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
},
|
||||
|
||||
setupController: function(controller, model) {
|
||||
controller.setProperties({
|
||||
originalPrimaryGroupId: model.get('primary_group_id'),
|
||||
model: model
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
@@ -46,6 +46,25 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n admin.groups.primary}}</div>
|
||||
<div class='value'>
|
||||
{{#if custom_groups}}
|
||||
{{combobox content=custom_groups value=primary_group_id nameProperty="name" none="admin.groups.no_primary"}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if primaryGroupDirty}}
|
||||
<div>
|
||||
<button class='btn ok' {{action savePrimaryGroup}}><i class='fa fa-check'></i></button>
|
||||
<button class='btn cancel' {{action resetPrimaryGroup}}><i class='fa fa-times'></i></button>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n user.ip_address.title}}</div>
|
||||
<div class='value'>{{ip_address}}</div>
|
||||
@@ -317,7 +336,7 @@
|
||||
|
||||
<section>
|
||||
<hr/>
|
||||
<button {{bind-attr class=":btn :btn-danger :pull-right deleteForbidden:hidden"}} {{action destroy target="content"}} {{bind-attr disabled="deleteForbidden"}} {{bind-attr}}>
|
||||
<button {{bind-attr class=":btn :btn-danger :pull-right deleteForbidden:hidden"}} {{action destroy target="content"}} {{bind-attr disabled="deleteForbidden"}}>
|
||||
<i class="fa fa-exclamation-triangle"></i>
|
||||
{{i18n admin.user.delete}}
|
||||
</button>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
/**
|
||||
The view class for an Admin User
|
||||
|
||||
@class AdminUserView
|
||||
@extends Discourse.View
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.AdminUserView = Discourse.View.extend(Discourse.ScrollTop);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
Displays a list of groups that a user belongs to.
|
||||
|
||||
@class Discourse.GroupsListComponent
|
||||
@extends Ember.Component
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.GroupsListComponent = Em.Component.extend({
|
||||
classNames: ['groups']
|
||||
});
|
||||
|
||||
@@ -25,7 +25,10 @@ Discourse.PostGapComponent = Ember.Component.extend({
|
||||
if (this.get('loading')) {
|
||||
buffer.push(I18n.t('loading'));
|
||||
} else {
|
||||
buffer.push(I18n.t('post.gap', {count: this.get('gap.length')}));
|
||||
var gapLength = this.get('gap.length');
|
||||
if (gapLength) {
|
||||
buffer.push(I18n.t('post.gap', {count: gapLength}));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ Discourse.StaticController = Discourse.Controller.extend({
|
||||
text = text.match(/<!-- preload-content: -->((?:.|[\n\r])*)<!-- :preload-content -->/)[1];
|
||||
this.set('content', text);
|
||||
} else {
|
||||
return Discourse.ajax(path, {dataType: 'html'}).then(function (result) {
|
||||
return Discourse.ajax(path + ".html", {dataType: 'html'}).then(function (result) {
|
||||
self.set('content', result);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,6 +37,11 @@ Discourse.ScreenTrack = Ember.Object.extend({
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
if(!this.get('topicId')) {
|
||||
// already stopped no need to "extra stop"
|
||||
return;
|
||||
}
|
||||
|
||||
this.tick();
|
||||
this.flush();
|
||||
this.reset();
|
||||
@@ -105,9 +110,10 @@ Discourse.ScreenTrack = Ember.Object.extend({
|
||||
var highestSeenByTopic = Discourse.Session.currentProp('highestSeenByTopic');
|
||||
if ((highestSeenByTopic[topicId] || 0) < highestSeen) {
|
||||
highestSeenByTopic[topicId] = highestSeen;
|
||||
Discourse.TopicTrackingState.current().updateSeen(topicId, highestSeen);
|
||||
}
|
||||
|
||||
Discourse.TopicTrackingState.current().updateSeen(topicId, highestSeen);
|
||||
|
||||
if (!$.isEmptyObject(newTimings)) {
|
||||
Discourse.ajax('/topics/timings', {
|
||||
data: {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
This mixin will cause a view to scroll the viewport to the top once it has been inserted
|
||||
|
||||
@class Discourse.ScrollTop
|
||||
@extends Ember.Mixin
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.ScrollTop = Em.Mixin.create({
|
||||
|
||||
_scrollTop: function() {
|
||||
Em.run.schedule('afterRender', function() {
|
||||
$(document).scrollTop(0);
|
||||
});
|
||||
}.on('didInsertElement'),
|
||||
|
||||
});
|
||||
|
||||
@@ -39,8 +39,9 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
},
|
||||
|
||||
updateSeen: function(topicId, highestSeen) {
|
||||
if(!topicId || !highestSeen) { return; }
|
||||
var state = this.states["t" + topicId];
|
||||
if(state && state.last_read_post_number < highestSeen) {
|
||||
if(state && (!state.last_read_post_number || state.last_read_post_number < highestSeen)) {
|
||||
state.last_read_post_number = highestSeen;
|
||||
this.incrementMessageCount();
|
||||
}
|
||||
@@ -84,9 +85,24 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
|
||||
sync: function(list, filter){
|
||||
var tracker = this;
|
||||
var states = this.states;
|
||||
|
||||
if(!list || !list.topics) { return; }
|
||||
|
||||
// compensate for delayed "new" topics
|
||||
// client side we know they are not new, server side we think they are
|
||||
for(var i=list.topics.length-1; i>=0; i--){
|
||||
var state = states["t"+ list.topics[i].id];
|
||||
if(state && state.last_read_post_number > 0){
|
||||
if(filter === "new"){
|
||||
list.topics.splice(i, 1);
|
||||
} else {
|
||||
list.topics[i].unseen = false;
|
||||
list.topics[i].dont_sync = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(filter === "new" && !list.more_topics_url){
|
||||
// scrub all new rows and reload from list
|
||||
_.each(this.states, function(state){
|
||||
@@ -112,10 +128,11 @@ Discourse.TopicTrackingState = Discourse.Model.extend({
|
||||
if(topic.unseen) {
|
||||
row.last_read_post_number = null;
|
||||
} else if (topic.unread || topic.new_posts){
|
||||
// subtle issue here
|
||||
row.last_read_post_number = topic.highest_post_number - ((topic.unread||0) + (topic.new_posts||0));
|
||||
} else {
|
||||
delete tracker.states["t" + topic.id];
|
||||
if(!topic.dont_sync) {
|
||||
delete tracker.states["t" + topic.id];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ function buildTopicRoute(filter) {
|
||||
},
|
||||
|
||||
model: function() {
|
||||
// attempt to stop early cause we need this to be called before .sync
|
||||
Discourse.ScreenTrack.current().stop();
|
||||
|
||||
return Discourse.TopicList.list(filter).then(function(list) {
|
||||
var tracking = Discourse.TopicTrackingState.current();
|
||||
if (tracking) {
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{{#if groups}}
|
||||
{{i18n groups.title count=groups.length}}:
|
||||
{{#each groups}}
|
||||
{{#link-to 'group' this class="group-link"}}{{name}}{{/link-to}}
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
@@ -29,7 +29,7 @@
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<label>{{i18n category.parent}}</label>
|
||||
{{categoryChooser valueAttribute="id" value=parent_category_id categories=parentCategories}}
|
||||
{{categoryChooser valueAttribute="id" value=parent_category_id categories=parentCategories rootNone=true}}
|
||||
{{/if}}
|
||||
</section>
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
{{/if}}
|
||||
|
||||
{{#if user_title}}<div class="user-title" {{action showPosterExpansion this}}>{{user_title}}</div>{{/if}}
|
||||
{{#if primary_group_name}}<div><a href='/groups/{{unbound primary_group_name}}' class='user-group'>{{unbound primary_group_name}}</a></div>{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="contents">
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
<h3>{{i18n last_post}} {{unboundDate path="user.last_posted_at" leaveAgo="true"}}</h3>
|
||||
<h3>{{i18n joined}} {{unboundDate path="user.created_at" leaveAgo="true"}}</h3>
|
||||
|
||||
{{groups-list groups=user.custom_groups}}
|
||||
|
||||
<div class='bottom'>
|
||||
{{#if user.bio_cooked}}<div class='bio'>{{{user.bio_cooked}}}</div>{{/if}}
|
||||
|
||||
|
||||
@@ -43,14 +43,7 @@
|
||||
|
||||
<div class='bio'>{{{bio_cooked}}}</div>
|
||||
|
||||
{{#if custom_groups}}
|
||||
<div class='groups'>
|
||||
{{i18n groups.title count=custom_groups.length}}:
|
||||
{{#each custom_groups}}
|
||||
{{#link-to 'group' this class="group-link"}}{{name}}{{/link-to}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{groups-list groups=custom_groups}}
|
||||
|
||||
{{#if isSuspended}}
|
||||
<div class='suspended'>
|
||||
|
||||
@@ -26,7 +26,11 @@ Discourse.CategoryChooserView = Discourse.ComboboxView.extend({
|
||||
|
||||
none: function() {
|
||||
if (Discourse.User.currentProp('staff') || Discourse.SiteSettings.allow_uncategorized_topics) {
|
||||
return 'category.none';
|
||||
if (this.get('rootNone')) {
|
||||
return "category.none";
|
||||
} else {
|
||||
return Discourse.Category.list().findBy('id', Discourse.Site.currentProp('uncategorized_category_id'));
|
||||
}
|
||||
} else {
|
||||
return 'category.choose';
|
||||
}
|
||||
|
||||
@@ -11,41 +11,45 @@ Discourse.ComboboxView = Discourse.View.extend({
|
||||
classNames: ['combobox'],
|
||||
valueAttribute: 'id',
|
||||
|
||||
render: function(buffer) {
|
||||
buildData: function(o) {
|
||||
var data = "";
|
||||
if (this.dataAttributes) {
|
||||
this.dataAttributes.forEach(function(a) {
|
||||
data += "data-" + a + "=\"" + o.get(a) + "\" ";
|
||||
});
|
||||
}
|
||||
return data;
|
||||
},
|
||||
|
||||
var nameProperty = this.get('nameProperty') || 'name';
|
||||
render: function(buffer) {
|
||||
var nameProperty = this.get('nameProperty') || 'name',
|
||||
none = this.get('none');
|
||||
|
||||
// Add none option if required
|
||||
if (this.get('none')) {
|
||||
buffer.push('<option value="">' + (I18n.t(this.get('none'))) + "</option>");
|
||||
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>");
|
||||
}
|
||||
|
||||
var selected = this.get('value');
|
||||
if (selected) { selected = selected.toString(); }
|
||||
|
||||
if (this.get('content')) {
|
||||
|
||||
var comboboxView = this;
|
||||
_.each(this.get('content'),function(o) {
|
||||
var val = o[comboboxView.get('valueAttribute')];
|
||||
var self = this;
|
||||
this.get('content').forEach(function(o) {
|
||||
var val = o[self.get('valueAttribute')];
|
||||
if (val) { val = val.toString(); }
|
||||
|
||||
var selectedText = (val === selected) ? "selected" : "";
|
||||
|
||||
var data = "";
|
||||
if (comboboxView.dataAttributes) {
|
||||
comboboxView.dataAttributes.forEach(function(a) {
|
||||
data += "data-" + a + "=\"" + o.get(a) + "\" ";
|
||||
});
|
||||
}
|
||||
buffer.push("<option " + selectedText + " value=\"" + val + "\" " + data + ">" + Em.get(o, nameProperty) + "</option>");
|
||||
buffer.push("<option " + selectedText + " value=\"" + val + "\" " + self.buildData(o) + ">" + Em.get(o, nameProperty) + "</option>");
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
valueChanged: function() {
|
||||
var $combo = this.$();
|
||||
var val = this.get('value');
|
||||
var $combo = this.$(),
|
||||
val = this.get('value');
|
||||
if (val !== undefined && val !== null) {
|
||||
$combo.val(val.toString());
|
||||
} else {
|
||||
@@ -55,8 +59,8 @@ Discourse.ComboboxView = Discourse.View.extend({
|
||||
}.observes('value'),
|
||||
|
||||
didInsertElement: function() {
|
||||
var $elem = this.$();
|
||||
var comboboxView = this;
|
||||
var $elem = this.$(),
|
||||
self = this;
|
||||
|
||||
$elem.chosen({ template: this.template, disable_search_threshold: 5 });
|
||||
if (this.overrideWidths) {
|
||||
@@ -74,7 +78,7 @@ Discourse.ComboboxView = Discourse.View.extend({
|
||||
}
|
||||
|
||||
$elem.chosen().change(function(e) {
|
||||
comboboxView.set('value', $(e.target).val());
|
||||
self.set('value', $(e.target).val());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,15 +7,9 @@
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.DiscoveryTopicsView = Discourse.View.extend(Discourse.LoadMore, {
|
||||
Discourse.DiscoveryTopicsView = Discourse.View.extend(Discourse.ScrollTop, Discourse.LoadMore, {
|
||||
eyelineSelector: '.topic-list-item',
|
||||
|
||||
_scrollTop: function() {
|
||||
Em.run.schedule('afterRender', function() {
|
||||
$(document).scrollTop(0);
|
||||
});
|
||||
}.on('didInsertElement'),
|
||||
|
||||
actions: {
|
||||
loadMore: function() {
|
||||
var self = this;
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
Discourse.GroupIndexView = Discourse.View.extend(Discourse.LoadMore, {
|
||||
/**
|
||||
Displays all posts within a group
|
||||
|
||||
@class Discourse.GroupIndexView
|
||||
@extends Ember.Mixin
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.GroupIndexView = Discourse.View.extend(Discourse.ScrollTop, Discourse.LoadMore, {
|
||||
eyelineSelector: '.user-stream .item'
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -12,13 +12,21 @@ Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, {
|
||||
classNameBindings: ['postTypeClass',
|
||||
'selected',
|
||||
'post.hidden:post-hidden',
|
||||
'post.deleted'],
|
||||
'post.deleted',
|
||||
'groupNameClass'],
|
||||
postBinding: 'content',
|
||||
|
||||
postTypeClass: function() {
|
||||
return this.get('post.post_type') === Discourse.Site.currentProp('post_types.moderator_action') ? 'moderator' : 'regular';
|
||||
}.property('post.post_type'),
|
||||
|
||||
groupNameClass: function() {
|
||||
var primaryGroupName = this.get('post.primary_group_name');
|
||||
if (primaryGroupName) {
|
||||
return "group-" + primaryGroupName;
|
||||
}
|
||||
}.property('post.primary_group_name'),
|
||||
|
||||
// If the cooked content changed, add the quote controls
|
||||
cookedChanged: function() {
|
||||
var postView = this;
|
||||
|
||||
@@ -40,6 +40,16 @@
|
||||
margin-top: 0px;
|
||||
color: $primary_medium;
|
||||
}
|
||||
.groups {
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
margin-top: 0px;
|
||||
color: $primary_medium;
|
||||
|
||||
.group-link {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
clear: both;
|
||||
|
||||
@@ -461,17 +461,26 @@ iframe {
|
||||
width: 45px;
|
||||
height: 45px;
|
||||
}
|
||||
|
||||
.contents {
|
||||
|
||||
.contents {
|
||||
|
||||
text-align: center;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
a {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 45px;
|
||||
}
|
||||
|
||||
a.user-group {
|
||||
margin: 4px 0 0 0;
|
||||
padding: 0px;
|
||||
color: $primary_light;
|
||||
font-size: 80%;
|
||||
width: 100%;
|
||||
line-height: 13px;
|
||||
}
|
||||
|
||||
h3 a {
|
||||
display: inline;
|
||||
width: auto;
|
||||
@@ -614,6 +623,7 @@ position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.user-title {
|
||||
margin-top: 8px;
|
||||
color: $primary_light;
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
@import "common/foundation/variables";
|
||||
@import "common/foundation/mixins";
|
||||
|
||||
.groups {
|
||||
.group-link {
|
||||
color: $tertiary_lightest;
|
||||
}
|
||||
}
|
||||
|
||||
.user-preferences {
|
||||
input.category-group {
|
||||
width: 500px;
|
||||
@@ -210,9 +216,6 @@
|
||||
|
||||
h1, h2 {margin-top: 10px;}
|
||||
|
||||
.group-link {
|
||||
color: $tertiary_lightest;
|
||||
}
|
||||
.bio {
|
||||
color: $primary_lighter;
|
||||
|
||||
|
||||
@@ -442,6 +442,7 @@ iframe {
|
||||
float: left;
|
||||
}
|
||||
|
||||
|
||||
.user-title {
|
||||
color: #aaa;
|
||||
padding-top: 2px;
|
||||
|
||||
@@ -17,6 +17,7 @@ class Admin::UsersController < Admin::AdminController
|
||||
:block,
|
||||
:unblock,
|
||||
:trust_level,
|
||||
:primary_group,
|
||||
:generate_api_key,
|
||||
:revoke_api_key]
|
||||
|
||||
@@ -94,6 +95,13 @@ class Admin::UsersController < Admin::AdminController
|
||||
render_serialized(@user, AdminUserSerializer)
|
||||
end
|
||||
|
||||
def primary_group
|
||||
guardian.ensure_can_change_primary_group!(@user)
|
||||
@user.primary_group_id = params[:primary_group_id]
|
||||
@user.save!
|
||||
render nothing: true
|
||||
end
|
||||
|
||||
def trust_level
|
||||
guardian.ensure_can_change_trust_level!(@user)
|
||||
logger = StaffActionLogger.new(current_user)
|
||||
|
||||
@@ -40,6 +40,11 @@ class ListController < ApplicationController
|
||||
list_opts = build_topic_list_options
|
||||
list_opts.merge!(options) if options
|
||||
user = list_target_user
|
||||
|
||||
if filter == :latest && params[:category].blank?
|
||||
list_opts[:no_definitions] = true
|
||||
end
|
||||
|
||||
list = TopicQuery.new(user, list_opts).public_send("list_#{filter}")
|
||||
list.more_topics_url = construct_url_with(list_opts)
|
||||
if Discourse.anonymous_filters.include?(filter)
|
||||
|
||||
@@ -30,7 +30,8 @@ module Jobs
|
||||
)', post.topic.category_id, CategoryUser.notification_levels[:muted])
|
||||
.each do |user|
|
||||
if Guardian.new(user).can_see?(post)
|
||||
UserNotifications.mailing_list_notify(user, post).deliver
|
||||
message = UserNotifications.mailing_list_notify(user, post)
|
||||
Email::Sender.new(message, :mailing_list, user).send
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module Jobs
|
||||
# various consistency checks
|
||||
class EnsureDbConsistency < Jobs::Scheduled
|
||||
every 1.day
|
||||
every 12.hours
|
||||
|
||||
def execute(args)
|
||||
TopicUser.ensure_consistency!
|
||||
|
||||
@@ -220,6 +220,7 @@ class Group < ActiveRecord::Base
|
||||
if @deletions
|
||||
@deletions.each do |gu|
|
||||
gu.destroy
|
||||
User.update_all 'primary_group_id = NULL', ['id = ? AND primary_group_id = ?', gu.user_id, gu.group_id]
|
||||
end
|
||||
end
|
||||
@deletions = nil
|
||||
|
||||
@@ -14,12 +14,14 @@ class AdminDetailedUserSerializer < AdminUserSerializer
|
||||
:private_topics_count,
|
||||
:can_delete_all_posts,
|
||||
:can_be_deleted,
|
||||
:suspend_reason
|
||||
:suspend_reason,
|
||||
:primary_group_id
|
||||
|
||||
has_one :approved_by, serializer: BasicUserSerializer, embed: :objects
|
||||
has_one :api_key, serializer: ApiKeySerializer, embed: :objects
|
||||
has_one :suspended_by, serializer: BasicUserSerializer, embed: :objects
|
||||
has_one :leader_requirements, serializer: LeaderRequirementsSerializer, embed: :objects
|
||||
has_many :custom_groups, embed: :object, serializer: BasicGroupSerializer
|
||||
|
||||
def can_revoke_admin
|
||||
scope.can_revoke_admin?(object)
|
||||
|
||||
@@ -29,31 +29,36 @@ class ListableTopicSerializer < BasicTopicSerializer
|
||||
end
|
||||
|
||||
def seen
|
||||
object.user_data.present?
|
||||
return true if !scope || !scope.user
|
||||
return true if object.user_data && !object.user_data.last_read_post_number.nil?
|
||||
return true if object.created_at < scope.user.treat_as_new_topic_start_date
|
||||
false
|
||||
end
|
||||
|
||||
def unseen
|
||||
return false if scope.blank?
|
||||
return false if scope.user.blank?
|
||||
return false if object.user_data.present?
|
||||
return false if object.created_at < scope.user.treat_as_new_topic_start_date
|
||||
true
|
||||
!seen
|
||||
end
|
||||
|
||||
def last_read_post_number
|
||||
return nil unless object.user_data
|
||||
object.user_data.last_read_post_number
|
||||
end
|
||||
alias :include_last_read_post_number? :seen
|
||||
|
||||
def has_user_data
|
||||
!!object.user_data
|
||||
end
|
||||
|
||||
alias :include_last_read_post_number? :has_user_data
|
||||
|
||||
def unread
|
||||
unread_helper.unread_posts
|
||||
end
|
||||
alias :include_unread? :seen
|
||||
alias :include_unread? :has_user_data
|
||||
|
||||
def new_posts
|
||||
unread_helper.new_posts
|
||||
end
|
||||
alias :include_new_posts? :seen
|
||||
alias :include_new_posts? :has_user_data
|
||||
|
||||
def include_excerpt?
|
||||
pinned
|
||||
|
||||
@@ -23,6 +23,7 @@ class PostSerializer < BasicPostSerializer
|
||||
:topic_slug,
|
||||
:topic_id,
|
||||
:display_username,
|
||||
:primary_group_name,
|
||||
:version,
|
||||
:can_edit,
|
||||
:can_delete,
|
||||
@@ -75,6 +76,11 @@ class PostSerializer < BasicPostSerializer
|
||||
object.user.try(:name)
|
||||
end
|
||||
|
||||
def primary_group_name
|
||||
return nil unless object.user && @topic_view
|
||||
return @topic_view.primary_group_names[object.user.primary_group_id] if object.user.primary_group_id
|
||||
end
|
||||
|
||||
def link_counts
|
||||
return @single_post_link_counts if @single_post_link_counts.present?
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ class TopicListItemSerializer < ListableTopicSerializer
|
||||
def starred
|
||||
object.user_data.starred?
|
||||
end
|
||||
alias :include_starred? :seen
|
||||
alias :include_starred? :has_user_data
|
||||
|
||||
def posters
|
||||
object.posters || []
|
||||
|
||||
Reference in New Issue
Block a user