REFACTOR: composer model (#7499)
This commit is contained in:
parent
2938e3f033
commit
54c2f24ee9
|
@ -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"),
|
},
|
||||||
|
|
||||||
composerTime: function() {
|
@computed
|
||||||
let total = this.get("composerTotalOpened") || 0,
|
composerTime: {
|
||||||
oldOpen = this.get("composerOpened");
|
get() {
|
||||||
if (oldOpen) {
|
let total = this.composerTotalOpened || 0;
|
||||||
total += new Date() - oldOpen;
|
const oldOpen = this.composerOpened;
|
||||||
|
|
||||||
|
if (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({
|
||||||
|
|
Loading…
Reference in New Issue