From 08a13fe79bf37ab97ed938a824fed565e3d6d08f Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 07:30:39 -0700 Subject: [PATCH 01/17] JSDoc: Fix all known issues for `lib/base`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #369. ␄ --- lib/base.js | 377 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 227 insertions(+), 150 deletions(-) diff --git a/lib/base.js b/lib/base.js index 0fff6365..6dbf25b6 100644 --- a/lib/base.js +++ b/lib/base.js @@ -10,30 +10,50 @@ // ==/UserLibrary== // ==/UserScript== +/** + * @file Pure + * [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) + * stuff. Nothing here should be [WEB + * API](https://developer.mozilla.org/en-US/docs/Web/API) aware, except + * `Logger`'s use of `console` (and apparently `crypto`). + * @module base + * @version 72 + */ + window.NexusHoratio ??= {}; window.NexusHoratio.base = (function base() { 'use strict'; - /** @type {number} - Bumped per release. */ + /** @const {number} module:base.version - Bumped per release. */ const version = 72; - /** @type {number} - Returned by some APIs like `findIndex()`. */ + /** + * @const {number} module:base.NOT_FOUND - Returned by some APIs like + * `findIndex()`. + */ const NOT_FOUND = -1; - /** @type {number} - Useful for testing length of an array. */ + /** + * @const {number} module:base.ONE_ITEM - Useful for testing length of an + * array. + */ const ONE_ITEM = 1; /** - * @type {number} - Identify the last item in a collection like `at(-1)`. + * @const {number} module:base.LAST_ITEM - Identify the last item in a + * collection like `at(-1)`. */ const LAST_ITEM = -1; const NULL = 'null'; /** - * Mapping of Google's Canonical Error Codes. - * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto + * Mapping of Google's [Canonical Error Codes]{@link + * https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto}. + * @readonly + * @enum {number} + * @alias module:base.Code */ const Code = { OK: 0, @@ -69,9 +89,10 @@ window.NexusHoratio.base = (function base() { /** * Ensures appropriate versions of NexusHoratio libraries are loaded. * - * @param {Version[]} versions - Versions required. - * @returns {object} - Namespace with only ensured libraries present. - * @throws {Error} - When requirements not met. + * @function module:base.ensure + * @param {module:base~Version[]} versions - Versions required. + * @returns {object} Namespace with only ensured libraries present. + * @throws {Error} When requirements not met. */ function ensure(versions) { let msg = 'Forgot to set a message'; @@ -168,13 +189,14 @@ window.NexusHoratio.base = (function base() { * * It takes a fixed list of event types upon construction and attempts to * use an unknown event will throw an error. + * @static */ class Dispatcher { /** - * @callback Handler + * @callback module:base.Dispatcher~Handler * @param {string} eventType - Event type. - * @param {*} data - Event data. + * @param {object} data - Event data. */ /** @@ -186,12 +208,12 @@ window.NexusHoratio.base = (function base() { } } - /** @type {boolean} - Whether dispatching is currently enabled. */ + /** @type {boolean} */ get enabled() { return this.#enabled; } - /** @param {boolean} val - Set if dispatching is currently enabled. */ + // eslint-disable-next-line require-jsdoc set enabled(val) { this.#enabled = Boolean(val); } @@ -200,8 +222,9 @@ window.NexusHoratio.base = (function base() { * Attach a function to an eventType. * * @param {string} eventType - Event type to connect with. - * @param {Handler} func - Single argument function to call. - * @returns {Dispatcher} - This instance, for chaining. + * @param {module:base.Dispatcher~Handler} func - Single argument function + * to call. + * @returns {module:base.Dispatcher} This instance, for chaining. */ on(eventType, func) { const handlers = this.#getHandlers(eventType); @@ -213,8 +236,8 @@ window.NexusHoratio.base = (function base() { * Remove all instances of a function registered to an eventType. * * @param {string} eventType - Event type to disconnect from. - * @param {Handler} func - Function to remove. - * @returns {Dispatcher} - This instance, for chaining. + * @param {module:base.Dispatcher~Handler} func - Function to remove. + * @returns {module:base.Dispatcher} This instance, for chaining. */ off(eventType, func) { const handlers = this.#getHandlers(eventType); @@ -230,7 +253,7 @@ window.NexusHoratio.base = (function base() { * * @param {string} eventType - Event type to use. * @param {object} data - Data to pass to each function. - * @returns {Dispatcher} - This instance, for chaining. + * @returns {module:base.Dispatcher} This instance, for chaining. */ fire(eventType, data) { if (this.enabled) { @@ -249,10 +272,10 @@ window.NexusHoratio.base = (function base() { * Look up array of handlers by event type. * * @param {string} eventType - Event type to look up. - * @throws {Error} - When eventType was not registered during + * @throws {Error} When eventType was not registered during * instantiation. - * @returns {Handler[]} - Handlers currently registered for this - * eventType. + * @returns {module:base.Dispatcher~Handler[]} Handlers currently + * registered for this eventType. */ #getHandlers = (eventType) => { const handlers = this.#handlers.get(eventType); @@ -491,20 +514,21 @@ window.NexusHoratio.base = (function base() { /* eslint-enable */ /** - * A simple message system that will queue messages to be delivered. + * A simple point-to-point system that will queue messages to be delivered. * * This is similar to the WEB API's `MessageChannel`. + * @static */ class MessageQueue { - /** @type {number} - Number of messages currently queued. */ + /** @type {number} */ get count() { return this.#messages.length; } /** - * @param {...*} items - Whatever to add to the queue. - * @returns {MessageQueue} - This instance, for chaining. + * @param {...object} items - Whatever to add to the queue. + * @returns {module:base.MessageQueue} This instance, for chaining. */ post(...items) { this.#messages.push(items); @@ -513,9 +537,9 @@ window.NexusHoratio.base = (function base() { } /** - * @param {?function(...*)} func - Function that receives the messages. - * If falsy, listener is removed. - * @returns {MessageQueue} - This instance, for chaining. + * @param {function(...object)} [func] - Function that receives the + * messages. If falsy, the listener is removed. + * @returns {module:base.MessageQueue} This instance, for chaining. */ listen(func) { if (func) { @@ -690,24 +714,24 @@ window.NexusHoratio.base = (function base() { this.assign(value); } - /** @returns {number} - Current value. */ + /** @returns {number} Current value. */ valueOf() { return this.#value; } - /** @returns {string} - Current value. */ + /** @returns {string} Current value. */ toString() { return `${this.valueOf()}`; } - /** @returns {number} - Current value. */ + /** @returns {number} Current value. */ toJSON() { return this.valueOf(); } /** * @param {number} value - Number to assign. - * @returns {NumberOp} - This instance. + * @returns {module:base~NumberOp} This instance. */ assign(value = 0) { this.#value = Number(value); @@ -716,7 +740,7 @@ window.NexusHoratio.base = (function base() { /** * @param {number} value - Number to add. - * @returns {NumberOp} - This instance. + * @returns {module:base~NumberOp} This instance. */ add(value) { this.#value += Number(value); @@ -822,14 +846,17 @@ window.NexusHoratio.base = (function base() { * First argument is a factory function that will create a new default value * for the key if not already present in the container. * - * The factory function may take arguments. When `.getWithArguments()` is - * used, those arguments will be passed to the factory. + * The factory function may take arguments. When {@link + * module:base.DefaultMap#getWithArguments getWithArguments} is used, those + * arguments will be passed to the factory. + * @static + * @extends Map */ class DefaultMap extends Map { /** - * @param {function(...args) : *} factory - Function that creates a new - * default value if a requested key is not present. + * @param {function(...args) : object} factory - Function that creates a + * new default value if a requested key is not present. * @param {Iterable} [iterable] - Passed to {Map} super(). */ constructor(factory, iterable) { @@ -843,8 +870,9 @@ window.NexusHoratio.base = (function base() { } /** - * @param {*} key - The key of the element to return from this instance. - * @returns {*} - The value associated with the key, perhaps newly + * @param {object} key - The key of the element to return from this + * instance. + * @returns {object} The value associated with the key, perhaps newly * created. */ get(key) { @@ -858,10 +886,11 @@ window.NexusHoratio.base = (function base() { /** * Variant of `Map.prototype.get()` that passes args to the factory. * - * @param {*} key - The key of the element to return from this instance. - * @param {...*} args - Extra arguments passed to the factory function if - * it is called. - * @returns {*} - The value associated with the key, perhaps newly + * @param {object} key - The key of the element to return from this + * instance. + * @param {...object} args - Extra arguments passed to the factory + * function if it is called. + * @returns {object} The value associated with the key, perhaps newly * created. */ getWithArguments(key, ...args) { @@ -970,52 +999,57 @@ window.NexusHoratio.base = (function base() { * Fancy-ish log messages (likely over engineered). * * Console nested message groups can be started and ended using the special - * method pairs, {@link Logger#entered}/{@link Logger#leaving} and {@link - * Logger#starting}/{@link Logger#finished}. By default, the former are - * opened and the latter collapsed (documented here as closed). + * intro/outro method pairs, {@link module:base.Logger#entered + * entered}/{@link module:base.Logger#leaving leaving} and {@link + * module:base.Logger#starting starting}/{@link module:base.Logger#finished + * finished}. By default, the former are opened and the latter collapsed + * (documented here as `closed`). * * Individual Loggers can be enabled/disabled by setting the {@link - * Logger##Config.enabled} boolean property. + * module:base.Logger~Config#enabled enabled} boolean property. * - * Each Logger will have also have a collection of {@link Logger##Group}s - * associated with it. These groups can have one of three modes: "opened", - * "closed", "silenced". The first two correspond to the browser console - * nested message groups. The intro and outro type of methods will handle - * the nesting. If a group is set as "silenced", no messages will be sent - * to the console. + * Each Logger will have also have a collection of {@link + * module:base.Logger~Group Group}s associated with it. These groups can + * have one of three modes: `opened`, `closed`, `silenced`. The first two + * correspond to the browser console nested message groups. The intro and + * outro type of methods will handle the nesting. If a group is set as + * `silenced`, no messages will be sent to the console. * * All Logger instances register a configuration with a singleton Map keyed * by the instance name. If more than one instance is created with the same * name, they all share the same configuration. * * Configurations can be exported as a plain object and reimported using the - * {@link Logger.configs} property. The object could be saved via the - * userscript manager. Depending on which one, it may have to be processed - * with the JSON.{stringify,parse} functions. Once exported, the object may - * be modified. This could be used to provide a UI to edit the object, - * though no schema is provided. + * {@link module:base.Logger.configs configs} property. The object could be + * saved via the userscript manager. Depending on which manager, it may + * have to be processed with the `JSON.{stringify,parse}` functions. Once + * exported, the object may be modified. This could be used to provide a UI + * to edit the object, though no schema is provided. * * Some values may be of interest to users for help in debugging a script. * - * The {callCount} value is how many times a logger would have been used for - * messages, even if the logger is disabled. Similarly, each group - * associated with a logger also has a {callCount}. These values can be - * used to determine which loggers and groups generate a lot of messages and - * could be disabled or silenced. + * The {@link module:base.Logger~Config#callCount callCount} value is how + * many times a logger would have been used for messages, even if the logger + * is disabled. Similarly, each group associated with a logger also has a + * {@link module:base.Logger~Group#callCount callCount}. These values can + * be used to determine which loggers and groups generate a lot of messages + * and could be disabled or silenced. * - * The {sequence} value is a rough indicator of how recently a logger or - * group was actually used. It is purposely not a timestamp, but rather, - * more closely associated with how often configurations are restored, - * e.g. during web page reloads. A low sequence number, relative to the - * others, may indicate a logger was renamed, groups removed, or simply - * parts of an application that have not been visited recently. Depending - * on the situation, one could clean up old configs, or explore other parts - * of the script. + * The {@link module:base.Logger~Config#sequence sequence} value is a rough + * indicator of how recently a logger or group was actually used. It is + * purposely not a timestamp, but rather, more closely associated with how + * often configurations are restored, e.g. during web page reloads. A low + * sequence number, relative to the others, may indicate a logger was + * renamed, groups removed, or simply parts of an application that have not + * been visited recently. Depending on the situation, one could clean up + * old configs, or explore other parts of the script. * * Handlers may be attached to listen for changes to the configuration - * singleton using the {@link Logger.onConfig} and {@link offConfig} static - * methods. Arrival of messages may controlled using {@link - * Logger.configDispatcherEnabled} (useful for disabling during testing). + * singleton using the {@link module:base.Logger.onConfig onConfig} and + * {@link module:base.Logger.offConfig offConfig} static methods. Arrival + * of messages may controlled using {@link + * module:base.Logger.configDispatcherEnabled configDispatcherEnabled} + * (useful for disabling during testing). * * @example * const log = new Logger('Bob'); @@ -1039,19 +1073,21 @@ window.NexusHoratio.base = (function base() { * GM.setValue('Logger', Logger.configs); * ... restart browser ... * Logger.configs = GM.getValue('Logger'); + * @static */ class Logger { /** - * @typedef {object} ConfigMutationRecord + * @typedef {object} module:base.Logger~ConfigMutationRecord * @property {string} logger - Name of the affected Logger instance. * @property {string?} group - Name of a possibly affected group. */ /** - * @callback ConfigMutationHandler + * @callback module:base.Logger~ConfigMutationHandler * @param {string} evt - Type of mutation. - * @param {ConfigMutationRecord} record - Details about the mutation. + * @param {module:base.Logger~ConfigMutationRecord} record - Details about + * the mutation. */ /** @param {string} loggerName - Name for this logger. */ @@ -1065,22 +1101,22 @@ window.NexusHoratio.base = (function base() { static sequence = 1; - /** @type {boolean} - Whether dispatching is currently enabled. */ + /** @type {boolean} */ static get configDispatcherEnabled() { return this.#configDispatcher.enabled; } - /** @param {boolean} val - Set if dispatching is currently enabled. */ + // eslint-disable-next-line require-jsdoc static set configDispatcherEnabled(val) { this.#configDispatcher.enabled = Boolean(val); } - /** @type {object} - Logger configurations. */ + /** @type {object} */ static get configs() { return Logger.#toPojo(); } - /** @param {object} val - Logger configurations. */ + // eslint-disable-next-line require-jsdoc static set configs(val) { const enabled = Logger.configDispatcherEnabled; Logger.configDispatcherEnabled = false; @@ -1093,7 +1129,7 @@ window.NexusHoratio.base = (function base() { } } - /** @type {string[]} - Names of known loggers. */ + /** @type {string[]} */ static get loggers() { return Array.from(this.#loggers.keys()); } @@ -1101,8 +1137,8 @@ window.NexusHoratio.base = (function base() { /** * Attach a function for all config change events. * - * @param {ConfigMutationHandler} callback - Function that receives - * events. + * @param {module:base.Logger~ConfigMutationHandler} callback - Function + * that receives events. */ static onConfig(callback) { for (const evt of this.#configEvents) { @@ -1113,8 +1149,8 @@ window.NexusHoratio.base = (function base() { /** * Remove all instances of a function attached for config change events. * - * @param {ConfigMutationHandler} callback - Function that receives - * events. + * @param {module:base.Logger~ConfigMutationHandler} callback - Function + * that receives events. */ static offConfig(callback) { for (const evt of this.#configEvents) { @@ -1126,7 +1162,7 @@ window.NexusHoratio.base = (function base() { * Get configuration of a specific Logger. * * @param {string} loggerName - Logger configuration to get. - * @returns {Logger.Config} - Current config for that Logger. + * @returns {module:base.Logger~Config} Current config for that Logger. */ static config(loggerName) { return this.#configs.getWithArguments(loggerName, loggerName); @@ -1143,27 +1179,30 @@ window.NexusHoratio.base = (function base() { this.#clear(); } - /** @type {boolean} - Whether logging is currently enabled. */ + /** + * @type {boolean} + * @alias module:base.Logger~Config#enabled + */ get enabled() { return this.#config.enabled; } - /** @type {boolean} - Indicates whether messages include a stack trace. */ + /** @type {boolean} */ get includeStackTrace() { return this.#config.includeStackTrace; } - /** @type {MessageQueue} */ + /** @type {module:base.MessageQueue} */ get mq() { return this.#mq; } - /** @type {string} - Name for this logger. */ + /** @type {string} */ get name() { return this.#name; } - /** @type {boolean} - Indicates whether current group is silenced. */ + /** @type {boolean} */ get silenced() { let ret = false; const group = this.#groupStack.at(LAST_ITEM); @@ -1178,7 +1217,7 @@ window.NexusHoratio.base = (function base() { * Log a specific message. * * @param {string} msg - Message to send to console.debug. - * @param {...*} rest - Arbitrary items to pass to console.debug. + * @param {...object} rest - Arbitrary items to pass to console.debug. */ log(msg, ...rest) { this.#log(msg, ...rest); @@ -1188,7 +1227,7 @@ window.NexusHoratio.base = (function base() { * Indicate entered a specific group. * * @param {string} group - Group that was entered. - * @param {...*} rest - Arbitrary items to pass to console.debug. + * @param {...object} rest - Arbitrary items to pass to console.debug. */ entered(group, ...rest) { this.#intro(group, Logger.#GroupMode.Opened, ...rest); @@ -1198,7 +1237,7 @@ window.NexusHoratio.base = (function base() { * Indicate leaving a specific group. * * @param {string} group - Group leaving. - * @param {...*} rest - Arbitrary items to pass to console.debug. + * @param {...object} rest - Arbitrary items to pass to console.debug. */ leaving(group, ...rest) { this.#outro(group, ...rest); @@ -1208,7 +1247,7 @@ window.NexusHoratio.base = (function base() { * Indicate starting a specific collapsed group. * * @param {string} group - Group that is being started. - * @param {...*} rest - Arbitrary items to pass to console.debug. + * @param {...object} rest - Arbitrary items to pass to console.debug. */ starting(group, ...rest) { this.#intro(group, Logger.#GroupMode.Closed, ...rest); @@ -1218,7 +1257,7 @@ window.NexusHoratio.base = (function base() { * Indicate finished with a specific collapsed group. * * @param {string} group - Group that was entered. - * @param {...*} rest - Arbitrary items to pass to console.debug. + * @param {...object} rest - Arbitrary items to pass to console.debug. */ finished(group, ...rest) { this.#outro(group, ...rest); @@ -1226,41 +1265,54 @@ window.NexusHoratio.base = (function base() { static #Config = class { - /** @param {string} loggerName - Name of new logger config. */ + /** + * @alias module:base.Logger~Config + * @param {string} loggerName - Name of new logger config. + */ constructor(loggerName) { this.#loggerName = loggerName; this.#fireConfigChange('logger'); } + /** + * @type {number} + * @alias module:base.Logger~Config#sequence + */ sequence = 0; - /** @type {NumberOp} */ + /** + * @type {module:base~NumberOp} + * @alias module:base.Logger~Config#callCount + */ get callCount() { return this.#callCount; } - /** @type {boolean} - Whether logging is currently enabled. */ + /** @type {boolean} */ get enabled() { return this.#enabled; } - /** @param {boolean} val - Set whether logging is currently enabled. */ + // eslint-disable-next-line require-jsdoc set enabled(val) { this.#enabled = Boolean(val); this.#fireConfigChange('enabled'); } - /** @type {Map} - Per group settings. */ + /** + * @type {Map} + * Per group settings. + */ get groups() { return this.#groups; } - /** @type {boolean} - Whether messages include a stack trace. */ + /** @type {boolean} */ get includeStackTrace() { return this.#includeStackTrace; } - /** @param {boolean} val - Set inclusion of stack traces. */ + // eslint-disable-next-line require-jsdoc set includeStackTrace(val) { this.#includeStackTrace = Boolean(val); this.#fireConfigChange('includeStackTrace'); @@ -1268,8 +1320,10 @@ window.NexusHoratio.base = (function base() { /** * @param {string} groupName - Name of the group to get. - * @param {Logger.#GroupMode} mode - Default mode if not seen before. - * @returns {Logger.#Group} - Requested group, perhaps newly made. + * @param {module:base.Logger~GroupMode} mode - Default mode if not seen + * before. + * @returns {module:base.Logger~Group} Requested group, perhaps newly + * made. */ group(groupName, mode) { const defaultMode = mode ?? 'opened'; @@ -1295,7 +1349,7 @@ window.NexusHoratio.base = (function base() { this.#fireConfigChange('used', groupName); } - /** @returns {object} - Config as a plain object. */ + /** @returns {object} Config as a plain object. */ toPojo() { const pojo = { callCount: this.callCount.valueOf(), @@ -1347,7 +1401,7 @@ window.NexusHoratio.base = (function base() { /** * @param {string} evt - Config change event to fire. * @param {string?} group - Name of group for {@link - * ConfigMutationRecord}. + * module:base.Logger~ConfigMutationRecord ConfigMutationRecord}. */ #fireConfigChange = (evt, group) => { Logger.#configDispatcher.fire(evt, { @@ -1361,8 +1415,11 @@ window.NexusHoratio.base = (function base() { static #Group = class { /** - * @param {Logger.#GroupMode} mode - Initial mode for this group. - * @param {string} logger - Name of associated {@link Logger} instance. + * @alias module:base.Logger~Group + * @param {module:base.Logger~GroupMode} mode - Initial mode for this + * group. + * @param {string} logger - Name of associated {@link + * module:base.Logger Logger} instance. * @param {string} group - Name of this group. */ constructor(mode, logger, group) { @@ -1373,17 +1430,20 @@ window.NexusHoratio.base = (function base() { this.#fireConfigChange('group'); } - /** @type {NumberOp} */ + /** + * @type {module:base~NumberOp} + * @alias module:base.Logger~Group#callCount + */ get callCount() { return this.#callCount; } - /** @type {Logger.#GroupMode} */ + /** @type {module:base.Logger~GroupMode} */ get mode() { return this.#mode; } - /** @param {Logger.#GroupMode} val - Mode to set this group. */ + // eslint-disable-next-line require-jsdoc set mode(val) { let newVal = val; if (!(newVal instanceof Logger.#GroupMode)) { @@ -1395,7 +1455,7 @@ window.NexusHoratio.base = (function base() { this.#fireConfigChange('mode'); } - /** @returns {object} - Group as a plain object. */ + /** @returns {object} Group as a plain object. */ toPojo() { const pojo = { mode: this.mode.name, @@ -1429,10 +1489,12 @@ window.NexusHoratio.base = (function base() { } - /** Enum/helper for Logger groups. */ static #GroupMode = class { /** + * Enum/helper for Logger groups. + * + * @alias module:base.Logger~GroupMode * @param {string} modeName - Mode name. * @param {string} [greeting] - Greeting when opening group. * @param {string} [farewell] - Salutation when closing group. @@ -1453,28 +1515,31 @@ window.NexusHoratio.base = (function base() { * Find GroupMode by name. * * @param {string} modeName - Mode name. - * @returns {GroupMode} - Mode, if found. + * @returns {module:base.Logger~GroupMode} Mode, if found. */ static byName(modeName) { return this.#known.get(modeName); } - /** @type {string} - Farewell when closing group. */ + /** @type {string} */ get farewell() { return this.#farewell; } - /** @type {string} - console.func to use for opening group. */ + /** + * @type {string} + * console.func to use for opening group. + */ get func() { return this.#func; } - /** @type {string} - Greeting when opening group. */ + /** @type {string} */ get greeting() { return this.#greeting; } - /** @type {string} - Mode name. */ + /** @type {string} */ get name() { return this.#name; } @@ -1534,7 +1599,7 @@ window.NexusHoratio.base = (function base() { } } - /** @returns {object} - Logger.#configs as a plain object. */ + /** @returns {object} Logger.#configs as a plain object. */ static #toPojo = () => { const pojo = { type: 'LoggerConfigs', @@ -1585,7 +1650,7 @@ window.NexusHoratio.base = (function base() { * Log a specific message. * * @param {string} msg - Message to send to console.debug. - * @param {...*} rest - Arbitrary items to pass to console.debug. + * @param {...object} rest - Arbitrary items to pass to console.debug. */ #log = (msg, ...rest) => { const group = this.#groupStack.at(LAST_ITEM); @@ -1604,8 +1669,8 @@ window.NexusHoratio.base = (function base() { * Introduces a specific group. * * @param {string} group - Group being created. - * @param {Logger.#GroupMode} defaultMode - Mode to use if new. - * @param {...*} rest - Arbitrary items to pass to console.debug. + * @param {module:base.Logger~GroupMode} defaultMode - Mode to use if new. + * @param {...object} rest - Arbitrary items to pass to console.debug. */ #intro = (group, defaultMode, ...rest) => { this.#groupStack.push(group); @@ -1625,7 +1690,7 @@ window.NexusHoratio.base = (function base() { * Concludes a specific group. * * @param {string} group - Group leaving. - * @param {...*} rest - Arbitrary items to pass to console.debug. + * @param {...object} rest - Arbitrary items to pass to console.debug. */ #outro = (group, ...rest) => { const mode = this.#config.group(group).mode; @@ -1653,7 +1718,6 @@ window.NexusHoratio.base = (function base() { /* eslint-disable require-jsdoc */ /* eslint-disable no-undefined */ - /** This must be nested due to accessing #private fields. */ static GroupModeTestCase = class extends NH.xunit.TestCase { testClassIsFrozen() { @@ -1775,7 +1839,7 @@ window.NexusHoratio.base = (function base() { // Assert - // Some of these are {@link NumberOp} + // Some of these are {@link module:base~NumberOp} this.defaultEqual = this.equalValueOf; const config = Logger.config(this.id); @@ -1844,7 +1908,7 @@ window.NexusHoratio.base = (function base() { // This test does not strictly follow Assemble/Act/Assert as it has // extra verifications during state changes. - // Some of these are {@link NumberOp} + // Some of these are {@link module:base~NumberOp} this.defaultEqual = this.equalValueOf; // Initial @@ -1881,7 +1945,7 @@ window.NexusHoratio.base = (function base() { // This test does not strictly follow Assemble/Act/Assert as it has // extra verifications during state changes. - // Some of these are {@link NumberOp} + // Some of these are {@link module:base~NumberOp} this.defaultEqual = this.equalValueOf; const grp = 'a-loop'; @@ -1952,8 +2016,8 @@ window.NexusHoratio.base = (function base() { /** * Execute TestCase tests. * - * @param {Logger} logger - Logger to use. - * @returns {boolean} - Success status. + * @param {module:base.Logger} logger - Logger to use. + * @returns {boolean} Success status. */ function doTestCases(logger) { const me = 'Running TestCases'; @@ -1993,7 +2057,9 @@ window.NexusHoratio.base = (function base() { /** * Basic test runner. * - * This depends on {Logger}, hence the location in this file. + * This depends on {@link module:base.Logger Logger}, hence the location in + * this file instead of {@link module:xunit}. + * @function runTests */ function runTests() { if (NH.xunit.testing.enabled) { @@ -2017,8 +2083,9 @@ window.NexusHoratio.base = (function base() { /** * Create a UUID-like string with a base. * + * @function module:base.uuId * @param {string} strBase - Base value for the string. - * @returns {string} - A unique string. + * @returns {string} A unique string. */ function uuId(strBase) { return `${strBase}-${crypto.randomUUID()}`; @@ -2027,8 +2094,9 @@ window.NexusHoratio.base = (function base() { /** * Normalize a string to be safe to use as an HTML element id. * + * @function module:base.safeId * @param {string} input - The string to normalize. - * @returns {string} - Normalized string. + * @returns {string} Normalized string. */ function safeId(input) { let result = input @@ -2093,8 +2161,9 @@ window.NexusHoratio.base = (function base() { * * https://en.wikipedia.org/wiki/Jenkins_hash_function#one_at_a_time * + * @function module:base.strHash * @param {string} s - String to hash. - * @returns {string} - Hash value. + * @returns {string} Hash value. */ function strHash(s) { const max = 4294967296n; @@ -2143,8 +2212,9 @@ window.NexusHoratio.base = (function base() { /** * @deprecated Compute the SHA-256 digest of a string. * + * @function module:base.sha256 * @param {string} string - String to hash. - * @returns {string} - Hex digest of the string. + * @returns {string} Hex digest of the string. */ async function sha256(string) { const radix = 16; @@ -2206,8 +2276,9 @@ window.NexusHoratio.base = (function base() { * Empty strings return an empty array. * Extra separators are consolidated. * + * @function module:base.simpleParseWords * @param {string} text - Text to parse. - * @returns {string[]} - Parsed text. + * @returns {string[]} Parsed text. */ function simpleParseWords(text) { const results = []; @@ -2388,8 +2459,9 @@ window.NexusHoratio.base = (function base() { /** * Base class for building services that can be turned on and off. * - * Subclasses should NOT override methods here, except for constructor(). - * Instead they should register listeners for appropriate events. + * Subclasses should NOT override methods here, except for the + * `constructor`. Instead they should register listeners for appropriate + * events. * * Generally, methods will fire two event verbs. The first, in present * tense, will instruct what should happen (activate, deactivate). The @@ -2427,7 +2499,7 @@ window.NexusHoratio.base = (function base() { * .on('deactivated', dummyEventCallback); * service.activate(); * service.deactivate(); - * + * @static */ class Service { @@ -2439,17 +2511,17 @@ window.NexusHoratio.base = (function base() { this.setShortName(shortName); } - /** @type {Logger} - Logger instance. */ + /** @type {module:base.Logger} */ get logger() { return this.#logger; } - /** @type {string} - Instance name. */ + /** @type {string} */ get name() { return this.#name; } - /** @type {string} - Shorter instance name. */ + /** @type {string} */ get shortName() { return this.#shortName; } @@ -2482,9 +2554,9 @@ window.NexusHoratio.base = (function base() { * Attach a function to an eventType. * * @param {string} eventType - Event type to connect with. - * @param {Dispatcher~Handler} func - Single argument function to - * call. - * @returns {Service} - This instance, for chaining. + * @param {module:base.Dispatcher~Handler} func - Single argument function + * to call. + * @returns {module:base.Service} This instance, for chaining. */ on(eventType, func) { this.#dispatcher.on(eventType, func); @@ -2495,8 +2567,8 @@ window.NexusHoratio.base = (function base() { * Remove all instances of a function registered to an eventType. * * @param {string} eventType - Event type to disconnect from. - * @param {Dispatcher~Handler} func - Function to remove. - * @returns {Service} - This instance, for chaining. + * @param {module:base.Dispatcher~Handler} func - Function to remove. + * @returns {module:base.Service} This instance, for chaining. */ off(eventType, func) { this.#dispatcher.off(eventType, func); @@ -2506,7 +2578,7 @@ window.NexusHoratio.base = (function base() { /** * @param {boolean} allow - Whether to allow this service to be activated * when already active. - * @returns {Service} - This instance, for chaining. + * @returns {module:base.Service} This instance, for chaining. */ allowReactivation(allow) { this.#allowReactivation = allow; @@ -2515,7 +2587,7 @@ window.NexusHoratio.base = (function base() { /** * @param {string} shortName - Custom name portion of this instance. - * @returns {Service} - This instance, for chaining. + * @returns {module:base.Service} This instance, for chaining. */ setShortName(shortName) { this.#name = `${this.constructor.name}: ${shortName}`; @@ -2548,6 +2620,11 @@ window.NexusHoratio.base = (function base() { static Test = class extends Service { + /** + * @alias Test + * @param {string} myName - Testing. + * @ignore + */ constructor(myName) { super(`The ${myName}`); this.on('activate', this.#onEvent) From 4efb5ebe71238a6f88df5853f01ae53b7bd6e826 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 07:31:56 -0700 Subject: [PATCH 02/17] JSDoc: Enable processing for `lib/base`. Issue #369. --- .jsdoc.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.jsdoc.json b/.jsdoc.json index 9e502530..62dcc3ed 100644 --- a/.jsdoc.json +++ b/.jsdoc.json @@ -3,7 +3,7 @@ "private": false }, "source": { - "include": ["demos.user.js", "lib/xunit.js"] + "include": ["demos.user.js", "lib/xunit.js", "lib/base.js"] }, "plugins": ["plugins/markdown"], "markdown": { From 358c2bf175106c7300065d0f5ded090427cade28 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 07:34:17 -0700 Subject: [PATCH 03/17] Bump version number. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ␄ --- lib/base.js | 6 +++--- lib/base.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/base.js b/lib/base.js index 6dbf25b6..c6006e81 100644 --- a/lib/base.js +++ b/lib/base.js @@ -2,7 +2,7 @@ // ==UserLibrary== // @name NH_base // @description Base library usable any time. -// @version 72 +// @version 73 // @license GPL-3.0-or-later; https://www.gnu.org/licenses/gpl-3.0-standalone.html // @homepageURL https://github.com/nexushoratio/userscripts // @supportURL https://github.com/nexushoratio/userscripts/issues @@ -17,7 +17,7 @@ * API](https://developer.mozilla.org/en-US/docs/Web/API) aware, except * `Logger`'s use of `console` (and apparently `crypto`). * @module base - * @version 72 + * @version 73 */ window.NexusHoratio ??= {}; @@ -26,7 +26,7 @@ window.NexusHoratio.base = (function base() { 'use strict'; /** @const {number} module:base.version - Bumped per release. */ - const version = 72; + const version = 73; /** * @const {number} module:base.NOT_FOUND - Returned by some APIs like diff --git a/lib/base.md b/lib/base.md index 5f7c3368..374f97ca 100644 --- a/lib/base.md +++ b/lib/base.md @@ -2,7 +2,7 @@ Pure [JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) stuff. Nothing here should be [WEB API](https://developer.mozilla.org/en-US/docs/Web/API) aware, except `Logger`'s use of `console` (and apparently `crypto`). -## Exported properties (as of version 71) +## Exported properties (as of version 73) * version - Bumped per release. * NOT_FOUND - Returned by some APIs like `findIndex()`. * ONE_ITEM - Useful for testing length of an array. From f4997c2c4a4358b8f5db722fd83d1f452dcaa7db Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 08:23:09 -0700 Subject: [PATCH 04/17] JSDoc: Fine-tune docs in `lib/xunit`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #369. ␄ --- lib/xunit.js | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/lib/xunit.js b/lib/xunit.js index 9c33fb2a..13205eb4 100644 --- a/lib/xunit.js +++ b/lib/xunit.js @@ -23,10 +23,7 @@ window.NexusHoratio ??= {}; window.NexusHoratio.xunit = (function xunit() { 'use strict'; - /** - * @const {number} version - Bumped per release. - * @static - */ + /** @const {number} module:xunit.version - Bumped per release. */ const version = 63; /** @@ -108,8 +105,7 @@ window.NexusHoratio.xunit = (function xunit() { } /** - * @callback ReprFunc - * @memberof module:xunit.TypeTool~ + * @callback module:xunit.TypeTool~ReprFunc * @param {object} item - Anything. * @returns {string} String version of item. */ @@ -215,7 +211,8 @@ window.NexusHoratio.xunit = (function xunit() { } /** - * See {@link base.runTests} for an example of how this might be used. + * See {@link module:base~runTests} for an example of how this might be + * used. * * @const {object} testing - Default testing support. * @property {boolean} enabled - Whether tests should be executed. @@ -229,10 +226,16 @@ window.NexusHoratio.xunit = (function xunit() { /** Data about a test execution. */ class TestExecution { - /** @type {number} - Returned from Date.now(). */ + /** + * Returned from Date.now(). + * @type {number} + */ start = 0; - /** @type {number} - Returned from Date.now(). */ + /** + * Returned from Date.now(). + * @type {number} + */ stop = 0; } @@ -241,8 +244,7 @@ window.NexusHoratio.xunit = (function xunit() { class TestResult { /** - * @typedef {object} MethodResult - * @memberof module:xunit~TestResult~ + * @typedef {object} module:xunit~TestResult~MethodResult * @property {string} methodName - Name of the associated {@link * module:xunit.TestCase} method. * @property {string?} error - Name of caught exception. @@ -523,7 +525,7 @@ window.NexusHoratio.xunit = (function xunit() { /** * @class * @extends Error - * @memberof module:xunit.TestCase~ + * @alias module:xunit.TestCase~Error */ static Error = class extends Error { @@ -537,14 +539,14 @@ window.NexusHoratio.xunit = (function xunit() { /** * @class * @extends module:xunit.TestCase~Error - * @memberof module:xunit.TestCase~ + * @alias module:xunit.TestCase~Fail */ static Fail = class extends this.Error {} /** * @class * @extends module:xunit.TestCase~Error - * @memberof module:xunit.TestCase~ + * @alias module:xunit.TestCase~Skip */ static Skip = class extends this.Error {} @@ -849,16 +851,14 @@ window.NexusHoratio.xunit = (function xunit() { } /** - * @typedef {object} EqualOutput - * @memberof module:xunit.TestCase~ + * @typedef {object} module:xunit.TestCase~EqualOutput * @property {boolean} equal - Result of equality test. * @property {string} detail - Details appropriate to the test (e.g., * where items differed). */ /** - * @callback EqualFunc - * @memberof module:xunit.TestCase~ + * @callback module:xunit.TestCase~EqualFunc * @param {object} first - First argument. * @param {object} second - Second argument. * @returns {module:xunit.TestCase~EqualOutput} Results of testing @@ -3436,8 +3436,7 @@ diff: | /** * Run registered TestCases. * - * @function runTests - * @static + * @function module:xunit.runTests * @returns {module:xunit~TestResult} Accumulated results of these tests. */ function runTests() { From 0d8dc27e05ad18ebfb4caf31e8160043814dacb2 Mon Sep 17 00:00:00 2001 From: Mike Castle Date: Sun, 19 Jul 2026 09:08:22 -0700 Subject: [PATCH 05/17] 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 78abbd12..233d5c35 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 06/17] 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 233d5c35..d0a1308c 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 07/17] 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 d0a1308c..de46042d 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 08/17] 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 de46042d..f64308f3 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 09/17] 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 f64308f3..b7fc6030 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 10/17] 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 b7fc6030..258bc1db 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 11/17] 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 258bc1db..b13603b1 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 12/17] 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 977e6b83..b182d4a2 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 b13603b1..ee46f2f4 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 13/17] 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 ee46f2f4..8b6da57e 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 14/17] 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 8b6da57e..05e393b1 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 15/17] 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 05e393b1..f8bf900a 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 16/17] 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 f8bf900a..55792755 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 17/17] 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 55792755..d4b397d9 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