DEV: Convert most JS models to native class syntax (#25608)

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 13:17:36 +00:00 committed by GitHub
parent 234795c70e
commit 6c597b648b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 1417 additions and 1502 deletions

View File

@ -3,8 +3,8 @@ import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import RestModel from "discourse/models/rest"; import RestModel from "discourse/models/rest";
export default RestModel.extend({ export default class ActionSummary extends RestModel {
canToggle: or("can_undo", "can_act"), @or("can_undo", "can_act") canToggle;
// Remove it // Remove it
removeAction() { removeAction() {
@ -14,11 +14,11 @@ export default RestModel.extend({
can_act: true, can_act: true,
can_undo: false, can_undo: false,
}); });
}, }
togglePromise(post) { togglePromise(post) {
return this.acted ? this.undo(post) : this.act(post); return this.acted ? this.undo(post) : this.act(post);
}, }
toggle(post) { toggle(post) {
if (!this.acted) { if (!this.acted) {
@ -28,7 +28,7 @@ export default RestModel.extend({
this.undo(post); this.undo(post);
return false; return false;
} }
}, }
// Perform this action // Perform this action
act(post, opts) { act(post, opts) {
@ -76,7 +76,7 @@ export default RestModel.extend({
popupAjaxError(error); popupAjaxError(error);
this.removeAction(post); this.removeAction(post);
}); });
}, }
// Undo this action // Undo this action
undo(post) { undo(post) {
@ -90,5 +90,5 @@ export default RestModel.extend({
post.updateActionsSummary(result); post.updateActionsSummary(result);
return { acted: false }; return { acted: false };
}); });
}, }
}); }

View File

@ -2,8 +2,8 @@ import { gt, not } from "@ember/object/computed";
import { propertyEqual } from "discourse/lib/computed"; import { propertyEqual } from "discourse/lib/computed";
import RestModel from "discourse/models/rest"; import RestModel from "discourse/models/rest";
export default RestModel.extend({ export default class Archetype extends RestModel {
hasOptions: gt("options.length", 0), @gt("options.length", 0) hasOptions;
isDefault: propertyEqual("id", "site.default_archetype"), @propertyEqual("id", "site.default_archetype") isDefault;
notDefault: not("isDefault"), @not("isDefault") notDefault;
}); }

View File

@ -2,16 +2,12 @@ import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
const AssociatedGroup = EmberObject.extend(); export default class AssociatedGroup extends EmberObject {
static list() {
AssociatedGroup.reopenClass({
list() {
return ajax("/associated_groups") return ajax("/associated_groups")
.then((result) => { .then((result) => {
return result.associated_groups.map((ag) => AssociatedGroup.create(ag)); return result.associated_groups.map((ag) => AssociatedGroup.create(ag));
}) })
.catch(popupAjaxError); .catch(popupAjaxError);
}, }
}); }
export default AssociatedGroup;

View File

@ -2,15 +2,15 @@ import RestModel from "discourse/models/rest";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
export default RestModel.extend({ export default class BadgeGrouping extends RestModel {
@discourseComputed("name") @discourseComputed("name")
i18nNameKey() { i18nNameKey() {
return this.name.toLowerCase().replace(/\s/g, "_"); return this.name.toLowerCase().replace(/\s/g, "_");
}, }
@discourseComputed("name") @discourseComputed("name")
displayName() { displayName() {
const i18nKey = `badges.badge_grouping.${this.i18nNameKey}.name`; const i18nKey = `badges.badge_grouping.${this.i18nNameKey}.name`;
return I18n.t(i18nKey, { defaultValue: this.name }); return I18n.t(i18nKey, { defaultValue: this.name });
}, }
}); }

View File

