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
244244import { initInputMarkdown } from './scripts/input-md-formatting.js';
245245import { AbortReason } from './scripts/util/AbortReason.js';
246246import { initSystemPrompts } from './scripts/sysprompt.js';
247+import { registerExtensionSlashCommands } from './scripts/extensions-slashcommands.js';
247248
248249//exporting functions and vars for mods
249250export {
@@ -957,6 +958,7 @@ async function firstLoadInit() {
957958 initLogprobs();
958959 initInputMarkdown();
959960 initExtensions();
961+ registerExtensionSlashCommands();
960962 doDailyExtensionUpdatesCheck();
961963 await hideLoader();
962964 await fixViewport();
public/scripts/extensions-slashcommands.js+276 -0
@@ -0,0 +1,276 @@
1+import { disableExtension, enableExtension, extension_settings, extensionNames } from './extensions.js';
2+import { SlashCommand } from './slash-commands/SlashCommand.js';
3+import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
4+import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
5+import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
6+import { SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
7+import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
8+import { 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+ */
14+function 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+
60+export 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 @@
11import { eventSource, event_types, saveSettings, saveSettingsDebounced, getRequestHeaders, animation_duration } from '../script.js';
22import { showLoader } from './loader.js';
33import { POPUP_RESULT, POPUP_TYPE, Popup, callGenericPopup } from './popup.js';
4-import { SlashCommand } from './slash-commands/SlashCommand.js';
5-import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
6-import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
7-import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
8-import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
9-import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
104import { renderTemplate, renderTemplateAsync } from './templates.js';
115import { equalsIgnoreCaseAndAccents, isFalseBoolean, isSubsetOf, isTrueBoolean, setValueByPath } from './utils.js';
126export {
137 getContext,
148 getApiUrl,
@@ -249,7 +243,7 @@ function onEnableExtensionClick() {
249243 enableExtension(name, false);
250244}
251245
252246export async function enableExtension(name, reload = true) {
253247 extension_settings.disabledExtensions = extension_settings.disabledExtensions.filter(x => x !== name);
254248 stateChanged = true;
255249 await saveSettings();
@@ -260,7 +254,7 @@ async function enableExtension(name, reload = true) {
260254 }
261255}
262256
263257export async function disableExtension(name, reload = true) {
264258 extension_settings.disabledExtensions.push(name);
265259 stateChanged = true;
266260 await saveSettings();
@@ -1049,277 +1043,9 @@ export async function openThirdPartyExtensionMenu(suggestUrl = '') {
10491043 await installExtension(url);
10501044}
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- */
1056-function 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-
1102-function 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
13201048export async function initExtensions() {
1321- registerExtensionSlashCommands();
1322-
13231049 await addExtensionsButtonAndMenu();
13241050 $('#extensionsMenuButton').css('display', 'flex');
13251051