Merge pull request #4206 from techAPJ/convert-topic
FEATURE: move a topic from PM to regular topic or vice versa
This commit is contained in:
commit
82daf93eb3
|
@ -545,6 +545,14 @@ export default Ember.Controller.extend(SelectedPostsCount, BufferedContent, {
|
||||||
changePostOwner(post) {
|
changePostOwner(post) {
|
||||||
this.get('selectedPosts').addObject(post);
|
this.get('selectedPosts').addObject(post);
|
||||||
this.send('changeOwner');
|
this.send('changeOwner');
|
||||||
|
},
|
||||||
|
|
||||||
|
convertToPublicTopic() {
|
||||||
|
this.get('content').convertTopic("public");
|
||||||
|
},
|
||||||
|
|
||||||
|
convertToPrivateMessage() {
|
||||||
|
this.get('content').convertTopic("private");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { propertyEqual } from 'discourse/lib/computed';
|
||||||
import { longDate } from 'discourse/lib/formatter';
|
import { longDate } from 'discourse/lib/formatter';
|
||||||
import computed from 'ember-addons/ember-computed-decorators';
|
import computed from 'ember-addons/ember-computed-decorators';
|
||||||
import ActionSummary from 'discourse/models/action-summary';
|
import ActionSummary from 'discourse/models/action-summary';
|
||||||
|
import { popupAjaxError } from 'discourse/lib/ajax-error';
|
||||||
|
|
||||||
export function loadTopicView(topic, args) {
|
export function loadTopicView(topic, args) {
|
||||||
const topicId = topic.get('id');
|
const topicId = topic.get('id');
|
||||||
|
@ -446,8 +447,13 @@ const Topic = RestModel.extend({
|
||||||
}).finally(()=>this.set('archiving', false));
|
}).finally(()=>this.set('archiving', false));
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
}
|
},
|
||||||
|
|
||||||
|
convertTopic(type) {
|
||||||
|
return Discourse.ajax(`/t/${this.get('id')}/convert-topic/${type}`, {type: 'PUT'}).then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
}).catch(popupAjaxError);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Topic.reopenClass({
|
Topic.reopenClass({
|
||||||
|
|
|
@ -256,6 +256,16 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
{{#if currentUser.admin}}
|
||||||
|
<li class="topic-admin-convert">
|
||||||
|
{{#if model.isPrivateMessage}}
|
||||||
|
{{d-button action="convertToPublicTopic" icon="comment" label="topic.actions.make_public"}}
|
||||||
|
{{else}}
|
||||||
|
{{d-button action="convertToPrivateMessage" icon="envelope" label="topic.actions.make_private"}}
|
||||||
|
{{/if}}
|
||||||
|
</li>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{plugin-outlet "topic-admin-menu-buttons"}}
|
{{plugin-outlet "topic-admin-menu-buttons"}}
|
||||||
{{/popup-menu}}
|
{{/popup-menu}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -20,7 +20,9 @@ const icons = {
|
||||||
'visible.disabled': 'eye-slash',
|
'visible.disabled': 'eye-slash',
|
||||||
'split_topic': 'sign-out',
|
'split_topic': 'sign-out',
|
||||||
'invited_user': 'plus-circle',
|
'invited_user': 'plus-circle',
|
||||||
'removed_user': 'minus-circle'
|
'removed_user': 'minus-circle',
|
||||||
|
'public_topic': 'comment',
|
||||||
|
'private_topic': 'envelope'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createWidget('post-small-action', {
|
export default createWidget('post-small-action', {
|
||||||
|
|
|
@ -27,6 +27,7 @@ class TopicsController < ApplicationController
|
||||||
:change_timestamps,
|
:change_timestamps,
|
||||||
:archive_message,
|
:archive_message,
|
||||||
:move_to_inbox,
|
:move_to_inbox,
|
||||||
|
:convert_topic,
|
||||||
:bookmark]
|
:bookmark]
|
||||||
|
|
||||||
before_filter :consider_user_for_promotion, only: :show
|
before_filter :consider_user_for_promotion, only: :show
|
||||||
|
@ -510,6 +511,22 @@ class TopicsController < ApplicationController
|
||||||
render nothing: true
|
render nothing: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def convert_topic
|
||||||
|
params.require(:id)
|
||||||
|
params.require(:type)
|
||||||
|
topic = Topic.find_by(id: params[:id])
|
||||||
|
guardian.ensure_can_convert_topic!(topic)
|
||||||
|
|
||||||
|
if params[:type] == "public"
|
||||||
|
converted_topic = topic.convert_to_public_topic(current_user)
|
||||||
|
else
|
||||||
|
converted_topic = topic.convert_to_private_message(current_user)
|
||||||
|
end
|
||||||
|
render_topic_changes(converted_topic)
|
||||||
|
rescue ActiveRecord::RecordInvalid => ex
|
||||||
|
render_json_error(ex)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def toggle_mute
|
def toggle_mute
|
||||||
|
|
|
@ -1047,6 +1047,18 @@ SQL
|
||||||
[result].flatten unless result.blank?
|
[result].flatten unless result.blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def convert_to_public_topic(user)
|
||||||
|
public_topic = TopicConverter.new(self, user).convert_to_public_topic
|
||||||
|
add_small_action(user, "public_topic") if public_topic
|
||||||
|
public_topic
|
||||||
|
end
|
||||||
|
|
||||||
|
def convert_to_private_message(user)
|
||||||
|
private_topic = TopicConverter.new(self, user).convert_to_private_message
|
||||||
|
add_small_action(user, "private_topic") if private_topic
|
||||||
|
private_topic
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def update_category_topic_count_by(num)
|
def update_category_topic_count_by(num)
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
class TopicConverter
|
||||||
|
|
||||||
|
attr_reader :topic
|
||||||
|
|
||||||
|
def initialize(topic, user)
|
||||||
|
@topic = topic
|
||||||
|
@user = user
|
||||||
|
end
|
||||||
|
|
||||||
|
def convert_to_public_topic
|
||||||
|
Topic.transaction do
|
||||||
|
@topic.category_id = SiteSetting.allow_uncategorized_topics ? SiteSetting.uncategorized_category_id : Category.where(read_restricted: false).first.id
|
||||||
|
@topic.archetype = Archetype.default
|
||||||
|
@topic.save
|
||||||
|
update_user_stats
|
||||||
|
watch_topic(topic)
|
||||||
|
end
|
||||||
|
@topic
|
||||||
|
end
|
||||||
|
|
||||||
|
def convert_to_private_message
|
||||||
|
Topic.transaction do
|
||||||
|
@topic.category_id = nil
|
||||||
|
@topic.archetype = Archetype.private_message
|
||||||
|
add_allowed_users
|
||||||
|
@topic.save
|
||||||
|
watch_topic(topic)
|
||||||
|
end
|
||||||
|
@topic
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def update_user_stats
|
||||||
|
@topic.posts.where(deleted_at: nil).each do |p|
|
||||||
|
user = User.find(p.user_id)
|
||||||
|
# update posts count
|
||||||
|
user.user_stat.post_count += 1
|
||||||
|
user.user_stat.save!
|
||||||
|
end
|
||||||
|
# update topics count
|
||||||
|
@topic.user.user_stat.topic_count += 1
|
||||||
|
@topic.user.user_stat.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_allowed_users
|
||||||
|
@topic.posts.where(deleted_at: nil).each do |p|
|
||||||
|
user = User.find(p.user_id)
|
||||||
|
@topic.topic_allowed_users.build(user_id: user.id) unless @topic.topic_allowed_users.where(user_id: user.id).exists?
|
||||||
|
# update posts count
|
||||||
|
user.user_stat.post_count -= 1
|
||||||
|
user.user_stat.save!
|
||||||
|
end
|
||||||
|
@topic.topic_allowed_users.build(user_id: @user.id)
|
||||||
|
# update topics count
|
||||||
|
@topic.user.user_stat.topic_count -= 1
|
||||||
|
@topic.user.user_stat.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def watch_topic(topic)
|
||||||
|
@topic.notifier.watch_topic!(topic.user_id)
|
||||||
|
|
||||||
|
@topic.topic_allowed_users(true).each do |tau|
|
||||||
|
next if tau.user_id == -1 || tau.user_id == topic.user_id
|
||||||
|
topic.notifier.watch!(tau.user_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -121,6 +121,8 @@ en:
|
||||||
email: 'send this link in an email'
|
email: 'send this link in an email'
|
||||||
|
|
||||||
action_codes:
|
action_codes:
|
||||||
|
public_topic: "made this topic public %{when}"
|
||||||
|
private_topic: "made this topic private %{when}"
|
||||||
split_topic: "split this topic %{when}"
|
split_topic: "split this topic %{when}"
|
||||||
invited_user: "invited %{who} %{when}"
|
invited_user: "invited %{who} %{when}"
|
||||||
removed_user: "removed %{who} %{when}"
|
removed_user: "removed %{who} %{when}"
|
||||||
|
@ -1344,6 +1346,8 @@ en:
|
||||||
invisible: "Make Unlisted"
|
invisible: "Make Unlisted"
|
||||||
visible: "Make Listed"
|
visible: "Make Listed"
|
||||||
reset_read: "Reset Read Data"
|
reset_read: "Reset Read Data"
|
||||||
|
make_public: "Make Public Topic"
|
||||||
|
make_private: "Make Private Message"
|
||||||
|
|
||||||
feature:
|
feature:
|
||||||
pin: "Pin Topic"
|
pin: "Pin Topic"
|
||||||
|
@ -2983,4 +2987,3 @@ en:
|
||||||
top: "There are no more top topics."
|
top: "There are no more top topics."
|
||||||
bookmarks: "There are no more bookmarked topics."
|
bookmarks: "There are no more bookmarked topics."
|
||||||
search: "There are no more search results."
|
search: "There are no more search results."
|
||||||
|
|
||||||
|
|
|
@ -486,6 +486,7 @@ Discourse::Application.routes.draw do
|
||||||
delete "t/:id" => "topics#destroy"
|
delete "t/:id" => "topics#destroy"
|
||||||
put "t/:id/archive-message" => "topics#archive_message"
|
put "t/:id/archive-message" => "topics#archive_message"
|
||||||
put "t/:id/move-to-inbox" => "topics#move_to_inbox"
|
put "t/:id/move-to-inbox" => "topics#move_to_inbox"
|
||||||
|
put "t/:id/convert-topic/:type" => "topics#convert_topic"
|
||||||
put "topics/bulk"
|
put "topics/bulk"
|
||||||
put "topics/reset-new" => 'topics#reset_new'
|
put "topics/reset-new" => 'topics#reset_new'
|
||||||
post "topics/timings"
|
post "topics/timings"
|
||||||
|
|
|
@ -58,6 +58,10 @@ module TopicGuardian
|
||||||
!Discourse.static_doc_topic_ids.include?(topic.id)
|
!Discourse.static_doc_topic_ids.include?(topic.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def can_convert_topic?(topic)
|
||||||
|
topic && !topic.trashed? && is_admin?
|
||||||
|
end
|
||||||
|
|
||||||
def can_reply_as_new_topic?(topic)
|
def can_reply_as_new_topic?(topic)
|
||||||
authenticated? && topic && not(topic.private_message?) && @user.has_trust_level?(TrustLevel[1])
|
authenticated? && topic && not(topic.private_message?) && @user.has_trust_level?(TrustLevel[1])
|
||||||
end
|
end
|
||||||
|
|
|
@ -872,6 +872,24 @@ describe Guardian do
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'can_convert_topic?' do
|
||||||
|
it 'returns false with a nil object' do
|
||||||
|
expect(Guardian.new(user).can_convert_topic?(nil)).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false when not logged in' do
|
||||||
|
expect(Guardian.new.can_convert_topic?(topic)).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false when not admin' do
|
||||||
|
expect(Guardian.new(moderator).can_convert_topic?(topic)).to be_falsey
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns true when an admin' do
|
||||||
|
expect(Guardian.new(admin).can_convert_topic?(topic)).to be_truthy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'can_edit?' do
|
describe 'can_edit?' do
|
||||||
|
|
||||||
it 'returns false with a nil object' do
|
it 'returns false with a nil object' do
|
||||||
|
|
|
@ -1235,4 +1235,63 @@ describe TopicsController do
|
||||||
expect(response.headers['X-Robots-Tag']).to eq(nil)
|
expect(response.headers['X-Robots-Tag']).to eq(nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "convert_topic" do
|
||||||
|
it 'needs you to be logged in' do
|
||||||
|
expect { xhr :put, :convert_topic, id: 111, type: "private" }.to raise_error(Discourse::NotLoggedIn)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'converting public topic to private message' do
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
let(:topic) { Fabricate(:topic, user: user) }
|
||||||
|
|
||||||
|
it "raises an error when the user doesn't have permission to convert topic" do
|
||||||
|
log_in
|
||||||
|
xhr :put, :convert_topic, id: topic.id, type: "private"
|
||||||
|
expect(response).to be_forbidden
|
||||||
|
end
|
||||||
|
|
||||||
|
context "success" do
|
||||||
|
before do
|
||||||
|
admin = log_in(:admin)
|
||||||
|
Topic.any_instance.expects(:convert_to_private_message).with(admin).returns(topic)
|
||||||
|
xhr :put, :convert_topic, id: topic.id, type: "private"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns success" do
|
||||||
|
expect(response).to be_success
|
||||||
|
result = ::JSON.parse(response.body)
|
||||||
|
expect(result['success']).to eq(true)
|
||||||
|
expect(result['url']).to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'converting private message to public topic' do
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
let(:topic) { Fabricate(:topic, user: user) }
|
||||||
|
|
||||||
|
it "raises an error when the user doesn't have permission to convert topic" do
|
||||||
|
log_in
|
||||||
|
xhr :put, :convert_topic, id: topic.id, type: "public"
|
||||||
|
expect(response).to be_forbidden
|
||||||
|
end
|
||||||
|
|
||||||
|
context "success" do
|
||||||
|
before do
|
||||||
|
admin = log_in(:admin)
|
||||||
|
Topic.any_instance.expects(:convert_to_public_topic).with(admin).returns(topic)
|
||||||
|
xhr :put, :convert_topic, id: topic.id, type: "public"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns success" do
|
||||||
|
expect(response).to be_success
|
||||||
|
result = ::JSON.parse(response.body)
|
||||||
|
expect(result['success']).to eq(true)
|
||||||
|
expect(result['url']).to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe TopicConverter do
|
||||||
|
|
||||||
|
context 'convert_to_public_topic' do
|
||||||
|
let(:admin) { Fabricate(:admin) }
|
||||||
|
let(:author) { Fabricate(:user) }
|
||||||
|
let(:private_message) { Fabricate(:private_message_topic, user: author) }
|
||||||
|
|
||||||
|
context 'success' do
|
||||||
|
it "converts private message to regular topic" do
|
||||||
|
topic = private_message.convert_to_public_topic(admin)
|
||||||
|
expect(topic).to be_valid
|
||||||
|
expect(topic.archetype).to eq("regular")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "updates user stats" do
|
||||||
|
topic_user = TopicUser.create!(user_id: author.id, topic_id: private_message.id, posted: true)
|
||||||
|
expect(private_message.user.user_stat.topic_count).to eq(0)
|
||||||
|
private_message.convert_to_public_topic(admin)
|
||||||
|
expect(private_message.reload.user.user_stat.topic_count).to eq(1)
|
||||||
|
expect(topic_user.reload.notification_level).to eq(TopicUser.notification_levels[:watching])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'convert_to_private_message' do
|
||||||
|
let(:admin) { Fabricate(:admin) }
|
||||||
|
let(:author) { Fabricate(:user) }
|
||||||
|
let(:topic) { Fabricate(:topic, user: author) }
|
||||||
|
|
||||||
|
context 'success' do
|
||||||
|
it "converts regular topic to private message" do
|
||||||
|
private_message = topic.convert_to_private_message(admin)
|
||||||
|
expect(private_message).to be_valid
|
||||||
|
expect(topic.archetype).to eq("private_message")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "updates user stats" do
|
||||||
|
Fabricate(:post, topic: topic, user: author)
|
||||||
|
topic_user = TopicUser.create!(user_id: author.id, topic_id: topic.id, posted: true)
|
||||||
|
author.user_stat.topic_count = 1
|
||||||
|
author.user_stat.save
|
||||||
|
expect(topic.user.user_stat.topic_count).to eq(1)
|
||||||
|
topic.convert_to_private_message(admin)
|
||||||
|
|
||||||
|
expect(topic.reload.topic_allowed_users.where(user_id: author.id).count).to eq(1)
|
||||||
|
expect(topic.reload.user.user_stat.topic_count).to eq(0)
|
||||||
|
expect(topic_user.reload.notification_level).to eq(TopicUser.notification_levels[:watching])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'topic has replies' do
|
||||||
|
before do
|
||||||
|
@replied_user = Fabricate(:coding_horror)
|
||||||
|
create_post(topic: topic, user: @replied_user)
|
||||||
|
topic.reload
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds users who replied to topic in Private Message' do
|
||||||
|
topic.convert_to_private_message(admin)
|
||||||
|
|
||||||
|
expect(topic.reload.topic_allowed_users.where(user_id: @replied_user.id).count).to eq(1)
|
||||||
|
expect(topic.reload.user.user_stat.post_count).to eq(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue