diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index a0ba519..a9216c3 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -11,9 +11,8 @@ env: ABS_JK: ${{ github.workspace }}/jk ABS_JSDOC: ${{ github.workspace }}/out API_README_MD: ${{ github.workspace }}/api.md - REL_README_DIR: proj - ABS_README_DIR: ${{ github.workspace }}/proj - PROJ_README_MD: ${{ github.workspace }}/proj/index.md + REL_MARKDOWN_DIR: md + ABS_MARKDOWN_DIR: ${{ github.workspace }}/md jobs: build: @@ -38,17 +37,22 @@ jobs: ref: scratch path: scratch - - name: Readme + - name: Prepare Markdown run: | - mkdir -p ${{ env.ABS_README_DIR }}/_layouts/ - cp -v "${GITHUB_REF_NAME}/pages/_layouts/"* ${{ env.ABS_README_DIR }}/_layouts - cat "${GITHUB_REF_NAME}/README.md" "${GITHUB_REF_NAME}/pages/"*.md > ${{ env.PROJ_README_MD }} + mkdir -p ${{ env.ABS_MARKDOWN_DIR }}/_layouts/ + cp -v "${GITHUB_REF_NAME}/pages/_layouts/"* ${{ env.ABS_MARKDOWN_DIR }}/_layouts + cp -v "${GITHUB_REF_NAME}/"*.md ${{ env.ABS_MARKDOWN_DIR }}/ + for md in "${GITHUB_REF_NAME}/pages/"*.md; do + echo >> ${{ env.ABS_MARKDOWN_DIR }}/README.md + cat "${md}" >> ${{ env.ABS_MARKDOWN_DIR }}/README.md + done + cat << EOF > ${{ env.API_README_MD }} [userscript docs](..) ${{ github.workflow }} generation:${{ github.run_number }} - Generated at: $(date -Imin) + Generated at: $(date -Imin) (via ${GITHUB_REF_NAME}) EOF - parallel: @@ -58,9 +62,13 @@ jobs: - name: Jekyll uses: actions/jekyll-build-pages@v1 with: - source: ${{ env.REL_README_DIR }} + source: ${{ env.REL_MARKDOWN_DIR }} destination: ${{ env.REL_JK }} + # - name: Jekyll (installed) + # run: | + # jekyll build --source ${{ env.REL_MARKDOWN_DIR }} --destination ${{ env.REL_JK }} + # We currently build API from two branches, one as a WIP. - name: JSDoc (stable) run: | diff --git a/.jsdoc.json b/.jsdoc.json index fbed2f9..1dcf8d1 100644 --- a/.jsdoc.json +++ b/.jsdoc.json @@ -1,13 +1,13 @@ { "opts": { - "private": true + "private": false }, "source": { - "include": ["demos.user.js", "lib/xunit.js"] + "include": ["lib/base.js"] }, "plugins": ["plugins/markdown"], "markdown": { - "tags": ["todo"] + "tags": ["license", "todo"] }, "templates": { "name": "Nexus Horatio's Userscripts", diff --git a/README.md b/README.md index 870b528..b4c3811 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ -Project on [GitHub](https://github.com/nexushoratio/userscripts) +[Docs](https://nexushoratio.github.io/userscripts) | +[Source](https://github.com/nexushoratio/userscripts) -# userscripts +## About [Userscripts](https://en.wikipedia.org/wiki/Userscript), often called Greasemonkey scripts, are a form of [augmented browsing](https://en.wikipedia.org/wiki/Browser_extension). What I may write will probably be here. diff --git a/demos.user.js b/demos.user.js index bd30177..32b3467 100644 --- a/demos.user.js +++ b/demos.user.js @@ -18,6 +18,12 @@ // @grant GM.getValue // @grant GM.setValue // @grant window.onurlchange +// @require http://localhost:8000/lib/xunit.js?0 +// @require http://localhost:8000/lib/base.js?4035407644 +// @require http://localhost:8000/lib/spa.js?0 +// @require http://localhost:8000/lib/userscript.js?0 +// @require http://localhost:8000/lib/web.js?0 +// @require http://localhost:8000/lib/widget.js?0 // ==/UserScript== /** diff --git a/lib/base.js b/lib/base.js index 0fff636..39fa3d0 100644 --- a/lib/base.js +++ b/lib/base.js @@ -10,6 +10,16 @@ // ==/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() { @@ -69,9 +79,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 +179,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:Dispatcher~Handler * @param {string} eventType - Event type. - * @param {*} data - Event data. + * @param {object} data - Event data. */ /** @@ -201,7 +213,7 @@ window.NexusHoratio.base = (function base() { * * @param {string} eventType - Event type to connect with. * @param {Handler} func - Single argument function to call. - * @returns {Dispatcher} - This instance, for chaining. + * @returns {Dispatcher} This instance, for chaining. */ on(eventType, func) { const handlers = this.#getHandlers(eventType); @@ -214,7 +226,7 @@ window.NexusHoratio.base = (function base() { * * @param {string} eventType - Event type to disconnect from. * @param {Handler} func - Function to remove. - * @returns {Dispatcher} - This instance, for chaining. + * @returns {Dispatcher} This instance, for chaining. */ off(eventType, func) { const handlers = this.#getHandlers(eventType); @@ -230,7 +242,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 {Dispatcher} This instance, for chaining. */ fire(eventType, data) { if (this.enabled) { @@ -249,9 +261,9 @@ 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 + * @returns {Handler[]} Handlers currently registered for this * eventType. */ #getHandlers = (eventType) => { @@ -494,6 +506,7 @@ window.NexusHoratio.base = (function base() { * A simple message system that will queue messages to be delivered. * * This is similar to the WEB API's `MessageChannel`. + * @static */ class MessageQueue { @@ -503,8 +516,8 @@ window.NexusHoratio.base = (function base() { } /** - * @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 +526,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, listener is removed. + * @returns {module:base.MessageQueue} This instance, for chaining. */ listen(func) { if (func) { @@ -682,6 +695,7 @@ window.NexusHoratio.base = (function base() { * The existence of the valueOf(), toString() and toJSON() methods will * probably allow this class to work in many situations through type * coercion. + * @ignore */ class NumberOp { @@ -690,24 +704,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 +730,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); @@ -824,12 +838,14 @@ window.NexusHoratio.base = (function base() { * * The factory function may take arguments. When `.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 +859,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 +875,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) { @@ -1039,17 +1057,18 @@ 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. */ @@ -1101,7 +1120,7 @@ window.NexusHoratio.base = (function base() { /** * Attach a function for all config change events. * - * @param {ConfigMutationHandler} callback - Function that receives + * @param {module:base.Logger~ConfigMutationHandler} callback - Function that receives * events. */ static onConfig(callback) { @@ -1113,7 +1132,7 @@ window.NexusHoratio.base = (function base() { /** * Remove all instances of a function attached for config change events. * - * @param {ConfigMutationHandler} callback - Function that receives + * @param {module:base.Logger~ConfigMutationHandler} callback - Function that receives * events. */ static offConfig(callback) { @@ -1126,7 +1145,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 {Logger.Config} Current config for that Logger. */ static config(loggerName) { return this.#configs.getWithArguments(loggerName, loggerName); @@ -1153,7 +1172,7 @@ window.NexusHoratio.base = (function base() { return this.#config.includeStackTrace; } - /** @type {MessageQueue} */ + /** @type {module:base.MessageQueue} */ get mq() { return this.#mq; } @@ -1178,7 +1197,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 +1207,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 +1217,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 +1227,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 +1237,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,7 +1245,10 @@ 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'); @@ -1234,7 +1256,7 @@ window.NexusHoratio.base = (function base() { sequence = 0; - /** @type {NumberOp} */ + /** @type {module:base.NumberOp} */ get callCount() { return this.#callCount; } @@ -1250,7 +1272,7 @@ window.NexusHoratio.base = (function base() { this.#fireConfigChange('enabled'); } - /** @type {Map} - Per group settings. */ + /** @type {Map} - Per group settings. */ get groups() { return this.#groups; } @@ -1268,8 +1290,8 @@ 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 {Logger.GroupMode} mode - Default mode if not seen before. + * @returns {Logger.Group} Requested group, perhaps newly made. */ group(groupName, mode) { const defaultMode = mode ?? 'opened'; @@ -1295,7 +1317,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(), @@ -1361,7 +1383,8 @@ window.NexusHoratio.base = (function base() { static #Group = class { /** - * @param {Logger.#GroupMode} mode - Initial mode for this group. + * @alias module:base.Logger~Group + * @param {Logger.GroupMode} mode - Initial mode for this group. * @param {string} logger - Name of associated {@link Logger} instance. * @param {string} group - Name of this group. */ @@ -1373,17 +1396,17 @@ window.NexusHoratio.base = (function base() { this.#fireConfigChange('group'); } - /** @type {NumberOp} */ + /** @type {module:base.NumberOp} */ get callCount() { return this.#callCount; } - /** @type {Logger.#GroupMode} */ + /** @type {Logger.GroupMode} */ get mode() { return this.#mode; } - /** @param {Logger.#GroupMode} val - Mode to set this group. */ + /** @param {Logger.GroupMode} val - Mode to set this group. */ set mode(val) { let newVal = val; if (!(newVal instanceof Logger.#GroupMode)) { @@ -1395,7 +1418,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 +1452,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,7 +1478,7 @@ window.NexusHoratio.base = (function base() { * Find GroupMode by name. * * @param {string} modeName - Mode name. - * @returns {GroupMode} - Mode, if found. + * @returns {GroupMode} Mode, if found. */ static byName(modeName) { return this.#known.get(modeName); @@ -1534,7 +1559,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 +1610,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 +1629,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 {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 +1650,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; @@ -1952,8 +1977,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 +2018,7 @@ window.NexusHoratio.base = (function base() { /** * Basic test runner. * - * This depends on {Logger}, hence the location in this file. + * This depends on {module:base.Logger}, hence the location in this file. */ function runTests() { if (NH.xunit.testing.enabled) { @@ -2017,8 +2042,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 +2053,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 +2120,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 +2171,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 +2235,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 = []; @@ -2427,7 +2457,7 @@ window.NexusHoratio.base = (function base() { * .on('deactivated', dummyEventCallback); * service.activate(); * service.deactivate(); - * + * @static */ class Service { @@ -2439,7 +2469,7 @@ window.NexusHoratio.base = (function base() { this.setShortName(shortName); } - /** @type {Logger} - Logger instance. */ + /** @type {module:base.Logger} - Logger instance. */ get logger() { return this.#logger; } @@ -2482,9 +2512,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 + * @param {module:base.Dispatcher~Handler} func - Single argument function to * call. - * @returns {Service} - This instance, for chaining. + * @returns {module:base.Service} This instance, for chaining. */ on(eventType, func) { this.#dispatcher.on(eventType, func); @@ -2495,8 +2525,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 +2536,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 +2545,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 +2578,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)