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>

4df18ccb0bb4ee2a0cf28c6060cb2fdad58f6add

Wolfsblvt <wolfsblvt@gmail.com>

Signed
7 files changed, +58 -7Ignore whitespace
public/script.js+3 -0
@@ -717,6 +717,7 @@ async function firstLoadInit() {
717717 initLoaderOverlay.appendChild(splashMessage);
718718
719719 const initLoaderHandle = loader.show({
720+ slug: 'app-init',
720721 toastMode: loader.ToastMode.NONE,
721722 overlayContent: initLoaderOverlay,
722723 });
@@ -10592,6 +10593,7 @@ export async function renameGroupOrCharacterChat({ characterId, groupId, oldFile
1059210593 }
1059310594
1059410595 const loaderHandle = showLoader ? loader.show({
10596+ slug: 'chat-rename',
1059510597 title: t`Rename Chat`,
1059610598 message: t`Renaming chat…`,
1059710599 toastMode: loader.ToastMode.STATIC,
@@ -11196,6 +11198,7 @@ jQuery(async function () {
1119611198 $('#select_chat_cross').trigger('click');
1119711199
1119811200 const loaderHandle = loader.show({
11201+ slug: 'chat-delete',
1119911202 title: t`Delete Chat`,
1120011203 message: t`Deleting chat…`,
1120111204 toastMode: loader.ToastMode.STATIC,
public/scripts/BulkEditOverlay.js+1 -0
@@ -848,6 +848,7 @@ class BulkEditOverlay {
848848 const deleteChats = checkbox.prop('checked') ?? false;
849849
850850 const loaderHandle = loader.show({
851+ slug: 'bulk-delete',
851852 title: t`Bulk Delete`,
852853 message: t`Deleting ${characterIds.length} character(s)…`,
853854 toastMode: loader.ToastMode.STATIC,
public/scripts/action-loader-slashcommands.js+19 -5
@@ -1,4 +1,4 @@
11import { ActionLoaderToastMode, getActiveLoaderHandles, getLoaderHandleById, hideActionLoader, showActionLoaderloader } from './action-loader.js';
22import { t } from './i18n.js';
33import { SlashCommand } from './slash-commands/SlashCommand.js';
44import { SlashCommandNamedArgument, ARGUMENT_TYPE, SlashCommandArgument } from './slash-commands/SlashCommandArgument.js';
@@ -116,6 +116,12 @@ export function registerActionLoaderSlashCommands() {
116116 typeList: [ARGUMENT_TYPE.STRING],
117117 }),
118118 SlashCommandNamedArgument.fromProps({
119+ name: 'slug',
120+ description: 'Unique slug for the loader (to identify it easily via code or CSS)',
121+ typeList: [ARGUMENT_TYPE.STRING],
122+ defaultValue: 'slash-wrap',
123+ }),
124+ SlashCommandNamedArgument.fromProps({
119125 name: 'stopTooltip',
120126 description: 'Tooltip text for the stop button (only used when toast=stoppable)',
121127 typeList: [ARGUMENT_TYPE.STRING],
@@ -148,7 +154,8 @@ export function registerActionLoaderSlashCommands() {
148154 const title = args.title ? String(args.title) : '';
149155 const stopTooltip = String(args.stopTooltip ?? t`Stop`);
150156
151157 const loaderactionLoader = showActionLoaderloader.show({
158+ slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-wrap',
152159 blocking,
153160 toastMode,
154161 message,
@@ -162,7 +169,7 @@ export function registerActionLoaderSlashCommands() {
162169 const result = await closureCopy.execute();
163170 return result.pipe;
164171 } finally {
165172 await loaderactionLoader.hide();
166173 }
167174 },
168175 }));
@@ -232,6 +239,12 @@ export function registerActionLoaderSlashCommands() {
232239 typeList: [ARGUMENT_TYPE.STRING],
233240 }),
234241 SlashCommandNamedArgument.fromProps({
242+ name: 'slug',
243+ description: 'Unique slug for the loader (to identify it easily via code or CSS)',
244+ typeList: [ARGUMENT_TYPE.STRING],
245+ defaultValue: 'slash-show',
246+ }),
247+ SlashCommandNamedArgument.fromProps({
235248 name: 'stopTooltip',
236249 description: 'Tooltip text for the stop button (only used when toast=stoppable)',
237250 typeList: [ARGUMENT_TYPE.STRING],
@@ -258,7 +271,8 @@ export function registerActionLoaderSlashCommands() {
258271 const title = args.title ? String(args.title) : '';
259272 const stopTooltip = String(args.stopTooltip ?? t`Stop`);
260273
261274 const handle = showActionLoaderloader.show({
275+ slug: typeof args.slug === 'string' ? String(args.slug) : 'slash-show',
262276 blocking,
263277 toastMode,
264278 message,
@@ -307,7 +321,7 @@ export function registerActionLoaderSlashCommands() {
307321 }
308322
309323 // No handle provided - hide all active loaders
310324 const result = await hideActionLoaderloader.hide();
311325 return result ? 'true' : 'false';
312326 },
313327 }));
public/scripts/action-loader.js+31 -2
@@ -33,6 +33,7 @@ export const ActionLoaderToastMode = {
3333 * @typedef {object} ActionLoaderOptions
3434 * @property {boolean} [blocking=true] - Whether to show the blocking overlay. Set to false for non-blocking toast-only loaders.
3535 * @property {ActionLoaderToastMode} [toastMode='stoppable'] - Toast display mode
36+ * @property {string} [slug=null] - Unique slug for the loader to identify it easily via code or CSS
3637 * @property {string} [message='Generating...'] - The message to display in the toast
3738 * @property {string} [title] - Optional title for the toast notification
3839 * @property {string} [stopTooltip='Stop'] - Tooltip text for the stop button
@@ -83,7 +84,10 @@ export class ActionLoaderHandle {
8384 }
8485
8586 /** @type {string} Unique identifier for this handle */
8687 #id;
88+
89+ /** @type {string|null} Unique slug for the loader */
90+ #slug = null;
8791
8892 /** @type {JQuery<HTMLElement>|null} The toast element for this loader */
8993 #toast = null;
@@ -105,6 +109,7 @@ export class ActionLoaderHandle {
105109 * @param {object} options - Configuration options
106110 * @param {boolean} [options.blocking=true] - Whether to show blocking overlay
107111 * @param {ActionLoaderToastMode} [options.toastMode] - Toast display mode
112+ * @param {string|null} [options.slug] - Unique slug for the loader (to identify it easily via code or CSS)
108113 * @param {string} [options.message='Generating...'] - Message to display in the toast
109114 * @param {string} [options.title] - Title for the toast notification
110115 * @param {string} [options.stopTooltip='Stop'] - Tooltip for the stop button
@@ -116,6 +121,7 @@ export class ActionLoaderHandle {
116121 constructor({
117122 blocking = true,
118123 toastMode = ActionLoaderToastMode.STOPPABLE,
124+ slug = null,
119125 message = t`Generating...`,
120126 title = '',
121127 stopTooltip = t`Stop`,
@@ -129,7 +135,8 @@ export class ActionLoaderHandle {
129135 return;
130136 }
131137
132138 this.#id = generateLoaderId();
139+ this.#slug = slug;
133140 this.#blocking = blocking;
134141 this.#onStop = onStop;
135142 this.#onHide = onHide;
@@ -164,6 +171,12 @@ export class ActionLoaderHandle {
164171 const toastContent = document.createElement('div');
165172 toastContent.className = 'action-loader-toast';
166173
174+ if (this.#slug) {
175+ toastContent.dataset.slug = this.#slug;
176+ }
177+ toastContent.dataset.loaderId = this.#id;
178+ toastContent.dataset.blocking = this.#blocking.toString();
179+
167180 const messageSpan = document.createElement('span');
168181 messageSpan.className = 'action-loader-message';
169182 messageSpan.textContent = message;
@@ -218,6 +231,22 @@ export class ActionLoaderHandle {
218231 }
219232
220233 /**
234+ * The unique identifier for this loader handle.
235+ * @returns {string}
236+ */
237+ get id() {
238+ return this.#id;
239+ }
240+
241+ /**
242+ * The unique slug for this loader handle, used to identify it easily via code or CSS.
243+ * @returns {string|null}
244+ */
245+ get slug() {
246+ return this.#slug;
247+ }
248+
249+ /**
221250 * Whether this handle is still active (not disposed).
222251 * @returns {boolean}
223252 */
public/scripts/bookmarks.js+1 -0
@@ -700,6 +700,7 @@ export function initBookmarks() {
700700 }
701701
702702 const loaderHandle = loader.show({
703+ slug: 'chat-load',
703704 title: t`Chat History`,
704705 message: t`Loading chat…`,
705706 toastMode: loader.ToastMode.STATIC,
public/scripts/extensions/stable-diffusion/index.js+2 -0
@@ -3061,6 +3061,7 @@ async function generatePicture(initiator, args, trigger, message, callback) {
30613061 // Show non-blocking stoppable toast for this generation
30623062 loaderHandle = loader.show({
30633063 blocking: false,
3064+ slug: `${MODULE_NAME}-image-generation`,
30643065 title: t`Image Generation`,
30653066 message: t`Generating an image...`,
30663067 onStop: stopListener,
@@ -5300,6 +5301,7 @@ async function generateMediaSwipe(mediaAttachment, message, onStart, onComplete,
53005301 // Show non-blocking stoppable toast for this generation
53015302 loaderHandle = loader.show({
53025303 blocking: false,
5304+ slug: `${MODULE_NAME}-image-generation`,
53035305 title: t`Image Generation`,
53045306 message: t`Generating an image...`,
53055307 onStop: stopListener,
public/scripts/loader.js+1 -0
@@ -28,6 +28,7 @@ export function showLoader() {
2828
2929 // Create a blocking loader with no toast (matches old behavior)
3030 legacyLoaderHandle = loader.show({
31+ slug: 'legacy-loader',
3132 blocking: true,
3233 toastMode: loader.ToastMode.NONE,
3334 });