DEV: Update topic and post models to native class syntax (#25612)

This commit was created with a combination of the ember-native-class-codemod and manual cleanup
This commit is contained in:
David Taylor 2024-02-08 15:09:50 +00:00 committed by GitHub
parent 6c597b648b
commit a3ef9e92eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 449 additions and 463 deletions

View File

@ -18,8 +18,99 @@ import User from "discourse/models/user";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
const Post = RestModel.extend({ export default class Post extends RestModel {
customShare: null, 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") @discourseComputed("url", "customShare")
shareUrl(url) { shareUrl(url) {
@ -29,30 +120,22 @@ const Post = RestModel.extend({
const user = User.current(); const user = User.current();
return resolveShareUrl(url, user); 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") @discourseComputed("name", "username")
showName(name, username) { showName(name, username) {
return name && name !== username && this.siteSettings.display_name_on_posts; return name && name !== username && this.siteSettings.display_name_on_posts;
}, }
@discourseComputed("firstPost", "deleted_by", "topic.deleted_by") @discourseComputed("firstPost", "deleted_by", "topic.deleted_by")
postDeletedBy(firstPost, deletedBy, topicDeletedBy) { postDeletedBy(firstPost, deletedBy, topicDeletedBy) {
return firstPost ? topicDeletedBy : deletedBy; return firstPost ? topicDeletedBy : deletedBy;
}, }
@discourseComputed("firstPost", "deleted_at", "topic.deleted_at") @discourseComputed("firstPost", "deleted_at", "topic.deleted_at")
postDeletedAt(firstPost, deletedAt, topicDeletedAt) { postDeletedAt(firstPost, deletedAt, topicDeletedAt) {
return firstPost ? topicDeletedAt : deletedAt; return firstPost ? topicDeletedAt : deletedAt;
}, }
@discourseComputed("post_number", "topic_id", "topic.slug") @discourseComputed("post_number", "topic_id", "topic.slug")
url(post_number, topic_id, topicSlug) { url(post_number, topic_id, topicSlug) {
@ -61,18 +144,18 @@ const Post = RestModel.extend({
topic_id || this.get("topic.id"), topic_id || this.get("topic.id"),
post_number post_number
); );
}, }
// Don't drop the /1 // Don't drop the /1
@discourseComputed("post_number", "url") @discourseComputed("post_number", "url")
urlWithNumber(postNumber, baseUrl) { urlWithNumber(postNumber, baseUrl) {
return postNumber === 1 ? `${baseUrl}/1` : baseUrl; return postNumber === 1 ? `${baseUrl}/1` : baseUrl;
}, }
@discourseComputed("username") @discourseComputed("username")
usernameUrl: userPath, usernameUrl(username) {
return userPath(username);
topicOwner: propertyEqual("topic.details.created_by.id", "user_id"), }
updatePostField(field, value) { updatePostField(field, value) {
const data = {}; const data = {};
@ -81,7 +164,7 @@ const Post = RestModel.extend({
return ajax(`/posts/${this.id}/${field}`, { type: "PUT", data }) return ajax(`/posts/${this.id}/${field}`, { type: "PUT", data })
.then(() => this.set(field, value)) .then(() => this.set(field, value))
.catch(popupAjaxError); .catch(popupAjaxError);
}, }
@discourseComputed("link_counts.@each.internal") @discourseComputed("link_counts.@each.internal")
internalLinks() { internalLinks() {
@ -90,7 +173,7 @@ const Post = RestModel.extend({
} }
return this.link_counts.filterBy("internal").filterBy("title"); return this.link_counts.filterBy("internal").filterBy("title");
}, }
@discourseComputed("actions_summary.@each.can_act") @discourseComputed("actions_summary.@each.can_act")
flagsAvailable() { flagsAvailable() {
@ -103,7 +186,7 @@ const Post = RestModel.extend({
return this.site.flagTypes.filter((item) => return this.site.flagTypes.filter((item) =>
this.get(`actionByName.${item.name_key}.can_act`) this.get(`actionByName.${item.name_key}.can_act`)
); );
}, }
@discourseComputed( @discourseComputed(
"siteSettings.use_pg_headlines_for_excerpt", "siteSettings.use_pg_headlines_for_excerpt",
@ -111,25 +194,25 @@ const Post = RestModel.extend({
) )
useTopicTitleHeadline(enabled, title) { useTopicTitleHeadline(enabled, title) {
return enabled && title; return enabled && title;
}, }
@discourseComputed("topic_title_headline") @discourseComputed("topic_title_headline")
topicTitleHeadline(title) { topicTitleHeadline(title) {
return fancyTitle(title, this.siteSettings.support_mixed_text_direction); return fancyTitle(title, this.siteSettings.support_mixed_text_direction);
}, }
afterUpdate(res) { afterUpdate(res) {
if (res.category) { if (res.category) {
this.site.updateCategory(res.category); this.site.updateCategory(res.category);
} }
}, }
updateProperties() { updateProperties() {
return { return {
post: { raw: this.raw, edit_reason: this.editReason }, post: { raw: this.raw, edit_reason: this.editReason },
image_sizes: this.imageSizes, image_sizes: this.imageSizes,
}; };
}, }
createProperties() { createProperties() {
// composer only used once, defer the dependency // composer only used once, defer the dependency
@ -148,7 +231,7 @@ const Post = RestModel.extend({
} }
return data; return data;
}, }
// Expands the first post's content, if embedded and shortened. // Expands the first post's content, if embedded and shortened.
expand() { expand() {
@ -158,7 +241,7 @@ const Post = RestModel.extend({
`<section class="expanded-embed">${post.cooked}</section>` `<section class="expanded-embed">${post.cooked}</section>`
); );
}); });
}, }
// Recover a deleted post // Recover a deleted post
recover() { recover() {
@ -192,7 +275,7 @@ const Post = RestModel.extend({
popupAjaxError(error); popupAjaxError(error);
this.setProperties(initProperties); this.setProperties(initProperties);
}); });
}, }
/** /**
Changes the state of the post to be deleted. Does not call the server, that should be 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(); return promise || Promise.resolve();
}, }
/** /**
Changes the state of the post to NOT be deleted. Does not call the server. 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, user_deleted: false,
}); });
} }
}, }
destroy(deletedBy, opts) { destroy(deletedBy, opts) {
return this.setDeletedState(deletedBy).then(() => { return this.setDeletedState(deletedBy).then(() => {
@ -259,7 +342,7 @@ const Post = RestModel.extend({
type: "DELETE", type: "DELETE",
}); });
}); });
}, }
/** /**
Updates a post from another's attributes. This will normally happen when a post is loading but 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() { expandHidden() {
return ajax(`/posts/${this.id}/cooked.json`).then((result) => { return ajax(`/posts/${this.id}/cooked.json`).then((result) => {
this.setProperties({ cooked: result.cooked, cooked_hidden: false }); this.setProperties({ cooked: result.cooked, cooked_hidden: false });
}); });
}, }
rebake() { rebake() {
return ajax(`/posts/${this.id}/rebake`, { type: "PUT" }).catch( return ajax(`/posts/${this.id}/rebake`, { type: "PUT" }).catch(
popupAjaxError popupAjaxError
); );
}, }
unhide() { unhide() {
return ajax(`/posts/${this.id}/unhide`, { type: "PUT" }); return ajax(`/posts/${this.id}/unhide`, { type: "PUT" });
}, }
createBookmark(data) { createBookmark(data) {
this.setProperties({ this.setProperties({
@ -324,12 +407,12 @@ const Post = RestModel.extend({
targetId: this.id, targetId: this.id,
}); });
this.appEvents.trigger("post-stream:refresh", { id: this.id }); this.appEvents.trigger("post-stream:refresh", { id: this.id });
}, }
deleteBookmark(bookmarked) { deleteBookmark(bookmarked) {
this.set("topic.bookmarked", bookmarked); this.set("topic.bookmarked", bookmarked);
this.clearBookmark(); this.clearBookmark();
}, }
clearBookmark() { clearBookmark() {
this.setProperties({ this.setProperties({
@ -344,14 +427,14 @@ const Post = RestModel.extend({
target: "post", target: "post",
targetId: this.id, targetId: this.id,
}); });
}, }
updateActionsSummary(json) { updateActionsSummary(json) {
if (json && json.id === this.id) { if (json && json.id === this.id) {
json = Post.munge(json); json = Post.munge(json);
this.set("actions_summary", json.actions_summary); this.set("actions_summary", json.actions_summary);
} }
}, }
updateLikeCount(count, userId, eventType) { updateLikeCount(count, userId, eventType) {
let ownAction = User.current()?.id === userId; let ownAction = User.current()?.id === userId;
@ -385,101 +468,15 @@ const Post = RestModel.extend({
Object.assign(this.actionByName["like"], newActionObject); Object.assign(this.actionByName["like"], newActionObject);
Object.assign(this.likeAction, newActionObject); Object.assign(this.likeAction, newActionObject);
} }
}, }
revertToRevision(version) { revertToRevision(version) {
return ajax(`/posts/${this.id}/revisions/${version}/revert`, { return ajax(`/posts/${this.id}/revisions/${version}/revert`, {
type: "PUT", type: "PUT",
}); });
}, }
get topicNotificationLevel() { get topicNotificationLevel() {
return this.topic.details.notification_level; 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;

View File

@ -45,20 +45,277 @@ export function loadTopicView(topic, args) {
export const ID_CONSTRAINT = /^\d+$/; export const ID_CONSTRAINT = /^\d+$/;
let _customLastUnreadUrlCallbacks = []; let _customLastUnreadUrlCallbacks = [];
const Topic = RestModel.extend({ export default class Topic extends RestModel {
message: null, static NotificationLevel = {
errorLoading: false, 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") @discourseComputed("last_read_post_number", "highest_post_number")
visited(lastReadPostNumber, highestPostNumber) { visited(lastReadPostNumber, highestPostNumber) {
// >= to handle case where there are deleted posts at the end of the topic // >= to handle case where there are deleted posts at the end of the topic
return lastReadPostNumber >= highestPostNumber; return lastReadPostNumber >= highestPostNumber;
}, }
@discourseComputed("posters.firstObject") @discourseComputed("posters.firstObject")
creator(poster) { creator(poster) {
return poster && poster.user; return poster && poster.user;
}, }
@discourseComputed("posters.[]") @discourseComputed("posters.[]")
lastPoster(posters) { lastPoster(posters) {
@ -66,11 +323,7 @@ const Topic = RestModel.extend({
const latest = posters.filter((p) => p.extras?.includes("latest"))[0]; const latest = posters.filter((p) => p.extras?.includes("latest"))[0];
return latest || posters.firstObject; return latest || posters.firstObject;
} }
}, }
lastPosterUser: alias("lastPoster.user"),
lastPosterGroup: alias("lastPoster.primary_group"),
allowedGroups: alias("details.allowed_groups"),
@discourseComputed("posters.[]", "participants.[]", "allowed_user_count") @discourseComputed("posters.[]", "participants.[]", "allowed_user_count")
featuredUsers(posters, participants, allowedUserCount) { featuredUsers(posters, participants, allowedUserCount) {
@ -109,12 +362,12 @@ const Topic = RestModel.extend({
} }
return users; return users;
}, }
@discourseComputed("fancy_title") @discourseComputed("fancy_title")
fancyTitle(title) { fancyTitle(title) {
return fancyTitle(title, this.siteSettings.support_mixed_text_direction); return fancyTitle(title, this.siteSettings.support_mixed_text_direction);
}, }
// returns createdAt if there's no bumped date // returns createdAt if there's no bumped date
@discourseComputed("bumped_at", "createdAt") @discourseComputed("bumped_at", "createdAt")
@ -124,7 +377,7 @@ const Topic = RestModel.extend({
} else { } else {
return createdAt; return createdAt;
} }
}, }
@discourseComputed("bumpedAt", "createdAt") @discourseComputed("bumpedAt", "createdAt")
bumpedAtTitle(bumpedAt, createdAt) { bumpedAtTitle(bumpedAt, createdAt) {
@ -139,12 +392,12 @@ const Topic = RestModel.extend({
})}\n${I18n.t("topic.bumped_at", { date: longDate(bumpedAt) })}` })}\n${I18n.t("topic.bumped_at", { date: longDate(bumpedAt) })}`
: I18n.t("topic.created_at", { date: longDate(createdAt) }); : I18n.t("topic.created_at", { date: longDate(createdAt) });
} }
}, }
@discourseComputed("created_at") @discourseComputed("created_at")
createdAt(created_at) { createdAt(created_at) {
return new Date(created_at); return new Date(created_at);
}, }
@discourseComputed @discourseComputed
postStream() { postStream() {
@ -152,7 +405,7 @@ const Topic = RestModel.extend({
id: this.id, id: this.id,
topic: this, topic: this,
}); });
}, }
@discourseComputed("tags") @discourseComputed("tags")
visibleListTags(tags) { visibleListTags(tags) {
@ -170,66 +423,62 @@ const Topic = RestModel.extend({
}); });
return newTags; return newTags;
}, }
@discourseComputed("related_messages") @discourseComputed("related_messages")
relatedMessages(relatedMessages) { relatedMessages(relatedMessages) {
if (relatedMessages) { if (relatedMessages) {
return relatedMessages.map((st) => this.store.createRecord("topic", st)); return relatedMessages.map((st) => this.store.createRecord("topic", st));
} }
}, }
@discourseComputed("suggested_topics") @discourseComputed("suggested_topics")
suggestedTopics(suggestedTopics) { suggestedTopics(suggestedTopics) {
if (suggestedTopics) { if (suggestedTopics) {
return suggestedTopics.map((st) => this.store.createRecord("topic", st)); return suggestedTopics.map((st) => this.store.createRecord("topic", st));
} }
}, }
@discourseComputed("posts_count") @discourseComputed("posts_count")
replyCount(postsCount) { replyCount(postsCount) {
return postsCount - 1; return postsCount - 1;
}, }
get details() { get details() {
return (this._details ??= this.store.createRecord("topicDetails", { return (this._details ??= this.store.createRecord("topicDetails", {
id: this.id, id: this.id,
topic: this, topic: this,
})); }));
}, }
set details(value) { set details(value) {
this._details = value; this._details = value;
}, }
@discourseComputed("visible") @discourseComputed("visible")
invisible(visible) { invisible(visible) {
return visible !== undefined ? !visible : undefined; return visible !== undefined ? !visible : undefined;
}, }
deleted: notEmpty("deleted_at"),
@discourseComputed("id") @discourseComputed("id")
searchContext(id) { searchContext(id) {
return { type: "topic", id }; return { type: "topic", id };
}, }
@computed("category_id") @computed("category_id")
get category() { get category() {
return Category.findById(this.category_id); return Category.findById(this.category_id);
}, }
set category(newCategory) { set category(newCategory) {
this.set("category_id", newCategory?.id); this.set("category_id", newCategory?.id);
}, }
@discourseComputed("url") @discourseComputed("url")
shareUrl(url) { shareUrl(url) {
const user = User.current(); const user = User.current();
return resolveShareUrl(url, user); return resolveShareUrl(url, user);
}, }
printUrl: fmt("url", "%@/print"),
@discourseComputed("id", "slug") @discourseComputed("id", "slug")
url(id, slug) { url(id, slug) {
@ -238,7 +487,7 @@ const Topic = RestModel.extend({
slug = "topic"; slug = "topic";
} }
return `${getURL("/t/")}${slug}/${id}`; return `${getURL("/t/")}${slug}/${id}`;
}, }
// Helper to build a Url with a post number // Helper to build a Url with a post number
urlForPostNumber(postNumber) { urlForPostNumber(postNumber) {
@ -247,7 +496,7 @@ const Topic = RestModel.extend({
url += `/${postNumber}`; url += `/${postNumber}`;
} }
return url; return url;
}, }
@discourseComputed("unread_posts", "new_posts") @discourseComputed("unread_posts", "new_posts")
totalUnread(unreadPosts, newPosts) { totalUnread(unreadPosts, newPosts) {
@ -255,7 +504,7 @@ const Topic = RestModel.extend({
id: "discourse.topic.totalUnread", id: "discourse.topic.totalUnread",
}); });
return unreadPosts || newPosts; return unreadPosts || newPosts;
}, }
@discourseComputed("unread_posts", "new_posts") @discourseComputed("unread_posts", "new_posts")
displayNewPosts(unreadPosts, newPosts) { displayNewPosts(unreadPosts, newPosts) {
@ -264,12 +513,12 @@ const Topic = RestModel.extend({
{ id: "discourse.topic.totalUnread" } { id: "discourse.topic.totalUnread" }
); );
return unreadPosts || newPosts; return unreadPosts || newPosts;
}, }
@discourseComputed("last_read_post_number", "url") @discourseComputed("last_read_post_number", "url")
lastReadUrl(lastReadPostNumber) { lastReadUrl(lastReadPostNumber) {
return this.urlForPostNumber(lastReadPostNumber); return this.urlForPostNumber(lastReadPostNumber);
}, }
@discourseComputed("last_read_post_number", "highest_post_number", "url") @discourseComputed("last_read_post_number", "highest_post_number", "url")
lastUnreadUrl(lastReadPostNumber, highestPostNumber) { lastUnreadUrl(lastReadPostNumber, highestPostNumber) {
@ -299,28 +548,28 @@ const Topic = RestModel.extend({
} }
return this.urlForPostNumber(postNumber); return this.urlForPostNumber(postNumber);
}, }
@discourseComputed("highest_post_number", "url") @discourseComputed("highest_post_number", "url")
lastPostUrl(highestPostNumber) { lastPostUrl(highestPostNumber) {
return this.urlForPostNumber(highestPostNumber); return this.urlForPostNumber(highestPostNumber);
}, }
@discourseComputed("url") @discourseComputed("url")
firstPostUrl() { firstPostUrl() {
return this.urlForPostNumber(1); return this.urlForPostNumber(1);
}, }
@discourseComputed("url") @discourseComputed("url")
summaryUrl() { summaryUrl() {
const summaryQueryString = this.has_summary ? "?filter=summary" : ""; const summaryQueryString = this.has_summary ? "?filter=summary" : "";
return `${this.urlForPostNumber(1)}${summaryQueryString}`; return `${this.urlForPostNumber(1)}${summaryQueryString}`;
}, }
@discourseComputed("last_poster.username") @discourseComputed("last_poster.username")
lastPosterUrl(username) { lastPosterUrl(username) {
return userPath(username); return userPath(username);
}, }
@discourseComputed("views") @discourseComputed("views")
viewsHeat(v) { viewsHeat(v) {
@ -334,20 +583,17 @@ const Topic = RestModel.extend({
return "heatmap-low"; return "heatmap-low";
} }
return null; return null;
}, }
@discourseComputed("archetype") @discourseComputed("archetype")
archetypeObject(archetype) { archetypeObject(archetype) {
return Site.currentProp("archetypes").findBy("id", archetype); return Site.currentProp("archetypes").findBy("id", archetype);
}, }
isPrivateMessage: equal("archetype", "private_message"),
isBanner: equal("archetype", "banner"),
toggleStatus(property) { toggleStatus(property) {
this.toggleProperty(property); this.toggleProperty(property);
return this.saveStatus(property, !!this.get(property)); return this.saveStatus(property, !!this.get(property));
}, }
saveStatus(property, value, until) { saveStatus(property, value, until) {
if (property === "closed") { if (property === "closed") {
@ -361,23 +607,23 @@ const Topic = RestModel.extend({
until, until,
}, },
}); });
}, }
makeBanner() { makeBanner() {
return ajax(`/t/${this.id}/make-banner`, { type: "PUT" }).then(() => return ajax(`/t/${this.id}/make-banner`, { type: "PUT" }).then(() =>
this.set("archetype", "banner") this.set("archetype", "banner")
); );
}, }
removeBanner() { removeBanner() {
return ajax(`/t/${this.id}/remove-banner`, { return ajax(`/t/${this.id}/remove-banner`, {
type: "PUT", type: "PUT",
}).then(() => this.set("archetype", "regular")); }).then(() => this.set("archetype", "regular"));
}, }
afterPostBookmarked(post) { afterPostBookmarked(post) {
post.set("bookmarked", true); post.set("bookmarked", true);
}, }
firstPost() { firstPost() {
const postStream = this.postStream; const postStream = this.postStream;
@ -393,7 +639,7 @@ const Topic = RestModel.extend({
} else { } else {
return this.postStream.loadPostByPostNumber(1); return this.postStream.loadPostByPostNumber(1);
} }
}, }
postById(id) { postById(id) {
const loaded = this.postStream.findLoadedPost(id); const loaded = this.postStream.findLoadedPost(id);
@ -402,13 +648,11 @@ const Topic = RestModel.extend({
} }
return this.postStream.loadPost(id); return this.postStream.loadPost(id);
}, }
deleteBookmarks() { deleteBookmarks() {
return ajax(`/t/${this.id}/remove_bookmarks`, { type: "PUT" }); return ajax(`/t/${this.id}/remove_bookmarks`, { type: "PUT" });
}, }
bookmarkCount: alias("bookmarks.length"),
removeBookmark(id) { removeBookmark(id) {
if (!this.bookmarks) { if (!this.bookmarks) {
@ -430,7 +674,7 @@ const Topic = RestModel.extend({
); );
this.set("bookmarked", this.bookmarks.length); this.set("bookmarked", this.bookmarks.length);
this.incrementProperty("bookmarksWereChanged"); this.incrementProperty("bookmarksWereChanged");
}, }
clearBookmarks() { clearBookmarks() {
this.toggleProperty("bookmarked"); this.toggleProperty("bookmarked");
@ -447,28 +691,28 @@ const Topic = RestModel.extend({
this.set("bookmarks", []); this.set("bookmarks", []);
return postIds; return postIds;
}, }
createGroupInvite(group) { createGroupInvite(group) {
return ajax(`/t/${this.id}/invite-group`, { return ajax(`/t/${this.id}/invite-group`, {
type: "POST", type: "POST",
data: { group }, data: { group },
}); });
}, }
createInvite(user, group_ids, custom_message) { createInvite(user, group_ids, custom_message) {
return ajax(`/t/${this.id}/invite`, { return ajax(`/t/${this.id}/invite`, {
type: "POST", type: "POST",
data: { user, group_ids, custom_message }, data: { user, group_ids, custom_message },
}); });
}, }
generateInviteLink(email, group_ids, topic_id) { generateInviteLink(email, group_ids, topic_id) {
return ajax("/invites", { return ajax("/invites", {
type: "POST", type: "POST",
data: { email, skip_email: true, group_ids, topic_id }, data: { email, skip_email: true, group_ids, topic_id },
}); });
}, }
// Delete this topic // Delete this topic
destroy(deleted_by, opts = {}) { destroy(deleted_by, opts = {}) {
@ -502,7 +746,7 @@ const Topic = RestModel.extend({
} }
}) })
.catch(popupAjaxError); .catch(popupAjaxError);
}, }
// Recover this topic if deleted // Recover this topic if deleted
recover() { recover() {
@ -516,7 +760,7 @@ const Topic = RestModel.extend({
data: { context: window.location.pathname }, data: { context: window.location.pathname },
type: "PUT", type: "PUT",
}); });
}, }
// Update our attributes from a JSON result // Update our attributes from a JSON result
updateFromJson(json) { updateFromJson(json) {
@ -543,15 +787,13 @@ const Topic = RestModel.extend({
} }
return this; return this;
}, }
reload() { reload() {
return ajax(`/t/${this.id}`, { type: "GET" }).then((topic_json) => return ajax(`/t/${this.id}`, { type: "GET" }).then((topic_json) =>
this.updateFromJson(topic_json) this.updateFromJson(topic_json)
); );
}, }
isPinnedUncategorized: and("pinned", "category.isUncategorizedCategory"),
clearPin() { clearPin() {
// Clear the pin optimistically from the object // Clear the pin optimistically from the object
@ -563,7 +805,7 @@ const Topic = RestModel.extend({
// On error, put the pin back // On error, put the pin back
this.setProperties({ pinned: true, unpinned: false }); this.setProperties({ pinned: true, unpinned: false });
}); });
}, }
togglePinnedForUser() { togglePinnedForUser() {
if (this.pinned) { if (this.pinned) {
@ -571,7 +813,7 @@ const Topic = RestModel.extend({
} else { } else {
this.rePin(); this.rePin();
} }
}, }
rePin() { rePin() {
// Clear the pin optimistically from the object // Clear the pin optimistically from the object
@ -583,23 +825,17 @@ const Topic = RestModel.extend({
// On error, put the pin back // On error, put the pin back
this.setProperties({ pinned: true, unpinned: false }); this.setProperties({ pinned: true, unpinned: false });
}); });
}, }
@discourseComputed("excerpt") @discourseComputed("excerpt")
escapedExcerpt(excerpt) { escapedExcerpt(excerpt) {
return emojiUnescape(excerpt); return emojiUnescape(excerpt);
}, }
hasExcerpt: notEmpty("excerpt"),
@discourseComputed("excerpt") @discourseComputed("excerpt")
excerptTruncated(excerpt) { excerptTruncated(excerpt) {
return excerpt && excerpt.slice(-8) === "&hellip;"; return excerpt && excerpt.slice(-8) === "&hellip;";
}, }
readLastPost: propertyEqual("last_read_post_number", "highest_post_number"),
canClearPin: and("pinned", "readLastPost"),
canEditTags: or("details.can_edit", "details.can_edit_tags"),
archiveMessage() { archiveMessage() {
this.set("archiving", true); this.set("archiving", true);
@ -617,7 +853,7 @@ const Topic = RestModel.extend({
.finally(() => this.set("archiving", false)); .finally(() => this.set("archiving", false));
return promise; return promise;
}, }
moveToInbox() { moveToInbox() {
this.set("archiving", true); this.set("archiving", true);
@ -633,7 +869,7 @@ const Topic = RestModel.extend({
.finally(() => this.set("archiving", false)); .finally(() => this.set("archiving", false));
return promise; return promise;
}, }
publish() { publish() {
return ajax(`/t/${this.id}/publish`, { return ajax(`/t/${this.id}/publish`, {
@ -642,7 +878,7 @@ const Topic = RestModel.extend({
}) })
.then(() => this.set("destination_category_id", null)) .then(() => this.set("destination_category_id", null))
.catch(popupAjaxError); .catch(popupAjaxError);
}, }
updateDestinationCategory(categoryId) { updateDestinationCategory(categoryId) {
this.set("destination_category_id", categoryId); this.set("destination_category_id", categoryId);
@ -650,7 +886,7 @@ const Topic = RestModel.extend({
type: "PUT", type: "PUT",
data: { category_id: categoryId }, data: { category_id: categoryId },
}); });
}, }
convertTopic(type, opts) { convertTopic(type, opts) {
let args = { type: "PUT" }; let args = { type: "PUT" };
@ -658,13 +894,13 @@ const Topic = RestModel.extend({
args.data = { category_id: opts.categoryId }; args.data = { category_id: opts.categoryId };
} }
return ajax(`/t/${this.id}/convert-topic/${type}`, args); return ajax(`/t/${this.id}/convert-topic/${type}`, args);
}, }
resetBumpDate() { resetBumpDate() {
return ajax(`/t/${this.id}/reset-bump-date`, { type: "PUT" }).catch( return ajax(`/t/${this.id}/reset-bump-date`, { type: "PUT" }).catch(
popupAjaxError popupAjaxError
); );
}, }
updateTags(tags) { updateTags(tags) {
if (!tags || tags.length === 0) { if (!tags || tags.length === 0) {
@ -675,254 +911,9 @@ const Topic = RestModel.extend({
type: "PUT", type: "PUT",
data: { tags }, 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) { function moveResult(result) {
if (result.success) { if (result.success) {
// We should be hesitant to flush the map but moving ids is one rare case // We should be hesitant to flush the map but moving ids is one rare case
@ -952,5 +943,3 @@ export function registerCustomLastUnreadUrlCallback(fn) {
export function clearCustomLastUnreadUrlCallbacks() { export function clearCustomLastUnreadUrlCallbacks() {
_customLastUnreadUrlCallbacks.clear(); _customLastUnreadUrlCallbacks.clear();
} }
export default Topic;