@gesslar/sassy 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -2
- package/src/BuildCommand.js +23 -38
- package/src/Command.js +2 -2
- package/src/Compiler.js +1 -1
- package/src/Evaluator.js +7 -2
- package/src/ResolveCommand.js +101 -7
- package/src/Session.js +2 -1
- package/src/ThemeToken.js +23 -0
- package/src/cli.js +8 -9
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"name": "gesslar",
|
|
6
6
|
"url": "https://gesslar.dev"
|
|
7
7
|
},
|
|
8
|
-
"version": "
|
|
8
|
+
"version": "2.0.0",
|
|
9
9
|
"license": "Unlicense",
|
|
10
10
|
"homepage": "https://github.com/gesslar/sassy#readme",
|
|
11
11
|
"repository": {
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@gesslar/colours": "^0.8.0",
|
|
47
|
-
"@gesslar/toolkit": "^3.
|
|
47
|
+
"@gesslar/toolkit": "^3.30.0",
|
|
48
48
|
"chokidar": "^5.0.0",
|
|
49
49
|
"color-support": "^1.1.3",
|
|
50
50
|
"commander": "^14.0.2",
|
|
@@ -54,6 +54,7 @@
|
|
|
54
54
|
"yaml": "^2.8.2"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
+
"@docusaurus/eslint-plugin": "^3.9.2",
|
|
57
58
|
"@gesslar/uglier": "^1.2.0",
|
|
58
59
|
"eslint": "^9.39.2",
|
|
59
60
|
"typescript": "^5.9.3"
|
|
@@ -69,6 +70,9 @@
|
|
|
69
70
|
"submit": "pnpm publish --access public --//registry.npmjs.org/:_authToken=\"${NPM_ACCESS_TOKEN}\"",
|
|
70
71
|
"examples": "node ./examples/validator.js",
|
|
71
72
|
"update": "pnpm self-update && pnpx npm-check-updates -u && pnpm install",
|
|
73
|
+
"docs:dev": "pnpm --dir docs start",
|
|
74
|
+
"docs:build": "pnpm --dir docs build",
|
|
75
|
+
"docs:deploy": "gh workflow run deploy-docs.yml",
|
|
72
76
|
"pr": "gt submit -p --ai",
|
|
73
77
|
"patch": "pnpm version patch",
|
|
74
78
|
"minor": "pnpm version minor",
|
package/src/BuildCommand.js
CHANGED
|
@@ -48,39 +48,33 @@ export default class BuildCommand extends Command {
|
|
|
48
48
|
return await Util.asyncEmit(this.emitter, event, ...args)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* @typedef {object} BuildCommandOptions
|
|
53
|
+
* @property {boolean} [watch] - Enable watch mode for file changes
|
|
54
|
+
* @property {string} [outputDir] - Custom output directory path
|
|
55
|
+
* @property {boolean} [dryRun] - Print JSON to stdout without writing files
|
|
56
|
+
* @property {boolean} [silent] - Silent mode, only show errors or dry-run output
|
|
57
|
+
*/
|
|
58
|
+
|
|
51
59
|
/**
|
|
52
60
|
* Executes the build command for the provided theme files.
|
|
53
61
|
* Processes each file in parallel, optionally watching for changes.
|
|
54
62
|
*
|
|
55
|
-
* @param {string
|
|
56
|
-
* @param {
|
|
57
|
-
* @param {boolean} [options.watch] - Enable watch mode for file changes
|
|
58
|
-
* @param {string} [options.outputDir] - Custom output directory path
|
|
59
|
-
* @param {boolean} [options.dryRun] - Print JSON to stdout without writing files
|
|
60
|
-
* @param {boolean} [options.silent] - Silent mode, only show errors or dry-run output
|
|
63
|
+
* @param {Array<string>} fileNames - Array of theme file paths to process
|
|
64
|
+
* @param {BuildCommandOptions} options - {@link BuildCommandOptions}
|
|
61
65
|
* @returns {Promise<void>} Resolves when all files are processed
|
|
62
66
|
* @throws {Error} When theme compilation fails
|
|
63
67
|
*/
|
|
64
68
|
async execute(fileNames, options) {
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* @typedef {object} BuildCommandOptions
|
|
68
|
-
* @property {boolean} [watch] Enable watch mode for file changes
|
|
69
|
-
* @property {string} [outputDir] Custom output directory path
|
|
70
|
-
* @property {boolean} [dryRun] Print JSON to stdout without writing files
|
|
71
|
-
* @property {boolean} [silent] Silent mode, only show errors or dry-run output
|
|
72
|
-
*/
|
|
73
69
|
const cwd = this.getCwd()
|
|
74
70
|
|
|
75
71
|
if(options.watch) {
|
|
76
72
|
options.watch && this.#initialiseInputHandler()
|
|
77
73
|
|
|
78
|
-
this.emitter.on("quit", async() =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.emitter.on("building", async() => await this.#startBuilding())
|
|
74
|
+
this.emitter.on("quit", async() => await this.#handleQuit())
|
|
75
|
+
this.emitter.on("building", () => this.#startBuilding())
|
|
82
76
|
this.emitter.on("finishedBuilding", () => this.#finishBuilding())
|
|
83
|
-
this.emitter.on("erasePrompt",
|
|
77
|
+
this.emitter.on("erasePrompt", () => this.#erasePrompt())
|
|
84
78
|
this.emitter.on("printPrompt", () => this.#printPrompt())
|
|
85
79
|
}
|
|
86
80
|
|
|
@@ -97,16 +91,6 @@ export default class BuildCommand extends Command {
|
|
|
97
91
|
if(Promised.hasRejected(sessionResults))
|
|
98
92
|
Promised.throw("Creating sessions.", sessionResults)
|
|
99
93
|
|
|
100
|
-
// if(sessionResults.some(theme => theme.status === "rejected")) {
|
|
101
|
-
// const rejected = sessionResults.filter(result => result.status === "rejected")
|
|
102
|
-
|
|
103
|
-
// rejected.forEach(item => {
|
|
104
|
-
// const sassError = Sass.new("Creating session for theme file.", item.reason)
|
|
105
|
-
// sassError.report(options.nerd)
|
|
106
|
-
// })
|
|
107
|
-
// process.exit(1)
|
|
108
|
-
// }
|
|
109
|
-
|
|
110
94
|
const sessions = Promised.values(sessionResults)
|
|
111
95
|
const firstRun = await Promised.settle(sessions.map(
|
|
112
96
|
async session => await session.run()))
|
|
@@ -123,7 +107,7 @@ export default class BuildCommand extends Command {
|
|
|
123
107
|
async #handleQuit() {
|
|
124
108
|
await this.asyncEmit("closeSession")
|
|
125
109
|
|
|
126
|
-
|
|
110
|
+
Term.write("\x1b[?25h")
|
|
127
111
|
|
|
128
112
|
Term.info()
|
|
129
113
|
Term.info("Exiting.")
|
|
@@ -160,16 +144,16 @@ export default class BuildCommand extends Command {
|
|
|
160
144
|
}
|
|
161
145
|
})
|
|
162
146
|
|
|
163
|
-
|
|
147
|
+
Term.write("\x1b[?25l")
|
|
164
148
|
}
|
|
165
149
|
|
|
166
|
-
|
|
150
|
+
#printPrompt() {
|
|
167
151
|
if(this.#hasPrompt && this.#building > 0)
|
|
168
152
|
return
|
|
169
153
|
|
|
170
|
-
|
|
154
|
+
Term.write("\n")
|
|
171
155
|
|
|
172
|
-
|
|
156
|
+
Term.write(Term.terminalMessage([
|
|
173
157
|
["info", "F5", ["<",">"]],
|
|
174
158
|
"rebuild all,",
|
|
175
159
|
["info", "Ctrl-C", ["<",">"]],
|
|
@@ -179,17 +163,18 @@ export default class BuildCommand extends Command {
|
|
|
179
163
|
this.#hasPrompt = true
|
|
180
164
|
}
|
|
181
165
|
|
|
182
|
-
|
|
166
|
+
#erasePrompt() {
|
|
183
167
|
if(!this.#hasPrompt)
|
|
184
168
|
return
|
|
185
169
|
|
|
186
170
|
this.#hasPrompt = false
|
|
187
171
|
|
|
188
|
-
|
|
172
|
+
Term.clearLine().moveStart()
|
|
189
173
|
}
|
|
190
174
|
|
|
191
|
-
|
|
192
|
-
|
|
175
|
+
#startBuilding() {
|
|
176
|
+
this.#erasePrompt()
|
|
177
|
+
|
|
193
178
|
this.#building++
|
|
194
179
|
}
|
|
195
180
|
|
package/src/Command.js
CHANGED
|
@@ -115,7 +115,7 @@ export default class Command {
|
|
|
115
115
|
/**
|
|
116
116
|
* Gets the array of CLI option names.
|
|
117
117
|
*
|
|
118
|
-
* @returns {string
|
|
118
|
+
* @returns {Array<string>} Array of option names
|
|
119
119
|
*/
|
|
120
120
|
getCliOptionNames() {
|
|
121
121
|
return this.#optionNames
|
|
@@ -192,7 +192,7 @@ export default class Command {
|
|
|
192
192
|
* Adds a single CLI option to the command.
|
|
193
193
|
*
|
|
194
194
|
* @param {string} name - The option name
|
|
195
|
-
* @param {string
|
|
195
|
+
* @param {Array<string>} options - Array containing option flag and description
|
|
196
196
|
* @param {boolean} preserve - Whether to preserve this option name in the list
|
|
197
197
|
* @returns {Promise<this>} Returns this instance for method chaining
|
|
198
198
|
*/
|
package/src/Compiler.js
CHANGED
|
@@ -222,7 +222,7 @@ export default class Compiler {
|
|
|
222
222
|
* evaluation.
|
|
223
223
|
*
|
|
224
224
|
* @param {object} work - The object to decompose
|
|
225
|
-
* @param {string
|
|
225
|
+
* @param {Array<string>} path - Current path array for nested properties
|
|
226
226
|
* @returns {Array<object>} Array of decomposed object entries with path information
|
|
227
227
|
*/
|
|
228
228
|
#decomposeObject(work, path = []) {
|
package/src/Evaluator.js
CHANGED
|
@@ -318,11 +318,16 @@ export default class Evaluator {
|
|
|
318
318
|
return null
|
|
319
319
|
|
|
320
320
|
const resolved = value.replace(captured, applied)
|
|
321
|
-
|
|
322
|
-
return new ThemeToken(value)
|
|
321
|
+
const token = new ThemeToken(value)
|
|
323
322
|
.setKind("function")
|
|
324
323
|
.setRawValue(captured)
|
|
325
324
|
.setValue(resolved)
|
|
325
|
+
|
|
326
|
+
if(resolved !== applied) {
|
|
327
|
+
token.setFunctionResult(applied)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return token
|
|
326
331
|
}
|
|
327
332
|
|
|
328
333
|
/**
|
package/src/ResolveCommand.js
CHANGED
|
@@ -14,6 +14,9 @@ import Theme from "./Theme.js"
|
|
|
14
14
|
* Provides introspection into the theme resolution process and variable dependencies.
|
|
15
15
|
*/
|
|
16
16
|
export default class ResolveCommand extends Command {
|
|
17
|
+
#extraOptions = null
|
|
18
|
+
#bg = null
|
|
19
|
+
|
|
17
20
|
/**
|
|
18
21
|
* Creates a new ResolveCommand instance.
|
|
19
22
|
*
|
|
@@ -28,6 +31,22 @@ export default class ResolveCommand extends Command {
|
|
|
28
31
|
"tokenColor": ["-t, --tokenColor <scope>", "resolve a tokenColors scope to its final evaluated value"],
|
|
29
32
|
"semanticTokenColor": ["-s, --semanticTokenColor <scope>", "resolve a semanticTokenColors scope to its final evaluated value"],
|
|
30
33
|
})
|
|
34
|
+
this.#extraOptions = {
|
|
35
|
+
"bg": ["--bg <hex>", "background colour for alpha swatch preview (e.g. 1a1a1a or '#1a1a1a')"],
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Builds the CLI command, adding extra options that are not mutually exclusive.
|
|
41
|
+
*
|
|
42
|
+
* @param {object} program - The commander.js program instance
|
|
43
|
+
* @returns {Promise<this>} Returns this instance for method chaining
|
|
44
|
+
*/
|
|
45
|
+
async buildCli(program) {
|
|
46
|
+
await super.buildCli(program)
|
|
47
|
+
this.addCliOptions(this.#extraOptions, false)
|
|
48
|
+
|
|
49
|
+
return this
|
|
31
50
|
}
|
|
32
51
|
|
|
33
52
|
/**
|
|
@@ -59,6 +78,16 @@ export default class ResolveCommand extends Command {
|
|
|
59
78
|
)
|
|
60
79
|
}
|
|
61
80
|
|
|
81
|
+
if(options.bg) {
|
|
82
|
+
const bg = options.bg.startsWith("#") ? options.bg : `#${options.bg}`
|
|
83
|
+
|
|
84
|
+
if(!Colour.isHex(bg)) {
|
|
85
|
+
throw Sass.new(`Invalid --bg colour: ${options.bg}`)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
this.#bg = Colour.normaliseHex(bg)
|
|
89
|
+
}
|
|
90
|
+
|
|
62
91
|
const resolveFunctionName = `resolve${Util.capitalize(optionName)}`
|
|
63
92
|
const optionValue = options[optionName]
|
|
64
93
|
const resolverFunction = this[resolveFunctionName]
|
|
@@ -334,6 +363,18 @@ export default class ResolveCommand extends Command {
|
|
|
334
363
|
}
|
|
335
364
|
}
|
|
336
365
|
|
|
366
|
+
// For function tokens embedded in a larger expression, show the
|
|
367
|
+
// direct function output before showing the full substituted result
|
|
368
|
+
const funcResult = token.getFunctionResult?.()
|
|
369
|
+
|
|
370
|
+
if(funcResult && !steps.some(s => s.value === funcResult)) {
|
|
371
|
+
steps.push({
|
|
372
|
+
value: funcResult,
|
|
373
|
+
type: "result",
|
|
374
|
+
level
|
|
375
|
+
})
|
|
376
|
+
}
|
|
377
|
+
|
|
337
378
|
// Add final result for this token
|
|
338
379
|
if(rawValue !== finalValue && !steps.some(s => s.value === finalValue)) {
|
|
339
380
|
steps.push({
|
|
@@ -387,13 +428,13 @@ export default class ResolveCommand extends Command {
|
|
|
387
428
|
const {value, depth, type} = step
|
|
388
429
|
const [line, kind] = this.#formatLeaf(value)
|
|
389
430
|
|
|
390
|
-
// Simple logic: only hex results get extra indentation with arrow, everything else is clean
|
|
431
|
+
// Simple logic: only hex results get extra indentation with arrow/swatch, everything else is clean
|
|
391
432
|
if(type === "result" && kind === "hex") {
|
|
392
|
-
// Hex results are indented one extra level with
|
|
433
|
+
// Hex results are indented one extra level with swatch or arrow
|
|
393
434
|
const prefix = " ".repeat(depth + 1)
|
|
394
|
-
const
|
|
435
|
+
const indicator = this.#makeIndicator(value, this.#bg)
|
|
395
436
|
|
|
396
|
-
out.push(`${prefix}${
|
|
437
|
+
out.push(`${prefix}${indicator} ${line}`)
|
|
397
438
|
} else {
|
|
398
439
|
// Everything else just gets clean indentation
|
|
399
440
|
const prefix = " ".repeat(depth)
|
|
@@ -409,6 +450,59 @@ export default class ResolveCommand extends Command {
|
|
|
409
450
|
#sub = Evaluator.sub
|
|
410
451
|
#hex = value => Colour.isHex(value)
|
|
411
452
|
|
|
453
|
+
/**
|
|
454
|
+
* Creates a truecolor swatch glyph from a hex value.
|
|
455
|
+
*
|
|
456
|
+
* @private
|
|
457
|
+
* @param {string} hex - A 6- or 8-digit hex colour.
|
|
458
|
+
* @returns {string} A truecolor `■` character.
|
|
459
|
+
*/
|
|
460
|
+
#swatch(hex) {
|
|
461
|
+
const solid = Colour.parseHexColour(hex).colour
|
|
462
|
+
const r = parseInt(solid.slice(1, 3), 16)
|
|
463
|
+
const g = parseInt(solid.slice(3, 5), 16)
|
|
464
|
+
const b = parseInt(solid.slice(5, 7), 16)
|
|
465
|
+
|
|
466
|
+
return `\x1b[38;2;${r};${g};${b}m■\x1b[0m`
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Creates a colour swatch or fallback arrow indicator for a hex value.
|
|
471
|
+
* When the colour has alpha and no --bg is provided, shows two swatches
|
|
472
|
+
* (against black and white). With --bg, shows a single swatch composited
|
|
473
|
+
* against the specified background.
|
|
474
|
+
*
|
|
475
|
+
* @private
|
|
476
|
+
* @param {string} hex - The hex colour value.
|
|
477
|
+
* @param {string|null} bg - Optional background hex for alpha compositing.
|
|
478
|
+
* @returns {string} Swatch indicator(s) or styled arrow.
|
|
479
|
+
*/
|
|
480
|
+
#makeIndicator(hex, bg = null) {
|
|
481
|
+
if(!Term.hasColor) {
|
|
482
|
+
return c`{arrow}→{/}`
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
const parsed = Colour.parseHexColour(hex)
|
|
486
|
+
const hasAlpha = !!parsed.alpha
|
|
487
|
+
|
|
488
|
+
if(!hasAlpha) {
|
|
489
|
+
return this.#swatch(hex)
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
const alphaPercent = Math.round(parsed.alpha.decimal * 100)
|
|
493
|
+
|
|
494
|
+
if(bg) {
|
|
495
|
+
const composited = Colour.mix(parsed.colour, bg, alphaPercent)
|
|
496
|
+
|
|
497
|
+
return this.#swatch(composited)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const onBlack = Colour.mix(parsed.colour, "#000000", alphaPercent)
|
|
501
|
+
const onWhite = Colour.mix(parsed.colour, "#ffffff", alphaPercent)
|
|
502
|
+
|
|
503
|
+
return `${this.#swatch(onBlack)}${this.#swatch(onWhite)}`
|
|
504
|
+
}
|
|
505
|
+
|
|
412
506
|
/**
|
|
413
507
|
* Formats a single ThemeToken for display in the theme resolution output,
|
|
414
508
|
* applying colour and style based on its type.
|
|
@@ -432,12 +526,12 @@ export default class ResolveCommand extends Command {
|
|
|
432
526
|
}
|
|
433
527
|
|
|
434
528
|
if(this.#func.test(value)) {
|
|
435
|
-
const
|
|
529
|
+
const match = this.#func.exec(value)
|
|
436
530
|
|
|
437
|
-
if(!
|
|
531
|
+
if(!match?.groups)
|
|
438
532
|
return [c`{leaf}${value}{/}`, "literal"]
|
|
439
533
|
|
|
440
|
-
const {func, args} =
|
|
534
|
+
const {func, args} = match.groups
|
|
441
535
|
|
|
442
536
|
return [
|
|
443
537
|
c`{func}${func}{/}{parens}${"("}{/}{leaf}${args}{/}{parens}${")"}{/}`,
|
package/src/Session.js
CHANGED
|
@@ -6,6 +6,7 @@ import {Promised, Sass, Term, Util} from "@gesslar/toolkit"
|
|
|
6
6
|
/**
|
|
7
7
|
* @import {Command} from "./Command.js"
|
|
8
8
|
* @import {Theme} from "./Theme.js"
|
|
9
|
+
* @import {FSWatcher} from "chokidar"
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -58,7 +59,7 @@ export default class Session {
|
|
|
58
59
|
/**
|
|
59
60
|
* Active file system watcher for theme dependencies.
|
|
60
61
|
*
|
|
61
|
-
* @type {
|
|
62
|
+
* @type {FSWatcher}
|
|
62
63
|
* @private
|
|
63
64
|
*/
|
|
64
65
|
#watcher = null
|
package/src/ThemeToken.js
CHANGED
|
@@ -30,6 +30,7 @@ export default class ThemeToken {
|
|
|
30
30
|
#parentTokenKey = null
|
|
31
31
|
#trail = new Array()
|
|
32
32
|
#parsedColor = null
|
|
33
|
+
#functionResult = null
|
|
33
34
|
|
|
34
35
|
/**
|
|
35
36
|
* Constructs a ThemeToken with a given token name.
|
|
@@ -242,6 +243,28 @@ export default class ThemeToken {
|
|
|
242
243
|
return this.#parsedColor
|
|
243
244
|
}
|
|
244
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Sets the direct result of a colour function evaluation, before
|
|
248
|
+
* substitution back into the enclosing expression.
|
|
249
|
+
*
|
|
250
|
+
* @param {string} result - The direct function output.
|
|
251
|
+
* @returns {ThemeToken} This token instance.
|
|
252
|
+
*/
|
|
253
|
+
setFunctionResult(result) {
|
|
254
|
+
this.#functionResult = result
|
|
255
|
+
|
|
256
|
+
return this
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Gets the direct result of a colour function evaluation.
|
|
261
|
+
*
|
|
262
|
+
* @returns {string|null} The direct function output or null.
|
|
263
|
+
*/
|
|
264
|
+
getFunctionResult() {
|
|
265
|
+
return this.#functionResult
|
|
266
|
+
}
|
|
267
|
+
|
|
245
268
|
/**
|
|
246
269
|
* Checks if this token has an ancestor with the given token name.
|
|
247
270
|
*
|
package/src/cli.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* sourceFile: FileObject // entry theme file
|
|
17
17
|
* source: object // raw parsed data (must contain `config`)
|
|
18
18
|
* output: object // final theme JSON object
|
|
19
|
-
* dependencies: FileObject
|
|
19
|
+
* dependencies: Array<FileObject> // secondary sources discovered during compile
|
|
20
20
|
* lookup: object // variable lookup data for compilation
|
|
21
21
|
* breadcrumbs: Map // variable resolution tracking
|
|
22
22
|
* }
|
|
@@ -80,14 +80,13 @@ void (async function main() {
|
|
|
80
80
|
c.alias.set("loc", "{F148}")
|
|
81
81
|
|
|
82
82
|
// Resolve command
|
|
83
|
-
c.alias.set("head", "{
|
|
84
|
-
c.alias.set("leaf", "{
|
|
85
|
-
c.alias.set("func", "{
|
|
86
|
-
c.alias.set("parens", "{
|
|
87
|
-
c.alias.set("
|
|
88
|
-
c.alias.set("hex", "{
|
|
89
|
-
c.alias.set("
|
|
90
|
-
c.alias.set("hexAlpha", "{F127}{<I}")
|
|
83
|
+
c.alias.set("head", "{F250}")
|
|
84
|
+
c.alias.set("leaf", "{F243}")
|
|
85
|
+
c.alias.set("func", "{<B}")
|
|
86
|
+
c.alias.set("parens", "{F208}")
|
|
87
|
+
c.alias.set("hash", "{F172}")
|
|
88
|
+
c.alias.set("hex", "{F025}")
|
|
89
|
+
c.alias.set("hexAlpha", "{F073}{<I}")
|
|
91
90
|
c.alias.set("arrow", "{F033}")
|
|
92
91
|
|
|
93
92
|
const cache = new Cache()
|