From 0d8dc27e05ad18ebfb4caf31e8160043814dacb2 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 09:08:22 -0700 Subject: [PATCH 01/19] Another partial ordering pair. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #302. ␄ --- linkedin-tool.user.js | 1 + 1 file changed, 1 insertion(+) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index 78abbd1..233d5c3 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -9380,6 +9380,7 @@ 'CertificationTopLevel, Skills', 'CertificationTopLevel, VolunteerExperienceTopLevel', 'ConnectedAccountsTopLevel, CertificationTopLevel', + 'ConnectedAccountsTopLevel, Skills', 'CourseTopLevelSection, Causes', 'CourseTopLevelSection, HonorsTopLevel', 'CourseTopLevelSection, Interests', From 07225bd1ed928bc7ca609d0abab1b1ae284612b2 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 09:09:40 -0700 Subject: [PATCH 02/19] Updated `UidMode`s for the *Highlights* section. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #302. ␄ --- linkedin-tool.user.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index 233d5c3..d0a1308 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -9553,7 +9553,8 @@ ], modes: [ this.ctor.UidMode.COMPANY, - this.ctor.UidMode.ID, + this.ctor.UidMode.ANCHOR_PROFILE, + this.ctor.UidMode.ARIA_LABEL, this.ctor.UidMode.FOOTER, ], }); @@ -9565,7 +9566,8 @@ ], modes: [ this.ctor.UidMode.COMPANY, - this.ctor.UidMode.ID, + this.ctor.UidMode.ANCHOR_PROFILE, + this.ctor.UidMode.ARIA_LABEL, this.ctor.UidMode.FOOTER, ], }); From 72568f84b985363093a5fbfc2dd5bf7afd8093a8 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 09:26:53 -0700 Subject: [PATCH 03/19] Additional update on how `Profile`'s `Scroller`s are configured. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes `#entriesCurrentModes` an instance variable. Issue #352. ␄ --- linkedin-tool.user.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index d0a1308..de46042 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -8926,9 +8926,6 @@ } /* eslint-enable */ - /** @type {UidMode[]} */ - static #entriesCurrentModes - /** * @typedef {object} ScrollerConfig * @property {Scroller~uidCallback} uidCallback - Callback to generate a @@ -9360,6 +9357,7 @@ } #checkingPartialOrder = false + #entriesCurrentModes #entriesCurrentUid #entriesScrollerConfigDefault = Symbol('default'); #entriesScrollerConfigs = new Map(); @@ -9837,7 +9835,7 @@ }; this.#entriesCurrentUid = config.uidCallback; - this.ctor.#entriesCurrentModes = config.modes; + this.#entriesCurrentModes = config.modes; this.#entryScroller = new Scroller(what, how); this.#entryScroller.dispatcher @@ -9857,7 +9855,7 @@ scroller.logger.entered(me, element); const results = this.ctor.#entriesModeToUid( - scroller, element, this.ctor.#entriesCurrentModes + scroller, element, this.#entriesCurrentModes ); if (results.size === 0) { From d70ae8f96126b47793f2ac556cbc6d72fd4725e0 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 09:29:52 -0700 Subject: [PATCH 04/19] Additional update on how `Profile`'s `Scroller`s are configured. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes `#entriesUidFromModes` use `Profile`'s logger. This should have been done as part of 33eac65. Issue #352. ␄ --- linkedin-tool.user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index de46042..f64308f 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -9852,7 +9852,7 @@ */ #entriesUidFromModes = (scroller, element) => { const me = this.#entriesUidFromModes.name; - scroller.logger.entered(me, element); + this.logger.entered(me, element); const results = this.ctor.#entriesModeToUid( scroller, element, this.#entriesCurrentModes @@ -9867,7 +9867,7 @@ .entries() .next().value; - scroller.logger.leaving(me, mode, uid); + this.logger.leaving(me, mode, uid); return [mode, uid]; } From b159e74fe6bc5c631244a74b7c7a074f3358c2ad Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 09:34:26 -0700 Subject: [PATCH 05/19] Update to latest `lib/xunit` and `lib/base`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The latest `lib/xunit` was just some JSDoc changes and did not get a version bump. Issue #369. ␄ --- linkedin-tool.user.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index f64308f..b7fc603 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -12,8 +12,8 @@ // @supportURL https://github.com/nexushoratio/userscripts/blob/main/linkedin-tool.md // @require https://cdn.jsdelivr.net/npm/@violentmonkey/shortcut@1 // @require https://cdn.jsdelivr.net/npm/commonmark@0.31.2 -// @require https://update.greasyfork.org/scripts/478188/1877646/NH_xunit.js -// @require https://update.greasyfork.org/scripts/477290/1869418/NH_base.js +// @require https://update.greasyfork.org/scripts/478188/1879877/NH_xunit.js +// @require https://update.greasyfork.org/scripts/477290/1879852/NH_base.js // @require https://update.greasyfork.org/scripts/478349/1846239/NH_userscript.js // @require https://update.greasyfork.org/scripts/478440/1862017/NH_web.js // @require https://update.greasyfork.org/scripts/478676/1848076/NH_widget.js @@ -33,7 +33,7 @@ const NH = window.NexusHoratio.base.ensure([ {name: 'xunit', minVersion: 63}, - {name: 'base', minVersion: 72}, + {name: 'base', minVersion: 73}, {name: 'userscript', minVersion: 17}, {name: 'web', minVersion: 16}, {name: 'widget', minVersion: 50}, From 3bda7700541c297080c69e97755a94b7b26f9240 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 11:49:19 -0700 Subject: [PATCH 06/19] Fix a couple of comments. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ␄ --- linkedin-tool.user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index b7fc603..258bc1d 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -2130,7 +2130,7 @@ const results = []; /** - * Simply eats any exception throw by the Promise. + * Simply eats any exception thrown by the Promise. * @param {Promise} prom - Whatever Promise we are wrapping. * @param {string} note - Put into log on error. * @returns {Promise} - Resolved promise. @@ -10483,7 +10483,7 @@ name: `${this.name} pagination`, containerItems: [ { - // This selector is also used in #onPaginationActivate. + // This selector is also used in #onPaginationActivate. container: 'main ul[data-testid="pagination-controls-list"]', items: ':scope > li', }, From 4b14f6f4f3b38b3522fa4e1cbbbfd143adcb0033 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 14:24:33 -0700 Subject: [PATCH 07/19] Remove unnecessary intermediate array. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #372. ␄ --- linkedin-tool.user.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index 258bc1d..b13603b 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -1540,10 +1540,7 @@ const me = this.activate.name; this.logger.entered(me); - const containers = new Set( - Array.from(await this.#waitForContainers()) - .filter(x => x) - ); + const containers = new Set(await this.#waitForContainers()); if (this.#base) { containers.add(this.#base); } From 20f990213475543bbebe02adc9931a395949d977 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 14:40:45 -0700 Subject: [PATCH 08/19] Rename some internal symbols. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #372. ␄ --- .ispell_words | 4 ++-- linkedin-tool.user.js | 14 ++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.ispell_words b/.ispell_words index 977e6b8..b182d4a 100644 --- a/.ispell_words +++ b/.ispell_words @@ -201,6 +201,8 @@ contactTabDefinition containerItems ContainerItemsSelector ContainerItemsSelectors +containersMutationHandler +containersMutationObserver containerTimeout contentDocument contenteditable @@ -681,7 +683,6 @@ matchSelf maxCountLength maxFieldLength maxUidLength -memberof menuItem messageBox messageBoxSelector @@ -932,7 +933,6 @@ primaryContentSelector primaryNavLinksComponentRef primaryNavSelector ProfilePostConnectDrawer -proj PublicationTopLevelSection pushd px diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index b13603b..ee46f2f 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -1295,7 +1295,9 @@ this.#validateInstance(); - this.#mutationObserver = new MutationObserver(this.#mutationHandler); + this.#containersMutationObserver = new MutationObserver( + this.#containersMutationHandler + ); this.#logger = new NH.base.Logger(`{${this.#name}}`); this.logger.log('Scroller constructed', this); @@ -1560,7 +1562,7 @@ this.#clickOptions); } this.logger.log('observing with', container, observeOptions); - this.#mutationObserver.observe(container, observeOptions); + this.#containersMutationObserver.observe(container, observeOptions); } this.logger.log('watcher:', await watcher); @@ -1579,7 +1581,7 @@ deactivate() { this.#mutationDispatcher.off('attributes', this.#attributesHandler); this.#mutationDispatcher.off('childList', this.#monitorConnectedness); - this.#mutationObserver.disconnect(); + this.#containersMutationObserver.disconnect(); for (const container of this.#onClickElements) { container.removeEventListener('click', this.#onClick, @@ -1627,6 +1629,7 @@ #clickOptions = {capture: true}; #containerItems #containerTimeout + #containersMutationObserver #currentItem = null; #currentItemId = null; #destroyed = false; @@ -1640,7 +1643,6 @@ #logger #maxUidLength #mutationDispatcher = new NH.base.Dispatcher('attributes', 'childList'); - #mutationObserver #name #observeAttributes #onClickElements = new Set(); @@ -1728,8 +1730,8 @@ * @param {MutationRecord[]} records - Standard mutation records. * @fires 'childList' */ - #mutationHandler = (records) => { - const me = this.#mutationHandler.name; + #containersMutationHandler = (records) => { + const me = this.#containersMutationHandler.name; this.logger.entered(me, `records: ${records.length}`); const types = new NH.base.DefaultMap(Array); From 0e00abf9d9849243e72d703834aa2a33453245a6 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 14:41:18 -0700 Subject: [PATCH 09/19] Remove an old logging statement. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #372. ␄ --- linkedin-tool.user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index ee46f2f..8b6da57 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -1565,7 +1565,7 @@ this.#containersMutationObserver.observe(container, observeOptions); } - this.logger.log('watcher:', await watcher); + await watcher; this.#mutationDispatcher.on('attributes', this.#attributesHandler); this.#mutationDispatcher.on('childList', this.#monitorConnectedness); From 0286285ac1abcd9c16566129b663c960a3764222 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 14:42:01 -0700 Subject: [PATCH 10/19] Temporarily disable the item cache. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #372. ␄ --- linkedin-tool.user.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index 8b6da57..05e393b 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -1778,6 +1778,9 @@ const me = this.#getItems.name; this.logger.entered(me); + // TODO(#372): Reenable the cache + this.#itemCache = null; + if (!this.#itemCache) { const items = []; if (this.#base) { From 62214dcf223e4bb5ffe6b2ae41955829f7858ca8 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 14:48:44 -0700 Subject: [PATCH 11/19] Remove the explicit shortcut for selecting the next page of results. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Native support is sufficient, and the ability to activate a selected page while focused on something else was a bit confusing. Closes #373. ␄ --- linkedin-tool.user.js | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index 05e393b..f8bf900 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -7199,15 +7199,6 @@ } ); - selectCurrentResultsPage = new Shortcut( - 'c', - 'Select current results page', - () => { - this.paginator.dull(); - NH.web.clickElement(this.paginator.item, ['button'], true); - } - ); - tabList = new Shortcut( 'l', 'Focus on discovery tab list (has native scrolling using arrows)', @@ -10418,14 +10409,6 @@ } ); - selectCurrentResultsPage = new Shortcut( - 'c', - 'Select current results page', - () => { - NH.web.clickElement(this.paginator.item, ['button']); - } - ); - firstItem = new Shortcut( '<', 'Go to the first item', From fa2ed0c606ec786a35862bef79e7ea0e6a8202e0 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 14:57:10 -0700 Subject: [PATCH 12/19] Update release notes. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ␄ --- linkedin-tool.user.js | 87 ++++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 30 deletions(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index f8bf900..5579275 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -193,11 +193,6 @@ '2026-06-30' ), ish('349', '`LinkedIn`: Remove navbar margin support', '2026-06-20'), - ish( - '350', - '`Scroller`: Add `this` as an argument to `uidCallback()`', - '2026-06-19' - ), ish('351', '**Feed**: load more feature no longer works', '2026-06-20'), ish( '352', @@ -233,9 +228,66 @@ ish( '370', 'Menu attaching on *Style-2* pages stopped working', '2026-07-15' ), + ish( + '373', + 'Remove "Select current results page" from all pages with pagination', + '2026-07-19' + ), + ish( + '372', + '`Scroller`: New item cache fails on at least one page', + '2026-07-19' + ), ]; const globalNewsContent = [ + { + date: '2026-07-19', + issues: ['373'], + subject: 'Remove the explicit shortcut for selecting the next page' + + ' of results', + }, + { + date: '2026-07-19', + issues: ['372'], + subject: 'Temporarily disable the item cache', + }, + { + date: '2026-07-19', + issues: ['372'], + subject: 'Remove an old logging statement', + }, + { + date: '2026-07-19', + issues: ['372'], + subject: 'Rename some internal symbols', + }, + { + date: '2026-07-19', + issues: ['372'], + subject: 'Remove unnecessary intermediate array', + }, + { + date: '2026-07-19', + issues: [''], + subject: 'Fix a couple of comments', + }, + { + date: '2026-07-19', + issues: ['352'], + subject: 'Additional update on how `Profile`\'s `Scroller`s are' + + ' configured', + }, + { + date: '2026-07-19', + issues: ['302'], + subject: 'Updated `UidMode`s for the *Highlights* section', + }, + { + date: '2026-07-19', + issues: ['302'], + subject: 'Another partial ordering pair', + }, { date: '2026-07-18', issues: ['352'], @@ -796,31 +848,6 @@ issues: ['351'], subject: 'Rework how **Feeds**\'s "load most posts" feature works', }, - { - date: '2026-06-19', - issues: [''], - subject: 'Add links to GitHub issues for certain debug alerts', - }, - { - date: '2026-06-19', - issues: [''], - subject: 'Linkify release notes to the associated issue', - }, - { - date: '2026-06-19', - issues: ['350'], - subject: 'Add `this` as the first argument to `uidCallback()`', - }, - { - date: '2026-06-19', - issues: ['302'], - subject: 'Support the *Education* section', - }, - { - date: '2026-06-19', - issues: ['302'], - subject: 'Rename a variable', - }, ]; /** From 0876d8da6557695849abf78999c81d34b50246e9 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 14:57:41 -0700 Subject: [PATCH 13/19] Bump version number. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ␄ --- linkedin-tool.user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index 5579275..d4b397d 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -4,7 +4,7 @@ // @match https://www.linkedin.com/* // @inject-into content // @noframes -// @version 77 +// @version 78 // @author Mike Castle // @description Minor enhancements to LinkedIn. Mostly just hotkeys. // @license GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0-standalone.html From 1b1f0f38f8dc8bb5f1569f01d72c9caeaa175df1 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Mon, 20 Jul 2026 08:51:35 -0700 Subject: [PATCH 14/19] Update `UidMode`s for the *About* section. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #302. ␄ --- linkedin-tool.user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index d4b397d..f23aea3 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -9593,8 +9593,8 @@ uidCallback: this.#entriesUidFromModes, selectors: [this.ctor.#entriesSelectorAbout], modes: [ - this.ctor.UidMode.TEST_ID, - this.ctor.UidMode.ANCHOR_PROFILE, + this.ctor.UidMode.ANCHOR_OVERLAY, + this.ctor.UidMode.DEFAULT, ], }); this.#entriesScrollerConfigs.set('Services', { From 6b7e0e331ae59279053363877232ad6b662d1edf Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Mon, 20 Jul 2026 09:18:27 -0700 Subject: [PATCH 15/19] Update `UidMode`s for the *Services* section. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #302. ␄ --- linkedin-tool.user.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index f23aea3..59cfd4c 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -9604,9 +9604,11 @@ this.ctor.#entriesSelectorFooter, ], modes: [ - this.ctor.UidMode.TEST_ID, - this.ctor.UidMode.ARIA_LABEL, + this.ctor.UidMode.MULTI_IMG, this.ctor.UidMode.HREF, + this.ctor.UidMode.FOOTER, + // ID goes after FOOTER because it may match the right arrow. + this.ctor.UidMode.ID, ], }); this.#entriesScrollerConfigs.set('Featured', { From be655ffb6dc23980d602acbbe1aec4470fb67a0e Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Mon, 20 Jul 2026 09:27:16 -0700 Subject: [PATCH 16/19] Make `#entriesSuggestUids` an instance method. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #352. ␄ --- linkedin-tool.user.js | 82 +++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index 59cfd4c..c19ad4a 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -9336,46 +9336,6 @@ return results; } - /** - * Suggest UID sources. - * - * @param {Scroller} scroller - Scroller instance. - * @param {Element} element - Element to examine. - */ - static #entriesSuggestUids = (scroller, element) => { - const me = this.#entriesSuggestUids.name; - scroller.logger.entered(me, element); - - const suggestions = []; - - const results = this.#entriesModeToUid( - scroller, element, Object.values(this.UidMode) - ); - for (const [key, value] of results.entries()) { - suggestions.push(key); - scroller.logger.log(key.description, value); - } - - const page = new URL(document.location); - const anchors = new Set(element.querySelectorAll('a') - .values() - .map(x => x.href) - .filter(x => !['/', page.pathname].includes(new URL(x).pathname))); - const ids = element.querySelectorAll(this.entriesUidSelectorId); - - if (anchors.size > 1) { - suggestions.push('anchors'); - scroller.logger.log('Anchors to consider:', anchors); - } - - if (ids.length > 1) { - suggestions.push('ids'); - scroller.logger.log('IDs to consider:', ids); - } - - scroller.logger.leaving(me, 'Suggested:', suggestions); - } - #checkingPartialOrder = false #entriesCurrentModes #entriesCurrentUid @@ -9865,6 +9825,46 @@ .on('out-of-range', this.#returnToSection); } + /** + * Suggest UID sources. + * + * @param {Scroller} scroller - Scroller instance. + * @param {Element} element - Element to examine. + */ + #entriesSuggestUids = (scroller, element) => { + const me = this.#entriesSuggestUids.name; + this.logger.entered(me, element); + + const suggestions = []; + + const results = this.ctor.#entriesModeToUid( + scroller, element, Object.values(this.ctor.UidMode) + ); + for (const [key, value] of results.entries()) { + suggestions.push(key); + this.logger.log(key.description, value); + } + + const page = new URL(document.location); + const anchors = new Set(element.querySelectorAll('a') + .values() + .map(x => x.href) + .filter(x => !['/', page.pathname].includes(new URL(x).pathname))); + const ids = element.querySelectorAll(this.entriesUidSelectorId); + + if (anchors.size > 1) { + suggestions.push('anchors'); + this.logger.log('Anchors to consider:', anchors); + } + + if (ids.length > 1) { + suggestions.push('ids'); + this.logger.log('IDs to consider:', ids); + } + + this.logger.leaving(me, 'Suggested:', suggestions); + } + /** * Return the first UID computed from supported modes. * @@ -9881,7 +9881,7 @@ ); if (results.size === 0) { - this.ctor.#entriesSuggestUids(scroller, element); + this.#entriesSuggestUids(scroller, element); results.set(this.ctor.UidMode.FALLBACK, scroller.defaultUid(element)); } From 14fbbcbd93a4d1197a360ff133ab46bbab726d75 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Mon, 20 Jul 2026 11:28:52 -0700 Subject: [PATCH 17/19] Factor out starting/stopping container monitoring. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #372. ␄ --- .ispell_words | 5 ++- linkedin-tool.user.js | 74 +++++++++++++++++++++++++------------------ 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/.ispell_words b/.ispell_words index b182d4a..732f508 100644 --- a/.ispell_words +++ b/.ispell_words @@ -648,7 +648,6 @@ linkedIn LinkedIn LinkedIn's LinkedInStyleService -Linkify listitem litLabel litOptions @@ -719,7 +718,6 @@ msgs multiImg mutationDispatcher mutationHandler -mutationObserver MutationObserver MutationRecord MutationRecords @@ -1044,7 +1042,6 @@ sectionScroller sectionsPartialOrder sectionUidPrefix seeMore -selectCurrentResultsPage selectorArray SelectorsWithoutBase SemVer @@ -1088,6 +1085,7 @@ spdx src srcdoc stackTrace +startContainers startItem startMutationObserverUrlMonitor startsWith @@ -1096,6 +1094,7 @@ startUrlMonitor startUserscriptManagerUrlMonitor StatusBar statusText +stopContainers stopMutationObserverUrlMonitor stopTest stopUrlMonitor diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index c19ad4a..af03af5 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -1569,30 +1569,8 @@ const me = this.activate.name; this.logger.entered(me); - const containers = new Set(await this.#waitForContainers()); - if (this.#base) { - containers.add(this.#base); - } - - const watcher = this.#currentItemWatcher(); - const observeOptions = { - childList: true, - subtree: true, - attributes: this.#observeAttributes, - }; - - for (const container of containers) { - if (this.#watchForClicks) { - this.#onClickElements.add(container); - container.addEventListener('click', - this.#onClick, - this.#clickOptions); - } - this.logger.log('observing with', container, observeOptions); - this.#containersMutationObserver.observe(container, observeOptions); - } - - await watcher; + await this.#startContainers(); + await this.#currentItemWatcher(); this.#mutationDispatcher.on('attributes', this.#attributesHandler); this.#mutationDispatcher.on('childList', this.#monitorConnectedness); @@ -1608,13 +1586,8 @@ deactivate() { this.#mutationDispatcher.off('attributes', this.#attributesHandler); this.#mutationDispatcher.off('childList', this.#monitorConnectedness); - this.#containersMutationObserver.disconnect(); - for (const container of this.#onClickElements) { - container.removeEventListener('click', - this.#onClick, - this.#clickOptions); - } - this.#onClickElements.clear(); + this.#stopContainers(); + this.dispatcher.fire('deactivate', null); } @@ -1713,6 +1686,45 @@ this.logger.leaving(me); } + #startContainers = async () => { + const me = this.#startContainers.name; + this.logger.entered(me); + + this.#stopContainers(); + const containers = new Set(await this.#waitForContainers()); + if (this.#base) { + containers.add(this.#base); + } + const observeOptions = { + childList: true, + subtree: true, + attributes: this.#observeAttributes, + }; + + for (const container of containers) { + if (this.#watchForClicks) { + this.#onClickElements.add(container); + container.addEventListener('click', + this.#onClick, + this.#clickOptions); + } + this.logger.log('observing with', container, observeOptions); + this.#containersMutationObserver.observe(container, observeOptions); + } + + this.logger.leaving(me); + } + + #stopContainers = () => { + this.#containersMutationObserver.disconnect(); + for (const container of this.#onClickElements) { + container.removeEventListener('click', + this.#onClick, + this.#clickOptions); + } + this.#onClickElements.clear(); + } + /** * If an item is clicked, switch to it. * @param {Event} evt - Standard 'click' event. From ccfeae6d81b89cff26115e4e0f57b1c31be25e18 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Mon, 20 Jul 2026 11:39:48 -0700 Subject: [PATCH 18/19] Update release notes. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ␄ --- linkedin-tool.user.js | 56 ++++++++++++++++--------------------------- 1 file changed, 20 insertions(+), 36 deletions(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index af03af5..49f38a5 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -193,7 +193,6 @@ '2026-06-30' ), ish('349', '`LinkedIn`: Remove navbar margin support', '2026-06-20'), - ish('351', '**Feed**: load more feature no longer works', '2026-06-20'), ish( '352', 'Migrate `Scroller` configurations from static to instance' + @@ -241,6 +240,26 @@ ]; const globalNewsContent = [ + { + date: '2026-07-20', + issues: ['372'], + subject: 'Factor out starting/stopping container monitoring', + }, + { + date: '2026-07-20', + issues: ['352'], + subject: 'Make `#entriesSuggestUids` an instance method', + }, + { + date: '2026-07-20', + issues: ['302'], + subject: 'Update `UidMode`s for the *Services* section', + }, + { + date: '2026-07-20', + issues: ['302'], + subject: 'Update `UidMode`s for the *About* section', + }, { date: '2026-07-19', issues: ['373'], @@ -813,41 +832,6 @@ subject: 'Migrate `JobsView` from `ScrollerStyleService` to' + ' `StyleService`', }, - { - date: '2026-06-20', - issues: ['349'], - subject: 'Stop scrolling the sidebars into view', - }, - { - date: '2026-06-20', - issues: ['302'], - subject: 'Suggest any implemented `UidMode` solutions that work', - }, - { - date: '2026-06-20', - issues: ['302'], - subject: 'Change suggested anchors to a `Set` to remove duplicates', - }, - { - date: '2026-06-20', - issues: ['240'], - subject: 'Migrate `Jobs` from `ScrollerStyleService` to `StyleService`', - }, - { - date: '2026-06-20', - issues: ['240'], - subject: 'Insert a blank line for consistency', - }, - { - date: '2026-06-20', - issues: ['240'], - subject: 'Fix how the closing `}` is added to the CSS Style', - }, - { - date: '2026-06-20', - issues: ['351'], - subject: 'Rework how **Feeds**\'s "load most posts" feature works', - }, ]; /** From eb6c3dddcdf2b79b4e8d0ce7392a0ffb0d322dcc Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Mon, 20 Jul 2026 11:40:19 -0700 Subject: [PATCH 19/19] Bump version number. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ␄ --- linkedin-tool.user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linkedin-tool.user.js b/linkedin-tool.user.js index 49f38a5..45cfa3f 100644 --- a/linkedin-tool.user.js +++ b/linkedin-tool.user.js @@ -4,7 +4,7 @@ // @match https://www.linkedin.com/* // @inject-into content // @noframes -// @version 78 +// @version 79 // @author Mike Castle // @description Minor enhancements to LinkedIn. Mostly just hotkeys. // @license GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0-standalone.html