mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06: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 { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import RestModel from "discourse/models/rest";
|
import RestModel from "discourse/models/rest";
|
||||||
|
|
||||||
export default RestModel.extend({
|
export default class ActionSummary extends RestModel {
|
||||||
canToggle: or("can_undo", "can_act"),
|
@or("can_undo", "can_act") canToggle;
|
||||||
|
|
||||||
// Remove it
|
// Remove it
|
||||||
removeAction() {
|
removeAction() {
|
||||||
@ -14,11 +14,11 @@ export default RestModel.extend({
|
|||||||
can_act: true,
|
can_act: true,
|
||||||
can_undo: false,
|
can_undo: false,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
togglePromise(post) {
|
togglePromise(post) {
|
||||||
return this.acted ? this.undo(post) : this.act(post);
|
return this.acted ? this.undo(post) : this.act(post);
|
||||||
},
|
}
|
||||||
|
|
||||||
toggle(post) {
|
toggle(post) {
|
||||||
if (!this.acted) {
|
if (!this.acted) {
|
||||||
@ -28,7 +28,7 @@ export default RestModel.extend({
|
|||||||
this.undo(post);
|
this.undo(post);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
// Perform this action
|
// Perform this action
|
||||||
act(post, opts) {
|
act(post, opts) {
|
||||||
@ -76,7 +76,7 @@ export default RestModel.extend({
|
|||||||
popupAjaxError(error);
|
popupAjaxError(error);
|
||||||
this.removeAction(post);
|
this.removeAction(post);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
// Undo this action
|
// Undo this action
|
||||||
undo(post) {
|
undo(post) {
|
||||||
@ -90,5 +90,5 @@ export default RestModel.extend({
|
|||||||
post.updateActionsSummary(result);
|
post.updateActionsSummary(result);
|
||||||
return { acted: false };
|
return { acted: false };
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
@ -2,8 +2,8 @@ import { gt, not } from "@ember/object/computed";
|
|||||||
import { propertyEqual } from "discourse/lib/computed";
|
import { propertyEqual } from "discourse/lib/computed";
|
||||||
import RestModel from "discourse/models/rest";
|
import RestModel from "discourse/models/rest";
|
||||||
|
|
||||||
export default RestModel.extend({
|
export default class Archetype extends RestModel {
|
||||||
hasOptions: gt("options.length", 0),
|
@gt("options.length", 0) hasOptions;
|
||||||
isDefault: propertyEqual("id", "site.default_archetype"),
|
@propertyEqual("id", "site.default_archetype") isDefault;
|
||||||
notDefault: not("isDefault"),
|
@not("isDefault") notDefault;
|
||||||
});
|
}
|
||||||
|
@ -2,16 +2,12 @@ import EmberObject from "@ember/object";
|
|||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
|
||||||
const AssociatedGroup = EmberObject.extend();
|
export default class AssociatedGroup extends EmberObject {
|
||||||
|
static list() {
|
||||||
AssociatedGroup.reopenClass({
|
|
||||||
list() {
|
|
||||||
return ajax("/associated_groups")
|
return ajax("/associated_groups")
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
return result.associated_groups.map((ag) => AssociatedGroup.create(ag));
|
return result.associated_groups.map((ag) => AssociatedGroup.create(ag));
|
||||||
})
|
})
|
||||||
.catch(popupAjaxError);
|
.catch(popupAjaxError);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
export default AssociatedGroup;
|
|
||||||
|
@ -2,15 +2,15 @@ import RestModel from "discourse/models/rest";
|
|||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
export default RestModel.extend({
|
export default class BadgeGrouping extends RestModel {
|
||||||
@discourseComputed("name")
|
@discourseComputed("name")
|
||||||
i18nNameKey() {
|
i18nNameKey() {
|
||||||
return this.name.toLowerCase().replace(/\s/g, "_");
|
return this.name.toLowerCase().replace(/\s/g, "_");
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("name")
|
@discourseComputed("name")
|
||||||
displayName() {
|
displayName() {
|
||||||
const i18nKey = `badges.badge_grouping.${this.i18nNameKey}.name`;
|
const i18nKey = `badges.badge_grouping.${this.i18nNameKey}.name`;
|
||||||
return I18n.t(i18nKey, { defaultValue: this.name });
|
return I18n.t(i18nKey, { defaultValue: this.name });
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
@ -7,63 +7,8 @@ import RestModel from "discourse/models/rest";
|
|||||||
import getURL from "discourse-common/lib/get-url";
|
import getURL from "discourse-common/lib/get-url";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
const Badge = RestModel.extend({
|
export default class Badge extends RestModel {
|
||||||
newBadge: none("id"),
|
static createFromJson(json) {
|
||||||
image: alias("image_url"),
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
url() {
|
|
||||||
return getURL(`/badges/${this.id}/${this.slug}`);
|
|
||||||
},
|
|
||||||
|
|
||||||
updateFromJson(json) {
|
|
||||||
if (json.badge) {
|
|
||||||
Object.keys(json.badge).forEach((key) => this.set(key, json.badge[key]));
|
|
||||||
}
|
|
||||||
if (json.badge_types) {
|
|
||||||
json.badge_types.forEach((badgeType) => {
|
|
||||||
if (badgeType.id === this.badge_type_id) {
|
|
||||||
this.set("badge_type", Object.create(badgeType));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("badge_type.name")
|
|
||||||
badgeTypeClassName(type) {
|
|
||||||
type = type || "";
|
|
||||||
return `badge-type-${type.toLowerCase()}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
save(data) {
|
|
||||||
let url = "/admin/badges",
|
|
||||||
type = "POST";
|
|
||||||
|
|
||||||
if (this.id) {
|
|
||||||
// We are updating an existing badge.
|
|
||||||
url += `/${this.id}`;
|
|
||||||
type = "PUT";
|
|
||||||
}
|
|
||||||
|
|
||||||
return ajax(url, { type, data }).then((json) => {
|
|
||||||
this.updateFromJson(json);
|
|
||||||
return this;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
if (this.newBadge) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
return ajax(`/admin/badges/${this.id}`, {
|
|
||||||
type: "DELETE",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Badge.reopenClass({
|
|
||||||
createFromJson(json) {
|
|
||||||
// Create BadgeType objects.
|
// Create BadgeType objects.
|
||||||
const badgeTypes = {};
|
const badgeTypes = {};
|
||||||
if ("badge_types" in json) {
|
if ("badge_types" in json) {
|
||||||
@ -103,9 +48,9 @@ Badge.reopenClass({
|
|||||||
} else {
|
} else {
|
||||||
return badges;
|
return badges;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
findAll(opts) {
|
static findAll(opts) {
|
||||||
let listable = "";
|
let listable = "";
|
||||||
if (opts && opts.onlyListable) {
|
if (opts && opts.onlyListable) {
|
||||||
listable = "?only_listable=true";
|
listable = "?only_listable=true";
|
||||||
@ -114,13 +59,65 @@ Badge.reopenClass({
|
|||||||
return ajax(`/badges.json${listable}`, { data: opts }).then((badgesJson) =>
|
return ajax(`/badges.json${listable}`, { data: opts }).then((badgesJson) =>
|
||||||
Badge.createFromJson(badgesJson)
|
Badge.createFromJson(badgesJson)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
findById(id) {
|
static findById(id) {
|
||||||
return ajax(`/badges/${id}`).then((badgeJson) =>
|
return ajax(`/badges/${id}`).then((badgeJson) =>
|
||||||
Badge.createFromJson(badgeJson)
|
Badge.createFromJson(badgeJson)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
|
||||||
export default Badge;
|
@none("id") newBadge;
|
||||||
|
|
||||||
|
@alias("image_url") image;
|
||||||
|
|
||||||
|
@discourseComputed
|
||||||
|
url() {
|
||||||
|
return getURL(`/badges/${this.id}/${this.slug}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFromJson(json) {
|
||||||
|
if (json.badge) {
|
||||||
|
Object.keys(json.badge).forEach((key) => this.set(key, json.badge[key]));
|
||||||
|
}
|
||||||
|
if (json.badge_types) {
|
||||||
|
json.badge_types.forEach((badgeType) => {
|
||||||
|
if (badgeType.id === this.badge_type_id) {
|
||||||
|
this.set("badge_type", Object.create(badgeType));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("badge_type.name")
|
||||||
|
badgeTypeClassName(type) {
|
||||||
|
type = type || "";
|
||||||
|
return `badge-type-${type.toLowerCase()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
save(data) {
|
||||||
|
let url = "/admin/badges",
|
||||||
|
type = "POST";
|
||||||
|
|
||||||
|
if (this.id) {
|
||||||
|
// We are updating an existing badge.
|
||||||
|
url += `/${this.id}`;
|
||||||
|
type = "PUT";
|
||||||
|
}
|
||||||
|
|
||||||
|
return ajax(url, { type, data }).then((json) => {
|
||||||
|
this.updateFromJson(json);
|
||||||
|
return this;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if (this.newBadge) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ajax(`/admin/badges/${this.id}`, {
|
||||||
|
type: "DELETE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -25,13 +25,33 @@ export const AUTO_DELETE_PREFERENCES = {
|
|||||||
export const NO_REMINDER_ICON = "bookmark";
|
export const NO_REMINDER_ICON = "bookmark";
|
||||||
export const WITH_REMINDER_ICON = "discourse-bookmark-clock";
|
export const WITH_REMINDER_ICON = "discourse-bookmark-clock";
|
||||||
|
|
||||||
const Bookmark = RestModel.extend({
|
export default class Bookmark extends RestModel {
|
||||||
newBookmark: none("id"),
|
static create(args) {
|
||||||
|
args = args || {};
|
||||||
|
args.currentUser = args.currentUser || User.current();
|
||||||
|
args.user = User.create(args.user);
|
||||||
|
return super.create(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
static createFor(user, bookmarkableType, bookmarkableId) {
|
||||||
|
return Bookmark.create({
|
||||||
|
bookmarkable_type: bookmarkableType,
|
||||||
|
bookmarkable_id: bookmarkableId,
|
||||||
|
user_id: user.id,
|
||||||
|
auto_delete_preference: user.user_option.bookmark_auto_delete_preference,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async applyTransformations(bookmarks) {
|
||||||
|
await applyModelTransformations("bookmark", bookmarks);
|
||||||
|
}
|
||||||
|
|
||||||
|
@none("id") newBookmark;
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
get url() {
|
get url() {
|
||||||
return getURL(`/bookmarks/${this.id}`);
|
return getURL(`/bookmarks/${this.id}`);
|
||||||
},
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.newBookmark) {
|
if (this.newBookmark) {
|
||||||
@ -41,14 +61,14 @@ const Bookmark = RestModel.extend({
|
|||||||
return ajax(this.url, {
|
return ajax(this.url, {
|
||||||
type: "DELETE",
|
type: "DELETE",
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
attachedTo() {
|
attachedTo() {
|
||||||
return {
|
return {
|
||||||
target: this.bookmarkable_type.toLowerCase(),
|
target: this.bookmarkable_type.toLowerCase(),
|
||||||
targetId: this.bookmarkable_id,
|
targetId: this.bookmarkable_id,
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
|
|
||||||
togglePin() {
|
togglePin() {
|
||||||
if (this.newBookmark) {
|
if (this.newBookmark) {
|
||||||
@ -58,16 +78,16 @@ const Bookmark = RestModel.extend({
|
|||||||
return ajax(this.url + "/toggle_pin", {
|
return ajax(this.url + "/toggle_pin", {
|
||||||
type: "PUT",
|
type: "PUT",
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
pinAction() {
|
pinAction() {
|
||||||
return this.pinned ? "unpin" : "pin";
|
return this.pinned ? "unpin" : "pin";
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("highest_post_number", "url")
|
@discourseComputed("highest_post_number", "url")
|
||||||
lastPostUrl(highestPostNumber) {
|
lastPostUrl(highestPostNumber) {
|
||||||
return this.urlForPostNumber(highestPostNumber);
|
return this.urlForPostNumber(highestPostNumber);
|
||||||
},
|
}
|
||||||
|
|
||||||
// Helper to build a Url with a post number
|
// Helper to build a Url with a post number
|
||||||
urlForPostNumber(postNumber) {
|
urlForPostNumber(postNumber) {
|
||||||
@ -76,7 +96,7 @@ const Bookmark = RestModel.extend({
|
|||||||
url += `/${postNumber}`;
|
url += `/${postNumber}`;
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
},
|
}
|
||||||
|
|
||||||
// returns createdAt if there's no bumped date
|
// returns createdAt if there's no bumped date
|
||||||
@discourseComputed("bumped_at", "createdAt")
|
@discourseComputed("bumped_at", "createdAt")
|
||||||
@ -86,7 +106,7 @@ const Bookmark = RestModel.extend({
|
|||||||
} else {
|
} else {
|
||||||
return createdAt;
|
return createdAt;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("bumpedAt", "createdAt")
|
@discourseComputed("bumpedAt", "createdAt")
|
||||||
bumpedAtTitle(bumpedAt, createdAt) {
|
bumpedAtTitle(bumpedAt, createdAt) {
|
||||||
@ -101,7 +121,7 @@ const Bookmark = RestModel.extend({
|
|||||||
})}\n${I18n.t("topic.bumped_at", { date: longDate(bumpedAt) })}`
|
})}\n${I18n.t("topic.bumped_at", { date: longDate(bumpedAt) })}`
|
||||||
: I18n.t("topic.created_at", { date: longDate(createdAt) });
|
: I18n.t("topic.created_at", { date: longDate(createdAt) });
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("name", "reminder_at")
|
@discourseComputed("name", "reminder_at")
|
||||||
reminderTitle(name, reminderAt) {
|
reminderTitle(name, reminderAt) {
|
||||||
@ -118,12 +138,12 @@ const Bookmark = RestModel.extend({
|
|||||||
return I18n.t("bookmarks.created_generic", {
|
return I18n.t("bookmarks.created_generic", {
|
||||||
name: name || "",
|
name: name || "",
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("created_at")
|
@discourseComputed("created_at")
|
||||||
createdAt(created_at) {
|
createdAt(created_at) {
|
||||||
return new Date(created_at);
|
return new Date(created_at);
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("tags")
|
@discourseComputed("tags")
|
||||||
visibleListTags(tags) {
|
visibleListTags(tags) {
|
||||||
@ -141,12 +161,12 @@ const Bookmark = RestModel.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return newTags;
|
return newTags;
|
||||||
},
|
}
|
||||||
|
|
||||||
@computed("category_id")
|
@computed("category_id")
|
||||||
get category() {
|
get category() {
|
||||||
return Category.findById(this.category_id);
|
return Category.findById(this.category_id);
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("reminder_at", "currentUser")
|
@discourseComputed("reminder_at", "currentUser")
|
||||||
formattedReminder(bookmarkReminderAt, currentUser) {
|
formattedReminder(bookmarkReminderAt, currentUser) {
|
||||||
@ -156,12 +176,12 @@ const Bookmark = RestModel.extend({
|
|||||||
currentUser?.user_option?.timezone || moment.tz.guess()
|
currentUser?.user_option?.timezone || moment.tz.guess()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("reminder_at")
|
@discourseComputed("reminder_at")
|
||||||
reminderAtExpired(bookmarkReminderAt) {
|
reminderAtExpired(bookmarkReminderAt) {
|
||||||
return moment(bookmarkReminderAt) < moment();
|
return moment(bookmarkReminderAt) < moment();
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed()
|
@discourseComputed()
|
||||||
topicForList() {
|
topicForList() {
|
||||||
@ -178,34 +198,10 @@ const Bookmark = RestModel.extend({
|
|||||||
last_read_post_number: this.last_read_post_number,
|
last_read_post_number: this.last_read_post_number,
|
||||||
highest_post_number: this.highest_post_number,
|
highest_post_number: this.highest_post_number,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("bookmarkable_type")
|
@discourseComputed("bookmarkable_type")
|
||||||
bookmarkableTopicAlike(bookmarkable_type) {
|
bookmarkableTopicAlike(bookmarkable_type) {
|
||||||
return ["Topic", "Post"].includes(bookmarkable_type);
|
return ["Topic", "Post"].includes(bookmarkable_type);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
Bookmark.reopenClass({
|
|
||||||
create(args) {
|
|
||||||
args = args || {};
|
|
||||||
args.currentUser = args.currentUser || User.current();
|
|
||||||
args.user = User.create(args.user);
|
|
||||||
return this._super(args);
|
|
||||||
},
|
|
||||||
|
|
||||||
createFor(user, bookmarkableType, bookmarkableId) {
|
|
||||||
return Bookmark.create({
|
|
||||||
bookmarkable_type: bookmarkableType,
|
|
||||||
bookmarkable_id: bookmarkableId,
|
|
||||||
user_id: user.id,
|
|
||||||
auto_delete_preference: user.user_option.bookmark_auto_delete_preference,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
async applyTransformations(bookmarks) {
|
|
||||||
await applyModelTransformations("bookmark", bookmarks);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Bookmark;
|
|
||||||
|
@ -8,38 +8,8 @@ import Topic from "discourse/models/topic";
|
|||||||
import { bind } from "discourse-common/utils/decorators";
|
import { bind } from "discourse-common/utils/decorators";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
const CategoryList = ArrayProxy.extend({
|
export default class CategoryList extends ArrayProxy {
|
||||||
init() {
|
static categoriesFrom(store, result, parentCategory = null) {
|
||||||
this.set("content", this.categories || []);
|
|
||||||
this._super(...arguments);
|
|
||||||
this.set("page", 1);
|
|
||||||
this.set("fetchedLastPage", false);
|
|
||||||
},
|
|
||||||
|
|
||||||
@bind
|
|
||||||
async loadMore() {
|
|
||||||
if (this.isLoading || this.fetchedLastPage) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set("isLoading", true);
|
|
||||||
|
|
||||||
const data = { page: this.page + 1 };
|
|
||||||
const result = await ajax("/categories.json", { data });
|
|
||||||
|
|
||||||
this.set("page", data.page);
|
|
||||||
if (result.category_list.categories.length === 0) {
|
|
||||||
this.set("fetchedLastPage", true);
|
|
||||||
}
|
|
||||||
this.set("isLoading", false);
|
|
||||||
|
|
||||||
const newCategoryList = CategoryList.categoriesFrom(this.store, result);
|
|
||||||
newCategoryList.forEach((c) => this.categories.pushObject(c));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
CategoryList.reopenClass({
|
|
||||||
categoriesFrom(store, result, parentCategory = null) {
|
|
||||||
// Find the period that is most relevant
|
// Find the period that is most relevant
|
||||||
const statPeriod =
|
const statPeriod =
|
||||||
["week", "month"].find(
|
["week", "month"].find(
|
||||||
@ -67,9 +37,9 @@ CategoryList.reopenClass({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return categories;
|
return categories;
|
||||||
},
|
}
|
||||||
|
|
||||||
_buildCategoryResult(c, statPeriod) {
|
static _buildCategoryResult(c, statPeriod) {
|
||||||
if (c.parent_category_id) {
|
if (c.parent_category_id) {
|
||||||
c.parentCategory = Category.findById(c.parent_category_id);
|
c.parentCategory = Category.findById(c.parent_category_id);
|
||||||
}
|
}
|
||||||
@ -126,9 +96,9 @@ CategoryList.reopenClass({
|
|||||||
const record = Site.current().updateCategory(c);
|
const record = Site.current().updateCategory(c);
|
||||||
record.setupGroupsAndPermissions();
|
record.setupGroupsAndPermissions();
|
||||||
return record;
|
return record;
|
||||||
},
|
}
|
||||||
|
|
||||||
listForParent(store, category) {
|
static listForParent(store, category) {
|
||||||
return ajax(
|
return ajax(
|
||||||
`/categories.json?parent_category_id=${category.get("id")}`
|
`/categories.json?parent_category_id=${category.get("id")}`
|
||||||
).then((result) =>
|
).then((result) =>
|
||||||
@ -138,9 +108,9 @@ CategoryList.reopenClass({
|
|||||||
parentCategory: category,
|
parentCategory: category,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
list(store) {
|
static list(store) {
|
||||||
return PreloadStore.getAndRemove("categories_list", () =>
|
return PreloadStore.getAndRemove("categories_list", () =>
|
||||||
ajax("/categories.json")
|
ajax("/categories.json")
|
||||||
).then((result) =>
|
).then((result) =>
|
||||||
@ -151,7 +121,33 @@ CategoryList.reopenClass({
|
|||||||
can_create_topic: result.category_list.can_create_topic,
|
can_create_topic: result.category_list.can_create_topic,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
|
||||||
export default CategoryList;
|
init() {
|
||||||
|
this.set("content", this.categories || []);
|
||||||
|
super.init(...arguments);
|
||||||
|
this.set("page", 1);
|
||||||
|
this.set("fetchedLastPage", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@bind
|
||||||
|
async loadMore() {
|
||||||
|
if (this.isLoading || this.fetchedLastPage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.set("isLoading", true);
|
||||||
|
|
||||||
|
const data = { page: this.page + 1 };
|
||||||
|
const result = await ajax("/categories.json", { data });
|
||||||
|
|
||||||
|
this.set("page", data.page);
|
||||||
|
if (result.category_list.categories.length === 0) {
|
||||||
|
this.set("fetchedLastPage", true);
|
||||||
|
}
|
||||||
|
this.set("isLoading", false);
|
||||||
|
|
||||||
|
const newCategoryList = CategoryList.categoriesFrom(this.store, result);
|
||||||
|
newCategoryList.forEach((c) => this.categories.pushObject(c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -14,370 +14,9 @@ import { MultiCache } from "discourse-common/utils/multi-cache";
|
|||||||
const STAFF_GROUP_NAME = "staff";
|
const STAFF_GROUP_NAME = "staff";
|
||||||
const CATEGORY_ASYNC_SEARCH_CACHE = {};
|
const CATEGORY_ASYNC_SEARCH_CACHE = {};
|
||||||
|
|
||||||
const Category = RestModel.extend({
|
export default class Category extends RestModel {
|
||||||
permissions: null,
|
|
||||||
|
|
||||||
@on("init")
|
|
||||||
setupGroupsAndPermissions() {
|
|
||||||
const availableGroups = this.available_groups;
|
|
||||||
if (!availableGroups) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.set("availableGroups", availableGroups);
|
|
||||||
|
|
||||||
const groupPermissions = this.group_permissions;
|
|
||||||
|
|
||||||
if (groupPermissions) {
|
|
||||||
this.set(
|
|
||||||
"permissions",
|
|
||||||
groupPermissions.map((elem) => {
|
|
||||||
availableGroups.removeObject(elem.group_name);
|
|
||||||
return elem;
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("required_tag_groups", "minimum_required_tags")
|
|
||||||
minimumRequiredTags() {
|
|
||||||
if (this.required_tag_groups?.length > 0) {
|
|
||||||
// it should require the max between the bare minimum set in the category and the sum of the min_count of the
|
|
||||||
// required_tag_groups
|
|
||||||
return Math.max(
|
|
||||||
this.required_tag_groups.reduce((sum, rtg) => sum + rtg.min_count, 0),
|
|
||||||
this.minimum_required_tags || 0
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return this.minimum_required_tags > 0 ? this.minimum_required_tags : null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
availablePermissions() {
|
|
||||||
return [
|
|
||||||
PermissionType.create({ id: PermissionType.FULL }),
|
|
||||||
PermissionType.create({ id: PermissionType.CREATE_POST }),
|
|
||||||
PermissionType.create({ id: PermissionType.READONLY }),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("id")
|
|
||||||
searchContext(id) {
|
|
||||||
return { type: "category", id, category: this };
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("parentCategory.ancestors")
|
|
||||||
ancestors(parentAncestors) {
|
|
||||||
return [...(parentAncestors || []), this];
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("parentCategory.level")
|
|
||||||
level(parentLevel) {
|
|
||||||
if (!parentLevel) {
|
|
||||||
return parentLevel === 0 ? 1 : 0;
|
|
||||||
} else {
|
|
||||||
return parentLevel + 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("has_children", "subcategories")
|
|
||||||
isParent(hasChildren, subcategories) {
|
|
||||||
return hasChildren || (subcategories && subcategories.length > 0);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("subcategories")
|
|
||||||
isGrandParent(subcategories) {
|
|
||||||
return (
|
|
||||||
subcategories &&
|
|
||||||
subcategories.some(
|
|
||||||
(cat) => cat.subcategories && cat.subcategories.length > 0
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("notification_level")
|
|
||||||
isMuted(notificationLevel) {
|
|
||||||
return notificationLevel === NotificationLevels.MUTED;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("isMuted", "subcategories")
|
|
||||||
isHidden(isMuted, subcategories) {
|
|
||||||
if (!isMuted) {
|
|
||||||
return false;
|
|
||||||
} else if (!subcategories) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subcategories.some((cat) => !cat.isHidden)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("isMuted", "subcategories")
|
|
||||||
hasMuted(isMuted, subcategories) {
|
|
||||||
if (isMuted) {
|
|
||||||
return true;
|
|
||||||
} else if (!subcategories) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (subcategories.some((cat) => cat.hasMuted)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("notification_level")
|
|
||||||
notificationLevelString(notificationLevel) {
|
|
||||||
// Get the key from the value
|
|
||||||
const notificationLevelString = Object.keys(NotificationLevels).find(
|
|
||||||
(key) => NotificationLevels[key] === notificationLevel
|
|
||||||
);
|
|
||||||
if (notificationLevelString) {
|
|
||||||
return notificationLevelString.toLowerCase();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("name")
|
|
||||||
path() {
|
|
||||||
return `/c/${Category.slugFor(this)}/${this.id}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("path")
|
|
||||||
url(path) {
|
|
||||||
return getURL(path);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed
|
|
||||||
fullSlug() {
|
|
||||||
return Category.slugFor(this).replace(/\//g, "-");
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("name")
|
|
||||||
nameLower(name) {
|
|
||||||
return name.toLowerCase();
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("url")
|
|
||||||
unreadUrl(url) {
|
|
||||||
return `${url}/l/unread`;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("url")
|
|
||||||
newUrl(url) {
|
|
||||||
return `${url}/l/new`;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("color", "text_color")
|
|
||||||
style(color, textColor) {
|
|
||||||
return `background-color: #${color}; color: #${textColor}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("topic_count")
|
|
||||||
moreTopics(topicCount) {
|
|
||||||
return topicCount > (this.num_featured_topics || 2);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("topic_count", "subcategories.[]")
|
|
||||||
totalTopicCount(topicCount, subcategories) {
|
|
||||||
if (subcategories) {
|
|
||||||
subcategories.forEach((subcategory) => {
|
|
||||||
topicCount += subcategory.topic_count;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return topicCount;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("default_slow_mode_seconds")
|
|
||||||
defaultSlowModeMinutes(seconds) {
|
|
||||||
return seconds ? seconds / 60 : null;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("notification_level")
|
|
||||||
isTracked(notificationLevel) {
|
|
||||||
return notificationLevel >= NotificationLevels.TRACKING;
|
|
||||||
},
|
|
||||||
|
|
||||||
get unreadTopicsCount() {
|
|
||||||
return this.topicTrackingState.countUnread({ categoryId: this.id });
|
|
||||||
},
|
|
||||||
|
|
||||||
get newTopicsCount() {
|
|
||||||
return this.topicTrackingState.countNew({ categoryId: this.id });
|
|
||||||
},
|
|
||||||
|
|
||||||
save() {
|
|
||||||
const id = this.id;
|
|
||||||
const url = id ? `/categories/${id}` : "/categories";
|
|
||||||
|
|
||||||
return ajax(url, {
|
|
||||||
contentType: "application/json",
|
|
||||||
data: JSON.stringify({
|
|
||||||
name: this.name,
|
|
||||||
slug: this.slug,
|
|
||||||
color: this.color,
|
|
||||||
text_color: this.text_color,
|
|
||||||
secure: this.secure,
|
|
||||||
permissions: this._permissionsForUpdate(),
|
|
||||||
auto_close_hours: this.auto_close_hours,
|
|
||||||
auto_close_based_on_last_post: this.get(
|
|
||||||
"auto_close_based_on_last_post"
|
|
||||||
),
|
|
||||||
default_slow_mode_seconds: this.default_slow_mode_seconds,
|
|
||||||
position: this.position,
|
|
||||||
email_in: this.email_in,
|
|
||||||
email_in_allow_strangers: this.email_in_allow_strangers,
|
|
||||||
mailinglist_mirror: this.mailinglist_mirror,
|
|
||||||
parent_category_id: this.parent_category_id,
|
|
||||||
uploaded_logo_id: this.get("uploaded_logo.id"),
|
|
||||||
uploaded_logo_dark_id: this.get("uploaded_logo_dark.id"),
|
|
||||||
uploaded_background_id: this.get("uploaded_background.id"),
|
|
||||||
uploaded_background_dark_id: this.get("uploaded_background_dark.id"),
|
|
||||||
allow_badges: this.allow_badges,
|
|
||||||
category_setting_attributes: this.category_setting,
|
|
||||||
custom_fields: this.custom_fields,
|
|
||||||
topic_template: this.topic_template,
|
|
||||||
form_template_ids: this.form_template_ids,
|
|
||||||
all_topics_wiki: this.all_topics_wiki,
|
|
||||||
allow_unlimited_owner_edits_on_first_post:
|
|
||||||
this.allow_unlimited_owner_edits_on_first_post,
|
|
||||||
allowed_tags: this.allowed_tags,
|
|
||||||
allowed_tag_groups: this.allowed_tag_groups,
|
|
||||||
allow_global_tags: this.allow_global_tags,
|
|
||||||
required_tag_groups: this.required_tag_groups,
|
|
||||||
sort_order: this.sort_order,
|
|
||||||
sort_ascending: this.sort_ascending,
|
|
||||||
topic_featured_link_allowed: this.topic_featured_link_allowed,
|
|
||||||
show_subcategory_list: this.show_subcategory_list,
|
|
||||||
num_featured_topics: this.num_featured_topics,
|
|
||||||
default_view: this.default_view,
|
|
||||||
subcategory_list_style: this.subcategory_list_style,
|
|
||||||
default_top_period: this.default_top_period,
|
|
||||||
minimum_required_tags: this.minimum_required_tags,
|
|
||||||
navigate_to_first_post_after_read: this.get(
|
|
||||||
"navigate_to_first_post_after_read"
|
|
||||||
),
|
|
||||||
search_priority: this.search_priority,
|
|
||||||
reviewable_by_group_name: this.reviewable_by_group_name,
|
|
||||||
read_only_banner: this.read_only_banner,
|
|
||||||
default_list_filter: this.default_list_filter,
|
|
||||||
}),
|
|
||||||
type: id ? "PUT" : "POST",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_permissionsForUpdate() {
|
|
||||||
const permissions = this.permissions;
|
|
||||||
let rval = {};
|
|
||||||
if (permissions.length) {
|
|
||||||
permissions.forEach((p) => (rval[p.group_name] = p.permission_type));
|
|
||||||
} else {
|
|
||||||
// empty permissions => staff-only access
|
|
||||||
rval[STAFF_GROUP_NAME] = PermissionType.FULL;
|
|
||||||
}
|
|
||||||
return rval;
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
return ajax(`/categories/${this.id || this.slug}`, {
|
|
||||||
type: "DELETE",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
addPermission(permission) {
|
|
||||||
this.permissions.addObject(permission);
|
|
||||||
this.availableGroups.removeObject(permission.group_name);
|
|
||||||
},
|
|
||||||
|
|
||||||
removePermission(group_name) {
|
|
||||||
const permission = this.permissions.findBy("group_name", group_name);
|
|
||||||
if (permission) {
|
|
||||||
this.permissions.removeObject(permission);
|
|
||||||
this.availableGroups.addObject(group_name);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updatePermission(group_name, type) {
|
|
||||||
this.permissions.forEach((p, i) => {
|
|
||||||
if (p.group_name === group_name) {
|
|
||||||
this.set(`permissions.${i}.permission_type`, type);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("topics")
|
|
||||||
latestTopic(topics) {
|
|
||||||
if (topics && topics.length) {
|
|
||||||
return topics[0];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("topics")
|
|
||||||
featuredTopics(topics) {
|
|
||||||
if (topics && topics.length) {
|
|
||||||
return topics.slice(0, this.num_featured_topics || 2);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
setNotification(notification_level) {
|
|
||||||
User.currentProp(
|
|
||||||
"muted_category_ids",
|
|
||||||
User.current().calculateMutedIds(
|
|
||||||
notification_level,
|
|
||||||
this.id,
|
|
||||||
"muted_category_ids"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const url = `/category/${this.id}/notifications`;
|
|
||||||
return ajax(url, { data: { notification_level }, type: "POST" }).then(
|
|
||||||
(data) => {
|
|
||||||
User.current().set(
|
|
||||||
"indirectly_muted_category_ids",
|
|
||||||
data.indirectly_muted_category_ids
|
|
||||||
);
|
|
||||||
this.set("notification_level", notification_level);
|
|
||||||
this.notifyPropertyChange("notification_level");
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("id")
|
|
||||||
isUncategorizedCategory(id) {
|
|
||||||
return Category.isUncategorized(id);
|
|
||||||
},
|
|
||||||
|
|
||||||
get canCreateTopic() {
|
|
||||||
return this.permission === PermissionType.FULL;
|
|
||||||
},
|
|
||||||
|
|
||||||
get subcategoryWithCreateTopicPermission() {
|
|
||||||
return this.subcategories?.find(
|
|
||||||
(subcategory) => subcategory.canCreateTopic
|
|
||||||
);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let _uncategorized;
|
|
||||||
|
|
||||||
const categoryMultiCache = new MultiCache(async (ids) => {
|
|
||||||
const result = await ajax("/categories/find", { data: { ids } });
|
|
||||||
|
|
||||||
return new Map(
|
|
||||||
result["categories"].map((category) => [category.id, category])
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
export function resetCategoryCache() {
|
|
||||||
categoryMultiCache.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
Category.reopenClass({
|
|
||||||
// Sort subcategories directly under parents
|
// Sort subcategories directly under parents
|
||||||
sortCategories(categories) {
|
static sortCategories(categories) {
|
||||||
const children = new Map();
|
const children = new Map();
|
||||||
|
|
||||||
categories.forEach((category) => {
|
categories.forEach((category) => {
|
||||||
@ -392,20 +31,20 @@ Category.reopenClass({
|
|||||||
values.flatMap((c) => [c, reduce(children.get(c.id) || [])]).flat();
|
values.flatMap((c) => [c, reduce(children.get(c.id) || [])]).flat();
|
||||||
|
|
||||||
return reduce(children.get(-1) || []);
|
return reduce(children.get(-1) || []);
|
||||||
},
|
}
|
||||||
|
|
||||||
isUncategorized(categoryId) {
|
static isUncategorized(categoryId) {
|
||||||
return categoryId === Site.currentProp("uncategorized_category_id");
|
return categoryId === Site.currentProp("uncategorized_category_id");
|
||||||
},
|
}
|
||||||
|
|
||||||
slugEncoded() {
|
static slugEncoded() {
|
||||||
let siteSettings = getOwnerWithFallback(this).lookup(
|
let siteSettings = getOwnerWithFallback(this).lookup(
|
||||||
"service:site-settings"
|
"service:site-settings"
|
||||||
);
|
);
|
||||||
return siteSettings.slug_generation_method === "encoded";
|
return siteSettings.slug_generation_method === "encoded";
|
||||||
},
|
}
|
||||||
|
|
||||||
findUncategorized() {
|
static findUncategorized() {
|
||||||
_uncategorized =
|
_uncategorized =
|
||||||
_uncategorized ||
|
_uncategorized ||
|
||||||
Category.list().findBy(
|
Category.list().findBy(
|
||||||
@ -413,9 +52,9 @@ Category.reopenClass({
|
|||||||
Site.currentProp("uncategorized_category_id")
|
Site.currentProp("uncategorized_category_id")
|
||||||
);
|
);
|
||||||
return _uncategorized;
|
return _uncategorized;
|
||||||
},
|
}
|
||||||
|
|
||||||
slugFor(category, separator = "/", depth = 3) {
|
static slugFor(category, separator = "/", depth = 3) {
|
||||||
if (!category) {
|
if (!category) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@ -434,21 +73,21 @@ Category.reopenClass({
|
|||||||
return !slug || slug.trim().length === 0
|
return !slug || slug.trim().length === 0
|
||||||
? `${result}${id}-category`
|
? `${result}${id}-category`
|
||||||
: result + slug;
|
: result + slug;
|
||||||
},
|
}
|
||||||
|
|
||||||
list() {
|
static list() {
|
||||||
return Site.currentProp("categoriesList");
|
return Site.currentProp("categoriesList");
|
||||||
},
|
}
|
||||||
|
|
||||||
listByActivity() {
|
static listByActivity() {
|
||||||
return Site.currentProp("sortedCategories");
|
return Site.currentProp("sortedCategories");
|
||||||
},
|
}
|
||||||
|
|
||||||
_idMap() {
|
static _idMap() {
|
||||||
return Site.currentProp("categoriesById");
|
return Site.currentProp("categoriesById");
|
||||||
},
|
}
|
||||||
|
|
||||||
findSingleBySlug(slug) {
|
static findSingleBySlug(slug) {
|
||||||
if (!this.slugEncoded()) {
|
if (!this.slugEncoded()) {
|
||||||
return Category.list().find((c) => Category.slugFor(c) === slug);
|
return Category.list().find((c) => Category.slugFor(c) === slug);
|
||||||
} else {
|
} else {
|
||||||
@ -456,16 +95,16 @@ Category.reopenClass({
|
|||||||
(c) => Category.slugFor(c) === encodeURI(slug)
|
(c) => Category.slugFor(c) === encodeURI(slug)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
findById(id) {
|
static findById(id) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return Category._idMap()[id];
|
return Category._idMap()[id];
|
||||||
},
|
}
|
||||||
|
|
||||||
findByIds(ids = []) {
|
static findByIds(ids = []) {
|
||||||
const categories = [];
|
const categories = [];
|
||||||
ids.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
const found = Category.findById(id);
|
const found = Category.findById(id);
|
||||||
@ -474,14 +113,14 @@ Category.reopenClass({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return categories;
|
return categories;
|
||||||
},
|
}
|
||||||
|
|
||||||
hasAsyncFoundAll(ids) {
|
static hasAsyncFoundAll(ids) {
|
||||||
const loadedCategoryIds = Site.current().loadedCategoryIds || new Set();
|
const loadedCategoryIds = Site.current().loadedCategoryIds || new Set();
|
||||||
return ids.every((id) => loadedCategoryIds.has(id));
|
return ids.every((id) => loadedCategoryIds.has(id));
|
||||||
},
|
}
|
||||||
|
|
||||||
async asyncFindByIds(ids = []) {
|
static async asyncFindByIds(ids = []) {
|
||||||
ids = ids.map((x) => parseInt(x, 10));
|
ids = ids.map((x) => parseInt(x, 10));
|
||||||
|
|
||||||
if (!Site.current().lazy_load_categories) {
|
if (!Site.current().lazy_load_categories) {
|
||||||
@ -508,9 +147,9 @@ Category.reopenClass({
|
|||||||
Site.current().set("loadedCategoryIds", loadedCategoryIds);
|
Site.current().set("loadedCategoryIds", loadedCategoryIds);
|
||||||
|
|
||||||
return categories;
|
return categories;
|
||||||
},
|
}
|
||||||
|
|
||||||
findBySlugAndParent(slug, parentCategory) {
|
static findBySlugAndParent(slug, parentCategory) {
|
||||||
if (this.slugEncoded()) {
|
if (this.slugEncoded()) {
|
||||||
slug = encodeURI(slug);
|
slug = encodeURI(slug);
|
||||||
}
|
}
|
||||||
@ -520,9 +159,9 @@ Category.reopenClass({
|
|||||||
(category.parentCategory || null) === parentCategory
|
(category.parentCategory || null) === parentCategory
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
findBySlugPath(slugPath) {
|
static findBySlugPath(slugPath) {
|
||||||
let category = null;
|
let category = null;
|
||||||
|
|
||||||
for (const slug of slugPath) {
|
for (const slug of slugPath) {
|
||||||
@ -534,9 +173,9 @@ Category.reopenClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return category;
|
return category;
|
||||||
},
|
}
|
||||||
|
|
||||||
async asyncFindBySlugPathWithID(slugPathWithID) {
|
static async asyncFindBySlugPathWithID(slugPathWithID) {
|
||||||
const result = await ajax("/categories/find", {
|
const result = await ajax("/categories/find", {
|
||||||
data: { slug_path_with_id: slugPathWithID },
|
data: { slug_path_with_id: slugPathWithID },
|
||||||
});
|
});
|
||||||
@ -546,9 +185,9 @@ Category.reopenClass({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return categories[categories.length - 1];
|
return categories[categories.length - 1];
|
||||||
},
|
}
|
||||||
|
|
||||||
findBySlugPathWithID(slugPathWithID) {
|
static findBySlugPathWithID(slugPathWithID) {
|
||||||
let parts = slugPathWithID.split("/").filter(Boolean);
|
let parts = slugPathWithID.split("/").filter(Boolean);
|
||||||
// slugs found by star/glob pathing in ember do not automatically url decode - ensure that these are decoded
|
// slugs found by star/glob pathing in ember do not automatically url decode - ensure that these are decoded
|
||||||
if (this.slugEncoded()) {
|
if (this.slugEncoded()) {
|
||||||
@ -575,9 +214,9 @@ Category.reopenClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return category;
|
return category;
|
||||||
},
|
}
|
||||||
|
|
||||||
findBySlug(slug, parentSlug) {
|
static findBySlug(slug, parentSlug) {
|
||||||
const categories = Category.list();
|
const categories = Category.list();
|
||||||
let category;
|
let category;
|
||||||
|
|
||||||
@ -615,34 +254,34 @@ Category.reopenClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return category;
|
return category;
|
||||||
},
|
}
|
||||||
|
|
||||||
fetchVisibleGroups(id) {
|
static fetchVisibleGroups(id) {
|
||||||
return ajax(`/c/${id}/visible_groups.json`);
|
return ajax(`/c/${id}/visible_groups.json`);
|
||||||
},
|
}
|
||||||
|
|
||||||
reloadById(id) {
|
static reloadById(id) {
|
||||||
return ajax(`/c/${id}/show.json`);
|
return ajax(`/c/${id}/show.json`);
|
||||||
},
|
}
|
||||||
|
|
||||||
reloadBySlugPath(slugPath) {
|
static reloadBySlugPath(slugPath) {
|
||||||
return ajax(`/c/${slugPath}/find_by_slug.json`);
|
return ajax(`/c/${slugPath}/find_by_slug.json`);
|
||||||
},
|
}
|
||||||
|
|
||||||
reloadCategoryWithPermissions(params, store, site) {
|
static reloadCategoryWithPermissions(params, store, site) {
|
||||||
return this.reloadBySlugPath(params.slug).then((result) =>
|
return this.reloadBySlugPath(params.slug).then((result) =>
|
||||||
this._includePermissions(result.category, store, site)
|
this._includePermissions(result.category, store, site)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
_includePermissions(category, store, site) {
|
static _includePermissions(category, store, site) {
|
||||||
const record = store.createRecord("category", category);
|
const record = store.createRecord("category", category);
|
||||||
record.setupGroupsAndPermissions();
|
record.setupGroupsAndPermissions();
|
||||||
site.updateCategory(record);
|
site.updateCategory(record);
|
||||||
return record;
|
return record;
|
||||||
},
|
}
|
||||||
|
|
||||||
search(term, opts) {
|
static search(term, opts) {
|
||||||
let limit = 5;
|
let limit = 5;
|
||||||
let parentCategoryId;
|
let parentCategoryId;
|
||||||
|
|
||||||
@ -713,9 +352,9 @@ Category.reopenClass({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return data.sortBy("read_restricted");
|
return data.sortBy("read_restricted");
|
||||||
},
|
}
|
||||||
|
|
||||||
async asyncSearch(term, opts) {
|
static async asyncSearch(term, opts) {
|
||||||
opts ||= {};
|
opts ||= {};
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
@ -735,7 +374,364 @@ Category.reopenClass({
|
|||||||
return result["categories"].map((category) =>
|
return result["categories"].map((category) =>
|
||||||
Site.current().updateCategory(category)
|
Site.current().updateCategory(category)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
|
permissions = null;
|
||||||
|
|
||||||
|
@on("init")
|
||||||
|
setupGroupsAndPermissions() {
|
||||||
|
const availableGroups = this.available_groups;
|
||||||
|
if (!availableGroups) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.set("availableGroups", availableGroups);
|
||||||
|
|
||||||
|
const groupPermissions = this.group_permissions;
|
||||||
|
|
||||||
|
if (groupPermissions) {
|
||||||
|
this.set(
|
||||||
|
"permissions",
|
||||||
|
groupPermissions.map((elem) => {
|
||||||
|
availableGroups.removeObject(elem.group_name);
|
||||||
|
return elem;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("required_tag_groups", "minimum_required_tags")
|
||||||
|
minimumRequiredTags() {
|
||||||
|
if (this.required_tag_groups?.length > 0) {
|
||||||
|
// it should require the max between the bare minimum set in the category and the sum of the min_count of the
|
||||||
|
// required_tag_groups
|
||||||
|
return Math.max(
|
||||||
|
this.required_tag_groups.reduce((sum, rtg) => sum + rtg.min_count, 0),
|
||||||
|
this.minimum_required_tags || 0
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return this.minimum_required_tags > 0 ? this.minimum_required_tags : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed
|
||||||
|
availablePermissions() {
|
||||||
|
return [
|
||||||
|
PermissionType.create({ id: PermissionType.FULL }),
|
||||||
|
PermissionType.create({ id: PermissionType.CREATE_POST }),
|
||||||
|
PermissionType.create({ id: PermissionType.READONLY }),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("id")
|
||||||
|
searchContext(id) {
|
||||||
|
return { type: "category", id, category: this };
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("parentCategory.ancestors")
|
||||||
|
ancestors(parentAncestors) {
|
||||||
|
return [...(parentAncestors || []), this];
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("parentCategory.level")
|
||||||
|
level(parentLevel) {
|
||||||
|
if (!parentLevel) {
|
||||||
|
return parentLevel === 0 ? 1 : 0;
|
||||||
|
} else {
|
||||||
|
return parentLevel + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("has_children", "subcategories")
|
||||||
|
isParent(hasChildren, subcategories) {
|
||||||
|
return hasChildren || (subcategories && subcategories.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("subcategories")
|
||||||
|
isGrandParent(subcategories) {
|
||||||
|
return (
|
||||||
|
subcategories &&
|
||||||
|
subcategories.some(
|
||||||
|
(cat) => cat.subcategories && cat.subcategories.length > 0
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("notification_level")
|
||||||
|
isMuted(notificationLevel) {
|
||||||
|
return notificationLevel === NotificationLevels.MUTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("isMuted", "subcategories")
|
||||||
|
isHidden(isMuted, subcategories) {
|
||||||
|
if (!isMuted) {
|
||||||
|
return false;
|
||||||
|
} else if (!subcategories) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcategories.some((cat) => !cat.isHidden)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("isMuted", "subcategories")
|
||||||
|
hasMuted(isMuted, subcategories) {
|
||||||
|
if (isMuted) {
|
||||||
|
return true;
|
||||||
|
} else if (!subcategories) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcategories.some((cat) => cat.hasMuted)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("notification_level")
|
||||||
|
notificationLevelString(notificationLevel) {
|
||||||
|
// Get the key from the value
|
||||||
|
const notificationLevelString = Object.keys(NotificationLevels).find(
|
||||||
|
(key) => NotificationLevels[key] === notificationLevel
|
||||||
|
);
|
||||||
|
if (notificationLevelString) {
|
||||||
|
return notificationLevelString.toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("name")
|
||||||
|
path() {
|
||||||
|
return `/c/${Category.slugFor(this)}/${this.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("path")
|
||||||
|
url(path) {
|
||||||
|
return getURL(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed
|
||||||
|
fullSlug() {
|
||||||
|
return Category.slugFor(this).replace(/\//g, "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("name")
|
||||||
|
nameLower(name) {
|
||||||
|
return name.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("url")
|
||||||
|
unreadUrl(url) {
|
||||||
|
return `${url}/l/unread`;
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("url")
|
||||||
|
newUrl(url) {
|
||||||
|
return `${url}/l/new`;
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("color", "text_color")
|
||||||
|
style(color, textColor) {
|
||||||
|
return `background-color: #${color}; color: #${textColor}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("topic_count")
|
||||||
|
moreTopics(topicCount) {
|
||||||
|
return topicCount > (this.num_featured_topics || 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("topic_count", "subcategories.[]")
|
||||||
|
totalTopicCount(topicCount, subcategories) {
|
||||||
|
if (subcategories) {
|
||||||
|
subcategories.forEach((subcategory) => {
|
||||||
|
topicCount += subcategory.topic_count;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return topicCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("default_slow_mode_seconds")
|
||||||
|
defaultSlowModeMinutes(seconds) {
|
||||||
|
return seconds ? seconds / 60 : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("notification_level")
|
||||||
|
isTracked(notificationLevel) {
|
||||||
|
return notificationLevel >= NotificationLevels.TRACKING;
|
||||||
|
}
|
||||||
|
|
||||||
|
get unreadTopicsCount() {
|
||||||
|
return this.topicTrackingState.countUnread({ categoryId: this.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
get newTopicsCount() {
|
||||||
|
return this.topicTrackingState.countNew({ categoryId: this.id });
|
||||||
|
}
|
||||||
|
|
||||||
|
save() {
|
||||||
|
const id = this.id;
|
||||||
|
const url = id ? `/categories/${id}` : "/categories";
|
||||||
|
|
||||||
|
return ajax(url, {
|
||||||
|
contentType: "application/json",
|
||||||
|
data: JSON.stringify({
|
||||||
|
name: this.name,
|
||||||
|
slug: this.slug,
|
||||||
|
color: this.color,
|
||||||
|
text_color: this.text_color,
|
||||||
|
secure: this.secure,
|
||||||
|
permissions: this._permissionsForUpdate(),
|
||||||
|
auto_close_hours: this.auto_close_hours,
|
||||||
|
auto_close_based_on_last_post: this.get(
|
||||||
|
"auto_close_based_on_last_post"
|
||||||
|
),
|
||||||
|
default_slow_mode_seconds: this.default_slow_mode_seconds,
|
||||||
|
position: this.position,
|
||||||
|
email_in: this.email_in,
|
||||||
|
email_in_allow_strangers: this.email_in_allow_strangers,
|
||||||
|
mailinglist_mirror: this.mailinglist_mirror,
|
||||||
|
parent_category_id: this.parent_category_id,
|
||||||
|
uploaded_logo_id: this.get("uploaded_logo.id"),
|
||||||
|
uploaded_logo_dark_id: this.get("uploaded_logo_dark.id"),
|
||||||
|
uploaded_background_id: this.get("uploaded_background.id"),
|
||||||
|
uploaded_background_dark_id: this.get("uploaded_background_dark.id"),
|
||||||
|
allow_badges: this.allow_badges,
|
||||||
|
category_setting_attributes: this.category_setting,
|
||||||
|
custom_fields: this.custom_fields,
|
||||||
|
topic_template: this.topic_template,
|
||||||
|
form_template_ids: this.form_template_ids,
|
||||||
|
all_topics_wiki: this.all_topics_wiki,
|
||||||
|
allow_unlimited_owner_edits_on_first_post:
|
||||||
|
this.allow_unlimited_owner_edits_on_first_post,
|
||||||
|
allowed_tags: this.allowed_tags,
|
||||||
|
allowed_tag_groups: this.allowed_tag_groups,
|
||||||
|
allow_global_tags: this.allow_global_tags,
|
||||||
|
required_tag_groups: this.required_tag_groups,
|
||||||
|
sort_order: this.sort_order,
|
||||||
|
sort_ascending: this.sort_ascending,
|
||||||
|
topic_featured_link_allowed: this.topic_featured_link_allowed,
|
||||||
|
show_subcategory_list: this.show_subcategory_list,
|
||||||
|
num_featured_topics: this.num_featured_topics,
|
||||||
|
default_view: this.default_view,
|
||||||
|
subcategory_list_style: this.subcategory_list_style,
|
||||||
|
default_top_period: this.default_top_period,
|
||||||
|
minimum_required_tags: this.minimum_required_tags,
|
||||||
|
navigate_to_first_post_after_read: this.get(
|
||||||
|
"navigate_to_first_post_after_read"
|
||||||
|
),
|
||||||
|
search_priority: this.search_priority,
|
||||||
|
reviewable_by_group_name: this.reviewable_by_group_name,
|
||||||
|
read_only_banner: this.read_only_banner,
|
||||||
|
default_list_filter: this.default_list_filter,
|
||||||
|
}),
|
||||||
|
type: id ? "PUT" : "POST",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_permissionsForUpdate() {
|
||||||
|
const permissions = this.permissions;
|
||||||
|
let rval = {};
|
||||||
|
if (permissions.length) {
|
||||||
|
permissions.forEach((p) => (rval[p.group_name] = p.permission_type));
|
||||||
|
} else {
|
||||||
|
// empty permissions => staff-only access
|
||||||
|
rval[STAFF_GROUP_NAME] = PermissionType.FULL;
|
||||||
|
}
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
return ajax(`/categories/${this.id || this.slug}`, {
|
||||||
|
type: "DELETE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addPermission(permission) {
|
||||||
|
this.permissions.addObject(permission);
|
||||||
|
this.availableGroups.removeObject(permission.group_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
removePermission(group_name) {
|
||||||
|
const permission = this.permissions.findBy("group_name", group_name);
|
||||||
|
if (permission) {
|
||||||
|
this.permissions.removeObject(permission);
|
||||||
|
this.availableGroups.addObject(group_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePermission(group_name, type) {
|
||||||
|
this.permissions.forEach((p, i) => {
|
||||||
|
if (p.group_name === group_name) {
|
||||||
|
this.set(`permissions.${i}.permission_type`, type);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("topics")
|
||||||
|
latestTopic(topics) {
|
||||||
|
if (topics && topics.length) {
|
||||||
|
return topics[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("topics")
|
||||||
|
featuredTopics(topics) {
|
||||||
|
if (topics && topics.length) {
|
||||||
|
return topics.slice(0, this.num_featured_topics || 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setNotification(notification_level) {
|
||||||
|
User.currentProp(
|
||||||
|
"muted_category_ids",
|
||||||
|
User.current().calculateMutedIds(
|
||||||
|
notification_level,
|
||||||
|
this.id,
|
||||||
|
"muted_category_ids"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const url = `/category/${this.id}/notifications`;
|
||||||
|
return ajax(url, { data: { notification_level }, type: "POST" }).then(
|
||||||
|
(data) => {
|
||||||
|
User.current().set(
|
||||||
|
"indirectly_muted_category_ids",
|
||||||
|
data.indirectly_muted_category_ids
|
||||||
|
);
|
||||||
|
this.set("notification_level", notification_level);
|
||||||
|
this.notifyPropertyChange("notification_level");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("id")
|
||||||
|
isUncategorizedCategory(id) {
|
||||||
|
return Category.isUncategorized(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
get canCreateTopic() {
|
||||||
|
return this.permission === PermissionType.FULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
get subcategoryWithCreateTopicPermission() {
|
||||||
|
return this.subcategories?.find(
|
||||||
|
(subcategory) => subcategory.canCreateTopic
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _uncategorized;
|
||||||
|
|
||||||
|
const categoryMultiCache = new MultiCache(async (ids) => {
|
||||||
|
const result = await ajax("/categories/find", { data: { ids } });
|
||||||
|
|
||||||
|
return new Map(
|
||||||
|
result["categories"].map((category) => [category.id, category])
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default Category;
|
export function resetCategoryCache() {
|
||||||
|
categoryMultiCache.reset();
|
||||||
|
}
|
||||||
|
@ -1,26 +1,24 @@
|
|||||||
import EmberObject from "@ember/object";
|
import EmberObject from "@ember/object";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
|
||||||
const Draft = EmberObject.extend();
|
export default class Draft extends EmberObject {
|
||||||
|
static clear(key, sequence) {
|
||||||
Draft.reopenClass({
|
|
||||||
clear(key, sequence) {
|
|
||||||
return ajax(`/drafts/${key}.json`, {
|
return ajax(`/drafts/${key}.json`, {
|
||||||
type: "DELETE",
|
type: "DELETE",
|
||||||
data: { draft_key: key, sequence },
|
data: { draft_key: key, sequence },
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
get(key) {
|
static get(key) {
|
||||||
return ajax(`/drafts/${key}.json`);
|
return ajax(`/drafts/${key}.json`);
|
||||||
},
|
}
|
||||||
|
|
||||||
getLocal(key, current) {
|
static getLocal(key, current) {
|
||||||
// TODO: implement this
|
// TODO: implement this
|
||||||
return current;
|
return current;
|
||||||
},
|
}
|
||||||
|
|
||||||
save(key, sequence, data, clientId, { forceSave = false } = {}) {
|
static save(key, sequence, data, clientId, { forceSave = false } = {}) {
|
||||||
data = typeof data === "string" ? data : JSON.stringify(data);
|
data = typeof data === "string" ? data : JSON.stringify(data);
|
||||||
return ajax("/drafts.json", {
|
return ajax("/drafts.json", {
|
||||||
type: "POST",
|
type: "POST",
|
||||||
@ -33,7 +31,5 @@ Draft.reopenClass({
|
|||||||
},
|
},
|
||||||
ignoreUnsent: false,
|
ignoreUnsent: false,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
export default Draft;
|
|
||||||
|
@ -2,9 +2,9 @@ import RestModel from "discourse/models/rest";
|
|||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
export default RestModel.extend({
|
export default class GroupHistory extends RestModel {
|
||||||
@discourseComputed("action")
|
@discourseComputed("action")
|
||||||
actionTitle(action) {
|
actionTitle(action) {
|
||||||
return I18n.t(`group_histories.actions.${action}`);
|
return I18n.t(`group_histories.actions.${action}`);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
@ -11,34 +11,56 @@ import Topic from "discourse/models/topic";
|
|||||||
import User from "discourse/models/user";
|
import User from "discourse/models/user";
|
||||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
const Group = RestModel.extend({
|
export default class Group extends RestModel {
|
||||||
user_count: 0,
|
static findAll(opts) {
|
||||||
limit: null,
|
return ajax("/groups/search.json", { data: opts }).then((groups) =>
|
||||||
offset: null,
|
groups.map((g) => Group.create(g))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
request_count: 0,
|
static loadMembers(name, opts) {
|
||||||
requestersLimit: null,
|
return ajax(`/groups/${name}/members.json`, { data: opts });
|
||||||
requestersOffset: null,
|
}
|
||||||
|
|
||||||
|
static mentionable(name) {
|
||||||
|
return ajax(`/groups/${name}/mentionable`);
|
||||||
|
}
|
||||||
|
|
||||||
|
static messageable(name) {
|
||||||
|
return ajax(`/groups/${name}/messageable`);
|
||||||
|
}
|
||||||
|
|
||||||
|
static checkName(name) {
|
||||||
|
return ajax("/groups/check-name", { data: { group_name: name } });
|
||||||
|
}
|
||||||
|
|
||||||
|
user_count = 0;
|
||||||
|
limit = null;
|
||||||
|
offset = null;
|
||||||
|
request_count = 0;
|
||||||
|
requestersLimit = null;
|
||||||
|
requestersOffset = null;
|
||||||
|
|
||||||
|
@equal("mentionable_level", 99) canEveryoneMention;
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
super.init(...arguments);
|
||||||
this.setProperties({ members: [], requesters: [] });
|
this.setProperties({ members: [], requesters: [] });
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("automatic_membership_email_domains")
|
@discourseComputed("automatic_membership_email_domains")
|
||||||
emailDomains(value) {
|
emailDomains(value) {
|
||||||
return isEmpty(value) ? "" : value;
|
return isEmpty(value) ? "" : value;
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("associated_group_ids")
|
@discourseComputed("associated_group_ids")
|
||||||
associatedGroupIds(value) {
|
associatedGroupIds(value) {
|
||||||
return isEmpty(value) ? [] : value;
|
return isEmpty(value) ? [] : value;
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("automatic")
|
@discourseComputed("automatic")
|
||||||
type(automatic) {
|
type(automatic) {
|
||||||
return automatic ? "automatic" : "custom";
|
return automatic ? "automatic" : "custom";
|
||||||
},
|
}
|
||||||
|
|
||||||
async reloadMembers(params, refresh) {
|
async reloadMembers(params, refresh) {
|
||||||
if (isEmpty(this.name) || !this.can_see_members) {
|
if (isEmpty(this.name) || !this.can_see_members) {
|
||||||
@ -73,7 +95,7 @@ const Group = RestModel.extend({
|
|||||||
limit: response.meta.limit,
|
limit: response.meta.limit,
|
||||||
offset: response.meta.offset,
|
offset: response.meta.offset,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
findRequesters(params, refresh) {
|
findRequesters(params, refresh) {
|
||||||
if (isEmpty(this.name) || !this.can_see_members) {
|
if (isEmpty(this.name) || !this.can_see_members) {
|
||||||
@ -103,7 +125,7 @@ const Group = RestModel.extend({
|
|||||||
requestersOffset: result.meta.offset,
|
requestersOffset: result.meta.offset,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
async removeOwner(member) {
|
async removeOwner(member) {
|
||||||
await ajax(`/admin/groups/${this.id}/owners.json`, {
|
await ajax(`/admin/groups/${this.id}/owners.json`, {
|
||||||
@ -111,7 +133,7 @@ const Group = RestModel.extend({
|
|||||||
data: { user_id: member.id },
|
data: { user_id: member.id },
|
||||||
});
|
});
|
||||||
await this.reloadMembers({}, true);
|
await this.reloadMembers({}, true);
|
||||||
},
|
}
|
||||||
|
|
||||||
async removeMember(member, params) {
|
async removeMember(member, params) {
|
||||||
await ajax(`/groups/${this.id}/members.json`, {
|
await ajax(`/groups/${this.id}/members.json`, {
|
||||||
@ -119,7 +141,7 @@ const Group = RestModel.extend({
|
|||||||
data: { user_id: member.id },
|
data: { user_id: member.id },
|
||||||
});
|
});
|
||||||
await this.reloadMembers(params, true);
|
await this.reloadMembers(params, true);
|
||||||
},
|
}
|
||||||
|
|
||||||
async leave() {
|
async leave() {
|
||||||
await ajax(`/groups/${this.id}/leave.json`, {
|
await ajax(`/groups/${this.id}/leave.json`, {
|
||||||
@ -127,7 +149,7 @@ const Group = RestModel.extend({
|
|||||||
});
|
});
|
||||||
this.set("can_see_members", this.members_visibility_level < 2);
|
this.set("can_see_members", this.members_visibility_level < 2);
|
||||||
await this.reloadMembers({}, true);
|
await this.reloadMembers({}, true);
|
||||||
},
|
}
|
||||||
|
|
||||||
async addMembers(usernames, filter, notifyUsers, emails = []) {
|
async addMembers(usernames, filter, notifyUsers, emails = []) {
|
||||||
const response = await ajax(`/groups/${this.id}/members.json`, {
|
const response = await ajax(`/groups/${this.id}/members.json`, {
|
||||||
@ -139,14 +161,14 @@ const Group = RestModel.extend({
|
|||||||
} else {
|
} else {
|
||||||
await this.reloadMembers();
|
await this.reloadMembers();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
async join() {
|
async join() {
|
||||||
await ajax(`/groups/${this.id}/join.json`, {
|
await ajax(`/groups/${this.id}/join.json`, {
|
||||||
type: "PUT",
|
type: "PUT",
|
||||||
});
|
});
|
||||||
await this.reloadMembers({}, true);
|
await this.reloadMembers({}, true);
|
||||||
},
|
}
|
||||||
|
|
||||||
async addOwners(usernames, filter, notifyUsers) {
|
async addOwners(usernames, filter, notifyUsers) {
|
||||||
const response = await ajax(`/groups/${this.id}/owners.json`, {
|
const response = await ajax(`/groups/${this.id}/owners.json`, {
|
||||||
@ -159,44 +181,42 @@ const Group = RestModel.extend({
|
|||||||
} else {
|
} else {
|
||||||
await this.reloadMembers({}, true);
|
await this.reloadMembers({}, true);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
_filterMembers(usernames) {
|
_filterMembers(usernames) {
|
||||||
return this.reloadMembers({ filter: usernames.join(",") });
|
return this.reloadMembers({ filter: usernames.join(",") });
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("display_name", "name")
|
@discourseComputed("display_name", "name")
|
||||||
displayName(groupDisplayName, name) {
|
displayName(groupDisplayName, name) {
|
||||||
return groupDisplayName || name;
|
return groupDisplayName || name;
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("flair_bg_color")
|
@discourseComputed("flair_bg_color")
|
||||||
flairBackgroundHexColor(flairBgColor) {
|
flairBackgroundHexColor(flairBgColor) {
|
||||||
return flairBgColor
|
return flairBgColor
|
||||||
? flairBgColor.replace(new RegExp("[^0-9a-fA-F]", "g"), "")
|
? flairBgColor.replace(new RegExp("[^0-9a-fA-F]", "g"), "")
|
||||||
: null;
|
: null;
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("flair_color")
|
@discourseComputed("flair_color")
|
||||||
flairHexColor(flairColor) {
|
flairHexColor(flairColor) {
|
||||||
return flairColor
|
return flairColor
|
||||||
? flairColor.replace(new RegExp("[^0-9a-fA-F]", "g"), "")
|
? flairColor.replace(new RegExp("[^0-9a-fA-F]", "g"), "")
|
||||||
: null;
|
: null;
|
||||||
},
|
}
|
||||||
|
|
||||||
canEveryoneMention: equal("mentionable_level", 99),
|
|
||||||
|
|
||||||
@discourseComputed("visibility_level")
|
@discourseComputed("visibility_level")
|
||||||
isPrivate(visibilityLevel) {
|
isPrivate(visibilityLevel) {
|
||||||
return visibilityLevel > 1;
|
return visibilityLevel > 1;
|
||||||
},
|
}
|
||||||
|
|
||||||
@observes("isPrivate", "canEveryoneMention")
|
@observes("isPrivate", "canEveryoneMention")
|
||||||
_updateAllowMembershipRequests() {
|
_updateAllowMembershipRequests() {
|
||||||
if (this.isPrivate || !this.canEveryoneMention) {
|
if (this.isPrivate || !this.canEveryoneMention) {
|
||||||
this.set("allow_membership_requests", false);
|
this.set("allow_membership_requests", false);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
@dependentKeyCompat
|
@dependentKeyCompat
|
||||||
get watchingCategories() {
|
get watchingCategories() {
|
||||||
@ -210,14 +230,14 @@ const Group = RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Category.findByIds(this.get("watching_category_ids"));
|
return Category.findByIds(this.get("watching_category_ids"));
|
||||||
},
|
}
|
||||||
|
|
||||||
set watchingCategories(categories) {
|
set watchingCategories(categories) {
|
||||||
this.set(
|
this.set(
|
||||||
"watching_category_ids",
|
"watching_category_ids",
|
||||||
categories.map((c) => c.id)
|
categories.map((c) => c.id)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
@dependentKeyCompat
|
@dependentKeyCompat
|
||||||
get trackingCategories() {
|
get trackingCategories() {
|
||||||
@ -231,14 +251,14 @@ const Group = RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Category.findByIds(this.get("tracking_category_ids"));
|
return Category.findByIds(this.get("tracking_category_ids"));
|
||||||
},
|
}
|
||||||
|
|
||||||
set trackingCategories(categories) {
|
set trackingCategories(categories) {
|
||||||
this.set(
|
this.set(
|
||||||
"tracking_category_ids",
|
"tracking_category_ids",
|
||||||
categories.map((c) => c.id)
|
categories.map((c) => c.id)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
@dependentKeyCompat
|
@dependentKeyCompat
|
||||||
get watchingFirstPostCategories() {
|
get watchingFirstPostCategories() {
|
||||||
@ -252,14 +272,14 @@ const Group = RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Category.findByIds(this.get("watching_first_post_category_ids"));
|
return Category.findByIds(this.get("watching_first_post_category_ids"));
|
||||||
},
|
}
|
||||||
|
|
||||||
set watchingFirstPostCategories(categories) {
|
set watchingFirstPostCategories(categories) {
|
||||||
this.set(
|
this.set(
|
||||||
"watching_first_post_category_ids",
|
"watching_first_post_category_ids",
|
||||||
categories.map((c) => c.id)
|
categories.map((c) => c.id)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
@dependentKeyCompat
|
@dependentKeyCompat
|
||||||
get regularCategories() {
|
get regularCategories() {
|
||||||
@ -273,14 +293,14 @@ const Group = RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Category.findByIds(this.get("regular_category_ids"));
|
return Category.findByIds(this.get("regular_category_ids"));
|
||||||
},
|
}
|
||||||
|
|
||||||
set regularCategories(categories) {
|
set regularCategories(categories) {
|
||||||
this.set(
|
this.set(
|
||||||
"regular_category_ids",
|
"regular_category_ids",
|
||||||
categories.map((c) => c.id)
|
categories.map((c) => c.id)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
@dependentKeyCompat
|
@dependentKeyCompat
|
||||||
get mutedCategories() {
|
get mutedCategories() {
|
||||||
@ -294,14 +314,14 @@ const Group = RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Category.findByIds(this.get("muted_category_ids"));
|
return Category.findByIds(this.get("muted_category_ids"));
|
||||||
},
|
}
|
||||||
|
|
||||||
set mutedCategories(categories) {
|
set mutedCategories(categories) {
|
||||||
this.set(
|
this.set(
|
||||||
"muted_category_ids",
|
"muted_category_ids",
|
||||||
categories.map((c) => c.id)
|
categories.map((c) => c.id)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
asJSON() {
|
asJSON() {
|
||||||
const attrs = {
|
const attrs = {
|
||||||
@ -382,7 +402,7 @@ const Group = RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return attrs;
|
return attrs;
|
||||||
},
|
}
|
||||||
|
|
||||||
async create() {
|
async create() {
|
||||||
const response = await ajax("/admin/groups", {
|
const response = await ajax("/admin/groups", {
|
||||||
@ -397,21 +417,21 @@ const Group = RestModel.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
await this.reloadMembers();
|
await this.reloadMembers();
|
||||||
},
|
}
|
||||||
|
|
||||||
save(opts = {}) {
|
save(opts = {}) {
|
||||||
return ajax(`/groups/${this.id}`, {
|
return ajax(`/groups/${this.id}`, {
|
||||||
type: "PUT",
|
type: "PUT",
|
||||||
data: Object.assign({ group: this.asJSON() }, opts),
|
data: Object.assign({ group: this.asJSON() }, opts),
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
if (!this.id) {
|
if (!this.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return ajax(`/admin/groups/${this.id}`, { type: "DELETE" });
|
return ajax(`/admin/groups/${this.id}`, { type: "DELETE" });
|
||||||
},
|
}
|
||||||
|
|
||||||
findLogs(offset, filters) {
|
findLogs(offset, filters) {
|
||||||
return ajax(`/groups/${this.name}/logs.json`, {
|
return ajax(`/groups/${this.name}/logs.json`, {
|
||||||
@ -422,7 +442,7 @@ const Group = RestModel.extend({
|
|||||||
all_loaded: results["all_loaded"],
|
all_loaded: results["all_loaded"],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
findPosts(opts) {
|
findPosts(opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
@ -445,7 +465,7 @@ const Group = RestModel.extend({
|
|||||||
return EmberObject.create(p);
|
return EmberObject.create(p);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
setNotification(notification_level, userId) {
|
setNotification(notification_level, userId) {
|
||||||
this.set("group_user.notification_level", notification_level);
|
this.set("group_user.notification_level", notification_level);
|
||||||
@ -453,38 +473,12 @@ const Group = RestModel.extend({
|
|||||||
data: { notification_level, user_id: userId },
|
data: { notification_level, user_id: userId },
|
||||||
type: "POST",
|
type: "POST",
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
requestMembership(reason) {
|
requestMembership(reason) {
|
||||||
return ajax(`/groups/${this.name}/request_membership.json`, {
|
return ajax(`/groups/${this.name}/request_membership.json`, {
|
||||||
type: "POST",
|
type: "POST",
|
||||||
data: { reason },
|
data: { reason },
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
Group.reopenClass({
|
|
||||||
findAll(opts) {
|
|
||||||
return ajax("/groups/search.json", { data: opts }).then((groups) =>
|
|
||||||
groups.map((g) => Group.create(g))
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
loadMembers(name, opts) {
|
|
||||||
return ajax(`/groups/${name}/members.json`, { data: opts });
|
|
||||||
},
|
|
||||||
|
|
||||||
mentionable(name) {
|
|
||||||
return ajax(`/groups/${name}/mentionable`);
|
|
||||||
},
|
|
||||||
|
|
||||||
messageable(name) {
|
|
||||||
return ajax(`/groups/${name}/messageable`);
|
|
||||||
},
|
|
||||||
|
|
||||||
checkName(name) {
|
|
||||||
return ajax("/groups/check-name", { data: { group_name: name } });
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Group;
|
|
||||||
|
@ -9,65 +9,16 @@ import Topic from "discourse/models/topic";
|
|||||||
import User from "discourse/models/user";
|
import User from "discourse/models/user";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
const Invite = EmberObject.extend({
|
export default class Invite extends EmberObject {
|
||||||
save(data) {
|
static create() {
|
||||||
const promise = this.id
|
const result = super.create(...arguments);
|
||||||
? ajax(`/invites/${this.id}`, { type: "PUT", data })
|
|
||||||
: ajax("/invites", { type: "POST", data });
|
|
||||||
|
|
||||||
return promise.then((result) => this.setProperties(result));
|
|
||||||
},
|
|
||||||
|
|
||||||
destroy() {
|
|
||||||
return ajax("/invites", {
|
|
||||||
type: "DELETE",
|
|
||||||
data: { id: this.id },
|
|
||||||
}).then(() => this.set("destroyed", true));
|
|
||||||
},
|
|
||||||
|
|
||||||
reinvite() {
|
|
||||||
return ajax("/invites/reinvite", {
|
|
||||||
type: "POST",
|
|
||||||
data: { email: this.email },
|
|
||||||
})
|
|
||||||
.then(() => this.set("reinvited", true))
|
|
||||||
.catch(popupAjaxError);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("invite_key")
|
|
||||||
shortKey(key) {
|
|
||||||
return key.slice(0, 4) + "...";
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("groups")
|
|
||||||
groupIds(groups) {
|
|
||||||
return groups ? groups.map((group) => group.id) : [];
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("topics.firstObject")
|
|
||||||
topic(topicData) {
|
|
||||||
return topicData ? Topic.create(topicData) : null;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("email", "domain")
|
|
||||||
emailOrDomain(email, domain) {
|
|
||||||
return email || domain;
|
|
||||||
},
|
|
||||||
|
|
||||||
topicId: alias("topics.firstObject.id"),
|
|
||||||
topicTitle: alias("topics.firstObject.title"),
|
|
||||||
});
|
|
||||||
|
|
||||||
Invite.reopenClass({
|
|
||||||
create() {
|
|
||||||
const result = this._super.apply(this, arguments);
|
|
||||||
if (result.user) {
|
if (result.user) {
|
||||||
result.user = User.create(result.user);
|
result.user = User.create(result.user);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
}
|
||||||
|
|
||||||
findInvitedBy(user, filter, search, offset) {
|
static findInvitedBy(user, filter, search, offset) {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
Promise.resolve();
|
Promise.resolve();
|
||||||
}
|
}
|
||||||
@ -87,15 +38,60 @@ Invite.reopenClass({
|
|||||||
result.invites = result.invites.map((i) => Invite.create(i));
|
result.invites = result.invites.map((i) => Invite.create(i));
|
||||||
return EmberObject.create(result);
|
return EmberObject.create(result);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
reinviteAll() {
|
static reinviteAll() {
|
||||||
return ajax("/invites/reinvite-all", { type: "POST" });
|
return ajax("/invites/reinvite-all", { type: "POST" });
|
||||||
},
|
}
|
||||||
|
|
||||||
destroyAllExpired() {
|
static destroyAllExpired() {
|
||||||
return ajax("/invites/destroy-all-expired", { type: "POST" });
|
return ajax("/invites/destroy-all-expired", { type: "POST" });
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
|
||||||
export default Invite;
|
@alias("topics.firstObject.id") topicId;
|
||||||
|
@alias("topics.firstObject.title") topicTitle;
|
||||||
|
|
||||||
|
save(data) {
|
||||||
|
const promise = this.id
|
||||||
|
? ajax(`/invites/${this.id}`, { type: "PUT", data })
|
||||||
|
: ajax("/invites", { type: "POST", data });
|
||||||
|
|
||||||
|
return promise.then((result) => this.setProperties(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
return ajax("/invites", {
|
||||||
|
type: "DELETE",
|
||||||
|
data: { id: this.id },
|
||||||
|
}).then(() => this.set("destroyed", true));
|
||||||
|
}
|
||||||
|
|
||||||
|
reinvite() {
|
||||||
|
return ajax("/invites/reinvite", {
|
||||||
|
type: "POST",
|
||||||
|
data: { email: this.email },
|
||||||
|
})
|
||||||
|
.then(() => this.set("reinvited", true))
|
||||||
|
.catch(popupAjaxError);
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("invite_key")
|
||||||
|
shortKey(key) {
|
||||||
|
return key.slice(0, 4) + "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("groups")
|
||||||
|
groupIds(groups) {
|
||||||
|
return groups ? groups.map((group) => group.id) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("topics.firstObject")
|
||||||
|
topic(topicData) {
|
||||||
|
return topicData ? Topic.create(topicData) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("email", "domain")
|
||||||
|
emailOrDomain(email, domain) {
|
||||||
|
return email || domain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,14 +1,10 @@
|
|||||||
import EmberObject from "@ember/object";
|
import EmberObject from "@ember/object";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
|
||||||
const LivePostCounts = EmberObject.extend({});
|
export default class LivePostCounts extends EmberObject {
|
||||||
|
static find() {
|
||||||
LivePostCounts.reopenClass({
|
|
||||||
find() {
|
|
||||||
return ajax("/about/live_post_counts.json").then((result) =>
|
return ajax("/about/live_post_counts.json").then((result) =>
|
||||||
LivePostCounts.create(result)
|
LivePostCounts.create(result)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
export default LivePostCounts;
|
|
||||||
|
@ -7,11 +7,31 @@ import getURL from "discourse-common/lib/get-url";
|
|||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
const LoginMethod = EmberObject.extend({
|
export default class LoginMethod extends EmberObject {
|
||||||
|
static buildPostForm(url) {
|
||||||
|
// Login always happens in an anonymous context, with no CSRF token
|
||||||
|
// So we need to fetch it before sending a POST request
|
||||||
|
return updateCsrfToken().then(() => {
|
||||||
|
const form = document.createElement("form");
|
||||||
|
form.setAttribute("style", "display:none;");
|
||||||
|
form.setAttribute("method", "post");
|
||||||
|
form.setAttribute("action", url);
|
||||||
|
|
||||||
|
const input = document.createElement("input");
|
||||||
|
input.setAttribute("name", "authenticity_token");
|
||||||
|
input.setAttribute("value", Session.currentProp("csrfToken"));
|
||||||
|
form.appendChild(input);
|
||||||
|
|
||||||
|
document.body.appendChild(form);
|
||||||
|
|
||||||
|
return form;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@discourseComputed
|
@discourseComputed
|
||||||
title() {
|
title() {
|
||||||
return this.title_override || I18n.t(`login.${this.name}.title`);
|
return this.title_override || I18n.t(`login.${this.name}.title`);
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed
|
@discourseComputed
|
||||||
screenReaderTitle() {
|
screenReaderTitle() {
|
||||||
@ -19,12 +39,12 @@ const LoginMethod = EmberObject.extend({
|
|||||||
this.title_override ||
|
this.title_override ||
|
||||||
I18n.t(`login.${this.name}.sr_title`, { defaultValue: this.title })
|
I18n.t(`login.${this.name}.sr_title`, { defaultValue: this.title })
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed
|
@discourseComputed
|
||||||
prettyName() {
|
prettyName() {
|
||||||
return this.pretty_name_override || I18n.t(`login.${this.name}.name`);
|
return this.pretty_name_override || I18n.t(`login.${this.name}.name`);
|
||||||
},
|
}
|
||||||
|
|
||||||
doLogin({ reconnect = false, signup = false, params = {} } = {}) {
|
doLogin({ reconnect = false, signup = false, params = {} } = {}) {
|
||||||
if (this.customLogin) {
|
if (this.customLogin) {
|
||||||
@ -56,30 +76,8 @@ const LoginMethod = EmberObject.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return LoginMethod.buildPostForm(authUrl).then((form) => form.submit());
|
return LoginMethod.buildPostForm(authUrl).then((form) => form.submit());
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
LoginMethod.reopenClass({
|
|
||||||
buildPostForm(url) {
|
|
||||||
// Login always happens in an anonymous context, with no CSRF token
|
|
||||||
// So we need to fetch it before sending a POST request
|
|
||||||
return updateCsrfToken().then(() => {
|
|
||||||
const form = document.createElement("form");
|
|
||||||
form.setAttribute("style", "display:none;");
|
|
||||||
form.setAttribute("method", "post");
|
|
||||||
form.setAttribute("action", url);
|
|
||||||
|
|
||||||
const input = document.createElement("input");
|
|
||||||
input.setAttribute("name", "authenticity_token");
|
|
||||||
input.setAttribute("value", Session.currentProp("csrfToken"));
|
|
||||||
form.appendChild(input);
|
|
||||||
|
|
||||||
document.body.appendChild(form);
|
|
||||||
|
|
||||||
return form;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
let methods;
|
let methods;
|
||||||
|
|
||||||
@ -101,5 +99,3 @@ export function findAll() {
|
|||||||
export function clearAuthMethods() {
|
export function clearAuthMethods() {
|
||||||
methods = undefined;
|
methods = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LoginMethod;
|
|
||||||
|
@ -5,27 +5,27 @@ import RestModel from "discourse/models/rest";
|
|||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import Category from "./category";
|
import Category from "./category";
|
||||||
|
|
||||||
const PendingPost = RestModel.extend({
|
export default class PendingPost extends RestModel {
|
||||||
expandedExcerpt: null,
|
expandedExcerpt = null;
|
||||||
postUrl: reads("topic_url"),
|
|
||||||
truncated: false,
|
@reads("topic_url") postUrl;
|
||||||
|
|
||||||
|
truncated = false;
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
super.init(...arguments);
|
||||||
cook(this.raw_text).then((cooked) => {
|
cook(this.raw_text).then((cooked) => {
|
||||||
this.set("expandedExcerpt", cooked);
|
this.set("expandedExcerpt", cooked);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("username")
|
@discourseComputed("username")
|
||||||
userUrl(username) {
|
userUrl(username) {
|
||||||
return userPath(username.toLowerCase());
|
return userPath(username.toLowerCase());
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("category_id")
|
@discourseComputed("category_id")
|
||||||
category() {
|
category() {
|
||||||
return Category.findById(this.category_id);
|
return Category.findById(this.category_id);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
export default PendingPost;
|
|
||||||
|
@ -6,20 +6,18 @@ export function buildPermissionDescription(id) {
|
|||||||
return I18n.t("permission_types." + PermissionType.DESCRIPTION_KEYS[id]);
|
return I18n.t("permission_types." + PermissionType.DESCRIPTION_KEYS[id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PermissionType = EmberObject.extend({
|
export default class PermissionType extends EmberObject {
|
||||||
|
static FULL = 1;
|
||||||
|
static CREATE_POST = 2;
|
||||||
|
static READONLY = 3;
|
||||||
|
static DESCRIPTION_KEYS = {
|
||||||
|
1: "full",
|
||||||
|
2: "create_post",
|
||||||
|
3: "readonly",
|
||||||
|
};
|
||||||
|
|
||||||
@discourseComputed("id")
|
@discourseComputed("id")
|
||||||
description(id) {
|
description(id) {
|
||||||
return buildPermissionDescription(id);
|
return buildPermissionDescription(id);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
PermissionType.FULL = 1;
|
|
||||||
PermissionType.CREATE_POST = 2;
|
|
||||||
PermissionType.READONLY = 3;
|
|
||||||
PermissionType.DESCRIPTION_KEYS = {
|
|
||||||
1: "full",
|
|
||||||
2: "create_post",
|
|
||||||
3: "readonly",
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PermissionType;
|
|
||||||
|
@ -3,6 +3,6 @@ import RestModel from "discourse/models/rest";
|
|||||||
|
|
||||||
export const MAX_MESSAGE_LENGTH = 500;
|
export const MAX_MESSAGE_LENGTH = 500;
|
||||||
|
|
||||||
export default RestModel.extend({
|
export default class PostActionType extends RestModel {
|
||||||
notCustomFlag: not("is_custom_flag"),
|
@not("is_custom_flag") notCustomFlag;
|
||||||
});
|
}
|
||||||
|
@ -33,23 +33,33 @@ export function resetLastEditNotificationClick() {
|
|||||||
_lastEditNotificationClick = null;
|
_lastEditNotificationClick = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RestModel.extend({
|
export default class PostStream extends RestModel {
|
||||||
_identityMap: null,
|
posts = null;
|
||||||
posts: null,
|
stream = null;
|
||||||
stream: null,
|
userFilters = null;
|
||||||
userFilters: null,
|
loaded = null;
|
||||||
loaded: null,
|
loadingAbove = null;
|
||||||
loadingAbove: null,
|
loadingBelow = null;
|
||||||
loadingBelow: null,
|
loadingFilter = null;
|
||||||
loadingFilter: null,
|
loadingNearPost = null;
|
||||||
loadingNearPost: null,
|
stagingPost = null;
|
||||||
stagingPost: null,
|
postsWithPlaceholders = null;
|
||||||
postsWithPlaceholders: null,
|
timelineLookup = null;
|
||||||
timelineLookup: null,
|
filterRepliesToPostNumber = null;
|
||||||
filterRepliesToPostNumber: null,
|
filterUpwardsPostID = null;
|
||||||
filterUpwardsPostID: null,
|
filter = null;
|
||||||
filter: null,
|
topicSummary = null;
|
||||||
topicSummary: null,
|
lastId = null;
|
||||||
|
|
||||||
|
@or("loadingAbove", "loadingBelow", "loadingFilter", "stagingPost") loading;
|
||||||
|
@not("loading") notLoading;
|
||||||
|
@equal("filter", "summary") summary;
|
||||||
|
@and("notLoading", "hasPosts", "lastPostNotLoaded") canAppendMore;
|
||||||
|
@and("notLoading", "hasPosts", "firstPostNotLoaded") canPrependMore;
|
||||||
|
@not("firstPostPresent") firstPostNotLoaded;
|
||||||
|
@not("loadedAllPosts") lastPostNotLoaded;
|
||||||
|
|
||||||
|
_identityMap = null;
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._identityMap = {};
|
this._identityMap = {};
|
||||||
@ -75,12 +85,7 @@ export default RestModel.extend({
|
|||||||
timelineLookup: [],
|
timelineLookup: [],
|
||||||
topicSummary: new TopicSummary(),
|
topicSummary: new TopicSummary(),
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
loading: or("loadingAbove", "loadingBelow", "loadingFilter", "stagingPost"),
|
|
||||||
notLoading: not("loading"),
|
|
||||||
|
|
||||||
summary: equal("filter", "summary"),
|
|
||||||
|
|
||||||
@discourseComputed(
|
@discourseComputed(
|
||||||
"isMegaTopic",
|
"isMegaTopic",
|
||||||
@ -89,20 +94,17 @@ export default RestModel.extend({
|
|||||||
)
|
)
|
||||||
filteredPostsCount(isMegaTopic, streamLength, topicHighestPostNumber) {
|
filteredPostsCount(isMegaTopic, streamLength, topicHighestPostNumber) {
|
||||||
return isMegaTopic ? topicHighestPostNumber : streamLength;
|
return isMegaTopic ? topicHighestPostNumber : streamLength;
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("posts.[]")
|
@discourseComputed("posts.[]")
|
||||||
hasPosts() {
|
hasPosts() {
|
||||||
return this.get("posts.length") > 0;
|
return this.get("posts.length") > 0;
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("hasPosts", "filteredPostsCount")
|
@discourseComputed("hasPosts", "filteredPostsCount")
|
||||||
hasLoadedData(hasPosts, filteredPostsCount) {
|
hasLoadedData(hasPosts, filteredPostsCount) {
|
||||||
return hasPosts && filteredPostsCount > 0;
|
return hasPosts && filteredPostsCount > 0;
|
||||||
},
|
}
|
||||||
|
|
||||||
canAppendMore: and("notLoading", "hasPosts", "lastPostNotLoaded"),
|
|
||||||
canPrependMore: and("notLoading", "hasPosts", "firstPostNotLoaded"),
|
|
||||||
|
|
||||||
@discourseComputed("hasLoadedData", "posts.[]")
|
@discourseComputed("hasLoadedData", "posts.[]")
|
||||||
firstPostPresent(hasLoadedData) {
|
firstPostPresent(hasLoadedData) {
|
||||||
@ -111,16 +113,12 @@ export default RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return !!this.posts.findBy("post_number", 1);
|
return !!this.posts.findBy("post_number", 1);
|
||||||
},
|
}
|
||||||
|
|
||||||
firstPostNotLoaded: not("firstPostPresent"),
|
|
||||||
|
|
||||||
lastId: null,
|
|
||||||
|
|
||||||
@discourseComputed("isMegaTopic", "stream.lastObject", "lastId")
|
@discourseComputed("isMegaTopic", "stream.lastObject", "lastId")
|
||||||
lastPostId(isMegaTopic, streamLastId, lastId) {
|
lastPostId(isMegaTopic, streamLastId, lastId) {
|
||||||
return isMegaTopic ? lastId : streamLastId;
|
return isMegaTopic ? lastId : streamLastId;
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("hasLoadedData", "lastPostId", "posts.@each.id")
|
@discourseComputed("hasLoadedData", "lastPostId", "posts.@each.id")
|
||||||
loadedAllPosts(hasLoadedData, lastPostId) {
|
loadedAllPosts(hasLoadedData, lastPostId) {
|
||||||
@ -132,9 +130,7 @@ export default RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return !!this.posts.findBy("id", lastPostId);
|
return !!this.posts.findBy("id", lastPostId);
|
||||||
},
|
}
|
||||||
|
|
||||||
lastPostNotLoaded: not("loadedAllPosts"),
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns a JS Object of current stream filter options. It should match the query
|
Returns a JS Object of current stream filter options. It should match the query
|
||||||
@ -167,7 +163,7 @@ export default RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("streamFilters.[]", "topic.posts_count", "posts.length")
|
@discourseComputed("streamFilters.[]", "topic.posts_count", "posts.length")
|
||||||
hasNoFilters() {
|
hasNoFilters() {
|
||||||
@ -176,7 +172,7 @@ export default RestModel.extend({
|
|||||||
streamFilters &&
|
streamFilters &&
|
||||||
(streamFilters.filter === "summary" || streamFilters.username_filters)
|
(streamFilters.filter === "summary" || streamFilters.username_filters)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns the window of posts above the current set in the stream, bound to the top of the stream.
|
Returns the window of posts above the current set in the stream, bound to the top of the stream.
|
||||||
@ -206,7 +202,7 @@ export default RestModel.extend({
|
|||||||
startIndex = 0;
|
startIndex = 0;
|
||||||
}
|
}
|
||||||
return stream.slice(startIndex, firstIndex);
|
return stream.slice(startIndex, firstIndex);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns the window of posts below the current set in the stream, bound by the bottom of the
|
Returns the window of posts below the current set in the stream, bound by the bottom of the
|
||||||
@ -234,7 +230,7 @@ export default RestModel.extend({
|
|||||||
lastIndex + 1,
|
lastIndex + 1,
|
||||||
lastIndex + this.get("topic.chunk_size") + 1
|
lastIndex + this.get("topic.chunk_size") + 1
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
cancelFilter() {
|
cancelFilter() {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
@ -244,7 +240,7 @@ export default RestModel.extend({
|
|||||||
mixedHiddenPosts: false,
|
mixedHiddenPosts: false,
|
||||||
filter: null,
|
filter: null,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
refreshAndJumpToSecondVisible() {
|
refreshAndJumpToSecondVisible() {
|
||||||
return this.refresh({}).then(() => {
|
return this.refresh({}).then(() => {
|
||||||
@ -252,20 +248,20 @@ export default RestModel.extend({
|
|||||||
DiscourseURL.jumpToPost(this.posts[1].get("post_number"));
|
DiscourseURL.jumpToPost(this.posts[1].get("post_number"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
showTopReplies() {
|
showTopReplies() {
|
||||||
this.cancelFilter();
|
this.cancelFilter();
|
||||||
this.set("filter", "summary");
|
this.set("filter", "summary");
|
||||||
return this.refreshAndJumpToSecondVisible();
|
return this.refreshAndJumpToSecondVisible();
|
||||||
},
|
}
|
||||||
|
|
||||||
// Filter the stream to a particular user.
|
// Filter the stream to a particular user.
|
||||||
filterParticipant(username) {
|
filterParticipant(username) {
|
||||||
this.cancelFilter();
|
this.cancelFilter();
|
||||||
this.userFilters.addObject(username);
|
this.userFilters.addObject(username);
|
||||||
return this.refreshAndJumpToSecondVisible();
|
return this.refreshAndJumpToSecondVisible();
|
||||||
},
|
}
|
||||||
|
|
||||||
filterReplies(postNumber, postId) {
|
filterReplies(postNumber, postId) {
|
||||||
this.cancelFilter();
|
this.cancelFilter();
|
||||||
@ -295,7 +291,7 @@ export default RestModel.extend({
|
|||||||
highlightPost(postNumber);
|
highlightPost(postNumber);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
filterUpwards(postID) {
|
filterUpwards(postID) {
|
||||||
this.cancelFilter();
|
this.cancelFilter();
|
||||||
@ -316,7 +312,7 @@ export default RestModel.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Loads a new set of posts into the stream. If you provide a `nearPost` option and the post
|
Loads a new set of posts into the stream. If you provide a `nearPost` option and the post
|
||||||
@ -378,7 +374,7 @@ export default RestModel.extend({
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.set("loadingNearPost", null);
|
this.set("loadingNearPost", null);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
// Fill in a gap of posts before a particular post
|
// Fill in a gap of posts before a particular post
|
||||||
fillGapBefore(post, gap) {
|
fillGapBefore(post, gap) {
|
||||||
@ -420,7 +416,7 @@ export default RestModel.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
}
|
||||||
|
|
||||||
// Fill in a gap of posts after a particular post
|
// Fill in a gap of posts after a particular post
|
||||||
fillGapAfter(post, gap) {
|
fillGapAfter(post, gap) {
|
||||||
@ -436,7 +432,7 @@ export default RestModel.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
}
|
||||||
|
|
||||||
gapExpanded() {
|
gapExpanded() {
|
||||||
this.appEvents.trigger("post-stream:refresh");
|
this.appEvents.trigger("post-stream:refresh");
|
||||||
@ -446,7 +442,7 @@ export default RestModel.extend({
|
|||||||
if (this.streamFilters && this.streamFilters.replies_to_post_number) {
|
if (this.streamFilters && this.streamFilters.replies_to_post_number) {
|
||||||
this.set("streamFilters.mixedHiddenPosts", true);
|
this.set("streamFilters.mixedHiddenPosts", true);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
// Appends the next window of posts to the stream. Call it when scrolling downwards.
|
// Appends the next window of posts to the stream. Call it when scrolling downwards.
|
||||||
appendMore() {
|
appendMore() {
|
||||||
@ -493,7 +489,7 @@ export default RestModel.extend({
|
|||||||
this.set("loadingBelow", false);
|
this.set("loadingBelow", false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
// Prepend the previous window of posts to the stream. Call it when scrolling upwards.
|
// Prepend the previous window of posts to the stream. Call it when scrolling upwards.
|
||||||
prependMore() {
|
prependMore() {
|
||||||
@ -535,7 +531,7 @@ export default RestModel.extend({
|
|||||||
this.set("loadingAbove", false);
|
this.set("loadingAbove", false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Stage a post for insertion in the stream. It should be rendered right away under the
|
Stage a post for insertion in the stream. It should be rendered right away under the
|
||||||
@ -573,7 +569,7 @@ export default RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return "offScreen";
|
return "offScreen";
|
||||||
},
|
}
|
||||||
|
|
||||||
// Commit the post we staged. Call this after a save succeeds.
|
// Commit the post we staged. Call this after a save succeeds.
|
||||||
commitPost(post) {
|
commitPost(post) {
|
||||||
@ -587,7 +583,7 @@ export default RestModel.extend({
|
|||||||
this.stream.removeObject(-1);
|
this.stream.removeObject(-1);
|
||||||
this._identityMap[-1] = null;
|
this._identityMap[-1] = null;
|
||||||
this.set("stagingPost", false);
|
this.set("stagingPost", false);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Undo a post we've staged in the stream. Remove it from being rendered and revert the
|
Undo a post we've staged in the stream. Remove it from being rendered and revert the
|
||||||
@ -607,7 +603,7 @@ export default RestModel.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// TODO unfudge reply count on parent post
|
// TODO unfudge reply count on parent post
|
||||||
},
|
}
|
||||||
|
|
||||||
prependPost(post) {
|
prependPost(post) {
|
||||||
this._initUserModels(post);
|
this._initUserModels(post);
|
||||||
@ -618,7 +614,7 @@ export default RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return post;
|
return post;
|
||||||
},
|
}
|
||||||
|
|
||||||
appendPost(post) {
|
appendPost(post) {
|
||||||
this._initUserModels(post);
|
this._initUserModels(post);
|
||||||
@ -639,7 +635,7 @@ export default RestModel.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return post;
|
return post;
|
||||||
},
|
}
|
||||||
|
|
||||||
removePosts(posts) {
|
removePosts(posts) {
|
||||||
if (isEmpty(posts)) {
|
if (isEmpty(posts)) {
|
||||||
@ -655,12 +651,12 @@ export default RestModel.extend({
|
|||||||
allPosts.removeObjects(posts);
|
allPosts.removeObjects(posts);
|
||||||
postIds.forEach((id) => delete identityMap[id]);
|
postIds.forEach((id) => delete identityMap[id]);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
// Returns a post from the identity map if it's been inserted.
|
// Returns a post from the identity map if it's been inserted.
|
||||||
findLoadedPost(id) {
|
findLoadedPost(id) {
|
||||||
return this._identityMap[id];
|
return this._identityMap[id];
|
||||||
},
|
}
|
||||||
|
|
||||||
loadPostByPostNumber(postNumber) {
|
loadPostByPostNumber(postNumber) {
|
||||||
const url = `/posts/by_number/${this.get("topic.id")}/${postNumber}`;
|
const url = `/posts/by_number/${this.get("topic.id")}/${postNumber}`;
|
||||||
@ -669,7 +665,7 @@ export default RestModel.extend({
|
|||||||
return ajax(url).then((post) => {
|
return ajax(url).then((post) => {
|
||||||
return this.storePost(store.createRecord("post", post));
|
return this.storePost(store.createRecord("post", post));
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
loadNearestPostToDate(date) {
|
loadNearestPostToDate(date) {
|
||||||
const url = `/posts/by-date/${this.get("topic.id")}/${date}`;
|
const url = `/posts/by-date/${this.get("topic.id")}/${date}`;
|
||||||
@ -678,7 +674,7 @@ export default RestModel.extend({
|
|||||||
return ajax(url).then((post) => {
|
return ajax(url).then((post) => {
|
||||||
return this.storePost(store.createRecord("post", post));
|
return this.storePost(store.createRecord("post", post));
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
loadPost(postId) {
|
loadPost(postId) {
|
||||||
const url = "/posts/" + postId;
|
const url = "/posts/" + postId;
|
||||||
@ -692,7 +688,7 @@ export default RestModel.extend({
|
|||||||
|
|
||||||
return this.storePost(store.createRecord("post", p));
|
return this.storePost(store.createRecord("post", p));
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/* mainly for backwards compatibility with plugins, used in quick messages plugin
|
/* mainly for backwards compatibility with plugins, used in quick messages plugin
|
||||||
* TODO: remove July 2022
|
* TODO: remove July 2022
|
||||||
@ -705,7 +701,7 @@ export default RestModel.extend({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
return this.triggerNewPostsInStream([postId], opts);
|
return this.triggerNewPostsInStream([postId], opts);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Finds and adds posts to the stream by id. Typically this would happen if we receive a message
|
Finds and adds posts to the stream by id. Typically this would happen if we receive a message
|
||||||
@ -768,7 +764,7 @@ export default RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return resolved;
|
return resolved;
|
||||||
},
|
}
|
||||||
|
|
||||||
triggerRecoveredPost(postId) {
|
triggerRecoveredPost(postId) {
|
||||||
const existing = this._identityMap[postId];
|
const existing = this._identityMap[postId];
|
||||||
@ -814,7 +810,7 @@ export default RestModel.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
triggerDeletedPost(postId) {
|
triggerDeletedPost(postId) {
|
||||||
const existing = this._identityMap[postId];
|
const existing = this._identityMap[postId];
|
||||||
@ -832,13 +828,13 @@ export default RestModel.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
}
|
||||||
|
|
||||||
triggerDestroyedPost(postId) {
|
triggerDestroyedPost(postId) {
|
||||||
const existing = this._identityMap[postId];
|
const existing = this._identityMap[postId];
|
||||||
this.removePosts([existing]);
|
this.removePosts([existing]);
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
}
|
||||||
|
|
||||||
triggerChangedPost(postId, updatedAt, opts) {
|
triggerChangedPost(postId, updatedAt, opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
@ -861,7 +857,7 @@ export default RestModel.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
return resolved;
|
return resolved;
|
||||||
},
|
}
|
||||||
|
|
||||||
triggerLikedPost(postId, likesCount, userID, eventType) {
|
triggerLikedPost(postId, likesCount, userID, eventType) {
|
||||||
const resolved = Promise.resolve();
|
const resolved = Promise.resolve();
|
||||||
@ -873,7 +869,7 @@ export default RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return resolved;
|
return resolved;
|
||||||
},
|
}
|
||||||
|
|
||||||
triggerReadPost(postId, readersCount) {
|
triggerReadPost(postId, readersCount) {
|
||||||
const resolved = Promise.resolve();
|
const resolved = Promise.resolve();
|
||||||
@ -886,7 +882,7 @@ export default RestModel.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return resolved;
|
return resolved;
|
||||||
},
|
}
|
||||||
|
|
||||||
triggerChangedTopicStats() {
|
triggerChangedTopicStats() {
|
||||||
if (this.firstPostNotLoaded) {
|
if (this.firstPostNotLoaded) {
|
||||||
@ -897,7 +893,7 @@ export default RestModel.extend({
|
|||||||
const firstPost = this.posts.findBy("post_number", 1);
|
const firstPost = this.posts.findBy("post_number", 1);
|
||||||
return firstPost.id;
|
return firstPost.id;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
postForPostNumber(postNumber) {
|
postForPostNumber(postNumber) {
|
||||||
if (!this.hasPosts) {
|
if (!this.hasPosts) {
|
||||||
@ -907,7 +903,7 @@ export default RestModel.extend({
|
|||||||
return this.posts.find((p) => {
|
return this.posts.find((p) => {
|
||||||
return p.get("post_number") === postNumber;
|
return p.get("post_number") === postNumber;
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns the closest post given a postNumber that may not exist in the stream.
|
Returns the closest post given a postNumber that may not exist in the stream.
|
||||||
@ -935,12 +931,12 @@ export default RestModel.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return closest;
|
return closest;
|
||||||
},
|
}
|
||||||
|
|
||||||
// Get the index of a post in the stream. (Use this for the topic progress bar.)
|
// Get the index of a post in the stream. (Use this for the topic progress bar.)
|
||||||
progressIndexOfPost(post) {
|
progressIndexOfPost(post) {
|
||||||
return this.progressIndexOfPostId(post);
|
return this.progressIndexOfPostId(post);
|
||||||
},
|
}
|
||||||
|
|
||||||
// Get the index in the stream of a post id. (Use this for the topic progress bar.)
|
// Get the index in the stream of a post id. (Use this for the topic progress bar.)
|
||||||
progressIndexOfPostId(post) {
|
progressIndexOfPostId(post) {
|
||||||
@ -952,7 +948,7 @@ export default RestModel.extend({
|
|||||||
const index = this.stream.indexOf(postId);
|
const index = this.stream.indexOf(postId);
|
||||||
return index + 1;
|
return index + 1;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Returns the closest post number given a postNumber that may not exist in the stream.
|
Returns the closest post number given a postNumber that may not exist in the stream.
|
||||||
@ -982,7 +978,7 @@ export default RestModel.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return closest;
|
return closest;
|
||||||
},
|
}
|
||||||
|
|
||||||
closestDaysAgoFor(postNumber) {
|
closestDaysAgoFor(postNumber) {
|
||||||
const timelineLookup = this.timelineLookup || [];
|
const timelineLookup = this.timelineLookup || [];
|
||||||
@ -1007,7 +1003,7 @@ export default RestModel.extend({
|
|||||||
if (val) {
|
if (val) {
|
||||||
return val[1];
|
return val[1];
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
// Find a postId for a postNumber, respecting gaps
|
// Find a postId for a postNumber, respecting gaps
|
||||||
findPostIdForPostNumber(postNumber) {
|
findPostIdForPostNumber(postNumber) {
|
||||||
@ -1037,7 +1033,7 @@ export default RestModel.extend({
|
|||||||
}
|
}
|
||||||
sum++;
|
sum++;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
updateFromJson(postStreamData) {
|
updateFromJson(postStreamData) {
|
||||||
const posts = this.posts;
|
const posts = this.posts;
|
||||||
@ -1057,7 +1053,7 @@ export default RestModel.extend({
|
|||||||
// Update our attributes
|
// Update our attributes
|
||||||
this.setProperties(postStreamData);
|
this.setProperties(postStreamData);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Stores a post in our identity map, and sets up the references it needs to
|
Stores a post in our identity map, and sets up the references it needs to
|
||||||
@ -1094,7 +1090,7 @@ export default RestModel.extend({
|
|||||||
this._identityMap[post.get("id")] = post;
|
this._identityMap[post.get("id")] = post;
|
||||||
}
|
}
|
||||||
return post;
|
return post;
|
||||||
},
|
}
|
||||||
|
|
||||||
fetchNextWindow(postNumber, asc, callback) {
|
fetchNextWindow(postNumber, asc, callback) {
|
||||||
let includeSuggested = !this.get("topic.suggested_topics");
|
let includeSuggested = !this.get("topic.suggested_topics");
|
||||||
@ -1124,7 +1120,7 @@ export default RestModel.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
findPostsByIds(postIds, opts) {
|
findPostsByIds(postIds, opts) {
|
||||||
const identityMap = this._identityMap;
|
const identityMap = this._identityMap;
|
||||||
@ -1134,7 +1130,7 @@ export default RestModel.extend({
|
|||||||
return this.loadIntoIdentityMap(unloaded, opts).then(() => {
|
return this.loadIntoIdentityMap(unloaded, opts).then(() => {
|
||||||
return postIds.map((p) => identityMap[p]).compact();
|
return postIds.map((p) => identityMap[p]).compact();
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
loadIntoIdentityMap(postIds, opts) {
|
loadIntoIdentityMap(postIds, opts) {
|
||||||
if (isEmpty(postIds)) {
|
if (isEmpty(postIds)) {
|
||||||
@ -1164,7 +1160,7 @@ export default RestModel.extend({
|
|||||||
posts.forEach((p) => this.storePost(store.createRecord("post", p)));
|
posts.forEach((p) => this.storePost(store.createRecord("post", p)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
backfillExcerpts(streamPosition) {
|
backfillExcerpts(streamPosition) {
|
||||||
this._excerpts = this._excerpts || [];
|
this._excerpts = this._excerpts || [];
|
||||||
@ -1211,7 +1207,7 @@ export default RestModel.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
return this._excerpts.loading;
|
return this._excerpts.loading;
|
||||||
},
|
}
|
||||||
|
|
||||||
excerpt(streamPosition) {
|
excerpt(streamPosition) {
|
||||||
if (this.isMegaTopic) {
|
if (this.isMegaTopic) {
|
||||||
@ -1234,11 +1230,11 @@ export default RestModel.extend({
|
|||||||
})
|
})
|
||||||
.catch((e) => reject(e));
|
.catch((e) => reject(e));
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
indexOf(post) {
|
indexOf(post) {
|
||||||
return this.stream.indexOf(post.get("id"));
|
return this.stream.indexOf(post.get("id"));
|
||||||
},
|
}
|
||||||
|
|
||||||
// Handles an error loading a topic based on a HTTP status code. Updates
|
// Handles an error loading a topic based on a HTTP status code. Updates
|
||||||
// the text to the correct values.
|
// the text to the correct values.
|
||||||
@ -1259,19 +1255,19 @@ export default RestModel.extend({
|
|||||||
topic.set("errorMessage", I18n.t("topic.server_error.description"));
|
topic.set("errorMessage", I18n.t("topic.server_error.description"));
|
||||||
topic.set("noRetry", error.jqXHR.status === 403);
|
topic.set("noRetry", error.jqXHR.status === 403);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
collapseSummary() {
|
collapseSummary() {
|
||||||
this.topicSummary.collapse();
|
this.topicSummary.collapse();
|
||||||
},
|
}
|
||||||
|
|
||||||
showSummary(currentUser) {
|
showSummary(currentUser) {
|
||||||
this.topicSummary.generateSummary(currentUser, this.get("topic.id"));
|
this.topicSummary.generateSummary(currentUser, this.get("topic.id"));
|
||||||
},
|
}
|
||||||
|
|
||||||
processSummaryUpdate(update) {
|
processSummaryUpdate(update) {
|
||||||
this.topicSummary.processUpdate(update);
|
this.topicSummary.processUpdate(update);
|
||||||
},
|
}
|
||||||
|
|
||||||
_initUserModels(post) {
|
_initUserModels(post) {
|
||||||
post.user = User.create({
|
post.user = User.create({
|
||||||
@ -1286,7 +1282,7 @@ export default RestModel.extend({
|
|||||||
if (post.mentioned_users) {
|
if (post.mentioned_users) {
|
||||||
post.mentioned_users = post.mentioned_users.map((u) => User.create(u));
|
post.mentioned_users = post.mentioned_users.map((u) => User.create(u));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
_checkIfShouldShowRevisions() {
|
_checkIfShouldShowRevisions() {
|
||||||
if (_lastEditNotificationClick) {
|
if (_lastEditNotificationClick) {
|
||||||
@ -1306,7 +1302,7 @@ export default RestModel.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
_setSuggestedTopics(result) {
|
_setSuggestedTopics(result) {
|
||||||
if (!result.suggested_topics) {
|
if (!result.suggested_topics) {
|
||||||
@ -1321,5 +1317,5 @@ export default RestModel.extend({
|
|||||||
if (this.topic.isPrivateMessage) {
|
if (this.topic.isPrivateMessage) {
|
||||||
this.pmTopicTrackingState.startTracking();
|
this.pmTopicTrackingState.startTracking();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
@ -2,8 +2,9 @@ import { computed } from "@ember/object";
|
|||||||
import RestModel from "discourse/models/rest";
|
import RestModel from "discourse/models/rest";
|
||||||
import { getAbsoluteURL } from "discourse-common/lib/get-url";
|
import { getAbsoluteURL } from "discourse-common/lib/get-url";
|
||||||
|
|
||||||
export default RestModel.extend({
|
export default class PublishedPage extends RestModel {
|
||||||
url: computed("slug", function () {
|
@computed("slug")
|
||||||
|
get url() {
|
||||||
return getAbsoluteURL(`/pub/${this.slug}`);
|
return getAbsoluteURL(`/pub/${this.slug}`);
|
||||||
}),
|
}
|
||||||
});
|
}
|
||||||
|
@ -2,24 +2,23 @@ import ArrayProxy from "@ember/array/proxy";
|
|||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default ArrayProxy.extend({
|
export default class ResultSet extends ArrayProxy {
|
||||||
loading: false,
|
loading = false;
|
||||||
loadingMore: false,
|
loadingMore = false;
|
||||||
totalRows: 0,
|
totalRows = 0;
|
||||||
refreshing: false,
|
refreshing = false;
|
||||||
|
content = null;
|
||||||
content: null,
|
loadMoreUrl = null;
|
||||||
loadMoreUrl: null,
|
refreshUrl = null;
|
||||||
refreshUrl: null,
|
findArgs = null;
|
||||||
findArgs: null,
|
store = null;
|
||||||
store: null,
|
resultSetMeta = null;
|
||||||
__type: null,
|
__type = null;
|
||||||
resultSetMeta: null,
|
|
||||||
|
|
||||||
@discourseComputed("totalRows", "length")
|
@discourseComputed("totalRows", "length")
|
||||||
canLoadMore(totalRows, length) {
|
canLoadMore(totalRows, length) {
|
||||||
return length < totalRows;
|
return length < totalRows;
|
||||||
},
|
}
|
||||||
|
|
||||||
loadMore() {
|
loadMore() {
|
||||||
const loadMoreUrl = this.loadMoreUrl;
|
const loadMoreUrl = this.loadMoreUrl;
|
||||||
@ -37,7 +36,7 @@ export default ArrayProxy.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
},
|
}
|
||||||
|
|
||||||
refresh() {
|
refresh() {
|
||||||
if (this.refreshing) {
|
if (this.refreshing) {
|
||||||
@ -53,5 +52,5 @@ export default ArrayProxy.extend({
|
|||||||
return this.store
|
return this.store
|
||||||
.refreshResults(this, this.__type, refreshUrl)
|
.refreshResults(this, this.__type, refreshUrl)
|
||||||
.finally(() => this.set("refreshing", false));
|
.finally(() => this.set("refreshing", false));
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
@ -5,6 +5,6 @@ export const CREATED = 0;
|
|||||||
export const TRANSITIONED_TO = 1;
|
export const TRANSITIONED_TO = 1;
|
||||||
export const EDITED = 2;
|
export const EDITED = 2;
|
||||||
|
|
||||||
export default RestModel.extend({
|
export default class ReviewableHistory extends RestModel {
|
||||||
created: equal("reviewable_history_type", CREATED),
|
@equal("reviewable_history_type", CREATED) created;
|
||||||
});
|
}
|
||||||
|
@ -12,7 +12,13 @@ export const REJECTED = 2;
|
|||||||
export const IGNORED = 3;
|
export const IGNORED = 3;
|
||||||
export const DELETED = 4;
|
export const DELETED = 4;
|
||||||
|
|
||||||
const Reviewable = RestModel.extend({
|
export default class Reviewable extends RestModel {
|
||||||
|
static munge(json) {
|
||||||
|
// ensure we are not overriding category computed property
|
||||||
|
delete json.category;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
@discourseComputed("type", "topic")
|
@discourseComputed("type", "topic")
|
||||||
resolvedType(type, topic) {
|
resolvedType(type, topic) {
|
||||||
// Display "Queued Topic" if the post will create a topic
|
// Display "Queued Topic" if the post will create a topic
|
||||||
@ -21,26 +27,26 @@ const Reviewable = RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return type;
|
return type;
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("resolvedType")
|
@discourseComputed("resolvedType")
|
||||||
humanType(resolvedType) {
|
humanType(resolvedType) {
|
||||||
return I18n.t(`review.types.${underscore(resolvedType)}.title`, {
|
return I18n.t(`review.types.${underscore(resolvedType)}.title`, {
|
||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("humanType")
|
@discourseComputed("humanType")
|
||||||
humanTypeCssClass(humanType) {
|
humanTypeCssClass(humanType) {
|
||||||
return "-" + dasherize(humanType);
|
return "-" + dasherize(humanType);
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("resolvedType")
|
@discourseComputed("resolvedType")
|
||||||
humanNoun(resolvedType) {
|
humanNoun(resolvedType) {
|
||||||
return I18n.t(`review.types.${underscore(resolvedType)}.noun`, {
|
return I18n.t(`review.types.${underscore(resolvedType)}.noun`, {
|
||||||
defaultValue: "reviewable",
|
defaultValue: "reviewable",
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("humanNoun")
|
@discourseComputed("humanNoun")
|
||||||
flaggedReviewableContextQuestion(humanNoun) {
|
flaggedReviewableContextQuestion(humanNoun) {
|
||||||
@ -66,12 +72,12 @@ const Reviewable = RestModel.extend({
|
|||||||
reviewable_human_score_types: listOfQuestions,
|
reviewable_human_score_types: listOfQuestions,
|
||||||
reviewable_type: humanNoun,
|
reviewable_type: humanNoun,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("category_id")
|
@discourseComputed("category_id")
|
||||||
category() {
|
category() {
|
||||||
return Category.findById(this.category_id);
|
return Category.findById(this.category_id);
|
||||||
},
|
}
|
||||||
|
|
||||||
update(updates) {
|
update(updates) {
|
||||||
// If no changes, do nothing
|
// If no changes, do nothing
|
||||||
@ -92,15 +98,5 @@ const Reviewable = RestModel.extend({
|
|||||||
|
|
||||||
this.setProperties(updated);
|
this.setProperties(updated);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
Reviewable.reopenClass({
|
|
||||||
munge(json) {
|
|
||||||
// ensure we are not overriding category computed property
|
|
||||||
delete json.category;
|
|
||||||
return json;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default Reviewable;
|
|
||||||
|
@ -3,14 +3,10 @@ import RestModel from "discourse/models/rest";
|
|||||||
|
|
||||||
// A data model representing current session data. You can put transient
|
// A data model representing current session data. You can put transient
|
||||||
// data here you might want later. It is not stored or serialized anywhere.
|
// data here you might want later. It is not stored or serialized anywhere.
|
||||||
const Session = RestModel.extend({
|
export default class Session extends RestModel.extend().reopenClass(Singleton) {
|
||||||
hasFocus: null,
|
hasFocus = null;
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.set("highestSeenByTopic", {});
|
this.set("highestSeenByTopic", {});
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
Session.reopenClass(Singleton);
|
|
||||||
|
|
||||||
export default Session;
|
|
||||||
|
@ -13,149 +13,17 @@ import deprecated from "discourse-common/lib/deprecated";
|
|||||||
import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
|
import { getOwnerWithFallback } from "discourse-common/lib/get-owner";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
const Site = RestModel.extend({
|
export default class Site extends RestModel.extend().reopenClass(Singleton) {
|
||||||
isReadOnly: alias("is_readonly"),
|
static createCurrent() {
|
||||||
|
|
||||||
init() {
|
|
||||||
this._super(...arguments);
|
|
||||||
|
|
||||||
this.topicCountDesc = ["topic_count:desc"];
|
|
||||||
this.categories = this.categories || [];
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("notification_types")
|
|
||||||
notificationLookup(notificationTypes) {
|
|
||||||
const result = [];
|
|
||||||
Object.keys(notificationTypes).forEach(
|
|
||||||
(k) => (result[notificationTypes[k]] = k)
|
|
||||||
);
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("post_action_types.[]")
|
|
||||||
flagTypes() {
|
|
||||||
const postActionTypes = this.post_action_types;
|
|
||||||
if (!postActionTypes) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return postActionTypes.filterBy("is_flag", true);
|
|
||||||
},
|
|
||||||
|
|
||||||
categoriesByCount: sort("categories", "topicCountDesc"),
|
|
||||||
|
|
||||||
collectUserFields(fields) {
|
|
||||||
fields = fields || {};
|
|
||||||
|
|
||||||
let siteFields = this.user_fields;
|
|
||||||
|
|
||||||
if (!isEmpty(siteFields)) {
|
|
||||||
return siteFields.map((f) => {
|
|
||||||
let value = fields ? fields[f.id.toString()] : null;
|
|
||||||
value = value || htmlSafe("—");
|
|
||||||
return { name: f.name, value };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
|
|
||||||
// Sort subcategories under parents
|
|
||||||
@discourseComputed("categoriesByCount", "categories.[]")
|
|
||||||
sortedCategories(categories) {
|
|
||||||
return Category.sortCategories(categories);
|
|
||||||
},
|
|
||||||
|
|
||||||
// Returns it in the correct order, by setting
|
|
||||||
@discourseComputed("categories.[]")
|
|
||||||
categoriesList(categories) {
|
|
||||||
return this.siteSettings.fixed_category_positions
|
|
||||||
? categories
|
|
||||||
: this.sortedCategories;
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("categories.[]", "categories.@each.notification_level")
|
|
||||||
trackedCategoriesList(categories) {
|
|
||||||
const trackedCategories = [];
|
|
||||||
|
|
||||||
for (const category of categories) {
|
|
||||||
if (category.isTracked) {
|
|
||||||
if (
|
|
||||||
this.siteSettings.allow_uncategorized_topics ||
|
|
||||||
!category.isUncategorizedCategory
|
|
||||||
) {
|
|
||||||
trackedCategories.push(category);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return trackedCategories;
|
|
||||||
},
|
|
||||||
|
|
||||||
postActionTypeById(id) {
|
|
||||||
return this.get("postActionByIdLookup.action" + id);
|
|
||||||
},
|
|
||||||
|
|
||||||
topicFlagTypeById(id) {
|
|
||||||
return this.get("topicFlagByIdLookup.action" + id);
|
|
||||||
},
|
|
||||||
|
|
||||||
removeCategory(id) {
|
|
||||||
const categories = this.categories;
|
|
||||||
const existingCategory = categories.findBy("id", id);
|
|
||||||
if (existingCategory) {
|
|
||||||
categories.removeObject(existingCategory);
|
|
||||||
delete this.categoriesById.categoryId;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateCategory(newCategory) {
|
|
||||||
const categories = this.categories;
|
|
||||||
const categoryId = get(newCategory, "id");
|
|
||||||
const existingCategory = categories.findBy("id", categoryId);
|
|
||||||
|
|
||||||
// Don't update null permissions
|
|
||||||
if (newCategory.permission === null) {
|
|
||||||
delete newCategory.permission;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (existingCategory) {
|
|
||||||
existingCategory.setProperties(newCategory);
|
|
||||||
return existingCategory;
|
|
||||||
} else {
|
|
||||||
// TODO insert in right order?
|
|
||||||
newCategory = this.store.createRecord("category", newCategory);
|
|
||||||
categories.pushObject(newCategory);
|
|
||||||
this.categoriesById[categoryId] = newCategory;
|
|
||||||
newCategory.set(
|
|
||||||
"parentCategory",
|
|
||||||
this.categoriesById[newCategory.parent_category_id]
|
|
||||||
);
|
|
||||||
newCategory.set(
|
|
||||||
"subcategories",
|
|
||||||
this.categories.filterBy("parent_category_id", categoryId)
|
|
||||||
);
|
|
||||||
if (newCategory.parentCategory) {
|
|
||||||
if (!newCategory.parentCategory.subcategories) {
|
|
||||||
newCategory.parentCategory.set("subcategories", []);
|
|
||||||
}
|
|
||||||
newCategory.parentCategory.subcategories.pushObject(newCategory);
|
|
||||||
}
|
|
||||||
return newCategory;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
Site.reopenClass(Singleton, {
|
|
||||||
// The current singleton will retrieve its attributes from the `PreloadStore`.
|
|
||||||
createCurrent() {
|
|
||||||
const store = getOwnerWithFallback(this).lookup("service:store");
|
const store = getOwnerWithFallback(this).lookup("service:store");
|
||||||
const siteAttributes = PreloadStore.get("site");
|
const siteAttributes = PreloadStore.get("site");
|
||||||
siteAttributes["isReadOnly"] = PreloadStore.get("isReadOnly");
|
siteAttributes["isReadOnly"] = PreloadStore.get("isReadOnly");
|
||||||
siteAttributes["isStaffWritesOnly"] = PreloadStore.get("isStaffWritesOnly");
|
siteAttributes["isStaffWritesOnly"] = PreloadStore.get("isStaffWritesOnly");
|
||||||
return store.createRecord("site", siteAttributes);
|
return store.createRecord("site", siteAttributes);
|
||||||
},
|
}
|
||||||
|
|
||||||
create() {
|
static create() {
|
||||||
const result = this._super.apply(this, arguments);
|
const result = super.create.apply(this, arguments);
|
||||||
const store = result.store;
|
const store = result.store;
|
||||||
|
|
||||||
if (result.categories) {
|
if (result.categories) {
|
||||||
@ -233,8 +101,136 @@ Site.reopenClass(Singleton, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
@alias("is_readonly") isReadOnly;
|
||||||
|
|
||||||
|
@sort("categories", "topicCountDesc") categoriesByCount;
|
||||||
|
init() {
|
||||||
|
super.init(...arguments);
|
||||||
|
|
||||||
|
this.topicCountDesc = ["topic_count:desc"];
|
||||||
|
this.categories = this.categories || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("notification_types")
|
||||||
|
notificationLookup(notificationTypes) {
|
||||||
|
const result = [];
|
||||||
|
Object.keys(notificationTypes).forEach(
|
||||||
|
(k) => (result[notificationTypes[k]] = k)
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("post_action_types.[]")
|
||||||
|
flagTypes() {
|
||||||
|
const postActionTypes = this.post_action_types;
|
||||||
|
if (!postActionTypes) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return postActionTypes.filterBy("is_flag", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
collectUserFields(fields) {
|
||||||
|
fields = fields || {};
|
||||||
|
|
||||||
|
let siteFields = this.user_fields;
|
||||||
|
|
||||||
|
if (!isEmpty(siteFields)) {
|
||||||
|
return siteFields.map((f) => {
|
||||||
|
let value = fields ? fields[f.id.toString()] : null;
|
||||||
|
value = value || htmlSafe("—");
|
||||||
|
return { name: f.name, value };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort subcategories under parents
|
||||||
|
@discourseComputed("categoriesByCount", "categories.[]")
|
||||||
|
sortedCategories(categories) {
|
||||||
|
return Category.sortCategories(categories);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns it in the correct order, by setting
|
||||||
|
@discourseComputed("categories.[]")
|
||||||
|
categoriesList(categories) {
|
||||||
|
return this.siteSettings.fixed_category_positions
|
||||||
|
? categories
|
||||||
|
: this.sortedCategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("categories.[]", "categories.@each.notification_level")
|
||||||
|
trackedCategoriesList(categories) {
|
||||||
|
const trackedCategories = [];
|
||||||
|
|
||||||
|
for (const category of categories) {
|
||||||
|
if (category.isTracked) {
|
||||||
|
if (
|
||||||
|
this.siteSettings.allow_uncategorized_topics ||
|
||||||
|
!category.isUncategorizedCategory
|
||||||
|
) {
|
||||||
|
trackedCategories.push(category);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return trackedCategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
postActionTypeById(id) {
|
||||||
|
return this.get("postActionByIdLookup.action" + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
topicFlagTypeById(id) {
|
||||||
|
return this.get("topicFlagByIdLookup.action" + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeCategory(id) {
|
||||||
|
const categories = this.categories;
|
||||||
|
const existingCategory = categories.findBy("id", id);
|
||||||
|
if (existingCategory) {
|
||||||
|
categories.removeObject(existingCategory);
|
||||||
|
delete this.categoriesById.categoryId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCategory(newCategory) {
|
||||||
|
const categories = this.categories;
|
||||||
|
const categoryId = get(newCategory, "id");
|
||||||
|
const existingCategory = categories.findBy("id", categoryId);
|
||||||
|
|
||||||
|
// Don't update null permissions
|
||||||
|
if (newCategory.permission === null) {
|
||||||
|
delete newCategory.permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingCategory) {
|
||||||
|
existingCategory.setProperties(newCategory);
|
||||||
|
return existingCategory;
|
||||||
|
} else {
|
||||||
|
// TODO insert in right order?
|
||||||
|
newCategory = this.store.createRecord("category", newCategory);
|
||||||
|
categories.pushObject(newCategory);
|
||||||
|
this.categoriesById[categoryId] = newCategory;
|
||||||
|
newCategory.set(
|
||||||
|
"parentCategory",
|
||||||
|
this.categoriesById[newCategory.parent_category_id]
|
||||||
|
);
|
||||||
|
newCategory.set(
|
||||||
|
"subcategories",
|
||||||
|
this.categories.filterBy("parent_category_id", categoryId)
|
||||||
|
);
|
||||||
|
if (newCategory.parentCategory) {
|
||||||
|
if (!newCategory.parentCategory.subcategories) {
|
||||||
|
newCategory.parentCategory.set("subcategories", []);
|
||||||
|
}
|
||||||
|
newCategory.parentCategory.subcategories.pushObject(newCategory);
|
||||||
|
}
|
||||||
|
return newCategory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof Discourse !== "undefined") {
|
if (typeof Discourse !== "undefined") {
|
||||||
let warned = false;
|
let warned = false;
|
||||||
@ -252,5 +248,3 @@ if (typeof Discourse !== "undefined") {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Site;
|
|
||||||
|
@ -3,10 +3,8 @@ import $ from "jquery";
|
|||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
|
|
||||||
const StaticPage = EmberObject.extend();
|
export default class StaticPage extends EmberObject {
|
||||||
|
static find(path) {
|
||||||
StaticPage.reopenClass({
|
|
||||||
find(path) {
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// Models shouldn't really be doing Ajax request, but this is a huge speed boost if we
|
// Models shouldn't really be doing Ajax request, but this is a huge speed boost if we
|
||||||
// preload content.
|
// preload content.
|
||||||
@ -23,7 +21,5 @@ StaticPage.reopenClass({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
export default StaticPage;
|
|
||||||
|
@ -2,7 +2,7 @@ import PermissionType from "discourse/models/permission-type";
|
|||||||
import RestModel from "discourse/models/rest";
|
import RestModel from "discourse/models/rest";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default RestModel.extend({
|
export default class TagGroup extends RestModel {
|
||||||
@discourseComputed("permissions")
|
@discourseComputed("permissions")
|
||||||
permissionName(permissions) {
|
permissionName(permissions) {
|
||||||
if (!permissions) {
|
if (!permissions) {
|
||||||
@ -16,5 +16,5 @@ export default RestModel.extend({
|
|||||||
} else {
|
} else {
|
||||||
return "private";
|
return "private";
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
@ -2,16 +2,16 @@ import { readOnly } from "@ember/object/computed";
|
|||||||
import RestModel from "discourse/models/rest";
|
import RestModel from "discourse/models/rest";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default RestModel.extend({
|
export default class Tag extends RestModel {
|
||||||
pmOnly: readOnly("pm_only"),
|
@readOnly("pm_only") pmOnly;
|
||||||
|
|
||||||
@discourseComputed("count", "pm_count")
|
@discourseComputed("count", "pm_count")
|
||||||
totalCount(count, pmCount) {
|
totalCount(count, pmCount) {
|
||||||
return pmCount ? count + pmCount : count;
|
return pmCount ? count + pmCount : count;
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("id")
|
@discourseComputed("id")
|
||||||
searchContext(id) {
|
searchContext(id) {
|
||||||
return { type: "tag", id, tag: this, name: id };
|
return { type: "tag", id, tag: this, name: id };
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
@ -8,10 +8,10 @@ import RestModel from "discourse/models/rest";
|
|||||||
When showing topics in lists and such this information should not be required.
|
When showing topics in lists and such this information should not be required.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
const TopicDetails = RestModel.extend({
|
export default class TopicDetails extends RestModel {
|
||||||
store: service(),
|
@service store;
|
||||||
|
|
||||||
loaded: false,
|
loaded = false;
|
||||||
|
|
||||||
updateFromJson(details) {
|
updateFromJson(details) {
|
||||||
const topic = this.topic;
|
const topic = this.topic;
|
||||||
@ -31,7 +31,7 @@ const TopicDetails = RestModel.extend({
|
|||||||
|
|
||||||
this.setProperties(details);
|
this.setProperties(details);
|
||||||
this.set("loaded", true);
|
this.set("loaded", true);
|
||||||
},
|
}
|
||||||
|
|
||||||
updateNotifications(level) {
|
updateNotifications(level) {
|
||||||
return ajax(`/t/${this.get("topic.id")}/notifications`, {
|
return ajax(`/t/${this.get("topic.id")}/notifications`, {
|
||||||
@ -43,7 +43,7 @@ const TopicDetails = RestModel.extend({
|
|||||||
notifications_reason_id: null,
|
notifications_reason_id: null,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
removeAllowedGroup(group) {
|
removeAllowedGroup(group) {
|
||||||
const groups = this.allowed_groups;
|
const groups = this.allowed_groups;
|
||||||
@ -55,7 +55,7 @@ const TopicDetails = RestModel.extend({
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
groups.removeObject(groups.findBy("name", name));
|
groups.removeObject(groups.findBy("name", name));
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
removeAllowedUser(user) {
|
removeAllowedUser(user) {
|
||||||
const users = this.allowed_users;
|
const users = this.allowed_users;
|
||||||
@ -67,7 +67,5 @@ const TopicDetails = RestModel.extend({
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
users.removeObject(users.findBy("username", username));
|
users.removeObject(users.findBy("username", username));
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
export default TopicDetails;
|
|
||||||
|
@ -40,9 +40,88 @@ function displayCategoryInList(site, category) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TopicList = RestModel.extend({
|
export default class TopicList extends RestModel {
|
||||||
session: service(),
|
static topicsFrom(store, result, opts) {
|
||||||
canLoadMore: notEmpty("more_topics_url"),
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
opts = opts || {};
|
||||||
|
let listKey = opts.listKey || "topics";
|
||||||
|
|
||||||
|
// Stitch together our side loaded data
|
||||||
|
|
||||||
|
const users = extractByKey(result.users, User);
|
||||||
|
const groups = extractByKey(result.primary_groups, EmberObject);
|
||||||
|
|
||||||
|
if (result.topic_list.categories) {
|
||||||
|
result.topic_list.categories.forEach((c) => {
|
||||||
|
Site.current().updateCategory(c);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.topic_list[listKey].map((t) => {
|
||||||
|
t.posters.forEach((p) => {
|
||||||
|
p.user = users[p.user_id];
|
||||||
|
p.extraClasses = p.extras;
|
||||||
|
if (p.primary_group_id) {
|
||||||
|
p.primary_group = groups[p.primary_group_id];
|
||||||
|
if (p.primary_group) {
|
||||||
|
p.extraClasses = `${p.extraClasses || ""} group-${
|
||||||
|
p.primary_group.name
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (t.participants) {
|
||||||
|
t.participants.forEach((p) => (p.user = users[p.user_id]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return store.createRecord("topic", t);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static munge(json, store) {
|
||||||
|
json.inserted = json.inserted || [];
|
||||||
|
json.can_create_topic = json.topic_list.can_create_topic;
|
||||||
|
json.more_topics_url = json.topic_list.more_topics_url;
|
||||||
|
json.for_period = json.topic_list.for_period;
|
||||||
|
json.loaded = true;
|
||||||
|
json.per_page = json.topic_list.per_page;
|
||||||
|
json.topics = this.topicsFrom(store, json);
|
||||||
|
|
||||||
|
if (json.topic_list.shared_drafts) {
|
||||||
|
json.sharedDrafts = this.topicsFrom(store, json, {
|
||||||
|
listKey: "shared_drafts",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
static find(filter, params) {
|
||||||
|
deprecated(
|
||||||
|
`TopicList.find is deprecated. Use \`findFiltered("topicList")\` on the \`store\` service instead.`,
|
||||||
|
{
|
||||||
|
id: "topic-list-find",
|
||||||
|
since: "3.1.0.beta5",
|
||||||
|
dropFrom: "3.2.0.beta1",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const store = getOwnerWithFallback(this).lookup("service:store");
|
||||||
|
return store.findFiltered("topicList", { filter, params });
|
||||||
|
}
|
||||||
|
|
||||||
|
// hide the category when it has no children
|
||||||
|
static hideUniformCategory(list, category) {
|
||||||
|
list.set("hideCategory", !displayCategoryInList(list.site, category));
|
||||||
|
}
|
||||||
|
|
||||||
|
@service session;
|
||||||
|
|
||||||
|
@notEmpty("more_topics_url") canLoadMore;
|
||||||
|
|
||||||
forEachNew(topics, callback) {
|
forEachNew(topics, callback) {
|
||||||
const topicIds = new Set();
|
const topicIds = new Set();
|
||||||
@ -53,7 +132,7 @@ const TopicList = RestModel.extend({
|
|||||||
callback(topic);
|
callback(topic);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
updateSortParams(order, ascending) {
|
updateSortParams(order, ascending) {
|
||||||
let params = { ...(this.params || {}) };
|
let params = { ...(this.params || {}) };
|
||||||
@ -67,7 +146,7 @@ const TopicList = RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.set("params", params);
|
this.set("params", params);
|
||||||
},
|
}
|
||||||
|
|
||||||
updateNewListSubsetParam(subset) {
|
updateNewListSubsetParam(subset) {
|
||||||
let params = { ...(this.params || {}) };
|
let params = { ...(this.params || {}) };
|
||||||
@ -79,7 +158,7 @@ const TopicList = RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.set("params", params);
|
this.set("params", params);
|
||||||
},
|
}
|
||||||
|
|
||||||
loadMore() {
|
loadMore() {
|
||||||
if (this.loadingMore) {
|
if (this.loadingMore) {
|
||||||
@ -128,7 +207,7 @@ const TopicList = RestModel.extend({
|
|||||||
// Return a promise indicating no more results
|
// Return a promise indicating no more results
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
// loads topics with these ids "before" the current topics
|
// loads topics with these ids "before" the current topics
|
||||||
loadBefore(topic_ids, storeInSession) {
|
loadBefore(topic_ids, storeInSession) {
|
||||||
@ -152,87 +231,5 @@ const TopicList = RestModel.extend({
|
|||||||
this.session.set("topicList", this);
|
this.session.set("topicList", this);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
TopicList.reopenClass({
|
|
||||||
topicsFrom(store, result, opts) {
|
|
||||||
if (!result) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
opts = opts || {};
|
|
||||||
let listKey = opts.listKey || "topics";
|
|
||||||
|
|
||||||
// Stitch together our side loaded data
|
|
||||||
|
|
||||||
const users = extractByKey(result.users, User);
|
|
||||||
const groups = extractByKey(result.primary_groups, EmberObject);
|
|
||||||
|
|
||||||
if (result.topic_list.categories) {
|
|
||||||
result.topic_list.categories.forEach((c) => {
|
|
||||||
Site.current().updateCategory(c);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.topic_list[listKey].map((t) => {
|
|
||||||
t.posters.forEach((p) => {
|
|
||||||
p.user = users[p.user_id];
|
|
||||||
p.extraClasses = p.extras;
|
|
||||||
if (p.primary_group_id) {
|
|
||||||
p.primary_group = groups[p.primary_group_id];
|
|
||||||
if (p.primary_group) {
|
|
||||||
p.extraClasses = `${p.extraClasses || ""} group-${
|
|
||||||
p.primary_group.name
|
|
||||||
}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (t.participants) {
|
|
||||||
t.participants.forEach((p) => (p.user = users[p.user_id]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return store.createRecord("topic", t);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
munge(json, store) {
|
|
||||||
json.inserted = json.inserted || [];
|
|
||||||
json.can_create_topic = json.topic_list.can_create_topic;
|
|
||||||
json.more_topics_url = json.topic_list.more_topics_url;
|
|
||||||
json.for_period = json.topic_list.for_period;
|
|
||||||
json.loaded = true;
|
|
||||||
json.per_page = json.topic_list.per_page;
|
|
||||||
json.topics = this.topicsFrom(store, json);
|
|
||||||
|
|
||||||
if (json.topic_list.shared_drafts) {
|
|
||||||
json.sharedDrafts = this.topicsFrom(store, json, {
|
|
||||||
listKey: "shared_drafts",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return json;
|
|
||||||
},
|
|
||||||
|
|
||||||
find(filter, params) {
|
|
||||||
deprecated(
|
|
||||||
`TopicList.find is deprecated. Use \`findFiltered("topicList")\` on the \`store\` service instead.`,
|
|
||||||
{
|
|
||||||
id: "topic-list-find",
|
|
||||||
since: "3.1.0.beta5",
|
|
||||||
dropFrom: "3.2.0.beta1",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const store = getOwnerWithFallback(this).lookup("service:store");
|
|
||||||
return store.findFiltered("topicList", { filter, params });
|
|
||||||
},
|
|
||||||
|
|
||||||
// hide the category when it has no children
|
|
||||||
hideUniformCategory(list, category) {
|
|
||||||
list.set("hideCategory", !displayCategoryInList(list.site, category));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default TopicList;
|
|
||||||
|
@ -1,10 +1,8 @@
|
|||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import RestModel from "discourse/models/rest";
|
import RestModel from "discourse/models/rest";
|
||||||
|
|
||||||
const TopicTimer = RestModel.extend({});
|
export default class TopicTimer extends RestModel {
|
||||||
|
static update(
|
||||||
TopicTimer.reopenClass({
|
|
||||||
update(
|
|
||||||
topicId,
|
topicId,
|
||||||
time,
|
time,
|
||||||
basedOnLastPost,
|
basedOnLastPost,
|
||||||
@ -32,7 +30,5 @@ TopicTimer.reopenClass({
|
|||||||
type: "POST",
|
type: "POST",
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
export default TopicTimer;
|
|
||||||
|
@ -47,19 +47,19 @@ function hasMutedTags(topicTags, mutedTags, siteSettings) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TopicTrackingState = EmberObject.extend({
|
export default class TopicTrackingState extends EmberObject {
|
||||||
messageCount: 0,
|
messageCount = 0;
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
super.init(...arguments);
|
||||||
|
|
||||||
this.states = new Map();
|
this.states = new Map();
|
||||||
this.stateChangeCallbacks = {};
|
this.stateChangeCallbacks = {};
|
||||||
this._trackedTopicLimit = 4000;
|
this._trackedTopicLimit = 4000;
|
||||||
},
|
}
|
||||||
|
|
||||||
willDestroy() {
|
willDestroy() {
|
||||||
this._super(...arguments);
|
super.willDestroy(...arguments);
|
||||||
|
|
||||||
this.messageBus.unsubscribe("/latest", this._processChannelPayload);
|
this.messageBus.unsubscribe("/latest", this._processChannelPayload);
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
this.messageBus.unsubscribe("/delete", this.onDeleteMessage);
|
this.messageBus.unsubscribe("/delete", this.onDeleteMessage);
|
||||||
this.messageBus.unsubscribe("/recover", this.onRecoverMessage);
|
this.messageBus.unsubscribe("/recover", this.onRecoverMessage);
|
||||||
this.messageBus.unsubscribe("/destroy", this.onDestroyMessage);
|
this.messageBus.unsubscribe("/destroy", this.onDestroyMessage);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subscribe to MessageBus channels which are used for publishing changes
|
* Subscribe to MessageBus channels which are used for publishing changes
|
||||||
@ -134,19 +134,19 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
this.onDestroyMessage,
|
this.onDestroyMessage,
|
||||||
meta["/destroy"] ?? messageBusDefaultNewMessageId
|
meta["/destroy"] ?? messageBusDefaultNewMessageId
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
onDeleteMessage(msg) {
|
onDeleteMessage(msg) {
|
||||||
this.modifyStateProp(msg, "deleted", true);
|
this.modifyStateProp(msg, "deleted", true);
|
||||||
this.incrementMessageCount();
|
this.incrementMessageCount();
|
||||||
},
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
onRecoverMessage(msg) {
|
onRecoverMessage(msg) {
|
||||||
this.modifyStateProp(msg, "deleted", false);
|
this.modifyStateProp(msg, "deleted", false);
|
||||||
this.incrementMessageCount();
|
this.incrementMessageCount();
|
||||||
},
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
onDestroyMessage(msg) {
|
onDestroyMessage(msg) {
|
||||||
@ -159,15 +159,15 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
) {
|
) {
|
||||||
DiscourseURL.redirectTo("/");
|
DiscourseURL.redirectTo("/");
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
mutedTopics() {
|
mutedTopics() {
|
||||||
return (this.currentUser && this.currentUser.muted_topics) || [];
|
return (this.currentUser && this.currentUser.muted_topics) || [];
|
||||||
},
|
}
|
||||||
|
|
||||||
unmutedTopics() {
|
unmutedTopics() {
|
||||||
return (this.currentUser && this.currentUser.unmuted_topics) || [];
|
return (this.currentUser && this.currentUser.unmuted_topics) || [];
|
||||||
},
|
}
|
||||||
|
|
||||||
trackMutedOrUnmutedTopic(data) {
|
trackMutedOrUnmutedTopic(data) {
|
||||||
let topics, key;
|
let topics, key;
|
||||||
@ -183,7 +183,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
});
|
});
|
||||||
this.currentUser && this.currentUser.set(key, topics);
|
this.currentUser && this.currentUser.set(key, topics);
|
||||||
},
|
}
|
||||||
|
|
||||||
pruneOldMutedAndUnmutedTopics() {
|
pruneOldMutedAndUnmutedTopics() {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@ -196,15 +196,15 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
this.currentUser &&
|
this.currentUser &&
|
||||||
this.currentUser.set("muted_topics", mutedTopics) &&
|
this.currentUser.set("muted_topics", mutedTopics) &&
|
||||||
this.currentUser.set("unmuted_topics", unmutedTopics);
|
this.currentUser.set("unmuted_topics", unmutedTopics);
|
||||||
},
|
}
|
||||||
|
|
||||||
isMutedTopic(topicId) {
|
isMutedTopic(topicId) {
|
||||||
return !!this.mutedTopics().findBy("topicId", topicId);
|
return !!this.mutedTopics().findBy("topicId", topicId);
|
||||||
},
|
}
|
||||||
|
|
||||||
isUnmutedTopic(topicId) {
|
isUnmutedTopic(topicId) {
|
||||||
return !!this.unmutedTopics().findBy("topicId", topicId);
|
return !!this.unmutedTopics().findBy("topicId", topicId);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the topic's last_read_post_number to the highestSeen post
|
* Updates the topic's last_read_post_number to the highestSeen post
|
||||||
@ -233,7 +233,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
this.modifyStateProp(topicId, "last_read_post_number", highestSeen);
|
this.modifyStateProp(topicId, "last_read_post_number", highestSeen);
|
||||||
this.incrementMessageCount();
|
this.incrementMessageCount();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to count incoming topics which will be displayed in a message
|
* Used to count incoming topics which will be displayed in a message
|
||||||
@ -321,7 +321,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
|
|
||||||
// hasIncoming relies on this count
|
// hasIncoming relies on this count
|
||||||
this.set("incomingCount", this.newIncoming.length);
|
this.set("incomingCount", this.newIncoming.length);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resets the number of incoming topics to 0 and flushes the new topics
|
* Resets the number of incoming topics to 0 and flushes the new topics
|
||||||
@ -333,7 +333,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
resetTracking() {
|
resetTracking() {
|
||||||
this.newIncoming = [];
|
this.newIncoming = [];
|
||||||
this.set("incomingCount", 0);
|
this.set("incomingCount", 0);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Track how many new topics came for the specified filter.
|
* Track how many new topics came for the specified filter.
|
||||||
@ -375,7 +375,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
this.set("filterTag", tag);
|
this.set("filterTag", tag);
|
||||||
this.set("filter", filter);
|
this.set("filter", filter);
|
||||||
this.set("incomingCount", 0);
|
this.set("incomingCount", 0);
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to determine whether to show the message at the top of the topic list
|
* Used to determine whether to show the message at the top of the topic list
|
||||||
@ -386,7 +386,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
@discourseComputed("incomingCount")
|
@discourseComputed("incomingCount")
|
||||||
hasIncoming(incomingCount) {
|
hasIncoming(incomingCount) {
|
||||||
return incomingCount && incomingCount > 0;
|
return incomingCount && incomingCount > 0;
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the topic ID provided from the tracker state.
|
* Removes the topic ID provided from the tracker state.
|
||||||
@ -400,7 +400,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
if (this.states.delete(this._stateKey(topicId))) {
|
if (this.states.delete(this._stateKey(topicId))) {
|
||||||
this._afterStateChange();
|
this._afterStateChange();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes multiple topics from the state at once, and increments
|
* Removes multiple topics from the state at once, and increments
|
||||||
@ -415,7 +415,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
topicIds.forEach((topicId) => this.removeTopic(topicId));
|
topicIds.forEach((topicId) => this.removeTopic(topicId));
|
||||||
this.incrementMessageCount();
|
this.incrementMessageCount();
|
||||||
this._afterStateChange();
|
this._afterStateChange();
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If we have a cached topic list, we can update it from our tracking information
|
* If we have a cached topic list, we can update it from our tracking information
|
||||||
@ -466,7 +466,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses the provided topic list to apply changes to the in-memory topic
|
* Uses the provided topic list to apply changes to the in-memory topic
|
||||||
@ -509,25 +509,25 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.incrementMessageCount();
|
this.incrementMessageCount();
|
||||||
},
|
}
|
||||||
|
|
||||||
incrementMessageCount() {
|
incrementMessageCount() {
|
||||||
this.incrementProperty("messageCount");
|
this.incrementProperty("messageCount");
|
||||||
},
|
}
|
||||||
|
|
||||||
_generateCallbackId() {
|
_generateCallbackId() {
|
||||||
return Math.random().toString(12).slice(2, 11);
|
return Math.random().toString(12).slice(2, 11);
|
||||||
},
|
}
|
||||||
|
|
||||||
onStateChange(cb) {
|
onStateChange(cb) {
|
||||||
let callbackId = this._generateCallbackId();
|
let callbackId = this._generateCallbackId();
|
||||||
this.stateChangeCallbacks[callbackId] = cb;
|
this.stateChangeCallbacks[callbackId] = cb;
|
||||||
return callbackId;
|
return callbackId;
|
||||||
},
|
}
|
||||||
|
|
||||||
offStateChange(callbackId) {
|
offStateChange(callbackId) {
|
||||||
delete this.stateChangeCallbacks[callbackId];
|
delete this.stateChangeCallbacks[callbackId];
|
||||||
},
|
}
|
||||||
|
|
||||||
getSubCategoryIds(categoryId) {
|
getSubCategoryIds(categoryId) {
|
||||||
const result = [categoryId];
|
const result = [categoryId];
|
||||||
@ -542,7 +542,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Set(result);
|
return new Set(result);
|
||||||
},
|
}
|
||||||
|
|
||||||
countCategoryByState({
|
countCategoryByState({
|
||||||
type,
|
type,
|
||||||
@ -606,7 +606,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}).length;
|
}).length;
|
||||||
},
|
}
|
||||||
|
|
||||||
countNew({ categoryId, tagId, noSubcategories, customFilterFn } = {}) {
|
countNew({ categoryId, tagId, noSubcategories, customFilterFn } = {}) {
|
||||||
return this.countCategoryByState({
|
return this.countCategoryByState({
|
||||||
@ -616,7 +616,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
noSubcategories,
|
noSubcategories,
|
||||||
customFilterFn,
|
customFilterFn,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
countUnread({ categoryId, tagId, noSubcategories, customFilterFn } = {}) {
|
countUnread({ categoryId, tagId, noSubcategories, customFilterFn } = {}) {
|
||||||
return this.countCategoryByState({
|
return this.countCategoryByState({
|
||||||
@ -626,7 +626,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
noSubcategories,
|
noSubcategories,
|
||||||
customFilterFn,
|
customFilterFn,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
countNewAndUnread({
|
countNewAndUnread({
|
||||||
categoryId,
|
categoryId,
|
||||||
@ -641,7 +641,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
noSubcategories,
|
noSubcategories,
|
||||||
customFilterFn,
|
customFilterFn,
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the provided callback for each of the currently tracked topics
|
* Calls the provided callback for each of the currently tracked topics
|
||||||
@ -657,7 +657,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
this._trackedTopics(opts).forEach((trackedTopic) => {
|
this._trackedTopics(opts).forEach((trackedTopic) => {
|
||||||
fn(trackedTopic.topic, trackedTopic.newTopic, trackedTopic.unreadTopic);
|
fn(trackedTopic.topic, trackedTopic.newTopic, trackedTopic.unreadTopic);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using the array of tags provided, tallies up all topics via forEachTracked
|
* Using the array of tags provided, tallies up all topics via forEachTracked
|
||||||
@ -710,7 +710,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return counts;
|
return counts;
|
||||||
},
|
}
|
||||||
|
|
||||||
countCategory(category_id, tagId) {
|
countCategory(category_id, tagId) {
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
@ -728,7 +728,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return sum;
|
return sum;
|
||||||
},
|
}
|
||||||
|
|
||||||
lookupCount({ type, category, tagId, noSubcategories, customFilterFn } = {}) {
|
lookupCount({ type, category, tagId, noSubcategories, customFilterFn } = {}) {
|
||||||
if (type === "latest") {
|
if (type === "latest") {
|
||||||
@ -782,7 +782,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
return this.countCategory(categoryId, tagId);
|
return this.countCategory(categoryId, tagId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
loadStates(data) {
|
loadStates(data) {
|
||||||
if (!data || data.length === 0) {
|
if (!data || data.length === 0) {
|
||||||
@ -796,7 +796,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
if (modified) {
|
if (modified) {
|
||||||
this._afterStateChange();
|
this._afterStateChange();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
_setState({ topic, data, skipAfterStateChange }) {
|
_setState({ topic, data, skipAfterStateChange }) {
|
||||||
const stateKey = this._stateKey(topic);
|
const stateKey = this._stateKey(topic);
|
||||||
@ -813,11 +813,11 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
} else {
|
} else {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
modifyState(topic, data) {
|
modifyState(topic, data) {
|
||||||
this._setState({ topic, data });
|
this._setState({ topic, data });
|
||||||
},
|
}
|
||||||
|
|
||||||
modifyStateProp(topic, prop, data) {
|
modifyStateProp(topic, prop, data) {
|
||||||
const state = this.findState(topic);
|
const state = this.findState(topic);
|
||||||
@ -825,11 +825,11 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
state[prop] = data;
|
state[prop] = data;
|
||||||
this._afterStateChange();
|
this._afterStateChange();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
findState(topicOrId) {
|
findState(topicOrId) {
|
||||||
return this.states.get(this._stateKey(topicOrId));
|
return this.states.get(this._stateKey(topicOrId));
|
||||||
},
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* private
|
* private
|
||||||
@ -860,7 +860,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
// this updates the topic in the state to match the
|
// this updates the topic in the state to match the
|
||||||
// topic from the list (e.g. updates category, highest read post
|
// topic from the list (e.g. updates category, highest read post
|
||||||
@ -908,7 +908,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return newState;
|
return newState;
|
||||||
},
|
}
|
||||||
|
|
||||||
// this stops sync of tracking state when list is filtered, in the past this
|
// this stops sync of tracking state when list is filtered, in the past this
|
||||||
// would cause the tracking state to become inconsistent.
|
// would cause the tracking state to become inconsistent.
|
||||||
@ -925,7 +925,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return shouldCompensate;
|
return shouldCompensate;
|
||||||
},
|
}
|
||||||
|
|
||||||
// any state that is not in the provided list must be updated
|
// any state that is not in the provided list must be updated
|
||||||
// based on the filter selected so we do not have any incorrect
|
// based on the filter selected so we do not have any incorrect
|
||||||
@ -957,7 +957,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
|
|
||||||
this.modifyState(topicKey, newState);
|
this.modifyState(topicKey, newState);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
// processes the data sent via messageBus, called by establishChannels
|
// processes the data sent via messageBus, called by establishChannels
|
||||||
@bind
|
@bind
|
||||||
@ -1054,7 +1054,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
this.incrementMessageCount();
|
this.incrementMessageCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
_dismissNewTopics(topicIds) {
|
_dismissNewTopics(topicIds) {
|
||||||
topicIds.forEach((topicId) => {
|
topicIds.forEach((topicId) => {
|
||||||
@ -1062,7 +1062,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.incrementMessageCount();
|
this.incrementMessageCount();
|
||||||
},
|
}
|
||||||
|
|
||||||
_dismissNewPosts(topicIds) {
|
_dismissNewPosts(topicIds) {
|
||||||
topicIds.forEach((topicId) => {
|
topicIds.forEach((topicId) => {
|
||||||
@ -1078,13 +1078,13 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.incrementMessageCount();
|
this.incrementMessageCount();
|
||||||
},
|
}
|
||||||
|
|
||||||
_addIncoming(topicId) {
|
_addIncoming(topicId) {
|
||||||
if (!this.newIncoming.includes(topicId)) {
|
if (!this.newIncoming.includes(topicId)) {
|
||||||
this.newIncoming.push(topicId);
|
this.newIncoming.push(topicId);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
_trackedTopics(opts = {}) {
|
_trackedTopics(opts = {}) {
|
||||||
return Array.from(this.states.values())
|
return Array.from(this.states.values())
|
||||||
@ -1096,7 +1096,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.compact();
|
.compact();
|
||||||
},
|
}
|
||||||
|
|
||||||
_stateKey(topicOrId) {
|
_stateKey(topicOrId) {
|
||||||
if (typeof topicOrId === "number") {
|
if (typeof topicOrId === "number") {
|
||||||
@ -1106,17 +1106,17 @@ const TopicTrackingState = EmberObject.extend({
|
|||||||
} else {
|
} else {
|
||||||
return `t${topicOrId.topic_id}`;
|
return `t${topicOrId.topic_id}`;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
_afterStateChange() {
|
_afterStateChange() {
|
||||||
this.notifyPropertyChange("states");
|
this.notifyPropertyChange("states");
|
||||||
Object.values(this.stateChangeCallbacks).forEach((cb) => cb());
|
Object.values(this.stateChangeCallbacks).forEach((cb) => cb());
|
||||||
},
|
}
|
||||||
|
|
||||||
_maxStateSizeReached() {
|
_maxStateSizeReached() {
|
||||||
return this.states.size >= this._trackedTopicLimit;
|
return this.states.size >= this._trackedTopicLimit;
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
export function startTracking(tracking) {
|
export function startTracking(tracking) {
|
||||||
PreloadStore.getAndRemove("topicTrackingStates").then((data) =>
|
PreloadStore.getAndRemove("topicTrackingStates").then((data) =>
|
||||||
@ -1127,5 +1127,3 @@ export function startTracking(tracking) {
|
|||||||
tracking.establishChannels(meta)
|
tracking.establishChannels(meta)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TopicTrackingState;
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import EmberObject from "@ember/object";
|
import EmberObject from "@ember/object";
|
||||||
|
|
||||||
export default EmberObject.extend({
|
export default class UserActionGroup extends EmberObject {
|
||||||
push(item) {
|
push(item) {
|
||||||
if (!this.items) {
|
if (!this.items) {
|
||||||
this.items = [];
|
this.items = [];
|
||||||
}
|
}
|
||||||
return this.items.push(item);
|
return this.items.push(item);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
@ -3,16 +3,16 @@ import RestModel from "discourse/models/rest";
|
|||||||
import UserAction from "discourse/models/user-action";
|
import UserAction from "discourse/models/user-action";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default RestModel.extend({
|
export default class UserActionStat extends RestModel {
|
||||||
|
@i18n("action_type", "user_action_groups.%@") description;
|
||||||
|
|
||||||
@discourseComputed("action_type")
|
@discourseComputed("action_type")
|
||||||
isPM(actionType) {
|
isPM(actionType) {
|
||||||
return (
|
return (
|
||||||
actionType === UserAction.TYPES.messages_sent ||
|
actionType === UserAction.TYPES.messages_sent ||
|
||||||
actionType === UserAction.TYPES.messages_received
|
actionType === UserAction.TYPES.messages_received
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
description: i18n("action_type", "user_action_groups.%@"),
|
|
||||||
|
|
||||||
@discourseComputed("action_type")
|
@discourseComputed("action_type")
|
||||||
isResponse(actionType) {
|
isResponse(actionType) {
|
||||||
@ -20,5 +20,5 @@ export default RestModel.extend({
|
|||||||
actionType === UserAction.TYPES.replies ||
|
actionType === UserAction.TYPES.replies ||
|
||||||
actionType === UserAction.TYPES.quotes
|
actionType === UserAction.TYPES.quotes
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
@ -26,153 +26,28 @@ Object.keys(UserActionTypes).forEach(
|
|||||||
(k) => (InvertedActionTypes[k] = UserActionTypes[k])
|
(k) => (InvertedActionTypes[k] = UserActionTypes[k])
|
||||||
);
|
);
|
||||||
|
|
||||||
const UserAction = RestModel.extend({
|
export default class UserAction extends RestModel {
|
||||||
@discourseComputed("category_id")
|
static TYPES = UserActionTypes;
|
||||||
category() {
|
|
||||||
return Category.findById(this.category_id);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("action_type")
|
static TYPES_INVERTED = InvertedActionTypes;
|
||||||
descriptionKey(action) {
|
|
||||||
if (action === null || UserAction.TO_SHOW.includes(action)) {
|
|
||||||
if (this.isPM) {
|
|
||||||
return this.sameUser ? "sent_by_you" : "sent_by_user";
|
|
||||||
} else {
|
|
||||||
return this.sameUser ? "posted_by_you" : "posted_by_user";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.topicType) {
|
static TO_COLLAPSE = [
|
||||||
return this.sameUser ? "you_posted_topic" : "user_posted_topic";
|
UserActionTypes.likes_given,
|
||||||
}
|
UserActionTypes.likes_received,
|
||||||
|
UserActionTypes.edits,
|
||||||
|
UserActionTypes.bookmarks,
|
||||||
|
];
|
||||||
|
|
||||||
if (this.postReplyType) {
|
static TO_SHOW = [
|
||||||
if (this.reply_to_post_number) {
|
UserActionTypes.likes_given,
|
||||||
return this.sameUser ? "you_replied_to_post" : "user_replied_to_post";
|
UserActionTypes.likes_received,
|
||||||
} else {
|
UserActionTypes.edits,
|
||||||
return this.sameUser ? "you_replied_to_topic" : "user_replied_to_topic";
|
UserActionTypes.bookmarks,
|
||||||
}
|
UserActionTypes.messages_sent,
|
||||||
}
|
UserActionTypes.messages_received,
|
||||||
|
];
|
||||||
|
|
||||||
if (this.mentionType) {
|
static collapseStream(stream) {
|
||||||
if (this.sameUser) {
|
|
||||||
return "you_mentioned_user";
|
|
||||||
} else {
|
|
||||||
return this.targetUser ? "user_mentioned_you" : "user_mentioned_user";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("username")
|
|
||||||
sameUser(username) {
|
|
||||||
return username === User.currentProp("username");
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("target_username")
|
|
||||||
targetUser(targetUsername) {
|
|
||||||
return targetUsername === User.currentProp("username");
|
|
||||||
},
|
|
||||||
|
|
||||||
presentName: or("name", "username"),
|
|
||||||
targetDisplayName: or("target_name", "target_username"),
|
|
||||||
actingDisplayName: or("acting_name", "acting_username"),
|
|
||||||
|
|
||||||
@discourseComputed("target_username")
|
|
||||||
targetUserUrl(username) {
|
|
||||||
return userPath(username);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("username")
|
|
||||||
usernameLower(username) {
|
|
||||||
return username.toLowerCase();
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("usernameLower")
|
|
||||||
userUrl(usernameLower) {
|
|
||||||
return userPath(usernameLower);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed()
|
|
||||||
postUrl() {
|
|
||||||
return postUrl(this.slug, this.topic_id, this.post_number);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed()
|
|
||||||
replyUrl() {
|
|
||||||
return postUrl(this.slug, this.topic_id, this.reply_to_post_number);
|
|
||||||
},
|
|
||||||
|
|
||||||
replyType: equal("action_type", UserActionTypes.replies),
|
|
||||||
postType: equal("action_type", UserActionTypes.posts),
|
|
||||||
topicType: equal("action_type", UserActionTypes.topics),
|
|
||||||
bookmarkType: equal("action_type", UserActionTypes.bookmarks),
|
|
||||||
messageSentType: equal("action_type", UserActionTypes.messages_sent),
|
|
||||||
messageReceivedType: equal("action_type", UserActionTypes.messages_received),
|
|
||||||
mentionType: equal("action_type", UserActionTypes.mentions),
|
|
||||||
isPM: or("messageSentType", "messageReceivedType"),
|
|
||||||
postReplyType: or("postType", "replyType"),
|
|
||||||
|
|
||||||
addChild(action) {
|
|
||||||
let groups = this.childGroups;
|
|
||||||
if (!groups) {
|
|
||||||
groups = {
|
|
||||||
likes: UserActionGroup.create({ icon: "heart" }),
|
|
||||||
stars: UserActionGroup.create({ icon: "star" }),
|
|
||||||
edits: UserActionGroup.create({ icon: "pencil-alt" }),
|
|
||||||
bookmarks: UserActionGroup.create({ icon: "bookmark" }),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
this.set("childGroups", groups);
|
|
||||||
|
|
||||||
const bucket = (function () {
|
|
||||||
switch (action.action_type) {
|
|
||||||
case UserActionTypes.likes_given:
|
|
||||||
case UserActionTypes.likes_received:
|
|
||||||
return "likes";
|
|
||||||
case UserActionTypes.edits:
|
|
||||||
return "edits";
|
|
||||||
case UserActionTypes.bookmarks:
|
|
||||||
return "bookmarks";
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
const current = groups[bucket];
|
|
||||||
if (current) {
|
|
||||||
current.push(action);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed(
|
|
||||||
"childGroups",
|
|
||||||
"childGroups.likes.items",
|
|
||||||
"childGroups.likes.items.[]",
|
|
||||||
"childGroups.stars.items",
|
|
||||||
"childGroups.stars.items.[]",
|
|
||||||
"childGroups.edits.items",
|
|
||||||
"childGroups.edits.items.[]",
|
|
||||||
"childGroups.bookmarks.items",
|
|
||||||
"childGroups.bookmarks.items.[]"
|
|
||||||
)
|
|
||||||
children() {
|
|
||||||
const g = this.childGroups;
|
|
||||||
let rval = [];
|
|
||||||
if (g) {
|
|
||||||
rval = [g.likes, g.stars, g.edits, g.bookmarks].filter(function (i) {
|
|
||||||
return i.get("items") && i.get("items").length > 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return rval;
|
|
||||||
},
|
|
||||||
|
|
||||||
switchToActing() {
|
|
||||||
this.setProperties({
|
|
||||||
username: this.acting_username,
|
|
||||||
name: this.actingDisplayName,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
UserAction.reopenClass({
|
|
||||||
collapseStream(stream) {
|
|
||||||
const uniq = {};
|
const uniq = {};
|
||||||
const collapsed = [];
|
const collapsed = [];
|
||||||
let pos = 0;
|
let pos = 0;
|
||||||
@ -204,26 +79,147 @@ UserAction.reopenClass({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
return collapsed;
|
return collapsed;
|
||||||
},
|
}
|
||||||
|
|
||||||
TYPES: UserActionTypes,
|
@or("name", "username") presentName;
|
||||||
TYPES_INVERTED: InvertedActionTypes,
|
@or("target_name", "target_username") targetDisplayName;
|
||||||
|
@or("acting_name", "acting_username") actingDisplayName;
|
||||||
|
@equal("action_type", UserActionTypes.replies) replyType;
|
||||||
|
@equal("action_type", UserActionTypes.posts) postType;
|
||||||
|
@equal("action_type", UserActionTypes.topics) topicType;
|
||||||
|
@equal("action_type", UserActionTypes.bookmarks) bookmarkType;
|
||||||
|
@equal("action_type", UserActionTypes.messages_sent) messageSentType;
|
||||||
|
@equal("action_type", UserActionTypes.messages_received) messageReceivedType;
|
||||||
|
@equal("action_type", UserActionTypes.mentions) mentionType;
|
||||||
|
@or("messageSentType", "messageReceivedType") isPM;
|
||||||
|
@or("postType", "replyType") postReplyType;
|
||||||
|
|
||||||
TO_COLLAPSE: [
|
@discourseComputed("category_id")
|
||||||
UserActionTypes.likes_given,
|
category() {
|
||||||
UserActionTypes.likes_received,
|
return Category.findById(this.category_id);
|
||||||
UserActionTypes.edits,
|
}
|
||||||
UserActionTypes.bookmarks,
|
|
||||||
],
|
|
||||||
|
|
||||||
TO_SHOW: [
|
@discourseComputed("action_type")
|
||||||
UserActionTypes.likes_given,
|
descriptionKey(action) {
|
||||||
UserActionTypes.likes_received,
|
if (action === null || UserAction.TO_SHOW.includes(action)) {
|
||||||
UserActionTypes.edits,
|
if (this.isPM) {
|
||||||
UserActionTypes.bookmarks,
|
return this.sameUser ? "sent_by_you" : "sent_by_user";
|
||||||
UserActionTypes.messages_sent,
|
} else {
|
||||||
UserActionTypes.messages_received,
|
return this.sameUser ? "posted_by_you" : "posted_by_user";
|
||||||
],
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
export default UserAction;
|
if (this.topicType) {
|
||||||
|
return this.sameUser ? "you_posted_topic" : "user_posted_topic";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.postReplyType) {
|
||||||
|
if (this.reply_to_post_number) {
|
||||||
|
return this.sameUser ? "you_replied_to_post" : "user_replied_to_post";
|
||||||
|
} else {
|
||||||
|
return this.sameUser ? "you_replied_to_topic" : "user_replied_to_topic";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mentionType) {
|
||||||
|
if (this.sameUser) {
|
||||||
|
return "you_mentioned_user";
|
||||||
|
} else {
|
||||||
|
return this.targetUser ? "user_mentioned_you" : "user_mentioned_user";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("username")
|
||||||
|
sameUser(username) {
|
||||||
|
return username === User.currentProp("username");
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("target_username")
|
||||||
|
targetUser(targetUsername) {
|
||||||
|
return targetUsername === User.currentProp("username");
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("target_username")
|
||||||
|
targetUserUrl(username) {
|
||||||
|
return userPath(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("username")
|
||||||
|
usernameLower(username) {
|
||||||
|
return username.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed("usernameLower")
|
||||||
|
userUrl(usernameLower) {
|
||||||
|
return userPath(usernameLower);
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed()
|
||||||
|
postUrl() {
|
||||||
|
return postUrl(this.slug, this.topic_id, this.post_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed()
|
||||||
|
replyUrl() {
|
||||||
|
return postUrl(this.slug, this.topic_id, this.reply_to_post_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
addChild(action) {
|
||||||
|
let groups = this.childGroups;
|
||||||
|
if (!groups) {
|
||||||
|
groups = {
|
||||||
|
likes: UserActionGroup.create({ icon: "heart" }),
|
||||||
|
stars: UserActionGroup.create({ icon: "star" }),
|
||||||
|
edits: UserActionGroup.create({ icon: "pencil-alt" }),
|
||||||
|
bookmarks: UserActionGroup.create({ icon: "bookmark" }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
this.set("childGroups", groups);
|
||||||
|
|
||||||
|
const bucket = (function () {
|
||||||
|
switch (action.action_type) {
|
||||||
|
case UserActionTypes.likes_given:
|
||||||
|
case UserActionTypes.likes_received:
|
||||||
|
return "likes";
|
||||||
|
case UserActionTypes.edits:
|
||||||
|
return "edits";
|
||||||
|
case UserActionTypes.bookmarks:
|
||||||
|
return "bookmarks";
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
const current = groups[bucket];
|
||||||
|
if (current) {
|
||||||
|
current.push(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@discourseComputed(
|
||||||
|
"childGroups",
|
||||||
|
"childGroups.likes.items",
|
||||||
|
"childGroups.likes.items.[]",
|
||||||
|
"childGroups.stars.items",
|
||||||
|
"childGroups.stars.items.[]",
|
||||||
|
"childGroups.edits.items",
|
||||||
|
"childGroups.edits.items.[]",
|
||||||
|
"childGroups.bookmarks.items",
|
||||||
|
"childGroups.bookmarks.items.[]"
|
||||||
|
)
|
||||||
|
children() {
|
||||||
|
const g = this.childGroups;
|
||||||
|
let rval = [];
|
||||||
|
if (g) {
|
||||||
|
rval = [g.likes, g.stars, g.edits, g.bookmarks].filter(function (i) {
|
||||||
|
return i.get("items") && i.get("items").length > 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return rval;
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToActing() {
|
||||||
|
this.setProperties({
|
||||||
|
username: this.acting_username,
|
||||||
|
name: this.actingDisplayName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,34 +7,8 @@ import Topic from "discourse/models/topic";
|
|||||||
import User from "discourse/models/user";
|
import User from "discourse/models/user";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
const UserBadge = EmberObject.extend({
|
export default class UserBadge extends EmberObject {
|
||||||
@discourseComputed
|
static createFromJson(json) {
|
||||||
postUrl() {
|
|
||||||
if (this.topic_title) {
|
|
||||||
return "/t/-/" + this.topic_id + "/" + this.post_number;
|
|
||||||
}
|
|
||||||
}, // avoid the extra bindings for now
|
|
||||||
|
|
||||||
revoke() {
|
|
||||||
return ajax("/user_badges/" + this.id, {
|
|
||||||
type: "DELETE",
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
favorite() {
|
|
||||||
this.toggleProperty("is_favorite");
|
|
||||||
return ajax(`/user_badges/${this.id}/toggle_favorite`, {
|
|
||||||
type: "PUT",
|
|
||||||
}).catch((e) => {
|
|
||||||
// something went wrong, switch the UI back:
|
|
||||||
this.toggleProperty("is_favorite");
|
|
||||||
popupAjaxError(e);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
UserBadge.reopenClass({
|
|
||||||
createFromJson(json) {
|
|
||||||
// Create User objects.
|
// Create User objects.
|
||||||
if (json.users === undefined) {
|
if (json.users === undefined) {
|
||||||
json.users = [];
|
json.users = [];
|
||||||
@ -105,7 +79,7 @@ UserBadge.reopenClass({
|
|||||||
}
|
}
|
||||||
return userBadges;
|
return userBadges;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Find all badges for a given username.
|
Find all badges for a given username.
|
||||||
@ -115,7 +89,7 @@ UserBadge.reopenClass({
|
|||||||
@param {Object} options
|
@param {Object} options
|
||||||
@returns {Promise} a promise that resolves to an array of `UserBadge`.
|
@returns {Promise} a promise that resolves to an array of `UserBadge`.
|
||||||
**/
|
**/
|
||||||
findByUsername(username, options) {
|
static findByUsername(username, options) {
|
||||||
if (!username) {
|
if (!username) {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
@ -126,7 +100,7 @@ UserBadge.reopenClass({
|
|||||||
return ajax(url).then(function (json) {
|
return ajax(url).then(function (json) {
|
||||||
return UserBadge.createFromJson(json);
|
return UserBadge.createFromJson(json);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Find all badge grants for a given badge ID.
|
Find all badge grants for a given badge ID.
|
||||||
@ -135,7 +109,7 @@ UserBadge.reopenClass({
|
|||||||
@param {String} badgeId
|
@param {String} badgeId
|
||||||
@returns {Promise} a promise that resolves to an array of `UserBadge`.
|
@returns {Promise} a promise that resolves to an array of `UserBadge`.
|
||||||
**/
|
**/
|
||||||
findByBadgeId(badgeId, options) {
|
static findByBadgeId(badgeId, options) {
|
||||||
if (!options) {
|
if (!options) {
|
||||||
options = {};
|
options = {};
|
||||||
}
|
}
|
||||||
@ -146,7 +120,7 @@ UserBadge.reopenClass({
|
|||||||
}).then(function (json) {
|
}).then(function (json) {
|
||||||
return UserBadge.createFromJson(json);
|
return UserBadge.createFromJson(json);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Grant the badge having id `badgeId` to the user identified by `username`.
|
Grant the badge having id `badgeId` to the user identified by `username`.
|
||||||
@ -156,7 +130,7 @@ UserBadge.reopenClass({
|
|||||||
@param {String} username username of the user to be granted the badge.
|
@param {String} username username of the user to be granted the badge.
|
||||||
@returns {Promise} a promise that resolves to an instance of `UserBadge`.
|
@returns {Promise} a promise that resolves to an instance of `UserBadge`.
|
||||||
**/
|
**/
|
||||||
grant(badgeId, username, reason) {
|
static grant(badgeId, username, reason) {
|
||||||
return ajax("/user_badges", {
|
return ajax("/user_badges", {
|
||||||
type: "POST",
|
type: "POST",
|
||||||
data: {
|
data: {
|
||||||
@ -167,7 +141,29 @@ UserBadge.reopenClass({
|
|||||||
}).then(function (json) {
|
}).then(function (json) {
|
||||||
return UserBadge.createFromJson(json);
|
return UserBadge.createFromJson(json);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
|
||||||
|
|
||||||
export default UserBadge;
|
@discourseComputed
|
||||||
|
postUrl() {
|
||||||
|
if (this.topic_title) {
|
||||||
|
return "/t/-/" + this.topic_id + "/" + this.post_number;
|
||||||
|
}
|
||||||
|
} // avoid the extra bindings for now
|
||||||
|
|
||||||
|
revoke() {
|
||||||
|
return ajax("/user_badges/" + this.id, {
|
||||||
|
type: "DELETE",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
favorite() {
|
||||||
|
this.toggleProperty("is_favorite");
|
||||||
|
return ajax(`/user_badges/${this.id}/toggle_favorite`, {
|
||||||
|
type: "PUT",
|
||||||
|
}).catch((e) => {
|
||||||
|
// something went wrong, switch the UI back:
|
||||||
|
this.toggleProperty("is_favorite");
|
||||||
|
popupAjaxError(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -9,16 +9,16 @@ import User from "discourse/models/user";
|
|||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
import I18n from "discourse-i18n";
|
import I18n from "discourse-i18n";
|
||||||
|
|
||||||
export default RestModel.extend({
|
export default class UserDraft extends RestModel {
|
||||||
@discourseComputed("draft_username")
|
@discourseComputed("draft_username")
|
||||||
editableDraft(draftUsername) {
|
editableDraft(draftUsername) {
|
||||||
return draftUsername === User.currentProp("username");
|
return draftUsername === User.currentProp("username");
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("username_lower")
|
@discourseComputed("username_lower")
|
||||||
userUrl(usernameLower) {
|
userUrl(usernameLower) {
|
||||||
return userPath(usernameLower);
|
return userPath(usernameLower);
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("topic_id")
|
@discourseComputed("topic_id")
|
||||||
postUrl(topicId) {
|
postUrl(topicId) {
|
||||||
@ -27,7 +27,7 @@ export default RestModel.extend({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return postUrl(this.slug, this.topic_id, this.post_number);
|
return postUrl(this.slug, this.topic_id, this.post_number);
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("draft_key")
|
@discourseComputed("draft_key")
|
||||||
draftType(draftKey) {
|
draftType(draftKey) {
|
||||||
@ -39,5 +39,5 @@ export default RestModel.extend({
|
|||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
@ -10,17 +10,16 @@ import RestModel from "discourse/models/rest";
|
|||||||
import UserDraft from "discourse/models/user-draft";
|
import UserDraft from "discourse/models/user-draft";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default RestModel.extend({
|
export default class UserDraftsStream extends RestModel {
|
||||||
limit: 30,
|
limit = 30;
|
||||||
|
loading = false;
|
||||||
loading: false,
|
hasMore = false;
|
||||||
hasMore: false,
|
content = null;
|
||||||
content: null,
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
super.init(...arguments);
|
||||||
this.reset();
|
this.reset();
|
||||||
},
|
}
|
||||||
|
|
||||||
reset() {
|
reset() {
|
||||||
this.setProperties({
|
this.setProperties({
|
||||||
@ -28,19 +27,19 @@ export default RestModel.extend({
|
|||||||
hasMore: true,
|
hasMore: true,
|
||||||
content: [],
|
content: [],
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
|
|
||||||
@discourseComputed("content.length", "loading")
|
@discourseComputed("content.length", "loading")
|
||||||
noContent(contentLength, loading) {
|
noContent(contentLength, loading) {
|
||||||
return contentLength === 0 && !loading;
|
return contentLength === 0 && !loading;
|
||||||
},
|
}
|
||||||
|
|
||||||
remove(draft) {
|
remove(draft) {
|
||||||
this.set(
|
this.set(
|
||||||
"content",
|
"content",
|
||||||
this.content.filter((item) => item.draft_key !== draft.draft_key)
|
this.content.filter((item) => item.draft_key !== draft.draft_key)
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
|
|
||||||
findItems(site) {
|
findItems(site) {
|
||||||
if (site) {
|
if (site) {
|
||||||
@ -92,5 +91,5 @@ export default RestModel.extend({
|
|||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.set("loading", false);
|
this.set("loading", false);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
});
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user