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

View File

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

View File

@ -2,16 +2,12 @@ import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
const AssociatedGroup = EmberObject.extend();
AssociatedGroup.reopenClass({
list() {
export default class AssociatedGroup extends EmberObject {
static list() {
return ajax("/associated_groups")
.then((result) => {
return result.associated_groups.map((ag) => AssociatedGroup.create(ag));
})
.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 I18n from "discourse-i18n";
export default RestModel.extend({
export default class BadgeGrouping extends RestModel {
@discourseComputed("name")
i18nNameKey() {
return this.name.toLowerCase().replace(/\s/g, "_");
},
}
@discourseComputed("name")
displayName() {
const i18nKey = `badges.badge_grouping.${this.i18nNameKey}.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 discourseComputed from "discourse-common/utils/decorators";
const Badge = RestModel.extend({
newBadge: none("id"),
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) {
export default class Badge extends RestModel {
static createFromJson(json) {
// Create BadgeType objects.
const badgeTypes = {};
if ("badge_types" in json) {
@ -103,9 +48,9 @@ Badge.reopenClass({
} else {
return badges;
}
},
}
findAll(opts) {
static findAll(opts) {
let listable = "";
if (opts && opts.onlyListable) {
listable = "?only_listable=true";
@ -114,13 +59,65 @@ Badge.reopenClass({
return ajax(`/badges.json${listable}`, { data: opts }).then((badgesJson) =>
Badge.createFromJson(badgesJson)
);
},
}
findById(id) {
static findById(id) {
return ajax(`/badges/${id}`).then((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 WITH_REMINDER_ICON = "discourse-bookmark-clock";
const Bookmark = RestModel.extend({
newBookmark: none("id"),
export default class Bookmark extends RestModel {
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
get url() {
return getURL(`/bookmarks/${this.id}`);
},
}
destroy() {
if (this.newBookmark) {
@ -41,14 +61,14 @@ const Bookmark = RestModel.extend({
return ajax(this.url, {
type: "DELETE",
});
},
}
attachedTo() {
return {
target: this.bookmarkable_type.toLowerCase(),
targetId: this.bookmarkable_id,
};
},
}
togglePin() {
if (this.newBookmark) {
@ -58,16 +78,16 @@ const Bookmark = RestModel.extend({
return ajax(this.url + "/toggle_pin", {
type: "PUT",
});
},
}
pinAction() {
return this.pinned ? "unpin" : "pin";
},
}
@discourseComputed("highest_post_number", "url")
lastPostUrl(highestPostNumber) {
return this.urlForPostNumber(highestPostNumber);
},
}
// Helper to build a Url with a post number
urlForPostNumber(postNumber) {
@ -76,7 +96,7 @@ const Bookmark = RestModel.extend({
url += `/${postNumber}`;
}
return url;
},
}
// returns createdAt if there's no bumped date
@discourseComputed("bumped_at", "createdAt")
@ -86,7 +106,7 @@ const Bookmark = RestModel.extend({
} else {
return createdAt;
}
},
}
@discourseComputed("bumpedAt", "createdAt")
bumpedAtTitle(bumpedAt, createdAt) {
@ -101,7 +121,7 @@ const Bookmark = RestModel.extend({
})}\n${I18n.t("topic.bumped_at", { date: longDate(bumpedAt) })}`
: I18n.t("topic.created_at", { date: longDate(createdAt) });
}
},
}
@discourseComputed("name", "reminder_at")
reminderTitle(name, reminderAt) {
@ -118,12 +138,12 @@ const Bookmark = RestModel.extend({
return I18n.t("bookmarks.created_generic", {
name: name || "",
});
},
}
@discourseComputed("created_at")
createdAt(created_at) {
return new Date(created_at);
},
}
@discourseComputed("tags")
visibleListTags(tags) {
@ -141,12 +161,12 @@ const Bookmark = RestModel.extend({
});
return newTags;
},
}
@computed("category_id")
get category() {
return Category.findById(this.category_id);
},
}
@discourseComputed("reminder_at", "currentUser")
formattedReminder(bookmarkReminderAt, currentUser) {
@ -156,12 +176,12 @@ const Bookmark = RestModel.extend({
currentUser?.user_option?.timezone || moment.tz.guess()
)
);
},
}
@discourseComputed("reminder_at")
reminderAtExpired(bookmarkReminderAt) {
return moment(bookmarkReminderAt) < moment();
},
}
@discourseComputed()
topicForList() {
@ -178,34 +198,10 @@ const Bookmark = RestModel.extend({
last_read_post_number: this.last_read_post_number,
highest_post_number: this.highest_post_number,
});
},
}
@discourseComputed("bookmarkable_type")
bookmarkableTopicAlike(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 I18n from "discourse-i18n";
const CategoryList = ArrayProxy.extend({
init() {
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) {
export default class CategoryList extends ArrayProxy {
static categoriesFrom(store, result, parentCategory = null) {
// Find the period that is most relevant
const statPeriod =
["week", "month"].find(
@ -67,9 +37,9 @@ CategoryList.reopenClass({
}
});
return categories;
},
}
_buildCategoryResult(c, statPeriod) {
static _buildCategoryResult(c, statPeriod) {
if (c.parent_category_id) {
c.parentCategory = Category.findById(c.parent_category_id);
}
@ -126,9 +96,9 @@ CategoryList.reopenClass({
const record = Site.current().updateCategory(c);
record.setupGroupsAndPermissions();
return record;
},
}
listForParent(store, category) {
static listForParent(store, category) {
return ajax(
`/categories.json?parent_category_id=${category.get("id")}`
).then((result) =>
@ -138,9 +108,9 @@ CategoryList.reopenClass({
parentCategory: category,
})
);
},
}
list(store) {
static list(store) {
return PreloadStore.getAndRemove("categories_list", () =>
ajax("/categories.json")
).then((result) =>
@ -151,7 +121,33 @@ CategoryList.reopenClass({
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 CATEGORY_ASYNC_SEARCH_CACHE = {};
const Category = RestModel.extend({
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({
export default class Category extends RestModel {
// Sort subcategories directly under parents
sortCategories(categories) {
static sortCategories(categories) {
const children = new Map();
categories.forEach((category) => {
@ -392,20 +31,20 @@ Category.reopenClass({
values.flatMap((c) => [c, reduce(children.get(c.id) || [])]).flat();
return reduce(children.get(-1) || []);
},
}
isUncategorized(categoryId) {
static isUncategorized(categoryId) {
return categoryId === Site.currentProp("uncategorized_category_id");
},
}
slugEncoded() {
static slugEncoded() {
let siteSettings = getOwnerWithFallback(this).lookup(
"service:site-settings"
);
return siteSettings.slug_generation_method === "encoded";
},
}
findUncategorized() {
static findUncategorized() {
_uncategorized =
_uncategorized ||
Category.list().findBy(
@ -413,9 +52,9 @@ Category.reopenClass({
Site.currentProp("uncategorized_category_id")
);
return _uncategorized;
},
}
slugFor(category, separator = "/", depth = 3) {
static slugFor(category, separator = "/", depth = 3) {
if (!category) {
return "";
}
@ -434,21 +73,21 @@ Category.reopenClass({
return !slug || slug.trim().length === 0
? `${result}${id}-category`
: result + slug;
},
}
list() {
static list() {
return Site.currentProp("categoriesList");
},
}
listByActivity() {
static listByActivity() {
return Site.currentProp("sortedCategories");
},
}
_idMap() {
static _idMap() {
return Site.currentProp("categoriesById");
},
}
findSingleBySlug(slug) {
static findSingleBySlug(slug) {
if (!this.slugEncoded()) {
return Category.list().find((c) => Category.slugFor(c) === slug);
} else {
@ -456,16 +95,16 @@ Category.reopenClass({
(c) => Category.slugFor(c) === encodeURI(slug)
);
}
},
}
findById(id) {
static findById(id) {
if (!id) {
return;
}
return Category._idMap()[id];
},
}
findByIds(ids = []) {
static findByIds(ids = []) {
const categories = [];
ids.forEach((id) => {
const found = Category.findById(id);
@ -474,14 +113,14 @@ Category.reopenClass({
}
});
return categories;
},
}
hasAsyncFoundAll(ids) {
static hasAsyncFoundAll(ids) {
const loadedCategoryIds = Site.current().loadedCategoryIds || new Set();
return ids.every((id) => loadedCategoryIds.has(id));
},
}
async asyncFindByIds(ids = []) {
static async asyncFindByIds(ids = []) {
ids = ids.map((x) => parseInt(x, 10));
if (!Site.current().lazy_load_categories) {
@ -508,9 +147,9 @@ Category.reopenClass({
Site.current().set("loadedCategoryIds", loadedCategoryIds);
return categories;
},
}
findBySlugAndParent(slug, parentCategory) {
static findBySlugAndParent(slug, parentCategory) {
if (this.slugEncoded()) {
slug = encodeURI(slug);
}
@ -520,9 +159,9 @@ Category.reopenClass({
(category.parentCategory || null) === parentCategory
);
});
},
}
findBySlugPath(slugPath) {
static findBySlugPath(slugPath) {
let category = null;
for (const slug of slugPath) {
@ -534,9 +173,9 @@ Category.reopenClass({
}
return category;
},
}
async asyncFindBySlugPathWithID(slugPathWithID) {
static async asyncFindBySlugPathWithID(slugPathWithID) {
const result = await ajax("/categories/find", {
data: { slug_path_with_id: slugPathWithID },
});
@ -546,9 +185,9 @@ Category.reopenClass({
);
return categories[categories.length - 1];
},
}
findBySlugPathWithID(slugPathWithID) {
static findBySlugPathWithID(slugPathWithID) {
let parts = slugPathWithID.split("/").filter(Boolean);
// slugs found by star/glob pathing in ember do not automatically url decode - ensure that these are decoded
if (this.slugEncoded()) {
@ -575,9 +214,9 @@ Category.reopenClass({
}
return category;
},
}
findBySlug(slug, parentSlug) {
static findBySlug(slug, parentSlug) {
const categories = Category.list();
let category;
@ -615,34 +254,34 @@ Category.reopenClass({
}
return category;
},
}
fetchVisibleGroups(id) {
static fetchVisibleGroups(id) {
return ajax(`/c/${id}/visible_groups.json`);
},
}
reloadById(id) {
static reloadById(id) {
return ajax(`/c/${id}/show.json`);
},
}
reloadBySlugPath(slugPath) {
static reloadBySlugPath(slugPath) {
return ajax(`/c/${slugPath}/find_by_slug.json`);
},
}
reloadCategoryWithPermissions(params, store, site) {
static reloadCategoryWithPermissions(params, store, site) {
return this.reloadBySlugPath(params.slug).then((result) =>
this._includePermissions(result.category, store, site)
);
},
}
_includePermissions(category, store, site) {
static _includePermissions(category, store, site) {
const record = store.createRecord("category", category);
record.setupGroupsAndPermissions();
site.updateCategory(record);
return record;
},
}
search(term, opts) {
static search(term, opts) {
let limit = 5;
let parentCategoryId;
@ -713,9 +352,9 @@ Category.reopenClass({
}
return data.sortBy("read_restricted");
},
}
async asyncSearch(term, opts) {
static async asyncSearch(term, opts) {
opts ||= {};
const data = {
@ -735,7 +374,364 @@ Category.reopenClass({
return result["categories"].map((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 { ajax } from "discourse/lib/ajax";
const Draft = EmberObject.extend();
Draft.reopenClass({
clear(key, sequence) {
export default class Draft extends EmberObject {
static clear(key, sequence) {
return ajax(`/drafts/${key}.json`, {
type: "DELETE",
data: { draft_key: key, sequence },
});
},
}
get(key) {
static get(key) {
return ajax(`/drafts/${key}.json`);
},
}
getLocal(key, current) {
static getLocal(key, current) {
// TODO: implement this
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);
return ajax("/drafts.json", {
type: "POST",
@ -33,7 +31,5 @@ Draft.reopenClass({
},
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 I18n from "discourse-i18n";
export default RestModel.extend({
export default class GroupHistory extends RestModel {
@discourseComputed("action")
actionTitle(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 discourseComputed, { observes } from "discourse-common/utils/decorators";
const Group = RestModel.extend({
user_count: 0,
limit: null,
offset: null,
export default class Group extends RestModel {
static findAll(opts) {
return ajax("/groups/search.json", { data: opts }).then((groups) =>
groups.map((g) => Group.create(g))
);
}
request_count: 0,
requestersLimit: null,
requestersOffset: null,
static loadMembers(name, opts) {
return ajax(`/groups/${name}/members.json`, { data: opts });
}
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() {
this._super(...arguments);
super.init(...arguments);
this.setProperties({ members: [], requesters: [] });
},
}
@discourseComputed("automatic_membership_email_domains")
emailDomains(value) {
return isEmpty(value) ? "" : value;
},
}
@discourseComputed("associated_group_ids")
associatedGroupIds(value) {
return isEmpty(value) ? [] : value;
},
}
@discourseComputed("automatic")
type(automatic) {
return automatic ? "automatic" : "custom";
},
}
async reloadMembers(params, refresh) {
if (isEmpty(this.name) || !this.can_see_members) {
@ -73,7 +95,7 @@ const Group = RestModel.extend({
limit: response.meta.limit,
offset: response.meta.offset,
});
},
}
findRequesters(params, refresh) {
if (isEmpty(this.name) || !this.can_see_members) {
@ -103,7 +125,7 @@ const Group = RestModel.extend({
requestersOffset: result.meta.offset,
});
});
},
}
async removeOwner(member) {
await ajax(`/admin/groups/${this.id}/owners.json`, {
@ -111,7 +133,7 @@ const Group = RestModel.extend({
data: { user_id: member.id },
});
await this.reloadMembers({}, true);
},
}
async removeMember(member, params) {
await ajax(`/groups/${this.id}/members.json`, {
@ -119,7 +141,7 @@ const Group = RestModel.extend({
data: { user_id: member.id },
});
await this.reloadMembers(params, true);
},
}
async leave() {
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);
await this.reloadMembers({}, true);
},
}
async addMembers(usernames, filter, notifyUsers, emails = []) {
const response = await ajax(`/groups/${this.id}/members.json`, {
@ -139,14 +161,14 @@ const Group = RestModel.extend({
} else {
await this.reloadMembers();
}
},
}
async join() {
await ajax(`/groups/${this.id}/join.json`, {
type: "PUT",
});
await this.reloadMembers({}, true);
},
}
async addOwners(usernames, filter, notifyUsers) {
const response = await ajax(`/groups/${this.id}/owners.json`, {
@ -159,44 +181,42 @@ const Group = RestModel.extend({
} else {
await this.reloadMembers({}, true);
}
},
}
_filterMembers(usernames) {
return this.reloadMembers({ filter: usernames.join(",") });
},
}
@discourseComputed("display_name", "name")
displayName(groupDisplayName, name) {
return groupDisplayName || name;
},
}
@discourseComputed("flair_bg_color")
flairBackgroundHexColor(flairBgColor) {
return flairBgColor
? flairBgColor.replace(new RegExp("[^0-9a-fA-F]", "g"), "")
: null;
},
}
@discourseComputed("flair_color")
flairHexColor(flairColor) {
return flairColor
? flairColor.replace(new RegExp("[^0-9a-fA-F]", "g"), "")
: null;
},
canEveryoneMention: equal("mentionable_level", 99),
}
@discourseComputed("visibility_level")
isPrivate(visibilityLevel) {
return visibilityLevel > 1;
},
}
@observes("isPrivate", "canEveryoneMention")
_updateAllowMembershipRequests() {
if (this.isPrivate || !this.canEveryoneMention) {
this.set("allow_membership_requests", false);
}
},
}
@dependentKeyCompat
get watchingCategories() {
@ -210,14 +230,14 @@ const Group = RestModel.extend({
}
return Category.findByIds(this.get("watching_category_ids"));
},
}
set watchingCategories(categories) {
this.set(
"watching_category_ids",
categories.map((c) => c.id)
);
},
}
@dependentKeyCompat
get trackingCategories() {
@ -231,14 +251,14 @@ const Group = RestModel.extend({
}
return Category.findByIds(this.get("tracking_category_ids"));
},
}
set trackingCategories(categories) {
this.set(
"tracking_category_ids",
categories.map((c) => c.id)
);
},
}
@dependentKeyCompat
get watchingFirstPostCategories() {
@ -252,14 +272,14 @@ const Group = RestModel.extend({
}
return Category.findByIds(this.get("watching_first_post_category_ids"));
},
}
set watchingFirstPostCategories(categories) {
this.set(
"watching_first_post_category_ids",
categories.map((c) => c.id)
);
},
}
@dependentKeyCompat
get regularCategories() {
@ -273,14 +293,14 @@ const Group = RestModel.extend({
}
return Category.findByIds(this.get("regular_category_ids"));
},
}
set regularCategories(categories) {
this.set(
"regular_category_ids",
categories.map((c) => c.id)
);
},
}
@dependentKeyCompat
get mutedCategories() {
@ -294,14 +314,14 @@ const Group = RestModel.extend({
}
return Category.findByIds(this.get("muted_category_ids"));
},
}
set mutedCategories(categories) {
this.set(
"muted_category_ids",
categories.map((c) => c.id)
);
},
}
asJSON() {
const attrs = {
@ -382,7 +402,7 @@ const Group = RestModel.extend({
}
return attrs;
},
}
async create() {
const response = await ajax("/admin/groups", {
@ -397,21 +417,21 @@ const Group = RestModel.extend({
});
await this.reloadMembers();
},
}
save(opts = {}) {
return ajax(`/groups/${this.id}`, {
type: "PUT",
data: Object.assign({ group: this.asJSON() }, opts),
});
},
}
destroy() {
if (!this.id) {
return;
}
return ajax(`/admin/groups/${this.id}`, { type: "DELETE" });
},
}
findLogs(offset, filters) {
return ajax(`/groups/${this.name}/logs.json`, {
@ -422,7 +442,7 @@ const Group = RestModel.extend({
all_loaded: results["all_loaded"],
});
});
},
}
findPosts(opts) {
opts = opts || {};
@ -445,7 +465,7 @@ const Group = RestModel.extend({
return EmberObject.create(p);
});
});
},
}
setNotification(notification_level, userId) {
this.set("group_user.notification_level", notification_level);
@ -453,38 +473,12 @@ const Group = RestModel.extend({
data: { notification_level, user_id: userId },
type: "POST",
});
},
}
requestMembership(reason) {
return ajax(`/groups/${this.name}/request_membership.json`, {
type: "POST",
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 discourseComputed from "discourse-common/utils/decorators";
const Invite = EmberObject.extend({
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;
},
topicId: alias("topics.firstObject.id"),
topicTitle: alias("topics.firstObject.title"),
});
Invite.reopenClass({
create() {
const result = this._super.apply(this, arguments);
export default class Invite extends EmberObject {
static create() {
const result = super.create(...arguments);
if (result.user) {
result.user = User.create(result.user);
}
return result;
},
}
findInvitedBy(user, filter, search, offset) {
static findInvitedBy(user, filter, search, offset) {
if (!user) {
Promise.resolve();
}
@ -87,15 +38,60 @@ Invite.reopenClass({
result.invites = result.invites.map((i) => Invite.create(i));
return EmberObject.create(result);
});
},
}
reinviteAll() {
static reinviteAll() {
return ajax("/invites/reinvite-all", { type: "POST" });
},
}
destroyAllExpired() {
static destroyAllExpired() {
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 { ajax } from "discourse/lib/ajax";
const LivePostCounts = EmberObject.extend({});
LivePostCounts.reopenClass({
find() {
export default class LivePostCounts extends EmberObject {
static find() {
return ajax("/about/live_post_counts.json").then((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 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
title() {
return this.title_override || I18n.t(`login.${this.name}.title`);
},
}
@discourseComputed
screenReaderTitle() {
@ -19,12 +39,12 @@ const LoginMethod = EmberObject.extend({
this.title_override ||
I18n.t(`login.${this.name}.sr_title`, { defaultValue: this.title })
);
},
}
@discourseComputed
prettyName() {
return this.pretty_name_override || I18n.t(`login.${this.name}.name`);
},
}
doLogin({ reconnect = false, signup = false, params = {} } = {}) {
if (this.customLogin) {
@ -56,30 +76,8 @@ const LoginMethod = EmberObject.extend({
}
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;
@ -101,5 +99,3 @@ export function findAll() {
export function clearAuthMethods() {
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 Category from "./category";
const PendingPost = RestModel.extend({
expandedExcerpt: null,
postUrl: reads("topic_url"),
truncated: false,
export default class PendingPost extends RestModel {
expandedExcerpt = null;
@reads("topic_url") postUrl;
truncated = false;
init() {
this._super(...arguments);
super.init(...arguments);
cook(this.raw_text).then((cooked) => {
this.set("expandedExcerpt", cooked);
});
},
}
@discourseComputed("username")
userUrl(username) {
return userPath(username.toLowerCase());
},
}
@discourseComputed("category_id")
category() {
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]);
}
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")
description(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 default RestModel.extend({
notCustomFlag: not("is_custom_flag"),
});
export default class PostActionType extends RestModel {
@not("is_custom_flag") notCustomFlag;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,13 @@ export const REJECTED = 2;
export const IGNORED = 3;
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")
resolvedType(type, topic) {
// Display "Queued Topic" if the post will create a topic
@ -21,26 +27,26 @@ const Reviewable = RestModel.extend({
}
return type;
},
}
@discourseComputed("resolvedType")
humanType(resolvedType) {
return I18n.t(`review.types.${underscore(resolvedType)}.title`, {
defaultValue: "",
});
},
}
@discourseComputed("humanType")
humanTypeCssClass(humanType) {
return "-" + dasherize(humanType);
},
}
@discourseComputed("resolvedType")
humanNoun(resolvedType) {
return I18n.t(`review.types.${underscore(resolvedType)}.noun`, {
defaultValue: "reviewable",
});
},
}
@discourseComputed("humanNoun")
flaggedReviewableContextQuestion(humanNoun) {
@ -66,12 +72,12 @@ const Reviewable = RestModel.extend({
reviewable_human_score_types: listOfQuestions,
reviewable_type: humanNoun,
});
},
}
@discourseComputed("category_id")
category() {
return Category.findById(this.category_id);
},
}
update(updates) {
// If no changes, do nothing
@ -92,15 +98,5 @@ const Reviewable = RestModel.extend({
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
// data here you might want later. It is not stored or serialized anywhere.
const Session = RestModel.extend({
hasFocus: null,
export default class Session extends RestModel.extend().reopenClass(Singleton) {
hasFocus = null;
init() {
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 discourseComputed from "discourse-common/utils/decorators";
const Site = RestModel.extend({
isReadOnly: alias("is_readonly"),
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() {
export default class Site extends RestModel.extend().reopenClass(Singleton) {
static createCurrent() {
const store = getOwnerWithFallback(this).lookup("service:store");
const siteAttributes = PreloadStore.get("site");
siteAttributes["isReadOnly"] = PreloadStore.get("isReadOnly");
siteAttributes["isStaffWritesOnly"] = PreloadStore.get("isStaffWritesOnly");
return store.createRecord("site", siteAttributes);
},
}
create() {
const result = this._super.apply(this, arguments);
static create() {
const result = super.create.apply(this, arguments);
const store = result.store;
if (result.categories) {
@ -233,8 +101,136 @@ Site.reopenClass(Singleton, {
}
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") {
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 { ajax } from "discourse/lib/ajax";
const StaticPage = EmberObject.extend();
StaticPage.reopenClass({
find(path) {
export default class StaticPage extends EmberObject {
static find(path) {
return new Promise((resolve) => {
// Models shouldn't really be doing Ajax request, but this is a huge speed boost if we
// 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 discourseComputed from "discourse-common/utils/decorators";
export default RestModel.extend({
export default class TagGroup extends RestModel {
@discourseComputed("permissions")
permissionName(permissions) {
if (!permissions) {
@ -16,5 +16,5 @@ export default RestModel.extend({
} else {
return "private";
}
},
});
}
}

View File

@ -2,16 +2,16 @@ import { readOnly } from "@ember/object/computed";
import RestModel from "discourse/models/rest";
import discourseComputed from "discourse-common/utils/decorators";
export default RestModel.extend({
pmOnly: readOnly("pm_only"),
export default class Tag extends RestModel {
@readOnly("pm_only") pmOnly;
@discourseComputed("count", "pm_count")
totalCount(count, pmCount) {
return pmCount ? count + pmCount : count;
},
}
@discourseComputed("id")
searchContext(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.
**/
const TopicDetails = RestModel.extend({
store: service(),
export default class TopicDetails extends RestModel {
@service store;
loaded: false,
loaded = false;
updateFromJson(details) {
const topic = this.topic;
@ -31,7 +31,7 @@ const TopicDetails = RestModel.extend({
this.setProperties(details);
this.set("loaded", true);
},
}
updateNotifications(level) {
return ajax(`/t/${this.get("topic.id")}/notifications`, {
@ -43,7 +43,7 @@ const TopicDetails = RestModel.extend({
notifications_reason_id: null,
});
});
},
}
removeAllowedGroup(group) {
const groups = this.allowed_groups;
@ -55,7 +55,7 @@ const TopicDetails = RestModel.extend({
}).then(() => {
groups.removeObject(groups.findBy("name", name));
});
},
}
removeAllowedUser(user) {
const users = this.allowed_users;
@ -67,7 +67,5 @@ const TopicDetails = RestModel.extend({
}).then(() => {
users.removeObject(users.findBy("username", username));
});
},
});
export default TopicDetails;
}
}

View File

@ -40,9 +40,88 @@ function displayCategoryInList(site, category) {
return true;
}
const TopicList = RestModel.extend({
session: service(),
canLoadMore: notEmpty("more_topics_url"),
export default class TopicList extends RestModel {
static 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);
});
}
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) {
const topicIds = new Set();
@ -53,7 +132,7 @@ const TopicList = RestModel.extend({
callback(topic);
}
});
},
}
updateSortParams(order, ascending) {
let params = { ...(this.params || {}) };
@ -67,7 +146,7 @@ const TopicList = RestModel.extend({
}
this.set("params", params);
},
}
updateNewListSubsetParam(subset) {
let params = { ...(this.params || {}) };
@ -79,7 +158,7 @@ const TopicList = RestModel.extend({
}
this.set("params", params);
},
}
loadMore() {
if (this.loadingMore) {
@ -128,7 +207,7 @@ const TopicList = RestModel.extend({
// Return a promise indicating no more results
return Promise.resolve();
}
},
}
// loads topics with these ids "before" the current topics
loadBefore(topic_ids, storeInSession) {
@ -152,87 +231,5 @@ const TopicList = RestModel.extend({
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 RestModel from "discourse/models/rest";
const TopicTimer = RestModel.extend({});
TopicTimer.reopenClass({
update(
export default class TopicTimer extends RestModel {
static update(
topicId,
time,
basedOnLastPost,
@ -32,7 +30,5 @@ TopicTimer.reopenClass({
type: "POST",
data,
});
},
});
export default TopicTimer;
}
}

View File

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

View File

@ -1,10 +1,10 @@
import EmberObject from "@ember/object";
export default EmberObject.extend({
export default class UserActionGroup extends EmberObject {
push(item) {
if (!this.items) {
this.items = [];
}
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 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")
isPM(actionType) {
return (
actionType === UserAction.TYPES.messages_sent ||
actionType === UserAction.TYPES.messages_received
);
},
description: i18n("action_type", "user_action_groups.%@"),
}
@discourseComputed("action_type")
isResponse(actionType) {
@ -20,5 +20,5 @@ export default RestModel.extend({
actionType === UserAction.TYPES.replies ||
actionType === UserAction.TYPES.quotes
);
},
});
}
}

View File

@ -26,153 +26,28 @@ Object.keys(UserActionTypes).forEach(
(k) => (InvertedActionTypes[k] = UserActionTypes[k])
);
const UserAction = RestModel.extend({
@discourseComputed("category_id")
category() {
return Category.findById(this.category_id);
},
export default class UserAction extends RestModel {
static TYPES = UserActionTypes;
@discourseComputed("action_type")
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";
}
}
static TYPES_INVERTED = InvertedActionTypes;
if (this.topicType) {
return this.sameUser ? "you_posted_topic" : "user_posted_topic";
}
static TO_COLLAPSE = [
UserActionTypes.likes_given,
UserActionTypes.likes_received,
UserActionTypes.edits,
UserActionTypes.bookmarks,
];
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";
}
}
static TO_SHOW = [
UserActionTypes.likes_given,
UserActionTypes.likes_received,
UserActionTypes.edits,
UserActionTypes.bookmarks,
UserActionTypes.messages_sent,
UserActionTypes.messages_received,
];
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");
},
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) {
static collapseStream(stream) {
const uniq = {};
const collapsed = [];
let pos = 0;
@ -204,26 +79,147 @@ UserAction.reopenClass({
}
});
return collapsed;
},
}
TYPES: UserActionTypes,
TYPES_INVERTED: InvertedActionTypes,
@or("name", "username") presentName;
@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: [
UserActionTypes.likes_given,
UserActionTypes.likes_received,
UserActionTypes.edits,
UserActionTypes.bookmarks,
],
@discourseComputed("category_id")
category() {
return Category.findById(this.category_id);
}
TO_SHOW: [
UserActionTypes.likes_given,
UserActionTypes.likes_received,
UserActionTypes.edits,
UserActionTypes.bookmarks,
UserActionTypes.messages_sent,
UserActionTypes.messages_received,
],
});
@discourseComputed("action_type")
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";
}
}
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 discourseComputed from "discourse-common/utils/decorators";
const UserBadge = EmberObject.extend({
@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);
});
},
});
UserBadge.reopenClass({
createFromJson(json) {
export default class UserBadge extends EmberObject {
static createFromJson(json) {
// Create User objects.
if (json.users === undefined) {
json.users = [];
@ -105,7 +79,7 @@ UserBadge.reopenClass({
}
return userBadges;
}
},
}
/**
Find all badges for a given username.
@ -115,7 +89,7 @@ UserBadge.reopenClass({
@param {Object} options
@returns {Promise} a promise that resolves to an array of `UserBadge`.
**/
findByUsername(username, options) {
static findByUsername(username, options) {
if (!username) {
return Promise.resolve([]);
}
@ -126,7 +100,7 @@ UserBadge.reopenClass({
return ajax(url).then(function (json) {
return UserBadge.createFromJson(json);
});
},
}
/**
Find all badge grants for a given badge ID.
@ -135,7 +109,7 @@ UserBadge.reopenClass({
@param {String} badgeId
@returns {Promise} a promise that resolves to an array of `UserBadge`.
**/
findByBadgeId(badgeId, options) {
static findByBadgeId(badgeId, options) {
if (!options) {
options = {};
}
@ -146,7 +120,7 @@ UserBadge.reopenClass({
}).then(function (json) {
return UserBadge.createFromJson(json);
});
},
}
/**
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.
@returns {Promise} a promise that resolves to an instance of `UserBadge`.
**/
grant(badgeId, username, reason) {
static grant(badgeId, username, reason) {
return ajax("/user_badges", {
type: "POST",
data: {
@ -167,7 +141,29 @@ UserBadge.reopenClass({
}).then(function (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 I18n from "discourse-i18n";
export default RestModel.extend({
export default class UserDraft extends RestModel {
@discourseComputed("draft_username")
editableDraft(draftUsername) {
return draftUsername === User.currentProp("username");
},
}
@discourseComputed("username_lower")
userUrl(usernameLower) {
return userPath(usernameLower);
},
}
@discourseComputed("topic_id")
postUrl(topicId) {
@ -27,7 +27,7 @@ export default RestModel.extend({
}
return postUrl(this.slug, this.topic_id, this.post_number);
},
}
@discourseComputed("draft_key")
draftType(draftKey) {
@ -39,5 +39,5 @@ export default RestModel.extend({
default:
return false;
}
},
});
}
}

View File

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