A common, extensible interface for sending topic columns across the wire

This allows plugins to specify topic columns to serialize and save in
the database via the composer when creating topics and editing their
first posts.
This commit is contained in:
Robin Ward 2015-01-06 14:53:12 -05:00
parent 97b4dec96c
commit 5667478b4d
5 changed files with 76 additions and 42 deletions

View File

@ -135,7 +135,6 @@ export default DiscourseController.extend({
var composer = this.get('model'), var composer = this.get('model'),
self = this; self = this;
// Clear the warning state if we're not showing the checkbox anymore // Clear the warning state if we're not showing the checkbox anymore
if (!this.get('showWarning')) { if (!this.get('showWarning')) {
this.set('model.isWarning', false); this.set('model.isWarning', false);

View File

@ -134,6 +134,7 @@ Discourse.Post = Discourse.Model.extend({
save: function(complete, error) { save: function(complete, error) {
var self = this; var self = this;
if (!this.get('newPost')) { if (!this.get('newPost')) {
// We're updating a post // We're updating a post
return Discourse.ajax("/posts/" + (this.get('id')), { return Discourse.ajax("/posts/" + (this.get('id')), {
type: 'PUT', type: 'PUT',
@ -155,17 +156,9 @@ Discourse.Post = Discourse.Model.extend({
} else { } else {
// We're saving a post // We're saving a post
var data = { var data = this.getProperties(Discourse.Composer.serializedFieldsForCreate());
raw: this.get('raw'), data.reply_to_post_number = this.get('reply_to_post_number');
topic_id: this.get('topic_id'), data.image_sizes = this.get('imageSizes');
is_warning: this.get('is_warning'),
reply_to_post_number: this.get('reply_to_post_number'),
category: this.get('category'),
archetype: this.get('archetype'),
title: this.get('title'),
image_sizes: this.get('imageSizes'),
target_usernames: this.get('target_usernames'),
};
var metaData = this.get('metaData'); var metaData = this.get('metaData');
// Put the metaData into the request // Put the metaData into the request

View File

@ -1,12 +1,3 @@
/**
A data model for representing the composer's current state
@class Composer
@extends Discourse.Model
@namespace Discourse
@module Discourse
**/
var CLOSED = 'closed', var CLOSED = 'closed',
SAVING = 'saving', SAVING = 'saving',
OPEN = 'open', OPEN = 'open',
@ -17,7 +8,23 @@ var CLOSED = 'closed',
PRIVATE_MESSAGE = 'privateMessage', PRIVATE_MESSAGE = 'privateMessage',
REPLY = 'reply', REPLY = 'reply',
EDIT = 'edit', EDIT = 'edit',
REPLY_AS_NEW_TOPIC_KEY = "reply_as_new_topic"; REPLY_AS_NEW_TOPIC_KEY = "reply_as_new_topic",
// When creating, these fields are moved into the post model from the composer model
_create_serializer = {
raw: 'reply',
title: 'title',
category: 'categoryId',
topic_id: 'topic.id',
is_warning: 'isWarning',
archetype: 'archetypeId',
target_usernames: 'targetUsernames'
},
_edit_topic_serializer = {
title: 'topic.title',
categoryId: 'topic.category.id'
};
Discourse.Composer = Discourse.Model.extend({ Discourse.Composer = Discourse.Model.extend({
@ -410,10 +417,11 @@ Discourse.Composer = Discourse.Model.extend({
// If we are editing a post, load it. // If we are editing a post, load it.
if (opts.action === EDIT && opts.post) { if (opts.action === EDIT && opts.post) {
this.setProperties({
title: this.get('topic.title'), var topicProps = this.serialize(_edit_topic_serializer);
loading: true topicProps.loading = true;
});
this.setProperties(topicProps);
Discourse.Post.load(opts.post.get('id')).then(function(result) { Discourse.Post.load(opts.post.get('id')).then(function(result) {
composer.setProperties({ composer.setProperties({
@ -463,10 +471,10 @@ Discourse.Composer = Discourse.Model.extend({
// Update the title if we've changed it // Update the title if we've changed it
if (this.get('title') && post.get('post_number') === 1) { if (this.get('title') && post.get('post_number') === 1) {
Discourse.Topic.update(this.get('topic'), {
title: this.get('title'), var topicProps = this.getProperties(Object.keys(_edit_topic_serializer));
category_id: this.get('categoryId')
}); Discourse.Topic.update(this.get('topic'), topicProps);
} }
post.setProperties({ post.setProperties({
@ -494,6 +502,21 @@ Discourse.Composer = Discourse.Model.extend({
}); });
}, },
serialize: function(serializer, dest) {
if (!dest) {
dest = {};
}
var self = this;
Object.keys(serializer).forEach(function(f) {
var val = self.get(serializer[f]);
if (typeof val !== 'undefined') {
Ember.set(dest, f, val);
}
});
return dest;
},
// Create a new Post // Create a new Post
createPost: function(opts) { createPost: function(opts) {
var post = this.get('post'), var post = this.get('post'),
@ -504,11 +527,6 @@ Discourse.Composer = Discourse.Model.extend({
// Build the post object // Build the post object
var createdPost = Discourse.Post.create({ var createdPost = Discourse.Post.create({
raw: this.get('reply'),
title: this.get('title'),
category: this.get('categoryId'),
topic_id: this.get('topic.id'),
is_warning: this.get('isWarning'),
imageSizes: opts.imageSizes, imageSizes: opts.imageSizes,
cooked: this.getCookedHtml(), cooked: this.getCookedHtml(),
reply_count: 0, reply_count: 0,
@ -517,17 +535,17 @@ Discourse.Composer = Discourse.Model.extend({
user_id: currentUser.get('id'), user_id: currentUser.get('id'),
uploaded_avatar_id: currentUser.get('uploaded_avatar_id'), uploaded_avatar_id: currentUser.get('uploaded_avatar_id'),
user_custom_fields: currentUser.get('custom_fields'), user_custom_fields: currentUser.get('custom_fields'),
archetype: this.get('archetypeId'),
post_type: Discourse.Site.currentProp('post_types.regular'), post_type: Discourse.Site.currentProp('post_types.regular'),
target_usernames: this.get('targetUsernames'), actions_summary: [],
actions_summary: Em.A(),
moderator: currentUser.get('moderator'), moderator: currentUser.get('moderator'),
admin: currentUser.get('admin'), admin: currentUser.get('admin'),
yours: true, yours: true,
newPost: true, newPost: true,
}); });
if(post) { this.serialize(_create_serializer, createdPost);
if (post) {
createdPost.setProperties({ createdPost.setProperties({
reply_to_post_number: post.get('post_number'), reply_to_post_number: post.get('post_number'),
reply_to_user: { reply_to_user: {
@ -698,6 +716,20 @@ Discourse.Composer.reopenClass({
return composer; return composer;
}, },
serializeToTopic: function(fieldName, property) {
if (!property) { property = fieldName; }
_edit_topic_serializer[fieldName] = property;
},
serializeOnCreate: function(fieldName, property) {
if (!property) { property = fieldName; }
_create_serializer[fieldName] = property;
},
serializedFieldsForCreate: function() {
return Object.keys(_create_serializer);
},
// The status the compose view can have // The status the compose view can have
CLOSED: CLOSED, CLOSED: CLOSED,
SAVING: SAVING, SAVING: SAVING,

View File

@ -371,6 +371,12 @@ Discourse.Topic.reopenClass({
update: function(topic, props) { update: function(topic, props) {
props = JSON.parse(JSON.stringify(props)) || {}; props = JSON.parse(JSON.stringify(props)) || {};
// We support `category_id` and `categoryId` for compatibility
if (typeof props.categoryId !== "undefined") {
props.category_id = props.categoryId;
delete props.categoryId;
}
// Annoyingly, empty arrays are not sent across the wire. This // Annoyingly, empty arrays are not sent across the wire. This
// allows us to make a distinction between arrays that were not // allows us to make a distinction between arrays that were not
// sent and arrays that we specifically want to be empty. // sent and arrays that we specifically want to be empty.

View File

@ -78,6 +78,8 @@ class PostsController < ApplicationController
def create_post(params) def create_post(params)
post_creator = PostCreator.new(current_user, params) post_creator = PostCreator.new(current_user, params)
post = post_creator.create post = post_creator.create
DiscourseEvent.trigger(:topic_saved, post.topic, params)
if post_creator.errors.present? if post_creator.errors.present?
# If the post was spam, flag all the user's posts as spam # If the post was spam, flag all the user's posts as spam
current_user.flag_linked_posts_as_spam if post_creator.spam? current_user.flag_linked_posts_as_spam if post_creator.spam?
@ -400,7 +402,6 @@ class PostsController < ApplicationController
permitted << :embed_url permitted << :embed_url
end end
params.require(:raw) params.require(:raw)
result = params.permit(*permitted).tap do |whitelisted| result = params.permit(*permitted).tap do |whitelisted|
whitelisted[:image_sizes] = params[:image_sizes] whitelisted[:image_sizes] = params[:image_sizes]
@ -414,6 +415,9 @@ class PostsController < ApplicationController
result[:is_warning] = (params[:is_warning] == "true") result[:is_warning] = (params[:is_warning] == "true")
end end
# Enable plugins to whitelist additional parameters they might need
DiscourseEvent.trigger(:permit_post_params, result, params)
result result
end end