From 4df18ccb0bb4ee2a0cf28c6060cb2fdad58f6add Mon Sep 17 00:00:00 2001 From: Wolfsblvt Date: Mon, 20 Apr 2026 21:29:40 +0200 Subject: [PATCH] Add Slug Parameter to Action Loader for Programmatic Identification (#5490) * feat: add slug parameter to action-loader for programmatic identification Add optional `slug` parameter to ActionLoaderHandle for easier identification via code or CSS. Update all loader.show() calls across the codebase to include descriptive slugs ('app-init', 'chat-rename', 'chat-delete', 'bulk-delete', 'chat-load', 'image-generation', 'legacy-loader'). Add data attributes (data-slug, data-loader-id, data-blocking) to toast content div. Expose slug via getter and make id private with getter. * Apply suggestions from code review Fix slug jsdoc wording Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: Add identifier to second loader in img gen --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com> --- public/script.js | 3 ++ public/scripts/BulkEditOverlay.js | 1 + public/scripts/action-loader-slashcommands.js | 24 +++++++++++--- public/scripts/action-loader.js | 33 +++++++++++++++++-- public/scripts/bookmarks.js | 1 + .../extensions/stable-diffusion/index.js | 2 ++ public/scripts/loader.js | 1 + 7 files changed, 58 insertions(+), 7 deletions(-) diff --git a/public/script.js b/public/script.js index 185431c15..2620fe858 100644 --- a/public/script.js +++ b/public/script.js @@ -717,6 +717,7 @@ async function firstLoadInit() { initLoaderOverlay.appendChild(splashMessage); const initLoaderHandle = loader.show({ + slug: 'app-init', toastMode: loader.ToastMode.NONE, overlayContent: initLoaderOverlay, }); @@ -10592,6 +10593,7 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile } const loaderHandle = showLoader ? loader.show({ + slug: 'chat-rename', title: t`Rename Chat`, message: t`Renaming chat…`, toastMode: loader.ToastMode.STATIC, @@ -11196,6 +11198,7 @@ jQuery(async function () { $('#select_chat_cross').trigger('click'); const loaderHandle = loader.show({ + slug: 'chat-delete', title: t`Delete Chat`, message: t`Deleting chat…`, toastMode: loader.ToastMode.STATIC, diff --git a/public/scripts/BulkEditOverlay.js b/public/scripts/BulkEditOverlay.js index 0ae6cf8d5..a43780cc2 100644 --- a/public/scripts/BulkEditOverlay.js +++ b/public/scripts/BulkEditOverlay.js @@ -848,6 +848,7 @@ class BulkEditOverlay { const deleteChats = checkbox.prop('checked') ?? false; const loaderHandle = loader.show({ + slug: 'bulk-delete', title: t`Bulk Delete`, message: t`Deleting ${characterIds.length} character(s)…`, toastMode: loader.ToastMode.STATIC, diff --git a/public/scripts/action-loader-slashcommands.js b/public/scripts/action-loader-slashcommands.js index 65d986e07..ee0666778 100644 --- a/public/scripts/action-loader-slashcommands.js +++ b/public/scripts/action-loader-slashcommands.js @@ -1,4 +1,4 @@ -import { ActionLoaderToastMode, getActiveLoaderHandles, getLoaderHandleById, hideActionLoader, showActionLoader } from './action-loader.js'; +import { ActionLoaderToastMode, getActiveLoaderHandles, getLoaderHandleById, loader } from './action-loader.js'; import { t } from './i18n.js'; import { SlashCommand } from './slash-commands/SlashCommand.js'; import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js'; @@ -115,6 +115,12 @@ export function registerActionLoaderSlashCommands() { description: 'Optional title for the toast notification', typeList: [ARGUMENT_TYPE.STRING], }), + SlashCommandNamedArgument.fromProps({ + name: 'slug', + description: 'Unique slug for the loader (to identify it easily via code or CSS)', + typeList: [ARGUMENT_TYPE.STRING], + defaultValue: 'slash-wrap', + }), SlashCommandNamedArgument.fromProps({ name: 'stopTooltip', description: 'Tooltip text for the stop button (only used when toast=stoppable)', @@ -148,7 +154,8 @@ export function registerActionLoaderSlashCommands() { const title = args.title ? String(args.title) : ''; const stopTooltip = String(args.stopTooltip ?? t`Stop`); - const loader = showActionLoader({ + const actionLoader = loader.show({ + slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-wrap', blocking, toastMode, message, @@ -162,7 +169,7 @@ export function registerActionLoaderSlashCommands() { const result = await closureCopy.execute(); return result.pipe; } finally { - await loader.hide(); + await actionLoader.hide(); } }, })); @@ -231,6 +238,12 @@ export function registerActionLoaderSlashCommands() { description: 'Optional title for the toast notification', typeList: [ARGUMENT_TYPE.STRING], }), + SlashCommandNamedArgument.fromProps({ + name: 'slug', + description: 'Unique slug for the loader (to identify it easily via code or CSS)', + typeList: [ARGUMENT_TYPE.STRING], + defaultValue: 'slash-show', + }), SlashCommandNamedArgument.fromProps({ name: 'stopTooltip', description: 'Tooltip text for the stop button (only used when toast=stoppable)', @@ -258,7 +271,8 @@ export function registerActionLoaderSlashCommands() { const title = args.title ? String(args.title) : ''; const stopTooltip = String(args.stopTooltip ?? t`Stop`); - const handle = showActionLoader({ + const handle = loader.show({ + slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-show', blocking, toastMode, message, @@ -307,7 +321,7 @@ export function registerActionLoaderSlashCommands() { } // No handle provided - hide all active loaders - const result = await hideActionLoader(); + const result = await loader.hide(); return result ? 'true' : 'false'; }, })); diff --git a/public/scripts/action-loader.js b/public/scripts/action-loader.js index cc5575edb..2108a9a77 100644 --- a/public/scripts/action-loader.js +++ b/public/scripts/action-loader.js @@ -33,6 +33,7 @@ export const ActionLoaderToastMode = { * @typedef {object} ActionLoaderOptions * @property {boolean} [blocking=true] - Whether to show the blocking overlay. Set to false for non-blocking toast-only loaders. * @property {ActionLoaderToastMode} [toastMode='stoppable'] - Toast display mode + * @property {string} [slug=null] - Unique slug for the loader to identify it easily via code or CSS * @property {string} [message='Generating...'] - The message to display in the toast * @property {string} [title] - Optional title for the toast notification * @property {string} [stopTooltip='Stop'] - Tooltip text for the stop button @@ -83,7 +84,10 @@ export class ActionLoaderHandle { } /** @type {string} Unique identifier for this handle */ - id; + #id; + + /** @type {string|null} Unique slug for the loader */ + #slug = null; /** @type {JQuery|null} The toast element for this loader */ #toast = null; @@ -105,6 +109,7 @@ export class ActionLoaderHandle { * @param {object} options - Configuration options * @param {boolean} [options.blocking=true] - Whether to show blocking overlay * @param {ActionLoaderToastMode} [options.toastMode] - Toast display mode + * @param {string|null} [options.slug] - Unique slug for the loader (to identify it easily via code or CSS) * @param {string} [options.message='Generating...'] - Message to display in the toast * @param {string} [options.title] - Title for the toast notification * @param {string} [options.stopTooltip='Stop'] - Tooltip for the stop button @@ -116,6 +121,7 @@ export class ActionLoaderHandle { constructor({ blocking = true, toastMode = ActionLoaderToastMode.STOPPABLE, + slug = null, message = t`Generating...`, title = '', stopTooltip = t`Stop`, @@ -129,7 +135,8 @@ export class ActionLoaderHandle { return; } - this.id = generateLoaderId(); + this.#id = generateLoaderId(); + this.#slug = slug; this.#blocking = blocking; this.#onStop = onStop; this.#onHide = onHide; @@ -164,6 +171,12 @@ export class ActionLoaderHandle { const toastContent = document.createElement('div'); toastContent.className = 'action-loader-toast'; + if (this.#slug) { + toastContent.dataset.slug = this.#slug; + } + toastContent.dataset.loaderId = this.#id; + toastContent.dataset.blocking = this.#blocking.toString(); + const messageSpan = document.createElement('span'); messageSpan.className = 'action-loader-message'; messageSpan.textContent = message; @@ -217,6 +230,22 @@ export class ActionLoaderHandle { } } + /** + * The unique identifier for this loader handle. + * @returns {string} + */ + get id() { + return this.#id; + } + + /** + * The unique slug for this loader handle, used to identify it easily via code or CSS. + * @returns {string|null} + */ + get slug() { + return this.#slug; + } + /** * Whether this handle is still active (not disposed). * @returns {boolean} diff --git a/public/scripts/bookmarks.js b/public/scripts/bookmarks.js index 50865c840..550bfd05c 100644 --- a/public/scripts/bookmarks.js +++ b/public/scripts/bookmarks.js @@ -700,6 +700,7 @@ export function initBookmarks() { } const loaderHandle = loader.show({ + slug: 'chat-load', title: t`Chat History`, message: t`Loading chat…`, toastMode: loader.ToastMode.STATIC, diff --git a/public/scripts/extensions/stable-diffusion/index.js b/public/scripts/extensions/stable-diffusion/index.js index 6cae4a058..1e49a18bf 100644 --- a/public/scripts/extensions/stable-diffusion/index.js +++ b/public/scripts/extensions/stable-diffusion/index.js @@ -3061,6 +3061,7 @@ async function generatePicture(initiator, args, trigger, message, callback) { // Show non-blocking stoppable toast for this generation loaderHandle = loader.show({ blocking: false, + slug: `${MODULE_NAME}-image-generation`, title: t`Image Generation`, message: t`Generating an image...`, onStop: stopListener, @@ -5300,6 +5301,7 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete, // Show non-blocking stoppable toast for this generation loaderHandle = loader.show({ blocking: false, + slug: `${MODULE_NAME}-image-generation`, title: t`Image Generation`, message: t`Generating an image...`, onStop: stopListener, diff --git a/public/scripts/loader.js b/public/scripts/loader.js index a5a3706ac..ab3abbe01 100644 --- a/public/scripts/loader.js +++ b/public/scripts/loader.js @@ -28,6 +28,7 @@ export function showLoader() { // Create a blocking loader with no toast (matches old behavior) legacyLoaderHandle = loader.show({ + slug: 'legacy-loader', blocking: true, toastMode: loader.ToastMode.NONE, });