mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 08:49:06 +08:00
Interface for reviewing queued posts
This commit is contained in:
parent
f1ede42569
commit
96d2c5069b
|
@ -0,0 +1,16 @@
|
|||
export default Ember.Controller.extend({
|
||||
|
||||
actions: {
|
||||
approve(post) {
|
||||
post.update({ state: 'approved' }).then(() => {
|
||||
this.get('model').removeObject(post);
|
||||
});
|
||||
},
|
||||
|
||||
reject(post) {
|
||||
post.update({ state: 'rejected' }).then(() => {
|
||||
this.get('model').removeObject(post);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
import registerUnbound from 'discourse/helpers/register-unbound';
|
||||
|
||||
registerUnbound('cook-text', function(text) {
|
||||
return new Handlebars.SafeString(Discourse.Markdown.cook(text));
|
||||
});
|
||||
|
|
@ -93,4 +93,6 @@ export default function() {
|
|||
this.resource('badges', function() {
|
||||
this.route('show', {path: '/:id/:slug'});
|
||||
});
|
||||
|
||||
this.resource('queued-posts', { path: '/queued-posts' });
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import DiscourseRoute from 'discourse/routes/discourse';
|
||||
|
||||
export default DiscourseRoute.extend({
|
||||
model() {
|
||||
return this.store.find('queuedPost', {status: 'new'});
|
||||
}
|
||||
});
|
||||
|
30
app/assets/javascripts/discourse/templates/queued-posts.hbs
Normal file
30
app/assets/javascripts/discourse/templates/queued-posts.hbs
Normal file
|
@ -0,0 +1,30 @@
|
|||
<div class='container'>
|
||||
<div class='queued-posts'>
|
||||
{{#each post in model}}
|
||||
<div class='queued-post'>
|
||||
{{#if post.title}}
|
||||
<h4 class='title'>{{post.title}}</h4>
|
||||
{{/if}}
|
||||
<div class='poster'>
|
||||
{{avatar post.user imageSize="large"}}
|
||||
</div>
|
||||
<div class='cooked'>
|
||||
<div class='names'>
|
||||
<span class='username'>{{post.user.username}}</span>
|
||||
</div>
|
||||
<div class='clearfix'></div>
|
||||
|
||||
{{{cook-text post.raw}}}
|
||||
|
||||
<div class='queue-controls'>
|
||||
{{d-button action="approve" actionParam=post label="queue.approve" icon="check" class="btn-primary approve"}}
|
||||
{{d-button action="reject" actionParam=post label="queue.reject" icon="times" class="btn-warning reject"}}
|
||||
</div>
|
||||
</div>
|
||||
<div class='clearfix'></div>
|
||||
</div>
|
||||
{{else}}
|
||||
<p>{{i18n "queue.none"}}</p>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
|
@ -28,12 +28,12 @@
|
|||
|
||||
{{#if currentUser.staff}}
|
||||
<li>
|
||||
<a href="/queued-posts">
|
||||
{{#link-to 'queued-posts'}}
|
||||
{{i18n "queue.title"}}
|
||||
{{#if currentUser.post_queue_new_count}}
|
||||
<span class='badge-notification flagged-posts'>{{currentUser.post_queue_new_count}}</span>
|
||||
{{/if}}
|
||||
</a>
|
||||
{{/link-to}}
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
@import "desktop/upload";
|
||||
@import "desktop/user";
|
||||
@import "desktop/history";
|
||||
@import "desktop/queued-posts";
|
||||
|
||||
/* These files doesn't actually exist, they are injected by DiscourseSassImporter. */
|
||||
|
||||
|
|
20
app/assets/stylesheets/desktop/queued-posts.scss
Normal file
20
app/assets/stylesheets/desktop/queued-posts.scss
Normal file
|
@ -0,0 +1,20 @@
|
|||
.queued-posts {
|
||||
.queued-post {
|
||||
padding: 1em 0;
|
||||
|
||||
.poster {
|
||||
width: 70px;
|
||||
float: left;
|
||||
}
|
||||
.cooked {
|
||||
width: $topic-body-width;
|
||||
float: left;
|
||||
}
|
||||
h4.title {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
border-bottom: 1px solid darken(scale-color-diff(), 10%);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,11 +7,4 @@ class Admin::AdminController < ApplicationController
|
|||
render nothing: true
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# this is not really necessary cause the routes are secure
|
||||
def ensure_staff
|
||||
raise Discourse::InvalidAccess.new unless current_user.staff?
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -371,6 +371,10 @@ class ApplicationController < ActionController::Base
|
|||
raise Discourse::NotLoggedIn.new unless current_user.present?
|
||||
end
|
||||
|
||||
def ensure_staff
|
||||
raise Discourse::InvalidAccess.new unless current_user && current_user.staff?
|
||||
end
|
||||
|
||||
def redirect_to_login_if_required
|
||||
return if current_user || (request.format.json? && api_key_valid?)
|
||||
|
||||
|
|
20
app/controllers/queued_posts_controller.rb
Normal file
20
app/controllers/queued_posts_controller.rb
Normal file
|
@ -0,0 +1,20 @@
|
|||
require_dependency 'queued_post_serializer'
|
||||
|
||||
class QueuedPostsController < ApplicationController
|
||||
|
||||
before_filter :ensure_staff
|
||||
|
||||
def index
|
||||
state = QueuedPost.states[(params[:state] || 'new').to_sym]
|
||||
state ||= QueuedPost.states[:new]
|
||||
|
||||
@queued_posts = QueuedPost.where(state: state)
|
||||
render_serialized(@queued_posts, QueuedPostSerializer, root: :queued_posts)
|
||||
end
|
||||
|
||||
def update
|
||||
qp = QueuedPost.where(id: params[:id]).first
|
||||
render_serialized(qp, QueuedPostSerializer, root: :queued_posts)
|
||||
end
|
||||
|
||||
end
|
|
@ -30,7 +30,7 @@ class QueuedPost < ActiveRecord::Base
|
|||
where(state: states[:new]).count
|
||||
end
|
||||
|
||||
def self.publish_new!
|
||||
def self.broadcast_new!
|
||||
msg = { post_queue_new_count: QueuedPost.new_count }
|
||||
MessageBus.publish('/queue_counts', msg, user_ids: User.staff.pluck(:id))
|
||||
end
|
||||
|
@ -60,10 +60,14 @@ class QueuedPost < ActiveRecord::Base
|
|||
created_post
|
||||
end
|
||||
|
||||
def self.all_attributes_for(queue)
|
||||
[QueuedPost.attributes_by_queue[:base], QueuedPost.attributes_by_queue[queue.to_sym]].flatten.compact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def post_attributes
|
||||
[QueuedPost.attributes_by_queue[:base], QueuedPost.attributes_by_queue[queue.to_sym]].flatten.compact
|
||||
QueuedPost.all_attributes_for(queue)
|
||||
end
|
||||
|
||||
def change_to!(state, changed_by)
|
||||
|
@ -83,7 +87,7 @@ class QueuedPost < ActiveRecord::Base
|
|||
updates.each {|k, v| send("#{k}=", v) }
|
||||
changes_applied
|
||||
|
||||
QueuedPost.publish_new!
|
||||
QueuedPost.broadcast_new!
|
||||
end
|
||||
|
||||
end
|
||||
|
|
14
app/serializers/queued_post_serializer.rb
Normal file
14
app/serializers/queued_post_serializer.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
class QueuedPostSerializer < ApplicationSerializer
|
||||
attributes :id,
|
||||
:queue,
|
||||
:user_id,
|
||||
:state,
|
||||
:topic_id,
|
||||
:approved_by_id,
|
||||
:rejected_by_id,
|
||||
:raw,
|
||||
:post_options,
|
||||
:created_at
|
||||
|
||||
has_one :user, serializer: BasicUserSerializer, embed: :object
|
||||
end
|
|
@ -226,7 +226,10 @@ en:
|
|||
placeholder: "type the topic title here"
|
||||
|
||||
queue:
|
||||
approve: 'Approve Post'
|
||||
reject: 'Reject Post'
|
||||
title: "Needs Approval"
|
||||
none: "There are no posts to review."
|
||||
|
||||
approval:
|
||||
title: "Post Needs Approval"
|
||||
|
|
|
@ -454,6 +454,9 @@ Discourse::Application.routes.draw do
|
|||
get "/posts/:id/raw-email" => "posts#raw_email"
|
||||
get "raw/:topic_id(/:post_number)" => "posts#markdown_num"
|
||||
|
||||
resources :queued_posts, constraints: StaffConstraint.new
|
||||
get 'queued-posts' => 'queued_posts#index'
|
||||
|
||||
resources :invites do
|
||||
collection do
|
||||
get "upload" => "invites#check_csv_chunk"
|
||||
|
|
|
@ -21,7 +21,7 @@ class NewPostManager
|
|||
|
||||
def initialize(user, args)
|
||||
@user = user
|
||||
@args = args
|
||||
@args = args.delete_if {|_, v| v.nil?}
|
||||
end
|
||||
|
||||
def perform
|
||||
|
@ -41,10 +41,16 @@ class NewPostManager
|
|||
def enqueue(queue)
|
||||
result = NewPostResult.new(:enqueued)
|
||||
enqueuer = PostEnqueuer.new(@user, queue)
|
||||
post = enqueuer.enqueue(@args)
|
||||
|
||||
QueuedPost.publish_new! if post && post.errors.empty?
|
||||
queued_args = {post_options: @args.dup}
|
||||
queued_args[:raw] = queued_args[:post_options].delete(:raw)
|
||||
queued_args[:topic_id] = queued_args[:post_options].delete(:topic_id)
|
||||
|
||||
post = enqueuer.enqueue(queued_args)
|
||||
|
||||
QueuedPost.broadcast_new! if post && post.errors.empty?
|
||||
|
||||
result.queued_post = post
|
||||
result.check_errors_from(enqueuer)
|
||||
result
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ class NewPostResult
|
|||
|
||||
attr_reader :action
|
||||
attr_accessor :post
|
||||
attr_accessor :queued_post
|
||||
|
||||
def initialize(action, success=false)
|
||||
@action = action
|
||||
|
|
|
@ -32,7 +32,7 @@ describe NewPostManager do
|
|||
result
|
||||
end
|
||||
|
||||
@queue_handler = -> (manager) { manager.args[:raw] =~ /queue me/ ? manager.enqueue('test') : nil }
|
||||
@queue_handler = -> (manager) { manager.args[:raw] =~ /queue me/ ? manager.enqueue('new_topic') : nil }
|
||||
|
||||
NewPostManager.add_handler(&@counter_handler)
|
||||
NewPostManager.add_handler(&@queue_handler)
|
||||
|
@ -56,10 +56,14 @@ describe NewPostManager do
|
|||
end
|
||||
|
||||
it "calls custom enqueuing handlers" do
|
||||
manager = NewPostManager.new(topic.user, raw: 'to the handler I say enqueue me!', topic_id: topic.id)
|
||||
manager = NewPostManager.new(topic.user, raw: 'to the handler I say enqueue me!', title: 'this is the title of the queued post')
|
||||
|
||||
result = manager.perform
|
||||
|
||||
enqueued = result.queued_post
|
||||
|
||||
expect(enqueued).to be_present
|
||||
expect(enqueued.post_options['title']).to eq('this is the title of the queued post')
|
||||
expect(result.action).to eq(:enqueued)
|
||||
expect(result).to be_success
|
||||
expect(result.post).to be_blank
|
||||
|
|
28
spec/controllers/queued_posts_controller_spec.rb
Normal file
28
spec/controllers/queued_posts_controller_spec.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
require 'spec_helper'
|
||||
|
||||
describe QueuedPostsController do
|
||||
context 'without authentication' do
|
||||
it 'fails' do
|
||||
xhr :get, :index
|
||||
expect(response).not_to be_success
|
||||
end
|
||||
end
|
||||
|
||||
context 'as a regular user' do
|
||||
let!(:user) { log_in(:user) }
|
||||
it 'fails' do
|
||||
xhr :get, :index
|
||||
expect(response).not_to be_success
|
||||
end
|
||||
end
|
||||
|
||||
context 'as an admin' do
|
||||
let!(:user) { log_in(:moderator) }
|
||||
|
||||
it 'returns the queued posts' do
|
||||
xhr :get, :index
|
||||
expect(response).to be_success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
29
test/javascripts/acceptance/queued-posts-test.js.es6
Normal file
29
test/javascripts/acceptance/queued-posts-test.js.es6
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { acceptance } from "helpers/qunit-helpers";
|
||||
|
||||
acceptance("Queued Posts", { loggedIn: true });
|
||||
|
||||
test("approve a post", () => {
|
||||
visit("/queued-posts");
|
||||
|
||||
andThen(() => {
|
||||
ok(exists('.queued-post'), 'it has posts listed');
|
||||
});
|
||||
|
||||
click('.queued-post:eq(0) button.approve');
|
||||
andThen(() => {
|
||||
ok(!exists('.queued-post'), 'it removes the post');
|
||||
});
|
||||
});
|
||||
|
||||
test("reject a post", () => {
|
||||
visit("/queued-posts");
|
||||
|
||||
andThen(() => {
|
||||
ok(exists('.queued-post'), 'it has posts listed');
|
||||
});
|
||||
|
||||
click('.queued-post:eq(0) button.reject');
|
||||
andThen(() => {
|
||||
ok(!exists('.queued-post'), 'it removes the post');
|
||||
});
|
||||
});
|
|
@ -94,6 +94,16 @@ export default function() {
|
|||
return response({});
|
||||
});
|
||||
|
||||
this.put('/queued_posts/:queued_post_id', function(request) {
|
||||
return response({ queued_post: {id: request.params.queued_post_id } });
|
||||
});
|
||||
|
||||
this.get('/queued_posts', function() {
|
||||
return response({
|
||||
queued_posts: [{id: 1}]
|
||||
});
|
||||
});
|
||||
|
||||
this.post('/session', function(request) {
|
||||
const data = parsePostData(request.requestBody);
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user