Blame Raw
Cohee · e3f41666 · · 238 lines (10.6 KB)
1 contributor
1import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js';
2
3
4export class AutoCompleteOption {
5 /** @type {string} */ name;
6 /** @type {string} */ typeIcon;
7 /** @type {string} */ type;
8 /** @type {number} */ nameOffset = 0;
9 /** @type {AutoCompleteFuzzyScore} */ score;
10 /** @type {string} */ replacer;
11 /** @type {HTMLElement} */ dom;
12 /** @type {(input:string)=>boolean} */ matchProvider;
13 /** @type {(input:string)=>string} */ valueProvider;
14 /** @type {boolean} */ makeSelectable = false;
15 /** @type {boolean} */ forceFullNameMatch = false;
16
17 /**
18 * Offset to adjust the replacement start position.
19 * Negative values start replacement earlier (e.g., -2 to include 2 chars before normal start).
20 * Used by closing tag autocomplete to replace leading whitespace.
21 * @type {number}
22 */
23 replacementStartOffset = 0;
24
25 /**
26 * Priority for sorting. Lower values = higher priority (sorted first).
27 * Default is 100 (normal priority). Use lower values for items that should appear at the top.
28 * @type {number}
29 */
30 sortPriority = 100;
31
32
33 /**
34 * Used as a comparison value when removing duplicates (e.g., when a SlashCommand has aliases).
35 * @type {any}
36 * */
37 get value() {
38 return this.name;
39 }
40
41 get isSelectable() {
42 return this.makeSelectable || !this.valueProvider;
43 }
44
45
46 /**
47 * @param {string} name
48 */
49 constructor(name, typeIcon = ' ', type = '', matchProvider = null, valueProvider = null, makeSelectable = false) {
50 this.name = name;
51 this.typeIcon = typeIcon;
52 this.type = type;
53 this.matchProvider = matchProvider;
54 this.valueProvider = valueProvider;
55 this.makeSelectable = makeSelectable;
56 }
57
58
59 makeItem(key, typeIcon, noSlash, namedArguments = [], unnamedArguments = [], returnType = 'void', helpString = '', aliasList = []) {
60 const li = document.createElement('li'); {
61 li.classList.add('item');
62 const type = document.createElement('span'); {
63 type.classList.add('type');
64 type.classList.add('monospace');
65 type.textContent = typeIcon;
66 li.append(type);
67 }
68 const specs = document.createElement('span'); {
69 specs.classList.add('specs');
70 const name = document.createElement('span'); {
71 name.classList.add('name');
72 name.classList.add('monospace');
73 name.textContent = noSlash ? '' : '/';
74 key.split('').forEach(char => {
75 const span = document.createElement('span'); {
76 span.textContent = char;
77 name.append(span);
78 }
79 });
80 specs.append(name);
81 }
82 const body = document.createElement('span'); {
83 body.classList.add('body');
84 const args = document.createElement('span'); {
85 args.classList.add('arguments');
86 for (const arg of namedArguments) {
87 const argItem = document.createElement('span'); {
88 argItem.classList.add('argument');
89 argItem.classList.add('namedArgument');
90 if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
91 if (arg.acceptsMultiple) argItem.classList.add('multiple');
92 const name = document.createElement('span'); {
93 name.classList.add('argument-name');
94 name.textContent = arg.name;
95 argItem.append(name);
96 }
97 if (arg.enumList.length > 0) {
98 const enums = document.createElement('span'); {
99 enums.classList.add('argument-enums');
100 for (const e of arg.enumList) {
101 const enumItem = document.createElement('span'); {
102 enumItem.classList.add('argument-enum');
103 enumItem.textContent = e;
104 enums.append(enumItem);
105 }
106 }
107 argItem.append(enums);
108 }
109 } else {
110 const types = document.createElement('span'); {
111 types.classList.add('argument-types');
112 for (const t of arg.typeList) {
113 const type = document.createElement('span'); {
114 type.classList.add('argument-type');
115 type.textContent = t;
116 types.append(type);
117 }
118 }
119 argItem.append(types);
120 }
121 }
122 args.append(argItem);
123 }
124 }
125 for (const arg of unnamedArguments) {
126 const argItem = document.createElement('span'); {
127 argItem.classList.add('argument');
128 argItem.classList.add('unnamedArgument');
129 if (!arg.isRequired || (arg.defaultValue ?? false)) argItem.classList.add('optional');
130 if (arg.acceptsMultiple) argItem.classList.add('multiple');
131 if (arg.enumList.length > 0) {
132 const enums = document.createElement('span'); {
133 enums.classList.add('argument-enums');
134 for (const e of arg.enumList) {
135 const enumItem = document.createElement('span'); {
136 enumItem.classList.add('argument-enum');
137 enumItem.textContent = e;
138 enums.append(enumItem);
139 }
140 }
141 argItem.append(enums);
142 }
143 } else {
144 const types = document.createElement('span'); {
145 types.classList.add('argument-types');
146 for (const t of arg.typeList) {
147 const type = document.createElement('span'); {
148 type.classList.add('argument-type');
149 type.textContent = t;
150 types.append(type);
151 }
152 }
153 argItem.append(types);
154 }
155 }
156 args.append(argItem);
157 }
158 }
159 body.append(args);
160 }
161 const returns = document.createElement('span'); {
162 returns.classList.add('returns');
163 returns.textContent = returnType ?? 'void';
164 // body.append(returns);
165 }
166 specs.append(body);
167 }
168 li.append(specs);
169 }
170 const stopgap = document.createElement('span'); {
171 stopgap.classList.add('stopgap');
172 stopgap.textContent = '';
173 li.append(stopgap);
174 }
175 const help = document.createElement('span'); {
176 help.classList.add('help');
177 const content = document.createElement('span'); {
178 content.classList.add('helpContent');
179 content.innerHTML = helpString;
180 const text = content.textContent;
181 content.innerHTML = '';
182 content.textContent = text;
183 help.append(content);
184 }
185 li.append(help);
186 }
187 if (aliasList.length > 0) {
188 const aliases = document.createElement('span'); {
189 aliases.classList.add('aliases');
190 aliases.append(' (alias: ');
191 for (const aliasName of aliasList) {
192 const alias = document.createElement('span'); {
193 alias.classList.add('monospace');
194 alias.textContent = `/${aliasName}`;
195 aliases.append(alias);
196 }
197 }
198 aliases.append(')');
199 // li.append(aliases);
200 }
201 }
202 }
203 return li;
204 }
205
206
207 /**
208 * @returns {HTMLElement}
209 */
210 renderItem() {
211 // throw new Error(`${this.constructor.name}.renderItem() is not implemented`);
212 let li;
213 li = this.makeItem(this.name, this.typeIcon, true);
214 li.setAttribute('data-name', this.name);
215 li.setAttribute('data-option-type', this.type);
216 return li;
217 }
218
219
220 /**
221 * @returns {DocumentFragment}
222 */
223 renderDetails() {
224 // throw new Error(`${this.constructor.name}.renderDetails() is not implemented`);
225 const frag = document.createDocumentFragment();
226 const specs = document.createElement('div'); {
227 specs.classList.add('specs');
228 const name = document.createElement('div'); {
229 name.classList.add('name');
230 name.classList.add('monospace');
231 name.textContent = this.name;
232 specs.append(name);
233 }
234 frag.append(specs);
235 }
236 return frag;
237 }
238}