Refactor extension slash commands into own file - Weird circle imports again with the slash command classes

169504aa68b243178050bccaaef63c5ef7e29f27

Wolfsblvt <wolfsblvt@gmail.com>

3 files changed, +281 -277Ignore whitespace
public/script.js+2 -0
@@ -244,6 +244,7 @@ import { commonEnumProviders, enumIcons } from './scripts/slash-commands/SlashCo
244import { initInputMarkdown } from './scripts/input-md-formatting.js';244import { initInputMarkdown } from './scripts/input-md-formatting.js';
245import { AbortReason } from './scripts/util/AbortReason.js';245import { AbortReason } from './scripts/util/AbortReason.js';
246import { initSystemPrompts } from './scripts/sysprompt.js';246import { initSystemPrompts } from './scripts/sysprompt.js';
247import { registerExtensionSlashCommands } from './scripts/extensions-slashcommands.js';
247248
248//exporting functions and vars for mods249//exporting functions and vars for mods
249export {250export {
@@ -957,6 +958,7 @@ async function firstLoadInit() {
957 initLogprobs();958 initLogprobs();
958 initInputMarkdown();959 initInputMarkdown();
959 initExtensions();960 initExtensions();
961 registerExtensionSlashCommands();
960 doDailyExtensionUpdatesCheck();962 doDailyExtensionUpdatesCheck();
961 await hideLoader();963 await hideLoader();
962 await fixViewport();964 await fixViewport();
public/scripts/extensions-slashcommands.js+276 -0
@@ -0,0 +1,276 @@
1import { disableExtension, enableExtension, extension_settings, extensionNames } from './extensions.js';
2import { SlashCommand } from './slash-commands/SlashCommand.js';
3import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
4import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
5import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
6import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
7import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
8import { equalsIgnoreCaseAndAccents, isFalseBoolean, isTrueBoolean } from './utils.js';
9
10/**
11 * @param {'enable' | 'disable' | 'toggle'} action - The action to perform on the extension
12 * @returns {(args: {[key: string]: string | SlashCommandClosure}, extensionName: string | SlashCommandClosure) => Promise<string>}
13 */
14function getExtensionActionCallback(action) {
15 return async (args, extensionName) => {
16 if (args?.reload instanceof SlashCommandClosure) throw new Error('\'reload\' argument cannot be a closure.');
17 if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
18 if (!extensionName) {
19 toastr.warning(`Extension name must be provided as an argument to ${action} this extension.`);
20 return '';
21 }
22
23 const reload = isTrueBoolean(args?.reload);
24 const internalExtensionName = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName));
25 if (!internalExtensionName) {
26 toastr.warning(`Extension ${extensionName} does not exist.`);
27 return '';
28 }
29
30 const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
31
32 if (action === 'enable' && isEnabled) {
33 toastr.info(`Extension ${extensionName} is already enabled.`);
34 return internalExtensionName;
35 }
36
37 if (action === 'disable' && !isEnabled) {
38 toastr.info(`Extension ${extensionName} is already disabled.`);
39 return internalExtensionName;
40 }
41
42 if (action === 'toggle') {
43 action = isEnabled ? 'disable' : 'enable';
44 }
45
46 reload && toastr.info(`${action.charAt(0).toUpperCase() + action.slice(1)}ing extension ${extensionName} and reloading...`);
47
48 if (action === 'enable') {
49 await enableExtension(internalExtensionName, reload);
50 } else {
51 await disableExtension(internalExtensionName, reload);
52 }
53
54 toastr.success(`Extension ${extensionName} ${action}d.`);
55
56 return internalExtensionName;
57 };
58}
59
60export function registerExtensionSlashCommands() {
61 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
62 name: 'extension-enable',
63 callback: getExtensionActionCallback('enable'),
64 namedArgumentList: [
65 SlashCommandNamedArgument.fromProps({
66 name: 'reload',
67 description: 'Whether to reload the page after enabling the extension',
68 typeList: [ARGUMENT_TYPE.BOOLEAN],
69 defaultValue: 'true',
70 enumList: commonEnumProviders.boolean('trueFalse')(),
71 }),
72 ],
73 unnamedArgumentList: [
74 SlashCommandArgument.fromProps({
75 description: 'Extension name',
76 typeList: [ARGUMENT_TYPE.STRING],
77 isRequired: true,
78 enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
79 forceEnum: true,
80 }),
81 ],
82 helpString: `
83 <div>
84 Enables a specified extension.
85 </div>
86 <div>
87 By default, the page will be reloaded automatically, stopping any further commands.<br />
88 If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay disabled until refreshed.
89 The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
90 </div>
91 <div>
92 <strong>Example:</strong>
93 <ul>
94 <li>
95 <pre><code class="language-stscript">/extension-enable Summarize</code></pre>
96 </li>
97 </ul>
98 </div>
99 `,
100 }));
101 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
102 name: 'extension-disable',
103 callback: getExtensionActionCallback('enable'),
104 namedArgumentList: [
105 SlashCommandNamedArgument.fromProps({
106 name: 'reload',
107 description: 'Whether to reload the page after disabling the extension',
108 typeList: [ARGUMENT_TYPE.BOOLEAN],
109 defaultValue: 'true',
110 enumList: commonEnumProviders.boolean('trueFalse')(),
111 }),
112 ],
113 unnamedArgumentList: [
114 SlashCommandArgument.fromProps({
115 description: 'Extension name',
116 typeList: [ARGUMENT_TYPE.STRING],
117 isRequired: true,
118 enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
119 forceEnum: true,
120 }),
121 ],
122 helpString: `
123 <div>
124 Disables a specified extension.
125 </div>
126 <div>
127 By default, the page will be reloaded automatically, stopping any further commands.<br />
128 If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay enabled until refreshed.
129 The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
130 </div>
131 <div>
132 <strong>Example:</strong>
133 <ul>
134 <li>
135 <pre><code class="language-stscript">/extension-disable Summarize</code></pre>
136 </li>
137 </ul>
138 </div>
139 `,
140 }));
141 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
142 name: 'extension-toggle',
143 callback: async (args, extensionName) => {
144 if (args?.state instanceof SlashCommandClosure) throw new Error('\'state\' argument cannot be a closure.');
145 if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
146
147 const action = isTrueBoolean(args?.state) ? 'enable' :
148 isFalseBoolean(args?.state) ? 'disable' :
149 'toggle';
150
151 return await getExtensionActionCallback(action)(args, extensionName);
152 },
153 namedArgumentList: [
154 SlashCommandNamedArgument.fromProps({
155 name: 'reload',
156 description: 'Whether to reload the page after toggling the extension',
157 typeList: [ARGUMENT_TYPE.BOOLEAN],
158 defaultValue: 'true',
159 enumList: commonEnumProviders.boolean('trueFalse')(),
160 }),
161 SlashCommandNamedArgument.fromProps({
162 name: 'state',
163 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.',
164 typeList: [ARGUMENT_TYPE.BOOLEAN],
165 enumList: commonEnumProviders.boolean('trueFalse')(),
166 }),
167 ],
168 unnamedArgumentList: [
169 SlashCommandArgument.fromProps({
170 description: 'Extension name',
171 typeList: [ARGUMENT_TYPE.STRING],
172 isRequired: true,
173 enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
174 forceEnum: true,
175 }),
176 ],
177 helpString: `
178 <div>
179 Toggles the state of a specified extension.
180 </div>
181 <div>
182 By default, the page will be reloaded automatically, stopping any further commands.<br />
183 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.
184 The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
185 </div>
186 <div>
187 <strong>Example:</strong>
188 <ul>
189 <li>
190 <pre><code class="language-stscript">/extension-toggle Summarize</code></pre>
191 </li>
192 <li>
193 <pre><code class="language-stscript">/extension-toggle Summarize state=true</code></pre>
194 </li>
195 </ul>
196 </div>
197 `,
198 }));
199 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
200 name: 'extension-state',
201 callback: async (_, extensionName) => {
202 if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
203 const internalExtensionName = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName));
204 if (!internalExtensionName) {
205 toastr.warning(`Extension ${extensionName} does not exist.`);
206 return '';
207 }
208
209 const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
210 return String(isEnabled);
211 },
212 unnamedArgumentList: [
213 SlashCommandArgument.fromProps({
214 description: 'Extension name',
215 typeList: [ARGUMENT_TYPE.STRING],
216 isRequired: true,
217 enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
218 forceEnum: true,
219 }),
220 ],
221 helpString: `
222 <div>
223 Returns the state of a specified extension (true if enabled, false if disabled).
224 </div>
225 <div>
226 <strong>Example:</strong>
227 <ul>
228 <li>
229 <pre><code class="language-stscript">/extension-state Summarize</code></pre>
230 </li>
231 </ul>
232 </div>
233 `,
234 }));
235 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
236 name: 'extension-exists',
237 aliases: ['extension-installed'],
238 callback: async (_, extensionName) => {
239 if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
240 const exists = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName)) !== undefined;
241 return exists ? 'true' : 'false';
242 },
243 unnamedArgumentList: [
244 SlashCommandArgument.fromProps({
245 description: 'Extension name',
246 typeList: [ARGUMENT_TYPE.STRING],
247 isRequired: true,
248 enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
249 forceEnum: true,
250 }),
251 ],
252 helpString: `
253 <div>
254 Checks if a specified extension exists.
255 </div>
256 <div>
257 <strong>Example:</strong>
258 <ul>
259 <li>
260 <pre><code class="language-stscript">/extension-exists LALib</code></pre>
261 </li>
262 </ul>
263 </div>
264 `,
265 }));
266
267 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
268 name: 'reload-page',
269 callback: async () => {
270 toastr.info('Reloading the page...');
271 location.reload();
272 return '';
273 },
274 helpString: 'Reloads the current page. All further commands will not be processed.',
275 }));
276}
public/scripts/extensions.js+3 -277
@@ -1,14 +1,8 @@
1import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';1import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';
2import { showLoader } from './loader.js';2import { showLoader } from './loader.js';
3import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';3import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
4import { SlashCommand } from './slash-commands/SlashCommand.js';
5import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
6import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
7import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
8import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
9import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
10import { renderTemplate, renderTemplateAsync } from './templates.js';4import { renderTemplate, renderTemplateAsync } from './templates.js';
11import { equalsIgnoreCaseAndAccents, isFalseBoolean, isSubsetOf, isTrueBoolean, setValueByPath } from './utils.js';5import { isSubsetOf, setValueByPath } from './utils.js';
12export {6export {
13 getContext,7 getContext,
14 getApiUrl,8 getApiUrl,
@@ -249,7 +243,7 @@ function onEnableExtensionClick() {
249 enableExtension(name, false);243 enableExtension(name, false);
250}244}
251245
252async function enableExtension(name, reload = true) {246export async function enableExtension(name, reload = true) {
253 extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name);247 extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name);
254 stateChanged = true;248 stateChanged = true;
255 await saveSettings();249 await saveSettings();
@@ -260,7 +254,7 @@ async function enableExtension(name, reload = true) {
260 }254 }
261}255}
262256
263async function disableExtension(name, reload = true) {257export async function disableExtension(name, reload = true) {
264 extension_settings.disabledExtensions.push(name);258 extension_settings.disabledExtensions.push(name);
265 stateChanged = true;259 stateChanged = true;
266 await saveSettings();260 await saveSettings();
@@ -1049,277 +1043,9 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
1049 await installExtension(url);1043 await installExtension(url);
1050}1044}
10511045
1052/**
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>}
1055 */
1056function getExtensionActionCallback(action) {
1057 return async (args, extensionName) => {
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 must be a string. Closures or arrays are not allowed.');
1060 if (!extensionName) {
1061 toastr.warning(`Extension name must be provided as an argument to ${action} this extension.`);
1062 return '';
1063 }
1064
1065 const reload = isTrueBoolean(args?.reload);
1066 const internalExtensionName = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName));
1067 if (!internalExtensionName) {
1068 toastr.warning(`Extension ${extensionName} does not exist.`);
1069 return '';
1070 }
1071
1072 const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
1073
1074 if (action === 'enable' && isEnabled) {
1075 toastr.info(`Extension ${extensionName} is already enabled.`);
1076 return internalExtensionName;
1077 }
1078
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') {
1091 await enableExtension(internalExtensionName, reload);
1092 } else {
1093 await disableExtension(internalExtensionName, reload);
1094 }
1095
1096 toastr.success(`Extension ${extensionName} ${action}d.`);
1097
1098 return internalExtensionName;
1099 };
1100}
1101
1102function registerExtensionSlashCommands() {
1103 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1104 name: 'extension-enable',
1105 callback: getExtensionActionCallback('enable'),
1106 namedArgumentList: [
1107 SlashCommandNamedArgument.fromProps({
1108 name: 'reload',
1109 description: 'Whether to reload the page after enabling the extension',
1110 typeList: [ARGUMENT_TYPE.BOOLEAN],
1111 defaultValue: 'true',
1112 enumList: commonEnumProviders.boolean('trueFalse')(),
1113 }),
1114 ],
1115 unnamedArgumentList: [
1116 SlashCommandArgument.fromProps({
1117 description: 'Extension name',
1118 typeList: [ARGUMENT_TYPE.STRING],
1119 isRequired: true,
1120 enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
1121 forceEnum: true,
1122 }),
1123 ],
1124 helpString: `
1125 <div>
1126 Enables a specified extension.
1127 </div>
1128 <div>
1129 By default, the page will be reloaded automatically, stopping any further commands.<br />
1130 If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay disabled until refreshed.
1131 The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
1132 </div>
1133 <div>
1134 <strong>Example:</strong>
1135 <ul>
1136 <li>
1137 <pre><code class="language-stscript">/extension-enable Summarize</code></pre>
1138 </li>
1139 </ul>
1140 </div>
1141 `,
1142 }));
1143 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1144 name: 'extension-disable',
1145 callback: getExtensionActionCallback('enable'),
1146 namedArgumentList: [
1147 SlashCommandNamedArgument.fromProps({
1148 name: 'reload',
1149 description: 'Whether to reload the page after disabling the extension',
1150 typeList: [ARGUMENT_TYPE.BOOLEAN],
1151 defaultValue: 'true',
1152 enumList: commonEnumProviders.boolean('trueFalse')(),
1153 }),
1154 ],
1155 unnamedArgumentList: [
1156 SlashCommandArgument.fromProps({
1157 description: 'Extension name',
1158 typeList: [ARGUMENT_TYPE.STRING],
1159 isRequired: true,
1160 enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
1161 forceEnum: true,
1162 }),
1163 ],
1164 helpString: `
1165 <div>
1166 Disables a specified extension.
1167 </div>
1168 <div>
1169 By default, the page will be reloaded automatically, stopping any further commands.<br />
1170 If <code>reload=false</code> named argument is passed, the page will not be reloaded, and the extension will stay enabled until refreshed.
1171 The page either needs to be refreshed, or <code>/reload-page</code> has to be called.
1172 </div>
1173 <div>
1174 <strong>Example:</strong>
1175 <ul>
1176 <li>
1177 <pre><code class="language-stscript">/extension-disable Summarize</code></pre>
1178 </li>
1179 </ul>
1180 </div>
1181 `,
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 must be a 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 }));
1241 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1242 name: 'extension-state',
1243 callback: async (_, extensionName) => {
1244 if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
1245 const internalExtensionName = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName));
1246 if (!internalExtensionName) {
1247 toastr.warning(`Extension ${extensionName} does not exist.`);
1248 return '';
1249 }
12501046
1251 const isEnabled = !extension_settings.disabledExtensions.includes(internalExtensionName);
1252 return String(isEnabled);
1253 },
1254 unnamedArgumentList: [
1255 SlashCommandArgument.fromProps({
1256 description: 'Extension name',
1257 typeList: [ARGUMENT_TYPE.STRING],
1258 isRequired: true,
1259 enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
1260 forceEnum: true,
1261 }),
1262 ],
1263 helpString: `
1264 <div>
1265 Returns the state of a specified extension (true if enabled, false if disabled).
1266 </div>
1267 <div>
1268 <strong>Example:</strong>
1269 <ul>
1270 <li>
1271 <pre><code class="language-stscript">/extension-state Summarize</code></pre>
1272 </li>
1273 </ul>
1274 </div>
1275 `,
1276 }));
1277 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1278 name: 'extension-exists',
1279 aliases: ['extension-installed'],
1280 callback: async (_, extensionName) => {
1281 if (typeof extensionName !== 'string') throw new Error('Extension name must be a string. Closures or arrays are not allowed.');
1282 const exists = extensionNames.find(x => equalsIgnoreCaseAndAccents(x, extensionName)) !== undefined;
1283 return exists ? 'true' : 'false';
1284 },
1285 unnamedArgumentList: [
1286 SlashCommandArgument.fromProps({
1287 description: 'Extension name',
1288 typeList: [ARGUMENT_TYPE.STRING],
1289 isRequired: true,
1290 enumProvider: () => extensionNames.map(name => new SlashCommandEnumValue(name)),
1291 forceEnum: true,
1292 }),
1293 ],
1294 helpString: `
1295 <div>
1296 Checks if a specified extension exists.
1297 </div>
1298 <div>
1299 <strong>Example:</strong>
1300 <ul>
1301 <li>
1302 <pre><code class="language-stscript">/extension-exists LALib</code></pre>
1303 </li>
1304 </ul>
1305 </div>
1306 `,
1307 }));
1308
1309 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1310 name: 'reload-page',
1311 callback: async () => {
1312 toastr.info('Reloading the page...');
1313 location.reload();
1314 return '';
1315 },
1316 helpString: 'Reloads the current page. All further commands will not be processed.',
1317 }));
1318}
13191047
1320export async function initExtensions() {1048export async function initExtensions() {
1321 registerExtensionSlashCommands();
1322
1323 await addExtensionsButtonAndMenu();1049 await addExtensionsButtonAndMenu();
1324 $('#extensionsMenuButton').css('display', 'flex');1050 $('#extensionsMenuButton').css('display', 'flex');
13251051