Add /extension-toggle
| @@ -8,7 +8,7 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom | |||
| 8 | import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; | 8 | import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; |
| 9 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | 9 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 10 | import { renderTemplate, renderTemplateAsync } from './templates.js'; | 10 | import { renderTemplate, renderTemplateAsync } from './templates.js'; |
| 11 | import { equalsIgnoreCaseAndAccents, isSubsetOf, isTrueBoolean, setValueByPath } from './utils.js'; | 11 | import { equalsIgnoreCaseAndAccents, isFalseBoolean, isSubsetOf, isTrueBoolean, setValueByPath } from './utils.js'; |
| 12 | export { | 12 | export { |
| 13 | getContext, | 13 | getContext, |
| 14 | getApiUrl, | 14 | getApiUrl, |
| @@ -1050,33 +1050,50 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') { | |||
| 1050 | } | 1050 | } |
| 1051 | 1051 | ||
| 1052 | /** | 1052 | /** |
| 1053 | * @param {boolean} enable - Whether to enable or disable the extension | 1053 | * @param {'enable' | 'disable' | 'toggle'} action - The action to perform on the extension |
| 1054 | * @returns {(args: {[key: string]: string | SlashCommandClosure}, extensionName: string | SlashCommandClosure) => Promise<string>} | 1054 | * @returns {(args: {[key: string]: string | SlashCommandClosure}, extensionName: string | SlashCommandClosure) => Promise<string>} |
| 1055 | */ | 1055 | */ |
| 1056 | function getExtensionToggleCallback(enable) { | 1056 | function getExtensionActionCallback(action) { |
| 1057 | return async (args, extensionName) => { | 1057 | return async (args, extensionName) => { |
| 1058 | if (args?.reload instanceof SlashCommandClosure) throw new Error('\'reload\' argument cannot be a closure.'); | 1058 | if (args?.reload instanceof SlashCommandClosure) throw new Error('\'reload\' argument cannot be a closure.'); |
| 1059 | if (typeof extensionName !== 'string') throw new Error('Extension name does only support string. Closures or arrays are not allowed.'); | 1059 | if (typeof extensionName !== 'string') throw new Error('Extension name does only support string. Closures or arrays are not allowed.'); |
| 1060 | if (!extensionName) { | 1060 | if (!extensionName) { |
| 1061 | toastr.warning(`Extension name must be provided as an argument to ${enable ? 'enable' : 'disable'} this extension.`); | 1061 | toastr.warning(`Extension name must be provided as an argument to ${action} this extension.`); |
| 1062 | return ''; | 1062 | return ''; |
| 1063 | } | 1063 | } |
| 1064 | 1064 | ||
| 1065 | const reload = isTrueBoolean(args?.reload); | 1065 | const reload = isTrueBoolean(args?.reload); |
| 1066 | |||
| 1067 | const internalExtensionName = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName)); | 1066 | const internalExtensionName = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName)); |
| 1068 | if (!internalExtensionName) { | 1067 | if (!internalExtensionName) { |
| 1069 | toastr.warning(`Extension ${extensionName} does not exist.`); | 1068 | toastr.warning(`Extension ${extensionName} does not exist.`); |
| 1070 | return ''; | 1069 | return ''; |
| 1071 | } | 1070 | } |
| 1072 | if (enable === !extension_settings.disabledExtensions.includes(internalExtensionName)) { | 1071 | |
| 1073 | toastr.info(`Extension ${extensionName} is already ${enable ? 'enabled' : 'disabled'}.`); | 1072 | const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName); |
| 1073 | |||
| 1074 | if (action === 'enable' && isEnabled) { | ||
| 1075 | toastr.info(`Extension ${extensionName} is already enabled.`); | ||
| 1074 | return internalExtensionName; | 1076 | return internalExtensionName; |
| 1075 | } | 1077 | } |
| 1076 | 1078 | ||
| 1077 | reload && toastr.info(`${enable ? 'Enabling' : 'Disabling'} extension ${extensionName} and reloading...`); | 1079 | if (action === 'disable' && !isEnabled) { |
| 1080 | toastr.info(`Extension ${extensionName} is already disabled.`); | ||
| 1081 | return internalExtensionName; | ||
| 1082 | } | ||
| 1083 | |||
| 1084 | if (action === 'toggle') { | ||
| 1085 | action = isEnabled ? 'disable' : 'enable'; | ||
| 1086 | } | ||
| 1087 | |||
| 1088 | reload && toastr.info(`${action.charAt(0).toUpperCase() + action.slice(1)}ing extension ${extensionName} and reloading...`); | ||
| 1089 | |||
| 1090 | if (action === 'enable') { | ||
| 1078 | await enableExtension(internalExtensionName, reload); | 1091 | await enableExtension(internalExtensionName, reload); |
| 1079 | toastr.success(`Extension ${extensionName} ${enable ? 'enabled' : 'disabled'}.`); | 1092 | } else { |
| 1093 | await disableExtension(internalExtensionName, reload); | ||
| 1094 | } | ||
| 1095 | |||
| 1096 | toastr.success(`Extension ${extensionName} ${action}d.`); | ||
| 1080 | 1097 | ||
| 1081 | return internalExtensionName; | 1098 | return internalExtensionName; |
| 1082 | }; | 1099 | }; |
| @@ -1085,7 +1102,7 @@ function getExtensionToggleCallback(enable) { | |||
| 1085 | function registerExtensionSlashCommands() { | 1102 | function registerExtensionSlashCommands() { |
| 1086 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 1103 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1087 | name: 'extension-enable', | 1104 | name: 'extension-enable', |
| 1088 | callback: getExtensionToggleCallback(true), | 1105 | callback: getExtensionActionCallback('enable'), |
| 1089 | namedArgumentList: [ | 1106 | namedArgumentList: [ |
| 1090 | SlashCommandNamedArgument.fromProps({ | 1107 | SlashCommandNamedArgument.fromProps({ |
| 1091 | name: 'reload', | 1108 | name: 'reload', |
| @@ -1125,7 +1142,7 @@ function registerExtensionSlashCommands() { | |||
| 1125 | })); | 1142 | })); |
| 1126 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 1143 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1127 | name: 'extension-disable', | 1144 | name: 'extension-disable', |
| 1128 | callback: getExtensionToggleCallback(false), | 1145 | callback: getExtensionActionCallback('enable'), |
| 1129 | namedArgumentList: [ | 1146 | namedArgumentList: [ |
| 1130 | SlashCommandNamedArgument.fromProps({ | 1147 | SlashCommandNamedArgument.fromProps({ |
| 1131 | name: 'reload', | 1148 | name: 'reload', |
| @@ -1163,6 +1180,64 @@ function registerExtensionSlashCommands() { | |||
| 1163 | </div> | 1180 | </div> |
| 1164 | `, | 1181 | `, |
| 1165 | })); | 1182 | })); |
| 1183 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | ||
| 1184 | name: 'extension-toggle', | ||
| 1185 | callback: async (args, extensionName) => { | ||
| 1186 | if (args?.state instanceof SlashCommandClosure) throw new Error('\'state\' argument cannot be a closure.'); | ||
| 1187 | if (typeof extensionName !== 'string') throw new Error('Extension name does only support string. Closures or arrays are not allowed.'); | ||
| 1188 | |||
| 1189 | const action = isTrueBoolean(args?.state) ? 'enable' : | ||
| 1190 | isFalseBoolean(args?.state) ? 'disable' : | ||
| 1191 | 'toggle'; | ||
| 1192 | |||
| 1193 | return await getExtensionActionCallback(action)(args, extensionName); | ||
| 1194 | }, | ||
| 1195 | namedArgumentList: [ | ||
| 1196 | SlashCommandNamedArgument.fromProps({ | ||
| 1197 | name: 'reload', | ||
| 1198 | description: 'Whether to reload the page after toggling the extension', | ||
| 1199 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 1200 | defaultValue: 'true', | ||
| 1201 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 1202 | }), | ||
| 1203 | SlashCommandNamedArgument.fromProps({ | ||
| 1204 | name: 'state', | ||
| 1205 | description: 'Explicitly set the state of the extension (true to enable, false to disable). If not provided, the state will be toggled to the opposite of the current state.', | ||
| 1206 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 1207 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 1208 | }), | ||
| 1209 | ], | ||
| 1210 | unnamedArgumentList: [ | ||
| 1211 | SlashCommandArgument.fromProps({ | ||
| 1212 | description: 'Extension name', | ||
| 1213 | typeList: [ARGUMENT_TYPE.STRING], | ||
| 1214 | isRequired: true, | ||
| 1215 | enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)), | ||
| 1216 | forceEnum: true, | ||
| 1217 | }), | ||
| 1218 | ], | ||
| 1219 | helpString: ` | ||
| 1220 | <div> | ||
| 1221 | Toggles the state of a specified extension. | ||
| 1222 | </div> | ||
| 1223 | <div> | ||
| 1224 | By default, the page will be reloaded automatically, stopping any further commands.<br /> | ||
| 1225 | If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay in its current state until refreshed. | ||
| 1226 | The page either needs to be refreshed, or <code>/reload-page</code> has to be called. | ||
| 1227 | </div> | ||
| 1228 | <div> | ||
| 1229 | <strong>Example:</strong> | ||
| 1230 | <ul> | ||
| 1231 | <li> | ||
| 1232 | <pre><code class="language-stscript">/extension-toggle Summarize</code></pre> | ||
| 1233 | </li> | ||
| 1234 | <li> | ||
| 1235 | <pre><code class="language-stscript">/extension-toggle Summarize state=true</code></pre> | ||
| 1236 | </li> | ||
| 1237 | </ul> | ||
| 1238 | </div> | ||
| 1239 | `, | ||
| 1240 | })); | ||
| 1166 | 1241 | ||
| 1167 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | 1242 | SlashCommandParser.addCommandObject(SlashCommand.fromProps({ |
| 1168 | name: 'reload-page', | 1243 | name: 'reload-page', |