mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 21:10:17 +08:00
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:
parent
234795c70e
commit
6c597b648b
|
@ -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 };
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 });
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}`);
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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("—");
|
||||
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("—");
|
||||
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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user