@nsis/dent-ui 0.1.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/.turbo/turbo-build.log +10 -0
- package/CHANGELOG.md +7 -0
- package/LICENSE +21 -0
- package/README.md +13 -0
- package/index.html +12 -0
- package/package.json +53 -0
- package/src/App.svelte +453 -0
- package/src/Editor.svelte +58 -0
- package/src/env.d.ts +1 -0
- package/src/main.ts +6 -0
- package/src/themes.ts +198 -0
- package/test/App.test.ts +55 -0
- package/test/Editor.test.ts +38 -0
- package/test/themes.test.ts +23 -0
- package/tsconfig.json +10 -0
- package/vite.config.ts +27 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
$ vite build
|
|
2
|
+
2:57:56 PM [vite-plugin-svelte] no Svelte config found at /home/runner/work/nsis-javascript-tools/nsis-javascript-tools/packages/dent-ui - using default configuration.
|
|
3
|
+
[36mvite v8.1.3 [32mbuilding client environment for production...[36m[39m
|
|
4
|
+
[2K
|
|
5
|
+
rendering chunks...
|
|
6
|
+
computing gzip size...
|
|
7
|
+
dist/index.html 0.30 kB │ gzip: 0.22 kB
|
|
8
|
+
dist/assets/index-CqDOLnCc.js 403.94 kB │ gzip: 131.52 kB
|
|
9
|
+
|
|
10
|
+
[32m✓ built in 219ms[39m
|
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jan T. Sott
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# @nsis/dent-ui
|
|
2
|
+
|
|
3
|
+
> Web UI for the Dent NSIS code formatter
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
[](https://www.npmjs.org/package/@nsis/dent-ui)
|
|
7
|
+
[](https://github.com/idleberg/nsis-javascript-tools/actions)
|
|
8
|
+
|
|
9
|
+
[Demo Time](https://idleberg.github.io/nsis-javascript-tools/dent/) 🙌
|
|
10
|
+
|
|
11
|
+
## License
|
|
12
|
+
|
|
13
|
+
This work is licensed under [The MIT License](LICENSE).
|
package/index.html
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Dent UI</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="app"></div>
|
|
10
|
+
<script type="module" src="/src/main.ts"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nsis/dent-ui",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Web UI for the Dent NSIS code formatter",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/main.ts",
|
|
9
|
+
"./DentUI.svelte": "./src/App.svelte"
|
|
10
|
+
},
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=20.0.0"
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/idleberg/nsis-javascript-tools.git"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/idleberg/nsis-javascript-tools/tree/main/packages/dent-ui#readme",
|
|
19
|
+
"keywords": [
|
|
20
|
+
"nsis",
|
|
21
|
+
"formatter",
|
|
22
|
+
"ui"
|
|
23
|
+
],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@codemirror/commands": "^6.10.4",
|
|
26
|
+
"@codemirror/language": "^6.12.4",
|
|
27
|
+
"@codemirror/state": "^6.7.1",
|
|
28
|
+
"@codemirror/view": "^6.43.5",
|
|
29
|
+
"@lezer/highlight": "^1.2.3",
|
|
30
|
+
"svelte": "^5.56.4",
|
|
31
|
+
"@nsis/codemirror": "0.13.0",
|
|
32
|
+
"@nsis/dent": "0.13.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@sveltejs/vite-plugin-svelte": "^7.2.0",
|
|
36
|
+
"@vitest/browser": "^4.1.10",
|
|
37
|
+
"@vitest/browser-playwright": "^4.1.10",
|
|
38
|
+
"playwright": "^1.53.0",
|
|
39
|
+
"svelte-check": "^4.7.2",
|
|
40
|
+
"vite": "^8.1.3",
|
|
41
|
+
"vitest": "^4.1.9",
|
|
42
|
+
"vitest-browser-svelte": "^2.2.0"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "vite build",
|
|
46
|
+
"dev": "vite",
|
|
47
|
+
"lint": "concurrently --prefix-colors blue,green,magenta npm:lint:*",
|
|
48
|
+
"lint:biome": "biome check",
|
|
49
|
+
"lint:biome:ci": "biome ci",
|
|
50
|
+
"check": "svelte-check --tsconfig ./tsconfig.json",
|
|
51
|
+
"test": "vitest run"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/App.svelte
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
|
3
|
+
import { EditorState } from '@codemirror/state';
|
|
4
|
+
import { EditorView, keymap } from '@codemirror/view';
|
|
5
|
+
import { nsisLanguage } from '@nsis/codemirror';
|
|
6
|
+
import { createFormatter } from '@nsis/dent';
|
|
7
|
+
import { onDestroy, onMount } from 'svelte';
|
|
8
|
+
import Editor from './Editor.svelte';
|
|
9
|
+
|
|
10
|
+
let dark = $state(document.documentElement.dataset.theme !== 'light');
|
|
11
|
+
let themePreference = $state(localStorage.getItem('theme') || 'system');
|
|
12
|
+
|
|
13
|
+
let useTabs = $state(true);
|
|
14
|
+
let singleQuote = $state(false);
|
|
15
|
+
let trimEmptyLines = $state(true);
|
|
16
|
+
let indentSize = $state(2);
|
|
17
|
+
let printWidth = $state(120);
|
|
18
|
+
let endOfLine = $state<'lf' | 'crlf'>('lf');
|
|
19
|
+
let autoFormat = $state(true);
|
|
20
|
+
|
|
21
|
+
let status = $state('');
|
|
22
|
+
let error = $state('');
|
|
23
|
+
let hasOutput = $state(false);
|
|
24
|
+
let inputView: EditorView | undefined;
|
|
25
|
+
let outputView: EditorView | undefined;
|
|
26
|
+
let debounceTimer: ReturnType<typeof setTimeout>;
|
|
27
|
+
let statusTimer: ReturnType<typeof setTimeout>;
|
|
28
|
+
|
|
29
|
+
const mql = matchMedia('(prefers-color-scheme: light)');
|
|
30
|
+
function handleColorSchemeChange() {
|
|
31
|
+
if (themePreference === 'system') applyTheme('system');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
onMount(() => {
|
|
35
|
+
mql.addEventListener('change', handleColorSchemeChange);
|
|
36
|
+
return () => mql.removeEventListener('change', handleColorSchemeChange);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
onDestroy(() => {
|
|
40
|
+
clearTimeout(debounceTimer);
|
|
41
|
+
clearTimeout(statusTimer);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function applyTheme(pref: string) {
|
|
45
|
+
const isLight = pref === 'light' || (pref === 'system' && mql.matches);
|
|
46
|
+
if (isLight) {
|
|
47
|
+
document.documentElement.dataset.theme = 'light';
|
|
48
|
+
} else {
|
|
49
|
+
delete document.documentElement.dataset.theme;
|
|
50
|
+
}
|
|
51
|
+
localStorage.setItem('theme', pref);
|
|
52
|
+
dark = !isLight;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
applyTheme(localStorage.getItem('theme') || 'system');
|
|
56
|
+
|
|
57
|
+
function scheduleAutoFormat() {
|
|
58
|
+
if (!autoFormat) return;
|
|
59
|
+
clearTimeout(debounceTimer);
|
|
60
|
+
debounceTimer = setTimeout(formatInput, 400);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function formatInput() {
|
|
64
|
+
error = '';
|
|
65
|
+
const source = inputView?.state.doc.toString() ?? '';
|
|
66
|
+
|
|
67
|
+
if (!source.trim()) {
|
|
68
|
+
setOutput('');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const { format } = createFormatter({
|
|
74
|
+
useTabs,
|
|
75
|
+
singleQuote,
|
|
76
|
+
trimEmptyLines,
|
|
77
|
+
indentSize,
|
|
78
|
+
printWidth,
|
|
79
|
+
endOfLine,
|
|
80
|
+
});
|
|
81
|
+
setOutput(format(source));
|
|
82
|
+
clearTimeout(statusTimer);
|
|
83
|
+
status = 'Formatted';
|
|
84
|
+
statusTimer = setTimeout(() => {
|
|
85
|
+
status = '';
|
|
86
|
+
}, 1500);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
error = err instanceof Error ? err.message : String(err);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function setOutput(text: string) {
|
|
93
|
+
if (!outputView) return;
|
|
94
|
+
outputView.dispatch({
|
|
95
|
+
changes: { from: 0, to: outputView.state.doc.length, insert: text },
|
|
96
|
+
});
|
|
97
|
+
hasOutput = !!text.trim();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function copyOutput() {
|
|
101
|
+
const text = outputView?.state.doc.toString() ?? '';
|
|
102
|
+
if (!text.trim()) return;
|
|
103
|
+
await navigator.clipboard.writeText(text);
|
|
104
|
+
clearTimeout(statusTimer);
|
|
105
|
+
status = 'Copied';
|
|
106
|
+
statusTimer = setTimeout(() => {
|
|
107
|
+
status = '';
|
|
108
|
+
}, 1500);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function clear() {
|
|
112
|
+
clearTimeout(debounceTimer);
|
|
113
|
+
if (inputView) {
|
|
114
|
+
inputView.dispatch({
|
|
115
|
+
changes: { from: 0, to: inputView.state.doc.length, insert: '' },
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
setOutput('');
|
|
119
|
+
error = '';
|
|
120
|
+
status = '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const inputExtensions = [
|
|
124
|
+
nsisLanguage,
|
|
125
|
+
history(),
|
|
126
|
+
keymap.of([...defaultKeymap, ...historyKeymap]),
|
|
127
|
+
EditorView.lineWrapping,
|
|
128
|
+
EditorView.updateListener.of((update) => {
|
|
129
|
+
if (update.docChanged) scheduleAutoFormat();
|
|
130
|
+
}),
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
const outputExtensions = [
|
|
134
|
+
nsisLanguage,
|
|
135
|
+
EditorState.readOnly.of(true),
|
|
136
|
+
EditorView.editable.of(false),
|
|
137
|
+
EditorView.lineWrapping,
|
|
138
|
+
];
|
|
139
|
+
</script>
|
|
140
|
+
|
|
141
|
+
<header>
|
|
142
|
+
<h1>Dent UI</h1>
|
|
143
|
+
<fieldset class="settings" aria-label="Formatter settings">
|
|
144
|
+
<label
|
|
145
|
+
><input
|
|
146
|
+
type="checkbox"
|
|
147
|
+
bind:checked={useTabs}
|
|
148
|
+
onchange={scheduleAutoFormat}
|
|
149
|
+
/> Tabs</label
|
|
150
|
+
>
|
|
151
|
+
<label
|
|
152
|
+
><input
|
|
153
|
+
type="checkbox"
|
|
154
|
+
bind:checked={singleQuote}
|
|
155
|
+
onchange={scheduleAutoFormat}
|
|
156
|
+
/> Single quotes</label
|
|
157
|
+
>
|
|
158
|
+
<label
|
|
159
|
+
><input
|
|
160
|
+
type="checkbox"
|
|
161
|
+
bind:checked={trimEmptyLines}
|
|
162
|
+
onchange={scheduleAutoFormat}
|
|
163
|
+
/> Trim empty lines</label
|
|
164
|
+
>
|
|
165
|
+
<label>
|
|
166
|
+
Indent
|
|
167
|
+
<select bind:value={indentSize} onchange={scheduleAutoFormat}>
|
|
168
|
+
<option value={2}>2</option>
|
|
169
|
+
<option value={4}>4</option>
|
|
170
|
+
<option value={8}>8</option>
|
|
171
|
+
</select>
|
|
172
|
+
</label>
|
|
173
|
+
<label>
|
|
174
|
+
Width
|
|
175
|
+
<input
|
|
176
|
+
type="number"
|
|
177
|
+
bind:value={printWidth}
|
|
178
|
+
onchange={scheduleAutoFormat}
|
|
179
|
+
min="40"
|
|
180
|
+
max="320"
|
|
181
|
+
step="10"
|
|
182
|
+
/>
|
|
183
|
+
</label>
|
|
184
|
+
<label>
|
|
185
|
+
EOL
|
|
186
|
+
<select bind:value={endOfLine} onchange={scheduleAutoFormat}>
|
|
187
|
+
<option value="lf">LF</option>
|
|
188
|
+
<option value="crlf">CRLF</option>
|
|
189
|
+
</select>
|
|
190
|
+
</label>
|
|
191
|
+
</fieldset>
|
|
192
|
+
<select
|
|
193
|
+
class="theme-select"
|
|
194
|
+
aria-label="Color theme"
|
|
195
|
+
bind:value={themePreference}
|
|
196
|
+
onchange={() => applyTheme(themePreference)}
|
|
197
|
+
>
|
|
198
|
+
<option value="system">System</option>
|
|
199
|
+
<option value="light">Light</option>
|
|
200
|
+
<option value="dark">Dark</option>
|
|
201
|
+
</select>
|
|
202
|
+
</header>
|
|
203
|
+
|
|
204
|
+
<div class="editors">
|
|
205
|
+
<div class="editor-pane">
|
|
206
|
+
<div class="pane-header">Input</div>
|
|
207
|
+
<Editor
|
|
208
|
+
{dark}
|
|
209
|
+
extensions={inputExtensions}
|
|
210
|
+
label="NSIS input editor"
|
|
211
|
+
oncreate={(v) => { inputView = v; v.focus(); }}
|
|
212
|
+
/>
|
|
213
|
+
</div>
|
|
214
|
+
<div class="editor-pane">
|
|
215
|
+
<div class="pane-header">Output</div>
|
|
216
|
+
<Editor
|
|
217
|
+
{dark}
|
|
218
|
+
extensions={outputExtensions}
|
|
219
|
+
label="Formatted output editor"
|
|
220
|
+
oncreate={(v) => (outputView = v)}
|
|
221
|
+
/>
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
|
|
225
|
+
<footer>
|
|
226
|
+
<button class="btn-primary" onclick={formatInput}>Format</button>
|
|
227
|
+
<button class="btn-secondary" onclick={clear}>Clear</button>
|
|
228
|
+
<label class="auto-label">
|
|
229
|
+
<input type="checkbox" bind:checked={autoFormat} /> Auto-format
|
|
230
|
+
</label>
|
|
231
|
+
{#if status}
|
|
232
|
+
<span class="status" role="status" aria-live="polite">{status}</span>
|
|
233
|
+
{/if}
|
|
234
|
+
{#if error}
|
|
235
|
+
<span class="error" role="alert">{error}</span>
|
|
236
|
+
{/if}
|
|
237
|
+
<button
|
|
238
|
+
class="btn-secondary copy-btn"
|
|
239
|
+
onclick={copyOutput}
|
|
240
|
+
disabled={!hasOutput}>Copy</button
|
|
241
|
+
>
|
|
242
|
+
</footer>
|
|
243
|
+
|
|
244
|
+
<style>
|
|
245
|
+
:global(:root) {
|
|
246
|
+
--bg: #1e1e2e;
|
|
247
|
+
--surface: #313244;
|
|
248
|
+
--overlay: #45475a;
|
|
249
|
+
--text: #cdd6f4;
|
|
250
|
+
--subtext: #a6adc8;
|
|
251
|
+
--blue: #89b4fa;
|
|
252
|
+
--mauve: #cba6f7;
|
|
253
|
+
--red: #f38ba8;
|
|
254
|
+
--green: #a6e3a1;
|
|
255
|
+
--border: #585b70;
|
|
256
|
+
--radius: 6px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
:global([data-theme="light"]) {
|
|
260
|
+
--bg: #eff1f5;
|
|
261
|
+
--surface: #e6e9ef;
|
|
262
|
+
--overlay: #ccd0da;
|
|
263
|
+
--text: #4c4f69;
|
|
264
|
+
--subtext: #6c6f85;
|
|
265
|
+
--blue: #1e66f5;
|
|
266
|
+
--mauve: #8839ef;
|
|
267
|
+
--red: #d20f39;
|
|
268
|
+
--green: #40a02b;
|
|
269
|
+
--border: #bcc0cc;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
:global(*) {
|
|
273
|
+
box-sizing: border-box;
|
|
274
|
+
margin: 0;
|
|
275
|
+
padding: 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
:global(body) {
|
|
279
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
280
|
+
"Helvetica Neue", sans-serif;
|
|
281
|
+
background: var(--bg);
|
|
282
|
+
color: var(--text);
|
|
283
|
+
height: 100vh;
|
|
284
|
+
display: flex;
|
|
285
|
+
flex-direction: column;
|
|
286
|
+
overflow: hidden;
|
|
287
|
+
-webkit-user-select: none;
|
|
288
|
+
user-select: none;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
:global(#app) {
|
|
292
|
+
display: contents;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
header {
|
|
296
|
+
padding: 12px 16px;
|
|
297
|
+
display: flex;
|
|
298
|
+
align-items: center;
|
|
299
|
+
gap: 16px;
|
|
300
|
+
border-bottom: 1px solid var(--border);
|
|
301
|
+
flex-shrink: 0;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
header h1 {
|
|
305
|
+
font-size: 16px;
|
|
306
|
+
font-weight: 600;
|
|
307
|
+
color: var(--blue);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.settings {
|
|
311
|
+
display: flex;
|
|
312
|
+
flex-wrap: wrap;
|
|
313
|
+
align-items: center;
|
|
314
|
+
gap: 12px;
|
|
315
|
+
font-size: 13px;
|
|
316
|
+
border: none;
|
|
317
|
+
padding: 0;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.settings label {
|
|
321
|
+
display: flex;
|
|
322
|
+
align-items: center;
|
|
323
|
+
gap: 5px;
|
|
324
|
+
color: var(--subtext);
|
|
325
|
+
cursor: pointer;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.settings input[type="checkbox"] {
|
|
329
|
+
accent-color: var(--blue);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.settings select,
|
|
333
|
+
.settings input[type="number"] {
|
|
334
|
+
background: var(--surface);
|
|
335
|
+
color: var(--text);
|
|
336
|
+
border: 1px solid var(--border);
|
|
337
|
+
border-radius: var(--radius);
|
|
338
|
+
padding: 3px 6px;
|
|
339
|
+
font-size: 13px;
|
|
340
|
+
font-family: inherit;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.settings input[type="number"] {
|
|
344
|
+
width: 70px;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.theme-select {
|
|
348
|
+
margin-left: auto;
|
|
349
|
+
background: var(--surface);
|
|
350
|
+
color: var(--text);
|
|
351
|
+
border: 1px solid var(--border);
|
|
352
|
+
border-radius: var(--radius);
|
|
353
|
+
padding: 3px 6px;
|
|
354
|
+
font-size: 12px;
|
|
355
|
+
font-family: inherit;
|
|
356
|
+
cursor: pointer;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.editors {
|
|
360
|
+
flex: 1;
|
|
361
|
+
display: grid;
|
|
362
|
+
grid-template-columns: 1fr 1fr;
|
|
363
|
+
gap: 0;
|
|
364
|
+
min-height: 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.editor-pane {
|
|
368
|
+
display: flex;
|
|
369
|
+
flex-direction: column;
|
|
370
|
+
min-height: 0;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.editor-pane:first-child {
|
|
374
|
+
border-right: 1px solid var(--border);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.pane-header {
|
|
378
|
+
padding: 8px 12px;
|
|
379
|
+
font-size: 12px;
|
|
380
|
+
font-weight: 600;
|
|
381
|
+
text-transform: uppercase;
|
|
382
|
+
letter-spacing: 0.05em;
|
|
383
|
+
color: var(--subtext);
|
|
384
|
+
background: var(--surface);
|
|
385
|
+
border-bottom: 1px solid var(--border);
|
|
386
|
+
flex-shrink: 0;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
footer {
|
|
390
|
+
padding: 8px 16px;
|
|
391
|
+
display: flex;
|
|
392
|
+
align-items: center;
|
|
393
|
+
gap: 12px;
|
|
394
|
+
border-top: 1px solid var(--border);
|
|
395
|
+
flex-shrink: 0;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
button {
|
|
399
|
+
font-family: inherit;
|
|
400
|
+
font-size: 13px;
|
|
401
|
+
font-weight: 500;
|
|
402
|
+
padding: 5px 14px;
|
|
403
|
+
border-radius: var(--radius);
|
|
404
|
+
border: 1px solid transparent;
|
|
405
|
+
cursor: pointer;
|
|
406
|
+
transition: background 0.15s;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
.btn-primary {
|
|
410
|
+
background: var(--blue);
|
|
411
|
+
color: var(--bg);
|
|
412
|
+
}
|
|
413
|
+
.btn-primary:hover {
|
|
414
|
+
filter: brightness(1.1);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.btn-secondary {
|
|
418
|
+
background: var(--surface);
|
|
419
|
+
color: var(--text);
|
|
420
|
+
border-color: var(--border);
|
|
421
|
+
}
|
|
422
|
+
.btn-secondary:hover {
|
|
423
|
+
background: var(--overlay);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.status {
|
|
427
|
+
font-size: 12px;
|
|
428
|
+
color: var(--green);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
.error {
|
|
432
|
+
font-size: 12px;
|
|
433
|
+
color: var(--red);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
.copy-btn {
|
|
437
|
+
margin-left: auto;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.auto-label {
|
|
441
|
+
display: flex;
|
|
442
|
+
align-items: center;
|
|
443
|
+
gap: 5px;
|
|
444
|
+
font-size: 13px;
|
|
445
|
+
color: var(--subtext);
|
|
446
|
+
cursor: pointer;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
button:disabled {
|
|
450
|
+
opacity: 0.4;
|
|
451
|
+
cursor: default;
|
|
452
|
+
}
|
|
453
|
+
</style>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Compartment, EditorState, type Extension } from '@codemirror/state';
|
|
3
|
+
import { EditorView } from '@codemirror/view';
|
|
4
|
+
import { onMount } from 'svelte';
|
|
5
|
+
import { darkTheme, editorTheme, lightTheme } from './themes.ts';
|
|
6
|
+
|
|
7
|
+
let {
|
|
8
|
+
dark = true,
|
|
9
|
+
extensions = [],
|
|
10
|
+
label = '',
|
|
11
|
+
oncreate,
|
|
12
|
+
}: {
|
|
13
|
+
dark?: boolean;
|
|
14
|
+
extensions?: Extension[];
|
|
15
|
+
label?: string;
|
|
16
|
+
oncreate?: (view: EditorView) => void;
|
|
17
|
+
} = $props();
|
|
18
|
+
|
|
19
|
+
let container: HTMLDivElement;
|
|
20
|
+
let view: EditorView | undefined;
|
|
21
|
+
const themeCompartment = new Compartment();
|
|
22
|
+
|
|
23
|
+
onMount(() => {
|
|
24
|
+
view = new EditorView({
|
|
25
|
+
state: EditorState.create({
|
|
26
|
+
doc: '',
|
|
27
|
+
extensions: [themeCompartment.of(dark ? darkTheme : lightTheme), editorTheme, ...extensions],
|
|
28
|
+
}),
|
|
29
|
+
parent: container,
|
|
30
|
+
});
|
|
31
|
+
oncreate?.(view);
|
|
32
|
+
return () => view?.destroy();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
let initialized = false;
|
|
36
|
+
$effect(() => {
|
|
37
|
+
const theme = dark ? darkTheme : lightTheme;
|
|
38
|
+
if (initialized) {
|
|
39
|
+
view?.dispatch({ effects: themeCompartment.reconfigure(theme) });
|
|
40
|
+
}
|
|
41
|
+
initialized = true;
|
|
42
|
+
});
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<div bind:this={container} aria-label={label}></div>
|
|
46
|
+
|
|
47
|
+
<style>
|
|
48
|
+
div {
|
|
49
|
+
flex: 1;
|
|
50
|
+
min-height: 0;
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-direction: column;
|
|
53
|
+
}
|
|
54
|
+
div :global(.cm-editor) {
|
|
55
|
+
flex: 1;
|
|
56
|
+
min-height: 0;
|
|
57
|
+
}
|
|
58
|
+
</style>
|
package/src/env.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="svelte" />
|
package/src/main.ts
ADDED
package/src/themes.ts
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
2
|
+
import { EditorView } from '@codemirror/view';
|
|
3
|
+
import { tags } from '@lezer/highlight';
|
|
4
|
+
|
|
5
|
+
export const editorTheme = EditorView.theme({
|
|
6
|
+
'&': { height: '100%', fontSize: '13px' },
|
|
7
|
+
'.cm-scroller': {
|
|
8
|
+
overflow: 'auto',
|
|
9
|
+
fontFamily: '"SF Mono", "Fira Code", "Cascadia Code", "JetBrains Mono", ui-monospace, monospace',
|
|
10
|
+
},
|
|
11
|
+
'.cm-content': { padding: '0.75rem 0' },
|
|
12
|
+
'.cm-line': { padding: '0 0.75rem' },
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const mochaTheme = EditorView.theme(
|
|
16
|
+
{
|
|
17
|
+
'&': { color: '#cdd6f4', backgroundColor: '#1e1e2e' },
|
|
18
|
+
'.cm-content': { caretColor: '#f5e0dc' },
|
|
19
|
+
'.cm-cursor, .cm-dropCursor': { borderLeftColor: '#f5e0dc' },
|
|
20
|
+
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
|
|
21
|
+
{ backgroundColor: '#585b7044' },
|
|
22
|
+
'.cm-activeLine': { backgroundColor: '#585b7015' },
|
|
23
|
+
'.cm-gutters': {
|
|
24
|
+
backgroundColor: '#1e1e2e',
|
|
25
|
+
color: '#6c7086',
|
|
26
|
+
border: 'none',
|
|
27
|
+
},
|
|
28
|
+
'.cm-activeLineGutter': { backgroundColor: '#585b7015' },
|
|
29
|
+
'.cm-selectionMatch': { backgroundColor: '#a6e3a122' },
|
|
30
|
+
'&.cm-focused .cm-matchingBracket': { backgroundColor: '#585b7044' },
|
|
31
|
+
'.cm-searchMatch': {
|
|
32
|
+
backgroundColor: '#f9e2af33',
|
|
33
|
+
outline: '1px solid #f9e2af66',
|
|
34
|
+
},
|
|
35
|
+
'.cm-searchMatch.cm-searchMatch-selected': { backgroundColor: '#a6e3a133' },
|
|
36
|
+
'.cm-foldPlaceholder': {
|
|
37
|
+
backgroundColor: 'transparent',
|
|
38
|
+
border: 'none',
|
|
39
|
+
color: '#6c7086',
|
|
40
|
+
},
|
|
41
|
+
'.cm-tooltip': { border: 'none', backgroundColor: '#313244' },
|
|
42
|
+
'.cm-tooltip-autocomplete': {
|
|
43
|
+
'& > ul > li[aria-selected]': {
|
|
44
|
+
backgroundColor: '#45475a',
|
|
45
|
+
color: '#cdd6f4',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{ dark: true },
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const mochaHighlightStyle = HighlightStyle.define([
|
|
53
|
+
{ tag: tags.keyword, color: '#cba6f7' },
|
|
54
|
+
{
|
|
55
|
+
tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName],
|
|
56
|
+
color: '#89b4fa',
|
|
57
|
+
},
|
|
58
|
+
{ tag: [tags.function(tags.variableName), tags.labelName], color: '#89b4fa' },
|
|
59
|
+
{
|
|
60
|
+
tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)],
|
|
61
|
+
color: '#fab387',
|
|
62
|
+
},
|
|
63
|
+
{ tag: [tags.definition(tags.name), tags.separator], color: '#cdd6f4' },
|
|
64
|
+
{
|
|
65
|
+
tag: [
|
|
66
|
+
tags.typeName,
|
|
67
|
+
tags.className,
|
|
68
|
+
tags.number,
|
|
69
|
+
tags.changed,
|
|
70
|
+
tags.annotation,
|
|
71
|
+
tags.modifier,
|
|
72
|
+
tags.self,
|
|
73
|
+
tags.namespace,
|
|
74
|
+
],
|
|
75
|
+
color: '#f9e2af',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
tag: [
|
|
79
|
+
tags.operator,
|
|
80
|
+
tags.operatorKeyword,
|
|
81
|
+
tags.url,
|
|
82
|
+
tags.escape,
|
|
83
|
+
tags.regexp,
|
|
84
|
+
tags.link,
|
|
85
|
+
tags.special(tags.string),
|
|
86
|
+
],
|
|
87
|
+
color: '#94e2d5',
|
|
88
|
+
},
|
|
89
|
+
{ tag: [tags.meta, tags.comment], color: '#6c7086' },
|
|
90
|
+
{ tag: tags.strong, fontWeight: 'bold' },
|
|
91
|
+
{ tag: tags.emphasis, fontStyle: 'italic' },
|
|
92
|
+
{ tag: tags.strikethrough, textDecoration: 'line-through' },
|
|
93
|
+
{ tag: tags.link, color: '#94e2d5', textDecoration: 'underline' },
|
|
94
|
+
{ tag: tags.heading, fontWeight: 'bold', color: '#f38ba8' },
|
|
95
|
+
{
|
|
96
|
+
tag: [tags.atom, tags.bool, tags.special(tags.variableName)],
|
|
97
|
+
color: '#fab387',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
tag: [tags.processingInstruction, tags.string, tags.inserted],
|
|
101
|
+
color: '#a6e3a1',
|
|
102
|
+
},
|
|
103
|
+
{ tag: tags.invalid, color: '#f38ba8' },
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
const latteTheme = EditorView.theme(
|
|
107
|
+
{
|
|
108
|
+
'&': { color: '#4c4f69', backgroundColor: '#eff1f5' },
|
|
109
|
+
'.cm-content': { caretColor: '#dc8a78' },
|
|
110
|
+
'.cm-cursor, .cm-dropCursor': { borderLeftColor: '#dc8a78' },
|
|
111
|
+
'&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection':
|
|
112
|
+
{ backgroundColor: '#acb0be44' },
|
|
113
|
+
'.cm-activeLine': { backgroundColor: '#acb0be15' },
|
|
114
|
+
'.cm-gutters': {
|
|
115
|
+
backgroundColor: '#eff1f5',
|
|
116
|
+
color: '#9ca0b0',
|
|
117
|
+
border: 'none',
|
|
118
|
+
},
|
|
119
|
+
'.cm-activeLineGutter': { backgroundColor: '#acb0be15' },
|
|
120
|
+
'.cm-selectionMatch': { backgroundColor: '#40a02b22' },
|
|
121
|
+
'&.cm-focused .cm-matchingBracket': { backgroundColor: '#acb0be44' },
|
|
122
|
+
'.cm-searchMatch': {
|
|
123
|
+
backgroundColor: '#df8e1d33',
|
|
124
|
+
outline: '1px solid #df8e1d66',
|
|
125
|
+
},
|
|
126
|
+
'.cm-searchMatch.cm-searchMatch-selected': { backgroundColor: '#40a02b33' },
|
|
127
|
+
'.cm-foldPlaceholder': {
|
|
128
|
+
backgroundColor: 'transparent',
|
|
129
|
+
border: 'none',
|
|
130
|
+
color: '#9ca0b0',
|
|
131
|
+
},
|
|
132
|
+
'.cm-tooltip': { border: 'none', backgroundColor: '#e6e9ef' },
|
|
133
|
+
'.cm-tooltip-autocomplete': {
|
|
134
|
+
'& > ul > li[aria-selected]': {
|
|
135
|
+
backgroundColor: '#ccd0da',
|
|
136
|
+
color: '#4c4f69',
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
{ dark: false },
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const latteHighlightStyle = HighlightStyle.define([
|
|
144
|
+
{ tag: tags.keyword, color: '#8839ef' },
|
|
145
|
+
{
|
|
146
|
+
tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName],
|
|
147
|
+
color: '#1e66f5',
|
|
148
|
+
},
|
|
149
|
+
{ tag: [tags.function(tags.variableName), tags.labelName], color: '#1e66f5' },
|
|
150
|
+
{
|
|
151
|
+
tag: [tags.color, tags.constant(tags.name), tags.standard(tags.name)],
|
|
152
|
+
color: '#fe640b',
|
|
153
|
+
},
|
|
154
|
+
{ tag: [tags.definition(tags.name), tags.separator], color: '#4c4f69' },
|
|
155
|
+
{
|
|
156
|
+
tag: [
|
|
157
|
+
tags.typeName,
|
|
158
|
+
tags.className,
|
|
159
|
+
tags.number,
|
|
160
|
+
tags.changed,
|
|
161
|
+
tags.annotation,
|
|
162
|
+
tags.modifier,
|
|
163
|
+
tags.self,
|
|
164
|
+
tags.namespace,
|
|
165
|
+
],
|
|
166
|
+
color: '#df8e1d',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
tag: [
|
|
170
|
+
tags.operator,
|
|
171
|
+
tags.operatorKeyword,
|
|
172
|
+
tags.url,
|
|
173
|
+
tags.escape,
|
|
174
|
+
tags.regexp,
|
|
175
|
+
tags.link,
|
|
176
|
+
tags.special(tags.string),
|
|
177
|
+
],
|
|
178
|
+
color: '#179299',
|
|
179
|
+
},
|
|
180
|
+
{ tag: [tags.meta, tags.comment], color: '#9ca0b0' },
|
|
181
|
+
{ tag: tags.strong, fontWeight: 'bold' },
|
|
182
|
+
{ tag: tags.emphasis, fontStyle: 'italic' },
|
|
183
|
+
{ tag: tags.strikethrough, textDecoration: 'line-through' },
|
|
184
|
+
{ tag: tags.link, color: '#179299', textDecoration: 'underline' },
|
|
185
|
+
{ tag: tags.heading, fontWeight: 'bold', color: '#d20f39' },
|
|
186
|
+
{
|
|
187
|
+
tag: [tags.atom, tags.bool, tags.special(tags.variableName)],
|
|
188
|
+
color: '#fe640b',
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
tag: [tags.processingInstruction, tags.string, tags.inserted],
|
|
192
|
+
color: '#40a02b',
|
|
193
|
+
},
|
|
194
|
+
{ tag: tags.invalid, color: '#d20f39' },
|
|
195
|
+
]);
|
|
196
|
+
|
|
197
|
+
export const darkTheme = [mochaTheme, syntaxHighlighting(mochaHighlightStyle)];
|
|
198
|
+
export const lightTheme = [latteTheme, syntaxHighlighting(latteHighlightStyle)];
|
package/test/App.test.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { page } from 'vitest/browser';
|
|
3
|
+
import { render } from 'vitest-browser-svelte';
|
|
4
|
+
import App from '../src/App.svelte';
|
|
5
|
+
|
|
6
|
+
describe('App', () => {
|
|
7
|
+
it('renders the heading', async () => {
|
|
8
|
+
render(App);
|
|
9
|
+
|
|
10
|
+
await expect.element(page.getByRole('heading', { name: 'Dent UI' })).toBeVisible();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('renders formatter settings fieldset', async () => {
|
|
14
|
+
render(App);
|
|
15
|
+
|
|
16
|
+
await expect.element(page.getByRole('group', { name: 'Formatter settings' })).toBeVisible();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('renders the Format button', async () => {
|
|
20
|
+
render(App);
|
|
21
|
+
|
|
22
|
+
await expect.element(page.getByRole('button', { name: 'Format' })).toBeVisible();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders the Clear button', async () => {
|
|
26
|
+
render(App);
|
|
27
|
+
|
|
28
|
+
await expect.element(page.getByRole('button', { name: 'Clear' })).toBeVisible();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('renders the Copy button as disabled initially', async () => {
|
|
32
|
+
render(App);
|
|
33
|
+
|
|
34
|
+
await expect.element(page.getByRole('button', { name: 'Copy' })).toBeDisabled();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('renders the theme selector', async () => {
|
|
38
|
+
render(App);
|
|
39
|
+
|
|
40
|
+
await expect.element(page.getByRole('combobox', { name: 'Color theme' })).toBeVisible();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('renders the auto-format checkbox checked by default', async () => {
|
|
44
|
+
render(App);
|
|
45
|
+
|
|
46
|
+
await expect.element(page.getByRole('checkbox', { name: 'Auto-format' })).toBeChecked();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders both editor panes', async () => {
|
|
50
|
+
render(App);
|
|
51
|
+
|
|
52
|
+
await expect.element(page.getByLabelText('NSIS input editor')).toBeVisible();
|
|
53
|
+
await expect.element(page.getByLabelText('Formatted output editor')).toBeVisible();
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { page } from 'vitest/browser';
|
|
3
|
+
import { render } from 'vitest-browser-svelte';
|
|
4
|
+
import Editor from '../src/Editor.svelte';
|
|
5
|
+
|
|
6
|
+
describe('Editor', () => {
|
|
7
|
+
it('renders a container div with aria-label', async () => {
|
|
8
|
+
render(Editor, {
|
|
9
|
+
props: { label: 'Test editor' },
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
await expect.element(page.getByLabelText('Test editor')).toBeVisible();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('calls oncreate with an EditorView instance', () => {
|
|
16
|
+
const oncreate = vi.fn();
|
|
17
|
+
|
|
18
|
+
render(Editor, {
|
|
19
|
+
props: { label: 'Test editor', oncreate },
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(oncreate).toHaveBeenCalledOnce();
|
|
23
|
+
const view = oncreate.mock.calls[0]?.[0];
|
|
24
|
+
expect(view).toBeDefined();
|
|
25
|
+
expect(typeof view?.dispatch).toBe('function');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('initialises with an empty document', () => {
|
|
29
|
+
const oncreate = vi.fn();
|
|
30
|
+
|
|
31
|
+
render(Editor, {
|
|
32
|
+
props: { label: 'Test editor', oncreate },
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const view = oncreate.mock.calls[0]?.[0];
|
|
36
|
+
expect(view?.state.doc.toString()).toBe('');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { darkTheme, editorTheme, lightTheme } from '../src/themes.ts';
|
|
3
|
+
|
|
4
|
+
describe('themes', () => {
|
|
5
|
+
it('exports editorTheme as a CodeMirror extension', () => {
|
|
6
|
+
expect(editorTheme).toBeDefined();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('exports darkTheme as an array of extensions', () => {
|
|
10
|
+
expect(Array.isArray(darkTheme)).toBe(true);
|
|
11
|
+
expect(darkTheme.length).toBe(2);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('exports lightTheme as an array of extensions', () => {
|
|
15
|
+
expect(Array.isArray(lightTheme)).toBe(true);
|
|
16
|
+
expect(lightTheme.length).toBe(2);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('darkTheme and lightTheme are different objects', () => {
|
|
20
|
+
expect(darkTheme).not.toBe(lightTheme);
|
|
21
|
+
expect(darkTheme[0]).not.toBe(lightTheme[0]);
|
|
22
|
+
});
|
|
23
|
+
});
|
package/tsconfig.json
ADDED
package/vite.config.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
|
2
|
+
import { playwright } from '@vitest/browser-playwright';
|
|
3
|
+
import { defineConfig } from 'vitest/config';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [
|
|
7
|
+
svelte({
|
|
8
|
+
compilerOptions: { css: 'injected' },
|
|
9
|
+
}),
|
|
10
|
+
],
|
|
11
|
+
resolve: {
|
|
12
|
+
conditions: ['browser'],
|
|
13
|
+
},
|
|
14
|
+
build: {
|
|
15
|
+
outDir: 'dist',
|
|
16
|
+
minify: true,
|
|
17
|
+
},
|
|
18
|
+
test: {
|
|
19
|
+
include: ['test/**/*.test.ts'],
|
|
20
|
+
browser: {
|
|
21
|
+
enabled: true,
|
|
22
|
+
provider: playwright(),
|
|
23
|
+
headless: true,
|
|
24
|
+
instances: [{ browser: 'chromium' }],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
});
|