Blame Raw
Cohee · e3f41666 · · 437 lines (24.7 KB)
1 contributor
1import { hljs } from '../../lib.js';
2import { t } from '../i18n.js';
3import { SlashCommandAbortController } from './SlashCommandAbortController.js';
4import { SlashCommandArgument, SlashCommandNamedArgument } from './SlashCommandArgument.js';
5import { SlashCommandClosure } from './SlashCommandClosure.js';
6import { SlashCommandDebugController } from './SlashCommandDebugController.js';
7import { SlashCommandScope } from './SlashCommandScope.js';
8
9/**
10 * @typedef {NamedArgumentsCapture & {
11 * _scope:SlashCommandScope,
12 * _parserFlags:import('./SlashCommandParser.js').ParserFlags,
13 * _abortController:SlashCommandAbortController,
14 * _debugController:SlashCommandDebugController,
15 * _hasUnnamedArgument:boolean,
16 * }} NamedArguments
17 */
18
19/**
20 * Alternative object for local JSDocs, where you don't need existing pipe, scope, etc. arguments
21 * @typedef {{[id:string]:string|SlashCommandClosure|(string|SlashCommandClosure)[]|undefined}} NamedArgumentsCapture
22 */
23
24/**
25 * @typedef {string|SlashCommandClosure|(string|SlashCommandClosure)[]} UnnamedArguments
26*/
27
28
29export class SlashCommand {
30 /**
31 * Creates a SlashCommand from a properties object.
32 * @param {Object} props
33 * @param {string} [props.name]
34 * @param {(namedArguments:NamedArguments|NamedArgumentsCapture, unnamedArguments:string|SlashCommandClosure|(string|SlashCommandClosure)[])=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>} [props.callback]
35 * @param {string} [props.helpString]
36 * @param {boolean} [props.splitUnnamedArgument]
37 * @param {Number} [props.splitUnnamedArgumentCount]
38 * @param {boolean} [props.rawQuotes] If set to true, does not remove wrapping quotes from the unnamed argument.
39 * @param {string[]} [props.aliases]
40 * @param {string} [props.returns]
41 * @param {SlashCommandNamedArgument[]} [props.namedArgumentList]
42 * @param {SlashCommandArgument[]} [props.unnamedArgumentList]
43 */
44 static fromProps(props) {
45 const instance = Object.assign(new this(), props);
46 return instance;
47 }
48
49
50 /**@type {string}*/ name;
51 /**@type {(namedArguments:NamedArguments, unnamedArguments:UnnamedArguments)=>string|SlashCommandClosure|Promise<string|SlashCommandClosure>}*/ callback;
52 /**@type {string}*/ helpString;
53 /**@type {boolean}*/ splitUnnamedArgument = false;
54 /**@type {Number}*/ splitUnnamedArgumentCount;
55 /** @type {boolean} */ rawQuotes = false;
56 /**@type {string[]}*/ aliases = [];
57 /**@type {string}*/ returns;
58 /**@type {SlashCommandNamedArgument[]}*/ namedArgumentList = [];
59 /**@type {SlashCommandArgument[]}*/ unnamedArgumentList = [];
60
61 /**@type {Object.<string, HTMLElement>}*/ helpCache = {};
62 /**@type {Object.<string, DocumentFragment>}*/ helpDetailsCache = {};
63
64 /**@type {boolean}*/ isExtension = false;
65 /**@type {boolean}*/ isThirdParty = false;
66 /**@type {string}*/ source;
67
68 renderHelpItem(key = null) {
69 key = key ?? this.name;
70 if (!this.helpCache[key]) {
71 const typeIcon = '[/]';
72 const li = document.createElement('li'); {
73 li.classList.add('item');
74 const type = document.createElement('span'); {
75 type.classList.add('type');
76 type.classList.add('monospace');
77 type.textContent = typeIcon;
78 li.append(type);
79 }
80 const specs = document.createElement('span'); {
81 specs.classList.add('specs');
82 const name = document.createElement('span'); {
83 name.classList.add('name');
84 name.classList.add('monospace');
85 name.textContent = '/';
86 key.split('').forEach(char => {
87 const span = document.createElement('span'); {
88 span.textContent = char;
89 name.append(span);
90 }
91 });
92 specs.append(name);
93 }
94 const body = document.createElement('span'); {
95 body.classList.add('body');
96 const args = document.createElement('span'); {
97 args.classList.add('arguments');
98 for (const arg of this.namedArgumentList) {
99 const argItem = document.createElement('span'); {
100 argItem.classList.add('argument');
101 argItem.classList.add('namedArgument');
102 if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
103 if (arg.acceptsMultiple) argItem.classList.add('multiple');
104 const name = document.createElement('span'); {
105 name.classList.add('argument-name');
106 name.textContent = arg.name;
107 argItem.append(name);
108 }
109 if (arg.enumList.length > 0) {
110 const enums = document.createElement('span'); {
111 enums.classList.add('argument-enums');
112 for (const e of arg.enumList) {
113 const enumItem = document.createElement('span'); {
114 enumItem.classList.add('argument-enum');
115 enumItem.textContent = e.value;
116 enums.append(enumItem);
117 }
118 }
119 argItem.append(enums);
120 }
121 } else {
122 const types = document.createElement('span'); {
123 types.classList.add('argument-types');
124 for (const t of arg.typeList) {
125 const type = document.createElement('span'); {
126 type.classList.add('argument-type');
127 type.textContent = t;
128 types.append(type);
129 }
130 }
131 argItem.append(types);
132 }
133 }
134 args.append(argItem);
135 }
136 }
137 for (const arg of this.unnamedArgumentList) {
138 const argItem = document.createElement('span'); {
139 argItem.classList.add('argument');
140 argItem.classList.add('unnamedArgument');
141 if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
142 if (arg.acceptsMultiple) argItem.classList.add('multiple');
143 if (arg.enumList.length > 0) {
144 const enums = document.createElement('span'); {
145 enums.classList.add('argument-enums');
146 for (const e of arg.enumList) {
147 const enumItem = document.createElement('span'); {
148 enumItem.classList.add('argument-enum');
149 enumItem.textContent = e.value;
150 enums.append(enumItem);
151 }
152 }
153 argItem.append(enums);
154 }
155 } else {
156 const types = document.createElement('span'); {
157 types.classList.add('argument-types');
158 for (const t of arg.typeList) {
159 const type = document.createElement('span'); {
160 type.classList.add('argument-type');
161 type.textContent = t;
162 types.append(type);
163 }
164 }
165 argItem.append(types);
166 }
167 }
168 args.append(argItem);
169 }
170 }
171 body.append(args);
172 }
173 const returns = document.createElement('span'); {
174 returns.classList.add('returns');
175 returns.textContent = this.returns ?? 'void';
176 body.append(returns);
177 }
178 specs.append(body);
179 }
180 li.append(specs);
181 }
182 const stopgap = document.createElement('span'); {
183 stopgap.classList.add('stopgap');
184 stopgap.textContent = '';
185 li.append(stopgap);
186 }
187 const help = document.createElement('span'); {
188 help.classList.add('help');
189 const content = document.createElement('span'); {
190 content.classList.add('helpContent');
191 content.innerHTML = this.helpString;
192 const text = content.textContent;
193 content.innerHTML = '';
194 content.textContent = text;
195 help.append(content);
196 }
197 li.append(help);
198 }
199 if (this.aliases.length > 0) {
200 const aliases = document.createElement('span'); {
201 aliases.classList.add('aliases');
202 aliases.append(' (alias: ');
203 for (const aliasName of this.aliases) {
204 const alias = document.createElement('span'); {
205 alias.classList.add('monospace');
206 alias.textContent = `/${aliasName}`;
207 aliases.append(alias);
208 }
209 }
210 aliases.append(')');
211 // li.append(aliases);
212 }
213 }
214 }
215 this.helpCache[key] = li;
216 }
217 return /**@type {HTMLElement}*/(this.helpCache[key].cloneNode(true));
218 }
219
220 renderHelpDetails(key = null) {
221 key = key ?? this.name;
222 if (!this.helpDetailsCache[key]) {
223 const frag = document.createDocumentFragment();
224 const cmd = this;
225 const namedArguments = cmd.namedArgumentList ?? [];
226 const unnamedArguments = cmd.unnamedArgumentList ?? [];
227 const returnType = cmd.returns ?? 'void';
228 const helpString = cmd.helpString ?? 'NO DETAILS';
229 const aliasList = [cmd.name, ...(cmd.aliases ?? [])].filter(it => it != key);
230 const specs = document.createElement('div'); {
231 specs.classList.add('specs');
232 const head = document.createElement('div'); {
233 head.classList.add('head');
234 const name = document.createElement('div'); {
235 name.classList.add('name');
236 name.classList.add('monospace');
237 name.title = t`Command name`;
238 name.textContent = `/${key}`;
239 head.append(name);
240 }
241 const src = document.createElement('div'); {
242 src.classList.add('source');
243 src.classList.add('fa-solid');
244 if (this.isExtension) {
245 src.classList.add('isExtension');
246 src.classList.add('fa-cubes');
247 if (this.isThirdParty) src.classList.add('isThirdParty');
248 else src.classList.add('isCore');
249 } else {
250 src.classList.add('isCore');
251 src.classList.add('fa-star-of-life');
252 }
253 src.title = [
254 this.isExtension ? 'Extension' : 'Core',
255 this.isThirdParty ? 'Third Party' : (this.isExtension ? 'Core' : null),
256 this.source,
257 ].filter(it => it).join('\n');
258 head.append(src);
259 }
260 if (this.rawQuotes) {
261 const rawQuotes = document.createElement('div'); {
262 rawQuotes.classList.add('rawQuotes');
263 rawQuotes.classList.add('fa-solid');
264 rawQuotes.classList.add('fa-quote-left');
265 rawQuotes.title = t`Does not alter quoted literal unnamed arguments. Pass raw=false argument to override.`;
266 head.append(rawQuotes);
267 }
268 }
269 specs.append(head);
270 }
271 const body = document.createElement('div'); {
272 body.classList.add('body');
273 const args = document.createElement('ul'); {
274 args.classList.add('arguments');
275 for (const arg of namedArguments) {
276 const listItem = document.createElement('li'); {
277 listItem.classList.add('argumentItem');
278 const argSpec = document.createElement('div'); {
279 argSpec.classList.add('argumentSpec');
280 const argItem = document.createElement('div'); {
281 argItem.classList.add('argument');
282 argItem.classList.add('namedArgument');
283 argItem.title = arg.isRequired ? t`Named argument` : t`Optional named argument`;
284 if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
285 if (arg.acceptsMultiple) argItem.classList.add('multiple');
286 const name = document.createElement('span'); {
287 name.classList.add('argument-name');
288 name.title = t`${argItem.title} - Name`;
289 name.textContent = arg.name;
290 argItem.append(name);
291 }
292 if (arg.enumList.length > 0) {
293 const enums = document.createElement('span'); {
294 enums.classList.add('argument-enums');
295 enums.title = t`${argItem.title} - Accepted values`;
296 for (const e of arg.enumList) {
297 const enumItem = document.createElement('span'); {
298 enumItem.classList.add('argument-enum');
299 enumItem.textContent = e.value;
300 enums.append(enumItem);
301 }
302 }
303 argItem.append(enums);
304 }
305 } else {
306 const types = document.createElement('span'); {
307 types.classList.add('argument-types');
308 types.title = t`${argItem.title} - Accepted types`;
309 for (const t of arg.typeList) {
310 const type = document.createElement('span'); {
311 type.classList.add('argument-type');
312 type.textContent = t;
313 types.append(type);
314 }
315 }
316 argItem.append(types);
317 }
318 }
319 argSpec.append(argItem);
320 }
321 if (arg.defaultValue !== null) {
322 const argDefault = document.createElement('div'); {
323 argDefault.classList.add('argument-default');
324 argDefault.title = t`Default value`;
325 argDefault.textContent = arg.defaultValue.toString();
326 argSpec.append(argDefault);
327 }
328 }
329 listItem.append(argSpec);
330 }
331 const desc = document.createElement('div'); {
332 desc.classList.add('argument-description');
333 desc.innerHTML = arg.description;
334 listItem.append(desc);
335 }
336 args.append(listItem);
337 }
338 }
339 for (const arg of unnamedArguments) {
340 const listItem = document.createElement('li'); {
341 listItem.classList.add('argumentItem');
342 const argSpec = document.createElement('div'); {
343 argSpec.classList.add('argumentSpec');
344 const argItem = document.createElement('div'); {
345 argItem.classList.add('argument');
346 argItem.classList.add('unnamedArgument');
347 argItem.title = arg.isRequired ? t`Unnamed argument` : t`Optional unnamed argument`;
348 if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
349 if (arg.acceptsMultiple) argItem.classList.add('multiple');
350 if (arg.enumList.length > 0) {
351 const enums = document.createElement('span'); {
352 enums.classList.add('argument-enums');
353 enums.title = t`${argItem.title} - Accepted values`;
354 for (const e of arg.enumList) {
355 const enumItem = document.createElement('span'); {
356 enumItem.classList.add('argument-enum');
357 enumItem.textContent = e.value;
358 enums.append(enumItem);
359 }
360 }
361 argItem.append(enums);
362 }
363 } else {
364 const types = document.createElement('span'); {
365 types.classList.add('argument-types');
366 types.title = t`${argItem.title} - Accepted types`;
367 for (const t of arg.typeList) {
368 const type = document.createElement('span'); {
369 type.classList.add('argument-type');
370 type.textContent = t;
371 types.append(type);
372 }
373 }
374 argItem.append(types);
375 }
376 }
377 argSpec.append(argItem);
378 }
379 if (arg.defaultValue !== null) {
380 const argDefault = document.createElement('div'); {
381 argDefault.classList.add('argument-default');
382 argDefault.title = t`Default value`;
383 argDefault.textContent = arg.defaultValue.toString();
384 argSpec.append(argDefault);
385 }
386 }
387 listItem.append(argSpec);
388 }
389 const desc = document.createElement('div'); {
390 desc.classList.add('argument-description');
391 desc.innerHTML = arg.description;
392 listItem.append(desc);
393 }
394 args.append(listItem);
395 }
396 }
397 body.append(args);
398 }
399 const returns = document.createElement('span'); {
400 returns.classList.add('returns');
401 returns.title = [null, undefined, 'void'].includes(returnType) ? t`Command does not return anything` : t`Return value`;
402 returns.textContent = returnType ?? 'void';
403 body.append(returns);
404 }
405 specs.append(body);
406 }
407 frag.append(specs);
408 }
409 const help = document.createElement('span'); {
410 help.classList.add('help');
411 help.innerHTML = helpString;
412 for (const code of help.querySelectorAll('pre > code')) {
413 code.classList.add('language-stscript');
414 hljs.highlightElement(/**@type {HTMLElement}*/(code));
415 }
416 frag.append(help);
417 }
418 if (aliasList.length > 0) {
419 const aliases = document.createElement('span'); {
420 aliases.classList.add('aliases');
421 for (const aliasName of aliasList) {
422 const alias = document.createElement('span'); {
423 alias.classList.add('alias');
424 alias.textContent = `/${aliasName}`;
425 aliases.append(alias);
426 }
427 }
428 frag.append(aliases);
429 }
430 }
431 this.helpDetailsCache[key] = frag;
432 }
433 const frag = document.createDocumentFragment();
434 frag.append(this.helpDetailsCache[key].cloneNode(true));
435 return frag;
436 }
437}