Macros 2.0 [➕Macros] - Add {{hasExtension}} macro, and refactor extension lookup logic (#4948)
* Add {{hasExtension}} macro and refactor extension lookup logic
- Move findExtension() from extensions-slashcommands.js to extensions.js and export it
- Update findExtension() to return object with name and enabled properties instead of just name string
- Add {{hasExtension}} macro to check if an extension is enabled
- Update /extension-state and /extension-exists commands to use refactored findExtension()
- Remove duplicate findExtension() implementation from extensions-slashcommands.js
* Refactor extension action callbacks to use extension object instead of separate name and enabled properties
* fix eslint
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { disableExtension, enableExtension, extension_settings, extensionNames } from './extensions.js';
|
||||
import { disableExtension, enableExtension, extensionNames, findExtension } from './extensions.js';
|
||||
import { SlashCommand } from './slash-commands/SlashCommand.js';
|
||||
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
|
||||
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
|
||||
import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
|
||||
import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
|
||||
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
|
||||
import { equalsIgnoreCaseAndAccents, isFalseBoolean, isTrueBoolean } from './utils.js';
|
||||
import { isFalseBoolean, isTrueBoolean } from './utils.js';
|
||||
|
||||
/**
|
||||
* @param {'enable' | 'disable' | 'toggle'} action - The action to perform on the extension
|
||||
@@ -22,30 +22,28 @@ function getExtensionActionCallback(action) {
|
||||
}
|
||||
|
||||
const reload = !isFalseBoolean(args?.reload?.toString());
|
||||
const internalExtensionName = findExtension(extensionName);
|
||||
if (!internalExtensionName) {
|
||||
const extension = findExtension(extensionName);
|
||||
if (!extension) {
|
||||
toastr.warning(`Extension ${extensionName} does not exist.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
|
||||
|
||||
if (action === 'enable' && isEnabled) {
|
||||
toastr.info(`Extension ${extensionName} is already enabled.`);
|
||||
return internalExtensionName;
|
||||
if (action === 'enable' && extension.enabled) {
|
||||
toastr.info(`Extension ${extension.name} is already enabled.`);
|
||||
return extension.name;
|
||||
}
|
||||
|
||||
if (action === 'disable' && !isEnabled) {
|
||||
toastr.info(`Extension ${extensionName} is already disabled.`);
|
||||
return internalExtensionName;
|
||||
if (action === 'disable' && !extension.enabled) {
|
||||
toastr.info(`Extension ${extension.name} is already disabled.`);
|
||||
return extension.name;
|
||||
}
|
||||
|
||||
if (action === 'toggle') {
|
||||
action = isEnabled ? 'disable' : 'enable';
|
||||
action = extension.enabled ? 'disable' : 'enable';
|
||||
}
|
||||
|
||||
if (reload) {
|
||||
toastr.info(`${action.charAt(0).toUpperCase() + action.slice(1)}ing extension ${extensionName} and reloading...`);
|
||||
toastr.info(`${action.charAt(0).toUpperCase() + action.slice(1)}ing extension ${extension.name} and reloading...`);
|
||||
|
||||
// Clear input, so it doesn't stay because the command didn't "finish",
|
||||
// and wait for a bit to both show the toast and let the clear bubble through.
|
||||
@@ -54,35 +52,23 @@ function getExtensionActionCallback(action) {
|
||||
}
|
||||
|
||||
if (action === 'enable') {
|
||||
await enableExtension(internalExtensionName, reload);
|
||||
await enableExtension(extension.name, reload);
|
||||
} else {
|
||||
await disableExtension(internalExtensionName, reload);
|
||||
await disableExtension(extension.name, reload);
|
||||
}
|
||||
|
||||
toastr.success(`Extension ${extensionName} ${action}d.`);
|
||||
toastr.success(`Extension ${extension.name} ${action}d.`);
|
||||
|
||||
|
||||
console.info(`Extension ${action}ed: ${extensionName}`);
|
||||
console.info(`Extension ${action}ed: ${extension.name}`);
|
||||
if (!reload) {
|
||||
console.info('Reload not requested, so page needs to be reloaded manually for changes to take effect.');
|
||||
}
|
||||
|
||||
return internalExtensionName;
|
||||
return extension.name;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an extension by name, allowing omission of the "third-party/" prefix.
|
||||
*
|
||||
* @param {string} name - The name of the extension to find
|
||||
* @returns {string?} - The matched extension name or undefined if not found
|
||||
*/
|
||||
function findExtension(name) {
|
||||
return extensionNames.find(extName => {
|
||||
return equalsIgnoreCaseAndAccents(extName, name) || equalsIgnoreCaseAndAccents(extName, `third-party/${name}`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides an array of SlashCommandEnumValue objects based on the extension names.
|
||||
* Each object contains the name of the extension and a description indicating if it is a third-party extension.
|
||||
@@ -244,14 +230,13 @@ export function registerExtensionSlashCommands() {
|
||||
name: 'extension-state',
|
||||
callback: async (_, extensionName) => {
|
||||
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
|
||||
const internalExtensionName = findExtension(extensionName);
|
||||
if (!internalExtensionName) {
|
||||
const extension = findExtension(extensionName);
|
||||
if (!extension) {
|
||||
toastr.warning(`Extension ${extensionName} does not exist.`);
|
||||
return '';
|
||||
}
|
||||
|
||||
const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
|
||||
return String(isEnabled);
|
||||
return String(extension.enabled);
|
||||
},
|
||||
returns: 'The state of the extension, whether it is enabled.',
|
||||
unnamedArgumentList: [
|
||||
@@ -282,8 +267,8 @@ export function registerExtensionSlashCommands() {
|
||||
aliases: ['extension-installed'],
|
||||
callback: async (_, extensionName) => {
|
||||
if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
|
||||
const exists = findExtension(extensionName) !== undefined;
|
||||
return exists ? 'true' : 'false';
|
||||
const extension = findExtension(extensionName);
|
||||
return extension !== null ? 'true' : 'false';
|
||||
},
|
||||
returns: 'Whether the extension exists and is installed.',
|
||||
unnamedArgumentList: [
|
||||
|
||||
@@ -4,7 +4,7 @@ import { eventSource, event_types, saveSettings, saveSettingsDebounced, getReque
|
||||
import { showLoader } from './loader.js';
|
||||
import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
|
||||
import { renderTemplate, renderTemplateAsync } from './templates.js';
|
||||
import { delay, isSubsetOf, sanitizeSelector, setValueByPath, versionCompare } from './utils.js';
|
||||
import { delay, equalsIgnoreCaseAndAccents, isSubsetOf, sanitizeSelector, setValueByPath, versionCompare } from './utils.js';
|
||||
import { getContext } from './st-context.js';
|
||||
import { isAdmin } from './user.js';
|
||||
import { addLocaleData, getCurrentLocale, t } from './i18n.js';
|
||||
@@ -331,6 +331,21 @@ export async function disableExtension(name, reload = true) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an extension by name, allowing omission of the "third-party/" prefix.
|
||||
*
|
||||
* @param {string} name - The name of the extension to find
|
||||
* @returns {{name: string, enabled: boolean}|null} Object with name and enabled properties, or null if not found
|
||||
*/
|
||||
export function findExtension(name) {
|
||||
const internalExtensionName = extensionNames.find(extName => {
|
||||
return equalsIgnoreCaseAndAccents(extName, name) || equalsIgnoreCaseAndAccents(extName, `third-party/${name}`);
|
||||
});
|
||||
if (!internalExtensionName) return null;
|
||||
const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
|
||||
return { name: internalExtensionName, enabled: isEnabled };
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads manifest.json files for extensions.
|
||||
* @param {string[]} names Array of extension names
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { MacroRegistry, MacroCategory } from '../engine/MacroRegistry.js';
|
||||
import { eventSource, event_types } from '../../events.js';
|
||||
import { findExtension } from '/scripts/extensions.js';
|
||||
|
||||
let lastGenerationTypeValue = '';
|
||||
let lastGenerationTypeTrackingInitialized = false;
|
||||
@@ -37,4 +38,20 @@ export function registerStateMacros() {
|
||||
returns: 'Type of the last queued generation request.',
|
||||
handler: () => lastGenerationTypeValue,
|
||||
});
|
||||
|
||||
// Macro that checks if an extension is enabled
|
||||
MacroRegistry.registerMacro('hasExtension', {
|
||||
category: MacroCategory.STATE,
|
||||
unnamedArgs: [{
|
||||
name: 'extensionName',
|
||||
type: 'string',
|
||||
description: 'The name of the extension to check',
|
||||
}],
|
||||
description: 'Checks if a specific extension is enabled. If the extension does not exist, returns false.',
|
||||
returns: 'true if the extension is enabled, false otherwise.',
|
||||
handler: ({ unnamedArgs: [extensionName] }) => {
|
||||
const extension = findExtension(extensionName);
|
||||
return String(extension?.enabled ?? false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user