mockaton 13.3.5 → 13.4.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/README.md +28 -16
- package/index.d.ts +1 -1
- package/index.js +1 -1
- package/package.json +1 -1
- package/src/client/ApiCommander.js +1 -1
- package/src/client/ApiConstants.js +2 -2
- package/src/client/IndexHtml.js +18 -18
- package/src/client/app-header.js +3 -3
- package/src/client/app-payload-viewer.js +4 -8
- package/src/client/app-store.js +3 -34
- package/src/client/app.js +3 -2
- package/src/client/dir/dittoSplitPaths.js +25 -0
- package/src/client/dir/dittoSplitPaths.test.js +28 -0
- package/src/client/{dir-tree.js → dir/groupByFolder.js} +2 -33
- package/src/client/{dir-tree.test.js → dir/groupByFolder.test.js} +2 -26
- package/src/client/graphics.js +2 -2
- package/src/client/utils/LocalStorage.js +69 -0
- package/src/client/utils/css.js +16 -0
- package/src/client/{dom-utils-test.js → utils/css.test.js} +1 -1
- package/src/client/utils/dom.js +68 -0
- package/src/client/utils/watcherDev.js +46 -0
- package/src/server/Api.js +16 -3
- package/src/server/MockDispatcher.js +11 -8
- package/src/server/Mockaton.js +16 -8
- package/src/server/Mockaton.test.js +14 -9
- package/src/server/ProxyRelay.js +9 -3
- package/src/server/{utils/UrlParsers.js → UrlParsers.js} +10 -4
- package/src/server/{utils/UrlParsers.test.js → UrlParsers.test.js} +14 -14
- package/src/server/config.js +2 -0
- package/src/server/utils/HttpServerResponse.js +18 -39
- package/src/server/{WatcherDevClient.js → utils/WatcherDevClient.js} +3 -17
- package/src/server/utils/fs.js +1 -1
- package/src/server/utils/logger.js +4 -4
- package/src/server/utils/mime.js +11 -11
- package/src/server/utils/mime.test.js +15 -11
- package/www/src/assets/openapi.json +147 -147
- package/src/client/dom-utils.js +0 -154
- package/src/client/watcherDev.js +0 -39
|
@@ -10,37 +10,97 @@
|
|
|
10
10
|
}
|
|
11
11
|
],
|
|
12
12
|
"paths": {
|
|
13
|
-
"/mockaton/
|
|
13
|
+
"/mockaton/state": {
|
|
14
|
+
"get": {
|
|
15
|
+
"summary": "Get complete Mockaton state",
|
|
16
|
+
"x-js-client-example": "await mockaton.getState()",
|
|
17
|
+
"responses": {
|
|
18
|
+
"200": {
|
|
19
|
+
"description": "Mockaton state",
|
|
20
|
+
"content": {
|
|
21
|
+
"application/json": {
|
|
22
|
+
"schema": {
|
|
23
|
+
"$ref": "#/components/schemas/State"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"/mockaton/write-mock": {
|
|
14
32
|
"patch": {
|
|
15
|
-
"summary": "
|
|
16
|
-
"description": "
|
|
17
|
-
"x-js-client-example": "await mockaton.
|
|
33
|
+
"summary": "Write a mock file",
|
|
34
|
+
"description": "Writes a mock file to the mocks directory. Requires `config.readOnly = false`",
|
|
35
|
+
"x-js-client-example": "await mockaton.writeMock('api/user/friends.GET.200.json', '{ \"friends\": [] }')",
|
|
36
|
+
"requestBody": {
|
|
37
|
+
"required": true,
|
|
38
|
+
"content": {
|
|
39
|
+
"application/json": {
|
|
40
|
+
"schema": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"minItems": 2,
|
|
43
|
+
"maxItems": 2,
|
|
44
|
+
"prefixItems": [
|
|
45
|
+
{
|
|
46
|
+
"$ref": "#/components/schemas/Filename"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"type": "string",
|
|
50
|
+
"description": "File content"
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
"additionalItems": false
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
18
58
|
"responses": {
|
|
19
59
|
"200": {
|
|
20
60
|
"description": "OK"
|
|
61
|
+
},
|
|
62
|
+
"403": {
|
|
63
|
+
"description": "Forbidden (read-only mode or outside mocksDir)"
|
|
21
64
|
}
|
|
22
65
|
}
|
|
23
66
|
}
|
|
24
67
|
},
|
|
25
|
-
"/mockaton/
|
|
68
|
+
"/mockaton/delete-mock": {
|
|
26
69
|
"patch": {
|
|
27
|
-
"summary": "
|
|
28
|
-
"
|
|
70
|
+
"summary": "Delete a mock file",
|
|
71
|
+
"description": "Deletes a mock file from the mocks directory. Requires `config.readOnly=false`",
|
|
72
|
+
"x-js-client-example": "await mockaton.deleteMock('api/user/friends.GET.200.json')",
|
|
29
73
|
"requestBody": {
|
|
30
74
|
"required": true,
|
|
31
75
|
"content": {
|
|
32
76
|
"application/json": {
|
|
33
77
|
"schema": {
|
|
34
|
-
"
|
|
35
|
-
"description": "Parentheses are optional, so you can pass a partial match. For example, passing `'demo-'` (without the final `a`) works too. On routes with many partial matches, their first mock in alphabetical order wins.",
|
|
36
|
-
"example": "(demo-a)"
|
|
78
|
+
"$ref": "#/components/schemas/Filename"
|
|
37
79
|
}
|
|
38
80
|
}
|
|
39
81
|
}
|
|
40
82
|
},
|
|
41
83
|
"responses": {
|
|
42
84
|
"200": {
|
|
43
|
-
"
|
|
85
|
+
"description": "OK"
|
|
86
|
+
},
|
|
87
|
+
"403": {
|
|
88
|
+
"description": "Forbidden (read-only mode or outside mocksDir)"
|
|
89
|
+
},
|
|
90
|
+
"422": {
|
|
91
|
+
"description": "Mock file does not exist"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"/mockaton/reset": {
|
|
97
|
+
"patch": {
|
|
98
|
+
"summary": "Re-initialize Mockaton",
|
|
99
|
+
"description": "The selected mocks, cookies, and delays go back to their defaults, but `proxyFallback`, `collectProxied`, and `corsAllowed` are not affected.",
|
|
100
|
+
"x-js-client-example": "await mockaton.reset()",
|
|
101
|
+
"responses": {
|
|
102
|
+
"200": {
|
|
103
|
+
"description": "OK"
|
|
44
104
|
}
|
|
45
105
|
}
|
|
46
106
|
}
|
|
@@ -77,57 +137,34 @@
|
|
|
77
137
|
}
|
|
78
138
|
}
|
|
79
139
|
},
|
|
80
|
-
"/mockaton/
|
|
140
|
+
"/mockaton/bulk-select-by-comment": {
|
|
81
141
|
"patch": {
|
|
82
|
-
"summary": "
|
|
83
|
-
"
|
|
84
|
-
"x-js-client-example": "await mockaton.toggleStatus('GET', '/api/user/friends', 500)",
|
|
142
|
+
"summary": "Select all mocks that have a particular comment",
|
|
143
|
+
"x-js-client-example": "await mockaton.bulkSelectByComment('(demo-a)')",
|
|
85
144
|
"requestBody": {
|
|
86
145
|
"required": true,
|
|
87
146
|
"content": {
|
|
88
147
|
"application/json": {
|
|
89
148
|
"schema": {
|
|
90
|
-
"type": "
|
|
91
|
-
"
|
|
92
|
-
"
|
|
93
|
-
|
|
94
|
-
{
|
|
95
|
-
"$ref": "#/components/schemas/Method"
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
"$ref": "#/components/schemas/UrlMask"
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
"type": "number",
|
|
102
|
-
"description": "HTTP Status to toggle",
|
|
103
|
-
"example": 500
|
|
104
|
-
}
|
|
105
|
-
],
|
|
106
|
-
"additionalItems": false
|
|
107
|
-
},
|
|
108
|
-
"example": [
|
|
109
|
-
"GET",
|
|
110
|
-
"/api/user",
|
|
111
|
-
500
|
|
112
|
-
]
|
|
149
|
+
"type": "string",
|
|
150
|
+
"description": "Parentheses are optional, so you can pass a partial match. For example, passing `'demo-'` (without the final `a`) works too. On routes with many partial matches, their first mock in alphabetical order wins.",
|
|
151
|
+
"example": "(demo-a)"
|
|
152
|
+
}
|
|
113
153
|
}
|
|
114
154
|
}
|
|
115
155
|
},
|
|
116
156
|
"responses": {
|
|
117
157
|
"200": {
|
|
118
158
|
"$ref": "#/components/responses/Broker"
|
|
119
|
-
},
|
|
120
|
-
"422": {
|
|
121
|
-
"description": "Route does not exist"
|
|
122
159
|
}
|
|
123
160
|
}
|
|
124
161
|
}
|
|
125
162
|
},
|
|
126
|
-
"/mockaton/
|
|
163
|
+
"/mockaton/toggle-status": {
|
|
127
164
|
"patch": {
|
|
128
|
-
"summary": "
|
|
129
|
-
"description": "
|
|
130
|
-
"x-js-client-example": "await mockaton.
|
|
165
|
+
"summary": "Toggle a specific status for a route",
|
|
166
|
+
"description": "Selects the first found mock with the given status, which could be the autogenerated one. Or, selects the default file.",
|
|
167
|
+
"x-js-client-example": "await mockaton.toggleStatus('GET', '/api/user/friends', 500)",
|
|
131
168
|
"requestBody": {
|
|
132
169
|
"required": true,
|
|
133
170
|
"content": {
|
|
@@ -144,9 +181,9 @@
|
|
|
144
181
|
"$ref": "#/components/schemas/UrlMask"
|
|
145
182
|
},
|
|
146
183
|
{
|
|
147
|
-
"type": "
|
|
148
|
-
"description": "
|
|
149
|
-
"example":
|
|
184
|
+
"type": "number",
|
|
185
|
+
"description": "HTTP Status to toggle",
|
|
186
|
+
"example": 500
|
|
150
187
|
}
|
|
151
188
|
],
|
|
152
189
|
"additionalItems": false
|
|
@@ -154,7 +191,7 @@
|
|
|
154
191
|
"example": [
|
|
155
192
|
"GET",
|
|
156
193
|
"/api/user",
|
|
157
|
-
|
|
194
|
+
500
|
|
158
195
|
]
|
|
159
196
|
}
|
|
160
197
|
}
|
|
@@ -164,7 +201,7 @@
|
|
|
164
201
|
"$ref": "#/components/responses/Broker"
|
|
165
202
|
},
|
|
166
203
|
"422": {
|
|
167
|
-
"description": "
|
|
204
|
+
"description": "Route does not exist"
|
|
168
205
|
}
|
|
169
206
|
}
|
|
170
207
|
}
|
|
@@ -266,70 +303,50 @@
|
|
|
266
303
|
}
|
|
267
304
|
}
|
|
268
305
|
},
|
|
269
|
-
"/mockaton/
|
|
306
|
+
"/mockaton/cookies": {
|
|
270
307
|
"patch": {
|
|
271
|
-
"summary": "
|
|
272
|
-
"x-js-client-example": "await mockaton.
|
|
308
|
+
"summary": "Select a cookie label",
|
|
309
|
+
"x-js-client-example": "await mockaton.selectCookie('Normal User')",
|
|
273
310
|
"requestBody": {
|
|
274
311
|
"required": true,
|
|
275
312
|
"content": {
|
|
276
313
|
"application/json": {
|
|
277
314
|
"schema": {
|
|
278
|
-
"type": "
|
|
279
|
-
"description": "
|
|
315
|
+
"type": "string",
|
|
316
|
+
"description": "Cookie key",
|
|
317
|
+
"example": "Normal User"
|
|
280
318
|
}
|
|
281
319
|
}
|
|
282
320
|
}
|
|
283
321
|
},
|
|
284
322
|
"responses": {
|
|
285
323
|
"200": {
|
|
286
|
-
"description": "
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
}
|
|
293
|
-
},
|
|
294
|
-
"/mockaton/fallback": {
|
|
295
|
-
"patch": {
|
|
296
|
-
"summary": "Set proxy fallback address",
|
|
297
|
-
"x-js-client-example": "await mockaton.setProxyFallback('http://example.test')",
|
|
298
|
-
"requestBody": {
|
|
299
|
-
"required": true,
|
|
300
|
-
"content": {
|
|
301
|
-
"application/json": {
|
|
302
|
-
"schema": {
|
|
303
|
-
"type": "string",
|
|
304
|
-
"description": "URL",
|
|
305
|
-
"example": "https://example.test"
|
|
324
|
+
"description": "Available cookie labels",
|
|
325
|
+
"content": {
|
|
326
|
+
"application/json": {
|
|
327
|
+
"schema": {
|
|
328
|
+
"$ref": "#/components/schemas/CookieSelectionList"
|
|
329
|
+
}
|
|
306
330
|
}
|
|
307
331
|
}
|
|
308
|
-
}
|
|
309
|
-
},
|
|
310
|
-
"responses": {
|
|
311
|
-
"200": {
|
|
312
|
-
"description": "OK"
|
|
313
332
|
},
|
|
314
333
|
"422": {
|
|
315
|
-
"description": "
|
|
334
|
+
"description": "Cookie key not found"
|
|
316
335
|
}
|
|
317
336
|
}
|
|
318
337
|
}
|
|
319
338
|
},
|
|
320
|
-
"/mockaton/
|
|
339
|
+
"/mockaton/cors": {
|
|
321
340
|
"patch": {
|
|
322
|
-
"summary": "Enable or disable
|
|
323
|
-
"
|
|
324
|
-
"x-js-client-example": "await mockaton.setCollectProxied(true)",
|
|
341
|
+
"summary": "Enable or disable CORS",
|
|
342
|
+
"x-js-client-example": "await mockaton.setCorsAllowed(false)",
|
|
325
343
|
"requestBody": {
|
|
326
344
|
"required": true,
|
|
327
345
|
"content": {
|
|
328
346
|
"application/json": {
|
|
329
347
|
"schema": {
|
|
330
348
|
"type": "boolean",
|
|
331
|
-
"description": "
|
|
332
|
-
"example": true
|
|
349
|
+
"description": "Is enabled?"
|
|
333
350
|
}
|
|
334
351
|
}
|
|
335
352
|
}
|
|
@@ -344,51 +361,45 @@
|
|
|
344
361
|
}
|
|
345
362
|
}
|
|
346
363
|
},
|
|
347
|
-
"/mockaton/
|
|
364
|
+
"/mockaton/fallback": {
|
|
348
365
|
"patch": {
|
|
349
|
-
"summary": "
|
|
350
|
-
"x-js-client-example": "await mockaton.
|
|
366
|
+
"summary": "Set proxy fallback address",
|
|
367
|
+
"x-js-client-example": "await mockaton.setProxyFallback('http://example.test')",
|
|
351
368
|
"requestBody": {
|
|
352
369
|
"required": true,
|
|
353
370
|
"content": {
|
|
354
371
|
"application/json": {
|
|
355
372
|
"schema": {
|
|
356
373
|
"type": "string",
|
|
357
|
-
"description": "
|
|
358
|
-
"example": "
|
|
374
|
+
"description": "URL",
|
|
375
|
+
"example": "https://example.test"
|
|
359
376
|
}
|
|
360
377
|
}
|
|
361
378
|
}
|
|
362
379
|
},
|
|
363
380
|
"responses": {
|
|
364
381
|
"200": {
|
|
365
|
-
"description": "
|
|
366
|
-
"content": {
|
|
367
|
-
"application/json": {
|
|
368
|
-
"schema": {
|
|
369
|
-
"$ref": "#/components/schemas/CookieSelectionList"
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
382
|
+
"description": "OK"
|
|
373
383
|
},
|
|
374
384
|
"422": {
|
|
375
|
-
"description": "
|
|
385
|
+
"description": "Invalid Proxy Fallback URL"
|
|
376
386
|
}
|
|
377
387
|
}
|
|
378
388
|
}
|
|
379
389
|
},
|
|
380
|
-
"/mockaton/
|
|
390
|
+
"/mockaton/collect-proxied": {
|
|
381
391
|
"patch": {
|
|
382
|
-
"summary": "Enable or disable
|
|
383
|
-
"description": "
|
|
384
|
-
"x-js-client-example": "await mockaton.
|
|
392
|
+
"summary": "Enable or disable collection of proxied responses",
|
|
393
|
+
"description": "Applicable only when there’s a proxy fallback server URL already set. See `PATCH /mockaton/fallback`",
|
|
394
|
+
"x-js-client-example": "await mockaton.setCollectProxied(true)",
|
|
385
395
|
"requestBody": {
|
|
386
396
|
"required": true,
|
|
387
397
|
"content": {
|
|
388
398
|
"application/json": {
|
|
389
399
|
"schema": {
|
|
390
400
|
"type": "boolean",
|
|
391
|
-
"description": "
|
|
401
|
+
"description": "Should Collect?",
|
|
402
|
+
"example": true
|
|
392
403
|
}
|
|
393
404
|
}
|
|
394
405
|
}
|
|
@@ -398,59 +409,69 @@
|
|
|
398
409
|
"description": "OK"
|
|
399
410
|
},
|
|
400
411
|
"422": {
|
|
401
|
-
"description": "
|
|
412
|
+
"description": "Expected boolean"
|
|
402
413
|
}
|
|
403
414
|
}
|
|
404
415
|
}
|
|
405
416
|
},
|
|
406
|
-
"/mockaton/
|
|
417
|
+
"/mockaton/proxied": {
|
|
407
418
|
"patch": {
|
|
408
|
-
"summary": "
|
|
409
|
-
"description": "
|
|
410
|
-
"x-js-client-example": "await mockaton.
|
|
419
|
+
"summary": "Set whether a route is proxied",
|
|
420
|
+
"description": "Applicable only when there’s a proxy fallback server URL already set. See `PATCH /mockaton/fallback`",
|
|
421
|
+
"x-js-client-example": "await mockaton.setRouteIsProxied('GET', '/api/user/friends', true)",
|
|
411
422
|
"requestBody": {
|
|
412
423
|
"required": true,
|
|
413
424
|
"content": {
|
|
414
425
|
"application/json": {
|
|
415
426
|
"schema": {
|
|
416
427
|
"type": "array",
|
|
417
|
-
"minItems":
|
|
418
|
-
"maxItems":
|
|
428
|
+
"minItems": 3,
|
|
429
|
+
"maxItems": 3,
|
|
419
430
|
"prefixItems": [
|
|
420
431
|
{
|
|
421
|
-
"$ref": "#/components/schemas/
|
|
432
|
+
"$ref": "#/components/schemas/Method"
|
|
422
433
|
},
|
|
423
434
|
{
|
|
424
|
-
"
|
|
425
|
-
|
|
435
|
+
"$ref": "#/components/schemas/UrlMask"
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
"type": "boolean",
|
|
439
|
+
"description": "proxied",
|
|
440
|
+
"example": true
|
|
426
441
|
}
|
|
427
442
|
],
|
|
428
443
|
"additionalItems": false
|
|
429
|
-
}
|
|
444
|
+
},
|
|
445
|
+
"example": [
|
|
446
|
+
"GET",
|
|
447
|
+
"/api/user",
|
|
448
|
+
true
|
|
449
|
+
]
|
|
430
450
|
}
|
|
431
451
|
}
|
|
432
452
|
},
|
|
433
453
|
"responses": {
|
|
434
454
|
"200": {
|
|
435
|
-
"
|
|
455
|
+
"$ref": "#/components/responses/Broker"
|
|
436
456
|
},
|
|
437
|
-
"
|
|
438
|
-
"description": "
|
|
457
|
+
"422": {
|
|
458
|
+
"description": "Invalid request. Possible reasons:\n- Route does not exist\n- Expected boolean for `proxied`\n- No `proxyFallback` configured\n"
|
|
439
459
|
}
|
|
440
460
|
}
|
|
441
461
|
}
|
|
442
462
|
},
|
|
443
|
-
"/mockaton/
|
|
463
|
+
"/mockaton/watch-mocks": {
|
|
444
464
|
"patch": {
|
|
445
|
-
"summary": "
|
|
446
|
-
"description": "
|
|
447
|
-
"x-js-client-example": "await mockaton.
|
|
465
|
+
"summary": "Enable or disable mock file watching",
|
|
466
|
+
"description": "Controls file watchers for mocksDir.",
|
|
467
|
+
"x-js-client-example": "await mockaton.setWatchMocks(true)",
|
|
448
468
|
"requestBody": {
|
|
449
469
|
"required": true,
|
|
450
470
|
"content": {
|
|
451
471
|
"application/json": {
|
|
452
472
|
"schema": {
|
|
453
|
-
"
|
|
473
|
+
"type": "boolean",
|
|
474
|
+
"description": "true to start watchers, false to stop them"
|
|
454
475
|
}
|
|
455
476
|
}
|
|
456
477
|
}
|
|
@@ -459,29 +480,8 @@
|
|
|
459
480
|
"200": {
|
|
460
481
|
"description": "OK"
|
|
461
482
|
},
|
|
462
|
-
"403": {
|
|
463
|
-
"description": "Forbidden (read-only mode or outside mocksDir)"
|
|
464
|
-
},
|
|
465
483
|
"422": {
|
|
466
|
-
"description": "
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
},
|
|
471
|
-
"/mockaton/state": {
|
|
472
|
-
"get": {
|
|
473
|
-
"summary": "Get complete Mockaton state",
|
|
474
|
-
"x-js-client-example": "await mockaton.getState()",
|
|
475
|
-
"responses": {
|
|
476
|
-
"200": {
|
|
477
|
-
"description": "Mockaton state",
|
|
478
|
-
"content": {
|
|
479
|
-
"application/json": {
|
|
480
|
-
"schema": {
|
|
481
|
-
"$ref": "#/components/schemas/State"
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
}
|
|
484
|
+
"description": "Invalid input - expected boolean"
|
|
485
485
|
}
|
|
486
486
|
}
|
|
487
487
|
}
|
package/src/client/dom-utils.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
export function t(translation) {
|
|
2
|
-
return translation[0]
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
export function classNames(...args) {
|
|
6
|
-
return args.filter(Boolean).join(' ')
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function createElement(tag, props, ...children) {
|
|
10
|
-
const elem = document.createElement(tag)
|
|
11
|
-
if (props)
|
|
12
|
-
for (const [k, v] of Object.entries(props))
|
|
13
|
-
if (v === undefined) continue
|
|
14
|
-
else if (k === 'ref') v.elem = elem
|
|
15
|
-
else if (k === 'style') Object.assign(elem.style, v)
|
|
16
|
-
else if (k.startsWith('on')) elem.addEventListener(k.slice(2).toLowerCase(), ...[v].flat())
|
|
17
|
-
else if (k in elem) elem[k] = v
|
|
18
|
-
else elem.setAttribute(k, v)
|
|
19
|
-
elem.append(...children.flat().filter(Boolean))
|
|
20
|
-
return elem
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function createSvgElement(tag, props, ...children) {
|
|
24
|
-
const elem = document.createElementNS('http://www.w3.org/2000/svg', tag)
|
|
25
|
-
for (const [k, v] of Object.entries(props))
|
|
26
|
-
elem.setAttribute(k, v)
|
|
27
|
-
elem.append(...children.flat().filter(Boolean))
|
|
28
|
-
return elem
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function Fragment(...args) {
|
|
32
|
-
const frag = new DocumentFragment()
|
|
33
|
-
for (const arg of args)
|
|
34
|
-
if (Array.isArray(arg))
|
|
35
|
-
frag.append(...arg)
|
|
36
|
-
else
|
|
37
|
-
frag.appendChild(arg)
|
|
38
|
-
return frag
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
export function restoreFocus(cb) {
|
|
43
|
-
const focusQuery = selectorFor(document.activeElement)
|
|
44
|
-
cb()
|
|
45
|
-
if (focusQuery)
|
|
46
|
-
document.querySelector(focusQuery)?.focus()
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function selectorFor(elem) {
|
|
50
|
-
if (!(elem instanceof Element))
|
|
51
|
-
return
|
|
52
|
-
const path = []
|
|
53
|
-
while (elem) {
|
|
54
|
-
let qualifier = ''
|
|
55
|
-
if (elem.hasAttribute('key'))
|
|
56
|
-
qualifier = `[key="${elem.getAttribute('key')}"]`
|
|
57
|
-
else {
|
|
58
|
-
let i = 0
|
|
59
|
-
let sib = elem
|
|
60
|
-
while ((sib = sib.previousElementSibling))
|
|
61
|
-
if (sib.tagName === elem.tagName)
|
|
62
|
-
i++
|
|
63
|
-
if (i)
|
|
64
|
-
qualifier = `:nth-of-type(${i + 1})`
|
|
65
|
-
}
|
|
66
|
-
path.push(elem.tagName + qualifier)
|
|
67
|
-
elem = elem.parentElement
|
|
68
|
-
}
|
|
69
|
-
return path.reverse().join('>')
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
export function extractClassNames({ cssRules }) {
|
|
74
|
-
// Class names must begin with _ or a letter, then it can have numbers and hyphens
|
|
75
|
-
// TODO think about tag.className selectors
|
|
76
|
-
const reClassName = /(?:^|[\s,{>])&?\s*\.([a-zA-Z_][\w-]*)/g
|
|
77
|
-
const cNames = {}
|
|
78
|
-
let match
|
|
79
|
-
for (const rule of cssRules)
|
|
80
|
-
while (match = reClassName.exec(rule.cssText))
|
|
81
|
-
cNames[match[1]] = match[1]
|
|
82
|
-
return cNames
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
export class QueryParamBool {
|
|
87
|
-
constructor(param) {
|
|
88
|
-
this.param = param
|
|
89
|
-
this.value = this.#init()
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
#init() {
|
|
93
|
-
const qs = new URLSearchParams(globalThis.location?.search)
|
|
94
|
-
if (qs.has(this.param))
|
|
95
|
-
return qs.get(this.param) !== '0'
|
|
96
|
-
const stored = globalThis.localStorage?.getItem(this.param) !== '0'
|
|
97
|
-
if (!stored)
|
|
98
|
-
this.#applyToUrl(false)
|
|
99
|
-
return stored
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
toggle() {
|
|
103
|
-
this.value = !this.value
|
|
104
|
-
if (this.value)
|
|
105
|
-
globalThis.localStorage?.removeItem(this.param)
|
|
106
|
-
else
|
|
107
|
-
globalThis.localStorage?.setItem(this.param, '0')
|
|
108
|
-
this.#applyToUrl(this.value)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
#applyToUrl(nextVal) {
|
|
112
|
-
const url = new URL(globalThis.location?.href)
|
|
113
|
-
if (nextVal)
|
|
114
|
-
url.searchParams.delete(this.param)
|
|
115
|
-
else
|
|
116
|
-
url.searchParams.set(this.param, '0')
|
|
117
|
-
history.replaceState(null, '', url)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
export class LocalStorageSet {
|
|
123
|
-
constructor(key) {
|
|
124
|
-
this.key = key
|
|
125
|
-
this.value = this.#parse()
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
add(item) {
|
|
129
|
-
this.value.add(item)
|
|
130
|
-
this.#persist()
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
delete(item) {
|
|
134
|
-
this.value.delete(item)
|
|
135
|
-
this.#persist()
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
has(item) {
|
|
139
|
-
return this.value.has(item)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
#parse() {
|
|
143
|
-
try {
|
|
144
|
-
return new Set(JSON.parse(globalThis.localStorage?.getItem(this.key) || '[]'))
|
|
145
|
-
}
|
|
146
|
-
catch {
|
|
147
|
-
return new Set()
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
#persist() {
|
|
152
|
-
globalThis.localStorage?.setItem(this.key, JSON.stringify([...this.value]))
|
|
153
|
-
}
|
|
154
|
-
}
|
package/src/client/watcherDev.js
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { API } from './ApiConstants.js'
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
let conn = null
|
|
5
|
-
let timer = null
|
|
6
|
-
|
|
7
|
-
window.addEventListener('beforeunload', teardown)
|
|
8
|
-
connect()
|
|
9
|
-
function connect() {
|
|
10
|
-
if (conn) return
|
|
11
|
-
|
|
12
|
-
clearTimeout(timer)
|
|
13
|
-
conn = new EventSource(API.watchHotReload)
|
|
14
|
-
|
|
15
|
-
conn.onmessage = function (event) {
|
|
16
|
-
const file = event.data
|
|
17
|
-
if (file.endsWith('.css'))
|
|
18
|
-
hotReloadCSS(file)
|
|
19
|
-
else if (file)
|
|
20
|
-
location.reload()
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
conn.onerror = function () {
|
|
24
|
-
console.error('hot reload')
|
|
25
|
-
teardown()
|
|
26
|
-
timer = setTimeout(connect, 3000)
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function teardown() {
|
|
31
|
-
clearTimeout(timer)
|
|
32
|
-
conn?.close()
|
|
33
|
-
conn = null
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
async function hotReloadCSS(file) {
|
|
37
|
-
const mod = await import(`./${file}?${Date.now()}`, { with: { type: 'css' } })
|
|
38
|
-
document.adoptedStyleSheets = [mod.default]
|
|
39
|
-
}
|