REFACTOR: composer model (#7499)

This commit is contained in:
Joffrey JAFFEUX 2019-05-08 16:53:12 +02:00 committed by GitHub
parent 2938e3f033
commit 54c2f24ee9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 204 additions and 243 deletions

View File

@ -3,8 +3,13 @@ import Topic from "discourse/models/topic";
import { throwAjaxError } from "discourse/lib/ajax-error"; import { throwAjaxError } from "discourse/lib/ajax-error";
import Quote from "discourse/lib/quote"; import Quote from "discourse/lib/quote";
import Draft from "discourse/models/draft"; import Draft from "discourse/models/draft";
import computed from "ember-addons/ember-computed-decorators"; import {
default as computed,
observes,
on
} from "ember-addons/ember-computed-decorators";
import { escapeExpression, tinyAvatar } from "discourse/lib/utilities"; import { escapeExpression, tinyAvatar } from "discourse/lib/utilities";
import { propertyNotEqual } from "discourse/lib/computed";
// The actions the composer can take // The actions the composer can take
export const CREATE_TOPIC = "createTopic", export const CREATE_TOPIC = "createTopic",
@ -77,13 +82,9 @@ const Composer = RestModel.extend({
draftSaving: false, draftSaving: false,
draftSaved: false, draftSaved: false,
@computed archetypes: Ember.computed.reads("site.archetypes"),
archetypes() {
return this.site.get("archetypes");
},
@computed("action") sharedDraft: Ember.computed.equal("action", CREATE_SHARED_DRAFT),
sharedDraft: action => action === CREATE_SHARED_DRAFT,
@computed @computed
categoryId: { categoryId: {
@ -104,6 +105,7 @@ const Composer = RestModel.extend({
if (oldCategoryId !== categoryId) { if (oldCategoryId !== categoryId) {
this.applyTopicTemplate(oldCategoryId, categoryId); this.applyTopicTemplate(oldCategoryId, categoryId);
} }
return categoryId; return categoryId;
} }
}, },
@ -115,8 +117,8 @@ const Composer = RestModel.extend({
@computed("category") @computed("category")
minimumRequiredTags(category) { minimumRequiredTags(category) {
return category && category.get("minimum_required_tags") > 0 return category && category.minimum_required_tags > 0
? category.get("minimum_required_tags") ? category.minimum_required_tags
: null; : null;
}, },
@ -128,15 +130,14 @@ const Composer = RestModel.extend({
@computed("privateMessage", "archetype.hasOptions") @computed("privateMessage", "archetype.hasOptions")
showCategoryChooser(isPrivateMessage, hasOptions) { showCategoryChooser(isPrivateMessage, hasOptions) {
const manyCategories = this.site.get("categories").length > 1; const manyCategories = this.site.categories.length > 1;
return !isPrivateMessage && (hasOptions || manyCategories); return !isPrivateMessage && (hasOptions || manyCategories);
}, },
@computed("creatingPrivateMessage", "topic") @computed("creatingPrivateMessage", "topic")
privateMessage(creatingPrivateMessage, topic) { privateMessage(creatingPrivateMessage, topic) {
return ( return (
creatingPrivateMessage || creatingPrivateMessage || (topic && topic.archetype === "private_message")
(topic && topic.get("archetype") === "private_message")
); );
}, },
@ -152,52 +153,56 @@ const Composer = RestModel.extend({
viewFullscreen: Ember.computed.equal("composeState", FULLSCREEN), viewFullscreen: Ember.computed.equal("composeState", FULLSCREEN),
viewOpenOrFullscreen: Ember.computed.or("viewOpen", "viewFullscreen"), viewOpenOrFullscreen: Ember.computed.or("viewOpen", "viewFullscreen"),
composeStateChanged: function() { @observes("composeState")
let oldOpen = this.get("composerOpened"), composeStateChanged() {
elem = $("html"); const oldOpen = this.composerOpened;
const elem = document.querySelector("html");
if (this.get("composeState") === FULLSCREEN) { if (this.composeState === FULLSCREEN) {
elem.addClass("fullscreen-composer"); elem.classList.add("fullscreen-composer");
} else { } else {
elem.removeClass("fullscreen-composer"); elem.classList.remove("fullscreen-composer");
} }
if (this.get("composeState") === OPEN) { if (this.composeState === OPEN) {
this.set("composerOpened", oldOpen || new Date()); this.set("composerOpened", oldOpen || new Date());
} else { } else {
if (oldOpen) { if (oldOpen) {
let oldTotal = this.get("composerTotalOpened") || 0; const oldTotal = this.composerTotalOpened || 0;
this.set("composerTotalOpened", oldTotal + (new Date() - oldOpen)); this.set("composerTotalOpened", oldTotal + (new Date() - oldOpen));
} }
this.set("composerOpened", null); this.set("composerOpened", null);
} }
}.observes("composeState"), },
@computed
composerTime: {
get() {
let total = this.composerTotalOpened || 0;
const oldOpen = this.composerOpened;
composerTime: function() {
let total = this.get("composerTotalOpened") || 0,
oldOpen = this.get("composerOpened");
if (oldOpen) { if (oldOpen) {
total += new Date() - oldOpen; total += new Date() - oldOpen;
} }
return total; return total;
} }
.property() },
.volatile(),
@computed("archetypeId") @computed("archetypeId")
archetype(archetypeId) { archetype(archetypeId) {
return this.get("archetypes").findBy("id", archetypeId); return this.archetypes.findBy("id", archetypeId);
}, },
archetypeChanged: function() { @observes("archetype")
archetypeChanged() {
return this.set("metaData", Ember.Object.create()); return this.set("metaData", Ember.Object.create());
}.observes("archetype"), },
// view detected user is typing // view detected user is typing
typing: _.throttle( typing: _.throttle(
function() { function() {
let typingTime = this.get("typingTime") || 0; const typingTime = this.typingTime || 0;
this.set("typingTime", typingTime + 100); this.set("typingTime", typingTime + 100);
}, },
100, 100,
@ -229,13 +234,11 @@ const Composer = RestModel.extend({
return false; return false;
} }
const categoryIds = this.site.get( const categoryIds = this.site.topic_featured_link_allowed_category_ids;
"topic_featured_link_allowed_category_ids"
);
if ( if (
!categoryId && !categoryId &&
categoryIds && categoryIds &&
(categoryIds.indexOf(this.site.get("uncategorized_category_id")) !== -1 || (categoryIds.indexOf(this.site.uncategorized_category_id) !== -1 ||
!this.siteSettings.allow_uncategorized_topics) !this.siteSettings.allow_uncategorized_topics)
) { ) {
return true; return true;
@ -248,15 +251,15 @@ const Composer = RestModel.extend({
}, },
@computed("canEditTopicFeaturedLink") @computed("canEditTopicFeaturedLink")
titlePlaceholder() { titlePlaceholder(canEditTopicFeaturedLink) {
return this.get("canEditTopicFeaturedLink") return canEditTopicFeaturedLink
? "composer.title_or_link_placeholder" ? "composer.title_or_link_placeholder"
: "composer.title_placeholder"; : "composer.title_placeholder";
}, },
@computed("action", "post", "topic", "topic.title") @computed("action", "post", "topic", "topic.title")
replyOptions(action, post, topic, topicTitle) { replyOptions(action, post, topic, topicTitle) {
let options = { const options = {
userLink: null, userLink: null,
topicLink: null, topicLink: null,
postLink: null, postLink: null,
@ -266,14 +269,14 @@ const Composer = RestModel.extend({
if (topic) { if (topic) {
options.topicLink = { options.topicLink = {
href: topic.get("url"), href: topic.url,
anchor: topic.get("fancy_title") || escapeExpression(topicTitle) anchor: topic.fancy_title || escapeExpression(topicTitle)
}; };
} }
if (post) { if (post) {
options.label = I18n.t(`post.${action}`); options.label = I18n.t(`post.${action}`);
options.userAvatar = tinyAvatar(post.get("avatar_template")); options.userAvatar = tinyAvatar(post.avatar_template);
if (!this.site.mobileView) { if (!this.site.mobileView) {
const originalUserName = post.get("reply_to_user.username"); const originalUserName = post.get("reply_to_user.username");
@ -288,16 +291,16 @@ const Composer = RestModel.extend({
} }
if (topic && post) { if (topic && post) {
const postNumber = post.get("post_number"); const postNumber = post.post_number;
options.postLink = { options.postLink = {
href: `${topic.get("url")}/${postNumber}`, href: `${topic.url}/${postNumber}`,
anchor: I18n.t("post.post_number", { number: postNumber }) anchor: I18n.t("post.post_number", { number: postNumber })
}; };
options.userLink = { options.userLink = {
href: `${topic.get("url")}/${postNumber}`, href: `${topic.url}/${postNumber}`,
anchor: post.get("username") anchor: post.username
}; };
} }
@ -307,7 +310,7 @@ const Composer = RestModel.extend({
@computed @computed
isStaffUser() { isStaffUser() {
const currentUser = Discourse.User.current(); const currentUser = Discourse.User.current();
return currentUser && currentUser.get("staff"); return currentUser && currentUser.staff;
}, },
@computed( @computed(
@ -342,13 +345,13 @@ const Composer = RestModel.extend({
// title is required when // title is required when
// - creating a new topic/private message // - creating a new topic/private message
// - editing the 1st post // - editing the 1st post
if (canEditTitle && !this.get("titleLengthValid")) return true; if (canEditTitle && !this.titleLengthValid) return true;
// reply is always required // reply is always required
if (missingReplyCharacters > 0) return true; if (missingReplyCharacters > 0) return true;
if ( if (
this.site.get("can_tag_topics") && this.site.can_tag_topics &&
!isStaffUser && !isStaffUser &&
topicFirstPost && topicFirstPost &&
minimumRequiredTags minimumRequiredTags
@ -359,14 +362,14 @@ const Composer = RestModel.extend({
} }
} }
if (this.get("privateMessage")) { if (this.privateMessage) {
// need at least one user when sending a PM // need at least one user when sending a PM
return ( return (
targetUsernames && (targetUsernames.trim() + ",").indexOf(",") === 0 targetUsernames && (targetUsernames.trim() + ",").indexOf(",") === 0
); );
} else { } else {
// has a category? (when needed) // has a category? (when needed)
return this.get("requiredCategoryMissing"); return this.requiredCategoryMissing;
} }
}, },
@ -381,51 +384,25 @@ const Composer = RestModel.extend({
@computed("minimumTitleLength", "titleLength", "post.static_doc") @computed("minimumTitleLength", "titleLength", "post.static_doc")
titleLengthValid(minTitleLength, titleLength, staticDoc) { titleLengthValid(minTitleLength, titleLength, staticDoc) {
if (this.user.get("admin") && staticDoc && titleLength > 0) return true; if (this.user.admin && staticDoc && titleLength > 0) return true;
if (titleLength < minTitleLength) return false; if (titleLength < minTitleLength) return false;
return titleLength <= this.siteSettings.max_topic_title_length; return titleLength <= this.siteSettings.max_topic_title_length;
}, },
@computed("metaData") @computed("metaData")
hasMetaData(metaData) { hasMetaData(metaData) {
return metaData ? Ember.isEmpty(Ember.keys(this.get("metaData"))) : false; return metaData ? Ember.isEmpty(Ember.keys(metaData)) : false;
}, },
/** replyDirty: propertyNotEqual("reply", "originalText"),
Did the user make changes to the reply?
@property replyDirty titleDirty: propertyNotEqual("title", "originalTitle"),
**/
@computed("reply", "originalText")
replyDirty(reply, originalText) {
return reply !== originalText;
},
/**
Did the user make changes to the topic title?
@property titleDirty
**/
@computed("title", "originalTitle")
titleDirty(title, originalTitle) {
return title !== originalTitle;
},
/**
Number of missing characters in the title until valid.
@property missingTitleCharacters
**/
@computed("minimumTitleLength", "titleLength") @computed("minimumTitleLength", "titleLength")
missingTitleCharacters(minimumTitleLength, titleLength) { missingTitleCharacters(minimumTitleLength, titleLength) {
return minimumTitleLength - titleLength; return minimumTitleLength - titleLength;
}, },
/**
Minimum number of characters for a title to be valid.
@property minimumTitleLength
**/
@computed("privateMessage") @computed("privateMessage")
minimumTitleLength(privateMessage) { minimumTitleLength(privateMessage) {
if (privateMessage) { if (privateMessage) {
@ -443,18 +420,13 @@ const Composer = RestModel.extend({
) { ) {
if ( if (
this.get("post.post_type") === this.site.get("post_types.small_action") || this.get("post.post_type") === this.site.get("post_types.small_action") ||
(canEditTopicFeaturedLink && this.get("featuredLink")) (canEditTopicFeaturedLink && this.featuredLink)
) { ) {
return 0; return 0;
} }
return minimumPostLength - replyLength; return minimumPostLength - replyLength;
}, },
/**
Minimum number of characters for a post body to be valid.
@property minimumPostLength
**/
@computed("privateMessage", "topicFirstPost", "topic.pm_with_non_human_user") @computed("privateMessage", "topicFirstPost", "topic.pm_with_non_human_user")
minimumPostLength(privateMessage, topicFirstPost, pmWithNonHumanUser) { minimumPostLength(privateMessage, topicFirstPost, pmWithNonHumanUser) {
if (pmWithNonHumanUser) { if (pmWithNonHumanUser) {
@ -469,22 +441,12 @@ const Composer = RestModel.extend({
} }
}, },
/**
Computes the length of the title minus non-significant whitespaces
@property titleLength
**/
@computed("title") @computed("title")
titleLength(title) { titleLength(title) {
title = title || ""; title = title || "";
return title.replace(/\s+/gim, " ").trim().length; return title.replace(/\s+/gim, " ").trim().length;
}, },
/**
Computes the length of the reply minus the quote(s) and non-significant whitespaces
@property replyLength
**/
@computed("reply") @computed("reply")
replyLength(reply) { replyLength(reply) {
reply = reply || ""; reply = reply || "";
@ -494,18 +456,13 @@ const Composer = RestModel.extend({
return reply.replace(/\s+/gim, " ").trim().length; return reply.replace(/\s+/gim, " ").trim().length;
}, },
_setupComposer: function() { @on("init")
this.set("archetypeId", this.site.get("default_archetype")); _setupComposer() {
}.on("init"), this.set("archetypeId", this.site.default_archetype);
},
/**
Append text to the current reply
@method appendText
@param {String} text the text to append
**/
appendText(text, position, opts) { appendText(text, position, opts) {
const reply = this.get("reply") || ""; const reply = this.reply || "";
position = typeof position === "number" ? position : reply.length; position = typeof position === "number" ? position : reply.length;
let before = reply.slice(0, position) || ""; let before = reply.slice(0, position) || "";
@ -547,24 +504,26 @@ const Composer = RestModel.extend({
}, },
prependText(text, opts) { prependText(text, opts) {
const reply = this.get("reply") || ""; const reply = this.reply || "";
if (opts && opts.new_line && reply.length > 0) { if (opts && opts.new_line && reply.length > 0) {
text = text.trim() + "\n\n"; text = text.trim() + "\n\n";
} }
this.set("reply", text + reply); this.set("reply", text + reply);
}, },
applyTopicTemplate(oldCategoryId, categoryId) { applyTopicTemplate(oldCategoryId, categoryId) {
if (this.get("action") !== CREATE_TOPIC) { if (this.action !== CREATE_TOPIC) {
return; return;
} }
let reply = this.get("reply");
let reply = this.reply;
// If the user didn't change the template, clear it // If the user didn't change the template, clear it
if (oldCategoryId) { if (oldCategoryId) {
const oldCat = this.site.categories.findBy("id", oldCategoryId); const oldCat = this.site.categories.findBy("id", oldCategoryId);
if (oldCat && oldCat.get("topic_template") === reply) { if (oldCat && oldCat.topic_template === reply) {
reply = ""; reply = "";
} }
} }
@ -572,9 +531,10 @@ const Composer = RestModel.extend({
if (!Ember.isEmpty(reply)) { if (!Ember.isEmpty(reply)) {
return; return;
} }
const category = this.site.categories.findBy("id", categoryId); const category = this.site.categories.findBy("id", categoryId);
if (category) { if (category) {
this.set("reply", category.get("topic_template") || ""); this.set("reply", category.topic_template || "");
} }
}, },
@ -591,21 +551,25 @@ const Composer = RestModel.extend({
if (!opts) opts = {}; if (!opts) opts = {};
this.set("loading", false); this.set("loading", false);
const replyBlank = Ember.isEmpty(this.get("reply")); const replyBlank = Ember.isEmpty(this.reply);
const composer = this; const composer = this;
if ( if (
!replyBlank && !replyBlank &&
((opts.reply || isEdit(opts.action)) && this.get("replyDirty")) ((opts.reply || isEdit(opts.action)) && this.replyDirty)
) { ) {
return; return;
} }
if (opts.action === REPLY && isEdit(this.get("action"))) if (opts.action === REPLY && isEdit(this.action)) {
this.set("reply", ""); this.set("reply", "");
}
if (!opts.draftKey) throw new Error("draft key is required"); if (!opts.draftKey) throw new Error("draft key is required");
if (opts.draftSequence === null)
if (opts.draftSequence === null) {
throw new Error("draft sequence is required"); throw new Error("draft sequence is required");
}
this.setProperties({ this.setProperties({
draftKey: opts.draftKey, draftKey: opts.draftKey,
@ -622,41 +586,40 @@ const Composer = RestModel.extend({
}); });
if (opts.post) { if (opts.post) {
this.set("post", opts.post); this.setProperties({
post: opts.post,
whisper: opts.post.post_type === this.site.post_types.whisper
});
this.set( if (!this.topic) {
"whisper", this.set("topic", opts.post.topic);
opts.post.get("post_type") === this.site.get("post_types.whisper")
);
if (!this.get("topic")) {
this.set("topic", opts.post.get("topic"));
} }
} else { } else {
this.set("post", null); this.set("post", null);
} }
this.setProperties({ this.setProperties({
archetypeId: opts.archetypeId || this.site.get("default_archetype"), archetypeId: opts.archetypeId || this.site.default_archetype,
metaData: opts.metaData ? Ember.Object.create(opts.metaData) : null, metaData: opts.metaData ? Ember.Object.create(opts.metaData) : null,
reply: opts.reply || this.get("reply") || "" reply: opts.reply || this.reply || ""
}); });
// We set the category id separately for topic templates on opening of composer // We set the category id separately for topic templates on opening of composer
this.set("categoryId", opts.categoryId || this.get("topic.category.id")); this.set("categoryId", opts.categoryId || this.get("topic.category.id"));
if (!this.get("categoryId") && this.get("creatingTopic")) { if (!this.categoryId && this.creatingTopic) {
const categories = this.site.get("categories"); const categories = this.site.categories;
if (categories.length === 1) { if (categories.length === 1) {
this.set("categoryId", categories[0].get("id")); this.set("categoryId", categories[0].id);
} }
} }
if (opts.postId) { if (opts.postId) {
this.set("loading", true); this.set("loading", true);
this.store.find("post", opts.postId).then(function(post) {
composer.set("post", post); this.store
composer.set("loading", false); .find("post", opts.postId)
}); .then(post => composer.setProperties({ post, loading: false }));
} }
// If we are editing a post, load it. // If we are editing a post, load it.
@ -670,10 +633,10 @@ const Composer = RestModel.extend({
} }
this.setProperties(topicProps); this.setProperties(topicProps);
this.store.find("post", opts.post.get("id")).then(function(post) { this.store.find("post", opts.post.id).then(post => {
composer.setProperties({ composer.setProperties({
reply: post.get("raw"), reply: post.raw,
originalText: post.get("raw"), originalText: post.raw,
loading: false loading: false
}); });
@ -685,12 +648,14 @@ const Composer = RestModel.extend({
originalText: opts.quote originalText: opts.quote
}); });
} }
if (opts.title) { if (opts.title) {
this.set("title", opts.title); this.set("title", opts.title);
} }
this.set("originalText", opts.draft ? "" : this.get("reply"));
if (this.get("editingFirstPost")) { this.set("originalText", opts.draft ? "" : this.reply);
this.set("originalTitle", this.get("title")); if (this.editingFirstPost) {
this.set("originalTitle", this.title);
} }
if (!isEdit(opts.action) || !opts.post) { if (!isEdit(opts.action) || !opts.post) {
@ -701,23 +666,16 @@ const Composer = RestModel.extend({
}, },
save(opts) { save(opts) {
if (!this.get("cantSubmitPost")) { if (!this.cantSubmitPost) {
// change category may result in some effect for topic featured link // change category may result in some effect for topic featured link
if (!this.get("canEditTopicFeaturedLink")) { if (!this.canEditTopicFeaturedLink) {
this.set("featuredLink", null); this.set("featuredLink", null);
} }
return this.get("editingPost") return this.editingPost ? this.editPost(opts) : this.createPost(opts);
? this.editPost(opts)
: this.createPost(opts);
} }
}, },
/**
Clear any state we have in preparation for a new composition.
@method clearState
**/
clearState() { clearState() {
this.setProperties({ this.setProperties({
originalText: null, originalText: null,
@ -736,27 +694,26 @@ const Composer = RestModel.extend({
}); });
}, },
// When you edit a post
editPost(opts) { editPost(opts) {
let post = this.get("post"); const post = this.post;
let oldCooked = post.get("cooked"); const oldCooked = post.cooked;
let promise = Ember.RSVP.resolve(); let promise = Ember.RSVP.resolve();
// Update the topic if we're editing the first post // Update the topic if we're editing the first post
if ( if (
this.get("title") && this.title &&
post.get("post_number") === 1 && post.post_number === 1 &&
this.get("topic.details.can_edit") this.get("topic.details.can_edit")
) { ) {
const topicProps = this.getProperties( const topicProps = this.getProperties(
Object.keys(_edit_topic_serializer) Object.keys(_edit_topic_serializer)
); );
let topic = this.get("topic"); const topic = this.topic;
// If we're editing a shared draft, keep the original category // If we're editing a shared draft, keep the original category
if (this.get("action") === EDIT_SHARED_DRAFT) { if (this.action === EDIT_SHARED_DRAFT) {
let destinationCategoryId = topicProps.categoryId; const destinationCategoryId = topicProps.categoryId;
promise = promise.then(() => promise = promise.then(() =>
topic.updateDestinationCategory(destinationCategoryId) topic.updateDestinationCategory(destinationCategoryId)
); );
@ -766,8 +723,8 @@ const Composer = RestModel.extend({
} }
const props = { const props = {
raw: this.get("reply"), raw: this.reply,
raw_old: this.get("editConflict") ? null : this.get("originalText"), raw_old: this.editConflict ? null : this.originalText,
edit_reason: opts.editReason, edit_reason: opts.editReason,
image_sizes: opts.imageSizes, image_sizes: opts.imageSizes,
cooked: this.getCookedHtml() cooked: this.getCookedHtml()
@ -775,7 +732,7 @@ const Composer = RestModel.extend({
this.set("composeState", SAVING); this.set("composeState", SAVING);
let rollback = throwAjaxError(error => { const rollback = throwAjaxError(error => {
post.set("cooked", oldCooked); post.set("cooked", oldCooked);
this.set("composeState", OPEN); this.set("composeState", OPEN);
if (error.jqXHR && error.jqXHR.status === 409) { if (error.jqXHR && error.jqXHR.status === 409) {
@ -806,52 +763,44 @@ const Composer = RestModel.extend({
return dest; return dest;
}, },
// Create a new Post
createPost(opts) { createPost(opts) {
const post = this.get("post"), const post = this.post;
topic = this.get("topic"), const topic = this.topic;
user = this.user, const user = this.user;
postStream = this.get("topic.postStream"); const postStream = this.get("topic.postStream");
let addedToStream = false; let addedToStream = false;
const postTypes = this.site.post_types;
const postTypes = this.site.get("post_types"); const postType = this.whisper ? postTypes.whisper : postTypes.regular;
const postType = this.get("whisper")
? postTypes.whisper
: postTypes.regular;
// Build the post object // Build the post object
const createdPost = this.store.createRecord("post", { const createdPost = this.store.createRecord("post", {
imageSizes: opts.imageSizes, imageSizes: opts.imageSizes,
cooked: this.getCookedHtml(), cooked: this.getCookedHtml(),
reply_count: 0, reply_count: 0,
name: user.get("name"), name: user.name,
display_username: user.get("name"), display_username: user.name,
username: user.get("username"), username: user.username,
user_id: user.get("id"), user_id: user.id,
user_title: user.get("title"), user_title: user.title,
avatar_template: user.get("avatar_template"), avatar_template: user.avatar_template,
user_custom_fields: user.get("custom_fields"), user_custom_fields: user.custom_fields,
post_type: postType, post_type: postType,
actions_summary: [], actions_summary: [],
moderator: user.get("moderator"), moderator: user.moderator,
admin: user.get("admin"), admin: user.admin,
yours: true, yours: true,
read: true, read: true,
wiki: false, wiki: false,
typingTime: this.get("typingTime"), typingTime: this.typingTime,
composerTime: this.get("composerTime") composerTime: this.composerTime
}); });
this.serialize(_create_serializer, createdPost); this.serialize(_create_serializer, createdPost);
if (post) { if (post) {
createdPost.setProperties({ createdPost.setProperties({
reply_to_post_number: post.get("post_number"), reply_to_post_number: post.post_number,
reply_to_user: { reply_to_user: post.getProperties("username", "avatar_template")
username: post.get("username"),
avatar_template: post.get("avatar_template")
}
}); });
} }
@ -861,15 +810,17 @@ const Composer = RestModel.extend({
if (postStream) { if (postStream) {
// If it's in reply to another post, increase the reply count // If it's in reply to another post, increase the reply count
if (post) { if (post) {
post.set("reply_count", (post.get("reply_count") || 0) + 1); post.setProperties({
post.set("replies", []); reply_count: (post.reply_count || 0) + 1,
replies: []
});
} }
// We do not stage posts in mobile view, we do not have the "cooked" // We do not stage posts in mobile view, we do not have the "cooked"
// Furthermore calculating cooked is very complicated, especially since // Furthermore calculating cooked is very complicated, especially since
// we would need to handle oneboxes and other bits that are not even in the // we would need to handle oneboxes and other bits that are not even in the
// engine, staging will just cause a blank post to render // engine, staging will just cause a blank post to render
if (!_.isEmpty(createdPost.get("cooked"))) { if (!_.isEmpty(createdPost.cooked)) {
state = postStream.stagePost(createdPost, user); state = postStream.stagePost(createdPost, user);
if (state === "alreadyStaging") { if (state === "alreadyStaging") {
return; return;
@ -878,12 +829,14 @@ const Composer = RestModel.extend({
} }
const composer = this; const composer = this;
composer.set("composeState", SAVING); composer.setProperties({
composer.set("stagedPost", state === "staged" && createdPost); composeState: SAVING,
stagedPost: state === "staged" && createdPost
});
return createdPost return createdPost
.save() .save()
.then(function(result) { .then(result => {
let saving = true; let saving = true;
if (result.responseJson.action === "enqueued") { if (result.responseJson.action === "enqueued") {
@ -913,11 +866,9 @@ const Composer = RestModel.extend({
saving = false; saving = false;
// Update topic_count for the category // Update topic_count for the category
const category = composer.site.get("categories").find(function(x) { const category = composer.site.categories.find(
return ( x => x.id === (parseInt(createdPost.category, 10) || 1)
x.get("id") === (parseInt(createdPost.get("category"), 10) || 1)
); );
});
if (category) category.incrementProperty("topic_count"); if (category) category.incrementProperty("topic_count");
Discourse.notifyPropertyChange("globalNotice"); Discourse.notifyPropertyChange("globalNotice");
} }
@ -934,12 +885,12 @@ const Composer = RestModel.extend({
return result; return result;
}) })
.catch( .catch(
throwAjaxError(function() { throwAjaxError(() => {
if (postStream) { if (postStream) {
postStream.undoPost(createdPost); postStream.undoPost(createdPost);
if (post) { if (post) {
post.set("reply_count", post.get("reply_count") - 1); post.set("reply_count", post.reply_count - 1);
} }
} }
Ember.run.next(() => composer.set("composeState", OPEN)); Ember.run.next(() => composer.set("composeState", OPEN));
@ -948,30 +899,42 @@ const Composer = RestModel.extend({
}, },
getCookedHtml() { getCookedHtml() {
return $("#reply-control .d-editor-preview") const editorPreviewNode = document.querySelector(
.html() "#reply-control .d-editor-preview"
.replace(/<span class="marker"><\/span>/g, ""); );
if (editorPreviewNode) {
return editorPreviewNode.innerHTML.replace(
/<span class="marker"><\/span>/g,
""
);
}
return "";
}, },
saveDraft() { saveDraft() {
// Do not save when drafts are disabled // Do not save when drafts are disabled
if (this.get("disableDrafts")) return; if (this.disableDrafts) return;
if (this.get("canEditTitle")) { if (this.canEditTitle) {
// Save title and/or post body // Save title and/or post body
if (!this.get("title") && !this.get("reply")) return; if (!this.title && !this.reply) return;
if ( if (
this.get("title") && this.title &&
this.get("titleLengthValid") && this.titleLengthValid &&
this.get("reply") && this.reply &&
this.get("replyLength") < this.siteSettings.min_post_length this.replyLength < this.siteSettings.min_post_length
) ) {
return; return;
}
} else { } else {
// Do not save when there is no reply // Do not save when there is no reply
if (!this.get("reply")) return; if (!this.reply) return;
// Do not save when the reply's length is too small // Do not save when the reply's length is too small
if (this.get("replyLength") < this.siteSettings.min_post_length) return; if (this.replyLength < this.siteSettings.min_post_length) return;
} }
this.setProperties({ this.setProperties({
@ -985,27 +948,27 @@ const Composer = RestModel.extend({
this._clearingStatus = null; this._clearingStatus = null;
} }
const data = { const data = this.getProperties(
reply: this.get("reply"), "reply",
action: this.get("action"), "action",
title: this.get("title"), "title",
categoryId: this.get("categoryId"), "categoryId",
postId: this.get("post.id"), "postId",
archetypeId: this.get("archetypeId"), "archetypeId",
whisper: this.get("whisper"), "whisper",
metaData: this.get("metaData"), "metaData",
usernames: this.get("targetUsernames"), "usernames",
composerTime: this.get("composerTime"), "composerTime",
typingTime: this.get("typingTime"), "typingTime",
tags: this.get("tags"), "tags",
noBump: this.get("noBump") "noBump"
}; );
if (this.get("post.id") && !Ember.isEmpty(this.get("originalText"))) { if (this.get("post.id") && !Ember.isEmpty(this.originalText)) {
data["originalText"] = this.get("originalText"); data.originalText = this.originalText;
} }
return Draft.save(this.get("draftKey"), this.get("draftSequence"), data) return Draft.save(this.draftKey, this.draftSequence, data)
.then(result => { .then(result => {
if (result.conflict_user) { if (result.conflict_user) {
this.setProperties({ this.setProperties({
@ -1030,24 +993,22 @@ const Composer = RestModel.extend({
}); });
}, },
dataChanged: function() { @observes("title", "reply")
const draftStatus = this.get("draftStatus"); dataChanged() {
const self = this; const draftStatus = this.draftStatus;
if (draftStatus && !this._clearingStatus) { if (draftStatus && !this._clearingStatus) {
this._clearingStatus = Ember.run.later( this._clearingStatus = Ember.run.later(
this, this,
function() { () => {
self.set("draftStatus", null); this.setProperties({ draftStatus: null, draftConflictUser: null });
self.set("draftConflictUser", null); this._clearingStatus = null;
self._clearingStatus = null; this.setProperties({ draftSaving: false, draftSaved: false });
self.set("draftSaving", false);
self.set("draftSaved", false);
}, },
Ember.Test ? 0 : 1000 Ember.Test ? 0 : 1000
); );
} }
}.observes("title", "reply") }
}); });
Composer.reopenClass({ Composer.reopenClass({