DEV: Clean up all message bus subscriptions (#18675)
1. "What Goes Up Must Come Down" – if you subscribe to message bus, make sure you also unsubscribe 2. When you unsubscribe - remove only your subscription, not **all** subscriptions on given channel
This commit is contained in:
parent
321b14d40c
commit
b0839ccf27
|
@ -2,7 +2,7 @@ import Controller from "@ember/controller";
|
|||
import { ajax } from "discourse/lib/ajax";
|
||||
import { action } from "@ember/object";
|
||||
import { alias } from "@ember/object/computed";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import discourseComputed, { bind } from "discourse-common/utils/decorators";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
|
||||
export default Controller.extend({
|
||||
|
@ -23,24 +23,22 @@ export default Controller.extend({
|
|||
subscribe() {
|
||||
this.messageBus.subscribe(
|
||||
`/web_hook_events/${this.get("model.extras.web_hook_id")}`,
|
||||
(data) => {
|
||||
if (data.event_type === "ping") {
|
||||
this.set("pingDisabled", false);
|
||||
}
|
||||
this._addIncoming(data.web_hook_event_id);
|
||||
}
|
||||
this._addIncoming
|
||||
);
|
||||
},
|
||||
|
||||
unsubscribe() {
|
||||
this.messageBus.unsubscribe("/web_hook_events/*");
|
||||
this.messageBus.unsubscribe("/web_hook_events/*", this._addIncoming);
|
||||
},
|
||||
|
||||
_addIncoming(eventId) {
|
||||
const incomingEventIds = this.incomingEventIds;
|
||||
@bind
|
||||
_addIncoming(data) {
|
||||
if (data.event_type === "ping") {
|
||||
this.set("pingDisabled", false);
|
||||
}
|
||||
|
||||
if (!incomingEventIds.includes(eventId)) {
|
||||
incomingEventIds.pushObject(eventId);
|
||||
if (!this.incomingEventIds.includes(data.web_hook_event_id)) {
|
||||
this.incomingEventIds.pushObject(data.web_hook_event_id);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -2,30 +2,33 @@ import Controller from "@ember/controller";
|
|||
import DiscourseURL from "discourse/lib/url";
|
||||
import I18n from "I18n";
|
||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||
import messageBus from "message-bus-client";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Controller.extend(ModalFunctionality, {
|
||||
message: I18n.t("admin.user.merging_user"),
|
||||
|
||||
onShow() {
|
||||
messageBus.subscribe("/merge_user", (data) => {
|
||||
if (data.merged) {
|
||||
if (/^\/admin\/users\/list\//.test(location)) {
|
||||
DiscourseURL.redirectTo(location);
|
||||
} else {
|
||||
DiscourseURL.redirectTo(
|
||||
`/admin/users/${data.user.id}/${data.user.username}`
|
||||
);
|
||||
}
|
||||
} else if (data.message) {
|
||||
this.set("message", data.message);
|
||||
} else if (data.failed) {
|
||||
this.set("message", I18n.t("admin.user.merge_failed"));
|
||||
}
|
||||
});
|
||||
this.messageBus.subscribe("/merge_user", this.onMessage);
|
||||
},
|
||||
|
||||
onClose() {
|
||||
this.messageBus.unsubscribe("/merge_user");
|
||||
this.messageBus.unsubscribe("/merge_user", this.onMessage);
|
||||
},
|
||||
|
||||
@bind
|
||||
onMessage(data) {
|
||||
if (data.merged) {
|
||||
if (/^\/admin\/users\/list\//.test(location)) {
|
||||
DiscourseURL.redirectTo(location);
|
||||
} else {
|
||||
DiscourseURL.redirectTo(
|
||||
`/admin/users/${data.user.id}/${data.user.username}`
|
||||
);
|
||||
}
|
||||
} else if (data.message) {
|
||||
this.set("message", data.message);
|
||||
} else if (data.failed) {
|
||||
this.set("message", I18n.t("admin.user.merge_failed"));
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import Backup from "admin/models/backup";
|
||||
import Route from "@ember/routing/route";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default Route.extend({
|
||||
activate() {
|
||||
this.messageBus.subscribe("/admin/backups", (backups) =>
|
||||
this.controller.set(
|
||||
"model",
|
||||
backups.map((backup) => Backup.create(backup))
|
||||
)
|
||||
);
|
||||
this.messageBus.subscribe("/admin/backups", this.onMessage);
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this.messageBus.unsubscribe("/admin/backups", this.onMessage);
|
||||
},
|
||||
|
||||
model() {
|
||||
|
@ -17,7 +17,11 @@ export default Route.extend({
|
|||
);
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this.messageBus.unsubscribe("/admin/backups");
|
||||
@bind
|
||||
onMessage(backups) {
|
||||
this.controller.set(
|
||||
"model",
|
||||
backups.map((backup) => Backup.create(backup))
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -10,46 +10,19 @@ import { extractError } from "discourse/lib/ajax-error";
|
|||
import getURL from "discourse-common/lib/get-url";
|
||||
import showModal from "discourse/lib/show-modal";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
const LOG_CHANNEL = "/admin/backups/logs";
|
||||
|
||||
export default DiscourseRoute.extend({
|
||||
dialog: service(),
|
||||
|
||||
activate() {
|
||||
this.messageBus.subscribe(LOG_CHANNEL, (log) => {
|
||||
if (log.message === "[STARTED]") {
|
||||
User.currentProp("hideReadOnlyAlert", true);
|
||||
this.controllerFor("adminBackups").set(
|
||||
"model.isOperationRunning",
|
||||
true
|
||||
);
|
||||
this.controllerFor("adminBackupsLogs").get("logs").clear();
|
||||
} else if (log.message === "[FAILED]") {
|
||||
this.controllerFor("adminBackups").set(
|
||||
"model.isOperationRunning",
|
||||
false
|
||||
);
|
||||
this.dialog.alert(
|
||||
I18n.t("admin.backups.operations.failed", {
|
||||
operation: log.operation,
|
||||
})
|
||||
);
|
||||
} else if (log.message === "[SUCCESS]") {
|
||||
User.currentProp("hideReadOnlyAlert", false);
|
||||
this.controllerFor("adminBackups").set(
|
||||
"model.isOperationRunning",
|
||||
false
|
||||
);
|
||||
if (log.operation === "restore") {
|
||||
// redirect to homepage when the restore is done (session might be lost)
|
||||
window.location = getURL("/");
|
||||
}
|
||||
} else {
|
||||
this.controllerFor("adminBackupsLogs")
|
||||
.get("logs")
|
||||
.pushObject(EmberObject.create(log));
|
||||
}
|
||||
});
|
||||
this.messageBus.subscribe(LOG_CHANNEL, this.onMessage);
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this.messageBus.unsubscribe(LOG_CHANNEL, this.onMessage);
|
||||
},
|
||||
|
||||
model() {
|
||||
|
@ -64,8 +37,31 @@ export default DiscourseRoute.extend({
|
|||
);
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this.messageBus.unsubscribe(LOG_CHANNEL);
|
||||
@bind
|
||||
onMessage(log) {
|
||||
if (log.message === "[STARTED]") {
|
||||
User.currentProp("hideReadOnlyAlert", true);
|
||||
this.controllerFor("adminBackups").set("model.isOperationRunning", true);
|
||||
this.controllerFor("adminBackupsLogs").get("logs").clear();
|
||||
} else if (log.message === "[FAILED]") {
|
||||
this.controllerFor("adminBackups").set("model.isOperationRunning", false);
|
||||
this.dialog.alert(
|
||||
I18n.t("admin.backups.operations.failed", {
|
||||
operation: log.operation,
|
||||
})
|
||||
);
|
||||
} else if (log.message === "[SUCCESS]") {
|
||||
User.currentProp("hideReadOnlyAlert", false);
|
||||
this.controllerFor("adminBackups").set("model.isOperationRunning", false);
|
||||
if (log.operation === "restore") {
|
||||
// redirect to homepage when the restore is done (session might be lost)
|
||||
window.location = getURL("/");
|
||||
}
|
||||
} else {
|
||||
this.controllerFor("adminBackupsLogs")
|
||||
.get("logs")
|
||||
.pushObject(EmberObject.create(log));
|
||||
}
|
||||
},
|
||||
|
||||
actions: {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { alias, not } from "@ember/object/computed";
|
||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||
import discourseComputed, {
|
||||
bind,
|
||||
observes,
|
||||
} from "discourse-common/utils/decorators";
|
||||
import Component from "@ember/component";
|
||||
|
||||
export default Component.extend({
|
||||
|
@ -40,18 +43,11 @@ export default Component.extend({
|
|||
this._super(...arguments);
|
||||
|
||||
this.topics.forEach((topic) => {
|
||||
const includeUnreadIndicator =
|
||||
typeof topic.unread_by_group_member !== "undefined";
|
||||
|
||||
if (includeUnreadIndicator) {
|
||||
const unreadIndicatorChannel = `/private-messages/unread-indicator/${topic.id}`;
|
||||
this.messageBus.subscribe(unreadIndicatorChannel, (data) => {
|
||||
const nodeClassList = document.querySelector(
|
||||
`.indicator-topic-${data.topic_id}`
|
||||
).classList;
|
||||
|
||||
nodeClassList.toggle("read", !data.show_indicator);
|
||||
});
|
||||
if (typeof topic.unread_by_group_member !== "undefined") {
|
||||
this.messageBus.subscribe(
|
||||
`/private-messages/unread-indicator/${topic.id}`,
|
||||
this.onMessage
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
@ -59,15 +55,19 @@ export default Component.extend({
|
|||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.topics.forEach((topic) => {
|
||||
const includeUnreadIndicator =
|
||||
typeof topic.unread_by_group_member !== "undefined";
|
||||
this.messageBus.unsubscribe(
|
||||
"/private-messages/unread-indicator/*",
|
||||
this.onMessage
|
||||
);
|
||||
},
|
||||
|
||||
if (includeUnreadIndicator) {
|
||||
const unreadIndicatorChannel = `/private-messages/unread-indicator/${topic.id}`;
|
||||
this.messageBus.unsubscribe(unreadIndicatorChannel);
|
||||
}
|
||||
});
|
||||
@bind
|
||||
onMessage(data) {
|
||||
const nodeClassList = document.querySelector(
|
||||
`.indicator-topic-${data.topic_id}`
|
||||
).classList;
|
||||
|
||||
nodeClassList.toggle("read", !data.show_indicator);
|
||||
},
|
||||
|
||||
@discourseComputed("topics")
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import getURL from "discourse-common/lib/get-url";
|
||||
import { cancel } from "@ember/runloop";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
import discourseComputed, { on } from "discourse-common/utils/decorators";
|
||||
import discourseComputed, { bind, on } from "discourse-common/utils/decorators";
|
||||
import Component from "@ember/component";
|
||||
import { action } from "@ember/object";
|
||||
import { isTesting } from "discourse-common/config/environment";
|
||||
|
@ -13,36 +13,49 @@ export default Component.extend({
|
|||
animatePrompt: false,
|
||||
_timeoutHandler: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.messageBus.subscribe("/refresh_client", this.onRefresh);
|
||||
this.messageBus.subscribe("/global/asset-version", this.onAsset);
|
||||
},
|
||||
|
||||
willDestroy() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.messageBus.unsubscribe("/refresh_client", this.onRefresh);
|
||||
this.messageBus.unsubscribe("/global/asset-version", this.onAsset);
|
||||
},
|
||||
|
||||
@bind
|
||||
onRefresh() {
|
||||
this.session.requiresRefresh = true;
|
||||
},
|
||||
|
||||
@bind
|
||||
onAsset(version) {
|
||||
if (this.session.assetVersion !== version) {
|
||||
this.session.requiresRefresh = true;
|
||||
}
|
||||
|
||||
if (!this._timeoutHandler && this.session.requiresRefresh) {
|
||||
if (isTesting()) {
|
||||
this.updatePromptState(true);
|
||||
} else {
|
||||
// Since we can do this transparently for people browsing the forum
|
||||
// hold back the message 24 hours.
|
||||
this._timeoutHandler = discourseLater(() => {
|
||||
this.updatePromptState(true);
|
||||
}, 1000 * 60 * 24 * 60);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@discourseComputed
|
||||
rootUrl() {
|
||||
return getURL("/");
|
||||
},
|
||||
|
||||
@on("init")
|
||||
initSubscriptions() {
|
||||
this.messageBus.subscribe("/refresh_client", () => {
|
||||
this.session.requiresRefresh = true;
|
||||
});
|
||||
|
||||
this.messageBus.subscribe("/global/asset-version", (version) => {
|
||||
if (this.session.assetVersion !== version) {
|
||||
this.session.requiresRefresh = true;
|
||||
}
|
||||
|
||||
if (!this._timeoutHandler && this.session.requiresRefresh) {
|
||||
if (isTesting()) {
|
||||
this.updatePromptState(true);
|
||||
} else {
|
||||
// Since we can do this transparently for people browsing the forum
|
||||
// hold back the message 24 hours.
|
||||
this._timeoutHandler = discourseLater(() => {
|
||||
this.updatePromptState(true);
|
||||
}, 1000 * 60 * 24 * 60);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updatePromptState(value) {
|
||||
// when adding the message, we inject the HTML then add the animation
|
||||
// when dismissing, things need to happen in the opposite order
|
||||
|
|
|
@ -77,13 +77,7 @@ export default Component.extend({
|
|||
this._super(...arguments);
|
||||
|
||||
if (this.includeUnreadIndicator) {
|
||||
this.messageBus.subscribe(this.unreadIndicatorChannel, (data) => {
|
||||
const nodeClassList = document.querySelector(
|
||||
`.indicator-topic-${data.topic_id}`
|
||||
).classList;
|
||||
|
||||
nodeClassList.toggle("read", !data.show_indicator);
|
||||
});
|
||||
this.messageBus.subscribe(this.unreadIndicatorChannel, this.onMessage);
|
||||
}
|
||||
|
||||
schedule("afterRender", () => {
|
||||
|
@ -101,9 +95,8 @@ export default Component.extend({
|
|||
willDestroyElement() {
|
||||
this._super(...arguments);
|
||||
|
||||
if (this.includeUnreadIndicator) {
|
||||
this.messageBus.unsubscribe(this.unreadIndicatorChannel);
|
||||
}
|
||||
this.messageBus.unsubscribe(this.unreadIndicatorChannel, this.onMessage);
|
||||
|
||||
if (this._shouldFocusLastVisited()) {
|
||||
const title = this._titleElement();
|
||||
if (title) {
|
||||
|
@ -113,6 +106,15 @@ export default Component.extend({
|
|||
}
|
||||
},
|
||||
|
||||
@bind
|
||||
onMessage(data) {
|
||||
const nodeClassList = document.querySelector(
|
||||
`.indicator-topic-${data.topic_id}`
|
||||
).classList;
|
||||
|
||||
nodeClassList.toggle("read", !data.show_indicator);
|
||||
},
|
||||
|
||||
@discourseComputed("topic.id")
|
||||
unreadIndicatorChannel(topicId) {
|
||||
return `/private-messages/unread-indicator/${topicId}`;
|
||||
|
|
|
@ -2,7 +2,10 @@ import Category from "discourse/models/category";
|
|||
import Controller, { inject as controller } from "@ember/controller";
|
||||
import DiscourseURL, { userPath } from "discourse/lib/url";
|
||||
import { alias, and, not, or } from "@ember/object/computed";
|
||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||
import discourseComputed, {
|
||||
bind,
|
||||
observes,
|
||||
} from "discourse-common/utils/decorators";
|
||||
import { isEmpty, isPresent } from "@ember/utils";
|
||||
import { next, schedule } from "@ember/runloop";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
|
@ -1599,157 +1602,9 @@ export default Controller.extend(bufferedProperty("model"), {
|
|||
subscribe() {
|
||||
this.unsubscribe();
|
||||
|
||||
const refresh = (args) =>
|
||||
this.appEvents.trigger("post-stream:refresh", args);
|
||||
|
||||
this.messageBus.subscribe(
|
||||
`/topic/${this.get("model.id")}`,
|
||||
(data) => {
|
||||
const topic = this.model;
|
||||
|
||||
if (isPresent(data.notification_level_change)) {
|
||||
topic.set(
|
||||
"details.notification_level",
|
||||
data.notification_level_change
|
||||
);
|
||||
topic.set(
|
||||
"details.notifications_reason_id",
|
||||
data.notifications_reason_id
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const postStream = this.get("model.postStream");
|
||||
|
||||
if (data.reload_topic) {
|
||||
topic.reload().then(() => {
|
||||
this.send("postChangedRoute", topic.get("post_number") || 1);
|
||||
this.appEvents.trigger("header:update-topic", topic);
|
||||
if (data.refresh_stream) {
|
||||
postStream.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case "acted":
|
||||
postStream
|
||||
.triggerChangedPost(data.id, data.updated_at, {
|
||||
preserveCooked: true,
|
||||
})
|
||||
.then(() => refresh({ id: data.id, refreshLikes: true }));
|
||||
break;
|
||||
case "read": {
|
||||
postStream
|
||||
.triggerReadPost(data.id, data.readers_count)
|
||||
.then(() => refresh({ id: data.id, refreshLikes: true }));
|
||||
break;
|
||||
}
|
||||
case "liked":
|
||||
case "unliked": {
|
||||
postStream
|
||||
.triggerLikedPost(
|
||||
data.id,
|
||||
data.likes_count,
|
||||
data.user_id,
|
||||
data.type
|
||||
)
|
||||
.then(() => refresh({ id: data.id, refreshLikes: true }));
|
||||
break;
|
||||
}
|
||||
case "revised":
|
||||
case "rebaked": {
|
||||
postStream
|
||||
.triggerChangedPost(data.id, data.updated_at)
|
||||
.then(() => refresh({ id: data.id }));
|
||||
break;
|
||||
}
|
||||
case "deleted": {
|
||||
postStream
|
||||
.triggerDeletedPost(data.id)
|
||||
.then(() => refresh({ id: data.id }));
|
||||
break;
|
||||
}
|
||||
case "destroyed": {
|
||||
postStream
|
||||
.triggerDestroyedPost(data.id)
|
||||
.then(() => refresh({ id: data.id }));
|
||||
break;
|
||||
}
|
||||
case "recovered": {
|
||||
postStream
|
||||
.triggerRecoveredPost(data.id)
|
||||
.then(() => refresh({ id: data.id }));
|
||||
break;
|
||||
}
|
||||
case "created": {
|
||||
this._newPostsInStream.push(data.id);
|
||||
|
||||
this.retryOnRateLimit(RETRIES_ON_RATE_LIMIT, () => {
|
||||
const postIds = this._newPostsInStream;
|
||||
this._newPostsInStream = [];
|
||||
|
||||
return postStream
|
||||
.triggerNewPostsInStream(postIds, { background: true })
|
||||
.then(() => refresh())
|
||||
.catch((e) => {
|
||||
this._newPostsInStream = postIds.concat(
|
||||
this._newPostsInStream
|
||||
);
|
||||
throw e;
|
||||
});
|
||||
});
|
||||
|
||||
if (this.get("currentUser.id") !== data.user_id) {
|
||||
this.documentTitle.incrementBackgroundContextCount();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "move_to_inbox": {
|
||||
topic.set("message_archived", false);
|
||||
break;
|
||||
}
|
||||
case "archived": {
|
||||
topic.set("message_archived", true);
|
||||
break;
|
||||
}
|
||||
case "stats": {
|
||||
let updateStream = false;
|
||||
["last_posted_at", "like_count", "posts_count"].forEach(
|
||||
(property) => {
|
||||
const value = data[property];
|
||||
if (typeof value !== "undefined") {
|
||||
topic.set(property, value);
|
||||
updateStream = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (data["last_poster"]) {
|
||||
topic.details.set("last_poster", data["last_poster"]);
|
||||
updateStream = true;
|
||||
}
|
||||
|
||||
if (updateStream) {
|
||||
postStream
|
||||
.triggerChangedTopicStats()
|
||||
.then((firstPostId) => refresh({ id: firstPostId }));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
let callback = customPostMessageCallbacks[data.type];
|
||||
if (callback) {
|
||||
callback(this, data);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("unknown topic bus message type", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
this.onMessage,
|
||||
this.get("model.message_bus_last_id")
|
||||
);
|
||||
},
|
||||
|
@ -1759,9 +1614,148 @@ export default Controller.extend(bufferedProperty("model"), {
|
|||
if (!this.get("model.id")) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.messageBus.unsubscribe("/topic/*");
|
||||
},
|
||||
|
||||
@bind
|
||||
onMessage(data) {
|
||||
const topic = this.model;
|
||||
const refresh = (args) =>
|
||||
this.appEvents.trigger("post-stream:refresh", args);
|
||||
|
||||
if (isPresent(data.notification_level_change)) {
|
||||
topic.set("details.notification_level", data.notification_level_change);
|
||||
topic.set(
|
||||
"details.notifications_reason_id",
|
||||
data.notifications_reason_id
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const postStream = this.get("model.postStream");
|
||||
|
||||
if (data.reload_topic) {
|
||||
topic.reload().then(() => {
|
||||
this.send("postChangedRoute", topic.get("post_number") || 1);
|
||||
this.appEvents.trigger("header:update-topic", topic);
|
||||
if (data.refresh_stream) {
|
||||
postStream.refresh();
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case "acted":
|
||||
postStream
|
||||
.triggerChangedPost(data.id, data.updated_at, {
|
||||
preserveCooked: true,
|
||||
})
|
||||
.then(() => refresh({ id: data.id, refreshLikes: true }));
|
||||
break;
|
||||
case "read": {
|
||||
postStream
|
||||
.triggerReadPost(data.id, data.readers_count)
|
||||
.then(() => refresh({ id: data.id, refreshLikes: true }));
|
||||
break;
|
||||
}
|
||||
case "liked":
|
||||
case "unliked": {
|
||||
postStream
|
||||
.triggerLikedPost(data.id, data.likes_count, data.user_id, data.type)
|
||||
.then(() => refresh({ id: data.id, refreshLikes: true }));
|
||||
break;
|
||||
}
|
||||
case "revised":
|
||||
case "rebaked": {
|
||||
postStream
|
||||
.triggerChangedPost(data.id, data.updated_at)
|
||||
.then(() => refresh({ id: data.id }));
|
||||
break;
|
||||
}
|
||||
case "deleted": {
|
||||
postStream
|
||||
.triggerDeletedPost(data.id)
|
||||
.then(() => refresh({ id: data.id }));
|
||||
break;
|
||||
}
|
||||
case "destroyed": {
|
||||
postStream
|
||||
.triggerDestroyedPost(data.id)
|
||||
.then(() => refresh({ id: data.id }));
|
||||
break;
|
||||
}
|
||||
case "recovered": {
|
||||
postStream
|
||||
.triggerRecoveredPost(data.id)
|
||||
.then(() => refresh({ id: data.id }));
|
||||
break;
|
||||
}
|
||||
case "created": {
|
||||
this._newPostsInStream.push(data.id);
|
||||
|
||||
this.retryOnRateLimit(RETRIES_ON_RATE_LIMIT, () => {
|
||||
const postIds = this._newPostsInStream;
|
||||
this._newPostsInStream = [];
|
||||
|
||||
return postStream
|
||||
.triggerNewPostsInStream(postIds, { background: true })
|
||||
.then(() => refresh())
|
||||
.catch((e) => {
|
||||
this._newPostsInStream = postIds.concat(this._newPostsInStream);
|
||||
throw e;
|
||||
});
|
||||
});
|
||||
|
||||
if (this.get("currentUser.id") !== data.user_id) {
|
||||
this.documentTitle.incrementBackgroundContextCount();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "move_to_inbox": {
|
||||
topic.set("message_archived", false);
|
||||
break;
|
||||
}
|
||||
case "archived": {
|
||||
topic.set("message_archived", true);
|
||||
break;
|
||||
}
|
||||
case "stats": {
|
||||
let updateStream = false;
|
||||
["last_posted_at", "like_count", "posts_count"].forEach((property) => {
|
||||
const value = data[property];
|
||||
if (typeof value !== "undefined") {
|
||||
topic.set(property, value);
|
||||
updateStream = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (data["last_poster"]) {
|
||||
topic.details.set("last_poster", data["last_poster"]);
|
||||
updateStream = true;
|
||||
}
|
||||
|
||||
if (updateStream) {
|
||||
postStream
|
||||
.triggerChangedTopicStats()
|
||||
.then((firstPostId) => refresh({ id: firstPostId }));
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
let callback = customPostMessageCallbacks[data.type];
|
||||
if (callback) {
|
||||
callback(this, data);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn("unknown topic bus message type", data);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
reply() {
|
||||
this.replyToPost();
|
||||
},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import EmberObject from "@ember/object";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import PreloadStore from "discourse/lib/preload-store";
|
||||
|
||||
export default {
|
||||
|
@ -6,14 +7,21 @@ export default {
|
|||
after: "message-bus",
|
||||
|
||||
initialize(container) {
|
||||
const site = container.lookup("service:site");
|
||||
this.site = container.lookup("service:site");
|
||||
this.messageBus = container.lookup("service:message-bus");
|
||||
|
||||
const banner = EmberObject.create(PreloadStore.get("banner") || {});
|
||||
const messageBus = container.lookup("service:message-bus");
|
||||
this.site.set("banner", banner);
|
||||
|
||||
site.set("banner", banner);
|
||||
this.messageBus.subscribe("/site/banner", this.onMessage);
|
||||
},
|
||||
|
||||
messageBus.subscribe("/site/banner", (data) => {
|
||||
site.set("banner", EmberObject.create(data || {}));
|
||||
});
|
||||
teardown() {
|
||||
this.messageBus.unsubscribe("/site/banner", this.onMessage);
|
||||
},
|
||||
|
||||
@bind
|
||||
onMessage(data = {}) {
|
||||
this.site.set("banner", EmberObject.create(data));
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import DiscourseURL from "discourse/lib/url";
|
||||
import { isDevelopment } from "discourse-common/config/environment";
|
||||
import discourseLater from "discourse-common/lib/later";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
// Use the message bus for live reloading of components for faster development.
|
||||
export default {
|
||||
name: "live-development",
|
||||
|
||||
initialize(container) {
|
||||
const messageBus = container.lookup("service:message-bus");
|
||||
this.messageBus = container.lookup("service:message-bus");
|
||||
const session = container.lookup("service:session");
|
||||
|
||||
// Preserve preview_theme_id=## and pp=async-flamegraph parameters across pages
|
||||
|
@ -38,37 +39,46 @@ export default {
|
|||
}
|
||||
|
||||
// Observe file changes
|
||||
messageBus.subscribe(
|
||||
this.messageBus.subscribe(
|
||||
"/file-change",
|
||||
(data) => {
|
||||
data.forEach((me) => {
|
||||
if (me === "refresh") {
|
||||
// Refresh if necessary
|
||||
document.location.reload(true);
|
||||
} else if (me.new_href && me.target) {
|
||||
const link_target = !!me.theme_id
|
||||
? `[data-target='${me.target}'][data-theme-id='${me.theme_id}']`
|
||||
: `[data-target='${me.target}']`;
|
||||
|
||||
const links = document.querySelectorAll(`link${link_target}`);
|
||||
if (links.length > 0) {
|
||||
const lastLink = links[links.length - 1];
|
||||
// this check is useful when message-bus has multiple file updates
|
||||
// it avoids the browser doing a lot of work for nothing
|
||||
// should the filenames be unchanged
|
||||
if (
|
||||
lastLink.href.split("/").pop() !== me.new_href.split("/").pop()
|
||||
) {
|
||||
this.refreshCSS(lastLink, me.new_href);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
this.onFileChange,
|
||||
session.mbLastFileChangeId
|
||||
);
|
||||
},
|
||||
|
||||
teardown() {
|
||||
this.messageBus.unsubscribe("/file-change", this.onFileChange);
|
||||
},
|
||||
|
||||
@bind
|
||||
onFileChange(data) {
|
||||
data.forEach((me) => {
|
||||
if (me === "refresh") {
|
||||
// Refresh if necessary
|
||||
document.location.reload(true);
|
||||
} else if (me.new_href && me.target) {
|
||||
let query = `link[data-target='${me.target}']`;
|
||||
|
||||
if (me.theme_id) {
|
||||
query += `[data-theme-id='${me.theme_id}']`;
|
||||
}
|
||||
|
||||
const links = document.querySelectorAll(query);
|
||||
|
||||
if (links.length > 0) {
|
||||
const lastLink = links[links.length - 1];
|
||||
|
||||
// this check is useful when message-bus has multiple file updates
|
||||
// it avoids the browser doing a lot of work for nothing
|
||||
// should the filenames be unchanged
|
||||
if (lastLink.href.split("/").pop() !== me.new_href.split("/").pop()) {
|
||||
this.refreshCSS(lastLink, me.new_href);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
refreshCSS(node, newHref) {
|
||||
const reloaded = node.cloneNode(true);
|
||||
reloaded.href = newHref;
|
||||
|
|
|
@ -1,35 +1,43 @@
|
|||
import I18n from "I18n";
|
||||
import logout from "discourse/lib/logout";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
let _showingLogout = false;
|
||||
|
||||
// Subscribe to "logout" change events via the Message Bus
|
||||
// Subscribe to "logout" change events via the Message Bus
|
||||
export default {
|
||||
name: "logout",
|
||||
after: "message-bus",
|
||||
|
||||
initialize(container) {
|
||||
const messageBus = container.lookup("service:message-bus"),
|
||||
dialog = container.lookup("service:dialog");
|
||||
this.messageBus = container.lookup("service:message-bus");
|
||||
this.dialog = container.lookup("service:dialog");
|
||||
|
||||
if (!messageBus) {
|
||||
if (!this.messageBus) {
|
||||
return;
|
||||
}
|
||||
|
||||
messageBus.subscribe("/logout", function () {
|
||||
if (!_showingLogout) {
|
||||
_showingLogout = true;
|
||||
this.messageBus.subscribe("/logout", this.onMessage);
|
||||
},
|
||||
|
||||
dialog.alert({
|
||||
message: I18n.t("logout"),
|
||||
confirmButtonLabel: "home",
|
||||
didConfirm: logout,
|
||||
didCancel: logout,
|
||||
shouldDisplayCancel: false,
|
||||
});
|
||||
}
|
||||
teardown() {
|
||||
this.messageBus.unsubscribe("/logout", this.onMessage);
|
||||
},
|
||||
|
||||
_showingLogout = true;
|
||||
@bind
|
||||
onMessage() {
|
||||
if (_showingLogout) {
|
||||
return;
|
||||
}
|
||||
|
||||
_showingLogout = true;
|
||||
|
||||
this.dialog.alert({
|
||||
message: I18n.t("logout"),
|
||||
confirmButtonLabel: "home",
|
||||
didConfirm: logout,
|
||||
didCancel: logout,
|
||||
shouldDisplayCancel: false,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
// Subscribe to "read-only" status change events via the Message Bus
|
||||
export default {
|
||||
name: "read-only",
|
||||
after: "message-bus",
|
||||
|
||||
initialize(container) {
|
||||
const messageBus = container.lookup("service:message-bus");
|
||||
const site = container.lookup("service:site");
|
||||
messageBus.subscribe("/site/read-only", (enabled) => {
|
||||
site.set("isReadOnly", enabled);
|
||||
});
|
||||
this.messageBus = container.lookup("service:message-bus");
|
||||
this.site = container.lookup("service:site");
|
||||
|
||||
this.messageBus.subscribe("/site/read-only", this.onMessage);
|
||||
},
|
||||
|
||||
teardown() {
|
||||
this.messageBus.unsubscribe("/site/read-only", this.onMessage);
|
||||
},
|
||||
|
||||
@bind
|
||||
onMessage(enabled) {
|
||||
this.site.set("isReadOnly", enabled);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,15 +1,24 @@
|
|||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default {
|
||||
name: "welcome-topic-banner",
|
||||
after: "message-bus",
|
||||
|
||||
initialize(container) {
|
||||
const site = container.lookup("service:site");
|
||||
this.site = container.lookup("service:site");
|
||||
this.messageBus = container.lookup("service:message-bus");
|
||||
|
||||
if (site.show_welcome_topic_banner) {
|
||||
const messageBus = container.lookup("service:message-bus");
|
||||
messageBus.subscribe("/site/welcome-topic-banner", (disabled) => {
|
||||
site.set("show_welcome_topic_banner", disabled);
|
||||
});
|
||||
if (this.site.show_welcome_topic_banner) {
|
||||
this.messageBus.subscribe("/site/welcome-topic-banner", this.onMessage);
|
||||
}
|
||||
},
|
||||
|
||||
teardown() {
|
||||
this.messageBus.unsubscribe("/site/welcome-topic-banner", this.onMessage);
|
||||
},
|
||||
|
||||
@bind
|
||||
onMessage(disabled) {
|
||||
this.site.set("show_welcome_topic_banner", disabled);
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import EmberObject, { get } from "@ember/object";
|
||||
import discourseComputed, { bind, on } from "discourse-common/utils/decorators";
|
||||
import discourseComputed, { bind } from "discourse-common/utils/decorators";
|
||||
import Category from "discourse/models/category";
|
||||
import { deepEqual, deepMerge } from "discourse-common/lib/object";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
|
@ -46,13 +46,33 @@ function hasMutedTags(topicTags, mutedTags, siteSettings) {
|
|||
const TopicTrackingState = EmberObject.extend({
|
||||
messageCount: 0,
|
||||
|
||||
@on("init")
|
||||
_setup() {
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.states = new Map();
|
||||
this.stateChangeCallbacks = {};
|
||||
this._trackedTopicLimit = 4000;
|
||||
},
|
||||
|
||||
willDestroy() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.messageBus.unsubscribe("/latest", this._processChannelPayload);
|
||||
|
||||
if (this.currentUser) {
|
||||
this.messageBus.unsubscribe("/new", this._processChannelPayload);
|
||||
this.messageBus.unsubscribe(`/unread`, this._processChannelPayload);
|
||||
this.messageBus.unsubscribe(
|
||||
`/unread/${this.currentUser.id}`,
|
||||
this._processChannelPayload
|
||||
);
|
||||
}
|
||||
|
||||
this.messageBus.unsubscribe("/delete", this.onDeleteMessage);
|
||||
this.messageBus.unsubscribe("/recover", this.onRecoverMessage);
|
||||
this.messageBus.unsubscribe("/destroy", this.onDestroyMessage);
|
||||
},
|
||||
|
||||
/**
|
||||
* Subscribe to MessageBus channels which are used for publishing changes
|
||||
* to the tracking state. Each message received will modify state for
|
||||
|
@ -74,26 +94,34 @@ const TopicTrackingState = EmberObject.extend({
|
|||
);
|
||||
}
|
||||
|
||||
this.messageBus.subscribe("/delete", (msg) => {
|
||||
this.modifyStateProp(msg, "deleted", true);
|
||||
this.incrementMessageCount();
|
||||
});
|
||||
this.messageBus.subscribe("/delete", this.onDeleteMessage);
|
||||
this.messageBus.subscribe("/recover", this.onRecoverMessage);
|
||||
this.messageBus.subscribe("/destroy", this.onDestroyMessage);
|
||||
},
|
||||
|
||||
this.messageBus.subscribe("/recover", (msg) => {
|
||||
this.modifyStateProp(msg, "deleted", false);
|
||||
this.incrementMessageCount();
|
||||
});
|
||||
@bind
|
||||
onDeleteMessage(msg) {
|
||||
this.modifyStateProp(msg, "deleted", true);
|
||||
this.incrementMessageCount();
|
||||
},
|
||||
|
||||
this.messageBus.subscribe("/destroy", (msg) => {
|
||||
this.incrementMessageCount();
|
||||
const currentRoute = DiscourseURL.router.currentRoute.parent;
|
||||
if (
|
||||
currentRoute.name === "topic" &&
|
||||
parseInt(currentRoute.params.id, 10) === msg.topic_id
|
||||
) {
|
||||
DiscourseURL.redirectTo("/");
|
||||
}
|
||||
});
|
||||
@bind
|
||||
onRecoverMessage(msg) {
|
||||
this.modifyStateProp(msg, "deleted", false);
|
||||
this.incrementMessageCount();
|
||||
},
|
||||
|
||||
@bind
|
||||
onDestroyMessage(msg) {
|
||||
this.incrementMessageCount();
|
||||
const currentRoute = DiscourseURL.router.currentRoute.parent;
|
||||
|
||||
if (
|
||||
currentRoute.name === "topic" &&
|
||||
parseInt(currentRoute.params.id, 10) === msg.topic_id
|
||||
) {
|
||||
DiscourseURL.redirectTo("/");
|
||||
}
|
||||
},
|
||||
|
||||
mutedTopics() {
|
||||
|
@ -280,7 +308,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||
* @param {String} filter - Valid values are all, categories, and any topic list
|
||||
* filters e.g. latest, unread, new. As well as this
|
||||
* specific category and tag URLs like tag/test/l/latest,
|
||||
* c/cat/subcat/6/l/latest or tags/c/cat/subcat/6/test/l/latest.
|
||||
* c/cat/sub-cat/6/l/latest or tags/c/cat/sub-cat/6/test/l/latest.
|
||||
*/
|
||||
trackIncoming(filter) {
|
||||
this.newIncoming = [];
|
||||
|
@ -312,7 +340,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||
},
|
||||
|
||||
/**
|
||||
* Used to determine whether toshow the message at the top of the topic list
|
||||
* Used to determine whether to show the message at the top of the topic list
|
||||
* e.g. "see 1 new or updated topic"
|
||||
*
|
||||
* @method incomingCount
|
||||
|
@ -555,7 +583,7 @@ const TopicTrackingState = EmberObject.extend({
|
|||
},
|
||||
|
||||
/**
|
||||
* Using the array of tags provided, tallys up all topics via forEachTracked
|
||||
* Using the array of tags provided, tallies up all topics via forEachTracked
|
||||
* that we are tracking, separated into new/unread/total.
|
||||
*
|
||||
* Total is only counted if opts.includeTotal is specified.
|
||||
|
|
|
@ -40,13 +40,13 @@ export default {
|
|||
// to register a null value for anon
|
||||
app.register("service:current-user", currentUser, { instantiate: false });
|
||||
|
||||
const topicTrackingState = TopicTrackingState.create({
|
||||
this.topicTrackingState = TopicTrackingState.create({
|
||||
messageBus: container.lookup("service:message-bus"),
|
||||
siteSettings,
|
||||
currentUser,
|
||||
});
|
||||
|
||||
app.register("service:topic-tracking-state", topicTrackingState, {
|
||||
app.register("service:topic-tracking-state", this.topicTrackingState, {
|
||||
instantiate: false,
|
||||
});
|
||||
|
||||
|
@ -108,6 +108,11 @@ export default {
|
|||
});
|
||||
}
|
||||
|
||||
startTracking(topicTrackingState);
|
||||
startTracking(this.topicTrackingState);
|
||||
},
|
||||
|
||||
teardown() {
|
||||
// Manually call `willDestroy` as this isn't an actual `Service`
|
||||
this.topicTrackingState.willDestroy();
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
import { isPresent } from "@ember/utils";
|
||||
import { action } from "@ember/object";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default DiscourseRoute.extend({
|
||||
model(params) {
|
||||
|
@ -51,34 +52,9 @@ export default DiscourseRoute.extend({
|
|||
},
|
||||
|
||||
activate() {
|
||||
this._updateClaimedBy = (data) => {
|
||||
const reviewables = this.controller.reviewables;
|
||||
if (reviewables) {
|
||||
const user = data.user
|
||||
? this.store.createRecord("user", data.user)
|
||||
: null;
|
||||
reviewables.forEach((reviewable) => {
|
||||
if (data.topic_id === reviewable.topic.id) {
|
||||
reviewable.set("claimed_by", user);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this._updateReviewables = (data) => {
|
||||
if (data.updates) {
|
||||
this.controller.reviewables.forEach((reviewable) => {
|
||||
const updates = data.updates[reviewable.id];
|
||||
if (updates) {
|
||||
reviewable.setProperties(updates);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.messageBus.subscribe("/reviewable_claimed", this._updateClaimedBy);
|
||||
this.messageBus.subscribe(
|
||||
this._reviewableCountsChannel(),
|
||||
this._reviewableCountsChannel,
|
||||
this._updateReviewables
|
||||
);
|
||||
},
|
||||
|
@ -86,19 +62,46 @@ export default DiscourseRoute.extend({
|
|||
deactivate() {
|
||||
this.messageBus.unsubscribe("/reviewable_claimed", this._updateClaimedBy);
|
||||
this.messageBus.unsubscribe(
|
||||
this._reviewableCountsChannel(),
|
||||
this._reviewableCountsChannel,
|
||||
this._updateReviewables
|
||||
);
|
||||
},
|
||||
|
||||
@bind
|
||||
_updateClaimedBy(data) {
|
||||
const reviewables = this.controller.reviewables;
|
||||
if (reviewables) {
|
||||
const user = data.user
|
||||
? this.store.createRecord("user", data.user)
|
||||
: null;
|
||||
reviewables.forEach((reviewable) => {
|
||||
if (data.topic_id === reviewable.topic.id) {
|
||||
reviewable.set("claimed_by", user);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@bind
|
||||
_updateReviewables(data) {
|
||||
if (data.updates) {
|
||||
this.controller.reviewables.forEach((reviewable) => {
|
||||
const updates = data.updates[reviewable.id];
|
||||
if (updates) {
|
||||
reviewable.setProperties(updates);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
get _reviewableCountsChannel() {
|
||||
return this.currentUser.redesigned_user_menu_enabled
|
||||
? `/reviewable_counts/${this.currentUser.id}`
|
||||
: "/reviewable_counts";
|
||||
},
|
||||
|
||||
@action
|
||||
refreshRoute() {
|
||||
this.refresh();
|
||||
},
|
||||
|
||||
_reviewableCountsChannel() {
|
||||
return this.currentUser.redesigned_user_menu_enabled
|
||||
? `/reviewable_counts/${this.currentUser.id}`
|
||||
: "/reviewable_counts";
|
||||
},
|
||||
});
|
||||
|
|
|
@ -2,25 +2,9 @@ import DiscourseRoute from "discourse/routes/discourse";
|
|||
import I18n from "I18n";
|
||||
import User from "discourse/models/user";
|
||||
import { action } from "@ember/object";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
|
||||
export default DiscourseRoute.extend({
|
||||
titleToken() {
|
||||
const username = this.modelFor("user").username;
|
||||
if (username) {
|
||||
return [I18n.t("user.profile"), username];
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
undoRevokeApiKey(key) {
|
||||
key.undoRevoke();
|
||||
},
|
||||
|
||||
@action
|
||||
revokeApiKey(key) {
|
||||
key.revoke();
|
||||
},
|
||||
|
||||
beforeModel() {
|
||||
if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) {
|
||||
this.replaceWith("discovery");
|
||||
|
@ -68,29 +52,64 @@ export default DiscourseRoute.extend({
|
|||
this._super(...arguments);
|
||||
|
||||
const user = this.modelFor("user");
|
||||
this.messageBus.subscribe(`/u/${user.username_lower}`, (data) =>
|
||||
user.loadUserAction(data)
|
||||
this.messageBus.subscribe(`/u/${user.username_lower}`, this.onUserMessage);
|
||||
this.messageBus.subscribe(
|
||||
`/u/${user.username_lower}/counters`,
|
||||
this.onUserCountersMessage
|
||||
);
|
||||
this.messageBus.subscribe(`/u/${user.username_lower}/counters`, (data) => {
|
||||
user.setProperties(data);
|
||||
Object.entries(data).forEach(([key, value]) =>
|
||||
this.appEvents.trigger(
|
||||
`count-updated:${user.username_lower}:${key}`,
|
||||
value
|
||||
)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this._super(...arguments);
|
||||
|
||||
const user = this.modelFor("user");
|
||||
this.messageBus.unsubscribe(`/u/${user.username_lower}`);
|
||||
this.messageBus.unsubscribe(`/u/${user.username_lower}/counters`);
|
||||
this.messageBus.unsubscribe(
|
||||
`/u/${user.username_lower}`,
|
||||
this.onUserMessage
|
||||
);
|
||||
this.messageBus.unsubscribe(
|
||||
`/u/${user.username_lower}/counters`,
|
||||
this.onUserCountersMessage
|
||||
);
|
||||
user.stopTrackingStatus();
|
||||
|
||||
// Remove the search context
|
||||
this.searchService.set("searchContext", null);
|
||||
},
|
||||
|
||||
@bind
|
||||
onUserMessage(data) {
|
||||
const user = this.modelFor("user");
|
||||
return user.loadUserAction(data);
|
||||
},
|
||||
|
||||
@bind
|
||||
onUserCountersMessage(data) {
|
||||
const user = this.modelFor("user");
|
||||
user.setProperties(data);
|
||||
|
||||
Object.entries(data).forEach(([key, value]) =>
|
||||
this.appEvents.trigger(
|
||||
`count-updated:${user.username_lower}:${key}`,
|
||||
value
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
titleToken() {
|
||||
const username = this.modelFor("user").username;
|
||||
if (username) {
|
||||
return [I18n.t("user.profile"), username];
|
||||
}
|
||||
},
|
||||
|
||||
@action
|
||||
undoRevokeApiKey(key) {
|
||||
key.undoRevoke();
|
||||
},
|
||||
|
||||
@action
|
||||
revokeApiKey(key) {
|
||||
key.revoke();
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
||||
import discourseComputed, {
|
||||
bind,
|
||||
observes,
|
||||
} from "discourse-common/utils/decorators";
|
||||
import Service from "@ember/service";
|
||||
import I18n from "I18n";
|
||||
import { autoUpdatingRelativeAge } from "discourse/lib/formatter";
|
||||
|
@ -29,32 +32,41 @@ export default Service.extend({
|
|||
this.set("text", text);
|
||||
}
|
||||
|
||||
this.messageBus.subscribe("/logs_error_rate_exceeded", (data) => {
|
||||
const duration = data.duration;
|
||||
const rate = data.rate;
|
||||
let siteSettingLimit = 0;
|
||||
this.messageBus.subscribe("/logs_error_rate_exceeded", this.onLogRateLimit);
|
||||
},
|
||||
|
||||
if (duration === "minute") {
|
||||
siteSettingLimit = this.siteSettings.alert_admins_if_errors_per_minute;
|
||||
} else if (duration === "hour") {
|
||||
siteSettingLimit = this.siteSettings.alert_admins_if_errors_per_hour;
|
||||
}
|
||||
willDestroy() {
|
||||
this._super(...arguments);
|
||||
|
||||
let translationKey = rate === siteSettingLimit ? "reached" : "exceeded";
|
||||
translationKey += `_${duration}_MF`;
|
||||
this.messageBus.unsubscribe(
|
||||
"/logs_error_rate_exceeded",
|
||||
this.onLogRateLimit
|
||||
);
|
||||
},
|
||||
|
||||
this.set(
|
||||
"text",
|
||||
I18n.messageFormat(`logs_error_rate_notice.${translationKey}`, {
|
||||
relativeAge: autoUpdatingRelativeAge(
|
||||
new Date(data.publish_at * 1000)
|
||||
),
|
||||
rate,
|
||||
limit: siteSettingLimit,
|
||||
url: getURL("/logs"),
|
||||
})
|
||||
);
|
||||
});
|
||||
@bind
|
||||
onLogRateLimit(data) {
|
||||
const { duration, rate } = data;
|
||||
let siteSettingLimit = 0;
|
||||
|
||||
if (duration === "minute") {
|
||||
siteSettingLimit = this.siteSettings.alert_admins_if_errors_per_minute;
|
||||
} else if (duration === "hour") {
|
||||
siteSettingLimit = this.siteSettings.alert_admins_if_errors_per_hour;
|
||||
}
|
||||
|
||||
let translationKey = rate === siteSettingLimit ? "reached" : "exceeded";
|
||||
translationKey += `_${duration}_MF`;
|
||||
|
||||
this.set(
|
||||
"text",
|
||||
I18n.messageFormat(`logs_error_rate_notice.${translationKey}`, {
|
||||
relativeAge: autoUpdatingRelativeAge(new Date(data.publish_at * 1000)),
|
||||
rate,
|
||||
limit: siteSettingLimit,
|
||||
url: getURL("/logs"),
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
@discourseComputed("text")
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { Promise } from "rsvp";
|
||||
|
||||
import Service from "@ember/service";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { bind, on } from "discourse-common/utils/decorators";
|
||||
import { bind } from "discourse-common/utils/decorators";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import { deepEqual, deepMerge } from "discourse-common/lib/object";
|
||||
import {
|
||||
|
@ -21,8 +20,9 @@ const PrivateMessageTopicTrackingState = Service.extend({
|
|||
filter: null,
|
||||
activeGroup: null,
|
||||
|
||||
@on("init")
|
||||
_setup() {
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.states = new Map();
|
||||
this.statesModificationCounter = 0;
|
||||
this.isTracking = false;
|
||||
|
@ -30,6 +30,16 @@ const PrivateMessageTopicTrackingState = Service.extend({
|
|||
this.stateChangeCallbacks = new Map();
|
||||
},
|
||||
|
||||
willDestroy() {
|
||||
this._super(...arguments);
|
||||
|
||||
if (this.currentUser) {
|
||||
this.messageBus.unsubscribe(this.userChannel(), this._processMessage);
|
||||
}
|
||||
|
||||
this.messageBus.unsubscribe(this.groupChannel("*"), this._processMessage);
|
||||
},
|
||||
|
||||
onStateChange(key, callback) {
|
||||
this.stateChangeCallbacks.set(key, callback);
|
||||
},
|
||||
|
@ -43,14 +53,6 @@ const PrivateMessageTopicTrackingState = Service.extend({
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
this._establishChannels();
|
||||
|
||||
return this._loadInitialState().finally(() => {
|
||||
this.set("isTracking", true);
|
||||
});
|
||||
},
|
||||
|
||||
_establishChannels() {
|
||||
this.messageBus.subscribe(this.userChannel(), this._processMessage);
|
||||
|
||||
this.currentUser.groupsWithMessages?.forEach((group) => {
|
||||
|
@ -59,6 +61,10 @@ const PrivateMessageTopicTrackingState = Service.extend({
|
|||
this._processMessage
|
||||
);
|
||||
});
|
||||
|
||||
return this._loadInitialState().finally(() => {
|
||||
this.set("isTracking", true);
|
||||
});
|
||||
},
|
||||
|
||||
lookupCount(type, opts = {}) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { debounce } from "discourse-common/utils/decorators";
|
||||
import { bind, debounce } from "discourse-common/utils/decorators";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { headerOffset } from "discourse/lib/offset-calculator";
|
||||
import isElementInViewport from "discourse/lib/is-element-in-viewport";
|
||||
|
@ -43,35 +43,32 @@ function initialize(api) {
|
|||
return this._super(bookmark, post);
|
||||
},
|
||||
|
||||
subscribe() {
|
||||
@bind
|
||||
onMessage(data) {
|
||||
this._super(...arguments);
|
||||
|
||||
this.messageBus.subscribe(`/topic/${this.model.id}`, (data) => {
|
||||
const topic = this.model;
|
||||
const topic = this.model;
|
||||
|
||||
// scroll only for discobot (-2 is discobot id)
|
||||
if (
|
||||
topic.isPrivateMessage &&
|
||||
this.currentUser &&
|
||||
this.currentUser.id !== data.user_id &&
|
||||
data.user_id === -2 &&
|
||||
data.type === "created"
|
||||
) {
|
||||
const postNumber = data.post_number;
|
||||
const notInPostStream = topic.get("highest_post_number") <= postNumber;
|
||||
const postNumberDifference = postNumber - topic.currentPost;
|
||||
|
||||
// scroll only for discobot (-2 is discobot id)
|
||||
if (
|
||||
topic.isPrivateMessage &&
|
||||
this.currentUser &&
|
||||
this.currentUser.id !== data.user_id &&
|
||||
data.user_id === -2 &&
|
||||
data.type === "created"
|
||||
notInPostStream &&
|
||||
postNumberDifference > 0 &&
|
||||
postNumberDifference < 7
|
||||
) {
|
||||
const postNumber = data.post_number;
|
||||
const notInPostStream =
|
||||
topic.get("highest_post_number") <= postNumber;
|
||||
const postNumberDifference = postNumber - topic.currentPost;
|
||||
|
||||
if (
|
||||
notInPostStream &&
|
||||
postNumberDifference > 0 &&
|
||||
postNumberDifference < 7
|
||||
) {
|
||||
this._scrollToDiscobotPost(data.post_number);
|
||||
}
|
||||
this._scrollToDiscobotPost(data.post_number);
|
||||
}
|
||||
});
|
||||
// No need to unsubscribe, core unsubscribes /topic/* routes
|
||||
}
|
||||
},
|
||||
|
||||
@debounce(500)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import EmberObject from "@ember/object";
|
||||
import WidgetGlue from "discourse/widgets/glue";
|
||||
import { getRegister } from "discourse-common/lib/get-owner";
|
||||
import { observes } from "discourse-common/utils/decorators";
|
||||
import { bind, observes } from "discourse-common/utils/decorators";
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
|
||||
const PLUGIN_ID = "discourse-poll";
|
||||
|
@ -34,16 +34,19 @@ function initializePolls(api) {
|
|||
|
||||
subscribe() {
|
||||
this._super(...arguments);
|
||||
this.messageBus.subscribe(`/polls/${this.model.id}`, (msg) => {
|
||||
const post = this.get("model.postStream").findLoadedPost(msg.post_id);
|
||||
post?.set("polls", msg.polls);
|
||||
});
|
||||
this.messageBus.subscribe(`/polls/${this.model.id}`, this._onPollMessage);
|
||||
},
|
||||
|
||||
unsubscribe() {
|
||||
this.messageBus.unsubscribe("/polls/*");
|
||||
this.messageBus.unsubscribe("/polls/*", this._onPollMessage);
|
||||
this._super(...arguments);
|
||||
},
|
||||
|
||||
@bind
|
||||
_onPollMessage(msg) {
|
||||
const post = this.get("model.postStream").findLoadedPost(msg.post_id);
|
||||
post?.set("polls", msg.polls);
|
||||
},
|
||||
});
|
||||
|
||||
api.modifyClass("model:post", {
|
||||
|
|
Loading…
Reference in New Issue