DEV: Use `@bind` instead of repeated `.bind(this)` (#14931)

Fixes some cases where event listeners weren't correctly removed. Also fixes a dependency tracking bug in user-private-messages
This commit is contained in:
Jarek Radosz 2021-11-15 10:07:53 +01:00 committed by GitHub
parent 08e625c446
commit f0d963faad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 116 additions and 89 deletions

View File

@ -17,7 +17,7 @@ import { TIME_SHORTCUT_TYPES } from "discourse/lib/time-shortcut";
import { action } from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import bootbox from "bootbox";
import discourseComputed, { on } from "discourse-common/utils/decorators";
import discourseComputed, { bind, on } from "discourse-common/utils/decorators";
import { formattedReminderTime } from "discourse/lib/bookmark";
import { and, notEmpty } from "@ember/object/computed";
import { popupAjaxError } from "discourse/lib/ajax-error";
@ -65,7 +65,7 @@ export default Component.extend({
_itsatrap: new ItsATrap(),
});
this.registerOnCloseHandler(this._onModalClose.bind(this));
this.registerOnCloseHandler(this._onModalClose);
this._loadBookmarkOptions();
this._bindKeyboardShortcuts();
@ -239,6 +239,7 @@ export default Component.extend({
}
},
@bind
_onModalClose(closeOpts) {
// we want to close without saving if the user already saved
// manually or deleted the bookmark, as well as when the modal

View File

@ -319,7 +319,7 @@ export default Component.extend(TextareaTextManipulation, {
}
if (isTesting()) {
this.element.addEventListener("paste", this.paste.bind(this));
this.element.addEventListener("paste", this.paste);
}
},

View File

@ -4,7 +4,7 @@ import I18n from "I18n";
import { ajax } from "discourse/lib/ajax";
import bootbox from "bootbox";
import { dasherize } from "@ember/string";
import discourseComputed from "discourse-common/utils/decorators";
import discourseComputed, { bind } from "discourse-common/utils/decorators";
import optionalService from "discourse/lib/optional-service";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { set } from "@ember/object";
@ -113,6 +113,7 @@ export default Component.extend({
return _components[type];
},
@bind
_performConfirmed(action) {
let reviewable = this.reviewable;
@ -264,7 +265,7 @@ export default Component.extend({
title: "review.reject_reason.title",
model: this.reviewable,
}).setProperties({
performConfirmed: this._performConfirmed.bind(this),
performConfirmed: this._performConfirmed,
action,
});
} else if (customModal) {
@ -272,7 +273,7 @@ export default Component.extend({
title: `review.${customModal}.title`,
model: this.reviewable,
}).setProperties({
performConfirmed: this._performConfirmed.bind(this),
performConfirmed: this._performConfirmed,
action,
});
} else {

View File

@ -27,12 +27,20 @@ export default Controller.extend({
return pmView === VIEW_NAME_WARNINGS && !viewingSelf && !isAdmin;
},
@discourseComputed("pmTopicTrackingState.newIncoming.[]", "group")
@discourseComputed(
"pmTopicTrackingState.newIncoming.[]",
"pmTopicTrackingState.statesModificationCounter",
"group"
)
newLinkText() {
return this._linkText("new");
},
@discourseComputed("pmTopicTrackingState.newIncoming.[]", "group")
@discourseComputed(
"pmTopicTrackingState.newIncoming.[]",
"pmTopicTrackingState.statesModificationCounter",
"group"
)
unreadLinkText() {
return this._linkText("unread");
},

View File

@ -1,6 +1,7 @@
import { UploadPreProcessorPlugin } from "discourse/lib/uppy-plugin-base";
import { Promise } from "rsvp";
import { HUGE_FILE_THRESHOLD_BYTES } from "discourse/mixins/uppy-upload";
import { bind } from "discourse-common/utils/decorators";
export default class UppyChecksum extends UploadPreProcessorPlugin {
static pluginId = "uppy-checksum";
@ -33,6 +34,7 @@ export default class UppyChecksum extends UploadPreProcessorPlugin {
return true;
}
@bind
_generateChecksum(fileIds) {
if (!this._canUseSubtleCrypto()) {
return this._skipAll(fileIds, true);
@ -85,14 +87,14 @@ export default class UppyChecksum extends UploadPreProcessorPlugin {
}
_hasCryptoCipher() {
return window.crypto && window.crypto.subtle && window.crypto.subtle.digest;
return window.crypto?.subtle?.digest;
}
install() {
this._install(this._generateChecksum.bind(this));
this._install(this._generateChecksum);
}
uninstall() {
this._uninstall(this._generateChecksum.bind(this));
this._uninstall(this._generateChecksum);
}
}

View File

@ -1,5 +1,6 @@
import { UploadPreProcessorPlugin } from "discourse/lib/uppy-plugin-base";
import { Promise } from "rsvp";
import { bind } from "discourse-common/utils/decorators";
export default class UppyMediaOptimization extends UploadPreProcessorPlugin {
static pluginId = "uppy-media-optimization";
@ -15,6 +16,7 @@ export default class UppyMediaOptimization extends UploadPreProcessorPlugin {
this.runParallel = opts.runParallel || false;
}
@bind
_optimizeFile(fileId) {
let file = this._getFile(fileId);
@ -42,13 +44,15 @@ export default class UppyMediaOptimization extends UploadPreProcessorPlugin {
});
}
@bind
_optimizeParallel(fileIds) {
return Promise.all(fileIds.map(this._optimizeFile.bind(this)));
return Promise.all(fileIds.map(this._optimizeFile));
}
@bind
async _optimizeSerial(fileIds) {
let optimizeTasks = fileIds.map((fileId) => () =>
this._optimizeFile.call(this, fileId)
this._optimizeFile(fileId)
);
for (const task of optimizeTasks) {
@ -58,17 +62,17 @@ export default class UppyMediaOptimization extends UploadPreProcessorPlugin {
install() {
if (this.runParallel) {
this._install(this._optimizeParallel.bind(this));
this._install(this._optimizeParallel);
} else {
this._install(this._optimizeSerial.bind(this));
this._install(this._optimizeSerial);
}
}
uninstall() {
if (this.runParallel) {
this._uninstall(this._optimizeParallel.bind(this));
this._uninstall(this._optimizeParallel);
} else {
this._uninstall(this._optimizeSerial.bind(this));
this._uninstall(this._optimizeSerial);
}
}
}

View File

@ -98,14 +98,14 @@ export default Mixin.create({
didInsertElement() {
this._super(...arguments);
afterTransition($(this.element), this._hide.bind(this));
afterTransition($(this.element), this._hide);
const id = this.elementId;
const triggeringLinkClass = this.triggeringLinkClass;
const previewClickEvent = `click.discourse-preview-${id}-${triggeringLinkClass}`;
const mobileScrollEvent = "scroll.mobile-card-cloak";
this.setProperties({
boundCardClickHandler: this._cardClickHandler.bind(this),
boundCardClickHandler: this._cardClickHandler,
previewClickEvent,
mobileScrollEvent,
});
@ -127,6 +127,7 @@ export default Mixin.create({
);
},
@bind
_cardClickHandler(event) {
if (this.avatarSelector) {
let matched = this._showCardOnClick(
@ -290,6 +291,7 @@ export default Mixin.create({
return mainOutletOffset.top - outletHeights;
},
@bind
_hide() {
if (!this.visible) {
$(this.element).css({ left: -9999, top: -9999 });

View File

@ -10,7 +10,7 @@ import { warn } from "@ember/debug";
import I18n from "I18n";
import getURL from "discourse-common/lib/get-url";
import { clipboardHelpers } from "discourse/lib/utilities";
import { observes, on } from "discourse-common/utils/decorators";
import { bind, observes, on } from "discourse-common/utils/decorators";
import {
bindFileInputChangeListener,
displayErrorForUpload,
@ -33,6 +33,8 @@ import bootbox from "bootbox";
// functionality and event binding.
//
export default Mixin.create(ExtendableUploader, UppyS3Multipart, {
uploadTargetBound: false,
@observes("composerModel.uploadCancelled")
_cancelUpload() {
if (!this.get("composerModel.uploadCancelled")) {
@ -46,17 +48,18 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, {
@on("willDestroyElement")
_unbindUploadTarget() {
if (!this.uploadTargetBound) {
return;
}
this.fileInputEl?.removeEventListener(
"change",
this.fileInputEventListener
);
this.element?.removeEventListener("paste", this.pasteEventListener);
this.element.removeEventListener("paste", this.pasteEventListener);
this.appEvents.off(
`${this.eventPrefix}:add-files`,
this._addFiles.bind(this)
);
this.appEvents.off(`${this.eventPrefix}:add-files`, this._addFiles);
this._reset();
@ -64,6 +67,8 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, {
this._uppyInstance.close();
this._uppyInstance = null;
}
this.uploadTargetBound = false;
},
_abortAndReset() {
@ -79,14 +84,14 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, {
this.fileInputEl = document.getElementById(this.fileUploadElementId);
const isPrivateMessage = this.get("composerModel.privateMessage");
this.appEvents.on(
`${this.eventPrefix}:add-files`,
this._addFiles.bind(this)
);
this.appEvents.on(`${this.eventPrefix}:add-files`, this._addFiles);
this._unbindUploadTarget();
this._bindFileInputChangeListener();
this._bindPasteListener();
this.fileInputEventListener = bindFileInputChangeListener(
this.fileInputEl,
this._addFiles
);
this.element.addEventListener("paste", this.pasteEventListener);
this._uppyInstance = new Uppy({
id: this.uppyId,
@ -222,7 +227,7 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, {
);
});
this._uppyInstance.on("upload-error", this._handleUploadError.bind(this));
this._uppyInstance.on("upload-error", this._handleUploadError);
this._uppyInstance.on("complete", () => {
this.appEvents.trigger(`${this.eventPrefix}:all-uploads-complete`);
@ -250,8 +255,11 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, {
this._setupPreProcessors();
this._setupUIPlugins();
this.uploadTargetBound = true;
},
@bind
_handleUploadError(file, error, response) {
this._inProgressUploads--;
this._resetUpload(file, { removePlaceholder: true });
@ -411,15 +419,8 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, {
}
},
_bindFileInputChangeListener() {
this.fileInputEventListener = bindFileInputChangeListener(
this.fileInputEl,
this._addFiles.bind(this)
);
},
_bindPasteListener() {
this.pasteEventListener = function pasteListener(event) {
@bind
pasteEventListener(event) {
if (
document.activeElement !== document.querySelector(this.editorInputClass)
) {
@ -438,11 +439,9 @@ export default Mixin.create(ExtendableUploader, UppyS3Multipart, {
if (event && event.clipboardData && event.clipboardData.files) {
this._addFiles([...event.clipboardData.files], { pasted: true });
}
}.bind(this);
this.element.addEventListener("paste", this.pasteEventListener);
},
@bind
_addFiles(files, opts = {}) {
files = Array.isArray(files) ? files : [files];
try {

View File

@ -6,6 +6,7 @@ import {
determinePostReplaceSelection,
safariHacksDisabled,
} from "discourse/lib/utilities";
import { bind } from "discourse-common/utils/decorators";
import { next, schedule } from "@ember/runloop";
const isInside = (text, regex) => {
@ -223,6 +224,7 @@ export default Mixin.create({
return null;
},
@bind
paste(e) {
if (!this._$textarea.is(":focus") && !isTesting()) {
return;

View File

@ -1,6 +1,6 @@
import EmberObject from "@ember/object";
import { ajax } from "discourse/lib/ajax";
import { on } from "discourse-common/utils/decorators";
import { bind, on } from "discourse-common/utils/decorators";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { deepEqual, deepMerge } from "discourse-common/lib/object";
import {
@ -64,21 +64,23 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({
let filterFn;
if (inbox === "user") {
filterFn = this._isPersonal.bind(this);
filterFn = this._isPersonal;
} else if (inbox === "group") {
filterFn = this._isGroup.bind(this);
filterFn = this._isGroup;
}
return Array.from(this.states.values()).filter((topic) => {
return (
typeFilterFn(topic) && (!filterFn || filterFn(topic, opts.groupName))
);
return typeFilterFn(topic) && filterFn?.(topic, opts.groupName);
}).length;
},
trackIncoming(inbox, filter, group) {
this.setProperties({ inbox, filter, activeGroup: group });
this.set("isTrackingIncoming", true);
this.setProperties({
inbox,
filter,
activeGroup: group,
isTrackingIncoming: true,
});
},
resetIncomingTracking() {
@ -130,6 +132,7 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({
);
},
@bind
_isPersonal(topic) {
const groups = this.currentUser?.groups;
@ -142,6 +145,7 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({
});
},
@bind
_isGroup(topic, activeGroupName) {
return this.currentUser.groups.some((group) => {
return (
@ -151,6 +155,7 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({
});
},
@bind
_processMessage(message) {
switch (message.message_type) {
case "new_topic":
@ -212,7 +217,7 @@ const PrivateMessageTopicTrackingState = EmberObject.extend({
},
_notifyIncoming(topicId) {
if (this.isTrackingIncoming && this.newIncoming.indexOf(topicId) === -1) {
if (this.isTrackingIncoming && !this.newIncoming.includes(topicId)) {
this.newIncoming.pushObject(topicId);
}
},

View File

@ -1,5 +1,5 @@
import EmberObject, { get } from "@ember/object";
import discourseComputed, { on } from "discourse-common/utils/decorators";
import discourseComputed, { bind, on } 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";
@ -65,15 +65,12 @@ const TopicTrackingState = EmberObject.extend({
* @method establishChannels
*/
establishChannels() {
this.messageBus.subscribe("/new", this._processChannelPayload.bind(this));
this.messageBus.subscribe(
"/latest",
this._processChannelPayload.bind(this)
);
this.messageBus.subscribe("/new", this._processChannelPayload);
this.messageBus.subscribe("/latest", this._processChannelPayload);
if (this.currentUser) {
this.messageBus.subscribe(
"/unread/" + this.currentUser.get("id"),
this._processChannelPayload.bind(this)
`/unread/${this.currentUser.id}`,
this._processChannelPayload
);
}
@ -414,7 +411,7 @@ const TopicTrackingState = EmberObject.extend({
// make sure all the state is up to date with what is accurate
// from the server
list.topics.forEach(this._syncStateFromListTopic.bind(this));
list.topics.forEach(this._syncStateFromListTopic);
// correct missing states, safeguard in case message bus is corrupt
if (this._shouldCompensateState(list, filter, queryParams)) {
@ -651,6 +648,7 @@ const TopicTrackingState = EmberObject.extend({
// this updates the topic in the state to match the
// topic from the list (e.g. updates category, highest read post
// number, tags etc.)
@bind
_syncStateFromListTopic(topic) {
const state = this.findState(topic.id) || {};
@ -739,6 +737,7 @@ const TopicTrackingState = EmberObject.extend({
},
// processes the data sent via messageBus, called by establishChannels
@bind
_processChannelPayload(data) {
if (["muted", "unmuted"].includes(data.message_type)) {
this.trackMutedOrUnmutedTopic(data);

View File

@ -11,6 +11,7 @@ import {
} from "discourse/tests/helpers/qunit-helpers";
import { fixturesByUrl } from "discourse/tests/helpers/create-pretender";
import selectKit from "../helpers/select-kit-helper";
import { cloneJSON } from "discourse-common/lib/object";
acceptance(
"User Private Messages - user with no group messages",
@ -83,7 +84,7 @@ acceptance(
});
server.get("/t/13.json", () => {
const response = { ...fixturesByUrl["/t/12/1.json"] };
const response = cloneJSON(fixturesByUrl["/t/12/1.json"]);
response.suggested_group_name = "awesome_group";
return helper.response(response);
});
@ -391,7 +392,7 @@ acceptance(
assert.ok(exists(".show-mores"), "displays the topic incoming info");
});
test("incoming unread messages while viewing group unread", async function (assert) {
test("incoming unread and new messages while viewing group unread", async function (assert) {
await visit("/u/charlie/messages/group/awesome_group/unread");
publishUnreadToMessageBus({ groupIds: [14], topicId: 1 });

View File

@ -485,7 +485,7 @@ export function exists(selector) {
export function publishToMessageBus(channelPath, ...args) {
MessageBus.callbacks
.filterBy("channel", channelPath)
.map((c) => c.func(...args));
.forEach((c) => c.func(...args));
}
export async function selectText(selector, endOffset = null) {

View File

@ -52,12 +52,15 @@ module("Unit | Utility | UppyMediaOptimization Plugin", function () {
const plugin = new UppyMediaOptimization(fakeUppy, {
runParallel: true,
});
plugin._optimizeParallel = function () {
return "using parallel";
};
plugin._optimizeSerial = function () {
return "using serial";
};
Object.defineProperty(plugin, "_optimizeParallel", {
value: () => "using parallel",
});
Object.defineProperty(plugin, "_optimizeSerial", {
value: () => "using serial",
});
plugin.install();
assert.strictEqual(plugin.uppy.preprocessors[0](), "using parallel");
plugin.runParallel = false;