mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-27 09:36:19 -05:00
Add a way to flag a topic
This commit is contained in:
parent
11ee4e7328
commit
6bbc3ec3e0
23 changed files with 251 additions and 26 deletions
|
@ -42,6 +42,10 @@ Discourse.AdminFlagsController = Ember.ArrayController.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
doneTopicFlags: function(item) {
|
||||||
|
this.send('disagreeFlags', item);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Deletes a post
|
Deletes a post
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,14 @@ Discourse.FlaggedPost = Discourse.Post.extend({
|
||||||
return !_.every(this.get('post_actions'), function(action) { return action.name_key !== 'spam'; });
|
return !_.every(this.get('post_actions'), function(action) { return action.name_key !== 'spam'; });
|
||||||
}.property('post_actions.@each.name_key'),
|
}.property('post_actions.@each.name_key'),
|
||||||
|
|
||||||
|
topicFlagged: function() {
|
||||||
|
return _.any(this.get('post_actions'), function(action) { return action.targets_topic; });
|
||||||
|
}.property('post_actions.@each.targets_topic'),
|
||||||
|
|
||||||
|
postAuthorFlagged: function() {
|
||||||
|
return _.any(this.get('post_actions'), function(action) { return !action.targets_topic; });
|
||||||
|
}.property('post_actions.@each.targets_topic'),
|
||||||
|
|
||||||
canDeleteAsSpammer: function() {
|
canDeleteAsSpammer: function() {
|
||||||
return (Discourse.User.currentProp('staff') && this.get('flaggedForSpam') && this.get('user.can_delete_all_posts') && this.get('user.can_be_deleted'));
|
return (Discourse.User.currentProp('staff') && this.get('flaggedForSpam') && this.get('user.can_delete_all_posts') && this.get('user.can_be_deleted'));
|
||||||
}.property('flaggedForSpam'),
|
}.property('flaggedForSpam'),
|
||||||
|
|
|
@ -26,9 +26,20 @@
|
||||||
{{#each flaggedPost in content}}
|
{{#each flaggedPost in content}}
|
||||||
<tr {{bind-attr class="flaggedPost.extraClasses"}}>
|
<tr {{bind-attr class="flaggedPost.extraClasses"}}>
|
||||||
|
|
||||||
<td class='user'>{{#if flaggedPost.user}}{{#link-to 'adminUser' flaggedPost.user}}{{avatar flaggedPost.user imageSize="small"}}{{/link-to}}{{/if}}</td>
|
<td class='user'>
|
||||||
|
{{#if flaggedPost.postAuthorFlagged}}
|
||||||
|
{{#if flaggedPost.user}}
|
||||||
|
{{#link-to 'adminUser' flaggedPost.user}}{{avatar flaggedPost.user imageSize="small"}}{{/link-to}}
|
||||||
|
{{/if}}
|
||||||
|
{{/if}}
|
||||||
|
</td>
|
||||||
|
|
||||||
<td class='excerpt'>{{#if flaggedPost.topicHidden}}<i title='{{i18n topic_statuses.invisible.help}}' class='fa fa-eye-slash'></i> {{/if}}<h3><a href='{{unbound flaggedPost.url}}'>{{flaggedPost.title}}</a></h3><br>{{{flaggedPost.excerpt}}}
|
<td class='excerpt'>
|
||||||
|
{{#if flaggedPost.topicHidden}}<i title='{{i18n topic_statuses.invisible.help}}' class='fa fa-eye-slash'></i> {{/if}}<h3><a href='{{unbound flaggedPost.url}}'>{{flaggedPost.title}}</a></h3>
|
||||||
|
<br>
|
||||||
|
{{#if flaggedPost.postAuthorFlagged}}
|
||||||
|
{{{flaggedPost.excerpt}}}
|
||||||
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class='flaggers'>
|
<td class='flaggers'>
|
||||||
|
@ -51,6 +62,15 @@
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
{{#if flaggedPost.topicFlagged}}
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td class='message'><div>{{{i18n admin.flags.topic_flagged}}}</div></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#each flaggedPost.messages}}
|
{{#each flaggedPost.messages}}
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
@ -74,6 +94,11 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" class="action">
|
<td colspan="4" class="action">
|
||||||
{{#if adminActiveFlagsView}}
|
{{#if adminActiveFlagsView}}
|
||||||
|
{{#if flaggedPost.topicFlagged}}
|
||||||
|
<a href='{{unbound flaggedPost.url}}'>{{i18n admin.flags.visit_topic}}</a><br>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if flaggedPost.postAuthorFlagged}}
|
||||||
{{#if flaggedPost.postHidden}}
|
{{#if flaggedPost.postHidden}}
|
||||||
<button title='{{i18n admin.flags.disagree_unhide_title}}' class='btn' {{action disagreeFlags flaggedPost}}><i class="fa fa-thumbs-o-down"></i> {{i18n admin.flags.disagree_unhide}}</button>
|
<button title='{{i18n admin.flags.disagree_unhide_title}}' class='btn' {{action disagreeFlags flaggedPost}}><i class="fa fa-thumbs-o-down"></i> {{i18n admin.flags.disagree_unhide}}</button>
|
||||||
<button title='{{i18n admin.flags.defer_title}}' class='btn' {{action deferFlags flaggedPost}}><i class="fa fa-external-link"></i> {{i18n admin.flags.defer}}</button>
|
<button title='{{i18n admin.flags.defer_title}}' class='btn' {{action deferFlags flaggedPost}}><i class="fa fa-external-link"></i> {{i18n admin.flags.defer}}</button>
|
||||||
|
@ -87,6 +112,9 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
<button title='{{i18n admin.flags.delete_post_title}}' class='btn' {{action deletePost flaggedPost}}><i class="fa fa-trash-o"></i> {{i18n admin.flags.delete_post}}</button>
|
<button title='{{i18n admin.flags.delete_post_title}}' class='btn' {{action deletePost flaggedPost}}><i class="fa fa-trash-o"></i> {{i18n admin.flags.delete_post}}</button>
|
||||||
|
{{else}}
|
||||||
|
<button title='{{i18n admin.flags.clear_topic_flags_title}}' class='btn' {{action doneTopicFlags flaggedPost}}>{{i18n admin.flags.clear_topic_flags}}</button>
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -13,6 +13,30 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
|
||||||
this.set('selected', null);
|
this.set('selected', null);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
flagsAvailable: function() {
|
||||||
|
if (!this.get('flagTopic')) {
|
||||||
|
return this.get('model.flagsAvailable');
|
||||||
|
} else {
|
||||||
|
var self = this,
|
||||||
|
lookup = Em.Object.create();
|
||||||
|
|
||||||
|
_.each(this.get("actions_summary"),function(a) {
|
||||||
|
var actionSummary;
|
||||||
|
a.flagTopic = self.get('model');
|
||||||
|
a.actionType = Discourse.Site.current().topicFlagTypeById(a.id);
|
||||||
|
actionSummary = Discourse.ActionSummary.create(a);
|
||||||
|
lookup.set(a.actionType.get('name_key'), actionSummary);
|
||||||
|
});
|
||||||
|
this.set('topicActionByName', lookup);
|
||||||
|
|
||||||
|
return Discourse.Site.currentProp('topic_flag_types').filter(function(item) {
|
||||||
|
return _.any(self.get("actions_summary"), function(a) {
|
||||||
|
return (a.id === item.get('id') && a.can_act);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}.property('post', 'flagTopic', 'actions_summary.@each.can_act'),
|
||||||
|
|
||||||
submitEnabled: function() {
|
submitEnabled: function() {
|
||||||
var selected = this.get('selected');
|
var selected = this.get('selected');
|
||||||
if (!selected) return false;
|
if (!selected) return false;
|
||||||
|
@ -29,6 +53,8 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
|
||||||
|
|
||||||
// Staff accounts can "take action"
|
// Staff accounts can "take action"
|
||||||
canTakeAction: function() {
|
canTakeAction: function() {
|
||||||
|
if (this.get("flagTopic")) return false;
|
||||||
|
|
||||||
// We can only take actions on non-custom flags
|
// We can only take actions on non-custom flags
|
||||||
if (this.get('selected.is_custom_flag')) return false;
|
if (this.get('selected.is_custom_flag')) return false;
|
||||||
return Discourse.User.currentProp('staff');
|
return Discourse.User.currentProp('staff');
|
||||||
|
@ -36,9 +62,9 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
|
||||||
|
|
||||||
submitText: function(){
|
submitText: function(){
|
||||||
if (this.get('selected.is_custom_flag')) {
|
if (this.get('selected.is_custom_flag')) {
|
||||||
return I18n.t("flagging.notify_action");
|
return I18n.t(this.get('flagTopic') ? "flagging_topic.notify_action" : "flagging.notify_action");
|
||||||
} else {
|
} else {
|
||||||
return I18n.t("flagging.action");
|
return I18n.t(this.get('flagTopic') ? "flagging_topic.action" : "flagging.action");
|
||||||
}
|
}
|
||||||
}.property('selected.is_custom_flag'),
|
}.property('selected.is_custom_flag'),
|
||||||
|
|
||||||
|
@ -50,7 +76,12 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
|
||||||
|
|
||||||
createFlag: function(opts) {
|
createFlag: function(opts) {
|
||||||
var self = this;
|
var self = this;
|
||||||
var postAction = this.get('actionByName.' + this.get('selected.name_key'));
|
var postAction; // an instance of ActionSummary
|
||||||
|
if (!this.get('flagTopic')) {
|
||||||
|
postAction = this.get('actionByName.' + this.get('selected.name_key'));
|
||||||
|
} else {
|
||||||
|
postAction = this.get('topicActionByName.' + this.get('selected.name_key'));
|
||||||
|
}
|
||||||
var params = this.get('selected.is_custom_flag') ? {message: this.get('message')} : {};
|
var params = this.get('selected.is_custom_flag') ? {message: this.get('message')} : {};
|
||||||
|
|
||||||
if (opts) params = $.extend(params, opts);
|
if (opts) params = $.extend(params, opts);
|
||||||
|
@ -58,6 +89,7 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
|
||||||
this.send('hideModal');
|
this.send('hideModal');
|
||||||
postAction.act(params).then(function() {
|
postAction.act(params).then(function() {
|
||||||
self.send('closeModal');
|
self.send('closeModal');
|
||||||
|
if (self.get('flagTopic')) { bootbox.alert(I18n.t('topic.flag_topic.success_message')); }
|
||||||
}, function(errors) {
|
}, function(errors) {
|
||||||
self.send('showModal');
|
self.send('showModal');
|
||||||
self.displayErrors(errors);
|
self.displayErrors(errors);
|
||||||
|
@ -70,6 +102,8 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
|
||||||
},
|
},
|
||||||
|
|
||||||
canDeleteSpammer: function() {
|
canDeleteSpammer: function() {
|
||||||
|
if (this.get("flagTopic")) return false;
|
||||||
|
|
||||||
if (Discourse.User.currentProp('staff') && this.get('selected.name_key') === 'spam') {
|
if (Discourse.User.currentProp('staff') && this.get('selected.name_key') === 'spam') {
|
||||||
return this.get('userDetails.can_be_deleted') && this.get('userDetails.can_delete_all_posts');
|
return this.get('userDetails.can_be_deleted') && this.get('userDetails.can_delete_all_posts');
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -70,10 +70,11 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
||||||
return Discourse.ajax("/post_actions", {
|
return Discourse.ajax("/post_actions", {
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
data: {
|
data: {
|
||||||
id: this.get('post.id'),
|
id: this.get('flagTopic') ? this.get('flagTopic.id') : this.get('post.id'),
|
||||||
post_action_type_id: this.get('id'),
|
post_action_type_id: this.get('id'),
|
||||||
message: opts.message,
|
message: opts.message,
|
||||||
take_action: opts.takeAction
|
take_action: opts.takeAction,
|
||||||
|
flag_topic: this.get('flagTopic') ? true : false
|
||||||
}
|
}
|
||||||
}).then(null, function (error) {
|
}).then(null, function (error) {
|
||||||
actionSummary.removeAction();
|
actionSummary.removeAction();
|
||||||
|
|
|
@ -30,6 +30,10 @@ Discourse.Site = Discourse.Model.extend({
|
||||||
return this.get("postActionByIdLookup.action" + id);
|
return this.get("postActionByIdLookup.action" + id);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
topicFlagTypeById: function(id) {
|
||||||
|
return this.get("topicFlagByIdLookup.action" + id);
|
||||||
|
},
|
||||||
|
|
||||||
updateCategory: function(newCategory) {
|
updateCategory: function(newCategory) {
|
||||||
var existingCategory = this.get('categories').findProperty('id', Em.get(newCategory, 'id'));
|
var existingCategory = this.get('categories').findProperty('id', Em.get(newCategory, 'id'));
|
||||||
if (existingCategory) existingCategory.mergeAttributes(newCategory);
|
if (existingCategory) existingCategory.mergeAttributes(newCategory);
|
||||||
|
@ -82,6 +86,16 @@ Discourse.Site.reopenClass(Discourse.Singleton, {
|
||||||
return actionType;
|
return actionType;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result.topic_flag_types) {
|
||||||
|
result.topicFlagByIdLookup = Em.Object.create();
|
||||||
|
result.topic_flag_types = _.map(result.topic_flag_types,function(p) {
|
||||||
|
var actionType = Discourse.PostActionType.create(p);
|
||||||
|
result.topicFlagByIdLookup.set("action" + p.id, actionType);
|
||||||
|
return actionType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (result.archetypes) {
|
if (result.archetypes) {
|
||||||
result.archetypes = _.map(result.archetypes,function(a) {
|
result.archetypes = _.map(result.archetypes,function(a) {
|
||||||
return Discourse.Archetype.create(a);
|
return Discourse.Archetype.create(a);
|
||||||
|
|
|
@ -316,6 +316,26 @@ Discourse.Topic.reopenClass({
|
||||||
MUTE: 0
|
MUTE: 0
|
||||||
},
|
},
|
||||||
|
|
||||||
|
createActionSummary: function(result) {
|
||||||
|
if (result.actions_summary) {
|
||||||
|
var lookup = Em.Object.create();
|
||||||
|
result.actions_summary = result.actions_summary.map(function(a) {
|
||||||
|
a.post = result;
|
||||||
|
a.actionType = Discourse.Site.current().postActionTypeById(a.id);
|
||||||
|
var actionSummary = Discourse.ActionSummary.create(a);
|
||||||
|
lookup.set(a.actionType.get('name_key'), actionSummary);
|
||||||
|
return actionSummary;
|
||||||
|
});
|
||||||
|
result.set('actionByName', lookup);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
create: function() {
|
||||||
|
var result = this._super.apply(this, arguments);
|
||||||
|
this.createActionSummary(result);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Find similar topics to a given title and body
|
Find similar topics to a given title and body
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,12 @@ Discourse.TopicRoute = Discourse.Route.extend({
|
||||||
this.controllerFor('flag').setProperties({ selected: null });
|
this.controllerFor('flag').setProperties({ selected: null });
|
||||||
},
|
},
|
||||||
|
|
||||||
|
showFlagTopic: function(topic) {
|
||||||
|
//Discourse.Route.showModal(this, 'flagTopic', topic);
|
||||||
|
Discourse.Route.showModal(this, 'flag', topic);
|
||||||
|
this.controllerFor('flag').setProperties({ selected: null, flagTopic: true });
|
||||||
|
},
|
||||||
|
|
||||||
showAutoClose: function() {
|
showAutoClose: function() {
|
||||||
Discourse.Route.showModal(this, 'editTopicAutoClose', this.modelFor('topic'));
|
Discourse.Route.showModal(this, 'editTopicAutoClose', this.modelFor('topic'));
|
||||||
this.controllerFor('modal').set('modalClass', 'edit-auto-close-modal');
|
this.controllerFor('modal').set('modalClass', 'edit-auto-close-modal');
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
A button for flagging a topic
|
||||||
|
|
||||||
|
@class FlagTopicButton
|
||||||
|
@extends Discourse.ButtonView
|
||||||
|
@namespace Discourse
|
||||||
|
@module Discourse
|
||||||
|
**/
|
||||||
|
Discourse.FlagTopicButton = Discourse.ButtonView.extend({
|
||||||
|
classNames: ['flag-topic'],
|
||||||
|
textKey: 'topic.flag_topic.title',
|
||||||
|
helpKey: 'topic.flag_topic.help',
|
||||||
|
|
||||||
|
click: function() {
|
||||||
|
this.get('controller').send('showFlagTopic', this.get('controller.content'));
|
||||||
|
},
|
||||||
|
|
||||||
|
renderIcon: function(buffer) {
|
||||||
|
buffer.push("<i class='fa fa-flag'></i>");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -8,7 +8,10 @@
|
||||||
**/
|
**/
|
||||||
Discourse.FlagView = Discourse.ModalBodyView.extend({
|
Discourse.FlagView = Discourse.ModalBodyView.extend({
|
||||||
templateName: 'modal/flag',
|
templateName: 'modal/flag',
|
||||||
title: I18n.t('flagging.title'),
|
|
||||||
|
title: function() {
|
||||||
|
return this.get('controller.flagTopic') ? I18n.t('flagging_topic.title') : I18n.t('flagging.title');
|
||||||
|
}.property('controller.flagTopic'),
|
||||||
|
|
||||||
selectedChanged: function() {
|
selectedChanged: function() {
|
||||||
var flagView = this;
|
var flagView = this;
|
||||||
|
|
|
@ -20,7 +20,6 @@ Discourse.TopicFooterButtonsView = Discourse.ContainerView.extend({
|
||||||
var topic = this.get('topic');
|
var topic = this.get('topic');
|
||||||
if (Discourse.User.current()) {
|
if (Discourse.User.current()) {
|
||||||
if (!topic.get('isPrivateMessage')) {
|
if (!topic.get('isPrivateMessage')) {
|
||||||
|
|
||||||
// We hide some controls from private messages
|
// We hide some controls from private messages
|
||||||
if (this.get('topic.details.can_invite_to')) {
|
if (this.get('topic.details.can_invite_to')) {
|
||||||
this.attachViewClass(Discourse.InviteReplyButton);
|
this.attachViewClass(Discourse.InviteReplyButton);
|
||||||
|
@ -28,6 +27,9 @@ Discourse.TopicFooterButtonsView = Discourse.ContainerView.extend({
|
||||||
this.attachViewClass(Discourse.StarButton);
|
this.attachViewClass(Discourse.StarButton);
|
||||||
this.attachViewClass(Discourse.ShareButton);
|
this.attachViewClass(Discourse.ShareButton);
|
||||||
this.attachViewClass(Discourse.ClearPinButton);
|
this.attachViewClass(Discourse.ClearPinButton);
|
||||||
|
if (this.get('topic.details.can_flag_topic')) {
|
||||||
|
this.attachViewClass(Discourse.FlagTopicButton);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.attachViewClass(Discourse.ReplyButton);
|
this.attachViewClass(Discourse.ReplyButton);
|
||||||
this.attachViewClass(Discourse.NotificationsButton);
|
this.attachViewClass(Discourse.NotificationsButton);
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
//= require ./discourse/controllers/controller
|
//= require ./discourse/controllers/controller
|
||||||
//= require ./discourse/controllers/object_controller
|
//= require ./discourse/controllers/object_controller
|
||||||
//= require ./discourse/views/modal/modal_body_view
|
//= require ./discourse/views/modal/modal_body_view
|
||||||
|
//= require ./discourse/views/modal/flag_view
|
||||||
//= require ./discourse/views/combobox_view
|
//= require ./discourse/views/combobox_view
|
||||||
//= require ./discourse/views/buttons/button_view
|
//= require ./discourse/views/buttons/button_view
|
||||||
//= require ./discourse/views/buttons/dropdown_button_view
|
//= require ./discourse/views/buttons/dropdown_button_view
|
||||||
|
|
|
@ -369,11 +369,11 @@ table {
|
||||||
}
|
}
|
||||||
td { vertical-align: top; }
|
td { vertical-align: top; }
|
||||||
th { text-align: left; }
|
th { text-align: left; }
|
||||||
.user { width: 40px; }
|
.user { width: 40px; padding-top: 12px; }
|
||||||
.excerpt {
|
.excerpt {
|
||||||
max-width: 740px;
|
max-width: 740px;
|
||||||
width: 740px;
|
width: 740px;
|
||||||
padding: 0 10px 10px 0;
|
padding: 8px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
.fa,h3 { display: inline-block; }
|
.fa,h3 { display: inline-block; }
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ class PostActionsController < ApplicationController
|
||||||
args = {}
|
args = {}
|
||||||
args[:message] = params[:message] if params[:message].present?
|
args[:message] = params[:message] if params[:message].present?
|
||||||
args[:take_action] = true if guardian.is_staff? and params[:take_action] == 'true'
|
args[:take_action] = true if guardian.is_staff? and params[:take_action] == 'true'
|
||||||
|
args[:flag_topic] = true if params[:flag_topic]
|
||||||
|
|
||||||
post_action = PostAction.act(current_user, @post, @post_action_type_id, args)
|
post_action = PostAction.act(current_user, @post, @post_action_type_id, args)
|
||||||
|
|
||||||
|
@ -63,7 +64,18 @@ class PostActionsController < ApplicationController
|
||||||
|
|
||||||
def fetch_post_from_params
|
def fetch_post_from_params
|
||||||
params.require(:id)
|
params.require(:id)
|
||||||
finder = Post.where(id: params[:id])
|
|
||||||
|
post_id = if params[:flag_topic]
|
||||||
|
begin
|
||||||
|
Topic.find(params[:id]).posts.first.id
|
||||||
|
rescue
|
||||||
|
raise Discourse::NotFound
|
||||||
|
end
|
||||||
|
else
|
||||||
|
params[:id]
|
||||||
|
end
|
||||||
|
|
||||||
|
finder = Post.where(id: post_id)
|
||||||
|
|
||||||
# Include deleted posts if the user is a moderator (to guardian ?)
|
# Include deleted posts if the user is a moderator (to guardian ?)
|
||||||
finder = finder.with_deleted if current_user.try(:moderator?)
|
finder = finder.with_deleted if current_user.try(:moderator?)
|
||||||
|
|
|
@ -12,6 +12,7 @@ class PostAction < ActiveRecord::Base
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :post_action_type
|
belongs_to :post_action_type
|
||||||
belongs_to :related_post, class_name: 'Post'
|
belongs_to :related_post, class_name: 'Post'
|
||||||
|
belongs_to :target_user, class_name: 'User'
|
||||||
|
|
||||||
rate_limit :post_action_rate_limiter
|
rate_limit :post_action_rate_limiter
|
||||||
|
|
||||||
|
@ -128,7 +129,8 @@ class PostAction < ActiveRecord::Base
|
||||||
post_action_type_id: post_action_type_id,
|
post_action_type_id: post_action_type_id,
|
||||||
message: opts[:message],
|
message: opts[:message],
|
||||||
staff_took_action: opts[:take_action] || false,
|
staff_took_action: opts[:take_action] || false,
|
||||||
related_post_id: related_post_id )
|
related_post_id: related_post_id,
|
||||||
|
targets_topic: !!opts[:flag_topic] )
|
||||||
rescue ActiveRecord::RecordNotUnique
|
rescue ActiveRecord::RecordNotUnique
|
||||||
# can happen despite being .create
|
# can happen despite being .create
|
||||||
# since already bookmarked
|
# since already bookmarked
|
||||||
|
@ -319,6 +321,7 @@ end
|
||||||
# staff_took_action :boolean default(FALSE), not null
|
# staff_took_action :boolean default(FALSE), not null
|
||||||
# defer :boolean
|
# defer :boolean
|
||||||
# defer_by :integer
|
# defer_by :integer
|
||||||
|
# targets_topic :boolean default(FALSE)
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
|
|
|
@ -28,6 +28,10 @@ class PostActionType < ActiveRecord::Base
|
||||||
@notify_flag_type_ids ||= types.only(:off_topic, :spam, :inappropriate, :notify_moderators).values
|
@notify_flag_type_ids ||= types.only(:off_topic, :spam, :inappropriate, :notify_moderators).values
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def topic_flag_types
|
||||||
|
@topic_flag_types ||= types.only(:spam, :inappropriate, :notify_moderators)
|
||||||
|
end
|
||||||
|
|
||||||
def is_flag?(sym)
|
def is_flag?(sym)
|
||||||
flag_types.valid?(sym)
|
flag_types.valid?(sym)
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,6 +17,10 @@ class Site
|
||||||
PostActionType.ordered
|
PostActionType.ordered
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def topic_flag_types
|
||||||
|
post_action_types.where(name_key: ['inappropriate', 'spam', 'notify_moderators'])
|
||||||
|
end
|
||||||
|
|
||||||
def notification_types
|
def notification_types
|
||||||
Notification.types
|
Notification.types
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,6 +12,7 @@ class SiteSerializer < ApplicationSerializer
|
||||||
|
|
||||||
has_many :categories, serializer: BasicCategorySerializer, embed: :objects
|
has_many :categories, serializer: BasicCategorySerializer, embed: :objects
|
||||||
has_many :post_action_types, embed: :objects
|
has_many :post_action_types, embed: :objects
|
||||||
|
has_many :topic_flag_types, serializer: TopicFlagTypeSerializer, embed: :objects
|
||||||
has_many :trust_levels, embed: :objects
|
has_many :trust_levels, embed: :objects
|
||||||
has_many :archetypes, embed: :objects, serializer: ArchetypeSerializer
|
has_many :archetypes, embed: :objects, serializer: ArchetypeSerializer
|
||||||
|
|
||||||
|
|
9
app/serializers/topic_flag_type_serializer.rb
Normal file
9
app/serializers/topic_flag_type_serializer.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
class TopicFlagTypeSerializer < PostActionTypeSerializer
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def i18n(field, vars={})
|
||||||
|
I18n.t("topic_flag_types.#{object.name_key}.#{field}", vars)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -35,7 +35,8 @@ class TopicViewSerializer < ApplicationSerializer
|
||||||
:details,
|
:details,
|
||||||
:highest_post_number,
|
:highest_post_number,
|
||||||
:last_read_post_number,
|
:last_read_post_number,
|
||||||
:deleted_by
|
:deleted_by,
|
||||||
|
:actions_summary
|
||||||
|
|
||||||
# Define a delegator for each attribute of the topic we want
|
# Define a delegator for each attribute of the topic we want
|
||||||
attributes *topic_attributes
|
attributes *topic_attributes
|
||||||
|
@ -97,6 +98,7 @@ class TopicViewSerializer < ApplicationSerializer
|
||||||
result[:can_invite_to] = true if scope.can_invite_to?(object.topic)
|
result[:can_invite_to] = true if scope.can_invite_to?(object.topic)
|
||||||
result[:can_create_post] = true if scope.can_create?(Post, object.topic)
|
result[:can_create_post] = true if scope.can_create?(Post, object.topic)
|
||||||
result[:can_reply_as_new_topic] = true if scope.can_reply_as_new_topic?(object.topic)
|
result[:can_reply_as_new_topic] = true if scope.can_reply_as_new_topic?(object.topic)
|
||||||
|
result[:can_flag_topic] = actions_summary.any? { |a| a[:can_act] }
|
||||||
result
|
result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -144,5 +146,17 @@ class TopicViewSerializer < ApplicationSerializer
|
||||||
PinnedCheck.new(object.topic, object.topic_user).pinned?
|
PinnedCheck.new(object.topic, object.topic_user).pinned?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def actions_summary
|
||||||
|
result = []
|
||||||
|
return [] unless post = object.posts.try(:first)
|
||||||
|
PostActionType.topic_flag_types.each do |sym, id|
|
||||||
|
result << { id: id,
|
||||||
|
count: 0,
|
||||||
|
hidden: false,
|
||||||
|
can_act: scope.post_can_act?(post, sym)}
|
||||||
|
# TODO: other keys? :can_clear_flags, :acted, :can_undo
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -788,6 +788,11 @@ en:
|
||||||
title: 'Share'
|
title: 'Share'
|
||||||
help: 'share a link to this topic'
|
help: 'share a link to this topic'
|
||||||
|
|
||||||
|
flag_topic:
|
||||||
|
title: 'Flag'
|
||||||
|
help: 'privately flag this topic for attention or send a private notification about it'
|
||||||
|
success_message: 'You successfully flagged this topic.'
|
||||||
|
|
||||||
inviting: "Inviting..."
|
inviting: "Inviting..."
|
||||||
|
|
||||||
invite_private:
|
invite_private:
|
||||||
|
@ -1077,6 +1082,11 @@ en:
|
||||||
more: "{{n}} to go..."
|
more: "{{n}} to go..."
|
||||||
left: "{{n}} remaining"
|
left: "{{n}} remaining"
|
||||||
|
|
||||||
|
flagging_topic:
|
||||||
|
title: "Why are you privately flagging this topic?"
|
||||||
|
action: "Flag Topic"
|
||||||
|
notify_action: "Notify"
|
||||||
|
|
||||||
topic_map:
|
topic_map:
|
||||||
title: "Topic Summary"
|
title: "Topic Summary"
|
||||||
links_shown: "show all {{totalLinks}} links..."
|
links_shown: "show all {{totalLinks}} links..."
|
||||||
|
@ -1239,12 +1249,16 @@ en:
|
||||||
disagree: "Disagree"
|
disagree: "Disagree"
|
||||||
disagree_title: "Disagree with flag, remove any flags from this post"
|
disagree_title: "Disagree with flag, remove any flags from this post"
|
||||||
delete_spammer_title: "Delete the user and all its posts and topics."
|
delete_spammer_title: "Delete the user and all its posts and topics."
|
||||||
|
clear_topic_flags: "Done"
|
||||||
|
clear_topic_flags_title: "The topic has been investigated and issues have been resolved. Click Done to remove the flags."
|
||||||
|
|
||||||
flagged_by: "Flagged by"
|
flagged_by: "Flagged by"
|
||||||
system: "System"
|
system: "System"
|
||||||
error: "Something went wrong"
|
error: "Something went wrong"
|
||||||
view_message: "Reply"
|
view_message: "Reply"
|
||||||
no_results: "There are no flags."
|
no_results: "There are no flags."
|
||||||
|
topic_flagged: "This <strong>topic</strong> has been flagged."
|
||||||
|
visit_topic: "Visit the topic to investigate and take action."
|
||||||
|
|
||||||
summary:
|
summary:
|
||||||
action_type_3:
|
action_type_3:
|
||||||
|
|
|
@ -360,6 +360,22 @@ en:
|
||||||
description: 'Vote for this post'
|
description: 'Vote for this post'
|
||||||
long_form: 'voted for this post'
|
long_form: 'voted for this post'
|
||||||
|
|
||||||
|
topic_flag_types:
|
||||||
|
spam:
|
||||||
|
title: 'Spam'
|
||||||
|
description: 'This topic is an advertisement. It is not useful or relevant to this site, but promotional in nature.'
|
||||||
|
long_form: 'flagged this as spam'
|
||||||
|
inappropriate:
|
||||||
|
title: 'Inappropriate'
|
||||||
|
description: 'This topic contains content that a reasonable person would consider offensive, abusive, or a violation of <a href="/faq">our community guidelines</a>.'
|
||||||
|
long_form: 'flagged this as inappropriate'
|
||||||
|
notify_moderators:
|
||||||
|
title: 'Notify moderators'
|
||||||
|
description: 'This topic requires general moderator attention based on the <a href="/faq">FAQ</a>, <a href="%{tos_url}">TOS</a>, or for another reason not listed above.'
|
||||||
|
long_form: 'notified moderators'
|
||||||
|
email_title: 'The topic "%{title}" requires moderator attention'
|
||||||
|
email_body: "%{link}\n\n%{message}"
|
||||||
|
|
||||||
flagging:
|
flagging:
|
||||||
you_must_edit: '<p>Your post was flagged by the community. Please see your private messages.</p>'
|
you_must_edit: '<p>Your post was flagged by the community. Please see your private messages.</p>'
|
||||||
user_must_edit: '<p>Flagged content temporarily hidden.</p>'
|
user_must_edit: '<p>Flagged content temporarily hidden.</p>'
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class AddTargetsTopicToPostActions < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :post_actions, :targets_topic, :boolean, default: false
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue