diff options
Diffstat (limited to 'source/libs')
-rw-r--r-- | source/libs/on-new-comments.ts | 43 | ||||
-rw-r--r-- | source/libs/on-pr-file-load.ts | 33 | ||||
-rw-r--r-- | source/libs/on-pr-merge-panel-open.ts | 34 |
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) + ); + } + }; } |