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'),
self = this;
// Clear the warning state if we're not showing the checkbox anymore
if (!this.get('showWarning')) {
this.set('model.isWarning', false);
@ -344,8 +343,8 @@ export default DiscourseController.extend({
if (composerModel.get('composeState') === Discourse.Composer.DRAFT &&
composerModel.get('draftKey') === opts.draftKey) {
composerModel.set('composeState', Discourse.Composer.OPEN);
return resolve();
composerModel.set('composeState', Discourse.Composer.OPEN);
return resolve();
}
// If it's a different draft, cancel it and try opening again.

View File

@ -134,6 +134,7 @@ Discourse.Post = Discourse.Model.extend({
save: function(complete, error) {
var self = this;
if (!this.get('newPost')) {
// We're updating a post
return Discourse.ajax("/posts/" + (this.get('id')), {
type: 'PUT',
@ -155,17 +156,9 @@ Discourse.Post = Discourse.Model.extend({
} else {
// We're saving a post
var data = {
raw: this.get('raw'),
topic_id: this.get('topic_id'),
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 data = this.getProperties(Discourse.Composer.serializedFieldsForCreate());
data.reply_to_post_number = this.get('reply_to_post_number');
data.image_sizes = this.get('imageSizes');
var metaData = this.get('metaData');
// 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',
SAVING = 'saving',
OPEN = 'open',
@ -17,7 +8,23 @@ var CLOSED = 'closed',
PRIVATE_MESSAGE = 'privateMessage',
REPLY = 'reply',
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({
@ -410,10 +417,11 @@ Discourse.Composer = Discourse.Model.extend({
// If we are editing a post, load it.
if (opts.action === EDIT && opts.post) {
this.setProperties({
title: this.get('topic.title'),
loading: true
});
var topicProps = this.serialize(_edit_topic_serializer);
topicProps.loading = true;
this.setProperties(topicProps);
Discourse.Post.load(opts.post.get('id')).then(function(result) {
composer.setProperties({
@ -463,10 +471,10 @@ Discourse.Composer = Discourse.Model.extend({
// Update the title if we've changed it
if (this.get('title') && post.get('post_number') === 1) {
Discourse.Topic.update(this.get('topic'), {
title: this.get('title'),
category_id: this.get('categoryId')
});
var topicProps = this.getProperties(Object.keys(_edit_topic_serializer));
Discourse.Topic.update(this.get('topic'), topicProps);
}
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
createPost: function(opts) {
var post = this.get('post'),
@ -504,11 +527,6 @@ Discourse.Composer = Discourse.Model.extend({
// Build the post object
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,
cooked: this.getCookedHtml(),
reply_count: 0,
@ -517,17 +535,17 @@ Discourse.Composer = Discourse.Model.extend({
user_id: currentUser.get('id'),
uploaded_avatar_id: currentUser.get('uploaded_avatar_id'),
user_custom_fields: currentUser.get('custom_fields'),
archetype: this.get('archetypeId'),
post_type: Discourse.Site.currentProp('post_types.regular'),
target_usernames: this.get('targetUsernames'),
actions_summary: Em.A(),
actions_summary: [],
moderator: currentUser.get('moderator'),
admin: currentUser.get('admin'),
yours: true,
newPost: true,
});
if(post) {
this.serialize(_create_serializer, createdPost);
if (post) {
createdPost.setProperties({
reply_to_post_number: post.get('post_number'),
reply_to_user: {
@ -698,6 +716,20 @@ Discourse.Composer.reopenClass({
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
CLOSED: CLOSED,
SAVING: SAVING,

View File

@ -371,6 +371,12 @@ Discourse.Topic.reopenClass({
update: function(topic, 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
// allows us to make a distinction between arrays that were not
// sent and arrays that we specifically want to be empty.

View File

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