DEV: Wrap Ember.run.debounce. ()

We want to wrap the `Ember.run.debounce` function and internally call `Ember.run` instead when running tests.

This commit changes discourseDebounce to work the same way as `Ember.run.debounce`.

Now that `discourseDebounce` works exactly like `Ember.run.debounce`, let's replace it and only use `DiscourseDebounce` from now on.

Move debounce to discourse-common to be able to reuse it in different bundles

Keep old debounce file for backwards-compatibility
This commit is contained in:
Roman Rizzi 2020-12-10 11:01:42 -03:00 committed by GitHub
parent fb2e24a77a
commit 8b426431a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 519 additions and 353 deletions

@ -1,7 +1,7 @@
import { observes, on } from "discourse-common/utils/decorators"; import { observes, on } from "discourse-common/utils/decorators";
import Component from "@ember/component"; import Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { scheduleOnce } from "@ember/runloop"; import { scheduleOnce } from "@ember/runloop";
export default Component.extend({ export default Component.extend({
@ -33,9 +33,7 @@ export default Component.extend({
} }
}, },
@on("init") _updateFormattedLogsFunc: function () {
@observes("logs.[]")
_updateFormattedLogs: discourseDebounce(function () {
const logs = this.logs; const logs = this.logs;
if (logs.length === 0) { if (logs.length === 0) {
return; return;
@ -57,7 +55,13 @@ export default Component.extend({
this.renderLogs(); this.renderLogs();
scheduleOnce("afterRender", this, this._scrollDown); scheduleOnce("afterRender", this, this._scrollDown);
}, 150), },
@on("init")
@observes("logs.[]")
_updateFormattedLogs() {
discourseDebounce(this, this._updateFormattedLogsFunc, 150);
},
renderLogs() { renderLogs() {
const formattedLogs = this.formattedLogs; const formattedLogs = this.formattedLogs;

@ -1,8 +1,9 @@
import { debounce, schedule } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import discourseDebounce from "discourse-common/lib/debounce";
import loadScript from "discourse/lib/load-script"; import loadScript from "discourse/lib/load-script";
import { makeArray } from "discourse-common/lib/helpers"; import { makeArray } from "discourse-common/lib/helpers";
import { number } from "discourse/lib/formatter"; import { number } from "discourse/lib/formatter";
import { schedule } from "@ember/runloop";
export default Component.extend({ export default Component.extend({
classNames: ["admin-report-chart"], classNames: ["admin-report-chart"],
@ -14,7 +15,7 @@ export default Component.extend({
this._super(...arguments); this._super(...arguments);
this.resizeHandler = () => this.resizeHandler = () =>
debounce(this, this._scheduleChartRendering, 500); discourseDebounce(this, this._scheduleChartRendering, 500);
}, },
didInsertElement() { didInsertElement() {
@ -34,7 +35,7 @@ export default Component.extend({
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
debounce(this, this._scheduleChartRendering, 100); discourseDebounce(this, this._scheduleChartRendering, 100);
}, },
_scheduleChartRendering() { _scheduleChartRendering() {

@ -1,8 +1,9 @@
import { debounce, schedule } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import discourseDebounce from "discourse-common/lib/debounce";
import loadScript from "discourse/lib/load-script"; import loadScript from "discourse/lib/load-script";
import { makeArray } from "discourse-common/lib/helpers"; import { makeArray } from "discourse-common/lib/helpers";
import { number } from "discourse/lib/formatter"; import { number } from "discourse/lib/formatter";
import { schedule } from "@ember/runloop";
export default Component.extend({ export default Component.extend({
classNames: ["admin-report-chart", "admin-report-stacked-chart"], classNames: ["admin-report-chart", "admin-report-stacked-chart"],
@ -11,7 +12,7 @@ export default Component.extend({
this._super(...arguments); this._super(...arguments);
this.resizeHandler = () => this.resizeHandler = () =>
debounce(this, this._scheduleChartRendering, 500); discourseDebounce(this, this._scheduleChartRendering, 500);
}, },
didInsertElement() { didInsertElement() {
@ -31,7 +32,7 @@ export default Component.extend({
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments); this._super(...arguments);
debounce(this, this._scheduleChartRendering, 100); discourseDebounce(this, this._scheduleChartRendering, 100);
}, },
_scheduleChartRendering() { _scheduleChartRendering() {

@ -1,7 +1,7 @@
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import { debounce } from "@ember/runloop";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import discourseDebounce from "discourse-common/lib/debounce";
const { get } = Ember; const { get } = Ember;
@ -34,7 +34,7 @@ export default Controller.extend({
actions: { actions: {
filterReports(filter) { filterReports(filter) {
debounce(this, this._performFiltering, filter, INPUT_DELAY); discourseDebounce(this, this._performFiltering, filter, INPUT_DELAY);
}, },
}, },

@ -1,11 +1,11 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
export default AdminEmailLogsController.extend({ export default AdminEmailLogsController.extend({
@observes("filter.{status,user,address,type}") @observes("filter.{status,user,address,type}")
filterEmailLogs: discourseDebounce(function () { filterEmailLogs() {
this.loadLogs(); discourseDebounce(this, this.loadLogs, INPUT_DELAY);
}, INPUT_DELAY), },
}); });

@ -1,14 +1,14 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import IncomingEmail from "admin/models/incoming-email"; import IncomingEmail from "admin/models/incoming-email";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
export default AdminEmailLogsController.extend({ export default AdminEmailLogsController.extend({
@observes("filter.{status,from,to,subject}") @observes("filter.{status,from,to,subject}")
filterIncomingEmails: discourseDebounce(function () { filterIncomingEmails() {
this.loadLogs(IncomingEmail); discourseDebounce(this, this.loadLogs, IncomingEmail, INPUT_DELAY);
}, INPUT_DELAY), },
actions: { actions: {
loadMore() { loadMore() {

@ -1,14 +1,14 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import IncomingEmail from "admin/models/incoming-email"; import IncomingEmail from "admin/models/incoming-email";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
export default AdminEmailLogsController.extend({ export default AdminEmailLogsController.extend({
@observes("filter.{status,from,to,subject,error}") @observes("filter.{status,from,to,subject,error}")
filterIncomingEmails: discourseDebounce(function () { filterIncomingEmails() {
this.loadLogs(IncomingEmail); discourseDebounce(this, this.loadLogs, IncomingEmail, INPUT_DELAY);
}, INPUT_DELAY), },
actions: { actions: {
loadMore() { loadMore() {

@ -1,11 +1,11 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
export default AdminEmailLogsController.extend({ export default AdminEmailLogsController.extend({
@observes("filter.{status,user,address,type,reply_key}") @observes("filter.{status,user,address,type,reply_key}")
filterEmailLogs: discourseDebounce(function () { filterEmailLogs() {
this.loadLogs(); discourseDebounce(this, this.loadLogs, INPUT_DELAY);
}, INPUT_DELAY), },
}); });

@ -1,11 +1,11 @@
import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import AdminEmailLogsController from "admin/controllers/admin-email-logs";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
export default AdminEmailLogsController.extend({ export default AdminEmailLogsController.extend({
@observes("filter.{status,user,address,type}") @observes("filter.{status,user,address,type}")
filterEmailLogs: discourseDebounce(function () { filterEmailLogs() {
this.loadLogs(); discourseDebounce(this, this.loadLogs, INPUT_DELAY);
}, INPUT_DELAY), },
}); });

@ -3,7 +3,7 @@ import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import ScreenedIpAddress from "admin/models/screened-ip-address"; import ScreenedIpAddress from "admin/models/screened-ip-address";
import bootbox from "bootbox"; import bootbox from "bootbox";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { exportEntity } from "discourse/lib/export-csv"; import { exportEntity } from "discourse/lib/export-csv";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
import { outputExportResult } from "discourse/lib/export-result"; import { outputExportResult } from "discourse/lib/export-result";
@ -13,13 +13,17 @@ export default Controller.extend({
filter: null, filter: null,
savedIpAddress: null, savedIpAddress: null,
@observes("filter") _debouncedShow() {
show: discourseDebounce(function () {
this.set("loading", true); this.set("loading", true);
ScreenedIpAddress.findAll(this.filter).then((result) => { ScreenedIpAddress.findAll(this.filter).then((result) => {
this.setProperties({ model: result, loading: false }); this.setProperties({ model: result, loading: false });
}); });
}, INPUT_DELAY), },
@observes("filter")
show() {
discourseDebounce(this, this._debouncedShow, INPUT_DELAY);
},
actions: { actions: {
allow(record) { allow(record) {

@ -3,20 +3,24 @@ import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import Permalink from "admin/models/permalink"; import Permalink from "admin/models/permalink";
import bootbox from "bootbox"; import bootbox from "bootbox";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
export default Controller.extend({ export default Controller.extend({
loading: false, loading: false,
filter: null, filter: null,
@observes("filter") _debouncedShow() {
show: discourseDebounce(function () {
Permalink.findAll(this.filter).then((result) => { Permalink.findAll(this.filter).then((result) => {
this.set("model", result); this.set("model", result);
this.set("loading", false); this.set("loading", false);
}); });
}, INPUT_DELAY), },
@observes("filter")
show() {
discourseDebounce(this, this._debouncedShow, INPUT_DELAY);
},
actions: { actions: {
recordAdded(arg) { recordAdded(arg) {

@ -2,7 +2,7 @@ import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import { alias } from "@ember/object/computed"; import { alias } from "@ember/object/computed";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
@ -112,13 +112,19 @@ export default Controller.extend({
}, },
@observes("filter", "onlyOverridden", "model") @observes("filter", "onlyOverridden", "model")
filterContent: discourseDebounce(function () { filterContent() {
if (this._skipBounce) { discourseDebounce(
this.set("_skipBounce", false); this,
} else { () => {
this.filterContentNow(this.categoryNameKey); if (this._skipBounce) {
} this.set("_skipBounce", false);
}, INPUT_DELAY), } else {
this.filterContentNow(this.categoryNameKey);
}
},
INPUT_DELAY
);
},
actions: { actions: {
clearFilter() { clearFilter() {

@ -1,5 +1,5 @@
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import { debounce } from "@ember/runloop"; import discourseDebounce from "discourse-common/lib/debounce";
let lastSearch; let lastSearch;
export default Controller.extend({ export default Controller.extend({
@ -28,14 +28,14 @@ export default Controller.extend({
toggleOverridden() { toggleOverridden() {
this.toggleProperty("overridden"); this.toggleProperty("overridden");
this.set("searching", true); this.set("searching", true);
debounce(this, this._performSearch, 400); discourseDebounce(this, this._performSearch, 400);
}, },
search() { search() {
const q = this.q; const q = this.q;
if (q !== lastSearch) { if (q !== lastSearch) {
this.set("searching", true); this.set("searching", true);
debounce(this, this._performSearch, 400); discourseDebounce(this, this._performSearch, 400);
lastSearch = q; lastSearch = q;
} }
}, },

@ -4,7 +4,7 @@ import CanCheckEmails from "discourse/mixins/can-check-emails";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { i18n } from "discourse/lib/computed"; import { i18n } from "discourse/lib/computed";
export default Controller.extend(CanCheckEmails, { export default Controller.extend(CanCheckEmails, {
@ -32,9 +32,9 @@ export default Controller.extend(CanCheckEmails, {
}, },
@observes("listFilter") @observes("listFilter")
_filterUsers: discourseDebounce(function () { _filterUsers() {
this.resetFilters(); discourseDebounce(this, this.resetFilters, INPUT_DELAY);
}, INPUT_DELAY), },
resetFilters() { resetFilters() {
this._page = 1; this._page = 1;

@ -2,7 +2,7 @@ import Controller from "@ember/controller";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import { alias } from "@ember/object/computed"; import { alias } from "@ember/object/computed";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
@ -48,10 +48,16 @@ export default Controller.extend({
}, },
@observes("filter") @observes("filter")
filterContent: discourseDebounce(function () { filterContent() {
this.filterContentNow(); discourseDebounce(
this.set("filtered", !isEmpty(this.filter)); this,
}, INPUT_DELAY), function () {
this.filterContentNow();
this.set("filtered", !isEmpty(this.filter));
},
INPUT_DELAY
);
},
actions: { actions: {
clearFilter() { clearFilter() {

@ -0,0 +1,15 @@
import { debounce, run } from "@ember/runloop";
import { isTesting } from "discourse-common/config/environment";
/**
Debounce a Javascript function. This means if it's called many times in a time limit it
should only be executed once (at the end of the limit counted from the last call made).
Original function will be called with the context and arguments from the last call made.
**/
export default function () {
if (isTesting()) {
return run(...arguments);
} else {
return debounce(...arguments);
}
}

@ -1,5 +1,5 @@
import Component from "@ember/component"; import Component from "@ember/component";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { get } from "@ember/object"; import { get } from "@ember/object";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { next } from "@ember/runloop"; import { next } from "@ember/runloop";
@ -30,31 +30,38 @@ export default Component.extend({
this.set("loading", false); this.set("loading", false);
}, },
search: discourseDebounce(function (title) { search(title) {
const currentTopicId = this.currentTopicId; discourseDebounce(
this,
function () {
const currentTopicId = this.currentTopicId;
if (isEmpty(title)) { if (isEmpty(title)) {
this.setProperties({ messages: null, loading: false }); this.setProperties({ messages: null, loading: false });
return; return;
} }
searchForTerm(title, { searchForTerm(title, {
typeFilter: "private_messages", typeFilter: "private_messages",
searchForId: true, searchForId: true,
restrictToArchetype: "private_message", restrictToArchetype: "private_message",
}).then((results) => { }).then((results) => {
if (results && results.posts && results.posts.length > 0) { if (results && results.posts && results.posts.length > 0) {
this.set( this.set(
"messages", "messages",
results.posts results.posts
.mapBy("topic") .mapBy("topic")
.filter((t) => t.get("id") !== currentTopicId) .filter((t) => t.get("id") !== currentTopicId)
); );
} else { } else {
this.setProperties({ messages: null, loading: false }); this.setProperties({ messages: null, loading: false });
} }
}); });
}, 300), },
title,
300
);
},
actions: { actions: {
chooseMessage(message) { chooseMessage(message) {

@ -1,6 +1,6 @@
import discourseComputed, { observes } from "discourse-common/utils/decorators"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
import Component from "@ember/component"; import Component from "@ember/component";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { next } from "@ember/runloop"; import { next } from "@ember/runloop";
import { searchForTerm } from "discourse/lib/search"; import { searchForTerm } from "discourse/lib/search";
@ -64,37 +64,46 @@ export default Component.extend({
this.set("loading", false); this.set("loading", false);
}, },
search: discourseDebounce(function (title) { search(title) {
if (!this.element || this.isDestroying || this.isDestroyed) { discourseDebounce(
return; this,
} function () {
if (!this.element || this.isDestroying || this.isDestroyed) {
return;
}
if (isEmpty(title) && isEmpty(this.additionalFilters)) { if (isEmpty(title) && isEmpty(this.additionalFilters)) {
this.setProperties({ topics: null, loading: false }); this.setProperties({ topics: null, loading: false });
return; return;
} }
const currentTopicId = this.currentTopicId; const currentTopicId = this.currentTopicId;
const titleWithFilters = `${title} ${this.additionalFilters}`; const titleWithFilters = `${title} ${this.additionalFilters}`;
let searchParams = {}; let searchParams = {};
if (!isEmpty(title)) { if (!isEmpty(title)) {
searchParams.typeFilter = "topic"; searchParams.typeFilter = "topic";
searchParams.restrictToArchetype = "regular"; searchParams.restrictToArchetype = "regular";
searchParams.searchForId = true; searchParams.searchForId = true;
} }
searchForTerm(titleWithFilters, searchParams).then((results) => { searchForTerm(titleWithFilters, searchParams).then((results) => {
if (results && results.posts && results.posts.length > 0) { if (results && results.posts && results.posts.length > 0) {
this.set( this.set(
"topics", "topics",
results.posts.mapBy("topic").filter((t) => t.id !== currentTopicId) results.posts
); .mapBy("topic")
} else { .filter((t) => t.id !== currentTopicId)
this.setProperties({ topics: null, loading: false }); );
} } else {
}); this.setProperties({ topics: null, loading: false });
}, 300), }
});
},
title,
300
);
},
actions: { actions: {
chooseTopic(topic) { chooseTopic(topic) {

@ -1,16 +1,10 @@
import { import { cancel, later, run, schedule, throttle } from "@ember/runloop";
cancel,
debounce,
later,
run,
schedule,
throttle,
} from "@ember/runloop";
import discourseComputed, { observes } from "discourse-common/utils/decorators"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
import Component from "@ember/component"; import Component from "@ember/component";
import Composer from "discourse/models/composer"; import Composer from "discourse/models/composer";
import KeyEnterEscape from "discourse/mixins/key-enter-escape"; import KeyEnterEscape from "discourse/mixins/key-enter-escape";
import afterTransition from "discourse/lib/after-transition"; import afterTransition from "discourse/lib/after-transition";
import discourseDebounce from "discourse-common/lib/debounce";
import { headerHeight } from "discourse/components/site-header"; import { headerHeight } from "discourse/components/site-header";
import { iOSWithVisualViewport } from "discourse/lib/utilities"; import { iOSWithVisualViewport } from "discourse/lib/utilities";
import positioningWorkaround from "discourse/lib/safari-hacks"; import positioningWorkaround from "discourse/lib/safari-hacks";
@ -76,7 +70,7 @@ export default Component.extend(KeyEnterEscape, {
return; return;
} }
debounce(this, this.debounceMove, 300); discourseDebounce(this, this.debounceMove, 300);
}); });
}, },

@ -15,7 +15,6 @@ import {
inCodeBlock, inCodeBlock,
tinyAvatar, tinyAvatar,
} from "discourse/lib/utilities"; } from "discourse/lib/utilities";
import { debounce, later, next, run, schedule, throttle } from "@ember/runloop";
import discourseComputed, { import discourseComputed, {
observes, observes,
on, on,
@ -28,12 +27,14 @@ import {
fetchUnseenMentions, fetchUnseenMentions,
linkSeenMentions, linkSeenMentions,
} from "discourse/lib/link-mentions"; } from "discourse/lib/link-mentions";
import { later, next, run, schedule, throttle } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import Composer from "discourse/models/composer"; import Composer from "discourse/models/composer";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import I18n from "I18n"; import I18n from "I18n";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import bootbox from "bootbox"; import bootbox from "bootbox";
import discourseDebounce from "discourse-common/lib/debounce";
import { findRawTemplate } from "discourse-common/lib/raw-templates"; import { findRawTemplate } from "discourse-common/lib/raw-templates";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import { iconHTML } from "discourse-common/lib/icon-library"; import { iconHTML } from "discourse-common/lib/icon-library";
@ -905,7 +906,7 @@ export default Component.extend({
// Paint mentions // Paint mentions
const unseenMentions = linkSeenMentions($preview, this.siteSettings); const unseenMentions = linkSeenMentions($preview, this.siteSettings);
if (unseenMentions.length) { if (unseenMentions.length) {
debounce( discourseDebounce(
this, this,
this._renderUnseenMentions, this._renderUnseenMentions,
$preview, $preview,
@ -920,7 +921,7 @@ export default Component.extend({
// Paint category and tag hashtags // Paint category and tag hashtags
const unseenHashtags = linkSeenHashtags($preview); const unseenHashtags = linkSeenHashtags($preview);
if (unseenHashtags.length > 0) { if (unseenHashtags.length > 0) {
debounce(this, this._renderUnseenHashtags, $preview, 450); discourseDebounce(this, this._renderUnseenHashtags, $preview, 450);
} }
// Paint oneboxes // Paint oneboxes
@ -947,7 +948,7 @@ export default Component.extend({
} }
}; };
debounce(this, paintFunc, 450); discourseDebounce(this, paintFunc, 450);
// Short upload urls need resolution // Short upload urls need resolution
resolveAllShortUrls(ajax, this.siteSettings, $preview[0]); resolveAllShortUrls(ajax, this.siteSettings, $preview[0]);

@ -1,10 +1,11 @@
import { alias, or } from "@ember/object/computed"; import { alias, or } from "@ember/object/computed";
import { debounce, next, schedule } from "@ember/runloop";
import discourseComputed, { observes } from "discourse-common/utils/decorators"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { next, schedule } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import I18n from "I18n"; import I18n from "I18n";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import discourseDebounce from "discourse-common/lib/debounce";
import { isTesting } from "discourse-common/config/environment"; import { isTesting } from "discourse-common/config/environment";
import { load } from "pretty-text/oneboxer"; import { load } from "pretty-text/oneboxer";
import { lookupCache } from "pretty-text/oneboxer-cache"; import { lookupCache } from "pretty-text/oneboxer-cache";
@ -22,7 +23,7 @@ export default Component.extend({
} }
if (this.get("composer.titleLength") > 0) { if (this.get("composer.titleLength") > 0) {
debounce(this, this._titleChanged, 10); discourseDebounce(this, this._titleChanged, 10);
} }
}, },
@ -83,7 +84,7 @@ export default Component.extend({
this._checkForUrl() this._checkForUrl()
); );
} else { } else {
debounce(this, this._checkForUrl, 500); discourseDebounce(this, this._checkForUrl, 500);
} }
}, },

@ -5,13 +5,13 @@ import {
inCodeBlock, inCodeBlock,
safariHacksDisabled, safariHacksDisabled,
} from "discourse/lib/utilities"; } from "discourse/lib/utilities";
import { debounce, later, next, schedule, scheduleOnce } from "@ember/runloop";
import discourseComputed, { import discourseComputed, {
observes, observes,
on, on,
} from "discourse-common/utils/decorators"; } from "discourse-common/utils/decorators";
import { emojiSearch, isSkinTonableEmoji } from "pretty-text/emoji"; import { emojiSearch, isSkinTonableEmoji } from "pretty-text/emoji";
import { emojiUrlFor, generateCookFunction } from "discourse/lib/text"; import { emojiUrlFor, generateCookFunction } from "discourse/lib/text";
import { later, next, schedule, scheduleOnce } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
import Mousetrap from "mousetrap"; import Mousetrap from "mousetrap";
@ -19,6 +19,7 @@ import { Promise } from "rsvp";
import { SKIP } from "discourse/lib/autocomplete"; import { SKIP } from "discourse/lib/autocomplete";
import { categoryHashtagTriggerRule } from "discourse/lib/category-hashtags"; import { categoryHashtagTriggerRule } from "discourse/lib/category-hashtags";
import deprecated from "discourse-common/lib/deprecated"; import deprecated from "discourse-common/lib/deprecated";
import discourseDebounce from "discourse-common/lib/debounce";
import { findRawTemplate } from "discourse-common/lib/raw-templates"; import { findRawTemplate } from "discourse-common/lib/raw-templates";
import { getRegister } from "discourse-common/lib/get-owner"; import { getRegister } from "discourse-common/lib/get-owner";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
@ -414,7 +415,7 @@ export default Component.extend({
if (isTesting()) { if (isTesting()) {
this._updatePreview(); this._updatePreview();
} else { } else {
debounce(this, this._updatePreview, 30); discourseDebounce(this, this._updatePreview, 30);
} }
}, },

@ -7,7 +7,7 @@ import I18n from "I18n";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { convertIconClass } from "discourse-common/lib/icon-library"; import { convertIconClass } from "discourse-common/lib/icon-library";
import { debounce } from "@ember/runloop"; import discourseDebounce from "discourse-common/lib/debounce";
import { escapeExpression } from "discourse/lib/utilities"; import { escapeExpression } from "discourse/lib/utilities";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import { htmlSafe } from "@ember/template"; import { htmlSafe } from "@ember/template";
@ -34,7 +34,7 @@ export default Component.extend({
@observes("model.flair_icon") @observes("model.flair_icon")
_loadSVGIcon(flairIcon) { _loadSVGIcon(flairIcon) {
if (flairIcon) { if (flairIcon) {
debounce(this, this._loadIcon, 1000); discourseDebounce(this, this._loadIcon, 1000);
} }
}, },

@ -3,7 +3,7 @@ import Component from "@ember/component";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import Group from "discourse/models/group"; import Group from "discourse/models/group";
import I18n from "I18n"; import I18n from "I18n";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { not } from "@ember/object/computed"; import { not } from "@ember/object/computed";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
@ -64,40 +64,46 @@ export default Component.extend({
); );
}, },
checkGroupName: discourseDebounce(function () { checkGroupName() {
if (isEmpty(this.nameInput)) { discourseDebounce(
return; this,
} function () {
if (isEmpty(this.nameInput)) {
Group.checkName(this.nameInput) return;
.then((response) => {
const validationName = "uniqueNameValidation";
if (response.available) {
this.set(
validationName,
EmberObject.create({
ok: true,
reason: I18n.t("admin.groups.new.name.available"),
})
);
this.set("disableSave", false);
this.set("model.name", this.nameInput);
} else {
let reason;
if (response.errors) {
reason = response.errors.join(" ");
} else {
reason = I18n.t("admin.groups.new.name.not_available");
}
this.set(validationName, this._failedInputValidation(reason));
} }
})
.catch(popupAjaxError); Group.checkName(this.nameInput)
}, 500), .then((response) => {
const validationName = "uniqueNameValidation";
if (response.available) {
this.set(
validationName,
EmberObject.create({
ok: true,
reason: I18n.t("admin.groups.new.name.available"),
})
);
this.set("disableSave", false);
this.set("model.name", this.nameInput);
} else {
let reason;
if (response.errors) {
reason = response.errors.join(" ");
} else {
reason = I18n.t("admin.groups.new.name.not_available");
}
this.set(validationName, this._failedInputValidation(reason));
}
})
.catch(popupAjaxError);
},
500
);
},
_failedInputValidation(reason) { _failedInputValidation(reason) {
this.set("disableSave", true); this.set("disableSave", true);

@ -9,7 +9,7 @@ import Sharing from "discourse/lib/sharing";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { alias } from "@ember/object/computed"; import { alias } from "@ember/object/computed";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { getAbsoluteURL } from "discourse-common/lib/get-url"; import { getAbsoluteURL } from "discourse-common/lib/get-url";
import { schedule } from "@ember/runloop"; import { schedule } from "@ember/runloop";
import toMarkdown from "discourse/lib/to-markdown"; import toMarkdown from "discourse/lib/to-markdown";
@ -183,10 +183,9 @@ export default Component.extend({
const { isWinphone, isAndroid } = this.capabilities; const { isWinphone, isAndroid } = this.capabilities;
const wait = isWinphone || isAndroid ? INPUT_DELAY : 25; const wait = isWinphone || isAndroid ? INPUT_DELAY : 25;
const onSelectionChanged = discourseDebounce( const onSelectionChanged = () => {
() => this._selectionChanged(), discourseDebounce(this, this._selectionChanged, wait);
wait };
);
$(document) $(document)
.on("mousedown.quote-button", (e) => { .on("mousedown.quote-button", (e) => {

@ -1,7 +1,8 @@
import { cloak, uncloak } from "discourse/widgets/post-stream"; import { cloak, uncloak } from "discourse/widgets/post-stream";
import { debounce, next, scheduleOnce } from "@ember/runloop"; import { next, scheduleOnce } from "@ember/runloop";
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
import MountWidget from "discourse/components/mount-widget"; import MountWidget from "discourse/components/mount-widget";
import discourseDebounce from "discourse-common/lib/debounce";
import { isWorkaroundActive } from "discourse/lib/safari-hacks"; import { isWorkaroundActive } from "discourse/lib/safari-hacks";
import offsetCalculator from "discourse/lib/offset-calculator"; import offsetCalculator from "discourse/lib/offset-calculator";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
@ -309,12 +310,13 @@ export default MountWidget.extend({
}, },
_debouncedScroll() { _debouncedScroll() {
debounce(this, this._scrollTriggered, 10); discourseDebounce(this, this._scrollTriggered, 10);
}, },
didInsertElement() { didInsertElement() {
this._super(...arguments); this._super(...arguments);
const debouncedScroll = () => debounce(this, this._scrollTriggered, 10); const debouncedScroll = () =>
discourseDebounce(this, this._scrollTriggered, 10);
this._previouslyNearby = {}; this._previouslyNearby = {};

@ -1,8 +1,9 @@
import { cancel, debounce, next } from "@ember/runloop"; import { cancel, next } from "@ember/runloop";
import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction"; import { isLTR, isRTL, siteDir } from "discourse/lib/text-direction";
import I18n from "I18n"; import I18n from "I18n";
import TextField from "@ember/component/text-field"; import TextField from "@ember/component/text-field";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import discourseDebounce from "discourse-common/lib/debounce";
const DEBOUNCE_MS = 500; const DEBOUNCE_MS = 500;
@ -38,7 +39,11 @@ export default TextField.extend({
} }
if (this.onChange) { if (this.onChange) {
cancel(this._timer); cancel(this._timer);
this._timer = debounce(this, this._debouncedChange, DEBOUNCE_MS); this._timer = discourseDebounce(
this,
this._debouncedChange,
DEBOUNCE_MS
);
} }
} }
}, },

@ -3,9 +3,10 @@ import PanEvents, {
SWIPE_VELOCITY, SWIPE_VELOCITY,
SWIPE_VELOCITY_THRESHOLD, SWIPE_VELOCITY_THRESHOLD,
} from "discourse/mixins/pan-events"; } from "discourse/mixins/pan-events";
import { debounce, later } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import EmberObject from "@ember/object"; import EmberObject from "@ember/object";
import discourseDebounce from "discourse-common/lib/debounce";
import { later } from "@ember/runloop";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
import showModal from "discourse/lib/show-modal"; import showModal from "discourse/lib/show-modal";
@ -53,7 +54,7 @@ export default Component.extend(PanEvents, {
}, },
_checkSize() { _checkSize() {
debounce(this, this._performCheckSize, 300, true); discourseDebounce(this, this._performCheckSize, 300, true);
}, },
// we need to store this so topic progress has something to init with // we need to store this so topic progress has something to init with

@ -6,7 +6,7 @@ import {
authorizesOneOrMoreExtensions, authorizesOneOrMoreExtensions,
uploadIcon, uploadIcon,
} from "discourse/lib/uploads"; } from "discourse/lib/uploads";
import { cancel, debounce, run } from "@ember/runloop"; import { cancel, run } from "@ember/runloop";
import { import {
cannotPostAgain, cannotPostAgain,
durationTextFromSeconds, durationTextFromSeconds,
@ -22,6 +22,7 @@ import { Promise } from "rsvp";
import bootbox from "bootbox"; import bootbox from "bootbox";
import { buildQuote } from "discourse/lib/quote"; import { buildQuote } from "discourse/lib/quote";
import deprecated from "discourse-common/lib/deprecated"; import deprecated from "discourse-common/lib/deprecated";
import discourseDebounce from "discourse-common/lib/debounce";
import { emojiUnescape } from "discourse/lib/text"; import { emojiUnescape } from "discourse/lib/text";
import { escapeExpression } from "discourse/lib/utilities"; import { escapeExpression } from "discourse/lib/utilities";
import { getOwner } from "discourse-common/lib/get-owner"; import { getOwner } from "discourse-common/lib/get-owner";
@ -1162,7 +1163,11 @@ export default Controller.extend({
// in test debounce is Ember.run, this will cause // in test debounce is Ember.run, this will cause
// an infinite loop // an infinite loop
if (!isTesting()) { if (!isTesting()) {
this._saveDraftDebounce = debounce(this, this._saveDraft, 2000); this._saveDraftDebounce = discourseDebounce(
this,
this._saveDraft,
2000
);
} }
} else { } else {
this._saveDraftPromise = model.saveDraft().finally(() => { this._saveDraftPromise = model.saveDraft().finally(() => {
@ -1188,7 +1193,7 @@ export default Controller.extend({
if (Date.now() - this._lastDraftSaved > 15000) { if (Date.now() - this._lastDraftSaved > 15000) {
this._saveDraft(); this._saveDraft();
} else { } else {
let method = isTesting() ? run : debounce; let method = isTesting() ? run : discourseDebounce;
this._saveDraftDebounce = method(this, this._saveDraft, 2000); this._saveDraftDebounce = method(this, this._saveDraft, 2000);
} }
} }

@ -1,7 +1,7 @@
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import discourseComputed, { observes } from "discourse-common/utils/decorators"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { action } from "@ember/object"; import { action } from "@ember/object";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { gt } from "@ember/object/computed"; import { gt } from "@ember/object/computed";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
@ -19,9 +19,15 @@ export default Controller.extend({
showActions: false, showActions: false,
@observes("filterInput") @observes("filterInput")
_setFilter: discourseDebounce(function () { _setFilter() {
this.set("filter", this.filterInput); discourseDebounce(
}, 500), this,
function () {
this.set("filter", this.filterInput);
},
500
);
},
@observes("order", "asc", "filter") @observes("order", "asc", "filter")
_filtersChanged() { _filtersChanged() {

@ -1,7 +1,7 @@
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import discourseComputed, { observes } from "discourse-common/utils/decorators"; import discourseComputed, { observes } from "discourse-common/utils/decorators";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
export default Controller.extend({ export default Controller.extend({
@ -17,9 +17,15 @@ export default Controller.extend({
loading: false, loading: false,
@observes("filterInput") @observes("filterInput")
_setFilter: discourseDebounce(function () { _setFilter() {
this.set("filter", this.filterInput); discourseDebounce(
}, 500), this,
function () {
this.set("filter", this.filterInput);
},
500
);
},
@observes("order", "desc", "filter") @observes("order", "desc", "filter")
_filtersChanged() { _filtersChanged() {

@ -2,8 +2,8 @@ import Controller, { inject as controller } from "@ember/controller";
import I18n from "I18n"; import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import { action } from "@ember/object"; import { action } from "@ember/object";
import { debounce } from "@ember/runloop";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import discourseDebounce from "discourse-common/lib/debounce";
export default Controller.extend({ export default Controller.extend({
application: controller(), application: controller(),
@ -45,7 +45,7 @@ export default Controller.extend({
@action @action
onFilterChanged(filter) { onFilterChanged(filter) {
debounce(this, this._debouncedFilter, filter, INPUT_DELAY); discourseDebounce(this, this._debouncedFilter, filter, INPUT_DELAY);
}, },
@action @action

@ -1,7 +1,8 @@
import { cancel, debounce, schedule } from "@ember/runloop"; import { cancel, schedule } from "@ember/runloop";
import Controller from "@ember/controller"; import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality"; import ModalFunctionality from "discourse/mixins/modal-functionality";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
import discourseDebounce from "discourse-common/lib/debounce";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { prefixProtocol } from "discourse/lib/url"; import { prefixProtocol } from "discourse/lib/url";
import { searchForTerm } from "discourse/lib/search"; import { searchForTerm } from "discourse/lib/search";
@ -177,7 +178,7 @@ export default Controller.extend(ModalFunctionality, {
} }
}, },
search() { search() {
this._debounced = debounce(this, this.triggerSearch, 400); this._debounced = discourseDebounce(this, this.triggerSearch, 400);
}, },
}, },
}); });

@ -18,7 +18,7 @@ import bootbox from "bootbox";
import { bufferedProperty } from "discourse/mixins/buffered-content"; import { bufferedProperty } from "discourse/mixins/buffered-content";
import { buildQuote } from "discourse/lib/quote"; import { buildQuote } from "discourse/lib/quote";
import { deepMerge } from "discourse-common/lib/object"; import { deepMerge } from "discourse-common/lib/object";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { escapeExpression } from "discourse/lib/utilities"; import { escapeExpression } from "discourse/lib/utilities";
import { extractLinkMeta } from "discourse/lib/render-topic-featured-link"; import { extractLinkMeta } from "discourse/lib/render-topic-featured-link";
import isElementInViewport from "discourse/lib/is-element-in-viewport"; import isElementInViewport from "discourse/lib/is-element-in-viewport";
@ -1500,15 +1500,22 @@ export default Controller.extend(bufferedProperty("model"), {
); );
}, },
_scrollToPost: discourseDebounce(function (postNumber) { _scrollToPost(postNumber) {
const $post = $(`.topic-post article#post_${postNumber}`); discourseDebounce(
this,
function () {
const $post = $(`.topic-post article#post_${postNumber}`);
if ($post.length === 0 || isElementInViewport($post)) { if ($post.length === 0 || isElementInViewport($post)) {
return; return;
} }
$("html, body").animate({ scrollTop: $post.offset().top }, 1000); $("html, body").animate({ scrollTop: $post.offset().top }, 1000);
}, 500), },
postNumber,
500
);
},
unsubscribe() { unsubscribe() {
// never unsubscribe when navigating from topic to topic // never unsubscribe when navigating from topic to topic

@ -5,7 +5,7 @@ import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import Invite from "discourse/models/invite"; import Invite from "discourse/models/invite";
import bootbox from "bootbox"; import bootbox from "bootbox";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
export default Controller.extend({ export default Controller.extend({
@ -26,13 +26,19 @@ export default Controller.extend({
}, },
@observes("searchTerm") @observes("searchTerm")
_searchTermChanged: discourseDebounce(function () { _searchTermChanged() {
Invite.findInvitedBy( discourseDebounce(
this.user, this,
this.filter, function () {
this.searchTerm Invite.findInvitedBy(
).then((invites) => this.set("model", invites)); this.user,
}, INPUT_DELAY), this.filter,
this.searchTerm
).then((invites) => this.set("model", invites));
},
INPUT_DELAY
);
},
inviteRedeemed: equal("filter", "redeemed"), inviteRedeemed: equal("filter", "redeemed"),
invitePending: equal("filter", "pending"), invitePending: equal("filter", "pending"),

@ -1,5 +1,5 @@
import Controller, { inject as controller } from "@ember/controller"; import Controller, { inject as controller } from "@ember/controller";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { equal } from "@ember/object/computed"; import { equal } from "@ember/object/computed";
import { longDate } from "discourse/lib/formatter"; import { longDate } from "discourse/lib/formatter";
import { observes } from "discourse-common/utils/decorators"; import { observes } from "discourse-common/utils/decorators";
@ -37,9 +37,15 @@ export default Controller.extend({
}, },
@observes("nameInput") @observes("nameInput")
_setName: discourseDebounce(function () { _setName() {
this.set("name", this.nameInput); discourseDebounce(
}, 500), this,
function () {
this.set("name", this.nameInput);
},
500
);
},
@observes("model.canLoadMore") @observes("model.canLoadMore")
_showFooter: function () { _showFooter: function () {

@ -1,7 +1,8 @@
import { cancel, debounce, later } from "@ember/runloop"; import { cancel, later } from "@ember/runloop";
import { caretPosition, setCaretPosition } from "discourse/lib/utilities"; import { caretPosition, setCaretPosition } from "discourse/lib/utilities";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import Site from "discourse/models/site"; import Site from "discourse/models/site";
import discourseDebounce from "discourse-common/lib/debounce";
import { iconHTML } from "discourse-common/lib/icon-library"; import { iconHTML } from "discourse-common/lib/icon-library";
/** /**
@ -422,7 +423,7 @@ export default function (options) {
$(this).on("keyup.autocomplete", function (e) { $(this).on("keyup.autocomplete", function (e) {
if (options.debounced) { if (options.debounced) {
debounce(this, performAutocomplete, e, INPUT_DELAY); discourseDebounce(this, performAutocomplete, e, INPUT_DELAY);
} else { } else {
performAutocomplete(e); performAutocomplete(e);
} }

@ -4,7 +4,7 @@ import Category from "discourse/models/category";
import { Promise } from "rsvp"; import { Promise } from "rsvp";
import { SEPARATOR } from "discourse/lib/category-hashtags"; import { SEPARATOR } from "discourse/lib/category-hashtags";
import { TAG_HASHTAG_POSTFIX } from "discourse/lib/tag-hashtags"; import { TAG_HASHTAG_POSTFIX } from "discourse/lib/tag-hashtags";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import { isTesting } from "discourse-common/config/environment"; import { isTesting } from "discourse-common/config/environment";
@ -27,38 +27,47 @@ function searchTags(term, categories, limit) {
isTesting() ? 50 : 5000 isTesting() ? 50 : 5000
); );
const debouncedSearch = discourseDebounce((q, cats, resultFunc) => { const debouncedSearch = (q, cats, resultFunc) => {
oldSearch = $.ajax(getURL("/tags/filter/search"), { discourseDebounce(
type: "GET", this,
cache: true, function () {
data: { limit: limit, q }, oldSearch = $.ajax(getURL("/tags/filter/search"), {
}); type: "GET",
cache: true,
var returnVal = CANCELLED_STATUS; data: { limit: limit, q },
oldSearch
.then((r) => {
const categoryNames = cats.map((c) => c.model.get("name"));
const tags = r.results.map((tag) => {
const tagName = tag.text;
return {
name: tagName,
text: categoryNames.includes(tagName)
? `${tagName}${TAG_HASHTAG_POSTFIX}`
: tagName,
count: tag.count,
};
}); });
returnVal = cats.concat(tags); var returnVal = CANCELLED_STATUS;
})
.always(() => { oldSearch
oldSearch = null; .then((r) => {
resultFunc(returnVal); const categoryNames = cats.map((c) => c.model.get("name"));
});
}, 300); const tags = r.results.map((tag) => {
const tagName = tag.text;
return {
name: tagName,
text: categoryNames.includes(tagName)
? `${tagName}${TAG_HASHTAG_POSTFIX}`
: tagName,
count: tag.count,
};
});
returnVal = cats.concat(tags);
})
.always(() => {
oldSearch = null;
resultFunc(returnVal);
});
},
q,
cats,
resultFunc,
300
);
};
debouncedSearch(term, categories, (result) => { debouncedSearch(term, categories, (result) => {
cancel(clearPromise); cancel(clearPromise);

@ -1,6 +1,5 @@
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { run } from "@ember/runloop"; import discourseDebounce from "discourse-common/lib/debounce";
const { debounce } = run;
let _queue = []; let _queue = [];
let _processing = 0; let _processing = 0;
@ -33,7 +32,7 @@ export default {
_queue.push({ runnable: () => callback, type, params }); _queue.push({ runnable: () => callback, type, params });
debounce(this, this._processQueue, DEBOUNCING_DELAY); discourseDebounce(this, this._processQueue, DEBOUNCING_DELAY);
}, },
_processQueue() { _processQueue() {
@ -50,7 +49,7 @@ export default {
// if queue has still jobs after splice, we request a future processing // if queue has still jobs after splice, we request a future processing
if (_queue.length > 0) { if (_queue.length > 0) {
debounce(this, this._processQueue, DEBOUNCING_DELAY); discourseDebounce(this, this._processQueue, DEBOUNCING_DELAY);
} }
let reports = {}; let reports = {};
@ -79,7 +78,7 @@ export default {
.finally(() => { .finally(() => {
_processing--; _processing--;
debounce(this, this._processQueue, DEBOUNCING_DELAY); discourseDebounce(this, this._processQueue, DEBOUNCING_DELAY);
}); });
}, },

@ -3,7 +3,7 @@ import {
safariHacksDisabled, safariHacksDisabled,
} from "discourse/lib/utilities"; } from "discourse/lib/utilities";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { helperContext } from "discourse-common/lib/helpers"; import { helperContext } from "discourse-common/lib/helpers";
import { later } from "@ember/runloop"; import { later } from "@ember/runloop";
@ -145,7 +145,9 @@ function positioningWorkaround($fixedElement) {
positioningWorkaround.blur(evt); positioningWorkaround.blur(evt);
}; };
var blurred = discourseDebounce(blurredNow, INPUT_DELAY); var blurred = function (evt) {
discourseDebounce(this, blurredNow, evt, INPUT_DELAY);
};
var positioningHack = function (evt) { var positioningHack = function (evt) {
let _this = this; let _this = this;
@ -214,13 +216,19 @@ function positioningWorkaround($fixedElement) {
} }
} }
const checkForInputs = discourseDebounce(function () { const checkForInputs = function () {
attachTouchStart(fixedElement, lastTouched); discourseDebounce(
this,
function () {
attachTouchStart(fixedElement, lastTouched);
$fixedElement.find("input[type=text],textarea").each(function () { $fixedElement.find("input[type=text],textarea").each(function () {
attachTouchStart(this, positioningHack); attachTouchStart(this, positioningHack);
}); });
}, 100); },
100
);
};
positioningWorkaround.touchstartEvent = function (element) { positioningWorkaround.touchstartEvent = function (element) {
var triggerHack = positioningHack.bind(element); var triggerHack = positioningHack.bind(element);

@ -1,7 +1,7 @@
import { cancel, later } from "@ember/runloop"; import { cancel, later } from "@ember/runloop";
import { CANCELLED_STATUS } from "discourse/lib/autocomplete"; import { CANCELLED_STATUS } from "discourse/lib/autocomplete";
import { Promise } from "rsvp"; import { Promise } from "rsvp";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { emailValid } from "discourse/lib/utilities"; import { emailValid } from "discourse/lib/utilities";
import { isTesting } from "discourse-common/config/environment"; import { isTesting } from "discourse-common/config/environment";
import { userPath } from "discourse/lib/url"; import { userPath } from "discourse/lib/url";
@ -81,7 +81,32 @@ function performSearch(
}); });
} }
var debouncedSearch = discourseDebounce(performSearch, 300); var debouncedSearch = function (
term,
topicId,
categoryId,
includeGroups,
includeMentionableGroups,
includeMessageableGroups,
allowedUsers,
groupMembersOf,
resultsFn
) {
discourseDebounce(
this,
performSearch,
term,
topicId,
categoryId,
includeGroups,
includeMentionableGroups,
includeMessageableGroups,
allowedUsers,
groupMembersOf,
resultsFn,
300
);
};
function organizeResults(r, options) { function organizeResults(r, options) {
if (r === CANCELLED_STATUS) { if (r === CANCELLED_STATUS) {

@ -1,5 +1,6 @@
import { debounce, later } from "@ember/runloop";
import Mixin from "@ember/object/mixin"; import Mixin from "@ember/object/mixin";
import discourseDebounce from "discourse-common/lib/debounce";
import { later } from "@ember/runloop";
const helper = { const helper = {
offset() { offset() {
@ -15,7 +16,7 @@ export default Mixin.create({
init() { init() {
this._super(...arguments); this._super(...arguments);
this.queueDockCheck = () => { this.queueDockCheck = () => {
debounce(this, this.safeDockCheck, 5); discourseDebounce(this, this.safeDockCheck, 5);
}; };
}, },

@ -1,5 +1,5 @@
import Mixin from "@ember/object/mixin"; import Mixin from "@ember/object/mixin";
import { debounce } from "@ember/runloop"; import discourseDebounce from "discourse-common/lib/debounce";
// Small buffer so that very tiny scrolls don't trigger mobile header switch // Small buffer so that very tiny scrolls don't trigger mobile header switch
const MOBILE_SCROLL_TOLERANCE = 5; const MOBILE_SCROLL_TOLERANCE = 5;
@ -47,9 +47,13 @@ export default Mixin.create({
// If the user reaches the very bottom of the topic, we only want to reset // If the user reaches the very bottom of the topic, we only want to reset
// this scroll direction after a second scrolldown. This is a nicer event // this scroll direction after a second scrolldown. This is a nicer event
// similar to what Safari and Chrome do. // similar to what Safari and Chrome do.
debounce(() => { discourseDebounce(
this._bottomHit = 1; this,
}, 1000); function () {
this._bottomHit = 1;
},
1000
);
if (this._bottomHit === 1) { if (this._bottomHit === 1) {
this.set("mobileScrollDirection", null); this.set("mobileScrollDirection", null);

@ -1,5 +1,5 @@
import Mixin from "@ember/object/mixin"; import Mixin from "@ember/object/mixin";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { scheduleOnce } from "@ember/runloop"; import { scheduleOnce } from "@ember/runloop";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
@ -45,7 +45,9 @@ const Scrolling = Mixin.create({
}; };
if (opts.debounce) { if (opts.debounce) {
onScrollMethod = discourseDebounce(onScrollMethod, opts.debounce); onScrollMethod = () => {
discourseDebounce(this, onScrollMethod, opts.debounce);
};
} }
ScrollingDOMMethods.bindOnScroll(onScrollMethod, opts.name); ScrollingDOMMethods.bindOnScroll(onScrollMethod, opts.name);

@ -3,7 +3,7 @@ import I18n from "I18n";
import Mixin from "@ember/object/mixin"; import Mixin from "@ember/object/mixin";
import User from "discourse/models/user"; import User from "discourse/models/user";
import discourseComputed from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { setting } from "discourse/lib/computed"; import { setting } from "discourse/lib/computed";
@ -14,20 +14,26 @@ export default Mixin.create({
minUsernameLength: setting("min_username_length"), minUsernameLength: setting("min_username_length"),
fetchExistingUsername: discourseDebounce(function () { fetchExistingUsername() {
User.checkUsername(null, this.accountEmail).then((result) => { discourseDebounce(
if ( this,
result.suggestion && function () {
(isEmpty(this.accountUsername) || User.checkUsername(null, this.accountEmail).then((result) => {
this.accountUsername === this.get("authOptions.username")) if (
) { result.suggestion &&
this.setProperties({ (isEmpty(this.accountUsername) ||
accountUsername: result.suggestion, this.accountUsername === this.get("authOptions.username"))
prefilledUsername: result.suggestion, ) {
this.setProperties({
accountUsername: result.suggestion,
prefilledUsername: result.suggestion,
});
}
}); });
} },
}); 500
}, 500), );
},
@discourseComputed("accountUsername") @discourseComputed("accountUsername")
basicUsernameValidation(accountUsername) { basicUsernameValidation(accountUsername) {
@ -87,54 +93,61 @@ export default Mixin.create({
); );
}, },
checkUsernameAvailability: discourseDebounce(function () { checkUsernameAvailability() {
if (this.shouldCheckUsernameAvailability()) { discourseDebounce(
return User.checkUsername(this.accountUsername, this.accountEmail).then( this,
(result) => { function () {
this.set("isDeveloper", false); if (this.shouldCheckUsernameAvailability()) {
if (result.available) { return User.checkUsername(
if (result.is_developer) { this.accountUsername,
this.set("isDeveloper", true); this.accountEmail
} ).then((result) => {
return this.set( this.set("isDeveloper", false);
"uniqueUsernameValidation", if (result.available) {
EmberObject.create({ if (result.is_developer) {
ok: true, this.set("isDeveloper", true);
reason: I18n.t("user.username.available"), }
})
);
} else {
const failedAttrs = {
failed: true,
element: document.querySelector("#new-account-username"),
};
if (result.suggestion) {
return this.set( return this.set(
"uniqueUsernameValidation", "uniqueUsernameValidation",
EmberObject.create( EmberObject.create({
Object.assign(failedAttrs, { ok: true,
reason: I18n.t("user.username.not_available", result), reason: I18n.t("user.username.available"),
}) })
)
); );
} else { } else {
return this.set( const failedAttrs = {
"uniqueUsernameValidation", failed: true,
EmberObject.create( element: document.querySelector("#new-account-username"),
Object.assign(failedAttrs, { };
reason: result.errors
? result.errors.join(" ") if (result.suggestion) {
: I18n.t("user.username.not_available_no_suggestion"), return this.set(
}) "uniqueUsernameValidation",
) EmberObject.create(
); Object.assign(failedAttrs, {
reason: I18n.t("user.username.not_available", result),
})
)
);
} else {
return this.set(
"uniqueUsernameValidation",
EmberObject.create(
Object.assign(failedAttrs, {
reason: result.errors
? result.errors.join(" ")
: I18n.t("user.username.not_available_no_suggestion"),
})
)
);
}
} }
} });
} }
); },
} 500
}, 500), );
},
// Actually wait for the async name check before we're 100% sure we're good to go // Actually wait for the async name check before we're 100% sure we're good to go
@discourseComputed("uniqueUsernameValidation", "basicUsernameValidation") @discourseComputed("uniqueUsernameValidation", "basicUsernameValidation")

@ -1,7 +1,7 @@
import { Placeholder } from "discourse/lib/posts-with-placeholders"; import { Placeholder } from "discourse/lib/posts-with-placeholders";
import { addWidgetCleanCallback } from "discourse/components/mount-widget"; import { addWidgetCleanCallback } from "discourse/components/mount-widget";
import { createWidget } from "discourse/widgets/widget"; import { createWidget } from "discourse/widgets/widget";
import { debounce } from "@ember/runloop"; import discourseDebounce from "discourse-common/lib/debounce";
import { isTesting } from "discourse-common/config/environment"; import { isTesting } from "discourse-common/config/environment";
import transformPost from "discourse/lib/transform-post"; import transformPost from "discourse/lib/transform-post";
@ -41,7 +41,7 @@ export function cloak(post, component) {
_heights[post.id] = $post.outerHeight(); _heights[post.id] = $post.outerHeight();
component.dirtyKeys.keyDirty(`post-${post.id}`); component.dirtyKeys.keyDirty(`post-${post.id}`);
debounce(component, "queueRerender", 1000); discourseDebounce(component, "queueRerender", 1000);
} }
export function uncloak(post, component) { export function uncloak(post, component) {

@ -1,7 +1,7 @@
import { isValidSearchTerm, searchForTerm } from "discourse/lib/search"; import { isValidSearchTerm, searchForTerm } from "discourse/lib/search";
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
import { createWidget } from "discourse/widgets/widget"; import { createWidget } from "discourse/widgets/widget";
import { debounce } from "@ember/runloop"; import discourseDebounce from "discourse-common/lib/debounce";
import { get } from "@ember/object"; import { get } from "@ember/object";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import { h } from "virtual-dom"; import { h } from "virtual-dom";
@ -284,7 +284,7 @@ export default createWidget("search-menu", {
searchData.noResults = false; searchData.noResults = false;
this.searchService().set("highlightTerm", searchData.term); this.searchService().set("highlightTerm", searchData.term);
searchData.loading = true; searchData.loading = true;
debounce(SearchHelper, SearchHelper.perform, this, 400); discourseDebounce(SearchHelper, SearchHelper.perform, this, 400);
}, },
moreOfType(type) { moreOfType(type) {

@ -1,5 +1,5 @@
import I18n from "I18n"; import I18n from "I18n";
import { debounce } from "@ember/runloop"; import discourseDebounce from "discourse-common/lib/debounce";
let _cache = {}; let _cache = {};
@ -184,7 +184,7 @@ export function resolveAllShortUrls(ajax, siteSettings, scope, opts) {
shortUploadElements = scope.querySelectorAll(attributes); shortUploadElements = scope.querySelectorAll(attributes);
if (shortUploadElements.length > 0) { if (shortUploadElements.length > 0) {
// this is carefully batched so we can do a leading debounce (trigger right away) // this is carefully batched so we can do a leading debounce (trigger right away)
return debounce( return discourseDebounce(
null, null,
_loadShortUrls, _loadShortUrls,
shortUploadElements, shortUploadElements,

@ -3,14 +3,7 @@ import PluginApiMixin, {
applyContentPluginApiCallbacks, applyContentPluginApiCallbacks,
applyOnChangePluginApiCallbacks, applyOnChangePluginApiCallbacks,
} from "select-kit/mixins/plugin-api"; } from "select-kit/mixins/plugin-api";
import { import { bind, cancel, next, schedule, throttle } from "@ember/runloop";
bind,
cancel,
debounce,
next,
schedule,
throttle,
} from "@ember/runloop";
import { isEmpty, isNone, isPresent } from "@ember/utils"; import { isEmpty, isNone, isPresent } from "@ember/utils";
import Component from "@ember/component"; import Component from "@ember/component";
import I18n from "I18n"; import I18n from "I18n";
@ -19,6 +12,7 @@ import { Promise } from "rsvp";
import UtilsMixin from "select-kit/mixins/utils"; import UtilsMixin from "select-kit/mixins/utils";
import { createPopper } from "@popperjs/core"; import { createPopper } from "@popperjs/core";
import deprecated from "discourse-common/lib/deprecated"; import deprecated from "discourse-common/lib/deprecated";
import discourseDebounce from "discourse-common/lib/debounce";
import { guidFor } from "@ember/object/internals"; import { guidFor } from "@ember/object/internals";
import { makeArray } from "discourse-common/lib/helpers"; import { makeArray } from "discourse-common/lib/helpers";
@ -386,7 +380,7 @@ export default Component.extend(
cancel(this._searchPromise); cancel(this._searchPromise);
} }
debounce(this, this._debouncedInput, event.target.value, 200); discourseDebounce(this, this._debouncedInput, event.target.value, 200);
}, },
_debouncedInput(filter) { _debouncedInput(filter) {

@ -6,7 +6,7 @@ import I18n from "I18n";
import { INPUT_DELAY } from "discourse-common/config/environment"; import { INPUT_DELAY } from "discourse-common/config/environment";
import { Promise } from "rsvp"; import { Promise } from "rsvp";
import { cookAsync } from "discourse/lib/text"; import { cookAsync } from "discourse/lib/text";
import discourseDebounce from "discourse/lib/debounce"; import discourseDebounce from "discourse-common/lib/debounce";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import loadScript from "discourse/lib/load-script"; import loadScript from "discourse/lib/load-script";
import { notEmpty } from "@ember/object/computed"; import { notEmpty } from "@ember/object/computed";
@ -56,18 +56,24 @@ export default Component.extend({
}, },
@observes("markup") @observes("markup")
_renderPreview: discourseDebounce(function () { _renderPreview() {
const markup = this.markup; discourseDebounce(
this,
function () {
const markup = this.markup;
if (markup) { if (markup) {
cookAsync(markup).then((result) => { cookAsync(markup).then((result) => {
this.set("currentPreview", result); this.set("currentPreview", result);
schedule("afterRender", () => schedule("afterRender", () =>
this.$(".preview .discourse-local-date").applyLocalDates() this.$(".preview .discourse-local-date").applyLocalDates()
); );
}); });
} }
}, INPUT_DELAY), },
INPUT_DELAY
);
},
@computed("date", "toDate", "toTime") @computed("date", "toDate", "toTime")
isRange(date, toDate, toTime) { isRange(date, toDate, toTime) {