diff --git a/app/assets/javascripts/admin/addon/components/admin-backups-logs.js b/app/assets/javascripts/admin/addon/components/admin-backups-logs.js index 2531919aa2a..f691f3ffc69 100644 --- a/app/assets/javascripts/admin/addon/components/admin-backups-logs.js +++ b/app/assets/javascripts/admin/addon/components/admin-backups-logs.js @@ -1,7 +1,7 @@ import { observes, on } from "discourse-common/utils/decorators"; import Component from "@ember/component"; import I18n from "I18n"; -import discourseDebounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse-common/lib/debounce"; import { scheduleOnce } from "@ember/runloop"; export default Component.extend({ @@ -33,9 +33,7 @@ export default Component.extend({ } }, - @on("init") - @observes("logs.[]") - _updateFormattedLogs: discourseDebounce(function () { + _updateFormattedLogsFunc: function () { const logs = this.logs; if (logs.length === 0) { return; @@ -57,7 +55,13 @@ export default Component.extend({ this.renderLogs(); scheduleOnce("afterRender", this, this._scrollDown); - }, 150), + }, + + @on("init") + @observes("logs.[]") + _updateFormattedLogs() { + discourseDebounce(this, this._updateFormattedLogsFunc, 150); + }, renderLogs() { const formattedLogs = this.formattedLogs; diff --git a/app/assets/javascripts/admin/addon/components/admin-report-chart.js b/app/assets/javascripts/admin/addon/components/admin-report-chart.js index 40f927f55da..8a894b95629 100644 --- a/app/assets/javascripts/admin/addon/components/admin-report-chart.js +++ b/app/assets/javascripts/admin/addon/components/admin-report-chart.js @@ -1,8 +1,9 @@ -import { debounce, schedule } from "@ember/runloop"; import Component from "@ember/component"; +import discourseDebounce from "discourse-common/lib/debounce"; import loadScript from "discourse/lib/load-script"; import { makeArray } from "discourse-common/lib/helpers"; import { number } from "discourse/lib/formatter"; +import { schedule } from "@ember/runloop"; export default Component.extend({ classNames: ["admin-report-chart"], @@ -14,7 +15,7 @@ export default Component.extend({ this._super(...arguments); this.resizeHandler = () => - debounce(this, this._scheduleChartRendering, 500); + discourseDebounce(this, this._scheduleChartRendering, 500); }, didInsertElement() { @@ -34,7 +35,7 @@ export default Component.extend({ didReceiveAttrs() { this._super(...arguments); - debounce(this, this._scheduleChartRendering, 100); + discourseDebounce(this, this._scheduleChartRendering, 100); }, _scheduleChartRendering() { diff --git a/app/assets/javascripts/admin/addon/components/admin-report-stacked-chart.js b/app/assets/javascripts/admin/addon/components/admin-report-stacked-chart.js index b07a8261c8e..4cac3b15dba 100644 --- a/app/assets/javascripts/admin/addon/components/admin-report-stacked-chart.js +++ b/app/assets/javascripts/admin/addon/components/admin-report-stacked-chart.js @@ -1,8 +1,9 @@ -import { debounce, schedule } from "@ember/runloop"; import Component from "@ember/component"; +import discourseDebounce from "discourse-common/lib/debounce"; import loadScript from "discourse/lib/load-script"; import { makeArray } from "discourse-common/lib/helpers"; import { number } from "discourse/lib/formatter"; +import { schedule } from "@ember/runloop"; export default Component.extend({ classNames: ["admin-report-chart", "admin-report-stacked-chart"], @@ -11,7 +12,7 @@ export default Component.extend({ this._super(...arguments); this.resizeHandler = () => - debounce(this, this._scheduleChartRendering, 500); + discourseDebounce(this, this._scheduleChartRendering, 500); }, didInsertElement() { @@ -31,7 +32,7 @@ export default Component.extend({ didReceiveAttrs() { this._super(...arguments); - debounce(this, this._scheduleChartRendering, 100); + discourseDebounce(this, this._scheduleChartRendering, 100); }, _scheduleChartRendering() { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-dashboard-reports.js b/app/assets/javascripts/admin/addon/controllers/admin-dashboard-reports.js index 445eb667d8b..ab10591c808 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-dashboard-reports.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-dashboard-reports.js @@ -1,7 +1,7 @@ import Controller from "@ember/controller"; import { INPUT_DELAY } from "discourse-common/config/environment"; -import { debounce } from "@ember/runloop"; import discourseComputed from "discourse-common/utils/decorators"; +import discourseDebounce from "discourse-common/lib/debounce"; const { get } = Ember; @@ -34,7 +34,7 @@ export default Controller.extend({ actions: { filterReports(filter) { - debounce(this, this._performFiltering, filter, INPUT_DELAY); + discourseDebounce(this, this._performFiltering, filter, INPUT_DELAY); }, }, diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-bounced.js b/app/assets/javascripts/admin/addon/controllers/admin-email-bounced.js index 874b7147fb9..15005495645 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-bounced.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-bounced.js @@ -1,11 +1,11 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; 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"; export default AdminEmailLogsController.extend({ @observes("filter.{status,user,address,type}") - filterEmailLogs: discourseDebounce(function () { - this.loadLogs(); - }, INPUT_DELAY), + filterEmailLogs() { + discourseDebounce(this, this.loadLogs, INPUT_DELAY); + }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-received.js b/app/assets/javascripts/admin/addon/controllers/admin-email-received.js index 8b977c4d507..0a2933fa23d 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-received.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-received.js @@ -1,14 +1,14 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import { INPUT_DELAY } from "discourse-common/config/environment"; 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"; export default AdminEmailLogsController.extend({ @observes("filter.{status,from,to,subject}") - filterIncomingEmails: discourseDebounce(function () { - this.loadLogs(IncomingEmail); - }, INPUT_DELAY), + filterIncomingEmails() { + discourseDebounce(this, this.loadLogs, IncomingEmail, INPUT_DELAY); + }, actions: { loadMore() { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-rejected.js b/app/assets/javascripts/admin/addon/controllers/admin-email-rejected.js index 5be59e6ab89..89c67f3cf95 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-rejected.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-rejected.js @@ -1,14 +1,14 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; import { INPUT_DELAY } from "discourse-common/config/environment"; 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"; export default AdminEmailLogsController.extend({ @observes("filter.{status,from,to,subject,error}") - filterIncomingEmails: discourseDebounce(function () { - this.loadLogs(IncomingEmail); - }, INPUT_DELAY), + filterIncomingEmails() { + discourseDebounce(this, this.loadLogs, IncomingEmail, INPUT_DELAY); + }, actions: { loadMore() { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-sent.js b/app/assets/javascripts/admin/addon/controllers/admin-email-sent.js index 660f681c62e..5cc83109f8f 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-sent.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-sent.js @@ -1,11 +1,11 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; 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"; export default AdminEmailLogsController.extend({ @observes("filter.{status,user,address,type,reply_key}") - filterEmailLogs: discourseDebounce(function () { - this.loadLogs(); - }, INPUT_DELAY), + filterEmailLogs() { + discourseDebounce(this, this.loadLogs, INPUT_DELAY); + }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-email-skipped.js b/app/assets/javascripts/admin/addon/controllers/admin-email-skipped.js index 874b7147fb9..15005495645 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-email-skipped.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-email-skipped.js @@ -1,11 +1,11 @@ import AdminEmailLogsController from "admin/controllers/admin-email-logs"; 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"; export default AdminEmailLogsController.extend({ @observes("filter.{status,user,address,type}") - filterEmailLogs: discourseDebounce(function () { - this.loadLogs(); - }, INPUT_DELAY), + filterEmailLogs() { + discourseDebounce(this, this.loadLogs, INPUT_DELAY); + }, }); diff --git a/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-ip-addresses.js b/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-ip-addresses.js index 32ed7ad944d..fb923131ed7 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-ip-addresses.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-logs-screened-ip-addresses.js @@ -3,7 +3,7 @@ import I18n from "I18n"; import { INPUT_DELAY } from "discourse-common/config/environment"; import ScreenedIpAddress from "admin/models/screened-ip-address"; 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 { observes } from "discourse-common/utils/decorators"; import { outputExportResult } from "discourse/lib/export-result"; @@ -13,13 +13,17 @@ export default Controller.extend({ filter: null, savedIpAddress: null, - @observes("filter") - show: discourseDebounce(function () { + _debouncedShow() { this.set("loading", true); ScreenedIpAddress.findAll(this.filter).then((result) => { this.setProperties({ model: result, loading: false }); }); - }, INPUT_DELAY), + }, + + @observes("filter") + show() { + discourseDebounce(this, this._debouncedShow, INPUT_DELAY); + }, actions: { allow(record) { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js b/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js index 2aaf42ff3e8..b11b3973117 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-permalinks.js @@ -3,20 +3,24 @@ import I18n from "I18n"; import { INPUT_DELAY } from "discourse-common/config/environment"; import Permalink from "admin/models/permalink"; import bootbox from "bootbox"; -import discourseDebounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse-common/lib/debounce"; import { observes } from "discourse-common/utils/decorators"; export default Controller.extend({ loading: false, filter: null, - @observes("filter") - show: discourseDebounce(function () { + _debouncedShow() { Permalink.findAll(this.filter).then((result) => { this.set("model", result); this.set("loading", false); }); - }, INPUT_DELAY), + }, + + @observes("filter") + show() { + discourseDebounce(this, this._debouncedShow, INPUT_DELAY); + }, actions: { recordAdded(arg) { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-site-settings.js b/app/assets/javascripts/admin/addon/controllers/admin-site-settings.js index 7dea3a237cf..58859ae9335 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-site-settings.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-site-settings.js @@ -2,7 +2,7 @@ import Controller from "@ember/controller"; import I18n from "I18n"; import { INPUT_DELAY } from "discourse-common/config/environment"; 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 { observes } from "discourse-common/utils/decorators"; @@ -112,13 +112,19 @@ export default Controller.extend({ }, @observes("filter", "onlyOverridden", "model") - filterContent: discourseDebounce(function () { - if (this._skipBounce) { - this.set("_skipBounce", false); - } else { - this.filterContentNow(this.categoryNameKey); - } - }, INPUT_DELAY), + filterContent() { + discourseDebounce( + this, + () => { + if (this._skipBounce) { + this.set("_skipBounce", false); + } else { + this.filterContentNow(this.categoryNameKey); + } + }, + INPUT_DELAY + ); + }, actions: { clearFilter() { diff --git a/app/assets/javascripts/admin/addon/controllers/admin-site-text-index.js b/app/assets/javascripts/admin/addon/controllers/admin-site-text-index.js index fb1a238c3e9..289c49da87f 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-site-text-index.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-site-text-index.js @@ -1,5 +1,5 @@ import Controller from "@ember/controller"; -import { debounce } from "@ember/runloop"; +import discourseDebounce from "discourse-common/lib/debounce"; let lastSearch; export default Controller.extend({ @@ -28,14 +28,14 @@ export default Controller.extend({ toggleOverridden() { this.toggleProperty("overridden"); this.set("searching", true); - debounce(this, this._performSearch, 400); + discourseDebounce(this, this._performSearch, 400); }, search() { const q = this.q; if (q !== lastSearch) { this.set("searching", true); - debounce(this, this._performSearch, 400); + discourseDebounce(this, this._performSearch, 400); lastSearch = q; } }, diff --git a/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js b/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js index 0c2952e9297..b0b25706e5a 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-users-list-show.js @@ -4,7 +4,7 @@ import CanCheckEmails from "discourse/mixins/can-check-emails"; import Controller from "@ember/controller"; import I18n from "I18n"; 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"; export default Controller.extend(CanCheckEmails, { @@ -32,9 +32,9 @@ export default Controller.extend(CanCheckEmails, { }, @observes("listFilter") - _filterUsers: discourseDebounce(function () { - this.resetFilters(); - }, INPUT_DELAY), + _filterUsers() { + discourseDebounce(this, this.resetFilters, INPUT_DELAY); + }, resetFilters() { this._page = 1; diff --git a/app/assets/javascripts/admin/addon/controllers/admin-watched-words.js b/app/assets/javascripts/admin/addon/controllers/admin-watched-words.js index 3ed8bee62fa..cfd86c6c22d 100644 --- a/app/assets/javascripts/admin/addon/controllers/admin-watched-words.js +++ b/app/assets/javascripts/admin/addon/controllers/admin-watched-words.js @@ -2,7 +2,7 @@ import Controller from "@ember/controller"; import EmberObject from "@ember/object"; import { INPUT_DELAY } from "discourse-common/config/environment"; 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 { observes } from "discourse-common/utils/decorators"; @@ -48,10 +48,16 @@ export default Controller.extend({ }, @observes("filter") - filterContent: discourseDebounce(function () { - this.filterContentNow(); - this.set("filtered", !isEmpty(this.filter)); - }, INPUT_DELAY), + filterContent() { + discourseDebounce( + this, + function () { + this.filterContentNow(); + this.set("filtered", !isEmpty(this.filter)); + }, + INPUT_DELAY + ); + }, actions: { clearFilter() { diff --git a/app/assets/javascripts/discourse-common/addon/lib/debounce.js b/app/assets/javascripts/discourse-common/addon/lib/debounce.js new file mode 100644 index 00000000000..541498fda63 --- /dev/null +++ b/app/assets/javascripts/discourse-common/addon/lib/debounce.js @@ -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); + } +} diff --git a/app/assets/javascripts/discourse/app/components/choose-message.js b/app/assets/javascripts/discourse/app/components/choose-message.js index 22833e61f5e..09d9a9123cb 100644 --- a/app/assets/javascripts/discourse/app/components/choose-message.js +++ b/app/assets/javascripts/discourse/app/components/choose-message.js @@ -1,5 +1,5 @@ import Component from "@ember/component"; -import discourseDebounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse-common/lib/debounce"; import { get } from "@ember/object"; import { isEmpty } from "@ember/utils"; import { next } from "@ember/runloop"; @@ -30,31 +30,38 @@ export default Component.extend({ this.set("loading", false); }, - search: discourseDebounce(function (title) { - const currentTopicId = this.currentTopicId; + search(title) { + discourseDebounce( + this, + function () { + const currentTopicId = this.currentTopicId; - if (isEmpty(title)) { - this.setProperties({ messages: null, loading: false }); - return; - } + if (isEmpty(title)) { + this.setProperties({ messages: null, loading: false }); + return; + } - searchForTerm(title, { - typeFilter: "private_messages", - searchForId: true, - restrictToArchetype: "private_message", - }).then((results) => { - if (results && results.posts && results.posts.length > 0) { - this.set( - "messages", - results.posts - .mapBy("topic") - .filter((t) => t.get("id") !== currentTopicId) - ); - } else { - this.setProperties({ messages: null, loading: false }); - } - }); - }, 300), + searchForTerm(title, { + typeFilter: "private_messages", + searchForId: true, + restrictToArchetype: "private_message", + }).then((results) => { + if (results && results.posts && results.posts.length > 0) { + this.set( + "messages", + results.posts + .mapBy("topic") + .filter((t) => t.get("id") !== currentTopicId) + ); + } else { + this.setProperties({ messages: null, loading: false }); + } + }); + }, + title, + 300 + ); + }, actions: { chooseMessage(message) { diff --git a/app/assets/javascripts/discourse/app/components/choose-topic.js b/app/assets/javascripts/discourse/app/components/choose-topic.js index 01352333ae3..ab0bc966bfc 100644 --- a/app/assets/javascripts/discourse/app/components/choose-topic.js +++ b/app/assets/javascripts/discourse/app/components/choose-topic.js @@ -1,6 +1,6 @@ import discourseComputed, { observes } from "discourse-common/utils/decorators"; import Component from "@ember/component"; -import discourseDebounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse-common/lib/debounce"; import { isEmpty } from "@ember/utils"; import { next } from "@ember/runloop"; import { searchForTerm } from "discourse/lib/search"; @@ -64,37 +64,46 @@ export default Component.extend({ this.set("loading", false); }, - search: discourseDebounce(function (title) { - if (!this.element || this.isDestroying || this.isDestroyed) { - return; - } + search(title) { + discourseDebounce( + this, + function () { + if (!this.element || this.isDestroying || this.isDestroyed) { + return; + } - if (isEmpty(title) && isEmpty(this.additionalFilters)) { - this.setProperties({ topics: null, loading: false }); - return; - } + if (isEmpty(title) && isEmpty(this.additionalFilters)) { + this.setProperties({ topics: null, loading: false }); + return; + } - const currentTopicId = this.currentTopicId; - const titleWithFilters = `${title} ${this.additionalFilters}`; - let searchParams = {}; + const currentTopicId = this.currentTopicId; + const titleWithFilters = `${title} ${this.additionalFilters}`; + let searchParams = {}; - if (!isEmpty(title)) { - searchParams.typeFilter = "topic"; - searchParams.restrictToArchetype = "regular"; - searchParams.searchForId = true; - } + if (!isEmpty(title)) { + searchParams.typeFilter = "topic"; + searchParams.restrictToArchetype = "regular"; + searchParams.searchForId = true; + } - searchForTerm(titleWithFilters, searchParams).then((results) => { - if (results && results.posts && results.posts.length > 0) { - this.set( - "topics", - results.posts.mapBy("topic").filter((t) => t.id !== currentTopicId) - ); - } else { - this.setProperties({ topics: null, loading: false }); - } - }); - }, 300), + searchForTerm(titleWithFilters, searchParams).then((results) => { + if (results && results.posts && results.posts.length > 0) { + this.set( + "topics", + results.posts + .mapBy("topic") + .filter((t) => t.id !== currentTopicId) + ); + } else { + this.setProperties({ topics: null, loading: false }); + } + }); + }, + title, + 300 + ); + }, actions: { chooseTopic(topic) { diff --git a/app/assets/javascripts/discourse/app/components/composer-body.js b/app/assets/javascripts/discourse/app/components/composer-body.js index 24f978c6877..4301331e15f 100644 --- a/app/assets/javascripts/discourse/app/components/composer-body.js +++ b/app/assets/javascripts/discourse/app/components/composer-body.js @@ -1,16 +1,10 @@ -import { - cancel, - debounce, - later, - run, - schedule, - throttle, -} from "@ember/runloop"; +import { cancel, later, run, schedule, throttle } from "@ember/runloop"; import discourseComputed, { observes } from "discourse-common/utils/decorators"; import Component from "@ember/component"; import Composer from "discourse/models/composer"; import KeyEnterEscape from "discourse/mixins/key-enter-escape"; import afterTransition from "discourse/lib/after-transition"; +import discourseDebounce from "discourse-common/lib/debounce"; import { headerHeight } from "discourse/components/site-header"; import { iOSWithVisualViewport } from "discourse/lib/utilities"; import positioningWorkaround from "discourse/lib/safari-hacks"; @@ -76,7 +70,7 @@ export default Component.extend(KeyEnterEscape, { return; } - debounce(this, this.debounceMove, 300); + discourseDebounce(this, this.debounceMove, 300); }); }, diff --git a/app/assets/javascripts/discourse/app/components/composer-editor.js b/app/assets/javascripts/discourse/app/components/composer-editor.js index bc90e6bf8cc..45dc0da37f4 100644 --- a/app/assets/javascripts/discourse/app/components/composer-editor.js +++ b/app/assets/javascripts/discourse/app/components/composer-editor.js @@ -15,7 +15,6 @@ import { inCodeBlock, tinyAvatar, } from "discourse/lib/utilities"; -import { debounce, later, next, run, schedule, throttle } from "@ember/runloop"; import discourseComputed, { observes, on, @@ -28,12 +27,14 @@ import { fetchUnseenMentions, linkSeenMentions, } from "discourse/lib/link-mentions"; +import { later, next, run, schedule, throttle } from "@ember/runloop"; import Component from "@ember/component"; import Composer from "discourse/models/composer"; import EmberObject from "@ember/object"; import I18n from "I18n"; import { ajax } from "discourse/lib/ajax"; import bootbox from "bootbox"; +import discourseDebounce from "discourse-common/lib/debounce"; import { findRawTemplate } from "discourse-common/lib/raw-templates"; import getURL from "discourse-common/lib/get-url"; import { iconHTML } from "discourse-common/lib/icon-library"; @@ -905,7 +906,7 @@ export default Component.extend({ // Paint mentions const unseenMentions = linkSeenMentions($preview, this.siteSettings); if (unseenMentions.length) { - debounce( + discourseDebounce( this, this._renderUnseenMentions, $preview, @@ -920,7 +921,7 @@ export default Component.extend({ // Paint category and tag hashtags const unseenHashtags = linkSeenHashtags($preview); if (unseenHashtags.length > 0) { - debounce(this, this._renderUnseenHashtags, $preview, 450); + discourseDebounce(this, this._renderUnseenHashtags, $preview, 450); } // Paint oneboxes @@ -947,7 +948,7 @@ export default Component.extend({ } }; - debounce(this, paintFunc, 450); + discourseDebounce(this, paintFunc, 450); // Short upload urls need resolution resolveAllShortUrls(ajax, this.siteSettings, $preview[0]); diff --git a/app/assets/javascripts/discourse/app/components/composer-title.js b/app/assets/javascripts/discourse/app/components/composer-title.js index 25895f9ea30..7aca53f07c4 100644 --- a/app/assets/javascripts/discourse/app/components/composer-title.js +++ b/app/assets/javascripts/discourse/app/components/composer-title.js @@ -1,10 +1,11 @@ import { alias, or } from "@ember/object/computed"; -import { debounce, next, schedule } from "@ember/runloop"; import discourseComputed, { observes } from "discourse-common/utils/decorators"; +import { next, schedule } from "@ember/runloop"; import Component from "@ember/component"; import EmberObject from "@ember/object"; import I18n from "I18n"; import { ajax } from "discourse/lib/ajax"; +import discourseDebounce from "discourse-common/lib/debounce"; import { isTesting } from "discourse-common/config/environment"; import { load } from "pretty-text/oneboxer"; import { lookupCache } from "pretty-text/oneboxer-cache"; @@ -22,7 +23,7 @@ export default Component.extend({ } 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() ); } else { - debounce(this, this._checkForUrl, 500); + discourseDebounce(this, this._checkForUrl, 500); } }, diff --git a/app/assets/javascripts/discourse/app/components/d-editor.js b/app/assets/javascripts/discourse/app/components/d-editor.js index 9fd17ad9dec..c5c636ab67e 100644 --- a/app/assets/javascripts/discourse/app/components/d-editor.js +++ b/app/assets/javascripts/discourse/app/components/d-editor.js @@ -5,13 +5,13 @@ import { inCodeBlock, safariHacksDisabled, } from "discourse/lib/utilities"; -import { debounce, later, next, schedule, scheduleOnce } from "@ember/runloop"; import discourseComputed, { observes, on, } from "discourse-common/utils/decorators"; import { emojiSearch, isSkinTonableEmoji } from "pretty-text/emoji"; import { emojiUrlFor, generateCookFunction } from "discourse/lib/text"; +import { later, next, schedule, scheduleOnce } from "@ember/runloop"; import Component from "@ember/component"; import I18n from "I18n"; import Mousetrap from "mousetrap"; @@ -19,6 +19,7 @@ import { Promise } from "rsvp"; import { SKIP } from "discourse/lib/autocomplete"; import { categoryHashtagTriggerRule } from "discourse/lib/category-hashtags"; import deprecated from "discourse-common/lib/deprecated"; +import discourseDebounce from "discourse-common/lib/debounce"; import { findRawTemplate } from "discourse-common/lib/raw-templates"; import { getRegister } from "discourse-common/lib/get-owner"; import { isEmpty } from "@ember/utils"; @@ -414,7 +415,7 @@ export default Component.extend({ if (isTesting()) { this._updatePreview(); } else { - debounce(this, this._updatePreview, 30); + discourseDebounce(this, this._updatePreview, 30); } }, diff --git a/app/assets/javascripts/discourse/app/components/group-flair-inputs.js b/app/assets/javascripts/discourse/app/components/group-flair-inputs.js index 465940927b9..5a53a6f740d 100644 --- a/app/assets/javascripts/discourse/app/components/group-flair-inputs.js +++ b/app/assets/javascripts/discourse/app/components/group-flair-inputs.js @@ -7,7 +7,7 @@ import I18n from "I18n"; import { action } from "@ember/object"; import { ajax } from "discourse/lib/ajax"; 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 getURL from "discourse-common/lib/get-url"; import { htmlSafe } from "@ember/template"; @@ -34,7 +34,7 @@ export default Component.extend({ @observes("model.flair_icon") _loadSVGIcon(flairIcon) { if (flairIcon) { - debounce(this, this._loadIcon, 1000); + discourseDebounce(this, this._loadIcon, 1000); } }, diff --git a/app/assets/javascripts/discourse/app/components/groups-form-profile-fields.js b/app/assets/javascripts/discourse/app/components/groups-form-profile-fields.js index 23672bc5b85..d2a439ed543 100644 --- a/app/assets/javascripts/discourse/app/components/groups-form-profile-fields.js +++ b/app/assets/javascripts/discourse/app/components/groups-form-profile-fields.js @@ -3,7 +3,7 @@ import Component from "@ember/component"; import EmberObject from "@ember/object"; import Group from "discourse/models/group"; import I18n from "I18n"; -import discourseDebounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse-common/lib/debounce"; import { isEmpty } from "@ember/utils"; import { not } from "@ember/object/computed"; import { popupAjaxError } from "discourse/lib/ajax-error"; @@ -64,40 +64,46 @@ export default Component.extend({ ); }, - checkGroupName: discourseDebounce(function () { - if (isEmpty(this.nameInput)) { - return; - } - - Group.checkName(this.nameInput) - .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)); + checkGroupName() { + discourseDebounce( + this, + function () { + if (isEmpty(this.nameInput)) { + return; } - }) - .catch(popupAjaxError); - }, 500), + + Group.checkName(this.nameInput) + .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) { this.set("disableSave", true); diff --git a/app/assets/javascripts/discourse/app/components/quote-button.js b/app/assets/javascripts/discourse/app/components/quote-button.js index 6a7393775e0..45ba6178363 100644 --- a/app/assets/javascripts/discourse/app/components/quote-button.js +++ b/app/assets/javascripts/discourse/app/components/quote-button.js @@ -9,7 +9,7 @@ import Sharing from "discourse/lib/sharing"; import { action } from "@ember/object"; import { alias } from "@ember/object/computed"; 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 { schedule } from "@ember/runloop"; import toMarkdown from "discourse/lib/to-markdown"; @@ -183,10 +183,9 @@ export default Component.extend({ const { isWinphone, isAndroid } = this.capabilities; const wait = isWinphone || isAndroid ? INPUT_DELAY : 25; - const onSelectionChanged = discourseDebounce( - () => this._selectionChanged(), - wait - ); + const onSelectionChanged = () => { + discourseDebounce(this, this._selectionChanged, wait); + }; $(document) .on("mousedown.quote-button", (e) => { diff --git a/app/assets/javascripts/discourse/app/components/scrolling-post-stream.js b/app/assets/javascripts/discourse/app/components/scrolling-post-stream.js index 054a5d95a6b..c6fb29a8f64 100644 --- a/app/assets/javascripts/discourse/app/components/scrolling-post-stream.js +++ b/app/assets/javascripts/discourse/app/components/scrolling-post-stream.js @@ -1,7 +1,8 @@ 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 MountWidget from "discourse/components/mount-widget"; +import discourseDebounce from "discourse-common/lib/debounce"; import { isWorkaroundActive } from "discourse/lib/safari-hacks"; import offsetCalculator from "discourse/lib/offset-calculator"; import { inject as service } from "@ember/service"; @@ -309,12 +310,13 @@ export default MountWidget.extend({ }, _debouncedScroll() { - debounce(this, this._scrollTriggered, 10); + discourseDebounce(this, this._scrollTriggered, 10); }, didInsertElement() { this._super(...arguments); - const debouncedScroll = () => debounce(this, this._scrollTriggered, 10); + const debouncedScroll = () => + discourseDebounce(this, this._scrollTriggered, 10); this._previouslyNearby = {}; diff --git a/app/assets/javascripts/discourse/app/components/text-field.js b/app/assets/javascripts/discourse/app/components/text-field.js index 187a1661b42..6561aed20e4 100644 --- a/app/assets/javascripts/discourse/app/components/text-field.js +++ b/app/assets/javascripts/discourse/app/components/text-field.js @@ -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 I18n from "I18n"; import TextField from "@ember/component/text-field"; import discourseComputed from "discourse-common/utils/decorators"; +import discourseDebounce from "discourse-common/lib/debounce"; const DEBOUNCE_MS = 500; @@ -38,7 +39,11 @@ export default TextField.extend({ } if (this.onChange) { cancel(this._timer); - this._timer = debounce(this, this._debouncedChange, DEBOUNCE_MS); + this._timer = discourseDebounce( + this, + this._debouncedChange, + DEBOUNCE_MS + ); } } }, diff --git a/app/assets/javascripts/discourse/app/components/topic-navigation.js b/app/assets/javascripts/discourse/app/components/topic-navigation.js index bacc74415ef..5b58ff9147e 100644 --- a/app/assets/javascripts/discourse/app/components/topic-navigation.js +++ b/app/assets/javascripts/discourse/app/components/topic-navigation.js @@ -3,9 +3,10 @@ import PanEvents, { SWIPE_VELOCITY, SWIPE_VELOCITY_THRESHOLD, } from "discourse/mixins/pan-events"; -import { debounce, later } from "@ember/runloop"; import Component from "@ember/component"; 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 showModal from "discourse/lib/show-modal"; @@ -53,7 +54,7 @@ export default Component.extend(PanEvents, { }, _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 diff --git a/app/assets/javascripts/discourse/app/controllers/composer.js b/app/assets/javascripts/discourse/app/controllers/composer.js index 9cb9d444f42..4b737b0a9fc 100644 --- a/app/assets/javascripts/discourse/app/controllers/composer.js +++ b/app/assets/javascripts/discourse/app/controllers/composer.js @@ -6,7 +6,7 @@ import { authorizesOneOrMoreExtensions, uploadIcon, } from "discourse/lib/uploads"; -import { cancel, debounce, run } from "@ember/runloop"; +import { cancel, run } from "@ember/runloop"; import { cannotPostAgain, durationTextFromSeconds, @@ -22,6 +22,7 @@ import { Promise } from "rsvp"; import bootbox from "bootbox"; import { buildQuote } from "discourse/lib/quote"; import deprecated from "discourse-common/lib/deprecated"; +import discourseDebounce from "discourse-common/lib/debounce"; import { emojiUnescape } from "discourse/lib/text"; import { escapeExpression } from "discourse/lib/utilities"; 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 // an infinite loop if (!isTesting()) { - this._saveDraftDebounce = debounce(this, this._saveDraft, 2000); + this._saveDraftDebounce = discourseDebounce( + this, + this._saveDraft, + 2000 + ); } } else { this._saveDraftPromise = model.saveDraft().finally(() => { @@ -1188,7 +1193,7 @@ export default Controller.extend({ if (Date.now() - this._lastDraftSaved > 15000) { this._saveDraft(); } else { - let method = isTesting() ? run : debounce; + let method = isTesting() ? run : discourseDebounce; this._saveDraftDebounce = method(this, this._saveDraft, 2000); } } diff --git a/app/assets/javascripts/discourse/app/controllers/group-index.js b/app/assets/javascripts/discourse/app/controllers/group-index.js index fd37fe892d7..b11eb199c53 100644 --- a/app/assets/javascripts/discourse/app/controllers/group-index.js +++ b/app/assets/javascripts/discourse/app/controllers/group-index.js @@ -1,7 +1,7 @@ import Controller, { inject as controller } from "@ember/controller"; import discourseComputed, { observes } from "discourse-common/utils/decorators"; 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 { popupAjaxError } from "discourse/lib/ajax-error"; @@ -19,9 +19,15 @@ export default Controller.extend({ showActions: false, @observes("filterInput") - _setFilter: discourseDebounce(function () { - this.set("filter", this.filterInput); - }, 500), + _setFilter() { + discourseDebounce( + this, + function () { + this.set("filter", this.filterInput); + }, + 500 + ); + }, @observes("order", "asc", "filter") _filtersChanged() { diff --git a/app/assets/javascripts/discourse/app/controllers/group-requests.js b/app/assets/javascripts/discourse/app/controllers/group-requests.js index 791da19ee0c..73da3601920 100644 --- a/app/assets/javascripts/discourse/app/controllers/group-requests.js +++ b/app/assets/javascripts/discourse/app/controllers/group-requests.js @@ -1,7 +1,7 @@ import Controller, { inject as controller } from "@ember/controller"; import discourseComputed, { observes } from "discourse-common/utils/decorators"; 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"; export default Controller.extend({ @@ -17,9 +17,15 @@ export default Controller.extend({ loading: false, @observes("filterInput") - _setFilter: discourseDebounce(function () { - this.set("filter", this.filterInput); - }, 500), + _setFilter() { + discourseDebounce( + this, + function () { + this.set("filter", this.filterInput); + }, + 500 + ); + }, @observes("order", "desc", "filter") _filtersChanged() { diff --git a/app/assets/javascripts/discourse/app/controllers/groups-index.js b/app/assets/javascripts/discourse/app/controllers/groups-index.js index a68dbedf58a..fdc3f909001 100644 --- a/app/assets/javascripts/discourse/app/controllers/groups-index.js +++ b/app/assets/javascripts/discourse/app/controllers/groups-index.js @@ -2,8 +2,8 @@ import Controller, { inject as controller } from "@ember/controller"; import I18n from "I18n"; import { INPUT_DELAY } from "discourse-common/config/environment"; import { action } from "@ember/object"; -import { debounce } from "@ember/runloop"; import discourseComputed from "discourse-common/utils/decorators"; +import discourseDebounce from "discourse-common/lib/debounce"; export default Controller.extend({ application: controller(), @@ -45,7 +45,7 @@ export default Controller.extend({ @action onFilterChanged(filter) { - debounce(this, this._debouncedFilter, filter, INPUT_DELAY); + discourseDebounce(this, this._debouncedFilter, filter, INPUT_DELAY); }, @action diff --git a/app/assets/javascripts/discourse/app/controllers/insert-hyperlink.js b/app/assets/javascripts/discourse/app/controllers/insert-hyperlink.js index 3d4b3babadd..42c3deeb323 100644 --- a/app/assets/javascripts/discourse/app/controllers/insert-hyperlink.js +++ b/app/assets/javascripts/discourse/app/controllers/insert-hyperlink.js @@ -1,7 +1,8 @@ -import { cancel, debounce, schedule } from "@ember/runloop"; +import { cancel, schedule } from "@ember/runloop"; import Controller from "@ember/controller"; import ModalFunctionality from "discourse/mixins/modal-functionality"; import { bind } from "discourse-common/utils/decorators"; +import discourseDebounce from "discourse-common/lib/debounce"; import { isEmpty } from "@ember/utils"; import { prefixProtocol } from "discourse/lib/url"; import { searchForTerm } from "discourse/lib/search"; @@ -177,7 +178,7 @@ export default Controller.extend(ModalFunctionality, { } }, search() { - this._debounced = debounce(this, this.triggerSearch, 400); + this._debounced = discourseDebounce(this, this.triggerSearch, 400); }, }, }); diff --git a/app/assets/javascripts/discourse/app/controllers/topic.js b/app/assets/javascripts/discourse/app/controllers/topic.js index 2bb4f750dde..42a88432c0e 100644 --- a/app/assets/javascripts/discourse/app/controllers/topic.js +++ b/app/assets/javascripts/discourse/app/controllers/topic.js @@ -18,7 +18,7 @@ import bootbox from "bootbox"; import { bufferedProperty } from "discourse/mixins/buffered-content"; import { buildQuote } from "discourse/lib/quote"; 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 { extractLinkMeta } from "discourse/lib/render-topic-featured-link"; import isElementInViewport from "discourse/lib/is-element-in-viewport"; @@ -1500,15 +1500,22 @@ export default Controller.extend(bufferedProperty("model"), { ); }, - _scrollToPost: discourseDebounce(function (postNumber) { - const $post = $(`.topic-post article#post_${postNumber}`); + _scrollToPost(postNumber) { + discourseDebounce( + this, + function () { + const $post = $(`.topic-post article#post_${postNumber}`); - if ($post.length === 0 || isElementInViewport($post)) { - return; - } + if ($post.length === 0 || isElementInViewport($post)) { + return; + } - $("html, body").animate({ scrollTop: $post.offset().top }, 1000); - }, 500), + $("html, body").animate({ scrollTop: $post.offset().top }, 1000); + }, + postNumber, + 500 + ); + }, unsubscribe() { // never unsubscribe when navigating from topic to topic diff --git a/app/assets/javascripts/discourse/app/controllers/user-invited-show.js b/app/assets/javascripts/discourse/app/controllers/user-invited-show.js index 9781e87007b..eb933835358 100644 --- a/app/assets/javascripts/discourse/app/controllers/user-invited-show.js +++ b/app/assets/javascripts/discourse/app/controllers/user-invited-show.js @@ -5,7 +5,7 @@ import I18n from "I18n"; import { INPUT_DELAY } from "discourse-common/config/environment"; import Invite from "discourse/models/invite"; import bootbox from "bootbox"; -import discourseDebounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse-common/lib/debounce"; import { popupAjaxError } from "discourse/lib/ajax-error"; export default Controller.extend({ @@ -26,13 +26,19 @@ export default Controller.extend({ }, @observes("searchTerm") - _searchTermChanged: discourseDebounce(function () { - Invite.findInvitedBy( - this.user, - this.filter, - this.searchTerm - ).then((invites) => this.set("model", invites)); - }, INPUT_DELAY), + _searchTermChanged() { + discourseDebounce( + this, + function () { + Invite.findInvitedBy( + this.user, + this.filter, + this.searchTerm + ).then((invites) => this.set("model", invites)); + }, + INPUT_DELAY + ); + }, inviteRedeemed: equal("filter", "redeemed"), invitePending: equal("filter", "pending"), diff --git a/app/assets/javascripts/discourse/app/controllers/users.js b/app/assets/javascripts/discourse/app/controllers/users.js index a0b180a1644..8ebaf7adf67 100644 --- a/app/assets/javascripts/discourse/app/controllers/users.js +++ b/app/assets/javascripts/discourse/app/controllers/users.js @@ -1,5 +1,5 @@ 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 { longDate } from "discourse/lib/formatter"; import { observes } from "discourse-common/utils/decorators"; @@ -37,9 +37,15 @@ export default Controller.extend({ }, @observes("nameInput") - _setName: discourseDebounce(function () { - this.set("name", this.nameInput); - }, 500), + _setName() { + discourseDebounce( + this, + function () { + this.set("name", this.nameInput); + }, + 500 + ); + }, @observes("model.canLoadMore") _showFooter: function () { diff --git a/app/assets/javascripts/discourse/app/lib/autocomplete.js b/app/assets/javascripts/discourse/app/lib/autocomplete.js index f08c7851d2a..a5aab455753 100644 --- a/app/assets/javascripts/discourse/app/lib/autocomplete.js +++ b/app/assets/javascripts/discourse/app/lib/autocomplete.js @@ -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 { INPUT_DELAY } from "discourse-common/config/environment"; import Site from "discourse/models/site"; +import discourseDebounce from "discourse-common/lib/debounce"; import { iconHTML } from "discourse-common/lib/icon-library"; /** @@ -422,7 +423,7 @@ export default function (options) { $(this).on("keyup.autocomplete", function (e) { if (options.debounced) { - debounce(this, performAutocomplete, e, INPUT_DELAY); + discourseDebounce(this, performAutocomplete, e, INPUT_DELAY); } else { performAutocomplete(e); } diff --git a/app/assets/javascripts/discourse/app/lib/category-tag-search.js b/app/assets/javascripts/discourse/app/lib/category-tag-search.js index 95fcdab9e05..d2e1fd5c83f 100644 --- a/app/assets/javascripts/discourse/app/lib/category-tag-search.js +++ b/app/assets/javascripts/discourse/app/lib/category-tag-search.js @@ -4,7 +4,7 @@ import Category from "discourse/models/category"; import { Promise } from "rsvp"; import { SEPARATOR } from "discourse/lib/category-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 { isTesting } from "discourse-common/config/environment"; @@ -27,38 +27,47 @@ function searchTags(term, categories, limit) { isTesting() ? 50 : 5000 ); - const debouncedSearch = discourseDebounce((q, cats, resultFunc) => { - oldSearch = $.ajax(getURL("/tags/filter/search"), { - type: "GET", - cache: true, - data: { limit: limit, q }, - }); - - var returnVal = CANCELLED_STATUS; - - 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, - }; + const debouncedSearch = (q, cats, resultFunc) => { + discourseDebounce( + this, + function () { + oldSearch = $.ajax(getURL("/tags/filter/search"), { + type: "GET", + cache: true, + data: { limit: limit, q }, }); - returnVal = cats.concat(tags); - }) - .always(() => { - oldSearch = null; - resultFunc(returnVal); - }); - }, 300); + var returnVal = CANCELLED_STATUS; + + 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); + }) + .always(() => { + oldSearch = null; + resultFunc(returnVal); + }); + }, + q, + cats, + resultFunc, + 300 + ); + }; debouncedSearch(term, categories, (result) => { cancel(clearPromise); diff --git a/app/assets/javascripts/discourse/app/lib/reports-loader.js b/app/assets/javascripts/discourse/app/lib/reports-loader.js index e0b108b7c7b..afd0b132ee6 100644 --- a/app/assets/javascripts/discourse/app/lib/reports-loader.js +++ b/app/assets/javascripts/discourse/app/lib/reports-loader.js @@ -1,6 +1,5 @@ import { ajax } from "discourse/lib/ajax"; -import { run } from "@ember/runloop"; -const { debounce } = run; +import discourseDebounce from "discourse-common/lib/debounce"; let _queue = []; let _processing = 0; @@ -33,7 +32,7 @@ export default { _queue.push({ runnable: () => callback, type, params }); - debounce(this, this._processQueue, DEBOUNCING_DELAY); + discourseDebounce(this, this._processQueue, DEBOUNCING_DELAY); }, _processQueue() { @@ -50,7 +49,7 @@ export default { // if queue has still jobs after splice, we request a future processing if (_queue.length > 0) { - debounce(this, this._processQueue, DEBOUNCING_DELAY); + discourseDebounce(this, this._processQueue, DEBOUNCING_DELAY); } let reports = {}; @@ -79,7 +78,7 @@ export default { .finally(() => { _processing--; - debounce(this, this._processQueue, DEBOUNCING_DELAY); + discourseDebounce(this, this._processQueue, DEBOUNCING_DELAY); }); }, diff --git a/app/assets/javascripts/discourse/app/lib/safari-hacks.js b/app/assets/javascripts/discourse/app/lib/safari-hacks.js index 7ecdb18b89c..99c8b335e1f 100644 --- a/app/assets/javascripts/discourse/app/lib/safari-hacks.js +++ b/app/assets/javascripts/discourse/app/lib/safari-hacks.js @@ -3,7 +3,7 @@ import { safariHacksDisabled, } from "discourse/lib/utilities"; 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 { later } from "@ember/runloop"; @@ -145,7 +145,9 @@ function positioningWorkaround($fixedElement) { positioningWorkaround.blur(evt); }; - var blurred = discourseDebounce(blurredNow, INPUT_DELAY); + var blurred = function (evt) { + discourseDebounce(this, blurredNow, evt, INPUT_DELAY); + }; var positioningHack = function (evt) { let _this = this; @@ -214,13 +216,19 @@ function positioningWorkaround($fixedElement) { } } - const checkForInputs = discourseDebounce(function () { - attachTouchStart(fixedElement, lastTouched); + const checkForInputs = function () { + discourseDebounce( + this, + function () { + attachTouchStart(fixedElement, lastTouched); - $fixedElement.find("input[type=text],textarea").each(function () { - attachTouchStart(this, positioningHack); - }); - }, 100); + $fixedElement.find("input[type=text],textarea").each(function () { + attachTouchStart(this, positioningHack); + }); + }, + 100 + ); + }; positioningWorkaround.touchstartEvent = function (element) { var triggerHack = positioningHack.bind(element); diff --git a/app/assets/javascripts/discourse/app/lib/user-search.js b/app/assets/javascripts/discourse/app/lib/user-search.js index b68e0755e8a..c77c5c6cd79 100644 --- a/app/assets/javascripts/discourse/app/lib/user-search.js +++ b/app/assets/javascripts/discourse/app/lib/user-search.js @@ -1,7 +1,7 @@ import { cancel, later } from "@ember/runloop"; import { CANCELLED_STATUS } from "discourse/lib/autocomplete"; import { Promise } from "rsvp"; -import discourseDebounce from "discourse/lib/debounce"; +import discourseDebounce from "discourse-common/lib/debounce"; import { emailValid } from "discourse/lib/utilities"; import { isTesting } from "discourse-common/config/environment"; 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) { if (r === CANCELLED_STATUS) { diff --git a/app/assets/javascripts/discourse/app/mixins/docking.js b/app/assets/javascripts/discourse/app/mixins/docking.js index 4147ae94de7..fee3e73b26e 100644 --- a/app/assets/javascripts/discourse/app/mixins/docking.js +++ b/app/assets/javascripts/discourse/app/mixins/docking.js @@ -1,5 +1,6 @@ -import { debounce, later } from "@ember/runloop"; import Mixin from "@ember/object/mixin"; +import discourseDebounce from "discourse-common/lib/debounce"; +import { later } from "@ember/runloop"; const helper = { offset() { @@ -15,7 +16,7 @@ export default Mixin.create({ init() { this._super(...arguments); this.queueDockCheck = () => { - debounce(this, this.safeDockCheck, 5); + discourseDebounce(this, this.safeDockCheck, 5); }; }, diff --git a/app/assets/javascripts/discourse/app/mixins/mobile-scroll-direction.js b/app/assets/javascripts/discourse/app/mixins/mobile-scroll-direction.js index 2be71c0d636..f751216c97d 100644 --- a/app/assets/javascripts/discourse/app/mixins/mobile-scroll-direction.js +++ b/app/assets/javascripts/discourse/app/mixins/mobile-scroll-direction.js @@ -1,5 +1,5 @@ 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 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 // this scroll direction after a second scrolldown. This is a nicer event // similar to what Safari and Chrome do. - debounce(() => { - this._bottomHit = 1; - }, 1000); + discourseDebounce( + this, + function () { + this._bottomHit = 1; + }, + 1000 + ); if (this._bottomHit === 1) { this.set("mobileScrollDirection", null); diff --git a/app/assets/javascripts/discourse/app/mixins/scrolling.js b/app/assets/javascripts/discourse/app/mixins/scrolling.js index f1df25463a7..26a16f0a7c3 100644 --- a/app/assets/javascripts/discourse/app/mixins/scrolling.js +++ b/app/assets/javascripts/discourse/app/mixins/scrolling.js @@ -1,5 +1,5 @@ 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 { inject as service } from "@ember/service"; @@ -45,7 +45,9 @@ const Scrolling = Mixin.create({ }; if (opts.debounce) { - onScrollMethod = discourseDebounce(onScrollMethod, opts.debounce); + onScrollMethod = () => { + discourseDebounce(this, onScrollMethod, opts.debounce); + }; } ScrollingDOMMethods.bindOnScroll(onScrollMethod, opts.name); diff --git a/app/assets/javascripts/discourse/app/mixins/username-validation.js b/app/assets/javascripts/discourse/app/mixins/username-validation.js index 9d26b91cea9..0749bd70f9f 100644 --- a/app/assets/javascripts/discourse/app/mixins/username-validation.js +++ b/app/assets/javascripts/discourse/app/mixins/username-validation.js @@ -3,7 +3,7 @@ import I18n from "I18n"; import Mixin from "@ember/object/mixin"; import User from "discourse/models/user"; 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 { setting } from "discourse/lib/computed"; @@ -14,20 +14,26 @@ export default Mixin.create({ minUsernameLength: setting("min_username_length"), - fetchExistingUsername: discourseDebounce(function () { - User.checkUsername(null, this.accountEmail).then((result) => { - if ( - result.suggestion && - (isEmpty(this.accountUsername) || - this.accountUsername === this.get("authOptions.username")) - ) { - this.setProperties({ - accountUsername: result.suggestion, - prefilledUsername: result.suggestion, + fetchExistingUsername() { + discourseDebounce( + this, + function () { + User.checkUsername(null, this.accountEmail).then((result) => { + if ( + result.suggestion && + (isEmpty(this.accountUsername) || + this.accountUsername === this.get("authOptions.username")) + ) { + this.setProperties({ + accountUsername: result.suggestion, + prefilledUsername: result.suggestion, + }); + } }); - } - }); - }, 500), + }, + 500 + ); + }, @discourseComputed("accountUsername") basicUsernameValidation(accountUsername) { @@ -87,54 +93,61 @@ export default Mixin.create({ ); }, - checkUsernameAvailability: discourseDebounce(function () { - if (this.shouldCheckUsernameAvailability()) { - return User.checkUsername(this.accountUsername, this.accountEmail).then( - (result) => { - this.set("isDeveloper", false); - if (result.available) { - if (result.is_developer) { - this.set("isDeveloper", true); - } - return this.set( - "uniqueUsernameValidation", - EmberObject.create({ - ok: true, - reason: I18n.t("user.username.available"), - }) - ); - } else { - const failedAttrs = { - failed: true, - element: document.querySelector("#new-account-username"), - }; - - if (result.suggestion) { + checkUsernameAvailability() { + discourseDebounce( + this, + function () { + if (this.shouldCheckUsernameAvailability()) { + return User.checkUsername( + this.accountUsername, + this.accountEmail + ).then((result) => { + this.set("isDeveloper", false); + if (result.available) { + if (result.is_developer) { + this.set("isDeveloper", true); + } return this.set( "uniqueUsernameValidation", - EmberObject.create( - Object.assign(failedAttrs, { - reason: I18n.t("user.username.not_available", result), - }) - ) + EmberObject.create({ + ok: true, + reason: I18n.t("user.username.available"), + }) ); } else { - return this.set( - "uniqueUsernameValidation", - EmberObject.create( - Object.assign(failedAttrs, { - reason: result.errors - ? result.errors.join(" ") - : I18n.t("user.username.not_available_no_suggestion"), - }) - ) - ); + const failedAttrs = { + failed: true, + element: document.querySelector("#new-account-username"), + }; + + if (result.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 @discourseComputed("uniqueUsernameValidation", "basicUsernameValidation") diff --git a/app/assets/javascripts/discourse/app/widgets/post-stream.js b/app/assets/javascripts/discourse/app/widgets/post-stream.js index e8dc61cf32d..69eccd84e2b 100644 --- a/app/assets/javascripts/discourse/app/widgets/post-stream.js +++ b/app/assets/javascripts/discourse/app/widgets/post-stream.js @@ -1,7 +1,7 @@ import { Placeholder } from "discourse/lib/posts-with-placeholders"; import { addWidgetCleanCallback } from "discourse/components/mount-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 transformPost from "discourse/lib/transform-post"; @@ -41,7 +41,7 @@ export function cloak(post, component) { _heights[post.id] = $post.outerHeight(); component.dirtyKeys.keyDirty(`post-${post.id}`); - debounce(component, "queueRerender", 1000); + discourseDebounce(component, "queueRerender", 1000); } export function uncloak(post, component) { diff --git a/app/assets/javascripts/discourse/app/widgets/search-menu.js b/app/assets/javascripts/discourse/app/widgets/search-menu.js index 999e063742e..28d8ce506b4 100644 --- a/app/assets/javascripts/discourse/app/widgets/search-menu.js +++ b/app/assets/javascripts/discourse/app/widgets/search-menu.js @@ -1,7 +1,7 @@ import { isValidSearchTerm, searchForTerm } from "discourse/lib/search"; import DiscourseURL from "discourse/lib/url"; import { createWidget } from "discourse/widgets/widget"; -import { debounce } from "@ember/runloop"; +import discourseDebounce from "discourse-common/lib/debounce"; import { get } from "@ember/object"; import getURL from "discourse-common/lib/get-url"; import { h } from "virtual-dom"; @@ -284,7 +284,7 @@ export default createWidget("search-menu", { searchData.noResults = false; this.searchService().set("highlightTerm", searchData.term); searchData.loading = true; - debounce(SearchHelper, SearchHelper.perform, this, 400); + discourseDebounce(SearchHelper, SearchHelper.perform, this, 400); }, moreOfType(type) { diff --git a/app/assets/javascripts/pretty-text/addon/upload-short-url.js b/app/assets/javascripts/pretty-text/addon/upload-short-url.js index 5080a52ec8d..febcbaf3198 100644 --- a/app/assets/javascripts/pretty-text/addon/upload-short-url.js +++ b/app/assets/javascripts/pretty-text/addon/upload-short-url.js @@ -1,5 +1,5 @@ import I18n from "I18n"; -import { debounce } from "@ember/runloop"; +import discourseDebounce from "discourse-common/lib/debounce"; let _cache = {}; @@ -184,7 +184,7 @@ export function resolveAllShortUrls(ajax, siteSettings, scope, opts) { shortUploadElements = scope.querySelectorAll(attributes); if (shortUploadElements.length > 0) { // this is carefully batched so we can do a leading debounce (trigger right away) - return debounce( + return discourseDebounce( null, _loadShortUrls, shortUploadElements, diff --git a/app/assets/javascripts/select-kit/addon/components/select-kit.js b/app/assets/javascripts/select-kit/addon/components/select-kit.js index a440f8d0967..54fbe4100b8 100644 --- a/app/assets/javascripts/select-kit/addon/components/select-kit.js +++ b/app/assets/javascripts/select-kit/addon/components/select-kit.js @@ -3,14 +3,7 @@ import PluginApiMixin, { applyContentPluginApiCallbacks, applyOnChangePluginApiCallbacks, } from "select-kit/mixins/plugin-api"; -import { - bind, - cancel, - debounce, - next, - schedule, - throttle, -} from "@ember/runloop"; +import { bind, cancel, next, schedule, throttle } from "@ember/runloop"; import { isEmpty, isNone, isPresent } from "@ember/utils"; import Component from "@ember/component"; import I18n from "I18n"; @@ -19,6 +12,7 @@ import { Promise } from "rsvp"; import UtilsMixin from "select-kit/mixins/utils"; import { createPopper } from "@popperjs/core"; import deprecated from "discourse-common/lib/deprecated"; +import discourseDebounce from "discourse-common/lib/debounce"; import { guidFor } from "@ember/object/internals"; import { makeArray } from "discourse-common/lib/helpers"; @@ -386,7 +380,7 @@ export default Component.extend( cancel(this._searchPromise); } - debounce(this, this._debouncedInput, event.target.value, 200); + discourseDebounce(this, this._debouncedInput, event.target.value, 200); }, _debouncedInput(filter) { diff --git a/plugins/discourse-local-dates/assets/javascripts/discourse/components/discourse-local-dates-create-form.js.es6 b/plugins/discourse-local-dates/assets/javascripts/discourse/components/discourse-local-dates-create-form.js.es6 index 581b5027813..454bf9ea9a5 100644 --- a/plugins/discourse-local-dates/assets/javascripts/discourse/components/discourse-local-dates-create-form.js.es6 +++ b/plugins/discourse-local-dates/assets/javascripts/discourse/components/discourse-local-dates-create-form.js.es6 @@ -6,7 +6,7 @@ import I18n from "I18n"; import { INPUT_DELAY } from "discourse-common/config/environment"; import { Promise } from "rsvp"; 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 loadScript from "discourse/lib/load-script"; import { notEmpty } from "@ember/object/computed"; @@ -56,18 +56,24 @@ export default Component.extend({ }, @observes("markup") - _renderPreview: discourseDebounce(function () { - const markup = this.markup; + _renderPreview() { + discourseDebounce( + this, + function () { + const markup = this.markup; - if (markup) { - cookAsync(markup).then((result) => { - this.set("currentPreview", result); - schedule("afterRender", () => - this.$(".preview .discourse-local-date").applyLocalDates() - ); - }); - } - }, INPUT_DELAY), + if (markup) { + cookAsync(markup).then((result) => { + this.set("currentPreview", result); + schedule("afterRender", () => + this.$(".preview .discourse-local-date").applyLocalDates() + ); + }); + } + }, + INPUT_DELAY + ); + }, @computed("date", "toDate", "toTime") isRange(date, toDate, toTime) {