| 1 | import { AutoCompleteFuzzyScore } from './AutoCompleteFuzzyScore.js'; |
| 2 | |
| 3 | |
| 4 | export 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 | } |