@ -7,63 +7,8 @@ import RestModel from "discourse/models/rest";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
const Badge = RestModel.extend({ export default class Badge extends RestModel {
newBadge: none("id"), static createFromJson(json) {
image: alias("image_url"),
@discourseComputed
url() {
return getURL(`/badges/${this.id}/${this.slug}`);
},
updateFromJson(json) {
if (json.badge) {
Object.keys(json.badge).forEach((key) => this.set(key, json.badge[key]));
}
if (json.badge_types) {
json.badge_types.forEach((badgeType) => {
if (badgeType.id === this.badge_type_id) {
this.set("badge_type", Object.create(badgeType));
}
});
}
},
@discourseComputed("badge_type.name")
badgeTypeClassName(type) {
type = type || "";
return `badge-type-${type.toLowerCase()}`;
},
save(data) {
let url = "/admin/badges",
type = "POST";
if (this.id) {
// We are updating an existing badge.
url += `/${this.id}`;
type = "PUT";
}
return ajax(url, { type, data }).then((json) => {
this.updateFromJson(json);
return this;
});
},
destroy() {
if (this.newBadge) {
return Promise.resolve();
}
return ajax(`/admin/badges/${this.id}`, {
type: "DELETE",
});
},
});
Badge.reopenClass({
createFromJson(json) {
// Create BadgeType objects. // Create BadgeType objects.
const badgeTypes = {}; const badgeTypes = {};
if ("badge_types" in json) { if ("badge_types" in json) {
@ -103,9 +48,9 @@ Badge.reopenClass({
} else { } else {
return badges; return badges;
} }
}, }
findAll(opts) { static findAll(opts) {
let listable = ""; let listable = "";
if (opts && opts.onlyListable) { if (opts && opts.onlyListable) {
listable = "?only_listable=true"; listable = "?only_listable=true";
@ -114,13 +59,65 @@ Badge.reopenClass({
return ajax(`/badges.json${listable}`, { data: opts }).then((badgesJson) => return ajax(`/badges.json${listable}`, { data: opts }).then((badgesJson) =>
Badge.createFromJson(badgesJson) Badge.createFromJson(badgesJson)
); );
}, }
findById(id) { static findById(id) {
return ajax(`/badges/${id}`).then((badgeJson) => return ajax(`/badges/${id}`).then((badgeJson) =>
Badge.createFromJson(badgeJson) Badge.createFromJson(badgeJson)
); );
}, }
});
export default Badge; @none("id") newBadge;
@alias("image_url") image;
@discourseComputed
url() {
return getURL(`/badges/${this.id}/${this.slug}`);
}
updateFromJson(json) {
if (json.badge) {
Object.keys(json.badge).forEach((key) => this.set(key, json.badge[key]));
}
if (json.badge_types) {
json.badge_types.forEach((badgeType) => {
if (badgeType.id === this.badge_type_id) {
this.set("badge_type", Object.create(badgeType));
}
});
}
}
@discourseComputed("badge_type.name")
badgeTypeClassName(type) {
type = type || "";
return `badge-type-${type.toLowerCase()}`;
}
save(data) {
let url = "/admin/badges",
type = "POST";
if (this.id) {
// We are updating an existing badge.
url += `/${this.id}`;
type = "PUT";
}
return ajax(url, { type, data }).then((json) => {
this.updateFromJson(json);
return this;
});
}
destroy() {
if (this.newBadge) {
return Promise.resolve();
}
return ajax(`/admin/badges/${this.id}`, {
type: "DELETE",
});
}
}

View File

@ -25,13 +25,33 @@ export const AUTO_DELETE_PREFERENCES = {
export const NO_REMINDER_ICON = "bookmark"; export const NO_REMINDER_ICON = "bookmark";
export const WITH_REMINDER_ICON = "discourse-bookmark-clock"; export const WITH_REMINDER_ICON = "discourse-bookmark-clock";
const Bookmark = RestModel.extend({ export default class Bookmark extends RestModel {
newBookmark: none("id"), static create(args) {
args = args || {};
args.currentUser = args.currentUser || User.current();
args.user = User.create(args.user);
return super.create(args);
}
static createFor(user, bookmarkableType, bookmarkableId) {
return Bookmark.create({
bookmarkable_type: bookmarkableType,
bookmarkable_id: bookmarkableId,
user_id: user.id,
auto_delete_preference: user.user_option.bookmark_auto_delete_preference,
});
}
static async applyTransformations(bookmarks) {
await applyModelTransformations("bookmark", bookmarks);
}
@none("id") newBookmark;
@computed @computed
get url() { get url() {
return getURL(`/bookmarks/${this.id}`); return getURL(`/bookmarks/${this.id}`);
}, }
destroy() { destroy() {
if (this.newBookmark) { if (this.newBookmark) {
@ -41,14 +61,14 @@ const Bookmark = RestModel.extend({
return ajax(this.url, { return ajax(this.url, {
type: "DELETE", type: "DELETE",
}); });
}, }
attachedTo() { attachedTo() {
return { return {
target: this.bookmarkable_type.toLowerCase(), target: this.bookmarkable_type.toLowerCase(),
targetId: this.bookmarkable_id, targetId: this.bookmarkable_id,
}; };
}, }
togglePin() { togglePin() {
if (this.newBookmark) { if (this.newBookmark) {
@ -58,16 +78,16 @@ const Bookmark = RestModel.extend({
return ajax(this.url + "/toggle_pin", { return ajax(this.url + "/toggle_pin", {
type: "PUT", type: "PUT",
}); });
}, }
pinAction() { pinAction() {
return this.pinned ? "unpin" : "pin"; return this.pinned ? "unpin" : "pin";
}, }
@discourseComputed("highest_post_number", "url") @discourseComputed("highest_post_number", "url")
lastPostUrl(highestPostNumber) { lastPostUrl(highestPostNumber) {
return this.urlForPostNumber(highestPostNumber); return this.urlForPostNumber(highestPostNumber);
}, }
// Helper to build a Url with a post number // Helper to build a Url with a post number
urlForPostNumber(postNumber) { urlForPostNumber(postNumber) {
@ -76,7 +96,7 @@ const Bookmark = RestModel.extend({
url += `/${postNumber}`; url += `/${postNumber}`;
} }
return url; return url;
}, }
// returns createdAt if there's no bumped date // returns createdAt if there's no bumped date
@discourseComputed("bumped_at", "createdAt") @discourseComputed("bumped_at", "createdAt")
@ -86,7 +106,7 @@ const Bookmark = RestModel.extend({
} else { } else {
return createdAt; return createdAt;
} }
}, }
@discourseComputed("bumpedAt", "createdAt") @discourseComputed("bumpedAt", "createdAt")
bumpedAtTitle(bumpedAt, createdAt) { bumpedAtTitle(bumpedAt, createdAt) {
@ -101,7 +121,7 @@ const Bookmark = 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("name", "reminder_at") @discourseComputed("name", "reminder_at")
reminderTitle(name, reminderAt) { reminderTitle(name, reminderAt) {
@ -118,12 +138,12 @@ const Bookmark = RestModel.extend({
return I18n.t("bookmarks.created_generic", { return I18n.t("bookmarks.created_generic", {
name: name || "", name: name || "",
}); });
}, }
@discourseComputed("created_at") @discourseComputed("created_at")
createdAt(created_at) { createdAt(created_at) {
return new Date(created_at); return new Date(created_at);
}, }
@discourseComputed("tags") @discourseComputed("tags")
visibleListTags(tags) { visibleListTags(tags) {
@ -141,12 +161,12 @@ const Bookmark = RestModel.extend({
}); });
return newTags; return newTags;
}, }
@computed("category_id") @computed("category_id")
get category() { get category() {
return Category.findById(this.category_id); return Category.findById(this.category_id);
}, }
@discourseComputed("reminder_at", "currentUser") @discourseComputed("reminder_at", "currentUser")
formattedReminder(bookmarkReminderAt, currentUser) { formattedReminder(bookmarkReminderAt, currentUser) {
@ -156,12 +176,12 @@ const Bookmark = RestModel.extend({
currentUser?.user_option?.timezone || moment.tz.guess() currentUser?.user_option?.timezone || moment.tz.guess()
) )
); );
}, }
@discourseComputed("reminder_at") @discourseComputed("reminder_at")
reminderAtExpired(bookmarkReminderAt) { reminderAtExpired(bookmarkReminderAt) {
return moment(bookmarkReminderAt) < moment(); return moment(bookmarkReminderAt) < moment();
}, }
@discourseComputed() @discourseComputed()
topicForList() { topicForList() {
@ -178,34 +198,10 @@ const Bookmark = RestModel.extend({
last_read_post_number: this.last_read_post_number, last_read_post_number: this.last_read_post_number,
highest_post_number: this.highest_post_number, highest_post_number: this.highest_post_number,
}); });
}, }
@discourseComputed("bookmarkable_type") @discourseComputed("bookmarkable_type")
bookmarkableTopicAlike(bookmarkable_type) { bookmarkableTopicAlike(bookmarkable_type) {
return ["Topic", "Post"].includes(bookmarkable_type); return ["Topic", "Post"].includes(bookmarkable_type);
}, }
}); }
Bookmark.reopenClass({
create(args) {
args = args || {};
args.currentUser = args.currentUser || User.current();
args.user = User.create(args.user);
return this._super(args);
},
createFor(user, bookmarkableType, bookmarkableId) {
return Bookmark.create({
bookmarkable_type: bookmarkableType,
bookmarkable_id: bookmarkableId,
user_id: user.id,
auto_delete_preference: user.user_option.bookmark_auto_delete_preference,
});
},
async applyTransformations(bookmarks) {
await applyModelTransformations("bookmark", bookmarks);
},
});
export default Bookmark;

View File

@ -8,38 +8,8 @@ import Topic from "discourse/models/topic";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
const CategoryList = ArrayProxy.extend({ export default class CategoryList extends ArrayProxy {
init() { static categoriesFrom(store, result, parentCategory = null) {
this.set("content", this.categories || []);
this._super(...arguments);
this.set("page", 1);
this.set("fetchedLastPage", false);
},
@bind
async loadMore() {
if (this.isLoading || this.fetchedLastPage) {
return;
}
this.set("isLoading", true);
const data = { page: this.page + 1 };
const result = await ajax("/categories.json", { data });
this.set("page", data.page);
if (result.category_list.categories.length === 0) {
this.set("fetchedLastPage", true);
}
this.set("isLoading", false);
const newCategoryList = CategoryList.categoriesFrom(this.store, result);
newCategoryList.forEach((c) => this.categories.pushObject(c));
},
});
CategoryList.reopenClass({
categoriesFrom(store, result, parentCategory = null) {
// Find the period that is most relevant // Find the period that is most relevant
const statPeriod = const statPeriod =
["week", "month"].find( ["week", "month"].find(
@ -67,9 +37,9 @@ CategoryList.reopenClass({
} }
}); });
return categories; return categories;
}, }
_buildCategoryResult(c, statPeriod) { static _buildCategoryResult(c, statPeriod) {
if (c.parent_category_id) { if (c.parent_category_id) {
c.parentCategory = Category.findById(c.parent_category_id); c.parentCategory = Category.findById(c.parent_category_id);
} }
@ -126,9 +96,9 @@ CategoryList.reopenClass({
const record = Site.current().updateCategory(c); const record = Site.current().updateCategory(c);
record.setupGroupsAndPermissions(); record.setupGroupsAndPermissions();
return record; return record;
}, }
listForParent(store, category) { static listForParent(store, category) {
return ajax( return ajax(
`/categories.json?parent_category_id=${category.get("id")}` `/categories.json?parent_category_id=${category.get("id")}`
).then((result) => ).then((result) =>
@ -138,9 +108,9 @@ CategoryList.reopenClass({
parentCategory: category, parentCategory: category,
}) })
); );
}, }
list(store) { static list(store) {
return PreloadStore.getAndRemove("categories_list", () => return PreloadStore.getAndRemove("categories_list", () =>
ajax("/categories.json") ajax("/categories.json")
).then((result) => ).then((result) =>
@ -151,7 +121,33 @@ CategoryList.reopenClass({
can_create_topic: result.category_list.can_create_topic, can_create_topic: result.category_list.can_create_topic,
}) })
); );
}, }
});
export default CategoryList; init() {
this.set("content", this.categories || []);
super.init(...arguments);
this.set("page", 1);
this.set("fetchedLastPage", false);
}
@bind
async loadMore() {
if (this.isLoading || this.fetchedLastPage) {
return;
}
this.set("isLoading", true);
const data = { page: this.page + 1 };
const result = await ajax("/categories.json", { data });
this.set("page", data.page);
if (result.category_list.categories.length === 0) {
this.set("fetchedLastPage", true);
}
this.set("isLoading", false);
const newCategoryList = CategoryList.categoriesFrom(this.store, result);
newCategoryList.forEach((c) => this.categories.pushObject(c));
}
}

View File

@ -14,370 +14,9 @@ import { MultiCache } from "discourse-common/utils/multi-cache";
const STAFF_GROUP_NAME = "staff"; const STAFF_GROUP_NAME = "staff";
const CATEGORY_ASYNC_SEARCH_CACHE = {}; const CATEGORY_ASYNC_SEARCH_CACHE = {};
const Category = RestModel.extend({ export default class Category extends RestModel {
permissions: null,
@on("init")
setupGroupsAndPermissions() {
const availableGroups = this.available_groups;
if (!availableGroups) {
return;
}
this.set("availableGroups", availableGroups);
const groupPermissions = this.group_permissions;
if (groupPermissions) {
this.set(
"permissions",
groupPermissions.map((elem) => {
availableGroups.removeObject(elem.group_name);
return elem;
})
);
}
},
@discourseComputed("required_tag_groups", "minimum_required_tags")
minimumRequiredTags() {
if (this.required_tag_groups?.length > 0) {
// it should require the max between the bare minimum set in the category and the sum of the min_count of the
// required_tag_groups
return Math.max(
this.required_tag_groups.reduce((sum, rtg) => sum + rtg.min_count, 0),
this.minimum_required_tags || 0
);
} else {
return this.minimum_required_tags > 0 ? this.minimum_required_tags : null;
}
},
@discourseComputed
availablePermissions() {
return [
PermissionType.create({ id: PermissionType.FULL }),
PermissionType.create({ id: PermissionType.CREATE_POST }),
PermissionType.create({ id: PermissionType.READONLY }),
];
},
@discourseComputed("id")
searchContext(id) {
return { type: "category", id, category: this };
},
@discourseComputed("parentCategory.ancestors")
ancestors(parentAncestors) {
return [...(parentAncestors || []), this];
},
@discourseComputed("parentCategory.level")
level(parentLevel) {
if (!parentLevel) {
return parentLevel === 0 ? 1 : 0;
} else {
return parentLevel + 1;
}
},
@discourseComputed("has_children", "subcategories")
isParent(hasChildren, subcategories) {
return hasChildren || (subcategories && subcategories.length > 0);
},
@discourseComputed("subcategories")
isGrandParent(subcategories) {
return (
subcategories &&
subcategories.some(
(cat) => cat.subcategories && cat.subcategories.length > 0
)
);
},
@discourseComputed("notification_level")
isMuted(notificationLevel) {
return notificationLevel === NotificationLevels.MUTED;
},
@discourseComputed("isMuted", "subcategories")
isHidden(isMuted, subcategories) {
if (!isMuted) {
return false;
} else if (!subcategories) {
return true;
}
if (subcategories.some((cat) => !cat.isHidden)) {
return false;
}
return true;
},
@discourseComputed("isMuted", "subcategories")
hasMuted(isMuted, subcategories) {
if (isMuted) {
return true;
} else if (!subcategories) {
return false;
}
if (subcategories.some((cat) => cat.hasMuted)) {
return true;
}
return false;
},
@discourseComputed("notification_level")
notificationLevelString(notificationLevel) {
// Get the key from the value
const notificationLevelString = Object.keys(NotificationLevels).find(
(key) => NotificationLevels[key] === notificationLevel
);
if (notificationLevelString) {
return notificationLevelString.toLowerCase();
}
},
@discourseComputed("name")
path() {
return `/c/${Category.slugFor(this)}/${this.id}`;
},
@discourseComputed("path")
url(path) {
return getURL(path);
},
@discourseComputed
fullSlug() {
return Category.slugFor(this).replace(/\//g, "-");
},
@discourseComputed("name")
nameLower(name) {
return name.toLowerCase();
},
@discourseComputed("url")
unreadUrl(url) {
return `${url}/l/unread`;
},
@discourseComputed("url")
newUrl(url) {
return `${url}/l/new`;
},
@discourseComputed("color", "text_color")
style(color, textColor) {
return `background-color: #${color}; color: #${textColor}`;
},
@discourseComputed("topic_count")
moreTopics(topicCount) {
return topicCount > (this.num_featured_topics || 2);
},
@discourseComputed("topic_count", "subcategories.[]")
totalTopicCount(topicCount, subcategories) {
if (subcategories) {
subcategories.forEach((subcategory) => {
topicCount += subcategory.topic_count;
});
}
return topicCount;
},
@discourseComputed("default_slow_mode_seconds")
defaultSlowModeMinutes(seconds) {
return seconds ? seconds / 60 : null;
},
@discourseComputed("notification_level")
isTracked(notificationLevel) {
return notificationLevel >= NotificationLevels.TRACKING;
},
get unreadTopicsCount() {
return this.topicTrackingState.countUnread({ categoryId: this.id });
},
get newTopicsCount() {
return this.topicTrackingState.countNew({ categoryId: this.id });
},
save() {
const id = this.id;
const url = id ? `/categories/${id}` : "/categories";
return ajax(url, {
contentType: "application/json",
data: JSON.stringify({
name: this.name,
slug: this.slug,
color: this.color,
text_color: this.text_color,
secure: this.secure,
permissions: this._permissionsForUpdate(),
auto_close_hours: this.auto_close_hours,
auto_close_based_on_last_post: this.get(
"auto_close_based_on_last_post"
),
default_slow_mode_seconds: this.default_slow_mode_seconds,
position: this.position,
email_in: this.email_in,
email_in_allow_strangers: this.email_in_allow_strangers,
mailinglist_mirror: this.mailinglist_mirror,
parent_category_id: this.parent_category_id,
uploaded_logo_id: this.get("uploaded_logo.id"),
uploaded_logo_dark_id: this.get("uploaded_logo_dark.id"),
uploaded_background_id: this.get("uploaded_background.id"),
uploaded_background_dark_id: this.get("uploaded_background_dark.id"),
allow_badges: this.allow_badges,
category_setting_attributes: this.category_setting,
custom_fields: this.custom_fields,
topic_template: this.topic_template,
form_template_ids: this.form_template_ids,
all_topics_wiki: this.all_topics_wiki,
allow_unlimited_owner_edits_on_first_post:
this.allow_unlimited_owner_edits_on_first_post,
allowed_tags: this.allowed_tags,
allowed_tag_groups: this.allowed_tag_groups,
allow_global_tags: this.allow_global_tags,
required_tag_groups: this.required_tag_groups,
sort_order: this.sort_order,
sort_ascending: this.sort_ascending,
topic_featured_link_allowed: this.topic_featured_link_allowed,
show_subcategory_list: this.show_subcategory_list,
num_featured_topics: this.num_featured_topics,
default_view: this.default_view,
subcategory_list_style: this.subcategory_list_style,
default_top_period: this.default_top_period,
minimum_required_tags: this.minimum_required_tags,
navigate_to_first_post_after_read: this.get(
"navigate_to_first_post_after_read"
),
search_priority: this.search_priority,
reviewable_by_group_name: this.reviewable_by_group_name,
read_only_banner: this.read_only_banner,
default_list_filter: this.default_list_filter,
}),
type: id ? "PUT" : "POST",
});
},
_permissionsForUpdate() {
const permissions = this.permissions;
let rval = {};
if (permissions.length) {
permissions.forEach((p) => (rval[p.group_name] = p.permission_type));
} else {
// empty permissions => staff-only access
rval[STAFF_GROUP_NAME] = PermissionType.FULL;
}
return rval;
},
destroy() {
return ajax(`/categories/${this.id || this.slug}`, {
type: "DELETE",
});
},
addPermission(permission) {
this.permissions.addObject(permission);
this.availableGroups.removeObject(permission.group_name);
},
removePermission(group_name) {
const permission = this.permissions.findBy("group_name", group_name);
if (permission) {
this.permissions.removeObject(permission);
this.availableGroups.addObject(group_name);
}
},
updatePermission(group_name, type) {
this.permissions.forEach((p, i) => {
if (p.group_name === group_name) {
this.set(`permissions.${i}.permission_type`, type);
}
});
},
@discourseComputed("topics")
latestTopic(topics) {
if (topics && topics.length) {
return topics[0];
}
},
@discourseComputed("topics")
featuredTopics(topics) {
if (topics && topics.length) {
return topics.slice(0, this.num_featured_topics || 2);
}
},
setNotification(notification_level) {
User.currentProp(
"muted_category_ids",
User.current().calculateMutedIds(
notification_level,
this.id,
"muted_category_ids"
)
);
const url = `/category/${this.id}/notifications`;
return ajax(url, { data: { notification_level }, type: "POST" }).then(
(data) => {
User.current().set(
"indirectly_muted_category_ids",
data.indirectly_muted_category_ids
);
this.set("notification_level", notification_level);
this.notifyPropertyChange("notification_level");
}
);
},
@discourseComputed("id")
isUncategorizedCategory(id) {
return Category.isUncategorized(id);
},
get canCreateTopic() {
return this.permission === PermissionType.FULL;
},
get subcategoryWithCreateTopicPermission() {
return this.subcategories?.find(
(subcategory) => subcategory.canCreateTopic
);
},
});
let _uncategorized;
const categoryMultiCache = new MultiCache(async (ids) => {
const result = await ajax("/categories/find", { data: { ids } });
return new Map(
result["categories"].map((category) => [category.id, category])
);
});
export function resetCategoryCache() {
categoryMultiCache.reset();
}
Category.reopenClass({
// Sort subcategories directly under parents // Sort subcategories directly under parents
sortCategories(categories) { static sortCategories(categories) {
const children = new Map(); const children = new Map();
categories.forEach((category) => { categories.forEach((category) => {
@ -392,20 +31,20 @@ Category.reopenClass({
values.flatMap((c) => [c, reduce(children.get(c.id) || [])]).flat(); values.flatMap((c) => [c, reduce(children.get(c.id) || [])]).flat();
return reduce(children.get(-1) || []); return reduce(children.get(-1) || []);
}, }
isUncategorized(categoryId) { static isUncategorized(categoryId) {
return categoryId === Site.currentProp("uncategorized_category_id"); return categoryId === Site.currentProp("uncategorized_category_id");
}, }
slugEncoded() { static slugEncoded() {
let siteSettings = getOwnerWithFallback(this).lookup( let siteSettings = getOwnerWithFallback(this).lookup(
"service:site-settings" "service:site-settings"
); );
return siteSettings.slug_generation_method === "encoded"; return siteSettings.slug_generation_method === "encoded";
}, }
findUncategorized() { static findUncategorized() {
_uncategorized = _uncategorized =
_uncategorized || _uncategorized ||
Category.list().findBy( Category.list().findBy(
@ -413,9 +52,9 @@ Category.reopenClass({
Site.currentProp("uncategorized_category_id") Site.currentProp("uncategorized_category_id")
); );
return _uncategorized; return _uncategorized;
}, }
slugFor(category, separator = "/", depth = 3) { static slugFor(category, separator = "/", depth = 3) {
if (!category) { if (!category) {
return ""; return "";
} }
@ -434,21 +73,21 @@ Category.reopenClass({
return !slug || slug.trim().length === 0 return !slug || slug.trim().length === 0
? `${result}${id}-category` ? `${result}${id}-category`
: result + slug; : result + slug;
}, }
list() { static list() {
return Site.currentProp("categoriesList"); return Site.currentProp("categoriesList");
}, }
listByActivity() { static listByActivity() {
return Site.currentProp("sortedCategories"); return Site.currentProp("sortedCategories");
}, }
_idMap() { static _idMap() {
return Site.currentProp("categoriesById"); return Site.currentProp("categoriesById");
}, }
findSingleBySlug(slug) { static findSingleBySlug(slug) {
if (!this.slugEncoded()) { if (!this.slugEncoded()) {
return Category.list().find((c) => Category.slugFor(c) === slug); return Category.list().find((c) => Category.slugFor(c) === slug);
} else { } else {
@ -456,16 +95,16 @@ Category.reopenClass({
(c) => Category.slugFor(c) === encodeURI(slug) (c) => Category.slugFor(c) === encodeURI(slug)
); );
} }
}, }
findById(id) { static findById(id) {
if (!id) { if (!id) {
return; return;
} }
return Category._idMap()[id]; return Category._idMap()[id];
}, }
findByIds(ids = []) { static findByIds(ids = []) {
const categories = []; const categories = [];
ids.forEach((id) => { ids.forEach((id) => {
const found = Category.findById(id); const found = Category.findById(id);
@ -474,14 +113,14 @@ Category.reopenClass({
} }
}); });
return categories; return categories;
}, }
hasAsyncFoundAll(ids) { static hasAsyncFoundAll(ids) {
const loadedCategoryIds = Site.current().loadedCategoryIds || new Set(); const loadedCategoryIds = Site.current().loadedCategoryIds || new Set();
return ids.every((id) => loadedCategoryIds.has(id)); return ids.every((id) => loadedCategoryIds.has(id));
}, }
async asyncFindByIds(ids = []) { static async asyncFindByIds(ids = []) {
ids = ids.map((x) => parseInt(x, 10)); ids = ids.map((x) => parseInt(x, 10));
if (!Site.current().lazy_load_categories) { if (!Site.current().lazy_load_categories) {
@ -508,9 +147,9 @@ Category.reopenClass({
Site.current().set("loadedCategoryIds", loadedCategoryIds); Site.current().set("loadedCategoryIds", loadedCategoryIds);
return categories; return categories;
}, }
findBySlugAndParent(slug, parentCategory) { static findBySlugAndParent(slug, parentCategory) {
if (this.slugEncoded()) { if (this.slugEncoded()) {
slug = encodeURI(slug); slug = encodeURI(slug);
} }
@ -520,9 +159,9 @@ Category.reopenClass({
(category.parentCategory || null) === parentCategory (category.parentCategory || null) === parentCategory
); );
}); });
}, }
findBySlugPath(slugPath) { static findBySlugPath(slugPath) {
let category = null; let category = null;
for (const slug of slugPath) { for (const slug of slugPath) {
@ -534,9 +173,9 @@ Category.reopenClass({
} }
return category; return category;
}, }
async asyncFindBySlugPathWithID(slugPathWithID) { static async asyncFindBySlugPathWithID(slugPathWithID) {
const result = await ajax("/categories/find", { const result = await ajax("/categories/find", {
data: { slug_path_with_id: slugPathWithID }, data: { slug_path_with_id: slugPathWithID },
}); });
@ -546,9 +185,9 @@ Category.reopenClass({
); );
return categories[categories.length - 1]; return categories[categories.length - 1];
}, }
findBySlugPathWithID(slugPathWithID) { static findBySlugPathWithID(slugPathWithID) {
let parts = slugPathWithID.split("/").filter(Boolean); let parts = slugPathWithID.split("/").filter(Boolean);
// slugs found by star/glob pathing in ember do not automatically url decode - ensure that these are decoded // slugs found by star/glob pathing in ember do not automatically url decode - ensure that these are decoded
if (this.slugEncoded()) { if (this.slugEncoded()) {
@ -575,9 +214,9 @@ Category.reopenClass({
} }
return category; return category;
}, }
findBySlug(slug, parentSlug) { static findBySlug(slug, parentSlug) {
const categories = Category.list(); const categories = Category.list();
let category; let category;
@ -615,34 +254,34 @@ Category.reopenClass({
} }
return category; return category;
}, }
fetchVisibleGroups(id) { static fetchVisibleGroups(id) {
return ajax(`/c/${id}/visible_groups.json`); return ajax(`/c/${id}/visible_groups.json`);
}, }
reloadById(id) { static reloadById(id) {
return ajax(`/c/${id}/show.json`); return ajax(`/c/${id}/show.json`);
}, }
reloadBySlugPath(slugPath) { static reloadBySlugPath(slugPath) {
return ajax(`/c/${slugPath}/find_by_slug.json`); return ajax(`/c/${slugPath}/find_by_slug.json`);
}, }
reloadCategoryWithPermissions(params, store, site) { static reloadCategoryWithPermissions(params, store, site) {
return this.reloadBySlugPath(params.slug).then((result) => return this.reloadBySlugPath(params.slug).then((result) =>
this._includePermissions(result.category, store, site) this._includePermissions(result.category, store, site)
); );
}, }
_includePermissions(category, store, site) { static _includePermissions(category, store, site) {
const record = store.createRecord("category", category); const record = store.createRecord("category", category);
record.setupGroupsAndPermissions(); record.setupGroupsAndPermissions();
site.updateCategory(record); site.updateCategory(record);
return record; return record;
}, }
search(term, opts) { static search(term, opts) {
let limit = 5; let limit = 5;
let parentCategoryId; let parentCategoryId;
@ -713,9 +352,9 @@ Category.reopenClass({
} }
return data.sortBy("read_restricted"); return data.sortBy("read_restricted");
}, }
async asyncSearch(term, opts) { static async asyncSearch(term, opts) {
opts ||= {}; opts ||= {};
const data = { const data = {
@ -735,7 +374,364 @@ Category.reopenClass({
return result["categories"].map((category) => return result["categories"].map((category) =>
Site.current().updateCategory(category) Site.current().updateCategory(category)
); );
}, }
permissions = null;
@on("init")
setupGroupsAndPermissions() {
const availableGroups = this.available_groups;
if (!availableGroups) {
return;
}
this.set("availableGroups", availableGroups);
const groupPermissions = this.group_permissions;
if (groupPermissions) {
this.set(
"permissions",
groupPermissions.map((elem) => {
availableGroups.removeObject(elem.group_name);
return elem;
})
);
}
}
@discourseComputed("required_tag_groups", "minimum_required_tags")
minimumRequiredTags() {
if (this.required_tag_groups?.length > 0) {
// it should require the max between the bare minimum set in the category and the sum of the min_count of the
// required_tag_groups
return Math.max(
this.required_tag_groups.reduce((sum, rtg) => sum + rtg.min_count, 0),
this.minimum_required_tags || 0
);
} else {
return this.minimum_required_tags > 0 ? this.minimum_required_tags : null;
}
}
@discourseComputed
availablePermissions() {
return [
PermissionType.create({ id: PermissionType.FULL }),
PermissionType.create({ id: PermissionType.CREATE_POST }),
PermissionType.create({ id: PermissionType.READONLY }),
];
}
@discourseComputed("id")
searchContext(id) {
return { type: "category", id, category: this };
}
@discourseComputed("parentCategory.ancestors")
ancestors(parentAncestors) {
return [...(parentAncestors || []), this];
}
@discourseComputed("parentCategory.level")
level(parentLevel) {
if (!parentLevel) {
return parentLevel === 0 ? 1 : 0;
} else {
return parentLevel + 1;
}
}
@discourseComputed("has_children", "subcategories")
isParent(hasChildren, subcategories) {
return hasChildren || (subcategories && subcategories.length > 0);
}
@discourseComputed("subcategories")
isGrandParent(subcategories) {
return (
subcategories &&
subcategories.some(
(cat) => cat.subcategories && cat.subcategories.length > 0
)
);
}
@discourseComputed("notification_level")
isMuted(notificationLevel) {
return notificationLevel === NotificationLevels.MUTED;
}
@discourseComputed("isMuted", "subcategories")
isHidden(isMuted, subcategories) {
if (!isMuted) {
return false;
} else if (!subcategories) {
return true;
}
if (subcategories.some((cat) => !cat.isHidden)) {
return false;
}
return true;
}
@discourseComputed("isMuted", "subcategories")
hasMuted(isMuted, subcategories) {
if (isMuted) {
return true;
} else if (!subcategories) {
return false;
}
if (subcategories.some((cat) => cat.hasMuted)) {
return true;
}
return false;
}
@discourseComputed("notification_level")
notificationLevelString(notificationLevel) {
// Get the key from the value
const notificationLevelString = Object.keys(NotificationLevels).find(
(key) => NotificationLevels[key] === notificationLevel
);
if (notificationLevelString) {
return notificationLevelString.toLowerCase();
}
}
@discourseComputed("name")
path() {
return `/c/${Category.slugFor(this)}/${this.id}`;
}
@discourseComputed("path")
url(path) {
return getURL(path);
}
@discourseComputed
fullSlug() {
return Category.slugFor(this).replace(/\//g, "-");
}
@discourseComputed("name")
nameLower(name) {
return name.toLowerCase();
}
@discourseComputed("url")
unreadUrl(url) {
return `${url}/l/unread`;
}
@discourseComputed("url")
newUrl(url) {
return `${url}/l/new`;
}
@discourseComputed("color", "text_color")
style(color, textColor) {
return `background-color: #${color}; color: #${textColor}`;
}
@discourseComputed("topic_count")
moreTopics(topicCount) {
return topicCount > (this.num_featured_topics || 2);
}
@discourseComputed("topic_count", "subcategories.[]")
totalTopicCount(topicCount, subcategories) {
if (subcategories) {
subcategories.forEach((subcategory) => {
topicCount += subcategory.topic_count;
});
}
return topicCount;
}
@discourseComputed("default_slow_mode_seconds")
defaultSlowModeMinutes(seconds) {
return seconds ? seconds / 60 : null;
}
@discourseComputed("notification_level")
isTracked(notificationLevel) {
return notificationLevel >= NotificationLevels.TRACKING;
}
get unreadTopicsCount() {
return this.topicTrackingState.countUnread({ categoryId: this.id });
}
get newTopicsCount() {
return this.topicTrackingState.countNew({ categoryId: this.id });
}
save() {
const id = this.id;
const url = id ? `/categories/${id}` : "/categories";
return ajax(url, {
contentType: "application/json",
data: JSON.stringify({
name: this.name,
slug: this.slug,
color: this.color,
text_color: this.text_color,
secure: this.secure,
permissions: this._permissionsForUpdate(),
auto_close_hours: this.auto_close_hours,
auto_close_based_on_last_post: this.get(
"auto_close_based_on_last_post"
),
default_slow_mode_seconds: this.default_slow_mode_seconds,
position: this.position,
email_in: this.email_in,
email_in_allow_strangers: this.email_in_allow_strangers,
mailinglist_mirror: this.mailinglist_mirror,
parent_category_id: this.parent_category_id,
uploaded_logo_id: this.get("uploaded_logo.id"),
uploaded_logo_dark_id: this.get("uploaded_logo_dark.id"),
uploaded_background_id: this.get("uploaded_background.id"),
uploaded_background_dark_id: this.get("uploaded_background_dark.id"),
allow_badges: this.allow_badges,
category_setting_attributes: this.category_setting,
custom_fields: this.custom_fields,
topic_template: this.topic_template,
form_template_ids: this.form_template_ids,
all_topics_wiki: this.all_topics_wiki,
allow_unlimited_owner_edits_on_first_post:
this.allow_unlimited_owner_edits_on_first_post,
allowed_tags: this.allowed_tags,
allowed_tag_groups: this.allowed_tag_groups,
allow_global_tags: this.allow_global_tags,
required_tag_groups: this.required_tag_groups,
sort_order: this.sort_order,
sort_ascending: this.sort_ascending,
topic_featured_link_allowed: this.topic_featured_link_allowed,
show_subcategory_list: this.show_subcategory_list,
num_featured_topics: this.num_featured_topics,
default_view: this.default_view,
subcategory_list_style: this.subcategory_list_style,
default_top_period: this.default_top_period,
minimum_required_tags: this.minimum_required_tags,
navigate_to_first_post_after_read: this.get(
"navigate_to_first_post_after_read"
),
search_priority: this.search_priority,
reviewable_by_group_name: this.reviewable_by_group_name,
read_only_banner: this.read_only_banner,
default_list_filter: this.default_list_filter,
}),
type: id ? "PUT" : "POST",
});
}
_permissionsForUpdate() {
const permissions = this.permissions;
let rval = {};
if (permissions.length) {
permissions.forEach((p) => (rval[p.group_name] = p.permission_type));
} else {
// empty permissions => staff-only access
rval[STAFF_GROUP_NAME] = PermissionType.FULL;
}
return rval;
}
destroy() {
return ajax(`/categories/${this.id || this.slug}`, {
type: "DELETE",
});
}
addPermission(permission) {
this.permissions.addObject(permission);
this.availableGroups.removeObject(permission.group_name);
}
removePermission(group_name) {
const permission = this.permissions.findBy("group_name", group_name);
if (permission) {
this.permissions.removeObject(permission);
this.availableGroups.addObject(group_name);
}
}
updatePermission(group_name, type) {
this.permissions.forEach((p, i) => {
if (p.group_name === group_name) {
this.set(`permissions.${i}.permission_type`, type);
}
});
}
@discourseComputed("topics")
latestTopic(topics) {
if (topics && topics.length) {
return topics[0];
}
}
@discourseComputed("topics")
featuredTopics(topics) {
if (topics && topics.length) {
return topics.slice(0, this.num_featured_topics || 2);
}
}
setNotification(notification_level) {
User.currentProp(
"muted_category_ids",
User.current().calculateMutedIds(
notification_level,
this.id,
"muted_category_ids"
)
);
const url = `/category/${this.id}/notifications`;
return ajax(url, { data: { notification_level }, type: "POST" }).then(
(data) => {
User.current().set(
"indirectly_muted_category_ids",
data.indirectly_muted_category_ids
);
this.set("notification_level", notification_level);
this.notifyPropertyChange("notification_level");
}
);
}
@discourseComputed("id")
isUncategorizedCategory(id) {
return Category.isUncategorized(id);
}
get canCreateTopic() {
return this.permission === PermissionType.FULL;
}
get subcategoryWithCreateTopicPermission() {
return this.subcategories?.find(
(subcategory) => subcategory.canCreateTopic
);
}
}
let _uncategorized;
const categoryMultiCache = new MultiCache(async (ids) => {
const result = await ajax("/categories/find", { data: { ids } });
return new Map(
result["categories"].map((category) => [category.id, category])
);
}); });
export default Category; export function resetCategoryCache() {
categoryMultiCache.reset();
}

View File

@ -1,26 +1,24 @@
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
const Draft = EmberObject.extend(); export default class Draft extends EmberObject {
static clear(key, sequence) {
Draft.reopenClass({
clear(key, sequence) {
return ajax(`/drafts/${key}.json`, { return ajax(`/drafts/${key}.json`, {
type: "DELETE", type: "DELETE",
data: { draft_key: key, sequence }, data: { draft_key: key, sequence },
}); });
}, }
get(key) { static get(key) {
return ajax(`/drafts/${key}.json`); return ajax(`/drafts/${key}.json`);
}, }
getLocal(key, current) { static getLocal(key, current) {
// TODO: implement this // TODO: implement this
return current; return current;
}, }
save(key, sequence, data, clientId, { forceSave = false } = {}) { static save(key, sequence, data, clientId, { forceSave = false } = {}) {
data = typeof data === "string" ? data : JSON.stringify(data); data = typeof data === "string" ? data : JSON.stringify(data);
return ajax("/drafts.json", { return ajax("/drafts.json", {
type: "POST", type: "POST",
@ -33,7 +31,5 @@ Draft.reopenClass({
}, },
ignoreUnsent: false, ignoreUnsent: false,
}); });
}, }
}); }
export default Draft;

View File

@ -2,9 +2,9 @@ import RestModel from "discourse/models/rest";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import I18n from "discourse-i18n"; import I18n from "discourse-i18n";
export default RestModel.extend({ export default class GroupHistory extends RestModel {
@discourseComputed("action") @discourseComputed("action")
actionTitle(action) { actionTitle(action) {
return I18n.t(`group_histories.actions.${action}`); return I18n.t(`group_histories.actions.${action}`);
}, }
}); }

View File

@ -11,34 +11,56 @@ import Topic from "discourse/models/topic";
import User from "discourse/models/user"; import User from "discourse/models/user";
import discourseComputed, { observes } from "discourse-common/utils/decorators"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
const Group = RestModel.extend({ export default class Group extends RestModel {
user_count: 0, static findAll(opts) {
limit: null, return ajax("/groups/search.json", { data: opts }).then((groups) =>
offset: null, groups.map((g) => Group.create(g))
);
}
request_count: 0, static loadMembers(name, opts) {
requestersLimit: null, return ajax(`/groups/${name}/members.json`, { data: opts });
requestersOffset: null, }
static mentionable(name) {
return ajax(`/groups/${name}/mentionable`);
}
static messageable(name) {
return ajax(`/groups/${name}/messageable`);
}
static checkName(name) {
return ajax("/groups/check-name", { data: { group_name: name } });
}
user_count = 0;
limit = null;
offset = null;
request_count = 0;
requestersLimit = null;
requestersOffset = null;
@equal("mentionable_level", 99) canEveryoneMention;
init() { init() {
this._super(...arguments); super.init(...arguments);
this.setProperties({ members: [], requesters: [] }); this.setProperties({ members: [], requesters: [] });
}, }
@discourseComputed("automatic_membership_email_domains") @discourseComputed("automatic_membership_email_domains")
emailDomains(value) { emailDomains(value) {
return isEmpty(value) ? "" : value; return isEmpty(value) ? "" : value;
}, }
@discourseComputed("associated_group_ids") @discourseComputed("associated_group_ids")
associatedGroupIds(value) { associatedGroupIds(value) {
return isEmpty(value) ? [] : value; return isEmpty(value) ? [] : value;
}, }
@discourseComputed("automatic") @discourseComputed("automatic")
type(automatic) { type(automatic) {
return automatic ? "automatic" : "custom"; return automatic ? "automatic" : "custom";
}, }
async reloadMembers(params, refresh) { async reloadMembers(params, refresh) {
if (isEmpty(this.name) || !this.can_see_members) { if (isEmpty(this.name) || !this.can_see_members) {
@ -73,7 +95,7 @@ const Group = RestModel.extend({
limit: response.meta.limit, limit: response.meta.limit,
offset: response.meta.offset, offset: response.meta.offset,
}); });
}, }
findRequesters(params, refresh) { findRequesters(params, refresh) {
if (isEmpty(this.name) || !this.can_see_members) { if (isEmpty(this.name) || !this.can_see_members) {
@ -103,7 +125,7 @@ const Group = RestModel.extend({
requestersOffset: result.meta.offset, requestersOffset: result.meta.offset,
}); });
}); });
}, }
async removeOwner(member) { async removeOwner(member) {
await ajax(`/admin/groups/${this.id}/owners.json`, { await ajax(`/admin/groups/${this.id}/owners.json`, {
@ -111,7 +133,7 @@ const Group = RestModel.extend({
data: { user_id: member.id }, data: { user_id: member.id },
}); });
await this.reloadMembers({}, true); await this.reloadMembers({}, true);
}, }
async removeMember(member, params) { async removeMember(member, params) {
await ajax(`/groups/${this.id}/members.json`, { await ajax(`/groups/${this.id}/members.json`, {
@ -119,7 +141,7 @@ const Group = RestModel.extend({
data: { user_id: member.id }, data: { user_id: member.id },
}); });
await this.reloadMembers(params, true); await this.reloadMembers(params, true);
}, }
async leave() { async leave() {
await ajax(`/groups/${this.id}/leave.json`, { await ajax(`/groups/${this.id}/leave.json`, {
@ -127,7 +149,7 @@ const Group = RestModel.extend({
}); });
this.set("can_see_members", this.members_visibility_level < 2); this.set("can_see_members", this.members_visibility_level < 2);
await this.reloadMembers({}, true); await this.reloadMembers({}, true);
}, }
async addMembers(usernames, filter, notifyUsers, emails = []) { async addMembers(usernames, filter, notifyUsers, emails = []) {
const response = await ajax(`/groups/${this.id}/members.json`, { const response = await ajax(`/groups/${this.id}/members.json`, {
@ -139,14 +161,14 @@ const Group = RestModel.extend({
} else { } else {
await this.reloadMembers(); await this.reloadMembers();
} }
}, }
async join() { async join() {
await ajax(`/groups/${this.id}/join.json`, { await ajax(`/groups/${this.id}/join.json`, {
type: "PUT", type: "PUT",
}); });
await this.reloadMembers({}, true); await this.reloadMembers({}, true);
}, }
async addOwners(usernames, filter, notifyUsers) { async addOwners(usernames, filter, notifyUsers) {
const response = await ajax(`/groups/${this.id}/owners.json`, { const response = await ajax(`/groups/${this.id}/owners.json`, {
@ -159,44 +181,42 @@ const Group = RestModel.extend({
} else { } else {
await this.reloadMembers({}, true); await this.reloadMembers({}, true);
} }
}, }
_filterMembers(usernames) { _filterMembers(usernames) {
return this.reloadMembers({ filter: usernames.join(",") }); return this.reloadMembers({ filter: usernames.join(",") });
}, }
@discourseComputed("display_name", "name") @discourseComputed("display_name", "name")
displayName(groupDisplayName, name) { displayName(groupDisplayName, name) {
return groupDisplayName || name; return groupDisplayName || name;
}, }
@discourseComputed("flair_bg_color") @discourseComputed("flair_bg_color")
flairBackgroundHexColor(flairBgColor) { flairBackgroundHexColor(flairBgColor) {
return flairBgColor return flairBgColor
? flairBgColor.replace(new RegExp("[^0-9a-fA-F]", "g"), "") ? flairBgColor.replace(new RegExp("[^0-9a-fA-F]", "g"), "")
: null; : null;
}, }
@discourseComputed("flair_color") @discourseComputed("flair_color")
flairHexColor(flairColor) { flairHexColor(flairColor) {
return flairColor return flairColor
? flairColor.replace(new RegExp("[^0-9a-fA-F]", "g"), "") ? flairColor.replace(new RegExp("[^0-9a-fA-F]", "g"), "")
: null; : null;
}, }
canEveryoneMention: equal("mentionable_level", 99),
@discourseComputed("visibility_level") @discourseComputed("visibility_level")
isPrivate(visibilityLevel) { isPrivate(visibilityLevel) {
return visibilityLevel > 1; return visibilityLevel > 1;
}, }
@observes("isPrivate", "canEveryoneMention") @observes("isPrivate", "canEveryoneMention")
_updateAllowMembershipRequests() { _updateAllowMembershipRequests() {
if (this.isPrivate || !this.canEveryoneMention) { if (this.isPrivate || !this.canEveryoneMention) {
this.set("allow_membership_requests", false); this.set("allow_membership_requests", false);
} }
}, }
@dependentKeyCompat @dependentKeyCompat
get watchingCategories() { get watchingCategories() {
@ -210,14 +230,14 @@ const Group = RestModel.extend({
} }
return Category.findByIds(this.get("watching_category_ids")); return Category.findByIds(this.get("watching_category_ids"));
}, }
set watchingCategories(categories) { set watchingCategories(categories) {
this.set( this.set(
"watching_category_ids", "watching_category_ids",
categories.map((c) => c.id) categories.map((c) => c.id)
); );
}, }
@dependentKeyCompat @dependentKeyCompat
get trackingCategories() { get trackingCategories() {
@ -231,14 +251,14 @@ const Group = RestModel.extend({
} }
return Category.findByIds(this.get("tracking_category_ids")); return Category.findByIds(this.get("tracking_category_ids"));
}, }
set trackingCategories(categories) { set trackingCategories(categories) {
this.set( this.set(
"tracking_category_ids", "tracking_category_ids",
categories.map((c) => c.id) categories.map((c) => c.id)
); );
}, }
@dependentKeyCompat @dependentKeyCompat
get watchingFirstPostCategories() { get watchingFirstPostCategories() {
@ -252,14 +272,14 @@ const Group = RestModel.extend({
} }
return Category.findByIds(this.get("watching_first_post_category_ids")); return Category.findByIds(this.get("watching_first_post_category_ids"));
}, }
set watchingFirstPostCategories(categories) { set watchingFirstPostCategories(categories) {
this.set( this.set(
"watching_first_post_category_ids", "watching_first_post_category_ids",
categories.map((c) => c.id) categories.map((c) => c.id)
); );
}, }
@dependentKeyCompat @dependentKeyCompat
get regularCategories() { get regularCategories() {
@ -273,14 +293,14 @@ const Group = RestModel.extend({
} }
return Category.findByIds(this.get("regular_category_ids")); return Category.findByIds(this.get("regular_category_ids"));
}, }
set regularCategories(categories) { set regularCategories(categories) {
this.set( this.set(
"regular_category_ids", "regular_category_ids",
categories.map((c) => c.id) categories.map((c) => c.id)
); );
}, }
@dependentKeyCompat @dependentKeyCompat
get mutedCategories() { get mutedCategories() {
@ -294,14 +314,14 @@ const Group = RestModel.extend({
} }
return Category.findByIds(this.get("muted_category_ids")); return Category.findByIds(this.get("muted_category_ids"));
}, }
set mutedCategories(categories) { set mutedCategories(categories) {
this.set( this.set(
"muted_category_ids", "muted_category_ids",
categories.map((c) => c.id) categories.map((c) => c.id)
); );
}, }
asJSON() { asJSON() {
const attrs = { const attrs = {
@ -382,7 +402,7 @@ const Group = RestModel.extend({
} }
return attrs; return attrs;
}, }
async create() { async create() {
const response = await ajax("/admin/groups", { const response = await ajax("/admin/groups", {
@ -397,21 +417,21 @@ const Group = RestModel.extend({
}); });
await this.reloadMembers(); await this.reloadMembers();
}, }
save(opts = {}) { save(opts = {}) {
return ajax(`/groups/${this.id}`, { return ajax(`/groups/${this.id}`, {
type: "PUT", type: "PUT",
data: Object.assign({ group: this.asJSON() }, opts), data: Object.assign({ group: this.asJSON() }, opts),
}); });
}, }
destroy() { destroy() {
if (!this.id) { if (!this.id) {
return; return;
} }
return ajax(`/admin/groups/${this.id}`, { type: "DELETE" }); return ajax(`/admin/groups/${this.id}`, { type: "DELETE" });
}, }
findLogs(offset, filters) { findLogs(offset, filters) {
return ajax(`/groups/${this.name}/logs.json`, { return ajax(`/groups/${this.name}/logs.json`, {
@ -422,7 +442,7 @@ const Group = RestModel.extend({
all_loaded: results["all_loaded"], all_loaded: results["all_loaded"],
}); });
}); });
}, }
findPosts(opts) { findPosts(opts) {
opts = opts || {}; opts = opts || {};
@ -445,7 +465,7 @@ const Group = RestModel.extend({
return EmberObject.create(p); return EmberObject.create(p);
}); });
}); });
}, }
setNotification(notification_level, userId) { setNotification(notification_level, userId) {
this.set("group_user.notification_level", notification_level); this.set("group_user.notification_level", notification_level);
@ -453,38 +473,12 @@ const Group = RestModel.extend({
data: { notification_level, user_id: userId }, data: { notification_level, user_id: userId },
type: "POST", type: "POST",
}); });
}, }
requestMembership(reason) { requestMembership(reason) {
return ajax(`/groups/${this.name}/request_membership.json`, { return ajax(`/groups/${this.name}/request_membership.json`, {
type: "POST", type: "POST",
data: { reason }, data: { reason },
}); });
}, }
}); }
Group.reopenClass({
findAll(opts) {
return ajax("/groups/search.json", { data: opts }).then((groups) =>
groups.map((g) => Group.create(g))
);
},
loadMembers(name, opts) {
return ajax(`/groups/${name}/members.json`, { data: opts });
},
mentionable(name) {
return ajax(`/groups/${name}/mentionable`);
},
messageable(name) {
return ajax(`/groups/${name}/messageable`);
},
checkName(name) {
return ajax("/groups/check-name", { data: { group_name: name } });
},
});
export default Group;

View File

@ -9,65 +9,16 @@ import Topic from "discourse/models/topic";
import User from "discourse/models/user"; import User from "discourse/models/user";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
const Invite = EmberObject.extend({ export default class Invite extends EmberObject {
save(data) { static create() {
const promise = this.id const result = super.create(...arguments);
? ajax(`/invites/${this.id}`, { type: "PUT", data })
: ajax("/invites", { type: "POST", data });
return promise.then((result) => this.setProperties(result));
},
destroy() {
return ajax("/invites", {
type: "DELETE",
data: { id: this.id },
}).then(() => this.set("destroyed", true));
},
reinvite() {
return ajax("/invites/reinvite", {
type: "POST",
data: { email: this.email },
})
.then(() => this.set("reinvited", true))
.catch(popupAjaxError);
},
@discourseComputed("invite_key")
shortKey(key) {
return key.slice(0, 4) + "...";
},
@discourseComputed("groups")
groupIds(groups) {
return groups ? groups.map((group) => group.id) : [];
},
@discourseComputed("topics.firstObject")
topic(topicData) {
return topicData ? Topic.create(topicData) : null;
},
@discourseComputed("email", "domain")
emailOrDomain(email, domain) {
return email || domain;
},
topicId: alias("topics.firstObject.id"),
topicTitle: alias("topics.firstObject.title"),
});
Invite.reopenClass({
create() {
const result = this._super.apply(this, arguments);
if (result.user) { if (result.user) {
result.user = User.create(result.user); result.user = User.create(result.user);
} }
return result; return result;
}, }
findInvitedBy(user, filter, search, offset) { static findInvitedBy(user, filter, search, offset) {
if (!user) { if (!user) {
Promise.resolve(); Promise.resolve();
} }
@ -87,15 +38,60 @@ Invite.reopenClass({
result.invites = result.invites.map((i) => Invite.create(i)); result.invites = result.invites.map((i) => Invite.create(i));
return EmberObject.create(result); return EmberObject.create(result);
}); });
}, }
reinviteAll() { static reinviteAll() {
return ajax("/invites/reinvite-all", { type: "POST" }); return ajax("/invites/reinvite-all", { type: "POST" });
}, }
destroyAllExpired() { static destroyAllExpired() {
return ajax("/invites/destroy-all-expired", { type: "POST" }); return ajax("/invites/destroy-all-expired", { type: "POST" });
}, }
});
export default Invite; @alias("topics.firstObject.id") topicId;
@alias("topics.firstObject.title") topicTitle;
save(data) {
const promise = this.id
? ajax(`/invites/${this.id}`, { type: "PUT", data })
: ajax("/invites", { type: "POST", data });
return promise.then((result) => this.setProperties(result));
}
destroy() {
return ajax("/invites", {
type: "DELETE",
data: { id: this.id },
}).then(() => this.set("destroyed", true));
}
reinvite() {
return ajax("/invites/reinvite", {
type: "POST",
data: { email: this.email },
})
.then(() => this.set("reinvited", true))
.catch(popupAjaxError);
}
@discourseComputed("invite_key")
shortKey(key) {
return key.slice(0, 4) + "...";
}
@discourseComputed("groups")
groupIds(groups) {
return groups ? groups.map((group) => group.id) : [];
}
@discourseComputed("topics.firstObject")
topic(topicData) {
return topicData ? Topic.create(topicData) : null;
}
@discourseComputed("email", "domain")
emailOrDomain(email, domain) {
return email || domain;
}
}

View File

@ -1,14 +1,10 @@
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
const LivePostCounts = EmberObject.extend({}); export default class LivePostCounts extends EmberObject {
static find() {
LivePostCounts.reopenClass({
find() {
return ajax("/about/live_post_counts.json").then((result) => return ajax("/about/live_post_counts.json").then((result) =>
LivePostCounts.create(result) LivePostCounts.create(result)
); );
}, }
}); }
export default LivePostCounts;

View File

@ -7,11 +7,31 @@ import getURL from "discourse-common/lib/get-url";
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 LoginMethod = EmberObject.extend({ export default class LoginMethod extends EmberObject {
static buildPostForm(url) {
// Login always happens in an anonymous context, with no CSRF token
// So we need to fetch it before sending a POST request
return updateCsrfToken().then(() => {
const form = document.createElement("form");
form.setAttribute("style", "display:none;");
form.setAttribute("method", "post");
form.setAttribute("action", url);
const input = document.createElement("input");
input.setAttribute("name", "authenticity_token");
input.setAttribute("value", Session.currentProp("csrfToken"));
form.appendChild(input);
document.body.appendChild(form);
return form;
});
}
@discourseComputed @discourseComputed
title() { title() {
return this.title_override || I18n.t(`login.${this.name}.title`); return this.title_override || I18n.t(`login.${this.name}.title`);
}, }
@discourseComputed @discourseComputed
screenReaderTitle() { screenReaderTitle() {
@ -19,12 +39,12 @@ const LoginMethod = EmberObject.extend({
this.title_override || this.title_override ||
I18n.t(`login.${this.name}.sr_title`, { defaultValue: this.title }) I18n.t(`login.${this.name}.sr_title`, { defaultValue: this.title })
); );
}, }
@discourseComputed @discourseComputed
prettyName() { prettyName() {
return this.pretty_name_override || I18n.t(`login.${this.name}.name`); return this.pretty_name_override || I18n.t(`login.${this.name}.name`);
}, }
doLogin({ reconnect = false, signup = false, params = {} } = {}) { doLogin({ reconnect = false, signup = false, params = {} } = {}) {
if (this.customLogin) { if (this.customLogin) {
@ -56,30 +76,8 @@ const LoginMethod = EmberObject.extend({
} }
return LoginMethod.buildPostForm(authUrl).then((form) => form.submit()); return LoginMethod.buildPostForm(authUrl).then((form) => form.submit());
}, }
}); }
LoginMethod.reopenClass({
buildPostForm(url) {
// Login always happens in an anonymous context, with no CSRF token
// So we need to fetch it before sending a POST request
return updateCsrfToken().then(() => {
const form = document.createElement("form");
form.setAttribute("style", "display:none;");
form.setAttribute("method", "post");
form.setAttribute("action", url);
const input = document.createElement("input");
input.setAttribute("name", "authenticity_token");
input.setAttribute("value", Session.currentProp("csrfToken"));
form.appendChild(input);
document.body.appendChild(form);
return form;
});
},
});
let methods; let methods;
@ -101,5 +99,3 @@ export function findAll() {
export function clearAuthMethods() { export function clearAuthMethods() {
methods = undefined; methods = undefined;
} }
export default LoginMethod;

View File

@ -5,27 +5,27 @@ import RestModel from "discourse/models/rest";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import Category from "./category"; import Category from "./category";
const PendingPost = RestModel.extend({ export default class PendingPost extends RestModel {
expandedExcerpt: null, expandedExcerpt = null;
postUrl: reads("topic_url"),
truncated: false, @reads("topic_url") postUrl;
truncated = false;
init() { init() {
this._super(...arguments); super.init(...arguments);
cook(this.raw_text).then((cooked) => { cook(this.raw_text).then((cooked) => {
this.set("expandedExcerpt", cooked); this.set("expandedExcerpt", cooked);
}); });
}, }
@discourseComputed("username") @discourseComputed("username")
userUrl(username) { userUrl(username) {
return userPath(username.toLowerCase()); return userPath(username.toLowerCase());
}, }
@discourseComputed("category_id") @discourseComputed("category_id")
category() { category() {
return Category.findById(this.category_id); return Category.findById(this.category_id);
}, }
}); }
export default PendingPost;

View File

@ -6,20 +6,18 @@ export function buildPermissionDescription(id) {
return I18n.t("permission_types." + PermissionType.DESCRIPTION_KEYS[id]); return I18n.t("permission_types." + PermissionType.DESCRIPTION_KEYS[id]);
} }
const PermissionType = EmberObject.extend({ export default class PermissionType extends EmberObject {
static FULL = 1;
static CREATE_POST = 2;
static READONLY = 3;
static DESCRIPTION_KEYS = {
1: "full",
2: "create_post",
3: "readonly",
};
@discourseComputed("id") @discourseComputed("id")
description(id) { description(id) {
return buildPermissionDescription(id); return buildPermissionDescription(id);
}, }
}); }
PermissionType.FULL = 1;
PermissionType.CREATE_POST = 2;
PermissionType.READONLY = 3;
PermissionType.DESCRIPTION_KEYS = {
1: "full",
2: "create_post",
3: "readonly",
};
export default PermissionType;

View File

@ -3,6 +3,6 @@ import RestModel from "discourse/models/rest";
export const MAX_MESSAGE_LENGTH = 500; export const MAX_MESSAGE_LENGTH = 500;
export default RestModel.extend({ export default class PostActionType extends RestModel {
notCustomFlag: not("is_custom_flag"), @not("is_custom_flag") notCustomFlag;
}); }

View File

@ -33,23 +33,33 @@ export function resetLastEditNotificationClick() {
_lastEditNotificationClick = null; _lastEditNotificationClick = null;
} }
export default RestModel.extend({ export default class PostStream extends RestModel {
_identityMap: null, posts = null;
posts: null, stream = null;
stream: null, userFilters = null;
userFilters: null, loaded = null;
loaded: null, loadingAbove = null;
loadingAbove: null, loadingBelow = null;
loadingBelow: null, loadingFilter = null;
loadingFilter: null, loadingNearPost = null;
loadingNearPost: null, stagingPost = null;
stagingPost: null, postsWithPlaceholders = null;
postsWithPlaceholders: null, timelineLookup = null;
timelineLookup: null, filterRepliesToPostNumber = null;
filterRepliesToPostNumber: null, filterUpwardsPostID = null;
filterUpwardsPostID: null, filter = null;
filter: null, topicSummary = null;
topicSummary: null, lastId = null;
@or("loadingAbove", "loadingBelow", "loadingFilter", "stagingPost") loading;
@not("loading") notLoading;
@equal("filter", "summary") summary;
@and("notLoading", "hasPosts", "lastPostNotLoaded") canAppendMore;
@and("notLoading", "hasPosts", "firstPostNotLoaded") canPrependMore;
@not("firstPostPresent") firstPostNotLoaded;
@not("loadedAllPosts") lastPostNotLoaded;
_identityMap = null;
init() { init() {
this._identityMap = {}; this._identityMap = {};
@ -75,12 +85,7 @@ export default RestModel.extend({
timelineLookup: [], timelineLookup: [],
topicSummary: new TopicSummary(), topicSummary: new TopicSummary(),
}); });
}, }
loading: or("loadingAbove", "loadingBelow", "loadingFilter", "stagingPost"),
notLoading: not("loading"),
summary: equal("filter", "summary"),
@discourseComputed( @discourseComputed(
"isMegaTopic", "isMegaTopic",
@ -89,20 +94,17 @@ export default RestModel.extend({
) )
filteredPostsCount(isMegaTopic, streamLength, topicHighestPostNumber) { filteredPostsCount(isMegaTopic, streamLength, topicHighestPostNumber) {
return isMegaTopic ? topicHighestPostNumber : streamLength; return isMegaTopic ? topicHighestPostNumber : streamLength;
}, }
@discourseComputed("posts.[]") @discourseComputed("posts.[]")
hasPosts() { hasPosts() {
return this.get("posts.length") > 0; return this.get("posts.length") > 0;
}, }
@discourseComputed("hasPosts", "filteredPostsCount") @discourseComputed("hasPosts", "filteredPostsCount")
hasLoadedData(hasPosts, filteredPostsCount) { hasLoadedData(hasPosts, filteredPostsCount) {
return hasPosts && filteredPostsCount > 0; return hasPosts && filteredPostsCount > 0;
}, }
canAppendMore: and("notLoading", "hasPosts", "lastPostNotLoaded"),
canPrependMore: and("notLoading", "hasPosts", "firstPostNotLoaded"),
@discourseComputed("hasLoadedData", "posts.[]") @discourseComputed("hasLoadedData", "posts.[]")
firstPostPresent(hasLoadedData) { firstPostPresent(hasLoadedData) {
@ -111,16 +113,12 @@ export default RestModel.extend({
} }
return !!this.posts.findBy("post_number", 1); return !!this.posts.findBy("post_number", 1);
}, }
firstPostNotLoaded: not("firstPostPresent"),
lastId: null,
@discourseComputed("isMegaTopic", "stream.lastObject", "lastId") @discourseComputed("isMegaTopic", "stream.lastObject", "lastId")
lastPostId(isMegaTopic, streamLastId, lastId) { lastPostId(isMegaTopic, streamLastId, lastId) {
return isMegaTopic ? lastId : streamLastId; return isMegaTopic ? lastId : streamLastId;
}, }
@discourseComputed("hasLoadedData", "lastPostId", "posts.@each.id") @discourseComputed("hasLoadedData", "lastPostId", "posts.@each.id")
loadedAllPosts(hasLoadedData, lastPostId) { loadedAllPosts(hasLoadedData, lastPostId) {
@ -132,9 +130,7 @@ export default RestModel.extend({
} }
return !!this.posts.findBy("id", lastPostId); return !!this.posts.findBy("id", lastPostId);
}, }
lastPostNotLoaded: not("loadedAllPosts"),
/** /**
Returns a JS Object of current stream filter options. It should match the query Returns a JS Object of current stream filter options. It should match the query
@ -167,7 +163,7 @@ export default RestModel.extend({
} }
return result; return result;
}, }
@discourseComputed("streamFilters.[]", "topic.posts_count", "posts.length") @discourseComputed("streamFilters.[]", "topic.posts_count", "posts.length")
hasNoFilters() { hasNoFilters() {
@ -176,7 +172,7 @@ export default RestModel.extend({
streamFilters && streamFilters &&
(streamFilters.filter === "summary" || streamFilters.username_filters) (streamFilters.filter === "summary" || streamFilters.username_filters)
); );
}, }
/** /**
Returns the window of posts above the current set in the stream, bound to the top of the stream. Returns the window of posts above the current set in the stream, bound to the top of the stream.
@ -206,7 +202,7 @@ export default RestModel.extend({
startIndex = 0; startIndex = 0;
} }
return stream.slice(startIndex, firstIndex); return stream.slice(startIndex, firstIndex);
}, }
/** /**
Returns the window of posts below the current set in the stream, bound by the bottom of the Returns the window of posts below the current set in the stream, bound by the bottom of the
@ -234,7 +230,7 @@ export default RestModel.extend({
lastIndex + 1, lastIndex + 1,
lastIndex + this.get("topic.chunk_size") + 1 lastIndex + this.get("topic.chunk_size") + 1
); );
}, }
cancelFilter() { cancelFilter() {
this.setProperties({ this.setProperties({
@ -244,7 +240,7 @@ export default RestModel.extend({
mixedHiddenPosts: false, mixedHiddenPosts: false,
filter: null, filter: null,
}); });
}, }
refreshAndJumpToSecondVisible() { refreshAndJumpToSecondVisible() {
return this.refresh({}).then(() => { return this.refresh({}).then(() => {
@ -252,20 +248,20 @@ export default RestModel.extend({
DiscourseURL.jumpToPost(this.posts[1].get("post_number")); DiscourseURL.jumpToPost(this.posts[1].get("post_number"));
} }
}); });
}, }
showTopReplies() { showTopReplies() {
this.cancelFilter(); this.cancelFilter();
this.set("filter", "summary"); this.set("filter", "summary");
return this.refreshAndJumpToSecondVisible(); return this.refreshAndJumpToSecondVisible();
}, }
// Filter the stream to a particular user. // Filter the stream to a particular user.
filterParticipant(username) { filterParticipant(username) {
this.cancelFilter(); this.cancelFilter();
this.userFilters.addObject(username); this.userFilters.addObject(username);
return this.refreshAndJumpToSecondVisible(); return this.refreshAndJumpToSecondVisible();
}, }
filterReplies(postNumber, postId) { filterReplies(postNumber, postId) {
this.cancelFilter(); this.cancelFilter();
@ -295,7 +291,7 @@ export default RestModel.extend({
highlightPost(postNumber); highlightPost(postNumber);
}); });
}); });
}, }
filterUpwards(postID) { filterUpwards(postID) {
this.cancelFilter(); this.cancelFilter();
@ -316,7 +312,7 @@ export default RestModel.extend({
}); });
} }
}); });
}, }
/** /**
Loads a new set of posts into the stream. If you provide a `nearPost` option and the post Loads a new set of posts into the stream. If you provide a `nearPost` option and the post
@ -378,7 +374,7 @@ export default RestModel.extend({
.finally(() => { .finally(() => {
this.set("loadingNearPost", null); this.set("loadingNearPost", null);
}); });
}, }
// Fill in a gap of posts before a particular post // Fill in a gap of posts before a particular post
fillGapBefore(post, gap) { fillGapBefore(post, gap) {
@ -420,7 +416,7 @@ export default RestModel.extend({
} }
} }
return Promise.resolve(); return Promise.resolve();
}, }
// Fill in a gap of posts after a particular post // Fill in a gap of posts after a particular post
fillGapAfter(post, gap) { fillGapAfter(post, gap) {
@ -436,7 +432,7 @@ export default RestModel.extend({
}); });
} }
return Promise.resolve(); return Promise.resolve();
}, }
gapExpanded() { gapExpanded() {
this.appEvents.trigger("post-stream:refresh"); this.appEvents.trigger("post-stream:refresh");
@ -446,7 +442,7 @@ export default RestModel.extend({
if (this.streamFilters && this.streamFilters.replies_to_post_number) { if (this.streamFilters && this.streamFilters.replies_to_post_number) {
this.set("streamFilters.mixedHiddenPosts", true); this.set("streamFilters.mixedHiddenPosts", true);
} }
}, }
// Appends the next window of posts to the stream. Call it when scrolling downwards. // Appends the next window of posts to the stream. Call it when scrolling downwards.
appendMore() { appendMore() {
@ -493,7 +489,7 @@ export default RestModel.extend({
this.set("loadingBelow", false); this.set("loadingBelow", false);
}); });
} }
}, }
// Prepend the previous window of posts to the stream. Call it when scrolling upwards. // Prepend the previous window of posts to the stream. Call it when scrolling upwards.
prependMore() { prependMore() {
@ -535,7 +531,7 @@ export default RestModel.extend({
this.set("loadingAbove", false); this.set("loadingAbove", false);
}); });
} }
}, }
/** /**
Stage a post for insertion in the stream. It should be rendered right away under the Stage a post for insertion in the stream. It should be rendered right away under the
@ -573,7 +569,7 @@ export default RestModel.extend({
} }
return "offScreen"; return "offScreen";
}, }
// Commit the post we staged. Call this after a save succeeds. // Commit the post we staged. Call this after a save succeeds.
commitPost(post) { commitPost(post) {
@ -587,7 +583,7 @@ export default RestModel.extend({
this.stream.removeObject(-1); this.stream.removeObject(-1);
this._identityMap[-1] = null; this._identityMap[-1] = null;
this.set("stagingPost", false); this.set("stagingPost", false);
}, }
/** /**
Undo a post we've staged in the stream. Remove it from being rendered and revert the Undo a post we've staged in the stream. Remove it from being rendered and revert the
@ -607,7 +603,7 @@ export default RestModel.extend({
}); });
// TODO unfudge reply count on parent post // TODO unfudge reply count on parent post
}, }
prependPost(post) { prependPost(post) {
this._initUserModels(post); this._initUserModels(post);
@ -618,7 +614,7 @@ export default RestModel.extend({
} }
return post; return post;
}, }
appendPost(post) { appendPost(post) {
this._initUserModels(post); this._initUserModels(post);
@ -639,7 +635,7 @@ export default RestModel.extend({
} }
} }
return post; return post;
}, }
removePosts(posts) { removePosts(posts) {
if (isEmpty(posts)) { if (isEmpty(posts)) {
@ -655,12 +651,12 @@ export default RestModel.extend({
allPosts.removeObjects(posts); allPosts.removeObjects(posts);
postIds.forEach((id) => delete identityMap[id]); postIds.forEach((id) => delete identityMap[id]);
}); });
}, }
// Returns a post from the identity map if it's been inserted. // Returns a post from the identity map if it's been inserted.
findLoadedPost(id) { findLoadedPost(id) {
return this._identityMap[id]; return this._identityMap[id];
}, }
loadPostByPostNumber(postNumber) { loadPostByPostNumber(postNumber) {
const url = `/posts/by_number/${this.get("topic.id")}/${postNumber}`; const url = `/posts/by_number/${this.get("topic.id")}/${postNumber}`;
@ -669,7 +665,7 @@ export default RestModel.extend({
return ajax(url).then((post) => { return ajax(url).then((post) => {
return this.storePost(store.createRecord("post", post)); return this.storePost(store.createRecord("post", post));
}); });
}, }
loadNearestPostToDate(date) { loadNearestPostToDate(date) {
const url = `/posts/by-date/${this.get("topic.id")}/${date}`; const url = `/posts/by-date/${this.get("topic.id")}/${date}`;
@ -678,7 +674,7 @@ export default RestModel.extend({
return ajax(url).then((post) => { return ajax(url).then((post) => {
return this.storePost(store.createRecord("post", post)); return this.storePost(store.createRecord("post", post));
}); });
}, }
loadPost(postId) { loadPost(postId) {
const url = "/posts/" + postId; const url = "/posts/" + postId;
@ -692,7 +688,7 @@ export default RestModel.extend({
return this.storePost(store.createRecord("post", p)); return this.storePost(store.createRecord("post", p));
}); });
}, }
/* mainly for backwards compatibility with plugins, used in quick messages plugin /* mainly for backwards compatibility with plugins, used in quick messages plugin
* TODO: remove July 2022 * TODO: remove July 2022
@ -705,7 +701,7 @@ export default RestModel.extend({
} }
); );
return this.triggerNewPostsInStream([postId], opts); return this.triggerNewPostsInStream([postId], opts);
}, }
/** /**
Finds and adds posts to the stream by id. Typically this would happen if we receive a message Finds and adds posts to the stream by id. Typically this would happen if we receive a message
@ -768,7 +764,7 @@ export default RestModel.extend({
} }
return resolved; return resolved;
}, }
triggerRecoveredPost(postId) { triggerRecoveredPost(postId) {
const existing = this._identityMap[postId]; const existing = this._identityMap[postId];
@ -814,7 +810,7 @@ export default RestModel.extend({
} }
}); });
} }
}, }
triggerDeletedPost(postId) { triggerDeletedPost(postId) {
const existing = this._identityMap[postId]; const existing = this._identityMap[postId];
@ -832,13 +828,13 @@ export default RestModel.extend({
}); });
} }
return Promise.resolve(); return Promise.resolve();
}, }
triggerDestroyedPost(postId) { triggerDestroyedPost(postId) {
const existing = this._identityMap[postId]; const existing = this._identityMap[postId];
this.removePosts([existing]); this.removePosts([existing]);
return Promise.resolve(); return Promise.resolve();
}, }
triggerChangedPost(postId, updatedAt, opts) { triggerChangedPost(postId, updatedAt, opts) {
opts = opts || {}; opts = opts || {};
@ -861,7 +857,7 @@ export default RestModel.extend({
}); });
} }
return resolved; return resolved;
}, }
triggerLikedPost(postId, likesCount, userID, eventType) { triggerLikedPost(postId, likesCount, userID, eventType) {
const resolved = Promise.resolve(); const resolved = Promise.resolve();
@ -873,7 +869,7 @@ export default RestModel.extend({
} }
return resolved; return resolved;
}, }
triggerReadPost(postId, readersCount) { triggerReadPost(postId, readersCount) {
const resolved = Promise.resolve(); const resolved = Promise.resolve();
@ -886,7 +882,7 @@ export default RestModel.extend({
}); });
return resolved; return resolved;
}, }
triggerChangedTopicStats() { triggerChangedTopicStats() {
if (this.firstPostNotLoaded) { if (this.firstPostNotLoaded) {
@ -897,7 +893,7 @@ export default RestModel.extend({
const firstPost = this.posts.findBy("post_number", 1); const firstPost = this.posts.findBy("post_number", 1);
return firstPost.id; return firstPost.id;
}); });
}, }
postForPostNumber(postNumber) { postForPostNumber(postNumber) {
if (!this.hasPosts) { if (!this.hasPosts) {
@ -907,7 +903,7 @@ export default RestModel.extend({
return this.posts.find((p) => { return this.posts.find((p) => {
return p.get("post_number") === postNumber; return p.get("post_number") === postNumber;
}); });
}, }
/** /**
Returns the closest post given a postNumber that may not exist in the stream. Returns the closest post given a postNumber that may not exist in the stream.
@ -935,12 +931,12 @@ export default RestModel.extend({
}); });
return closest; return closest;
}, }
// Get the index of a post in the stream. (Use this for the topic progress bar.) // Get the index of a post in the stream. (Use this for the topic progress bar.)
progressIndexOfPost(post) { progressIndexOfPost(post) {
return this.progressIndexOfPostId(post); return this.progressIndexOfPostId(post);
}, }
// Get the index in the stream of a post id. (Use this for the topic progress bar.) // Get the index in the stream of a post id. (Use this for the topic progress bar.)
progressIndexOfPostId(post) { progressIndexOfPostId(post) {
@ -952,7 +948,7 @@ export default RestModel.extend({
const index = this.stream.indexOf(postId); const index = this.stream.indexOf(postId);
return index + 1; return index + 1;
} }
}, }
/** /**
Returns the closest post number given a postNumber that may not exist in the stream. Returns the closest post number given a postNumber that may not exist in the stream.
@ -982,7 +978,7 @@ export default RestModel.extend({
}); });
return closest; return closest;
}, }
closestDaysAgoFor(postNumber) { closestDaysAgoFor(postNumber) {
const timelineLookup = this.timelineLookup || []; const timelineLookup = this.timelineLookup || [];
@ -1007,7 +1003,7 @@ export default RestModel.extend({
if (val) { if (val) {
return val[1]; return val[1];
} }
}, }
// Find a postId for a postNumber, respecting gaps // Find a postId for a postNumber, respecting gaps
findPostIdForPostNumber(postNumber) { findPostIdForPostNumber(postNumber) {
@ -1037,7 +1033,7 @@ export default RestModel.extend({
} }
sum++; sum++;
} }
}, }
updateFromJson(postStreamData) { updateFromJson(postStreamData) {
const posts = this.posts; const posts = this.posts;
@ -1057,7 +1053,7 @@ export default RestModel.extend({
// Update our attributes // Update our attributes
this.setProperties(postStreamData); this.setProperties(postStreamData);
} }
}, }
/** /**
Stores a post in our identity map, and sets up the references it needs to Stores a post in our identity map, and sets up the references it needs to
@ -1094,7 +1090,7 @@ export default RestModel.extend({
this._identityMap[post.get("id")] = post; this._identityMap[post.get("id")] = post;
} }
return post; return post;
}, }
fetchNextWindow(postNumber, asc, callback) { fetchNextWindow(postNumber, asc, callback) {
let includeSuggested = !this.get("topic.suggested_topics"); let includeSuggested = !this.get("topic.suggested_topics");
@ -1124,7 +1120,7 @@ export default RestModel.extend({
}); });
} }
}); });
}, }
findPostsByIds(postIds, opts) { findPostsByIds(postIds, opts) {
const identityMap = this._identityMap; const identityMap = this._identityMap;
@ -1134,7 +1130,7 @@ export default RestModel.extend({
return this.loadIntoIdentityMap(unloaded, opts).then(() => { return this.loadIntoIdentityMap(unloaded, opts).then(() => {
return postIds.map((p) => identityMap[p]).compact(); return postIds.map((p) => identityMap[p]).compact();
}); });
}, }
loadIntoIdentityMap(postIds, opts) { loadIntoIdentityMap(postIds, opts) {
if (isEmpty(postIds)) { if (isEmpty(postIds)) {
@ -1164,7 +1160,7 @@ export default RestModel.extend({
posts.forEach((p) => this.storePost(store.createRecord("post", p))); posts.forEach((p) => this.storePost(store.createRecord("post", p)));
} }
}); });
}, }
backfillExcerpts(streamPosition) { backfillExcerpts(streamPosition) {
this._excerpts = this._excerpts || []; this._excerpts = this._excerpts || [];
@ -1211,7 +1207,7 @@ export default RestModel.extend({
}); });
return this._excerpts.loading; return this._excerpts.loading;
}, }
excerpt(streamPosition) { excerpt(streamPosition) {
if (this.isMegaTopic) { if (this.isMegaTopic) {
@ -1234,11 +1230,11 @@ export default RestModel.extend({
}) })
.catch((e) => reject(e)); .catch((e) => reject(e));
}); });
}, }
indexOf(post) { indexOf(post) {
return this.stream.indexOf(post.get("id")); return this.stream.indexOf(post.get("id"));
}, }
// Handles an error loading a topic based on a HTTP status code. Updates // Handles an error loading a topic based on a HTTP status code. Updates
// the text to the correct values. // the text to the correct values.
@ -1259,19 +1255,19 @@ export default RestModel.extend({
topic.set("errorMessage", I18n.t("topic.server_error.description")); topic.set("errorMessage", I18n.t("topic.server_error.description"));
topic.set("noRetry", error.jqXHR.status === 403); topic.set("noRetry", error.jqXHR.status === 403);
} }
}, }
collapseSummary() { collapseSummary() {
this.topicSummary.collapse(); this.topicSummary.collapse();
}, }
showSummary(currentUser) { showSummary(currentUser) {
this.topicSummary.generateSummary(currentUser, this.get("topic.id")); this.topicSummary.generateSummary(currentUser, this.get("topic.id"));
}, }
processSummaryUpdate(update) { processSummaryUpdate(update) {
this.topicSummary.processUpdate(update); this.topicSummary.processUpdate(update);
}, }
_initUserModels(post) { _initUserModels(post) {
post.user = User.create({ post.user = User.create({
@ -1286,7 +1282,7 @@ export default RestModel.extend({
if (post.mentioned_users) { if (post.mentioned_users) {
post.mentioned_users = post.mentioned_users.map((u) => User.create(u)); post.mentioned_users = post.mentioned_users.map((u) => User.create(u));
} }
}, }
_checkIfShouldShowRevisions() { _checkIfShouldShowRevisions() {
if (_lastEditNotificationClick) { if (_lastEditNotificationClick) {
@ -1306,7 +1302,7 @@ export default RestModel.extend({
}); });
} }
} }
}, }
_setSuggestedTopics(result) { _setSuggestedTopics(result) {
if (!result.suggested_topics) { if (!result.suggested_topics) {
@ -1321,5 +1317,5 @@ export default RestModel.extend({
if (this.topic.isPrivateMessage) { if (this.topic.isPrivateMessage) {
this.pmTopicTrackingState.startTracking(); this.pmTopicTrackingState.startTracking();
} }
}, }
}); }

View File

@ -2,8 +2,9 @@ import { computed } from "@ember/object";
import RestModel from "discourse/models/rest"; import RestModel from "discourse/models/rest";
import { getAbsoluteURL } from "discourse-common/lib/get-url"; import { getAbsoluteURL } from "discourse-common/lib/get-url";
export default RestModel.extend({ export default class PublishedPage extends RestModel {
url: computed("slug", function () { @computed("slug")
get url() {
return getAbsoluteURL(`/pub/${this.slug}`); return getAbsoluteURL(`/pub/${this.slug}`);
}), }
}); }

View File

@ -2,24 +2,23 @@ import ArrayProxy from "@ember/array/proxy";
import { Promise } from "rsvp"; import { Promise } from "rsvp";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default ArrayProxy.extend({ export default class ResultSet extends ArrayProxy {
loading: false, loading = false;
loadingMore: false, loadingMore = false;
totalRows: 0, totalRows = 0;
refreshing: false, refreshing = false;
content = null;
content: null, loadMoreUrl = null;
loadMoreUrl: null, refreshUrl = null;
refreshUrl: null, findArgs = null;
findArgs: null, store = null;
store: null, resultSetMeta = null;
__type: null, __type = null;
resultSetMeta: null,
@discourseComputed("totalRows", "length") @discourseComputed("totalRows", "length")
canLoadMore(totalRows, length) { canLoadMore(totalRows, length) {
return length < totalRows; return length < totalRows;
}, }
loadMore() { loadMore() {
const loadMoreUrl = this.loadMoreUrl; const loadMoreUrl = this.loadMoreUrl;
@ -37,7 +36,7 @@ export default ArrayProxy.extend({
} }
return Promise.resolve(); return Promise.resolve();
}, }
refresh() { refresh() {
if (this.refreshing) { if (this.refreshing) {
@ -53,5 +52,5 @@ export default ArrayProxy.extend({
return this.store return this.store
.refreshResults(this, this.__type, refreshUrl) .refreshResults(this, this.__type, refreshUrl)
.finally(() => this.set("refreshing", false)); .finally(() => this.set("refreshing", false));
}, }
}); }

View File

@ -5,6 +5,6 @@ export const CREATED = 0;
export const TRANSITIONED_TO = 1; export const TRANSITIONED_TO = 1;
export const EDITED = 2; export const EDITED = 2;
export default RestModel.extend({ export default class ReviewableHistory extends RestModel {
created: equal("reviewable_history_type", CREATED), @equal("reviewable_history_type", CREATED) created;
}); }

View File

@ -12,7 +12,13 @@ export const REJECTED = 2;
export const IGNORED = 3; export const IGNORED = 3;
export const DELETED = 4; export const DELETED = 4;
const Reviewable = RestModel.extend({ export default class Reviewable extends RestModel {
static munge(json) {
// ensure we are not overriding category computed property
delete json.category;
return json;
}
@discourseComputed("type", "topic") @discourseComputed("type", "topic")
resolvedType(type, topic) { resolvedType(type, topic) {
// Display "Queued Topic" if the post will create a topic // Display "Queued Topic" if the post will create a topic
@ -21,26 +27,26 @@ const Reviewable = RestModel.extend({
} }
return type; return type;
}, }
@discourseComputed("resolvedType") @discourseComputed("resolvedType")
humanType(resolvedType) { humanType(resolvedType) {
return I18n.t(`review.types.${underscore(resolvedType)}.title`, { return I18n.t(`review.types.${underscore(resolvedType)}.title`, {
defaultValue: "", defaultValue: "",
}); });
}, }
@discourseComputed("humanType") @discourseComputed("humanType")
humanTypeCssClass(humanType) { humanTypeCssClass(humanType) {
return "-" + dasherize(humanType); return "-" + dasherize(humanType);
}, }
@discourseComputed("resolvedType") @discourseComputed("resolvedType")
humanNoun(resolvedType) { humanNoun(resolvedType) {
return I18n.t(`review.types.${underscore(resolvedType)}.noun`, { return I18n.t(`review.types.${underscore(resolvedType)}.noun`, {
defaultValue: "reviewable", defaultValue: "reviewable",
}); });
}, }
@discourseComputed("humanNoun") @discourseComputed("humanNoun")
flaggedReviewableContextQuestion(humanNoun) { flaggedReviewableContextQuestion(humanNoun) {
@ -66,12 +72,12 @@ const Reviewable = RestModel.extend({
reviewable_human_score_types: listOfQuestions, reviewable_human_score_types: listOfQuestions,
reviewable_type: humanNoun, reviewable_type: humanNoun,
}); });
}, }
@discourseComputed("category_id") @discourseComputed("category_id")
category() { category() {
return Category.findById(this.category_id); return Category.findById(this.category_id);
}, }
update(updates) { update(updates) {
// If no changes, do nothing // If no changes, do nothing
@ -92,15 +98,5 @@ const Reviewable = RestModel.extend({
this.setProperties(updated); this.setProperties(updated);
}); });
}, }
}); }
Reviewable.reopenClass({
munge(json) {
// ensure we are not overriding category computed property
delete json.category;
return json;
},
});
export default Reviewable;

View File

@ -3,14 +3,10 @@ import RestModel from "discourse/models/rest";
// A data model representing current session data. You can put transient // A data model representing current session data. You can put transient
// data here you might want later. It is not stored or serialized anywhere. // data here you might want later. It is not stored or serialized anywhere.
const Session = RestModel.extend({ export default class Session extends RestModel.extend().reopenClass(Singleton) {
hasFocus: null, hasFocus = null;
init() { init() {
this.set("highestSeenByTopic", {}); this.set("highestSeenByTopic", {});
}, }
}); }
Session.reopenClass(Singleton);
export default Session;

View File

@ -13,149 +13,17 @@ import deprecated from "discourse-common/lib/deprecated";
import { getOwnerWithFallback } from "discourse-common/lib/get-owner"; import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
const Site = RestModel.extend({ export default class Site extends RestModel.extend().reopenClass(Singleton) {
isReadOnly: alias("is_readonly"), static createCurrent() {
init() {
this._super(...arguments);
this.topicCountDesc = ["topic_count:desc"];
this.categories = this.categories || [];
},
@discourseComputed("notification_types")
notificationLookup(notificationTypes) {
const result = [];
Object.keys(notificationTypes).forEach(
(k) => (result[notificationTypes[k]] = k)
);
return result;
},
@discourseComputed("post_action_types.[]")
flagTypes() {
const postActionTypes = this.post_action_types;
if (!postActionTypes) {
return [];
}
return postActionTypes.filterBy("is_flag", true);
},
categoriesByCount: sort("categories", "topicCountDesc"),
collectUserFields(fields) {
fields = fields || {};
let siteFields = this.user_fields;
if (!isEmpty(siteFields)) {
return siteFields.map((f) => {
let value = fields ? fields[f.id.toString()] : null;
value = value || htmlSafe("&mdash;");
return { name: f.name, value };
});
}
return [];
},
// Sort subcategories under parents
@discourseComputed("categoriesByCount", "categories.[]")
sortedCategories(categories) {
return Category.sortCategories(categories);
},
// Returns it in the correct order, by setting
@discourseComputed("categories.[]")
categoriesList(categories) {
return this.siteSettings.fixed_category_positions
? categories
: this.sortedCategories;
},
@discourseComputed("categories.[]", "categories.@each.notification_level")
trackedCategoriesList(categories) {
const trackedCategories = [];
for (const category of categories) {
if (category.isTracked) {
if (
this.siteSettings.allow_uncategorized_topics ||
!category.isUncategorizedCategory
) {
trackedCategories.push(category);
}
}
}
return trackedCategories;
},
postActionTypeById(id) {
return this.get("postActionByIdLookup.action" + id);
},
topicFlagTypeById(id) {
return this.get("topicFlagByIdLookup.action" + id);
},
removeCategory(id) {
const categories = this.categories;
const existingCategory = categories.findBy("id", id);
if (existingCategory) {
categories.removeObject(existingCategory);
delete this.categoriesById.categoryId;
}
},
updateCategory(newCategory) {
const categories = this.categories;
const categoryId = get(newCategory, "id");
const existingCategory = categories.findBy("id", categoryId);
// Don't update null permissions
if (newCategory.permission === null) {
delete newCategory.permission;
}
if (existingCategory) {
existingCategory.setProperties(newCategory);
return existingCategory;
} else {
// TODO insert in right order?
newCategory = this.store.createRecord("category", newCategory);
categories.pushObject(newCategory);
this.categoriesById[categoryId] = newCategory;
newCategory.set(
"parentCategory",
this.categoriesById[newCategory.parent_category_id]
);
newCategory.set(
"subcategories",
this.categories.filterBy("parent_category_id", categoryId)
);
if (newCategory.parentCategory) {
if (!newCategory.parentCategory.subcategories) {
newCategory.parentCategory.set("subcategories", []);
}
newCategory.parentCategory.subcategories.pushObject(newCategory);
}
return newCategory;
}
},
});
Site.reopenClass(Singleton, {
// The current singleton will retrieve its attributes from the `PreloadStore`.
createCurrent() {
const store = getOwnerWithFallback(this).lookup("service:store"); const store = getOwnerWithFallback(this).lookup("service:store");
const siteAttributes = PreloadStore.get("site"); const siteAttributes = PreloadStore.get("site");
siteAttributes["isReadOnly"] = PreloadStore.get("isReadOnly"); siteAttributes["isReadOnly"] = PreloadStore.get("isReadOnly");
siteAttributes["isStaffWritesOnly"] = PreloadStore.get("isStaffWritesOnly"); siteAttributes["isStaffWritesOnly"] = PreloadStore.get("isStaffWritesOnly");
return store.createRecord("site", siteAttributes); return store.createRecord("site", siteAttributes);
}, }
create() { static create() {
const result = this._super.apply(this, arguments); const result = super.create.apply(this, arguments);
const store = result.store; const store = result.store;
if (result.categories) { if (result.categories) {
@ -233,8 +101,136 @@ Site.reopenClass(Singleton, {
} }
return result; return result;
}, }
});
@alias("is_readonly") isReadOnly;
@sort("categories", "topicCountDesc") categoriesByCount;
init() {
super.init(...arguments);
this.topicCountDesc = ["topic_count:desc"];
this.categories = this.categories || [];
}
@discourseComputed("notification_types")
notificationLookup(notificationTypes) {
const result = [];
Object.keys(notificationTypes).forEach(
(k) => (result[notificationTypes[k]] = k)
);
return result;
}
@discourseComputed("post_action_types.[]")
flagTypes() {
const postActionTypes = this.post_action_types;
if (!postActionTypes) {
return [];
}
return postActionTypes.filterBy("is_flag", true);
}
collectUserFields(fields) {
fields = fields || {};
let siteFields = this.user_fields;
if (!isEmpty(siteFields)) {
return siteFields.map((f) => {
let value = fields ? fields[f.id.toString()] : null;
value = value || htmlSafe("&mdash;");
return { name: f.name, value };
});
}
return [];
}
// Sort subcategories under parents
@discourseComputed("categoriesByCount", "categories.[]")
sortedCategories(categories) {
return Category.sortCategories(categories);
}
// Returns it in the correct order, by setting
@discourseComputed("categories.[]")
categoriesList(categories) {
return this.siteSettings.fixed_category_positions
? categories
: this.sortedCategories;
}
@discourseComputed("categories.[]", "categories.@each.notification_level")
trackedCategoriesList(categories) {
const trackedCategories = [];
for (const category of categories) {
if (category.isTracked) {
if (
this.siteSettings.allow_uncategorized_topics ||
!category.isUncategorizedCategory
) {
trackedCategories.push(category);
}
}
}
return trackedCategories;
}
postActionTypeById(id) {
return this.get("postActionByIdLookup.action" + id);
}
topicFlagTypeById(id) {
return this.get("topicFlagByIdLookup.action" + id);
}
removeCategory(id) {
const categories = this.categories;
const existingCategory = categories.findBy("id", id);
if (existingCategory) {
categories.removeObject(existingCategory);
delete this.categoriesById.categoryId;
}
}
updateCategory(newCategory) {
const categories = this.categories;
const categoryId = get(newCategory, "id");
const existingCategory = categories.findBy("id", categoryId);
// Don't update null permissions
if (newCategory.permission === null) {
delete newCategory.permission;
}
if (existingCategory) {
existingCategory.setProperties(newCategory);
return existingCategory;
} else {
// TODO insert in right order?
newCategory = this.store.createRecord("category", newCategory);
categories.pushObject(newCategory);
this.categoriesById[categoryId] = newCategory;
newCategory.set(
"parentCategory",
this.categoriesById[newCategory.parent_category_id]
);
newCategory.set(
"subcategories",
this.categories.filterBy("parent_category_id", categoryId)
);
if (newCategory.parentCategory) {
if (!newCategory.parentCategory.subcategories) {
newCategory.parentCategory.set("subcategories", []);
}
newCategory.parentCategory.subcategories.pushObject(newCategory);
}
return newCategory;
}
}
}
if (typeof Discourse !== "undefined") { if (typeof Discourse !== "undefined") {
let warned = false; let warned = false;
@ -252,5 +248,3 @@ if (typeof Discourse !== "undefined") {
}, },
}); });
} }
export default Site;

View File

@ -3,10 +3,8 @@ import $ from "jquery";
import { Promise } from "rsvp"; import { Promise } from "rsvp";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
const StaticPage = EmberObject.extend(); export default class StaticPage extends EmberObject {
static find(path) {
StaticPage.reopenClass({
find(path) {
return new Promise((resolve) => { return new Promise((resolve) => {
// Models shouldn't really be doing Ajax request, but this is a huge speed boost if we // Models shouldn't really be doing Ajax request, but this is a huge speed boost if we
// preload content. // preload content.
@ -23,7 +21,5 @@ StaticPage.reopenClass({
); );
} }
}); });
}, }
}); }
export default StaticPage;

View File

@ -2,7 +2,7 @@ import PermissionType from "discourse/models/permission-type";
import RestModel from "discourse/models/rest"; import RestModel from "discourse/models/rest";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default RestModel.extend({ export default class TagGroup extends RestModel {
@discourseComputed("permissions") @discourseComputed("permissions")
permissionName(permissions) { permissionName(permissions) {
if (!permissions) { if (!permissions) {
@ -16,5 +16,5 @@ export default RestModel.extend({
} else { } else {
return "private"; return "private";
} }
}, }
}); }

View File

@ -2,16 +2,16 @@ import { readOnly } from "@ember/object/computed";
import RestModel from "discourse/models/rest"; import RestModel from "discourse/models/rest";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default RestModel.extend({ export default class Tag extends RestModel {
pmOnly: readOnly("pm_only"), @readOnly("pm_only") pmOnly;
@discourseComputed("count", "pm_count") @discourseComputed("count", "pm_count")
totalCount(count, pmCount) { totalCount(count, pmCount) {
return pmCount ? count + pmCount : count; return pmCount ? count + pmCount : count;
}, }
@discourseComputed("id") @discourseComputed("id")
searchContext(id) { searchContext(id) {
return { type: "tag", id, tag: this, name: id }; return { type: "tag", id, tag: this, name: id };
}, }
}); }

View File

@ -8,10 +8,10 @@ import RestModel from "discourse/models/rest";
When showing topics in lists and such this information should not be required. When showing topics in lists and such this information should not be required.
**/ **/
const TopicDetails = RestModel.extend({ export default class TopicDetails extends RestModel {
store: service(), @service store;
loaded: false, loaded = false;
updateFromJson(details) { updateFromJson(details) {
const topic = this.topic; const topic = this.topic;
@ -31,7 +31,7 @@ const TopicDetails = RestModel.extend({
this.setProperties(details); this.setProperties(details);
this.set("loaded", true); this.set("loaded", true);
}, }
updateNotifications(level) { updateNotifications(level) {
return ajax(`/t/${this.get("topic.id")}/notifications`, { return ajax(`/t/${this.get("topic.id")}/notifications`, {
@ -43,7 +43,7 @@ const TopicDetails = RestModel.extend({
notifications_reason_id: null, notifications_reason_id: null,
}); });
}); });
}, }
removeAllowedGroup(group) { removeAllowedGroup(group) {
const groups = this.allowed_groups; const groups = this.allowed_groups;
@ -55,7 +55,7 @@ const TopicDetails = RestModel.extend({
}).then(() => { }).then(() => {
groups.removeObject(groups.findBy("name", name)); groups.removeObject(groups.findBy("name", name));
}); });
}, }
removeAllowedUser(user) { removeAllowedUser(user) {
const users = this.allowed_users; const users = this.allowed_users;
@ -67,7 +67,5 @@ const TopicDetails = RestModel.extend({
}).then(() => { }).then(() => {
users.removeObject(users.findBy("username", username)); users.removeObject(users.findBy("username", username));
}); });
}, }
}); }
export default TopicDetails;

View File

@ -40,9 +40,88 @@ function displayCategoryInList(site, category) {
return true; return true;
} }
const TopicList = RestModel.extend({ export default class TopicList extends RestModel {
session: service(), static topicsFrom(store, result, opts) {
canLoadMore: notEmpty("more_topics_url"), if (!result) {
return;
}
opts = opts || {};
let listKey = opts.listKey || "topics";
// Stitch together our side loaded data
const users = extractByKey(result.users, User);
const groups = extractByKey(result.primary_groups, EmberObject);
if (result.topic_list.categories) {
result.topic_list.categories.forEach((c) => {
Site.current().updateCategory(c);
});
}
return result.topic_list[listKey].map((t) => {
t.posters.forEach((p) => {
p.user = users[p.user_id];
p.extraClasses = p.extras;
if (p.primary_group_id) {
p.primary_group = groups[p.primary_group_id];
if (p.primary_group) {
p.extraClasses = `${p.extraClasses || ""} group-${
p.primary_group.name
}`;
}
}
});
if (t.participants) {
t.participants.forEach((p) => (p.user = users[p.user_id]));
}
return store.createRecord("topic", t);
});
}
static munge(json, store) {
json.inserted = json.inserted || [];
json.can_create_topic = json.topic_list.can_create_topic;
json.more_topics_url = json.topic_list.more_topics_url;
json.for_period = json.topic_list.for_period;
json.loaded = true;
json.per_page = json.topic_list.per_page;
json.topics = this.topicsFrom(store, json);
if (json.topic_list.shared_drafts) {
json.sharedDrafts = this.topicsFrom(store, json, {
listKey: "shared_drafts",
});
}
return json;
}
static find(filter, params) {
deprecated(
`TopicList.find is deprecated. Use \`findFiltered("topicList")\` on the \`store\` service instead.`,
{
id: "topic-list-find",
since: "3.1.0.beta5",
dropFrom: "3.2.0.beta1",
}
);
const store = getOwnerWithFallback(this).lookup("service:store");
return store.findFiltered("topicList", { filter, params });
}
// hide the category when it has no children
static hideUniformCategory(list, category) {
list.set("hideCategory", !displayCategoryInList(list.site, category));
}
@service session;
@notEmpty("more_topics_url") canLoadMore;
forEachNew(topics, callback) { forEachNew(topics, callback) {
const topicIds = new Set(); const topicIds = new Set();
@ -53,7 +132,7 @@ const TopicList = RestModel.extend({
callback(topic); callback(topic);
} }
}); });
}, }
updateSortParams(order, ascending) { updateSortParams(order, ascending) {
let params = { ...(this.params || {}) }; let params = { ...(this.params || {}) };
@ -67,7 +146,7 @@ const TopicList = RestModel.extend({
} }
this.set("params", params); this.set("params", params);
}, }
updateNewListSubsetParam(subset) { updateNewListSubsetParam(subset) {
let params = { ...(this.params || {}) }; let params = { ...(this.params || {}) };
@ -79,7 +158,7 @@ const TopicList = RestModel.extend({
} }
this.set("params", params); this.set("params", params);
}, }
loadMore() { loadMore() {
if (this.loadingMore) { if (this.loadingMore) {
@ -128,7 +207,7 @@ const TopicList = RestModel.extend({
// Return a promise indicating no more results // Return a promise indicating no more results
return Promise.resolve(); return Promise.resolve();
} }
}, }
// loads topics with these ids "before" the current topics // loads topics with these ids "before" the current topics
loadBefore(topic_ids, storeInSession) { loadBefore(topic_ids, storeInSession) {
@ -152,87 +231,5 @@ const TopicList = RestModel.extend({
this.session.set("topicList", this); this.session.set("topicList", this);
} }
}); });
}, }
}); }
TopicList.reopenClass({
topicsFrom(store, result, opts) {
if (!result) {
return;
}
opts = opts || {};
let listKey = opts.listKey || "topics";
// Stitch together our side loaded data
const users = extractByKey(result.users, User);
const groups = extractByKey(result.primary_groups, EmberObject);
if (result.topic_list.categories) {
result.topic_list.categories.forEach((c) => {
Site.current().updateCategory(c);
});
}
return result.topic_list[listKey].map((t) => {
t.posters.forEach((p) => {
p.user = users[p.user_id];
p.extraClasses = p.extras;
if (p.primary_group_id) {
p.primary_group = groups[p.primary_group_id];
if (p.primary_group) {
p.extraClasses = `${p.extraClasses || ""} group-${
p.primary_group.name
}`;
}
}
});
if (t.participants) {
t.participants.forEach((p) => (p.user = users[p.user_id]));
}
return store.createRecord("topic", t);
});
},
munge(json, store) {
json.inserted = json.inserted || [];
json.can_create_topic = json.topic_list.can_create_topic;
json.more_topics_url = json.topic_list.more_topics_url;
json.for_period = json.topic_list.for_period;
json.loaded = true;
json.per_page = json.topic_list.per_page;
json.topics = this.topicsFrom(store, json);
if (json.topic_list.shared_drafts) {
json.sharedDrafts = this.topicsFrom(store, json, {
listKey: "shared_drafts",
});
}
return json;
},
find(filter, params) {
deprecated(
`TopicList.find is deprecated. Use \`findFiltered("topicList")\` on the \`store\` service instead.`,
{
id: "topic-list-find",
since: "3.1.0.beta5",
dropFrom: "3.2.0.beta1",
}
);
const store = getOwnerWithFallback(this).lookup("service:store");
return store.findFiltered("topicList", { filter, params });
},
// hide the category when it has no children
hideUniformCategory(list, category) {
list.set("hideCategory", !displayCategoryInList(list.site, category));
},
});
export default TopicList;

View File

@ -1,10 +1,8 @@
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import RestModel from "discourse/models/rest"; import RestModel from "discourse/models/rest";
const TopicTimer = RestModel.extend({}); export default class TopicTimer extends RestModel {
static update(
TopicTimer.reopenClass({
update(
topicId, topicId,
time, time,
basedOnLastPost, basedOnLastPost,
@ -32,7 +30,5 @@ TopicTimer.reopenClass({
type: "POST", type: "POST",
data, data,
}); });
}, }
}); }
export default TopicTimer;

View File

@ -47,19 +47,19 @@ function hasMutedTags(topicTags, mutedTags, siteSettings) {
); );
} }
const TopicTrackingState = EmberObject.extend({ export default class TopicTrackingState extends EmberObject {
messageCount: 0, messageCount = 0;
init() { init() {
this._super(...arguments); super.init(...arguments);
this.states = new Map(); this.states = new Map();
this.stateChangeCallbacks = {}; this.stateChangeCallbacks = {};
this._trackedTopicLimit = 4000; this._trackedTopicLimit = 4000;
}, }
willDestroy() { willDestroy() {
this._super(...arguments); super.willDestroy(...arguments);
this.messageBus.unsubscribe("/latest", this._processChannelPayload); this.messageBus.unsubscribe("/latest", this._processChannelPayload);
@ -75,7 +75,7 @@ const TopicTrackingState = EmberObject.extend({
this.messageBus.unsubscribe("/delete", this.onDeleteMessage); this.messageBus.unsubscribe("/delete", this.onDeleteMessage);
this.messageBus.unsubscribe("/recover", this.onRecoverMessage); this.messageBus.unsubscribe("/recover", this.onRecoverMessage);
this.messageBus.unsubscribe("/destroy", this.onDestroyMessage); this.messageBus.unsubscribe("/destroy", this.onDestroyMessage);
}, }
/** /**
* Subscribe to MessageBus channels which are used for publishing changes * Subscribe to MessageBus channels which are used for publishing changes
@ -134,19 +134,19 @@ const TopicTrackingState = EmberObject.extend({
this.onDestroyMessage, this.onDestroyMessage,
meta["/destroy"] ?? messageBusDefaultNewMessageId meta["/destroy"] ?? messageBusDefaultNewMessageId
); );
}, }
@bind @bind
onDeleteMessage(msg) { onDeleteMessage(msg) {
this.modifyStateProp(msg, "deleted", true); this.modifyStateProp(msg, "deleted", true);
this.incrementMessageCount(); this.incrementMessageCount();
}, }
@bind @bind
onRecoverMessage(msg) { onRecoverMessage(msg) {
this.modifyStateProp(msg, "deleted", false); this.modifyStateProp(msg, "deleted", false);
this.incrementMessageCount(); this.incrementMessageCount();
}, }
@bind @bind
onDestroyMessage(msg) { onDestroyMessage(msg) {
@ -159,15 +159,15 @@ const TopicTrackingState = EmberObject.extend({
) { ) {
DiscourseURL.redirectTo("/"); DiscourseURL.redirectTo("/");
} }
}, }
mutedTopics() { mutedTopics() {
return (this.currentUser && this.currentUser.muted_topics) || []; return (this.currentUser && this.currentUser.muted_topics) || [];
}, }
unmutedTopics() { unmutedTopics() {
return (this.currentUser && this.currentUser.unmuted_topics) || []; return (this.currentUser && this.currentUser.unmuted_topics) || [];
}, }
trackMutedOrUnmutedTopic(data) { trackMutedOrUnmutedTopic(data) {
let topics, key; let topics, key;
@ -183,7 +183,7 @@ const TopicTrackingState = EmberObject.extend({
createdAt: Date.now(), createdAt: Date.now(),
}); });
this.currentUser && this.currentUser.set(key, topics); this.currentUser && this.currentUser.set(key, topics);
}, }
pruneOldMutedAndUnmutedTopics() { pruneOldMutedAndUnmutedTopics() {
const now = Date.now(); const now = Date.now();
@ -196,15 +196,15 @@ const TopicTrackingState = EmberObject.extend({
this.currentUser && this.currentUser &&
this.currentUser.set("muted_topics", mutedTopics) && this.currentUser.set("muted_topics", mutedTopics) &&
this.currentUser.set("unmuted_topics", unmutedTopics); this.currentUser.set("unmuted_topics", unmutedTopics);
}, }
isMutedTopic(topicId) { isMutedTopic(topicId) {
return !!this.mutedTopics().findBy("topicId", topicId); return !!this.mutedTopics().findBy("topicId", topicId);
}, }
isUnmutedTopic(topicId) { isUnmutedTopic(topicId) {
return !!this.unmutedTopics().findBy("topicId", topicId); return !!this.unmutedTopics().findBy("topicId", topicId);
}, }
/** /**
* Updates the topic's last_read_post_number to the highestSeen post * Updates the topic's last_read_post_number to the highestSeen post
@ -233,7 +233,7 @@ const TopicTrackingState = EmberObject.extend({
this.modifyStateProp(topicId, "last_read_post_number", highestSeen); this.modifyStateProp(topicId, "last_read_post_number", highestSeen);
this.incrementMessageCount(); this.incrementMessageCount();
} }
}, }
/** /**
* Used to count incoming topics which will be displayed in a message * Used to count incoming topics which will be displayed in a message
@ -321,7 +321,7 @@ const TopicTrackingState = EmberObject.extend({
// hasIncoming relies on this count // hasIncoming relies on this count
this.set("incomingCount", this.newIncoming.length); this.set("incomingCount", this.newIncoming.length);
}, }
/** /**
* Resets the number of incoming topics to 0 and flushes the new topics * Resets the number of incoming topics to 0 and flushes the new topics
@ -333,7 +333,7 @@ const TopicTrackingState = EmberObject.extend({
resetTracking() { resetTracking() {
this.newIncoming = []; this.newIncoming = [];
this.set("incomingCount", 0); this.set("incomingCount", 0);
}, }
/** /**
* Track how many new topics came for the specified filter. * Track how many new topics came for the specified filter.
@ -375,7 +375,7 @@ const TopicTrackingState = EmberObject.extend({
this.set("filterTag", tag); this.set("filterTag", tag);
this.set("filter", filter); this.set("filter", filter);
this.set("incomingCount", 0); this.set("incomingCount", 0);
}, }
/** /**
* Used to determine whether to show the message at the top of the topic list * Used to determine whether to show the message at the top of the topic list
@ -386,7 +386,7 @@ const TopicTrackingState = EmberObject.extend({
@discourseComputed("incomingCount") @discourseComputed("incomingCount")
hasIncoming(incomingCount) { hasIncoming(incomingCount) {
return incomingCount && incomingCount > 0; return incomingCount && incomingCount > 0;
}, }
/** /**
* Removes the topic ID provided from the tracker state. * Removes the topic ID provided from the tracker state.
@ -400,7 +400,7 @@ const TopicTrackingState = EmberObject.extend({
if (this.states.delete(this._stateKey(topicId))) { if (this.states.delete(this._stateKey(topicId))) {
this._afterStateChange(); this._afterStateChange();
} }
}, }
/** /**
* Removes multiple topics from the state at once, and increments * Removes multiple topics from the state at once, and increments
@ -415,7 +415,7 @@ const TopicTrackingState = EmberObject.extend({
topicIds.forEach((topicId) => this.removeTopic(topicId)); topicIds.forEach((topicId) => this.removeTopic(topicId));
this.incrementMessageCount(); this.incrementMessageCount();
this._afterStateChange(); this._afterStateChange();
}, }
/** /**
* If we have a cached topic list, we can update it from our tracking information * If we have a cached topic list, we can update it from our tracking information
@ -466,7 +466,7 @@ const TopicTrackingState = EmberObject.extend({
}); });
} }
}); });
}, }
/** /**
* Uses the provided topic list to apply changes to the in-memory topic * Uses the provided topic list to apply changes to the in-memory topic
@ -509,25 +509,25 @@ const TopicTrackingState = EmberObject.extend({
} }
this.incrementMessageCount(); this.incrementMessageCount();
}, }
incrementMessageCount() { incrementMessageCount() {
this.incrementProperty("messageCount"); this.incrementProperty("messageCount");
}, }
_generateCallbackId() { _generateCallbackId() {
return Math.random().toString(12).slice(2, 11); return Math.random().toString(12).slice(2, 11);
}, }
onStateChange(cb) { onStateChange(cb) {
let callbackId = this._generateCallbackId(); let callbackId = this._generateCallbackId();
this.stateChangeCallbacks[callbackId] = cb; this.stateChangeCallbacks[callbackId] = cb;
return callbackId; return callbackId;
}, }
offStateChange(callbackId) { offStateChange(callbackId) {
delete this.stateChangeCallbacks[callbackId]; delete this.stateChangeCallbacks[callbackId];
}, }
getSubCategoryIds(categoryId) { getSubCategoryIds(categoryId) {
const result = [categoryId]; const result = [categoryId];
@ -542,7 +542,7 @@ const TopicTrackingState = EmberObject.extend({
} }
return new Set(result); return new Set(result);
}, }
countCategoryByState({ countCategoryByState({
type, type,
@ -606,7 +606,7 @@ const TopicTrackingState = EmberObject.extend({
return true; return true;
}).length; }).length;
}, }
countNew({ categoryId, tagId, noSubcategories, customFilterFn } = {}) { countNew({ categoryId, tagId, noSubcategories, customFilterFn } = {}) {
return this.countCategoryByState({ return this.countCategoryByState({
@ -616,7 +616,7 @@ const TopicTrackingState = EmberObject.extend({
noSubcategories, noSubcategories,
customFilterFn, customFilterFn,
}); });
}, }
countUnread({ categoryId, tagId, noSubcategories, customFilterFn } = {}) { countUnread({ categoryId, tagId, noSubcategories, customFilterFn } = {}) {
return this.countCategoryByState({ return this.countCategoryByState({
@ -626,7 +626,7 @@ const TopicTrackingState = EmberObject.extend({
noSubcategories, noSubcategories,
customFilterFn, customFilterFn,
}); });
}, }
countNewAndUnread({ countNewAndUnread({
categoryId, categoryId,
@ -641,7 +641,7 @@ const TopicTrackingState = EmberObject.extend({
noSubcategories, noSubcategories,
customFilterFn, customFilterFn,
}); });
}, }
/** /**
* Calls the provided callback for each of the currently tracked topics * Calls the provided callback for each of the currently tracked topics
@ -657,7 +657,7 @@ const TopicTrackingState = EmberObject.extend({
this._trackedTopics(opts).forEach((trackedTopic) => { this._trackedTopics(opts).forEach((trackedTopic) => {
fn(trackedTopic.topic, trackedTopic.newTopic, trackedTopic.unreadTopic); fn(trackedTopic.topic, trackedTopic.newTopic, trackedTopic.unreadTopic);
}); });
}, }
/** /**
* Using the array of tags provided, tallies up all topics via forEachTracked * Using the array of tags provided, tallies up all topics via forEachTracked
@ -710,7 +710,7 @@ const TopicTrackingState = EmberObject.extend({
); );
return counts; return counts;
}, }
countCategory(category_id, tagId) { countCategory(category_id, tagId) {
let sum = 0; let sum = 0;
@ -728,7 +728,7 @@ const TopicTrackingState = EmberObject.extend({
} }
} }
return sum; return sum;
}, }
lookupCount({ type, category, tagId, noSubcategories, customFilterFn } = {}) { lookupCount({ type, category, tagId, noSubcategories, customFilterFn } = {}) {
if (type === "latest") { if (type === "latest") {
@ -782,7 +782,7 @@ const TopicTrackingState = EmberObject.extend({
return this.countCategory(categoryId, tagId); return this.countCategory(categoryId, tagId);
} }
} }
}, }
loadStates(data) { loadStates(data) {
if (!data || data.length === 0) { if (!data || data.length === 0) {
@ -796,7 +796,7 @@ const TopicTrackingState = EmberObject.extend({
if (modified) { if (modified) {
this._afterStateChange(); this._afterStateChange();
} }
}, }
_setState({ topic, data, skipAfterStateChange }) { _setState({ topic, data, skipAfterStateChange }) {
const stateKey = this._stateKey(topic); const stateKey = this._stateKey(topic);
@ -813,11 +813,11 @@ const TopicTrackingState = EmberObject.extend({
} else { } else {
return false; return false;
} }
}, }
modifyState(topic, data) { modifyState(topic, data) {
this._setState({ topic, data }); this._setState({ topic, data });
}, }
modifyStateProp(topic, prop, data) { modifyStateProp(topic, prop, data) {
const state = this.findState(topic); const state = this.findState(topic);
@ -825,11 +825,11 @@ const TopicTrackingState = EmberObject.extend({
state[prop] = data; state[prop] = data;
this._afterStateChange(); this._afterStateChange();
} }
}, }
findState(topicOrId) { findState(topicOrId) {
return this.states.get(this._stateKey(topicOrId)); return this.states.get(this._stateKey(topicOrId));
}, }
/* /*
* private * private
@ -860,7 +860,7 @@ const TopicTrackingState = EmberObject.extend({
} }
} }
} }
}, }
// this updates the topic in the state to match the // this updates the topic in the state to match the
// topic from the list (e.g. updates category, highest read post // topic from the list (e.g. updates category, highest read post
@ -908,7 +908,7 @@ const TopicTrackingState = EmberObject.extend({
} }
return newState; return newState;
}, }
// this stops sync of tracking state when list is filtered, in the past this // this stops sync of tracking state when list is filtered, in the past this
// would cause the tracking state to become inconsistent. // would cause the tracking state to become inconsistent.
@ -925,7 +925,7 @@ const TopicTrackingState = EmberObject.extend({
} }
return shouldCompensate; return shouldCompensate;
}, }
// any state that is not in the provided list must be updated // any state that is not in the provided list must be updated
// based on the filter selected so we do not have any incorrect // based on the filter selected so we do not have any incorrect
@ -957,7 +957,7 @@ const TopicTrackingState = EmberObject.extend({
this.modifyState(topicKey, newState); this.modifyState(topicKey, newState);
} }
}, }
// processes the data sent via messageBus, called by establishChannels // processes the data sent via messageBus, called by establishChannels
@bind @bind
@ -1054,7 +1054,7 @@ const TopicTrackingState = EmberObject.extend({
this.incrementMessageCount(); this.incrementMessageCount();
} }
} }
}, }
_dismissNewTopics(topicIds) { _dismissNewTopics(topicIds) {
topicIds.forEach((topicId) => { topicIds.forEach((topicId) => {
@ -1062,7 +1062,7 @@ const TopicTrackingState = EmberObject.extend({
}); });
this.incrementMessageCount(); this.incrementMessageCount();
}, }
_dismissNewPosts(topicIds) { _dismissNewPosts(topicIds) {
topicIds.forEach((topicId) => { topicIds.forEach((topicId) => {
@ -1078,13 +1078,13 @@ const TopicTrackingState = EmberObject.extend({
}); });
this.incrementMessageCount(); this.incrementMessageCount();
}, }
_addIncoming(topicId) { _addIncoming(topicId) {
if (!this.newIncoming.includes(topicId)) { if (!this.newIncoming.includes(topicId)) {
this.newIncoming.push(topicId); this.newIncoming.push(topicId);
} }
}, }
_trackedTopics(opts = {}) { _trackedTopics(opts = {}) {
return Array.from(this.states.values()) return Array.from(this.states.values())
@ -1096,7 +1096,7 @@ const TopicTrackingState = EmberObject.extend({
} }
}) })
.compact(); .compact();
}, }
_stateKey(topicOrId) { _stateKey(topicOrId) {
if (typeof topicOrId === "number") { if (typeof topicOrId === "number") {
@ -1106,17 +1106,17 @@ const TopicTrackingState = EmberObject.extend({
} else { } else {
return `t${topicOrId.topic_id}`; return `t${topicOrId.topic_id}`;
} }
}, }
_afterStateChange() { _afterStateChange() {
this.notifyPropertyChange("states"); this.notifyPropertyChange("states");
Object.values(this.stateChangeCallbacks).forEach((cb) => cb()); Object.values(this.stateChangeCallbacks).forEach((cb) => cb());
}, }
_maxStateSizeReached() { _maxStateSizeReached() {
return this.states.size >= this._trackedTopicLimit; return this.states.size >= this._trackedTopicLimit;
}, }
}); }
export function startTracking(tracking) { export function startTracking(tracking) {
PreloadStore.getAndRemove("topicTrackingStates").then((data) => PreloadStore.getAndRemove("topicTrackingStates").then((data) =>
@ -1127,5 +1127,3 @@ export function startTracking(tracking) {
tracking.establishChannels(meta) tracking.establishChannels(meta)
); );
} }
export default TopicTrackingState;

View File

@ -1,10 +1,10 @@
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
export default EmberObject.extend({ export default class UserActionGroup extends EmberObject {
push(item) { push(item) {
if (!this.items) { if (!this.items) {
this.items = []; this.items = [];
} }
return this.items.push(item); return this.items.push(item);
}, }
}); }

View File

@ -3,16 +3,16 @@ import RestModel from "discourse/models/rest";
import UserAction from "discourse/models/user-action"; import UserAction from "discourse/models/user-action";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default RestModel.extend({ export default class UserActionStat extends RestModel {
@i18n("action_type", "user_action_groups.%@") description;
@discourseComputed("action_type") @discourseComputed("action_type")
isPM(actionType) { isPM(actionType) {
return ( return (
actionType === UserAction.TYPES.messages_sent || actionType === UserAction.TYPES.messages_sent ||
actionType === UserAction.TYPES.messages_received actionType === UserAction.TYPES.messages_received
); );
}, }
description: i18n("action_type", "user_action_groups.%@"),
@discourseComputed("action_type") @discourseComputed("action_type")
isResponse(actionType) { isResponse(actionType) {
@ -20,5 +20,5 @@ export default RestModel.extend({
actionType === UserAction.TYPES.replies || actionType === UserAction.TYPES.replies ||
actionType === UserAction.TYPES.quotes actionType === UserAction.TYPES.quotes
); );
}, }
}); }

View File

@ -26,153 +26,28 @@ Object.keys(UserActionTypes).forEach(
(k) => (InvertedActionTypes[k] = UserActionTypes[k]) (k) => (InvertedActionTypes[k] = UserActionTypes[k])
); );
const UserAction = RestModel.extend({ export default class UserAction extends RestModel {
@discourseComputed("category_id") static TYPES = UserActionTypes;
category() {
return Category.findById(this.category_id);
},
@discourseComputed("action_type") static TYPES_INVERTED = InvertedActionTypes;
descriptionKey(action) {
if (action === null || UserAction.TO_SHOW.includes(action)) {
if (this.isPM) {
return this.sameUser ? "sent_by_you" : "sent_by_user";
} else {
return this.sameUser ? "posted_by_you" : "posted_by_user";
}
}
if (this.topicType) { static TO_COLLAPSE = [
return this.sameUser ? "you_posted_topic" : "user_posted_topic"; UserActionTypes.likes_given,
} UserActionTypes.likes_received,
UserActionTypes.edits,
UserActionTypes.bookmarks,
];
if (this.postReplyType) { static TO_SHOW = [
if (this.reply_to_post_number) { UserActionTypes.likes_given,
return this.sameUser ? "you_replied_to_post" : "user_replied_to_post"; UserActionTypes.likes_received,
} else { UserActionTypes.edits,
return this.sameUser ? "you_replied_to_topic" : "user_replied_to_topic"; UserActionTypes.bookmarks,
} UserActionTypes.messages_sent,
} UserActionTypes.messages_received,
];
if (this.mentionType) { static collapseStream(stream) {
if (this.sameUser) {
return "you_mentioned_user";
} else {
return this.targetUser ? "user_mentioned_you" : "user_mentioned_user";
}
}
},
@discourseComputed("username")
sameUser(username) {
return username === User.currentProp("username");
},
@discourseComputed("target_username")
targetUser(targetUsername) {
return targetUsername === User.currentProp("username");
},
presentName: or("name", "username"),
targetDisplayName: or("target_name", "target_username"),
actingDisplayName: or("acting_name", "acting_username"),
@discourseComputed("target_username")
targetUserUrl(username) {
return userPath(username);
},
@discourseComputed("username")
usernameLower(username) {
return username.toLowerCase();
},
@discourseComputed("usernameLower")
userUrl(usernameLower) {
return userPath(usernameLower);
},
@discourseComputed()
postUrl() {
return postUrl(this.slug, this.topic_id, this.post_number);
},
@discourseComputed()
replyUrl() {
return postUrl(this.slug, this.topic_id, this.reply_to_post_number);
},
replyType: equal("action_type", UserActionTypes.replies),
postType: equal("action_type", UserActionTypes.posts),
topicType: equal("action_type", UserActionTypes.topics),
bookmarkType: equal("action_type", UserActionTypes.bookmarks),
messageSentType: equal("action_type", UserActionTypes.messages_sent),
messageReceivedType: equal("action_type", UserActionTypes.messages_received),
mentionType: equal("action_type", UserActionTypes.mentions),
isPM: or("messageSentType", "messageReceivedType"),
postReplyType: or("postType", "replyType"),
addChild(action) {
let groups = this.childGroups;
if (!groups) {
groups = {
likes: UserActionGroup.create({ icon: "heart" }),
stars: UserActionGroup.create({ icon: "star" }),
edits: UserActionGroup.create({ icon: "pencil-alt" }),
bookmarks: UserActionGroup.create({ icon: "bookmark" }),
};
}
this.set("childGroups", groups);
const bucket = (function () {
switch (action.action_type) {
case UserActionTypes.likes_given:
case UserActionTypes.likes_received:
return "likes";
case UserActionTypes.edits:
return "edits";
case UserActionTypes.bookmarks:
return "bookmarks";
}
})();
const current = groups[bucket];
if (current) {
current.push(action);
}
},
@discourseComputed(
"childGroups",
"childGroups.likes.items",
"childGroups.likes.items.[]",
"childGroups.stars.items",
"childGroups.stars.items.[]",
"childGroups.edits.items",
"childGroups.edits.items.[]",
"childGroups.bookmarks.items",
"childGroups.bookmarks.items.[]"
)
children() {
const g = this.childGroups;
let rval = [];
if (g) {
rval = [g.likes, g.stars, g.edits, g.bookmarks].filter(function (i) {
return i.get("items") && i.get("items").length > 0;
});
}
return rval;
},
switchToActing() {
this.setProperties({
username: this.acting_username,
name: this.actingDisplayName,
});
},
});
UserAction.reopenClass({
collapseStream(stream) {
const uniq = {}; const uniq = {};
const collapsed = []; const collapsed = [];
let pos = 0; let pos = 0;
@ -204,26 +79,147 @@ UserAction.reopenClass({
} }
}); });
return collapsed; return collapsed;
}, }
TYPES: UserActionTypes, @or("name", "username") presentName;
TYPES_INVERTED: InvertedActionTypes, @or("target_name", "target_username") targetDisplayName;
@or("acting_name", "acting_username") actingDisplayName;
@equal("action_type", UserActionTypes.replies) replyType;
@equal("action_type", UserActionTypes.posts) postType;
@equal("action_type", UserActionTypes.topics) topicType;
@equal("action_type", UserActionTypes.bookmarks) bookmarkType;
@equal("action_type", UserActionTypes.messages_sent) messageSentType;
@equal("action_type", UserActionTypes.messages_received) messageReceivedType;
@equal("action_type", UserActionTypes.mentions) mentionType;
@or("messageSentType", "messageReceivedType") isPM;
@or("postType", "replyType") postReplyType;
TO_COLLAPSE: [ @discourseComputed("category_id")
UserActionTypes.likes_given, category() {
UserActionTypes.likes_received, return Category.findById(this.category_id);
UserActionTypes.edits, }
UserActionTypes.bookmarks,
],
TO_SHOW: [ @discourseComputed("action_type")
UserActionTypes.likes_given, descriptionKey(action) {
UserActionTypes.likes_received, if (action === null || UserAction.TO_SHOW.includes(action)) {
UserActionTypes.edits, if (this.isPM) {
UserActionTypes.bookmarks, return this.sameUser ? "sent_by_you" : "sent_by_user";
UserActionTypes.messages_sent, } else {
UserActionTypes.messages_received, return this.sameUser ? "posted_by_you" : "posted_by_user";
], }
}); }
export default UserAction; if (this.topicType) {
return this.sameUser ? "you_posted_topic" : "user_posted_topic";
}
if (this.postReplyType) {
if (this.reply_to_post_number) {
return this.sameUser ? "you_replied_to_post" : "user_replied_to_post";
} else {
return this.sameUser ? "you_replied_to_topic" : "user_replied_to_topic";
}
}
if (this.mentionType) {
if (this.sameUser) {
return "you_mentioned_user";
} else {
return this.targetUser ? "user_mentioned_you" : "user_mentioned_user";
}
}
}
@discourseComputed("username")
sameUser(username) {
return username === User.currentProp("username");
}
@discourseComputed("target_username")
targetUser(targetUsername) {
return targetUsername === User.currentProp("username");
}
@discourseComputed("target_username")
targetUserUrl(username) {
return userPath(username);
}
@discourseComputed("username")
usernameLower(username) {
return username.toLowerCase();
}
@discourseComputed("usernameLower")
userUrl(usernameLower) {
return userPath(usernameLower);
}
@discourseComputed()
postUrl() {
return postUrl(this.slug, this.topic_id, this.post_number);
}
@discourseComputed()
replyUrl() {
return postUrl(this.slug, this.topic_id, this.reply_to_post_number);
}
addChild(action) {
let groups = this.childGroups;
if (!groups) {
groups = {
likes: UserActionGroup.create({ icon: "heart" }),
stars: UserActionGroup.create({ icon: "star" }),
edits: UserActionGroup.create({ icon: "pencil-alt" }),
bookmarks: UserActionGroup.create({ icon: "bookmark" }),
};
}
this.set("childGroups", groups);
const bucket = (function () {
switch (action.action_type) {
case UserActionTypes.likes_given:
case UserActionTypes.likes_received:
return "likes";
case UserActionTypes.edits:
return "edits";
case UserActionTypes.bookmarks:
return "bookmarks";
}
})();
const current = groups[bucket];
if (current) {
current.push(action);
}
}
@discourseComputed(
"childGroups",
"childGroups.likes.items",
"childGroups.likes.items.[]",
"childGroups.stars.items",
"childGroups.stars.items.[]",
"childGroups.edits.items",
"childGroups.edits.items.[]",
"childGroups.bookmarks.items",
"childGroups.bookmarks.items.[]"
)
children() {
const g = this.childGroups;
let rval = [];
if (g) {
rval = [g.likes, g.stars, g.edits, g.bookmarks].filter(function (i) {
return i.get("items") && i.get("items").length > 0;
});
}
return rval;
}
switchToActing() {
this.setProperties({
username: this.acting_username,
name: this.actingDisplayName,
});
}
}

View File

@ -7,34 +7,8 @@ import Topic from "discourse/models/topic";
import User from "discourse/models/user"; import User from "discourse/models/user";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
const UserBadge = EmberObject.extend({ export default class UserBadge extends EmberObject {
@discourseComputed static createFromJson(json) {
postUrl() {
if (this.topic_title) {
return "/t/-/" + this.topic_id + "/" + this.post_number;
}
}, // avoid the extra bindings for now
revoke() {
return ajax("/user_badges/" + this.id, {
type: "DELETE",
});
},
favorite() {
this.toggleProperty("is_favorite");
return ajax(`/user_badges/${this.id}/toggle_favorite`, {
type: "PUT",
}).catch((e) => {
// something went wrong, switch the UI back:
this.toggleProperty("is_favorite");
popupAjaxError(e);
});
},
});
UserBadge.reopenClass({
createFromJson(json) {
// Create User objects. // Create User objects.
if (json.users === undefined) { if (json.users === undefined) {
json.users = []; json.users = [];
@ -105,7 +79,7 @@ UserBadge.reopenClass({
} }
return userBadges; return userBadges;
} }
}, }
/** /**
Find all badges for a given username. Find all badges for a given username.
@ -115,7 +89,7 @@ UserBadge.reopenClass({
@param {Object} options @param {Object} options
@returns {Promise} a promise that resolves to an array of `UserBadge`. @returns {Promise} a promise that resolves to an array of `UserBadge`.
**/ **/
findByUsername(username, options) { static findByUsername(username, options) {
if (!username) { if (!username) {
return Promise.resolve([]); return Promise.resolve([]);
} }
@ -126,7 +100,7 @@ UserBadge.reopenClass({
return ajax(url).then(function (json) { return ajax(url).then(function (json) {
return UserBadge.createFromJson(json); return UserBadge.createFromJson(json);
}); });
}, }
/** /**
Find all badge grants for a given badge ID. Find all badge grants for a given badge ID.
@ -135,7 +109,7 @@ UserBadge.reopenClass({
@param {String} badgeId @param {String} badgeId
@returns {Promise} a promise that resolves to an array of `UserBadge`. @returns {Promise} a promise that resolves to an array of `UserBadge`.
**/ **/
findByBadgeId(badgeId, options) { static findByBadgeId(badgeId, options) {
if (!options) { if (!options) {
options = {}; options = {};
} }
@ -146,7 +120,7 @@ UserBadge.reopenClass({
}).then(function (json) { }).then(function (json) {
return UserBadge.createFromJson(json); return UserBadge.createFromJson(json);
}); });
}, }
/** /**
Grant the badge having id `badgeId` to the user identified by `username`. Grant the badge having id `badgeId` to the user identified by `username`.
@ -156,7 +130,7 @@ UserBadge.reopenClass({
@param {String} username username of the user to be granted the badge. @param {String} username username of the user to be granted the badge.
@returns {Promise} a promise that resolves to an instance of `UserBadge`. @returns {Promise} a promise that resolves to an instance of `UserBadge`.
**/ **/
grant(badgeId, username, reason) { static grant(badgeId, username, reason) {
return ajax("/user_badges", { return ajax("/user_badges", {
type: "POST", type: "POST",
data: { data: {
@ -167,7 +141,29 @@ UserBadge.reopenClass({
}).then(function (json) { }).then(function (json) {
return UserBadge.createFromJson(json); return UserBadge.createFromJson(json);
}); });
}, }
});
export default UserBadge; @discourseComputed
postUrl() {
if (this.topic_title) {
return "/t/-/" + this.topic_id + "/" + this.post_number;
}
} // avoid the extra bindings for now
revoke() {
return ajax("/user_badges/" + this.id, {
type: "DELETE",
});
}
favorite() {
this.toggleProperty("is_favorite");
return ajax(`/user_badges/${this.id}/toggle_favorite`, {
type: "PUT",
}).catch((e) => {
// something went wrong, switch the UI back:
this.toggleProperty("is_favorite");
popupAjaxError(e);
});
}
}

View File

@ -9,16 +9,16 @@ 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";
export default RestModel.extend({ export default class UserDraft extends RestModel {
@discourseComputed("draft_username") @discourseComputed("draft_username")
editableDraft(draftUsername) { editableDraft(draftUsername) {
return draftUsername === User.currentProp("username"); return draftUsername === User.currentProp("username");
}, }
@discourseComputed("username_lower") @discourseComputed("username_lower")
userUrl(usernameLower) { userUrl(usernameLower) {
return userPath(usernameLower); return userPath(usernameLower);
}, }
@discourseComputed("topic_id") @discourseComputed("topic_id")
postUrl(topicId) { postUrl(topicId) {
@ -27,7 +27,7 @@ export default RestModel.extend({
} }
return postUrl(this.slug, this.topic_id, this.post_number); return postUrl(this.slug, this.topic_id, this.post_number);
}, }
@discourseComputed("draft_key") @discourseComputed("draft_key")
draftType(draftKey) { draftType(draftKey) {
@ -39,5 +39,5 @@ export default RestModel.extend({
default: default:
return false; return false;
} }
}, }
}); }

View File

@ -10,17 +10,16 @@ import RestModel from "discourse/models/rest";
import UserDraft from "discourse/models/user-draft"; import UserDraft from "discourse/models/user-draft";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
export default RestModel.extend({ export default class UserDraftsStream extends RestModel {
limit: 30, limit = 30;
loading = false;
loading: false, hasMore = false;
hasMore: false, content = null;
content: null,
init() { init() {
this._super(...arguments); super.init(...arguments);
this.reset(); this.reset();
}, }
reset() { reset() {
this.setProperties({ this.setProperties({
@ -28,19 +27,19 @@ export default RestModel.extend({
hasMore: true, hasMore: true,
content: [], content: [],
}); });
}, }
@discourseComputed("content.length", "loading") @discourseComputed("content.length", "loading")
noContent(contentLength, loading) { noContent(contentLength, loading) {
return contentLength === 0 && !loading; return contentLength === 0 && !loading;
}, }
remove(draft) { remove(draft) {
this.set( this.set(
"content", "content",
this.content.filter((item) => item.draft_key !== draft.draft_key) this.content.filter((item) => item.draft_key !== draft.draft_key)
); );
}, }
findItems(site) { findItems(site) {
if (site) { if (site) {
@ -92,5 +91,5 @@ export default RestModel.extend({
.finally(() => { .finally(() => {
this.set("loading", false); this.set("loading", false);
}); });
}, }
}); }