evikit 2.13.0 → 2.14.1

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 CHANGED
@@ -59,6 +59,7 @@ Our language will be standard modern JavaScript as supported by Node.js directly
59
59
  - [File upload](#file-upload)
60
60
  - [Translation](#translation)
61
61
  - [Styling](#styling)
62
+ - [EviKit CSS](#evikit-css)
62
63
  - _TODO_ [Testing](#testing)
63
64
  - [Linting](#linting)
64
65
  - [esbuild](#esbuild)
@@ -881,7 +882,9 @@ export function acceptLanguages() {
881
882
 
882
883
  ### Styling
883
884
 
884
- EviKit is decoupled from styling, it's up to you how to design your app. I've had good experience with [daisyUI](https://daisyui.com/). Let's see how to set it up.
885
+ #### Tailwind and daisyUI
886
+
887
+ I've had good experience styling apps with [daisyUI](https://daisyui.com/). Let's see how to set it up.
885
888
 
886
889
  ```sh
887
890
  npm i -D @tailwindcss/vite daisyui
@@ -925,6 +928,126 @@ Change your `index.html` like this:
925
928
 
926
929
  Style your app however you like.
927
930
 
931
+ #### EviKit CSS
932
+
933
+ There's a stylesheet with a CSS reset and a small number of utility classes named like Tailwind and daisyUI. I use it to build my EviKit-based apps [with esbuild](#esbuild) and no plugins:
934
+
935
+ - [EviKit's own landing page](https://codeberg.org/nykula/create-evikit)
936
+ - [Lanquiz trivia game](https://codeberg.org/nykula/lanquiz)
937
+ - [Yoself container monitoring tool](https://codeberg.org/nykula/yoself)
938
+
939
+ The styles are written from scratch and aren't meant to replicate Tailwind exactly, just make apps look acceptable. In particular, they use a different palette, [X11 colors](https://www.w3.org/TR/css-color-3/) as provided by CSS itself; think late 90s, not web3.
940
+
941
+ To try them, import EviKit in `index.css`:
942
+
943
+ ```css
944
+ @import "evikit/css";
945
+ ```
946
+
947
+ And change your `index.html` like in the above instructions for Tailwind.
948
+
949
+ Margin:
950
+
951
+ - m-8
952
+ - mb-2 mb-4 sm:mb-4 -mb-4 mb-6 mb-8
953
+ - mt-0 mt-2 mt-4 -mt-4 mt-6
954
+ - mx-0 mx-auto
955
+ - my-2 my-4 my-8
956
+
957
+ Padding:
958
+
959
+ - p-4 p-6 p-8 lg:p-8
960
+ - pb-8
961
+
962
+ Gap:
963
+
964
+ - gap-2 gap-4 gap-8
965
+ - gap-x-4
966
+ - gap-y-6
967
+
968
+ Width:
969
+
970
+ - max-w-full sm:max-w-sm max-w-lg max-w-2xl max-w-5xl
971
+ - w-32 w-auto w-fit w-full
972
+
973
+ Height:
974
+
975
+ - h-auto
976
+ - min-h-screen
977
+
978
+ Font:
979
+
980
+ - font-thin font-semibold font-bold
981
+ - tabular-nums
982
+ - text-base
983
+ - text-sm lg:text-sm
984
+ - text-lg
985
+ - text-xl lg:text-xl
986
+ - text-2xl
987
+ - lg:text-3xl
988
+ - text-4xl lg:text-4xl
989
+ - tracking-wider
990
+ - uppercase
991
+ - whitespace-nowrap
992
+
993
+ Image:
994
+
995
+ - bg-center
996
+ - bg-cover
997
+ - object-contain
998
+
999
+ Layout:
1000
+
1001
+ - absolute right-2 top-2
1002
+ - contents
1003
+ - flex flex-col flex-wrap
1004
+ - grid
1005
+ - grid-cols-1
1006
+ - grid-cols-2 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-2
1007
+ - lg:grid-cols-3
1008
+ - lg:grid-cols-4
1009
+ - 2xl:grid-cols-6
1010
+ - hidden lg:hidden
1011
+ - items-center justify-center
1012
+ - overflow-auto overflow-hidden
1013
+ - row-span-2
1014
+ - text-center sm:text-center md:text-center
1015
+
1016
+ Border:
1017
+
1018
+ - border lg:border
1019
+ - border-b
1020
+ - !border-none lg:border-none
1021
+ - rounded-lg lg:rounded-lg
1022
+ - rounded-xl
1023
+ - shadow-sm lg:shadow-sm
1024
+
1025
+ Component:
1026
+
1027
+ - badge badge-info
1028
+ - btn btn-ghost btn-disabled
1029
+ - fieldset fieldset-legend label input
1030
+ - link
1031
+ - lg:table table-zebra
1032
+
1033
+ Color:
1034
+
1035
+ - opacity-0 opacity-50
1036
+ - bg-gray-50 bg-gray-100
1037
+ - !bg-transparent
1038
+ - border-gray-100
1039
+ - text-green-400
1040
+ - text-orange-400
1041
+ - text-red-400
1042
+ - fill-amber-300/[50%] stroke-amber-500 text-amber-400
1043
+ - fill-cyan-300/[50%] stroke-cyan-500 text-cyan-400
1044
+ - fill-fuchsia-300/[50%] stroke-fuchsia-500 text-fuchsia-400
1045
+ - fill-indigo-300/[50%] stroke-indigo-500 text-indigo-400
1046
+ - fill-lime-300/[50%] stroke-lime-500 text-lime-400
1047
+ - fill-purple-300/[50%] stroke-purple-500 text-purple-400
1048
+ - fill-teal-300/[50%] stroke-teal-500 text-teal-400
1049
+ - fill-yellow-300/[50%] stroke-yellow-500 text-yellow-400
1050
+
928
1051
  ### Background worker
929
1052
 
930
1053
  You can run something repeatedly in main process, not in listeners for each CPU core. In development, it will restart whenever you edit api routes.
@@ -1007,7 +1130,7 @@ You can add these commands (without npx) as `format` and `lint` scripts in your
1007
1130
 
1008
1131
  ## esbuild
1009
1132
 
1010
- If you don't need Tailwind (e.g. `index.css` is enough for you) or bundle analyzer, and are ok with full-page refresh instead of hot module replacement, you can have EviKit use [esbuild](https://esbuild.github.io/) as a smaller alternative to Vite.
1133
+ If you don't need Tailwind (e.g. [EviKit CSS](#evikit-css) is enough for you) or bundle analyzer, and are ok with full-page refresh instead of hot module replacement, you can have EviKit use [esbuild](https://esbuild.github.io/) as a smaller alternative to Vite.
1011
1134
 
1012
1135
  ```sh
1013
1136
  npm i -D esbuild
package/index.css ADDED
@@ -0,0 +1,717 @@
1
+ * {
2
+ color: inherit;
3
+ font: inherit;
4
+ list-style: none;
5
+ margin: 0;
6
+ padding: 0;
7
+ text-align: inherit;
8
+ text-decoration: none;
9
+ }
10
+
11
+ html {
12
+ font-family: system-ui, sans-serif;
13
+ line-height: 1.5rem;
14
+ }
15
+
16
+ b {
17
+ font-weight: bold;
18
+ }
19
+
20
+ code {
21
+ font-family: monospace;
22
+ }
23
+
24
+ pre {
25
+ font-family: monospace;
26
+ font-size: 1rem;
27
+ }
28
+
29
+ summary {
30
+ list-style: revert;
31
+ }
32
+
33
+ /* Margin. */
34
+
35
+ .m-8 {
36
+ margin: 2rem;
37
+ }
38
+
39
+ .mb-2 {
40
+ margin-bottom: 0.5rem;
41
+ }
42
+
43
+ .mb-4 {
44
+ margin-bottom: 1rem;
45
+ }
46
+
47
+ @media (min-width: 640px) {
48
+ .sm\:mb-4 {
49
+ margin-bottom: 1rem;
50
+ }
51
+ }
52
+
53
+ .\-mb-4 {
54
+ margin-bottom: -1rem;
55
+ }
56
+
57
+ .mb-6 {
58
+ margin-bottom: 1.5rem;
59
+ }
60
+
61
+ .mb-8 {
62
+ margin-bottom: 2rem;
63
+ }
64
+
65
+ .mt-0 {
66
+ margin-top: 0;
67
+ }
68
+
69
+ .mt-2 {
70
+ margin-top: 0.5rem;
71
+ }
72
+
73
+ .mt-4 {
74
+ margin-top: 1rem;
75
+ }
76
+
77
+ .-mt-4 {
78
+ margin-top: -1rem;
79
+ }
80
+
81
+ .mt-6 {
82
+ margin-top: 1.5rem;
83
+ }
84
+
85
+ .mx-0 {
86
+ margin-left: 0;
87
+ margin-right: 0;
88
+ }
89
+
90
+ .mx-auto {
91
+ margin-left: auto;
92
+ margin-right: auto;
93
+ }
94
+
95
+ .my-2 {
96
+ margin-bottom: 0.5rem;
97
+ margin-top: 0.5rem;
98
+ }
99
+
100
+ .my-4 {
101
+ margin-bottom: 1rem;
102
+ margin-top: 1rem;
103
+ }
104
+
105
+ .my-8 {
106
+ margin-bottom: 2rem;
107
+ margin-top: 2rem;
108
+ }
109
+
110
+ /* Padding. */
111
+
112
+ .p-4 {
113
+ padding: 1rem;
114
+ }
115
+
116
+ .p-6 {
117
+ padding: 1.5rem;
118
+ }
119
+
120
+ .p-8 {
121
+ padding: 2rem;
122
+ }
123
+
124
+ @media (min-width: 992px) {
125
+ .lg\:p-8 {
126
+ padding: 2rem;
127
+ }
128
+ }
129
+
130
+ .pb-8 {
131
+ padding-bottom: 2rem;
132
+ }
133
+
134
+ /* Gap. */
135
+
136
+ .gap-2 {
137
+ gap: 0.5rem;
138
+ }
139
+
140
+ .gap-4 {
141
+ gap: 1rem;
142
+ }
143
+
144
+ .gap-8 {
145
+ gap: 2rem;
146
+ }
147
+
148
+ .gap-x-4 {
149
+ column-gap: 1rem;
150
+ }
151
+
152
+ .gap-y-6 {
153
+ row-gap: 1.5rem;
154
+ }
155
+
156
+ /* Width. */
157
+
158
+ .max-w-full {
159
+ max-width: 100%;
160
+ }
161
+
162
+ @media (min-width: 640px) {
163
+ .sm\:max-w-sm {
164
+ max-width: 22rem;
165
+ }
166
+ }
167
+
168
+ .max-w-lg {
169
+ max-width: 32rem;
170
+ }
171
+
172
+ .max-w-2xl {
173
+ max-width: 40rem;
174
+ }
175
+
176
+ .max-w-5xl {
177
+ max-width: 60rem;
178
+ }
179
+
180
+ .w-32 {
181
+ width: 8rem;
182
+ }
183
+
184
+ .w-auto {
185
+ width: auto;
186
+ }
187
+
188
+ .w-fit {
189
+ width: fit-content;
190
+ }
191
+
192
+ .w-full {
193
+ width: 100%;
194
+ }
195
+
196
+ /* Height. */
197
+
198
+ .h-auto {
199
+ height: auto;
200
+ }
201
+
202
+ .min-h-screen {
203
+ min-height: 100vh;
204
+ }
205
+
206
+ /* Font. */
207
+
208
+ .font-thin {
209
+ font-weight: 300;
210
+ }
211
+
212
+ .font-semibold {
213
+ font-weight: 600;
214
+ }
215
+
216
+ .font-bold {
217
+ font-weight: 700;
218
+ }
219
+
220
+ .tabular-nums {
221
+ font-variant-numeric: tabular-nums;
222
+ }
223
+
224
+ .text-base {
225
+ font-size: 1rem;
226
+ line-height: 1.5rem;
227
+ }
228
+
229
+ .text-sm {
230
+ font-size: 0.875rem;
231
+ line-height: 1.25rem;
232
+ }
233
+
234
+ @media (min-width: 992px) {
235
+ .lg\:text-sm {
236
+ font-size: 0.875rem;
237
+ line-height: 1.25rem;
238
+ }
239
+ }
240
+
241
+ .text-lg {
242
+ font-size: 1.125rem;
243
+ line-height: 1.625rem;
244
+ }
245
+
246
+ .text-xl {
247
+ font-size: 1.25rem;
248
+ line-height: 1.75rem;
249
+ }
250
+
251
+ @media (min-width: 992px) {
252
+ .lg\:text-xl {
253
+ font-size: 1.25rem;
254
+ line-height: 1.75rem;
255
+ }
256
+ }
257
+
258
+ .text-2xl {
259
+ font-size: 1.5rem;
260
+ line-height: 2.125rem;
261
+ }
262
+
263
+ @media (min-width: 992px) {
264
+ .lg\:text-3xl {
265
+ font-size: 1.875rem;
266
+ line-height: 2.25rem;
267
+ }
268
+ }
269
+
270
+ .text-4xl {
271
+ font-size: 2.25rem;
272
+ line-height: 2.75rem;
273
+ }
274
+
275
+ @media (min-width: 992px) {
276
+ .lg\:text-4xl {
277
+ font-size: 2.25rem;
278
+ line-height: 2.75rem;
279
+ }
280
+ }
281
+
282
+ .tracking-wider {
283
+ letter-spacing: 0.05rem;
284
+ }
285
+
286
+ .uppercase {
287
+ text-transform: uppercase;
288
+ }
289
+
290
+ .whitespace-nowrap {
291
+ white-space: nowrap;
292
+ }
293
+
294
+ /* Image. */
295
+
296
+ .bg-center {
297
+ background-position: center;
298
+ }
299
+
300
+ .bg-cover {
301
+ background-size: cover;
302
+ }
303
+
304
+ .object-contain {
305
+ object-fit: contain;
306
+ }
307
+
308
+ /* Layout. */
309
+
310
+ .absolute {
311
+ position: absolute;
312
+ }
313
+
314
+ .contents {
315
+ display: contents;
316
+ }
317
+
318
+ .flex {
319
+ display: flex;
320
+ }
321
+
322
+ .flex-col {
323
+ flex-direction: column;
324
+ }
325
+
326
+ .flex-wrap {
327
+ flex-wrap: wrap;
328
+ }
329
+
330
+ .grid {
331
+ display: grid;
332
+ }
333
+
334
+ .grid-cols-1 {
335
+ grid-template-columns: 1fr;
336
+ }
337
+
338
+ .grid-cols-2 {
339
+ grid-template-columns: 1fr 1fr;
340
+ }
341
+
342
+ @media (min-width: 640px) {
343
+ .sm\:grid-cols-2 {
344
+ grid-template-columns: 1fr 1fr;
345
+ }
346
+ }
347
+
348
+ @media (min-width: 768px) {
349
+ .md\:grid-cols-2 {
350
+ grid-template-columns: 1fr 1fr;
351
+ }
352
+ }
353
+
354
+ @media (min-width: 992px) {
355
+ .lg\:grid-cols-2 {
356
+ grid-template-columns: 1fr 1fr;
357
+ }
358
+ }
359
+
360
+ @media (min-width: 992px) {
361
+ .lg\:grid-cols-3 {
362
+ grid-template-columns: 1fr 1fr 1fr;
363
+ }
364
+ }
365
+
366
+ @media (min-width: 992px) {
367
+ .lg\:grid-cols-4 {
368
+ grid-template-columns: 1fr 1fr 1fr 1fr;
369
+ }
370
+ }
371
+
372
+ @media (min-width: 1200px) {
373
+ .\2xl\:grid-cols-6 {
374
+ grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr;
375
+ }
376
+ }
377
+
378
+ .hidden {
379
+ display: none;
380
+ }
381
+
382
+ @media (min-width: 992px) {
383
+ .lg\:hidden {
384
+ display: none;
385
+ }
386
+ }
387
+
388
+ .items-center {
389
+ align-items: center;
390
+ }
391
+
392
+ .justify-center {
393
+ justify-content: center;
394
+ }
395
+
396
+ .overflow-auto {
397
+ overflow: auto;
398
+ }
399
+
400
+ .overflow-hidden {
401
+ overflow: hidden;
402
+ }
403
+
404
+ .right-2 {
405
+ right: 0.5rem;
406
+ }
407
+
408
+ .row-span-2 {
409
+ grid-row: span 2;
410
+ }
411
+
412
+ .text-center {
413
+ text-align: center;
414
+ }
415
+
416
+ @media (min-width: 640px) {
417
+ .sm\:text-center {
418
+ text-align: center;
419
+ }
420
+ }
421
+
422
+ @media (min-width: 768px) {
423
+ .md\:text-center {
424
+ text-align: center;
425
+ }
426
+ }
427
+
428
+ .top-2 {
429
+ top: 0.5rem;
430
+ }
431
+
432
+ /* Border. */
433
+
434
+ .border {
435
+ border: 1px solid;
436
+ }
437
+
438
+ @media (min-width: 992px) {
439
+ .lg\:border {
440
+ border: 1px solid;
441
+ }
442
+ }
443
+
444
+ .border-b {
445
+ border-bottom: 1px solid;
446
+ }
447
+
448
+ .\!border-none {
449
+ border: none !important;
450
+ }
451
+
452
+ @media (min-width: 992px) {
453
+ .lg\:border-none {
454
+ border: none;
455
+ }
456
+ }
457
+
458
+ .rounded-lg {
459
+ border-radius: 0.5rem;
460
+ }
461
+
462
+ @media (min-width: 992px) {
463
+ .lg\:rounded-lg {
464
+ border-radius: 0.5rem;
465
+ }
466
+ }
467
+
468
+ .rounded-xl {
469
+ border-radius: 1rem;
470
+ }
471
+
472
+ .shadow-sm {
473
+ box-shadow: 0 0.125em 0.25em rgba(0, 0, 0, 0.1);
474
+ }
475
+
476
+ @media (min-width: 992px) {
477
+ .lg\:shadow-sm {
478
+ box-shadow: 0 0.125em 0.25em rgba(0, 0, 0, 0.1);
479
+ }
480
+ }
481
+
482
+ /* Component. */
483
+
484
+ .badge {
485
+ border-radius: 0.5rem;
486
+ display: inline-flex;
487
+ gap: 0.5rem;
488
+ font-size: 0.875rem;
489
+ padding: 0.0625rem 0.75rem;
490
+ }
491
+
492
+ .badge-info {
493
+ background-color: color-mix(in srgb, darkturquoise 6.25%, transparent);
494
+ }
495
+
496
+ .btn {
497
+ background-color: rgba(0, 0, 0, 0.03125);
498
+ border: 1px solid rgba(0, 0, 0, 0.0625);
499
+ border-radius: 0.25rem;
500
+ cursor: pointer;
501
+ font-size: 0.875rem;
502
+ font-weight: 600;
503
+ padding: 0.5rem 1rem;
504
+ }
505
+
506
+ .btn:focus,
507
+ .btn:hover {
508
+ background-color: rgba(0, 0, 0, 0.125);
509
+ }
510
+
511
+ .btn-ghost:not(:focus):not(:hover) {
512
+ background-color: transparent;
513
+ border-color: transparent;
514
+ }
515
+
516
+ .btn-disabled {
517
+ background-color: rgba(0, 0, 0, 0.03125);
518
+ border-color: rgba(0, 0, 0, 0.03125);
519
+ }
520
+
521
+ .fieldset {
522
+ border: none;
523
+ display: flex;
524
+ flex-direction: column;
525
+ gap: 0.25rem;
526
+ padding-bottom: 0.25rem;
527
+ padding-top: 0.25rem;
528
+ }
529
+
530
+ .fieldset-legend {
531
+ font-size: 0.75rem;
532
+ font-weight: bold;
533
+ }
534
+
535
+ .label {
536
+ display: table;
537
+ font-size: 0.75rem;
538
+ }
539
+
540
+ .input {
541
+ border: 1px solid rgba(0, 0, 0, 0.25);
542
+ border-radius: 0.25rem;
543
+ font-size: 0.875rem;
544
+ max-width: 100%;
545
+ padding: 0.5rem 0.75rem;
546
+ width: 20rem;
547
+ }
548
+
549
+ .link {
550
+ text-decoration: underline;
551
+ }
552
+
553
+ .lg\:table {
554
+ border-collapse: collapse;
555
+ font-size: 0.875rem;
556
+ width: 100%;
557
+ }
558
+
559
+ @media (min-width: 992px) {
560
+ .lg\:table {
561
+ display: table;
562
+ }
563
+ }
564
+
565
+ .lg\:table thead th {
566
+ font-weight: 700;
567
+ opacity: 0.5;
568
+ }
569
+
570
+ .lg\:table tr > * {
571
+ padding: 0.75rem;
572
+ }
573
+
574
+ .table-zebra tr:nth-child(even) {
575
+ background-color: rgba(0, 0, 0, 0.025);
576
+ border-bottom: 1px solid rgba(0, 0, 0, 0.075);
577
+ }
578
+
579
+ @media (prefers-color-scheme: dark) {
580
+ .table-zebra tr:nth-child(even) {
581
+ background-color: rgba(255, 255, 255, 0.05);
582
+ }
583
+ }
584
+
585
+ /* Color. */
586
+
587
+ .opacity-0 {
588
+ opacity: 0;
589
+ }
590
+
591
+ .opacity-50 {
592
+ opacity: 0.5;
593
+ }
594
+
595
+ .bg-gray-50 {
596
+ background-color: rgba(0, 0, 0, 0.015625);
597
+ }
598
+
599
+ .bg-gray-100 {
600
+ background-color: rgba(0, 0, 0, 0.03125);
601
+ }
602
+
603
+ .\!bg-transparent {
604
+ background-color: transparent !important;
605
+ }
606
+
607
+ .border-gray-100 {
608
+ border-color: rgba(0, 0, 0, 0.03125);
609
+ }
610
+
611
+ .fill-amber-300\/\[50\%\] {
612
+ fill: color-mix(in srgb, chocolate 25%, transparent);
613
+ }
614
+
615
+ .fill-cyan-300\/\[50\%\] {
616
+ fill: color-mix(in srgb, darkcyan 25%, transparent);
617
+ }
618
+
619
+ .fill-fuchsia-300\/\[50\%\] {
620
+ fill: color-mix(in srgb, palevioletred 25%, transparent);
621
+ }
622
+
623
+ .fill-indigo-300\/\[50\%\] {
624
+ fill: color-mix(in srgb, darkolivegreen 25%, transparent);
625
+ }
626
+
627
+ .fill-lime-300\/\[50\%\] {
628
+ fill: color-mix(in srgb, green 25%, transparent);
629
+ }
630
+
631
+ .fill-purple-300\/\[50\%\] {
632
+ fill: color-mix(in srgb, darkorchid 25%, transparent);
633
+ }
634
+
635
+ .fill-teal-300\/\[50\%\] {
636
+ fill: color-mix(in srgb, darkturquoise 25%, transparent);
637
+ }
638
+
639
+ .fill-yellow-300\/\[50\%\] {
640
+ fill: color-mix(in srgb, yellowgreen 25%, transparent);
641
+ }
642
+
643
+ .stroke-amber-500 {
644
+ stroke: chocolate;
645
+ }
646
+
647
+ .stroke-cyan-500 {
648
+ stroke: darkcyan;
649
+ }
650
+
651
+ .stroke-fuchsia-500 {
652
+ stroke: palevioletred;
653
+ }
654
+
655
+ .stroke-indigo-500 {
656
+ stroke: darkolivegreen;
657
+ }
658
+
659
+ .stroke-lime-500 {
660
+ stroke: green;
661
+ }
662
+
663
+ .stroke-purple-500 {
664
+ stroke: darkorchid;
665
+ }
666
+
667
+ .stroke-teal-500 {
668
+ stroke: darkturquoise;
669
+ }
670
+
671
+ .stroke-yellow-500 {
672
+ stroke: yellowgreen;
673
+ }
674
+
675
+ .text-amber-400 {
676
+ color: chocolate;
677
+ }
678
+
679
+ .text-cyan-400 {
680
+ color: darkcyan;
681
+ }
682
+
683
+ .text-fuchsia-400 {
684
+ color: palevioletred;
685
+ }
686
+
687
+ .text-green-400 {
688
+ color: limegreen;
689
+ }
690
+
691
+ .text-indigo-400 {
692
+ color: darkolivegreen;
693
+ }
694
+
695
+ .text-lime-400 {
696
+ color: green;
697
+ }
698
+
699
+ .text-orange-400 {
700
+ color: orange;
701
+ }
702
+
703
+ .text-purple-400 {
704
+ color: darkorchid;
705
+ }
706
+
707
+ .text-red-400 {
708
+ color: red;
709
+ }
710
+
711
+ .text-teal-400 {
712
+ color: darkturquoise;
713
+ }
714
+
715
+ .text-yellow-400 {
716
+ color: yellowgreen;
717
+ }
package/package.json CHANGED
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "name": "evikit",
3
3
  "description": "Preact/SQLite SSR framework for small web apps",
4
- "version": "2.13.0",
4
+ "version": "2.14.1",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": "./index.js",
8
+ "./css": "./index.css",
8
9
  "./db": "./db.js",
9
10
  "./server": "./server.js",
10
- "./sqlite": "./sqlite.js",
11
- "./testing": "./testing.js"
11
+ "./sqlite": "./sqlite.js"
12
12
  },
13
13
  "bin": {
14
14
  "evikit-extract": "bin/evikit-extract.js"
@@ -0,0 +1,55 @@
1
+ import { readFile } from "node:fs/promises";
2
+
3
+ const html = await Promise.all(
4
+ [
5
+ "https://evikit.js.org",
6
+ "https://evikit.js.org/todo",
7
+ "https://yoself.nykula.com",
8
+ "https://yoself.nykula.com/404",
9
+ ].map(async (url) => await (await fetch(url)).text()),
10
+ );
11
+ const classes = [
12
+ ...new Set([
13
+ ...html.flatMap((html) =>
14
+ [...html.matchAll(/class=(?:"([^"]+)"|'([^']+)'|([^ ]+))/g)].flatMap(
15
+ ([, double, single, none]) =>
16
+ (double || single || none || "").split(" "),
17
+ ),
18
+ ),
19
+ ...(
20
+ await (
21
+ await fetch(
22
+ "https://codeberg.org/nykula/lanquiz/raw/branch/main/classes.log",
23
+ )
24
+ ).text()
25
+ )
26
+ .split("\n")
27
+ .filter(Boolean),
28
+ ]),
29
+ ]
30
+ .map((className) => ({
31
+ base: className.split(/[:!]|^!?-/).at(-1) || "",
32
+ className,
33
+ selector: `.${className.replace(/(\[|\]|[:/#!%])/g, "\\$1")}`,
34
+ }))
35
+ .filter(({ base }) => base)
36
+ .sort((a, b) => a.base.localeCompare(b.base));
37
+ const css = await readFile("./index.css", "utf-8");
38
+ const missing = classes
39
+ .filter(({ selector }) => !css.includes(selector))
40
+ .map(({ className }) => className)
41
+ .join("\n");
42
+ const extra = [...css.matchAll(/\n *(\.[^ ,]+)/g)]
43
+ .map(([, selector]) => (selector || "").replace(/:focus|:hover/, ""))
44
+ .filter((selector) => !classes.some((c) => c.selector == selector))
45
+ .map((selector) => selector?.replace(/[.\\]/g, ""))
46
+ .join("\n");
47
+ if (missing) {
48
+ console.log(`Missing:\n${missing}`);
49
+ }
50
+ if (missing && extra) {
51
+ console.log();
52
+ }
53
+ if (extra) {
54
+ console.log(`Extra:\n${extra}`);
55
+ }