diff --git a/app/assets/javascripts/discourse/app/models/post.js b/app/assets/javascripts/discourse/app/models/post.js index aa9ce098b2b..153b62deaeb 100644 --- a/app/assets/javascripts/discourse/app/models/post.js +++ b/app/assets/javascripts/discourse/app/models/post.js @@ -18,8 +18,99 @@ import User from "discourse/models/user"; import discourseComputed from "discourse-common/utils/decorators"; import I18n from "discourse-i18n"; -const Post = RestModel.extend({ - customShare: null, +export default class Post extends RestModel { + static munge(json) { + if (json.actions_summary) { + const lookup = EmberObject.create(); + + // this area should be optimized, it is creating way too many objects per post + json.actions_summary = json.actions_summary.map((a) => { + a.actionType = Site.current().postActionTypeById(a.id); + a.count = a.count || 0; + const actionSummary = ActionSummary.create(a); + lookup[a.actionType.name_key] = actionSummary; + + if (a.actionType.name_key === "like") { + json.likeAction = actionSummary; + } + return actionSummary; + }); + + json.actionByName = lookup; + } + + if (json && json.reply_to_user) { + json.reply_to_user = User.create(json.reply_to_user); + } + + return json; + } + + static updateBookmark(postId, bookmarked) { + return ajax(`/posts/${postId}/bookmark`, { + type: "PUT", + data: { bookmarked }, + }); + } + + static destroyBookmark(postId) { + return ajax(`/posts/${postId}/bookmark`, { + type: "DELETE", + }); + } + + static deleteMany(post_ids, { agreeWithFirstReplyFlag = true } = {}) { + return ajax("/posts/destroy_many", { + type: "DELETE", + data: { post_ids, agree_with_first_reply_flag: agreeWithFirstReplyFlag }, + }); + } + + static mergePosts(post_ids) { + return ajax("/posts/merge_posts", { + type: "PUT", + data: { post_ids }, + }).catch(popupAjaxError); + } + + static loadRevision(postId, version) { + return ajax(`/posts/${postId}/revisions/${version}.json`).then((result) => + EmberObject.create(result) + ); + } + + static hideRevision(postId, version) { + return ajax(`/posts/${postId}/revisions/${version}/hide`, { + type: "PUT", + }); + } + + static permanentlyDeleteRevisions(postId) { + return ajax(`/posts/${postId}/revisions/permanently_delete`, { + type: "DELETE", + }); + } + + static showRevision(postId, version) { + return ajax(`/posts/${postId}/revisions/${version}/show`, { + type: "PUT", + }); + } + + static loadRawEmail(postId) { + return ajax(`/posts/${postId}/raw-email.json`); + } + + customShare = null; + + @equal("trust_level", 0) new_user; + @equal("post_number", 1) firstPost; + @or("deleted_at", "deletedViaTopic") deleted; + @not("deleted") notDeleted; + @propertyEqual("topic.details.created_by.id", "user_id") topicOwner; + + // Posts can show up as deleted if the topic is deleted + @and("firstPost", "topic.deleted_at") deletedViaTopic; @discourseComputed("url", "customShare") shareUrl(url) { @@ -29,30 +120,22 @@ const Post = RestModel.extend({ const user = User.current(); return resolveShareUrl(url, user); - }, - - new_user: equal("trust_level", 0), - firstPost: equal("post_number", 1), - - // Posts can show up as deleted if the topic is deleted - deletedViaTopic: and("firstPost", "topic.deleted_at"), - deleted: or("deleted_at", "deletedViaTopic"), - notDeleted: not("deleted"), + } @discourseComputed("name", "username") showName(name, username) { return name && name !== username && this.siteSettings.display_name_on_posts; - }, + } @discourseComputed("firstPost", "deleted_by", "topic.deleted_by") postDeletedBy(firstPost, deletedBy, topicDeletedBy) { return firstPost ? topicDeletedBy : deletedBy; - }, + } @discourseComputed("firstPost", "deleted_at", "topic.deleted_at") postDeletedAt(firstPost, deletedAt, topicDeletedAt) { return firstPost ? topicDeletedAt : deletedAt; - }, + } @discourseComputed("post_number", "topic_id", "topic.slug") url(post_number, topic_id, topicSlug) { @@ -61,18 +144,18 @@ const Post = RestModel.extend({ topic_id || this.get("topic.id"), post_number ); - }, + } // Don't drop the /1 @discourseComputed("post_number", "url") urlWithNumber(postNumber, baseUrl) { return postNumber === 1 ? `${baseUrl}/1` : baseUrl; - }, + } @discourseComputed("username") - usernameUrl: userPath, - - topicOwner: propertyEqual("topic.details.created_by.id", "user_id"), + usernameUrl(username) { + return userPath(username); + } updatePostField(field, value) { const data = {}; @@ -81,7 +164,7 @@ const Post = RestModel.extend({ return ajax(`/posts/${this.id}/${field}`, { type: "PUT", data }) .then(() => this.set(field, value)) .catch(popupAjaxError); - }, + } @discourseComputed("link_counts.@each.internal") internalLinks() { @@ -90,7 +173,7 @@ const Post = RestModel.extend({ } return this.link_counts.filterBy("internal").filterBy("title"); - }, + } @discourseComputed("actions_summary.@each.can_act") flagsAvailable() { @@ -103,7 +186,7 @@ const Post = RestModel.extend({ return this.site.flagTypes.filter((item) => this.get(`actionByName.${item.name_key}.can_act`) ); - }, + } @discourseComputed( "siteSettings.use_pg_headlines_for_excerpt", @@ -111,25 +194,25 @@ const Post = RestModel.extend({ ) useTopicTitleHeadline(enabled, title) { return enabled && title; - }, + } @discourseComputed("topic_title_headline") topicTitleHeadline(title) { return fancyTitle(title, this.siteSettings.support_mixed_text_direction); - }, + } afterUpdate(res) { if (res.category) { this.site.updateCategory(res.category); } - }, + } updateProperties() { return { post: { raw: this.raw, edit_reason: this.editReason }, image_sizes: this.imageSizes, }; - }, + } createProperties() { // composer only used once, defer the dependency @@ -148,7 +231,7 @@ const Post = RestModel.extend({ } return data; - }, + } // Expands the first post's content, if embedded and shortened. expand() { @@ -158,7 +241,7 @@ const Post = RestModel.extend({ `
${post.cooked}
` ); }); - }, + } // Recover a deleted post recover() { @@ -192,7 +275,7 @@ const Post = RestModel.extend({ popupAjaxError(error); this.setProperties(initProperties); }); - }, + } /** Changes the state of the post to be deleted. Does not call the server, that should be @@ -231,7 +314,7 @@ const Post = RestModel.extend({ } return promise || Promise.resolve(); - }, + } /** Changes the state of the post to NOT be deleted. Does not call the server. @@ -250,7 +333,7 @@ const Post = RestModel.extend({ user_deleted: false, }); } - }, + } destroy(deletedBy, opts) { return this.setDeletedState(deletedBy).then(() => { @@ -259,7 +342,7 @@ const Post = RestModel.extend({ type: "DELETE", }); }); - }, + } /** Updates a post from another's attributes. This will normally happen when a post is loading but @@ -291,23 +374,23 @@ const Post = RestModel.extend({ } } }); - }, + } expandHidden() { return ajax(`/posts/${this.id}/cooked.json`).then((result) => { this.setProperties({ cooked: result.cooked, cooked_hidden: false }); }); - }, + } rebake() { return ajax(`/posts/${this.id}/rebake`, { type: "PUT" }).catch( popupAjaxError ); - }, + } unhide() { return ajax(`/posts/${this.id}/unhide`, { type: "PUT" }); - }, + } createBookmark(data) { this.setProperties({ @@ -324,12 +407,12 @@ const Post = RestModel.extend({ targetId: this.id, }); this.appEvents.trigger("post-stream:refresh", { id: this.id }); - }, + } deleteBookmark(bookmarked) { this.set("topic.bookmarked", bookmarked); this.clearBookmark(); - }, + } clearBookmark() { this.setProperties({ @@ -344,14 +427,14 @@ const Post = RestModel.extend({ target: "post", targetId: this.id, }); - }, + } updateActionsSummary(json) { if (json && json.id === this.id) { json = Post.munge(json); this.set("actions_summary", json.actions_summary); } - }, + } updateLikeCount(count, userId, eventType) { let ownAction = User.current()?.id === userId; @@ -385,101 +468,15 @@ const Post = RestModel.extend({ Object.assign(this.actionByName["like"], newActionObject); Object.assign(this.likeAction, newActionObject); } - }, + } revertToRevision(version) { return ajax(`/posts/${this.id}/revisions/${version}/revert`, { type: "PUT", }); - }, + } get topicNotificationLevel() { return this.topic.details.notification_level; - }, -}); - -Post.reopenClass({ - munge(json) { - if (json.actions_summary) { - const lookup = EmberObject.create(); - - // this area should be optimized, it is creating way too many objects per post - json.actions_summary = json.actions_summary.map((a) => { - a.actionType = Site.current().postActionTypeById(a.id); - a.count = a.count || 0; - const actionSummary = ActionSummary.create(a); - lookup[a.actionType.name_key] = actionSummary; - - if (a.actionType.name_key === "like") { - json.likeAction = actionSummary; - } - return actionSummary; - }); - - json.actionByName = lookup; - } - - if (json && json.reply_to_user) { - json.reply_to_user = User.create(json.reply_to_user); - } - - return json; - }, - - updateBookmark(postId, bookmarked) { - return ajax(`/posts/${postId}/bookmark`, { - type: "PUT", - data: { bookmarked }, - }); - }, - - destroyBookmark(postId) { - return ajax(`/posts/${postId}/bookmark`, { - type: "DELETE", - }); - }, - - deleteMany(post_ids, { agreeWithFirstReplyFlag = true } = {}) { - return ajax("/posts/destroy_many", { - type: "DELETE", - data: { post_ids, agree_with_first_reply_flag: agreeWithFirstReplyFlag }, - }); - }, - - mergePosts(post_ids) { - return ajax("/posts/merge_posts", { - type: "PUT", - data: { post_ids }, - }).catch(popupAjaxError); - }, - - loadRevision(postId, version) { - return ajax(`/posts/${postId}/revisions/${version}.json`).then((result) => - EmberObject.create(result) - ); - }, - - hideRevision(postId, version) { - return ajax(`/posts/${postId}/revisions/${version}/hide`, { - type: "PUT", - }); - }, - - permanentlyDeleteRevisions(postId) { - return ajax(`/posts/${postId}/revisions/permanently_delete`, { - type: "DELETE", - }); - }, - - showRevision(postId, version) { - return ajax(`/posts/${postId}/revisions/${version}/show`, { - type: "PUT", - }); - }, - - loadRawEmail(postId) { - return ajax(`/posts/${postId}/raw-email.json`); - }, -}); - -export default Post; + } +} diff --git a/app/assets/javascripts/discourse/app/models/topic.js b/app/assets/javascripts/discourse/app/models/topic.js index ecdeae99969..8c8de83a3aa 100644 --- a/app/assets/javascripts/discourse/app/models/topic.js +++ b/app/assets/javascripts/discourse/app/models/topic.js @@ -45,20 +45,277 @@ export function loadTopicView(topic, args) { export const ID_CONSTRAINT = /^\d+$/; let _customLastUnreadUrlCallbacks = []; -const Topic = RestModel.extend({ - message: null, - errorLoading: false, +export default class Topic extends RestModel { + static NotificationLevel = { + WATCHING: 3, + TRACKING: 2, + REGULAR: 1, + MUTED: 0, + }; + + static munge(json) { + // ensure we are not overriding category computed property + delete json.category; + json.bookmarks = json.bookmarks || []; + return json; + } + + static createActionSummary(result) { + if (result.actions_summary) { + const lookup = EmberObject.create(); + result.actions_summary = result.actions_summary.map((a) => { + a.post = result; + a.actionType = Site.current().postActionTypeById(a.id); + const actionSummary = ActionSummary.create(a); + lookup.set(a.actionType.get("name_key"), actionSummary); + return actionSummary; + }); + result.set("actionByName", lookup); + } + } + + static update(topic, props, opts = {}) { + // We support `category_id` and `categoryId` for compatibility + if (typeof props.categoryId !== "undefined") { + props.category_id = props.categoryId; + delete props.categoryId; + } + + // Make sure we never change the category for private messages + if (topic.get("isPrivateMessage")) { + delete props.category_id; + } + + const data = { ...props }; + if (opts.fastEdit) { + data.keep_existing_draft = true; + } + return ajax(topic.get("url"), { + type: "PUT", + data: JSON.stringify(data), + contentType: "application/json", + }).then((result) => { + // The title can be cleaned up server side + props.title = result.basic_topic.title; + props.fancy_title = result.basic_topic.fancy_title; + if (topic.is_shared_draft) { + props.destination_category_id = props.category_id; + delete props.category_id; + } + topic.setProperties(props); + }); + } + + static create() { + const result = super.create.apply(this, arguments); + this.createActionSummary(result); + return result; + } + + // Load a topic, but accepts a set of filters + static find(topicId, opts) { + let url = getURL("/t/") + topicId; + if (opts.nearPost) { + url += `/${opts.nearPost}`; + } + + const data = {}; + if (opts.postsAfter) { + data.posts_after = opts.postsAfter; + } + if (opts.postsBefore) { + data.posts_before = opts.postsBefore; + } + if (opts.trackVisit) { + data.track_visit = true; + } + + // Add username filters if we have them + if (opts.userFilters && opts.userFilters.length > 0) { + data.username_filters = []; + opts.userFilters.forEach(function (username) { + data.username_filters.push(username); + }); + } + + // Add the summary of filter if we have it + if (opts.summary === true) { + data.summary = true; + } + + // Check the preload store. If not, load it via JSON + return ajax(`${url}.json`, { data }); + } + + static changeOwners(topicId, opts) { + const promise = ajax(`/t/${topicId}/change-owner`, { + type: "POST", + data: opts, + }).then((result) => { + if (result.success) { + return result; + } + promise.reject(new Error("error changing ownership of posts")); + }); + return promise; + } + + static changeTimestamp(topicId, timestamp) { + const promise = ajax(`/t/${topicId}/change-timestamp`, { + type: "PUT", + data: { timestamp }, + }).then((result) => { + if (result.success) { + return result; + } + promise.reject(new Error("error updating timestamp of topic")); + }); + return promise; + } + + static bulkOperation(topics, operation, options, tracked) { + const data = { + topic_ids: topics.mapBy("id"), + operation, + tracked, + }; + + if (options) { + if (options.select) { + data.silent = true; + } + } + + return ajax("/topics/bulk", { + type: "PUT", + data, + }); + } + + static bulkOperationByFilter(filter, operation, options, tracked) { + const data = { filter, operation, tracked }; + + if (options) { + if (options.categoryId) { + data.category_id = options.categoryId; + } + if (options.includeSubcategories) { + data.include_subcategories = true; + } + if (options.tagName) { + data.tag_name = options.tagName; + } + + if (options.private_message_inbox) { + data.private_message_inbox = options.private_message_inbox; + + if (options.group_name) { + data.group_name = options.group_name; + } + } + } + + return ajax("/topics/bulk", { + type: "PUT", + data, + }); + } + + static resetNew(category, include_subcategories, opts = {}) { + let { tracked, tag, topicIds } = { + tracked: false, + tag: null, + topicIds: null, + ...opts, + }; + + const data = { tracked }; + if (category) { + data.category_id = category.id; + data.include_subcategories = include_subcategories; + } + if (tag) { + data.tag_id = tag.id; + } + if (topicIds) { + data.topic_ids = topicIds; + } + + if (opts.dismissPosts) { + data.dismiss_posts = opts.dismissPosts; + } + + if (opts.dismissTopics) { + data.dismiss_topics = opts.dismissTopics; + } + + if (opts.untrack) { + data.untrack = opts.untrack; + } + + return ajax("/topics/reset-new", { type: "PUT", data }); + } + + static pmResetNew(opts = {}) { + const data = {}; + + if (opts.topicIds) { + data.topic_ids = opts.topicIds; + } + + if (opts.inbox) { + data.inbox = opts.inbox; + + if (opts.groupName) { + data.group_name = opts.groupName; + } + } + + return ajax("/topics/pm-reset-new", { type: "PUT", data }); + } + + static idForSlug(slug) { + return ajax(`/t/id_for/${slug}`); + } + + static setSlowMode(topicId, seconds, enabledUntil) { + const data = { seconds }; + data.enabled_until = enabledUntil; + + return ajax(`/t/${topicId}/slow_mode`, { type: "PUT", data }); + } + + static async applyTransformations(topics) { + await applyModelTransformations("topic", topics); + } + + message = null; + errorLoading = false; + + @alias("lastPoster.user") lastPosterUser; + @alias("lastPoster.primary_group") lastPosterGroup; + @alias("details.allowed_groups") allowedGroups; + @notEmpty("deleted_at") deleted; + @fmt("url", "%@/print") printUrl; + @equal("archetype", "private_message") isPrivateMessage; + @equal("archetype", "banner") isBanner; + @alias("bookmarks.length") bookmarkCount; + @and("pinned", "category.isUncategorizedCategory") isPinnedUncategorized; + @notEmpty("excerpt") hasExcerpt; + @propertyEqual("last_read_post_number", "highest_post_number") readLastPost; + @and("pinned", "readLastPost") canClearPin; + @or("details.can_edit", "details.can_edit_tags") canEditTags; @discourseComputed("last_read_post_number", "highest_post_number") visited(lastReadPostNumber, highestPostNumber) { // >= to handle case where there are deleted posts at the end of the topic return lastReadPostNumber >= highestPostNumber; - }, + } @discourseComputed("posters.firstObject") creator(poster) { return poster && poster.user; - }, + } @discourseComputed("posters.[]") lastPoster(posters) { @@ -66,11 +323,7 @@ const Topic = RestModel.extend({ const latest = posters.filter((p) => p.extras?.includes("latest"))[0]; return latest || posters.firstObject; } - }, - - lastPosterUser: alias("lastPoster.user"), - lastPosterGroup: alias("lastPoster.primary_group"), - allowedGroups: alias("details.allowed_groups"), + } @discourseComputed("posters.[]", "participants.[]", "allowed_user_count") featuredUsers(posters, participants, allowedUserCount) { @@ -109,12 +362,12 @@ const Topic = RestModel.extend({ } return users; - }, + } @discourseComputed("fancy_title") fancyTitle(title) { return fancyTitle(title, this.siteSettings.support_mixed_text_direction); - }, + } // returns createdAt if there's no bumped date @discourseComputed("bumped_at", "createdAt") @@ -124,7 +377,7 @@ const Topic = RestModel.extend({ } else { return createdAt; } - }, + } @discourseComputed("bumpedAt", "createdAt") bumpedAtTitle(bumpedAt, createdAt) { @@ -139,12 +392,12 @@ const Topic = RestModel.extend({ })}\n${I18n.t("topic.bumped_at", { date: longDate(bumpedAt) })}` : I18n.t("topic.created_at", { date: longDate(createdAt) }); } - }, + } @discourseComputed("created_at") createdAt(created_at) { return new Date(created_at); - }, + } @discourseComputed postStream() { @@ -152,7 +405,7 @@ const Topic = RestModel.extend({ id: this.id, topic: this, }); - }, + } @discourseComputed("tags") visibleListTags(tags) { @@ -170,66 +423,62 @@ const Topic = RestModel.extend({ }); return newTags; - }, + } @discourseComputed("related_messages") relatedMessages(relatedMessages) { if (relatedMessages) { return relatedMessages.map((st) => this.store.createRecord("topic", st)); } - }, + } @discourseComputed("suggested_topics") suggestedTopics(suggestedTopics) { if (suggestedTopics) { return suggestedTopics.map((st) => this.store.createRecord("topic", st)); } - }, + } @discourseComputed("posts_count") replyCount(postsCount) { return postsCount - 1; - }, + } get details() { return (this._details ??= this.store.createRecord("topicDetails", { id: this.id, topic: this, })); - }, + } set details(value) { this._details = value; - }, + } @discourseComputed("visible") invisible(visible) { return visible !== undefined ? !visible : undefined; - }, - - deleted: notEmpty("deleted_at"), + } @discourseComputed("id") searchContext(id) { return { type: "topic", id }; - }, + } @computed("category_id") get category() { return Category.findById(this.category_id); - }, + } set category(newCategory) { this.set("category_id", newCategory?.id); - }, + } @discourseComputed("url") shareUrl(url) { const user = User.current(); return resolveShareUrl(url, user); - }, - - printUrl: fmt("url", "%@/print"), + } @discourseComputed("id", "slug") url(id, slug) { @@ -238,7 +487,7 @@ const Topic = RestModel.extend({ slug = "topic"; } return `${getURL("/t/")}${slug}/${id}`; - }, + } // Helper to build a Url with a post number urlForPostNumber(postNumber) { @@ -247,7 +496,7 @@ const Topic = RestModel.extend({ url += `/${postNumber}`; } return url; - }, + } @discourseComputed("unread_posts", "new_posts") totalUnread(unreadPosts, newPosts) { @@ -255,7 +504,7 @@ const Topic = RestModel.extend({ id: "discourse.topic.totalUnread", }); return unreadPosts || newPosts; - }, + } @discourseComputed("unread_posts", "new_posts") displayNewPosts(unreadPosts, newPosts) { @@ -264,12 +513,12 @@ const Topic = RestModel.extend({ { id: "discourse.topic.totalUnread" } ); return unreadPosts || newPosts; - }, + } @discourseComputed("last_read_post_number", "url") lastReadUrl(lastReadPostNumber) { return this.urlForPostNumber(lastReadPostNumber); - }, + } @discourseComputed("last_read_post_number", "highest_post_number", "url") lastUnreadUrl(lastReadPostNumber, highestPostNumber) { @@ -299,28 +548,28 @@ const Topic = RestModel.extend({ } return this.urlForPostNumber(postNumber); - }, + } @discourseComputed("highest_post_number", "url") lastPostUrl(highestPostNumber) { return this.urlForPostNumber(highestPostNumber); - }, + } @discourseComputed("url") firstPostUrl() { return this.urlForPostNumber(1); - }, + } @discourseComputed("url") summaryUrl() { const summaryQueryString = this.has_summary ? "?filter=summary" : ""; return `${this.urlForPostNumber(1)}${summaryQueryString}`; - }, + } @discourseComputed("last_poster.username") lastPosterUrl(username) { return userPath(username); - }, + } @discourseComputed("views") viewsHeat(v) { @@ -334,20 +583,17 @@ const Topic = RestModel.extend({ return "heatmap-low"; } return null; - }, + } @discourseComputed("archetype") archetypeObject(archetype) { return Site.currentProp("archetypes").findBy("id", archetype); - }, - - isPrivateMessage: equal("archetype", "private_message"), - isBanner: equal("archetype", "banner"), + } toggleStatus(property) { this.toggleProperty(property); return this.saveStatus(property, !!this.get(property)); - }, + } saveStatus(property, value, until) { if (property === "closed") { @@ -361,23 +607,23 @@ const Topic = RestModel.extend({ until, }, }); - }, + } makeBanner() { return ajax(`/t/${this.id}/make-banner`, { type: "PUT" }).then(() => this.set("archetype", "banner") ); - }, + } removeBanner() { return ajax(`/t/${this.id}/remove-banner`, { type: "PUT", }).then(() => this.set("archetype", "regular")); - }, + } afterPostBookmarked(post) { post.set("bookmarked", true); - }, + } firstPost() { const postStream = this.postStream; @@ -393,7 +639,7 @@ const Topic = RestModel.extend({ } else { return this.postStream.loadPostByPostNumber(1); } - }, + } postById(id) { const loaded = this.postStream.findLoadedPost(id); @@ -402,13 +648,11 @@ const Topic = RestModel.extend({ } return this.postStream.loadPost(id); - }, + } deleteBookmarks() { return ajax(`/t/${this.id}/remove_bookmarks`, { type: "PUT" }); - }, - - bookmarkCount: alias("bookmarks.length"), + } removeBookmark(id) { if (!this.bookmarks) { @@ -430,7 +674,7 @@ const Topic = RestModel.extend({ ); this.set("bookmarked", this.bookmarks.length); this.incrementProperty("bookmarksWereChanged"); - }, + } clearBookmarks() { this.toggleProperty("bookmarked"); @@ -447,28 +691,28 @@ const Topic = RestModel.extend({ this.set("bookmarks", []); return postIds; - }, + } createGroupInvite(group) { return ajax(`/t/${this.id}/invite-group`, { type: "POST", data: { group }, }); - }, + } createInvite(user, group_ids, custom_message) { return ajax(`/t/${this.id}/invite`, { type: "POST", data: { user, group_ids, custom_message }, }); - }, + } generateInviteLink(email, group_ids, topic_id) { return ajax("/invites", { type: "POST", data: { email, skip_email: true, group_ids, topic_id }, }); - }, + } // Delete this topic destroy(deleted_by, opts = {}) { @@ -502,7 +746,7 @@ const Topic = RestModel.extend({ } }) .catch(popupAjaxError); - }, + } // Recover this topic if deleted recover() { @@ -516,7 +760,7 @@ const Topic = RestModel.extend({ data: { context: window.location.pathname }, type: "PUT", }); - }, + } // Update our attributes from a JSON result updateFromJson(json) { @@ -543,15 +787,13 @@ const Topic = RestModel.extend({ } return this; - }, + } reload() { return ajax(`/t/${this.id}`, { type: "GET" }).then((topic_json) => this.updateFromJson(topic_json) ); - }, - - isPinnedUncategorized: and("pinned", "category.isUncategorizedCategory"), + } clearPin() { // Clear the pin optimistically from the object @@ -563,7 +805,7 @@ const Topic = RestModel.extend({ // On error, put the pin back this.setProperties({ pinned: true, unpinned: false }); }); - }, + } togglePinnedForUser() { if (this.pinned) { @@ -571,7 +813,7 @@ const Topic = RestModel.extend({ } else { this.rePin(); } - }, + } rePin() { // Clear the pin optimistically from the object @@ -583,23 +825,17 @@ const Topic = RestModel.extend({ // On error, put the pin back this.setProperties({ pinned: true, unpinned: false }); }); - }, + } @discourseComputed("excerpt") escapedExcerpt(excerpt) { return emojiUnescape(excerpt); - }, - - hasExcerpt: notEmpty("excerpt"), + } @discourseComputed("excerpt") excerptTruncated(excerpt) { return excerpt && excerpt.slice(-8) === "…"; - }, - - readLastPost: propertyEqual("last_read_post_number", "highest_post_number"), - canClearPin: and("pinned", "readLastPost"), - canEditTags: or("details.can_edit", "details.can_edit_tags"), + } archiveMessage() { this.set("archiving", true); @@ -617,7 +853,7 @@ const Topic = RestModel.extend({ .finally(() => this.set("archiving", false)); return promise; - }, + } moveToInbox() { this.set("archiving", true); @@ -633,7 +869,7 @@ const Topic = RestModel.extend({ .finally(() => this.set("archiving", false)); return promise; - }, + } publish() { return ajax(`/t/${this.id}/publish`, { @@ -642,7 +878,7 @@ const Topic = RestModel.extend({ }) .then(() => this.set("destination_category_id", null)) .catch(popupAjaxError); - }, + } updateDestinationCategory(categoryId) { this.set("destination_category_id", categoryId); @@ -650,7 +886,7 @@ const Topic = RestModel.extend({ type: "PUT", data: { category_id: categoryId }, }); - }, + } convertTopic(type, opts) { let args = { type: "PUT" }; @@ -658,13 +894,13 @@ const Topic = RestModel.extend({ args.data = { category_id: opts.categoryId }; } return ajax(`/t/${this.id}/convert-topic/${type}`, args); - }, + } resetBumpDate() { return ajax(`/t/${this.id}/reset-bump-date`, { type: "PUT" }).catch( popupAjaxError ); - }, + } updateTags(tags) { if (!tags || tags.length === 0) { @@ -675,253 +911,8 @@ const Topic = RestModel.extend({ type: "PUT", data: { tags }, }); - }, -}); - -Topic.reopenClass({ - NotificationLevel: { - WATCHING: 3, - TRACKING: 2, - REGULAR: 1, - MUTED: 0, - }, - - munge(json) { - // ensure we are not overriding category computed property - delete json.category; - json.bookmarks = json.bookmarks || []; - return json; - }, - - createActionSummary(result) { - if (result.actions_summary) { - const lookup = EmberObject.create(); - result.actions_summary = result.actions_summary.map((a) => { - a.post = result; - a.actionType = Site.current().postActionTypeById(a.id); - const actionSummary = ActionSummary.create(a); - lookup.set(a.actionType.get("name_key"), actionSummary); - return actionSummary; - }); - result.set("actionByName", lookup); - } - }, - - update(topic, props, opts = {}) { - // We support `category_id` and `categoryId` for compatibility - if (typeof props.categoryId !== "undefined") { - props.category_id = props.categoryId; - delete props.categoryId; - } - - // Make sure we never change the category for private messages - if (topic.get("isPrivateMessage")) { - delete props.category_id; - } - - const data = { ...props }; - if (opts.fastEdit) { - data.keep_existing_draft = true; - } - return ajax(topic.get("url"), { - type: "PUT", - data: JSON.stringify(data), - contentType: "application/json", - }).then((result) => { - // The title can be cleaned up server side - props.title = result.basic_topic.title; - props.fancy_title = result.basic_topic.fancy_title; - if (topic.is_shared_draft) { - props.destination_category_id = props.category_id; - delete props.category_id; - } - topic.setProperties(props); - }); - }, - - create() { - const result = this._super.apply(this, arguments); - this.createActionSummary(result); - return result; - }, - - // Load a topic, but accepts a set of filters - find(topicId, opts) { - let url = getURL("/t/") + topicId; - if (opts.nearPost) { - url += `/${opts.nearPost}`; - } - - const data = {}; - if (opts.postsAfter) { - data.posts_after = opts.postsAfter; - } - if (opts.postsBefore) { - data.posts_before = opts.postsBefore; - } - if (opts.trackVisit) { - data.track_visit = true; - } - - // Add username filters if we have them - if (opts.userFilters && opts.userFilters.length > 0) { - data.username_filters = []; - opts.userFilters.forEach(function (username) { - data.username_filters.push(username); - }); - } - - // Add the summary of filter if we have it - if (opts.summary === true) { - data.summary = true; - } - - // Check the preload store. If not, load it via JSON - return ajax(`${url}.json`, { data }); - }, - - changeOwners(topicId, opts) { - const promise = ajax(`/t/${topicId}/change-owner`, { - type: "POST", - data: opts, - }).then((result) => { - if (result.success) { - return result; - } - promise.reject(new Error("error changing ownership of posts")); - }); - return promise; - }, - - changeTimestamp(topicId, timestamp) { - const promise = ajax(`/t/${topicId}/change-timestamp`, { - type: "PUT", - data: { timestamp }, - }).then((result) => { - if (result.success) { - return result; - } - promise.reject(new Error("error updating timestamp of topic")); - }); - return promise; - }, - - bulkOperation(topics, operation, options, tracked) { - const data = { - topic_ids: topics.mapBy("id"), - operation, - tracked, - }; - - if (options) { - if (options.select) { - data.silent = true; - } - } - - return ajax("/topics/bulk", { - type: "PUT", - data, - }); - }, - - bulkOperationByFilter(filter, operation, options, tracked) { - const data = { filter, operation, tracked }; - - if (options) { - if (options.categoryId) { - data.category_id = options.categoryId; - } - if (options.includeSubcategories) { - data.include_subcategories = true; - } - if (options.tagName) { - data.tag_name = options.tagName; - } - - if (options.private_message_inbox) { - data.private_message_inbox = options.private_message_inbox; - - if (options.group_name) { - data.group_name = options.group_name; - } - } - } - - return ajax("/topics/bulk", { - type: "PUT", - data, - }); - }, - - resetNew(category, include_subcategories, opts = {}) { - let { tracked, tag, topicIds } = { - tracked: false, - tag: null, - topicIds: null, - ...opts, - }; - - const data = { tracked }; - if (category) { - data.category_id = category.id; - data.include_subcategories = include_subcategories; - } - if (tag) { - data.tag_id = tag.id; - } - if (topicIds) { - data.topic_ids = topicIds; - } - - if (opts.dismissPosts) { - data.dismiss_posts = opts.dismissPosts; - } - - if (opts.dismissTopics) { - data.dismiss_topics = opts.dismissTopics; - } - - if (opts.untrack) { - data.untrack = opts.untrack; - } - - return ajax("/topics/reset-new", { type: "PUT", data }); - }, - - pmResetNew(opts = {}) { - const data = {}; - - if (opts.topicIds) { - data.topic_ids = opts.topicIds; - } - - if (opts.inbox) { - data.inbox = opts.inbox; - - if (opts.groupName) { - data.group_name = opts.groupName; - } - } - - return ajax("/topics/pm-reset-new", { type: "PUT", data }); - }, - - idForSlug(slug) { - return ajax(`/t/id_for/${slug}`); - }, - - setSlowMode(topicId, seconds, enabledUntil) { - const data = { seconds }; - data.enabled_until = enabledUntil; - - return ajax(`/t/${topicId}/slow_mode`, { type: "PUT", data }); - }, - - async applyTransformations(topics) { - await applyModelTransformations("topic", topics); - }, -}); + } +} function moveResult(result) { if (result.success) { @@ -952,5 +943,3 @@ export function registerCustomLastUnreadUrlCallback(fn) { export function clearCustomLastUnreadUrlCallbacks() { _customLastUnreadUrlCallbacks.clear(); } - -export default Topic;