From 5667478b4d164c93ae90982240e6421cef8a672e Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Tue, 6 Jan 2015 14:53:12 -0500 Subject: [PATCH] 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. --- .../discourse/controllers/composer.js.es6 | 5 +- .../javascripts/discourse/models/_post.js | 15 +--- .../javascripts/discourse/models/composer.js | 86 +++++++++++++------ .../javascripts/discourse/models/topic.js | 6 ++ app/controllers/posts_controller.rb | 6 +- 5 files changed, 76 insertions(+), 42 deletions(-) diff --git a/app/assets/javascripts/discourse/controllers/composer.js.es6 b/app/assets/javascripts/discourse/controllers/composer.js.es6 index 4dc72a310b4..aaa43a740cf 100644 --- a/app/assets/javascripts/discourse/controllers/composer.js.es6 +++ b/app/assets/javascripts/discourse/controllers/composer.js.es6 @@ -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. diff --git a/app/assets/javascripts/discourse/models/_post.js b/app/assets/javascripts/discourse/models/_post.js index 9745c357a96..686e8230ff9 100644 --- a/app/assets/javascripts/discourse/models/_post.js +++ b/app/assets/javascripts/discourse/models/_post.js @@ -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 diff --git a/app/assets/javascripts/discourse/models/composer.js b/app/assets/javascripts/discourse/models/composer.js index 6b166a8ab93..1e25f9b67b2 100644 --- a/app/assets/javascripts/discourse/models/composer.js +++ b/app/assets/javascripts/discourse/models/composer.js @@ -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, diff --git a/app/assets/javascripts/discourse/models/topic.js b/app/assets/javascripts/discourse/models/topic.js index a6c904ba793..57fe8d907a7 100644 --- a/app/assets/javascripts/discourse/models/topic.js +++ b/app/assets/javascripts/discourse/models/topic.js @@ -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. diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index a62d4c0596c..746549962c7 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -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