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
|
||||
|
||||
|
|
|
@ -62,6 +62,14 @@ Discourse.FlaggedPost = Discourse.Post.extend({
|
|||
return !_.every(this.get('post_actions'), function(action) { return action.name_key !== 'spam'; });
|
||||
}.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() {
|
||||
return (Discourse.User.currentProp('staff') && this.get('flaggedForSpam') && this.get('user.can_delete_all_posts') && this.get('user.can_be_deleted'));
|
||||
}.property('flaggedForSpam'),
|
||||
|
|
|
@ -26,9 +26,20 @@
|
|||
{{#each flaggedPost in content}}
|
||||
<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 class='flaggers'>
|
||||
|
@ -51,6 +62,15 @@
|
|||
|
||||
</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}}
|
||||
<tr>
|
||||
<td></td>
|
||||
|
@ -74,19 +94,27 @@
|
|||
<tr>
|
||||
<td colspan="4" class="action">
|
||||
{{#if adminActiveFlagsView}}
|
||||
{{#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.defer_title}}' class='btn' {{action deferFlags flaggedPost}}><i class="fa fa-external-link"></i> {{i18n admin.flags.defer}}</button>
|
||||
{{#if flaggedPost.topicFlagged}}
|
||||
<a href='{{unbound flaggedPost.url}}'>{{i18n admin.flags.visit_topic}}</a><br>
|
||||
{{/if}}
|
||||
|
||||
{{#if flaggedPost.postAuthorFlagged}}
|
||||
{{#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.defer_title}}' class='btn' {{action deferFlags flaggedPost}}><i class="fa fa-external-link"></i> {{i18n admin.flags.defer}}</button>
|
||||
{{else}}
|
||||
<button title='{{i18n admin.flags.agree_hide_title}}' class='btn' {{action agreeFlags flaggedPost}}><i class="fa fa-thumbs-o-up"></i> {{i18n admin.flags.agree_hide}}</button>
|
||||
<button title='{{i18n admin.flags.disagree_title}}' class='btn' {{action disagreeFlags flaggedPost}}><i class="fa fa-thumbs-o-down"></i> {{i18n admin.flags.disagree}}</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if flaggedPost.canDeleteAsSpammer}}
|
||||
<button title='{{i18n admin.flags.delete_spammer_title}}' class="btn" {{action deleteSpammer flaggedPost}}><i class="fa fa-exclamation-triangle"></i> {{i18n flagging.delete_spammer}}</button>
|
||||
{{/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>
|
||||
{{else}}
|
||||
<button title='{{i18n admin.flags.agree_hide_title}}' class='btn' {{action agreeFlags flaggedPost}}><i class="fa fa-thumbs-o-up"></i> {{i18n admin.flags.agree_hide}}</button>
|
||||
<button title='{{i18n admin.flags.disagree_title}}' class='btn' {{action disagreeFlags flaggedPost}}><i class="fa fa-thumbs-o-down"></i> {{i18n admin.flags.disagree}}</button>
|
||||
<button title='{{i18n admin.flags.clear_topic_flags_title}}' class='btn' {{action doneTopicFlags flaggedPost}}>{{i18n admin.flags.clear_topic_flags}}</button>
|
||||
{{/if}}
|
||||
|
||||
{{#if flaggedPost.canDeleteAsSpammer}}
|
||||
<button title='{{i18n admin.flags.delete_spammer_title}}' class="btn" {{action deleteSpammer flaggedPost}}><i class="fa fa-exclamation-triangle"></i> {{i18n flagging.delete_spammer}}</button>
|
||||
{{/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>
|
||||
{{/if}}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -13,6 +13,30 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
|
|||
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() {
|
||||
var selected = this.get('selected');
|
||||
if (!selected) return false;
|
||||
|
@ -29,6 +53,8 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
|
|||
|
||||
// Staff accounts can "take action"
|
||||
canTakeAction: function() {
|
||||
if (this.get("flagTopic")) return false;
|
||||
|
||||
// We can only take actions on non-custom flags
|
||||
if (this.get('selected.is_custom_flag')) return false;
|
||||
return Discourse.User.currentProp('staff');
|
||||
|
@ -36,9 +62,9 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
|
|||
|
||||
submitText: function(){
|
||||
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 {
|
||||
return I18n.t("flagging.action");
|
||||
return I18n.t(this.get('flagTopic') ? "flagging_topic.action" : "flagging.action");
|
||||
}
|
||||
}.property('selected.is_custom_flag'),
|
||||
|
||||
|
@ -50,7 +76,12 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
|
|||
|
||||
createFlag: function(opts) {
|
||||
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')} : {};
|
||||
|
||||
if (opts) params = $.extend(params, opts);
|
||||
|
@ -58,6 +89,7 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
|
|||
this.send('hideModal');
|
||||
postAction.act(params).then(function() {
|
||||
self.send('closeModal');
|
||||
if (self.get('flagTopic')) { bootbox.alert(I18n.t('topic.flag_topic.success_message')); }
|
||||
}, function(errors) {
|
||||
self.send('showModal');
|
||||
self.displayErrors(errors);
|
||||
|
@ -70,6 +102,8 @@ Discourse.FlagController = Discourse.ObjectController.extend(Discourse.ModalFunc
|
|||
},
|
||||
|
||||
canDeleteSpammer: function() {
|
||||
if (this.get("flagTopic")) return false;
|
||||
|
||||
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');
|
||||
} else {
|
||||
|
|
|
@ -70,10 +70,11 @@ Discourse.ActionSummary = Discourse.Model.extend({
|
|||
return Discourse.ajax("/post_actions", {
|
||||
type: 'POST',
|
||||
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'),
|
||||
message: opts.message,
|
||||
take_action: opts.takeAction
|
||||
take_action: opts.takeAction,
|
||||
flag_topic: this.get('flagTopic') ? true : false
|
||||
}
|
||||
}).then(null, function (error) {
|
||||
actionSummary.removeAction();
|
||||
|
|
|
@ -30,6 +30,10 @@ Discourse.Site = Discourse.Model.extend({
|
|||
return this.get("postActionByIdLookup.action" + id);
|
||||
},
|
||||
|
||||
topicFlagTypeById: function(id) {
|
||||
return this.get("topicFlagByIdLookup.action" + id);
|
||||
},
|
||||
|
||||
updateCategory: function(newCategory) {
|
||||
var existingCategory = this.get('categories').findProperty('id', Em.get(newCategory, 'id'));
|
||||
if (existingCategory) existingCategory.mergeAttributes(newCategory);
|
||||
|
@ -82,6 +86,16 @@ Discourse.Site.reopenClass(Discourse.Singleton, {
|
|||
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) {
|
||||
result.archetypes = _.map(result.archetypes,function(a) {
|
||||
return Discourse.Archetype.create(a);
|
||||
|
|
|
@ -316,6 +316,26 @@ Discourse.Topic.reopenClass({
|
|||
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
|
||||
|
||||
|
|
|
@ -27,6 +27,12 @@ Discourse.TopicRoute = Discourse.Route.extend({
|
|||
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() {
|
||||
Discourse.Route.showModal(this, 'editTopicAutoClose', this.modelFor('topic'));
|
||||
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({
|
||||
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() {
|
||||
var flagView = this;
|
||||
|
|
|
@ -20,7 +20,6 @@ Discourse.TopicFooterButtonsView = Discourse.ContainerView.extend({
|
|||
var topic = this.get('topic');
|
||||
if (Discourse.User.current()) {
|
||||
if (!topic.get('isPrivateMessage')) {
|
||||
|
||||
// We hide some controls from private messages
|
||||
if (this.get('topic.details.can_invite_to')) {
|
||||
this.attachViewClass(Discourse.InviteReplyButton);
|
||||
|
@ -28,6 +27,9 @@ Discourse.TopicFooterButtonsView = Discourse.ContainerView.extend({
|
|||
this.attachViewClass(Discourse.StarButton);
|
||||
this.attachViewClass(Discourse.ShareButton);
|
||||
this.attachViewClass(Discourse.ClearPinButton);
|
||||
if (this.get('topic.details.can_flag_topic')) {
|
||||
this.attachViewClass(Discourse.FlagTopicButton);
|
||||
}
|
||||
}
|
||||
this.attachViewClass(Discourse.ReplyButton);
|
||||
this.attachViewClass(Discourse.NotificationsButton);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
//= require ./discourse/controllers/controller
|
||||
//= require ./discourse/controllers/object_controller
|
||||
//= require ./discourse/views/modal/modal_body_view
|
||||
//= require ./discourse/views/modal/flag_view
|
||||
//= require ./discourse/views/combobox_view
|
||||
//= require ./discourse/views/buttons/button_view
|
||||
//= require ./discourse/views/buttons/dropdown_button_view
|
||||
|
|
|
@ -369,11 +369,11 @@ table {
|
|||
}
|
||||
td { vertical-align: top; }
|
||||
th { text-align: left; }
|
||||
.user { width: 40px; }
|
||||
.user { width: 40px; padding-top: 12px; }
|
||||
.excerpt {
|
||||
max-width: 740px;
|
||||
width: 740px;
|
||||
padding: 0 10px 10px 0;
|
||||
padding: 8px;
|
||||
word-wrap: break-word;
|
||||
.fa,h3 { display: inline-block; }
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ class PostActionsController < ApplicationController
|
|||
args = {}
|
||||
args[:message] = params[:message] if params[:message].present?
|
||||
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)
|
||||
|
||||
|
@ -63,7 +64,18 @@ class PostActionsController < ApplicationController
|
|||
|
||||
def fetch_post_from_params
|
||||
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 ?)
|
||||
finder = finder.with_deleted if current_user.try(:moderator?)
|
||||
|
|
|
@ -12,6 +12,7 @@ class PostAction < ActiveRecord::Base
|
|||
belongs_to :user
|
||||
belongs_to :post_action_type
|
||||
belongs_to :related_post, class_name: 'Post'
|
||||
belongs_to :target_user, class_name: 'User'
|
||||
|
||||
rate_limit :post_action_rate_limiter
|
||||
|
||||
|
@ -128,7 +129,8 @@ class PostAction < ActiveRecord::Base
|
|||
post_action_type_id: post_action_type_id,
|
||||
message: opts[:message],
|
||||
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
|
||||
# can happen despite being .create
|
||||
# since already bookmarked
|
||||
|
@ -319,6 +321,7 @@ end
|
|||
# staff_took_action :boolean default(FALSE), not null
|
||||
# defer :boolean
|
||||
# defer_by :integer
|
||||
# targets_topic :boolean default(FALSE)
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
|
|
@ -28,6 +28,10 @@ class PostActionType < ActiveRecord::Base
|
|||
@notify_flag_type_ids ||= types.only(:off_topic, :spam, :inappropriate, :notify_moderators).values
|
||||
end
|
||||
|
||||
def topic_flag_types
|
||||
@topic_flag_types ||= types.only(:spam, :inappropriate, :notify_moderators)
|
||||
end
|
||||
|
||||
def is_flag?(sym)
|
||||
flag_types.valid?(sym)
|
||||
end
|
||||
|
|
|
@ -17,6 +17,10 @@ class Site
|
|||
PostActionType.ordered
|
||||
end
|
||||
|
||||
def topic_flag_types
|
||||
post_action_types.where(name_key: ['inappropriate', 'spam', 'notify_moderators'])
|
||||
end
|
||||
|
||||
def notification_types
|
||||
Notification.types
|
||||
end
|
||||
|
|
|
@ -12,6 +12,7 @@ class SiteSerializer < ApplicationSerializer
|
|||
|
||||
has_many :categories, serializer: BasicCategorySerializer, 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 :archetypes, embed: :objects, serializer: ArchetypeSerializer
|
||||
|
||||
|
@ -31,7 +32,7 @@ class SiteSerializer < ApplicationSerializer
|
|||
def periods
|
||||
TopTopic.periods.map(&:to_s)
|
||||
end
|
||||
|
||||
|
||||
def top_menu_items
|
||||
Discourse.top_menu_items.map(&:to_s)
|
||||
end
|
||||
|
|
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,
|
||||
:highest_post_number,
|
||||
:last_read_post_number,
|
||||
:deleted_by
|
||||
:deleted_by,
|
||||
:actions_summary
|
||||
|
||||
# Define a delegator for each attribute of the topic we want
|
||||
attributes *topic_attributes
|
||||
|
@ -97,6 +98,7 @@ class TopicViewSerializer < ApplicationSerializer
|
|||
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_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
|
||||
end
|
||||
|
||||
|
@ -144,5 +146,17 @@ class TopicViewSerializer < ApplicationSerializer
|
|||
PinnedCheck.new(object.topic, object.topic_user).pinned?
|
||||
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
|
||||
|
|
|
@ -788,6 +788,11 @@ en:
|
|||
title: 'Share'
|
||||
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..."
|
||||
|
||||
invite_private:
|
||||
|
@ -1077,6 +1082,11 @@ en:
|
|||
more: "{{n}} to go..."
|
||||
left: "{{n}} remaining"
|
||||
|
||||
flagging_topic:
|
||||
title: "Why are you privately flagging this topic?"
|
||||
action: "Flag Topic"
|
||||
notify_action: "Notify"
|
||||
|
||||
topic_map:
|
||||
title: "Topic Summary"
|
||||
links_shown: "show all {{totalLinks}} links..."
|
||||
|
@ -1239,12 +1249,16 @@ en:
|
|||
disagree: "Disagree"
|
||||
disagree_title: "Disagree with flag, remove any flags from this post"
|
||||
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"
|
||||
system: "System"
|
||||
error: "Something went wrong"
|
||||
view_message: "Reply"
|
||||
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:
|
||||
action_type_3:
|
||||
|
|
|
@ -360,6 +360,22 @@ en:
|
|||
description: 'Vote 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:
|
||||
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>'
|
||||
|
|
|
@ -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