/** A data model representing a post in a topic @class Post @extends Discourse.Model @namespace Discourse @module Discourse **/ Discourse.Post = Discourse.Model.extend({ url: (function() { return Discourse.Utilities.postUrl(this.get('topic.slug') || this.get('topic_slug'), this.get('topic_id'), this.get('post_number')); }).property('post_number', 'topic_id', 'topic.slug'), originalPostUrl: (function() { return "/t/" + (this.get('topic_id')) + "/" + (this.get('reply_to_post_number')); }).property('reply_to_post_number'), showUserReplyTab: (function() { return this.get('reply_to_user') && (this.get('reply_to_post_number') < (this.get('post_number') - 1)); }).property('reply_to_user', 'reply_to_post_number', 'post_number'), firstPost: (function() { if (this.get('bestOfFirst') === true) return true; return this.get('post_number') === 1; }).property('post_number'), hasHistory: (function() { return this.get('version') > 1; }).property('version'), postElementId: (function() { return "post_" + (this.get('post_number')); }).property(), // The class for the read icon of the post. It starts with read-icon then adds 'seen' or // 'last-read' if the post has been seen or is the highest post number seen so far respectively. bookmarkClass: (function() { var result, topic; result = 'read-icon'; if (this.get('bookmarked')) return result + ' bookmarked'; topic = this.get('topic'); if (topic && topic.get('last_read_post_number') === this.get('post_number')) { result += ' last-read'; } else { if (this.get('read')) { result += ' seen'; } } return result; }).property('read', 'topic.last_read_post_number', 'bookmarked'), // Custom tooltips for the bookmark icons bookmarkTooltip: (function() { var topic; if (this.get('bookmarked')) return Em.String.i18n('bookmarks.created'); if (!this.get('read')) return ""; topic = this.get('topic'); if (topic && topic.get('last_read_post_number') === this.get('post_number')) { return Em.String.i18n('bookmarks.last_read'); } return Em.String.i18n('bookmarks.not_bookmarked'); }).property('read', 'topic.last_read_post_number', 'bookmarked'), bookmarkedChanged: (function() { var _this = this; return $.ajax({ url: "/posts/" + (this.get('id')) + "/bookmark", type: 'PUT', data: { bookmarked: this.get('bookmarked') ? true : false }, error: function(error) { var errors; errors = $.parseJSON(error.responseText).errors; bootbox.alert(errors[0]); return _this.toggleProperty('bookmarked'); } }); }).observes('bookmarked'), internalLinks: (function() { if (this.blank('link_counts')) return null; return this.get('link_counts').filterProperty('internal').filterProperty('title'); }).property('link_counts.@each.internal'), // Edits are the version - 1, so version 2 = 1 edit editCount: (function() { return this.get('version') - 1; }).property('version'), historyHeat: (function() { var rightNow, updatedAt, updatedAtDate; if (!(updatedAt = this.get('updated_at'))) return; rightNow = new Date().getTime(); // Show heat on age updatedAtDate = Date.create(updatedAt).getTime(); if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 12)) return 'heatmap-high'; if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 24)) return 'heatmap-med'; if (updatedAtDate > (rightNow - 60 * 60 * 1000 * 48)) return 'heatmap-low'; }).property('updated_at'), flagsAvailable: (function() { var _this = this; return Discourse.get('site.flagTypes').filter(function(item) { return _this.get("actionByName." + (item.get('name_key')) + ".can_act"); }); }).property('Discourse.site.flagTypes', 'actions_summary.@each.can_act'), actionsHistory: (function() { if (!this.present('actions_summary')) return null; return this.get('actions_summary').filter(function(i) { if (i.get('count') === 0) return false; if (i.get('users') && i.get('users').length > 0) return true; return !i.get('hidden'); }); }).property('actions_summary.@each.users', 'actions_summary.@each.count'), // Save a post and call the callback when done. save: function(complete, error) { var data, metaData; if (!this.get('newPost')) { // We're updating a post return $.ajax({ url: "/posts/" + (this.get('id')), type: 'PUT', data: { post: { raw: this.get('raw') }, image_sizes: this.get('imageSizes') }, success: function(result) { // If we received a category update, update it if (result.category) Discourse.get('site').updateCategory(result.category); return typeof complete === "function" ? complete(Discourse.Post.create(result.post)) : void 0; }, error: function(result) { return typeof error === "function" ? error(result) : void 0; } }); } else { // We're saving a post data = { post: this.getProperties('raw', 'topic_id', 'reply_to_post_number', 'category'), archetype: this.get('archetype'), title: this.get('title'), image_sizes: this.get('imageSizes'), target_usernames: this.get('target_usernames') }; // Put the metaData into the request if (metaData = this.get('metaData')) { data.meta_data = {}; Ember.keys(metaData).forEach(function(key) { data.meta_data[key] = metaData.get(key); }); } return $.ajax({ type: 'POST', url: "/posts", data: data, success: function(result) { return typeof complete === "function" ? complete(Discourse.Post.create(result)) : void 0; }, error: function(result) { return typeof error === "function" ? error(result) : void 0; } }); } }, recover: function() { return $.ajax("/posts/" + (this.get('id')) + "/recover", { type: 'PUT', cache: false }); }, "delete": function(complete) { return $.ajax("/posts/" + (this.get('id')), { type: 'DELETE', success: function(result) { return typeof complete === "function" ? complete() : void 0; } }); }, // Update the properties of this post from an obj, ignoring cooked as we should already // have that rendered. updateFromSave: function(obj) { var lookup, _this = this; if (!obj) { return; } Object.each(obj, function(key, val) { if (key === 'actions_summary') { return false; } if (val) { return _this.set(key, val); } }); // Rebuild actions summary this.set('actions_summary', Em.A()); if (obj.actions_summary) { lookup = Em.Object.create(); obj.actions_summary.each(function(a) { var actionSummary; a.post = _this; a.actionType = Discourse.get("site").postActionTypeById(a.id); actionSummary = Discourse.ActionSummary.create(a); _this.get('actions_summary').pushObject(actionSummary); return lookup.set(a.actionType.get('name_key'), actionSummary); }); return this.set('actionByName', lookup); } }, // Load replies to this post loadReplies: function() { var promise, _this = this; promise = new RSVP.Promise(); this.set('loadingReplies', true); this.set('replies', []); $.getJSON("/posts/" + (this.get('id')) + "/replies", function(loaded) { loaded.each(function(reply) { var post; post = Discourse.Post.create(reply); post.set('topic', _this.get('topic')); return _this.get('replies').pushObject(post); }); _this.set('loadingReplies', false); return promise.resolve(); }); return promise; }, loadVersions: function(callback) { return $.get("/posts/" + (this.get('id')) + "/versions.json", function(result) { return callback(result); }); }, // Whether to show replies directly below showRepliesBelow: (function() { var reply_count, _ref; reply_count = this.get('reply_count'); // We don't show replies if there aren't any if (reply_count === 0) return false; // Always show replies if the setting `supress_reply_directly_below` is false. if (!Discourse.SiteSettings.supress_reply_directly_below) return true; // Always show replies if there's more than one if (reply_count > 1) return true; // If we have *exactly* one reply, we have to consider if it's directly below us if ((_ref = this.get('topic')) ? _ref.isReplyDirectlyBelow(this) : void 0) return false; return true; }).property('reply_count') }); Discourse.Post.reopenClass({ createActionSummary: function(result) { var lookup; if (result.actions_summary) { lookup = Em.Object.create(); result.actions_summary = result.actions_summary.map(function(a) { var actionSummary; a.post = result; a.actionType = Discourse.get("site").postActionTypeById(a.id); actionSummary = Discourse.ActionSummary.create(a); lookup.set(a.actionType.get('name_key'), actionSummary); return actionSummary; }); return result.set('actionByName', lookup); } }, create: function(obj, topic) { var result; result = this._super(obj); this.createActionSummary(result); if (obj.reply_to_user) { result.set('reply_to_user', Discourse.User.create(obj.reply_to_user)); } result.set('topic', topic); return result; }, deleteMany: function(posts) { return $.ajax("/posts/destroy_many", { type: 'DELETE', data: { post_ids: posts.map(function(p) { return p.get('id'); }) } }); }, loadVersion: function(postId, version, callback) { var _this = this; return $.getJSON("/posts/" + postId + ".json?version=" + version, function(result) { return callback(Discourse.Post.create(result)); }); }, loadByPostNumber: function(topicId, postId, callback) { var _this = this; return $.getJSON("/posts/by_number/" + topicId + "/" + postId + ".json", function(result) { return callback(Discourse.Post.create(result)); }); }, loadQuote: function(postId) { var promise, _this = this; promise = new RSVP.Promise(); $.getJSON("/posts/" + postId + ".json", function(result) { var post; post = Discourse.Post.create(result); return promise.resolve(Discourse.BBCode.buildQuoteBBCode(post, post.get('raw'))); }); return promise; }, load: function(postId, callback) { var _this = this; return $.getJSON("/posts/" + postId + ".json", function(result) { return callback(Discourse.Post.create(result)); }); } });