Revert "DEV: Clean up all message bus subscriptions (#18675)" (#19267)

This reverts commit b0839ccf27.
This commit is contained in:
Jarek Radosz 2022-11-30 17:29:10 +01:00 committed by GitHub
parent 6a389fd15a
commit 49e0fc04f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 496 additions and 624 deletions

View File

@ -2,7 +2,7 @@ import Controller from "@ember/controller";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { alias } from "@ember/object/computed"; import { alias } from "@ember/object/computed";
import discourseComputed, { bind } from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
export default Controller.extend({ export default Controller.extend({
@ -23,22 +23,24 @@ export default Controller.extend({
subscribe() { subscribe() {
this.messageBus.subscribe( this.messageBus.subscribe(
`/web_hook_events/${this.get("model.extras.web_hook_id")}`, `/web_hook_events/${this.get("model.extras.web_hook_id")}`,
this._addIncoming (data) => {
if (data.event_type === "ping") {
this.set("pingDisabled", false);
}
this._addIncoming(data.web_hook_event_id);
}
); );
}, },
unsubscribe() { unsubscribe() {
this.messageBus.unsubscribe("/web_hook_events/*", this._addIncoming); this.messageBus.unsubscribe("/web_hook_events/*");
}, },
@bind _addIncoming(eventId) {
_addIncoming(data) { const incomingEventIds = this.incomingEventIds;
if (data.event_type === "ping") {
this.set("pingDisabled", false);
}
if (!this.incomingEventIds.includes(data.web_hook_event_id)) { if (!incomingEventIds.includes(eventId)) {
this.incomingEventIds.pushObject(data.web_hook_event_id); incomingEventIds.pushObject(eventId);
} }
}, },

View File

@ -2,33 +2,30 @@ import Controller from "@ember/controller";
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
import I18n from "I18n"; import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import { bind } from "discourse-common/utils/decorators"; import messageBus from "message-bus-client";
export default Controller.extend(ModalFunctionality, { export default Controller.extend(ModalFunctionality, {
message: I18n.t("admin.user.merging_user"), message: I18n.t("admin.user.merging_user"),
onShow() { onShow() {
this.messageBus.subscribe("/merge_user", this.onMessage); 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"));
}
});
}, },
onClose() { onClose() {
this.messageBus.unsubscribe("/merge_user", this.onMessage); this.messageBus.unsubscribe("/merge_user");
},
@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"));
}
}, },
}); });

View File

@ -1,14 +1,14 @@
import Backup from "admin/models/backup"; import Backup from "admin/models/backup";
import Route from "@ember/routing/route"; import Route from "@ember/routing/route";
import { bind } from "discourse-common/utils/decorators";
export default Route.extend({ export default Route.extend({
activate() { activate() {
this.messageBus.subscribe("/admin/backups", this.onMessage); this.messageBus.subscribe("/admin/backups", (backups) =>
}, this.controller.set(
"model",
deactivate() { backups.map((backup) => Backup.create(backup))
this.messageBus.unsubscribe("/admin/backups", this.onMessage); )
);
}, },
model() { model() {
@ -17,11 +17,7 @@ export default Route.extend({
); );
}, },
@bind deactivate() {
onMessage(backups) { this.messageBus.unsubscribe("/admin/backups");
this.controller.set(
"model",
backups.map((backup) => Backup.create(backup))
);
}, },
}); });

View File

@ -10,19 +10,46 @@ import { extractError } from "discourse/lib/ajax-error";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { bind } from "discourse-common/utils/decorators";
const LOG_CHANNEL = "/admin/backups/logs"; const LOG_CHANNEL = "/admin/backups/logs";
export default DiscourseRoute.extend({ export default DiscourseRoute.extend({
dialog: service(), dialog: service(),
activate() { activate() {
this.messageBus.subscribe(LOG_CHANNEL, this.onMessage); this.messageBus.subscribe(LOG_CHANNEL, (log) => {
}, if (log.message === "[STARTED]") {
User.currentProp("hideReadOnlyAlert", true);
deactivate() { this.controllerFor("adminBackups").set(
this.messageBus.unsubscribe(LOG_CHANNEL, this.onMessage); "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));
}
});
}, },
model() { model() {
@ -37,31 +64,8 @@ export default DiscourseRoute.extend({
); );
}, },
@bind deactivate() {
onMessage(log) { this.messageBus.unsubscribe(LOG_CHANNEL);
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: { actions: {

View File

@ -1,8 +1,5 @@
import { alias, not } from "@ember/object/computed"; import { alias, not } from "@ember/object/computed";
import discourseComputed, { import discourseComputed, { observes } from "discourse-common/utils/decorators";
bind,
observes,
} from "discourse-common/utils/decorators";
import Component from "@ember/component"; import Component from "@ember/component";
export default Component.extend({ export default Component.extend({
@ -43,11 +40,18 @@ export default Component.extend({
this._super(...arguments); this._super(...arguments);
this.topics.forEach((topic) => { this.topics.forEach((topic) => {
if (typeof topic.unread_by_group_member !== "undefined") { const includeUnreadIndicator =
this.messageBus.subscribe( typeof topic.unread_by_group_member !== "undefined";
`/private-messages/unread-indicator/${topic.id}`,
this.onMessage 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);
});
} }
}); });
}, },
@ -55,19 +59,15 @@ export default Component.extend({
willDestroyElement() { willDestroyElement() {
this._super(...arguments); this._super(...arguments);
this.messageBus.unsubscribe( this.topics.forEach((topic) => {
"/private-messages/unread-indicator/*", const includeUnreadIndicator =
this.onMessage typeof topic.unread_by_group_member !== "undefined";
);
},
@bind if (includeUnreadIndicator) {
onMessage(data) { const unreadIndicatorChannel = `/private-messages/unread-indicator/${topic.id}`;
const nodeClassList = document.querySelector( this.messageBus.unsubscribe(unreadIndicatorChannel);
`.indicator-topic-${data.topic_id}` }
).classList; });
nodeClassList.toggle("read", !data.show_indicator);
}, },
@discourseComputed("topics") @discourseComputed("topics")

View File

@ -1,7 +1,7 @@
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import { cancel } from "@ember/runloop"; import { cancel } from "@ember/runloop";
import discourseLater from "discourse-common/lib/later"; import discourseLater from "discourse-common/lib/later";
import discourseComputed, { bind, on } from "discourse-common/utils/decorators"; import discourseComputed, { on } from "discourse-common/utils/decorators";
import Component from "@ember/component"; import Component from "@ember/component";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { isTesting } from "discourse-common/config/environment"; import { isTesting } from "discourse-common/config/environment";
@ -13,49 +13,36 @@ export default Component.extend({
animatePrompt: false, animatePrompt: false,
_timeoutHandler: null, _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 @discourseComputed
rootUrl() { rootUrl() {
return getURL("/"); 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) { updatePromptState(value) {
// when adding the message, we inject the HTML then add the animation // when adding the message, we inject the HTML then add the animation
// when dismissing, things need to happen in the opposite order // when dismissing, things need to happen in the opposite order

View File

@ -77,7 +77,13 @@ export default Component.extend({
this._super(...arguments); this._super(...arguments);
if (this.includeUnreadIndicator) { if (this.includeUnreadIndicator) {
this.messageBus.subscribe(this.unreadIndicatorChannel, this.onMessage); this.messageBus.subscribe(this.unreadIndicatorChannel, (data) => {
const nodeClassList = document.querySelector(
`.indicator-topic-${data.topic_id}`
).classList;
nodeClassList.toggle("read", !data.show_indicator);
});
} }
schedule("afterRender", () => { schedule("afterRender", () => {
@ -95,8 +101,9 @@ export default Component.extend({
willDestroyElement() { willDestroyElement() {
this._super(...arguments); this._super(...arguments);
this.messageBus.unsubscribe(this.unreadIndicatorChannel, this.onMessage); if (this.includeUnreadIndicator) {
this.messageBus.unsubscribe(this.unreadIndicatorChannel);
}
if (this._shouldFocusLastVisited()) { if (this._shouldFocusLastVisited()) {
const title = this._titleElement(); const title = this._titleElement();
if (title) { if (title) {
@ -106,15 +113,6 @@ 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") @discourseComputed("topic.id")
unreadIndicatorChannel(topicId) { unreadIndicatorChannel(topicId) {
return `/private-messages/unread-indicator/${topicId}`; return `/private-messages/unread-indicator/${topicId}`;

View File

@ -2,10 +2,7 @@ import Category from "discourse/models/category";
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import DiscourseURL, { userPath } from "discourse/lib/url"; import DiscourseURL, { userPath } from "discourse/lib/url";
import { alias, and, not, or } from "@ember/object/computed"; import { alias, and, not, or } from "@ember/object/computed";
import discourseComputed, { import discourseComputed, { observes } from "discourse-common/utils/decorators";
bind,
observes,
} from "discourse-common/utils/decorators";
import { isEmpty, isPresent } from "@ember/utils"; import { isEmpty, isPresent } from "@ember/utils";
import { next, schedule } from "@ember/runloop"; import { next, schedule } from "@ember/runloop";
import discourseLater from "discourse-common/lib/later"; import discourseLater from "discourse-common/lib/later";
@ -1602,9 +1599,157 @@ export default Controller.extend(bufferedProperty("model"), {
subscribe() { subscribe() {
this.unsubscribe(); this.unsubscribe();
const refresh = (args) =>
this.appEvents.trigger("post-stream:refresh", args);
this.messageBus.subscribe( this.messageBus.subscribe(
`/topic/${this.get("model.id")}`, `/topic/${this.get("model.id")}`,
this.onMessage, (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.get("model.message_bus_last_id") this.get("model.message_bus_last_id")
); );
}, },
@ -1614,148 +1759,9 @@ export default Controller.extend(bufferedProperty("model"), {
if (!this.get("model.id")) { if (!this.get("model.id")) {
return; return;
} }
this.messageBus.unsubscribe("/topic/*"); 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() { reply() {
this.replyToPost(); this.replyToPost();
}, },

View File

@ -1,5 +1,4 @@
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import { bind } from "discourse-common/utils/decorators";
import PreloadStore from "discourse/lib/preload-store"; import PreloadStore from "discourse/lib/preload-store";
export default { export default {
@ -7,21 +6,14 @@ export default {
after: "message-bus", after: "message-bus",
initialize(container) { initialize(container) {
this.site = container.lookup("service:site"); const site = container.lookup("service:site");
this.messageBus = container.lookup("service:message-bus");
const banner = EmberObject.create(PreloadStore.get("banner") || {}); const banner = EmberObject.create(PreloadStore.get("banner") || {});
this.site.set("banner", banner); const messageBus = container.lookup("service:message-bus");
this.messageBus.subscribe("/site/banner", this.onMessage); site.set("banner", banner);
},
teardown() { messageBus.subscribe("/site/banner", (data) => {
this.messageBus.unsubscribe("/site/banner", this.onMessage); site.set("banner", EmberObject.create(data || {}));
}, });
@bind
onMessage(data = {}) {
this.site.set("banner", EmberObject.create(data));
}, },
}; };

View File

@ -1,14 +1,13 @@
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
import { isDevelopment } from "discourse-common/config/environment"; import { isDevelopment } from "discourse-common/config/environment";
import discourseLater from "discourse-common/lib/later"; 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. // Use the message bus for live reloading of components for faster development.
export default { export default {
name: "live-development", name: "live-development",
initialize(container) { initialize(container) {
this.messageBus = container.lookup("service:message-bus"); const messageBus = container.lookup("service:message-bus");
const session = container.lookup("service:session"); const session = container.lookup("service:session");
// Preserve preview_theme_id=## and pp=async-flamegraph parameters across pages // Preserve preview_theme_id=## and pp=async-flamegraph parameters across pages
@ -39,46 +38,37 @@ export default {
} }
// Observe file changes // Observe file changes
this.messageBus.subscribe( messageBus.subscribe(
"/file-change", "/file-change",
this.onFileChange, (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);
}
}
}
});
},
session.mbLastFileChangeId 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) { refreshCSS(node, newHref) {
const reloaded = node.cloneNode(true); const reloaded = node.cloneNode(true);
reloaded.href = newHref; reloaded.href = newHref;

View File

@ -1,43 +1,35 @@
import I18n from "I18n"; import I18n from "I18n";
import logout from "discourse/lib/logout"; import logout from "discourse/lib/logout";
import { bind } from "discourse-common/utils/decorators";
let _showingLogout = false; let _showingLogout = false;
// Subscribe to "logout" change events via the Message Bus // Subscribe to "logout" change events via the Message Bus
export default { export default {
name: "logout", name: "logout",
after: "message-bus", after: "message-bus",
initialize(container) { initialize(container) {
this.messageBus = container.lookup("service:message-bus"); const messageBus = container.lookup("service:message-bus"),
this.dialog = container.lookup("service:dialog"); dialog = container.lookup("service:dialog");
if (!this.messageBus) { if (!messageBus) {
return; return;
} }
this.messageBus.subscribe("/logout", this.onMessage); messageBus.subscribe("/logout", function () {
}, if (!_showingLogout) {
_showingLogout = true;
teardown() { dialog.alert({
this.messageBus.unsubscribe("/logout", this.onMessage); message: I18n.t("logout"),
}, confirmButtonLabel: "home",
didConfirm: logout,
didCancel: logout,
shouldDisplayCancel: false,
});
}
@bind _showingLogout = true;
onMessage() {
if (_showingLogout) {
return;
}
_showingLogout = true;
this.dialog.alert({
message: I18n.t("logout"),
confirmButtonLabel: "home",
didConfirm: logout,
didCancel: logout,
shouldDisplayCancel: false,
}); });
}, },
}; };

View File

@ -1,23 +1,13 @@
import { bind } from "discourse-common/utils/decorators";
// Subscribe to "read-only" status change events via the Message Bus // Subscribe to "read-only" status change events via the Message Bus
export default { export default {
name: "read-only", name: "read-only",
after: "message-bus", after: "message-bus",
initialize(container) { initialize(container) {
this.messageBus = container.lookup("service:message-bus"); const messageBus = container.lookup("service:message-bus");
this.site = container.lookup("service:site"); const site = container.lookup("service:site");
messageBus.subscribe("/site/read-only", (enabled) => {
this.messageBus.subscribe("/site/read-only", this.onMessage); site.set("isReadOnly", enabled);
}, });
teardown() {
this.messageBus.unsubscribe("/site/read-only", this.onMessage);
},
@bind
onMessage(enabled) {
this.site.set("isReadOnly", enabled);
}, },
}; };

View File

@ -1,24 +1,15 @@
import { bind } from "discourse-common/utils/decorators";
export default { export default {
name: "welcome-topic-banner", name: "welcome-topic-banner",
after: "message-bus", after: "message-bus",
initialize(container) { initialize(container) {
this.site = container.lookup("service:site"); const site = container.lookup("service:site");
this.messageBus = container.lookup("service:message-bus");
if (this.site.show_welcome_topic_banner) { if (site.show_welcome_topic_banner) {
this.messageBus.subscribe("/site/welcome-topic-banner", this.onMessage); const messageBus = container.lookup("service:message-bus");
messageBus.subscribe("/site/welcome-topic-banner", (disabled) => {
site.set("show_welcome_topic_banner", disabled);
});
} }
}, },
teardown() {
this.messageBus.unsubscribe("/site/welcome-topic-banner", this.onMessage);
},
@bind
onMessage(disabled) {
this.site.set("show_welcome_topic_banner", disabled);
},
}; };

View File

@ -1,5 +1,5 @@
import EmberObject, { get } from "@ember/object"; import EmberObject, { get } from "@ember/object";
import discourseComputed, { bind } from "discourse-common/utils/decorators"; import discourseComputed, { bind, on } from "discourse-common/utils/decorators";
import Category from "discourse/models/category"; import Category from "discourse/models/category";
import { deepEqual, deepMerge } from "discourse-common/lib/object"; import { deepEqual, deepMerge } from "discourse-common/lib/object";
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
@ -46,33 +46,13 @@ function hasMutedTags(topicTags, mutedTags, siteSettings) {
const TopicTrackingState = EmberObject.extend({ const TopicTrackingState = EmberObject.extend({
messageCount: 0, messageCount: 0,
init() { @on("init")
this._super(...arguments); _setup() {
this.states = new Map(); this.states = new Map();
this.stateChangeCallbacks = {}; this.stateChangeCallbacks = {};
this._trackedTopicLimit = 4000; 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 * Subscribe to MessageBus channels which are used for publishing changes
* to the tracking state. Each message received will modify state for * to the tracking state. Each message received will modify state for
@ -94,34 +74,26 @@ const TopicTrackingState = EmberObject.extend({
); );
} }
this.messageBus.subscribe("/delete", this.onDeleteMessage); this.messageBus.subscribe("/delete", (msg) => {
this.messageBus.subscribe("/recover", this.onRecoverMessage); this.modifyStateProp(msg, "deleted", true);
this.messageBus.subscribe("/destroy", this.onDestroyMessage); this.incrementMessageCount();
}, });
@bind this.messageBus.subscribe("/recover", (msg) => {
onDeleteMessage(msg) { this.modifyStateProp(msg, "deleted", false);
this.modifyStateProp(msg, "deleted", true); this.incrementMessageCount();
this.incrementMessageCount(); });
},
@bind this.messageBus.subscribe("/destroy", (msg) => {
onRecoverMessage(msg) { this.incrementMessageCount();
this.modifyStateProp(msg, "deleted", false); const currentRoute = DiscourseURL.router.currentRoute.parent;
this.incrementMessageCount(); if (
}, currentRoute.name === "topic" &&
parseInt(currentRoute.params.id, 10) === msg.topic_id
@bind ) {
onDestroyMessage(msg) { DiscourseURL.redirectTo("/");
this.incrementMessageCount(); }
const currentRoute = DiscourseURL.router.currentRoute.parent; });
if (
currentRoute.name === "topic" &&
parseInt(currentRoute.params.id, 10) === msg.topic_id
) {
DiscourseURL.redirectTo("/");
}
}, },
mutedTopics() { mutedTopics() {
@ -308,7 +280,7 @@ const TopicTrackingState = EmberObject.extend({
* @param {String} filter - Valid values are all, categories, and any topic list * @param {String} filter - Valid values are all, categories, and any topic list
* filters e.g. latest, unread, new. As well as this * filters e.g. latest, unread, new. As well as this
* specific category and tag URLs like tag/test/l/latest, * specific category and tag URLs like tag/test/l/latest,
* c/cat/sub-cat/6/l/latest or tags/c/cat/sub-cat/6/test/l/latest. * c/cat/subcat/6/l/latest or tags/c/cat/subcat/6/test/l/latest.
*/ */
trackIncoming(filter) { trackIncoming(filter) {
this.newIncoming = []; this.newIncoming = [];
@ -340,7 +312,7 @@ const TopicTrackingState = EmberObject.extend({
}, },
/** /**
* Used to determine whether to show the message at the top of the topic list * Used to determine whether toshow the message at the top of the topic list
* e.g. "see 1 new or updated topic" * e.g. "see 1 new or updated topic"
* *
* @method incomingCount * @method incomingCount
@ -583,7 +555,7 @@ const TopicTrackingState = EmberObject.extend({
}, },
/** /**
* Using the array of tags provided, tallies up all topics via forEachTracked * Using the array of tags provided, tallys up all topics via forEachTracked
* that we are tracking, separated into new/unread/total. * that we are tracking, separated into new/unread/total.
* *
* Total is only counted if opts.includeTotal is specified. * Total is only counted if opts.includeTotal is specified.

View File

@ -40,13 +40,13 @@ export default {
// to register a null value for anon // to register a null value for anon
app.register("service:current-user", currentUser, { instantiate: false }); app.register("service:current-user", currentUser, { instantiate: false });
this.topicTrackingState = TopicTrackingState.create({ const topicTrackingState = TopicTrackingState.create({
messageBus: container.lookup("service:message-bus"), messageBus: container.lookup("service:message-bus"),
siteSettings, siteSettings,
currentUser, currentUser,
}); });
app.register("service:topic-tracking-state", this.topicTrackingState, { app.register("service:topic-tracking-state", topicTrackingState, {
instantiate: false, instantiate: false,
}); });
@ -108,11 +108,6 @@ export default {
}); });
} }
startTracking(this.topicTrackingState); startTracking(topicTrackingState);
},
teardown() {
// Manually call `willDestroy` as this isn't an actual `Service`
this.topicTrackingState.willDestroy();
}, },
}; };

View File

@ -1,7 +1,6 @@
import DiscourseRoute from "discourse/routes/discourse"; import DiscourseRoute from "discourse/routes/discourse";
import { isPresent } from "@ember/utils"; import { isPresent } from "@ember/utils";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { bind } from "discourse-common/utils/decorators";
export default DiscourseRoute.extend({ export default DiscourseRoute.extend({
model(params) { model(params) {
@ -52,9 +51,34 @@ export default DiscourseRoute.extend({
}, },
activate() { 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("/reviewable_claimed", this._updateClaimedBy);
this.messageBus.subscribe( this.messageBus.subscribe(
this._reviewableCountsChannel, this._reviewableCountsChannel(),
this._updateReviewables this._updateReviewables
); );
}, },
@ -62,46 +86,19 @@ export default DiscourseRoute.extend({
deactivate() { deactivate() {
this.messageBus.unsubscribe("/reviewable_claimed", this._updateClaimedBy); this.messageBus.unsubscribe("/reviewable_claimed", this._updateClaimedBy);
this.messageBus.unsubscribe( this.messageBus.unsubscribe(
this._reviewableCountsChannel, this._reviewableCountsChannel(),
this._updateReviewables 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 @action
refreshRoute() { refreshRoute() {
this.refresh(); this.refresh();
}, },
_reviewableCountsChannel() {
return this.currentUser.redesigned_user_menu_enabled
? `/reviewable_counts/${this.currentUser.id}`
: "/reviewable_counts";
},
}); });

View File

@ -2,9 +2,25 @@ import DiscourseRoute from "discourse/routes/discourse";
import I18n from "I18n"; import I18n from "I18n";
import User from "discourse/models/user"; import User from "discourse/models/user";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { bind } from "discourse-common/utils/decorators";
export default DiscourseRoute.extend({ 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() { beforeModel() {
if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) { if (this.siteSettings.hide_user_profiles_from_public && !this.currentUser) {
this.replaceWith("discovery"); this.replaceWith("discovery");
@ -52,64 +68,29 @@ export default DiscourseRoute.extend({
this._super(...arguments); this._super(...arguments);
const user = this.modelFor("user"); const user = this.modelFor("user");
this.messageBus.subscribe(`/u/${user.username_lower}`, this.onUserMessage); this.messageBus.subscribe(`/u/${user.username_lower}`, (data) =>
this.messageBus.subscribe( user.loadUserAction(data)
`/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() { deactivate() {
this._super(...arguments); this._super(...arguments);
const user = this.modelFor("user"); const user = this.modelFor("user");
this.messageBus.unsubscribe( this.messageBus.unsubscribe(`/u/${user.username_lower}`);
`/u/${user.username_lower}`, this.messageBus.unsubscribe(`/u/${user.username_lower}/counters`);
this.onUserMessage
);
this.messageBus.unsubscribe(
`/u/${user.username_lower}/counters`,
this.onUserCountersMessage
);
user.stopTrackingStatus(); user.stopTrackingStatus();
// Remove the search context // Remove the search context
this.searchService.set("searchContext", null); 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();
},
}); });

View File

@ -1,7 +1,4 @@
import discourseComputed, { import discourseComputed, { observes } from "discourse-common/utils/decorators";
bind,
observes,
} from "discourse-common/utils/decorators";
import Service from "@ember/service"; import Service from "@ember/service";
import I18n from "I18n"; import I18n from "I18n";
import { autoUpdatingRelativeAge } from "discourse/lib/formatter"; import { autoUpdatingRelativeAge } from "discourse/lib/formatter";
@ -32,41 +29,32 @@ export default Service.extend({
this.set("text", text); this.set("text", text);
} }
this.messageBus.subscribe("/logs_error_rate_exceeded", this.onLogRateLimit); this.messageBus.subscribe("/logs_error_rate_exceeded", (data) => {
}, const duration = data.duration;
const rate = data.rate;
let siteSettingLimit = 0;
willDestroy() { if (duration === "minute") {
this._super(...arguments); siteSettingLimit = this.siteSettings.alert_admins_if_errors_per_minute;
} else if (duration === "hour") {
siteSettingLimit = this.siteSettings.alert_admins_if_errors_per_hour;
}
this.messageBus.unsubscribe( let translationKey = rate === siteSettingLimit ? "reached" : "exceeded";
"/logs_error_rate_exceeded", translationKey += `_${duration}_MF`;
this.onLogRateLimit
);
},
@bind this.set(
onLogRateLimit(data) { "text",
const { duration, rate } = data; I18n.messageFormat(`logs_error_rate_notice.${translationKey}`, {
let siteSettingLimit = 0; relativeAge: autoUpdatingRelativeAge(
new Date(data.publish_at * 1000)
if (duration === "minute") { ),
siteSettingLimit = this.siteSettings.alert_admins_if_errors_per_minute; rate,
} else if (duration === "hour") { limit: siteSettingLimit,
siteSettingLimit = this.siteSettings.alert_admins_if_errors_per_hour; url: getURL("/logs"),
} })
);
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") @discourseComputed("text")

View File

@ -1,7 +1,8 @@
import { Promise } from "rsvp"; import { Promise } from "rsvp";
import Service from "@ember/service"; import Service from "@ember/service";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { bind } from "discourse-common/utils/decorators"; import { bind, on } from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { deepEqual, deepMerge } from "discourse-common/lib/object"; import { deepEqual, deepMerge } from "discourse-common/lib/object";
import { import {
@ -20,9 +21,8 @@ const PrivateMessageTopicTrackingState = Service.extend({
filter: null, filter: null,
activeGroup: null, activeGroup: null,
init() { @on("init")
this._super(...arguments); _setup() {
this.states = new Map(); this.states = new Map();
this.statesModificationCounter = 0; this.statesModificationCounter = 0;
this.isTracking = false; this.isTracking = false;
@ -30,16 +30,6 @@ const PrivateMessageTopicTrackingState = Service.extend({
this.stateChangeCallbacks = new Map(); 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) { onStateChange(key, callback) {
this.stateChangeCallbacks.set(key, callback); this.stateChangeCallbacks.set(key, callback);
}, },
@ -53,6 +43,14 @@ const PrivateMessageTopicTrackingState = Service.extend({
return Promise.resolve(); return Promise.resolve();
} }
this._establishChannels();
return this._loadInitialState().finally(() => {
this.set("isTracking", true);
});
},
_establishChannels() {
this.messageBus.subscribe(this.userChannel(), this._processMessage); this.messageBus.subscribe(this.userChannel(), this._processMessage);
this.currentUser.groupsWithMessages?.forEach((group) => { this.currentUser.groupsWithMessages?.forEach((group) => {
@ -61,10 +59,6 @@ const PrivateMessageTopicTrackingState = Service.extend({
this._processMessage this._processMessage
); );
}); });
return this._loadInitialState().finally(() => {
this.set("isTracking", true);
});
}, },
lookupCount(type, opts = {}) { lookupCount(type, opts = {}) {

View File

@ -1,4 +1,4 @@
import { bind, debounce } from "discourse-common/utils/decorators"; import { debounce } from "discourse-common/utils/decorators";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { headerOffset } from "discourse/lib/offset-calculator"; import { headerOffset } from "discourse/lib/offset-calculator";
import isElementInViewport from "discourse/lib/is-element-in-viewport"; import isElementInViewport from "discourse/lib/is-element-in-viewport";
@ -43,32 +43,35 @@ function initialize(api) {
return this._super(bookmark, post); return this._super(bookmark, post);
}, },
@bind subscribe() {
onMessage(data) {
this._super(...arguments); this._super(...arguments);
const topic = this.model; this.messageBus.subscribe(`/topic/${this.model.id}`, (data) => {
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 ( if (
notInPostStream && topic.isPrivateMessage &&
postNumberDifference > 0 && this.currentUser &&
postNumberDifference < 7 this.currentUser.id !== data.user_id &&
data.user_id === -2 &&
data.type === "created"
) { ) {
this._scrollToDiscobotPost(data.post_number); 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);
}
} }
} });
// No need to unsubscribe, core unsubscribes /topic/* routes
}, },
@debounce(500) @debounce(500)

View File

@ -1,7 +1,7 @@
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import WidgetGlue from "discourse/widgets/glue"; import WidgetGlue from "discourse/widgets/glue";
import { getRegister } from "discourse-common/lib/get-owner"; import { getRegister } from "discourse-common/lib/get-owner";
import { bind, observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
import { withPluginApi } from "discourse/lib/plugin-api"; import { withPluginApi } from "discourse/lib/plugin-api";
const PLUGIN_ID = "discourse-poll"; const PLUGIN_ID = "discourse-poll";
@ -34,19 +34,16 @@ function initializePolls(api) {
subscribe() { subscribe() {
this._super(...arguments); this._super(...arguments);
this.messageBus.subscribe(`/polls/${this.model.id}`, this._onPollMessage); this.messageBus.subscribe(`/polls/${this.model.id}`, (msg) => {
const post = this.get("model.postStream").findLoadedPost(msg.post_id);
post?.set("polls", msg.polls);
});
}, },
unsubscribe() { unsubscribe() {
this.messageBus.unsubscribe("/polls/*", this._onPollMessage); this.messageBus.unsubscribe("/polls/*");
this._super(...arguments); 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", { api.modifyClass("model:post", {