summaryrefslogtreecommitdiff
path: root/source/libs
diff options
context:
space:
mode:
Diffstat (limited to 'source/libs')
-rw-r--r--source/libs/on-new-comments.ts43
-rw-r--r--source/libs/on-pr-file-load.ts33
-rw-r--r--source/libs/on-pr-merge-panel-open.ts34
3 files changed, 56 insertions, 54 deletions
diff --git a/source/libs/on-new-comments.ts b/source/libs/on-new-comments.ts
index 26c7d43b..265ad867 100644
--- a/source/libs/on-new-comments.ts
+++ b/source/libs/on-new-comments.ts
@@ -1,44 +1,51 @@
import select from 'select-dom';
-import observeEl from './simplified-element-observer';
+import delegate, {DelegateSubscription} from 'delegate-it';
+const discussionsWithListeners = new WeakSet();
const handlers = new Set<VoidFunction>();
-const observed = new WeakSet();
+const delegates = new Set<DelegateSubscription>();
+const observer = new MutationObserver(run);
function run(): void {
// Run all callbacks without letting an error stop the loop and without silencing it
handlers.forEach(async callback => callback());
}
-// On new page loads, run the callbacks and look for the new elements.
-// (addEventListener doesn't add duplicate listeners)
-function addListenersOnNewElements(): void {
- for (const loadMore of select.all('.js-ajax-pagination')) {
- loadMore.addEventListener('page:loaded', run);
- loadMore.addEventListener('page:loaded', addListenersOnNewElements);
+function removeListeners(): void {
+ for (const subscription of delegates) {
+ subscription.destroy();
}
- // Outdated comment are loaded later using an include-fragment element
- for (const fragment of select.all('details.outdated-comment > include-fragment')) {
- fragment.addEventListener('load', run);
- }
+ delegates.clear();
+ handlers.clear();
+ observer.disconnect();
}
-function setup(): void {
+function addListeners(): void {
const discussion = select('.js-discussion');
- if (!discussion || observed.has(discussion)) {
+ if (!discussion || discussionsWithListeners.has(discussion)) {
return;
}
- observed.add(discussion);
+ // Ensure listeners are only ever added once
+ discussionsWithListeners.add(discussion);
+
+ // Remember to remove all listeners when a new page is loaded
+ document.addEventListener('pjax:beforeReplace', removeListeners);
// When new comments come in via AJAX
- observeEl(discussion, run);
+ observer.observe(discussion, {
+ childList: true
+ });
// When hidden comments are loaded by clicking "Load more..."
- addListenersOnNewElements();
+ delegates.add(delegate('.js-ajax-pagination', 'page:loaded', run));
+
+ // Outdated comment are loaded later using an include-fragment element
+ delegates.add(delegate('details.outdated-comment > include-fragment', 'load', run, true));
}
export default function (callback: VoidFunction): void {
- setup();
+ addListeners();
handlers.add(callback);
}
diff --git a/source/libs/on-pr-file-load.ts b/source/libs/on-pr-file-load.ts
index b06c8d4a..1c3f3330 100644
--- a/source/libs/on-pr-file-load.ts
+++ b/source/libs/on-pr-file-load.ts
@@ -1,22 +1,19 @@
-import select from 'select-dom';
+import mem from 'mem';
+import delegate, {DelegateSubscription, DelegateEventHandler, DelegateEvent} from 'delegate-it';
-// In PRs' Files tab, some files are loaded progressively later.
-const handlers = new WeakMap<EventListener, EventListener>();
+const fragmentSelector = [
+ 'include-fragment.diff-progressive-loader', // Incremental file loader on scroll
+ 'include-fragment.js-diff-entry-loader' // File diff loader on clicking "Load Diff"
+].join();
-export default function onPrFileLoad(callback: EventListener): void {
- // When a fragment loads, more fragments might be nested in it. The following code avoids duplicate event handlers.
- const recursiveCallback = handlers.get(callback) ?? ((event: Event) => {
- callback(event);
- onPrFileLoad(callback);
- });
- handlers.set(callback, recursiveCallback);
+// This lets you call `onPrFileLoad` multiple times with the same callback but only ever a `load` listener is registered
+const getDeduplicatedHandler = mem((callback: EventListener): DelegateEventHandler => {
+ return (event: DelegateEvent) => event.delegateTarget.addEventListener('load', callback);
+});
- const fragments = select.all([
- 'include-fragment.diff-progressive-loader', // Incremental file loader on scroll
- 'include-fragment.js-diff-entry-loader' // File diff loader on clicking "Load Diff"
- ].join());
-
- for (const fragment of fragments) {
- fragment.addEventListener('load', recursiveCallback);
- }
+export default function onPrFileLoad(callback: EventListener): DelegateSubscription {
+ // `loadstart` is fired when the fragment is still attached so event delegation works.
+ // `load` is fired after it’s detached, so `delegate` would never listen to it.
+ // This is why we listen to a global `loadstart` and then add a specific `load` listener on the element, which is fired even when the element is detached.
+ return delegate(fragmentSelector, 'loadstart', getDeduplicatedHandler(callback), true);
}
diff --git a/source/libs/on-pr-merge-panel-open.ts b/source/libs/on-pr-merge-panel-open.ts
index d8df675b..6ed88d48 100644
--- a/source/libs/on-pr-merge-panel-open.ts
+++ b/source/libs/on-pr-merge-panel-open.ts
@@ -13,27 +13,25 @@ const sessionResumeHandler = mem((callback: EventListener) => async (event: Even
callback(event);
});
-export default function (callback: EventListener): DelegateSubscription[] {
+export default function (callback: EventListener): DelegateSubscription {
document.addEventListener(
'session:resume',
sessionResumeHandler(callback)
);
+ const toggleSubscription = delegate(
+ '.js-merge-pr:not(.is-rebasing)',
+ 'details:toggled',
+ delegateHandler(callback)
+ );
- return [
- {
- // Imitate a DelegateSubscription for this event as well
- destroy() {
- document.removeEventListener(
- 'session:resume',
- sessionResumeHandler(callback)
- );
- }
- },
- ...delegate(
- '#discussion_bucket',
- '.js-merge-pr:not(.is-rebasing)',
- 'details:toggled',
- delegateHandler(callback)
- )
- ];
+ // Imitate a DelegateSubscription for this event as well
+ return {
+ destroy() {
+ toggleSubscription.destroy();
+ document.removeEventListener(
+ 'session:resume',
+ sessionResumeHandler(callback)
+ );
+ }
+ };
}