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>
This commit is contained in:
Wolfsblvt
2026-04-20 21:29:40 +02:00
committed by GitHub
parent e5d4ff5fae
commit 4df18ccb0b
7 changed files with 58 additions and 7 deletions
+1
View File
@@ -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,
+19 -5
View File
@@ -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';
},
}));
+31 -2
View File
@@ -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<HTMLElement>|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}
+1
View File
@@ -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,
@@ -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,
+1
View File
@@ -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,
});