mevebu 2.3.0 → 2.5.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 +7 -7
- package/mevebu/app/tailwind.css +83 -0
- package/mevebu/libs/js/core.js +1 -1
- package/mevebu/libs/js/js-tokens.js +195 -0
- package/mevebu/libs/js/registry.js +180 -0
- package/mevebu/libs/js/tools/cron-next-runs.js +179 -0
- package/mevebu/libs/js/tools/html-to-jsx.js +144 -0
- package/mevebu/libs/js/tools/javascript-formatter.js +182 -0
- package/mevebu/libs/js/tools/javascript-minifier.js +111 -0
- package/mevebu/libs/js/tools/json-diff.js +111 -0
- package/mevebu/libs/js/tools/json-path-tester.js +215 -0
- package/mevebu/libs/js/tools/json-to-yaml.js +59 -0
- package/mevebu/libs/js/tools/regex-cheatsheet.js +100 -0
- package/mevebu/libs/js/tools/xml-minifier.js +103 -0
- package/mevebu/libs/js/tools/yaml-to-json.js +46 -0
- package/mevebu/libs/js/vendor/js-yaml.LICENSE +21 -0
- package/mevebu/libs/js/vendor/js-yaml.mjs +3851 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# mevebu
|
|
4
4
|
|
|
5
|
-
**
|
|
5
|
+
**220 free browser tools, one page each. No sign up. No uploads. Everything runs in your tab.**
|
|
6
6
|
|
|
7
7
|
A private toolbox for the small jobs that do not deserve a whole app: format JSON, decode a JWT, make a password, build a cron line, pick a color, scan a QR code, and a lot more.
|
|
8
8
|
|
|
@@ -10,7 +10,7 @@ A private toolbox for the small jobs that do not deserve a whole app: format JSO
|
|
|
10
10
|
[](https://www.npmjs.com/package/mevebu)
|
|
11
11
|
[](./LICENSE)
|
|
12
12
|
[](https://github.com/mevebu/mevebu/stargazers)
|
|
13
|
-

|
|
14
14
|

|
|
15
15
|
|
|
16
16
|
[Quick start](#quick-start) · [What's inside](#whats-inside) · [Privacy](#privacy-by-design) · [Credit](#credit) · [Development](#development) · [FAQ](#faq)
|
|
@@ -23,7 +23,7 @@ A private toolbox for the small jobs that do not deserve a whole app: format JSO
|
|
|
23
23
|
|
|
24
24
|
## What is mevebu?
|
|
25
25
|
|
|
26
|
-
mevebu is a collection of **
|
|
26
|
+
mevebu is a collection of **220 small online tools** that all run **inside your browser**. Each tool lives on its own page, does one job, and does it fast. Most of them update as you type, so there is no submit button and no waiting.
|
|
27
27
|
|
|
28
28
|
Think of it as a clean, private alternative to the usual "free online tools" websites. There are no accounts, no ads in your way, no uploads, and no tracking. Your text, passwords, tokens, and files stay on your device.
|
|
29
29
|
|
|
@@ -64,13 +64,13 @@ mevebu tries port `5555` first, then `5678`, then `5432`, and finally any free p
|
|
|
64
64
|
|
|
65
65
|
## What's inside
|
|
66
66
|
|
|
67
|
-
mevebu has **
|
|
67
|
+
mevebu has **225 tools in 20 categories**. Here is the full breakdown:
|
|
68
68
|
|
|
69
69
|
| Category | Tools |
|
|
70
70
|
| ------------------ | ----: |
|
|
71
71
|
| Data Generator | 10 |
|
|
72
72
|
| Passwords and keys | 20 |
|
|
73
|
-
| Developer |
|
|
73
|
+
| Developer | 31 |
|
|
74
74
|
| Convert | 15 |
|
|
75
75
|
| Text and data | 14 |
|
|
76
76
|
| Daily use | 8 |
|
|
@@ -88,7 +88,7 @@ mevebu has **215 tools in 20 categories**. Here is the full breakdown:
|
|
|
88
88
|
| GitHub | 8 |
|
|
89
89
|
| Units | 8 |
|
|
90
90
|
| QR codes | 2 |
|
|
91
|
-
| **Total** | **
|
|
91
|
+
| **Total** | **225** |
|
|
92
92
|
|
|
93
93
|
The counts come straight from the tool registry, so this table and the app never drift apart.
|
|
94
94
|
|
|
@@ -176,7 +176,7 @@ That is all. The catalog, search, breadcrumbs, and category pages pick it up aut
|
|
|
176
176
|
## FAQ
|
|
177
177
|
|
|
178
178
|
**What is mevebu?**
|
|
179
|
-
mevebu is a free, open source collection of
|
|
179
|
+
mevebu is a free, open source collection of 220 browser based tools for developers, students, creators, and everyday use. Every tool runs locally in your browser.
|
|
180
180
|
|
|
181
181
|
**Is mevebu really free?**
|
|
182
182
|
Yes. It is MIT licensed, with no paid tier, no account, and no usage limits.
|
package/mevebu/app/tailwind.css
CHANGED
|
@@ -3,8 +3,20 @@
|
|
|
3
3
|
@layer theme, base, components, utilities;
|
|
4
4
|
@layer theme {
|
|
5
5
|
:root, :host {
|
|
6
|
+
--color-amber-300: oklch(87.9% 0.169 91.605);
|
|
7
|
+
--color-amber-400: oklch(82.8% 0.189 84.429);
|
|
8
|
+
--color-amber-500: oklch(76.9% 0.188 70.08);
|
|
9
|
+
--color-amber-600: oklch(66.6% 0.179 58.318);
|
|
10
|
+
--color-emerald-400: oklch(76.5% 0.177 163.223);
|
|
11
|
+
--color-emerald-500: oklch(69.6% 0.17 162.48);
|
|
12
|
+
--color-emerald-600: oklch(59.6% 0.145 163.225);
|
|
13
|
+
--color-rose-400: oklch(71.2% 0.194 13.428);
|
|
14
|
+
--color-rose-500: oklch(64.5% 0.246 16.439);
|
|
15
|
+
--color-rose-600: oklch(58.6% 0.253 17.585);
|
|
6
16
|
--color-black: #000;
|
|
7
17
|
--spacing: 0.25rem;
|
|
18
|
+
--text-xs: 0.75rem;
|
|
19
|
+
--text-xs--line-height: calc(1 / 0.75);
|
|
8
20
|
--text-sm: 14px;
|
|
9
21
|
--text-sm--line-height: 1.5;
|
|
10
22
|
--text-base: 16px;
|
|
@@ -21,6 +33,7 @@
|
|
|
21
33
|
--font-weight-semibold: 600;
|
|
22
34
|
--font-weight-bold: 700;
|
|
23
35
|
--leading-normal: 1.5;
|
|
36
|
+
--radius-xs: 0.125rem;
|
|
24
37
|
--radius-lg: 0.5rem;
|
|
25
38
|
--radius-xl: 0.75rem;
|
|
26
39
|
--default-transition-duration: 150ms;
|
|
@@ -608,6 +621,9 @@
|
|
|
608
621
|
.w-0 {
|
|
609
622
|
width: 0px;
|
|
610
623
|
}
|
|
624
|
+
.w-5 {
|
|
625
|
+
width: calc(var(--spacing) * 5);
|
|
626
|
+
}
|
|
611
627
|
.w-24 {
|
|
612
628
|
width: calc(var(--spacing) * 24);
|
|
613
629
|
}
|
|
@@ -964,6 +980,9 @@
|
|
|
964
980
|
.rounded-xl {
|
|
965
981
|
border-radius: var(--radius-xl);
|
|
966
982
|
}
|
|
983
|
+
.rounded-xs {
|
|
984
|
+
border-radius: var(--radius-xs);
|
|
985
|
+
}
|
|
967
986
|
.border {
|
|
968
987
|
border-style: var(--tw-border-style);
|
|
969
988
|
border-width: 1px;
|
|
@@ -996,6 +1015,12 @@
|
|
|
996
1015
|
--tw-border-style: dashed;
|
|
997
1016
|
border-style: dashed;
|
|
998
1017
|
}
|
|
1018
|
+
.border-amber-500\/40 {
|
|
1019
|
+
border-color: color-mix(in srgb, oklch(76.9% 0.188 70.08) 40%, transparent);
|
|
1020
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
1021
|
+
border-color: color-mix(in oklab, var(--color-amber-500) 40%, transparent);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
999
1024
|
.border-current\/15 {
|
|
1000
1025
|
border-color: currentcolor;
|
|
1001
1026
|
@supports (color: color-mix(in lab, red, red)) {
|
|
@@ -1011,6 +1036,12 @@
|
|
|
1011
1036
|
border-color: color-mix(in oklab, var(--danger) 40%, transparent);
|
|
1012
1037
|
}
|
|
1013
1038
|
}
|
|
1039
|
+
.border-emerald-500\/40 {
|
|
1040
|
+
border-color: color-mix(in srgb, oklch(69.6% 0.17 162.48) 40%, transparent);
|
|
1041
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
1042
|
+
border-color: color-mix(in oklab, var(--color-emerald-500) 40%, transparent);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1014
1045
|
.border-jade {
|
|
1015
1046
|
border-color: var(--jade);
|
|
1016
1047
|
}
|
|
@@ -1020,6 +1051,12 @@
|
|
|
1020
1051
|
.border-orbit-hard {
|
|
1021
1052
|
border-color: var(--orbit-hard);
|
|
1022
1053
|
}
|
|
1054
|
+
.border-rose-500\/40 {
|
|
1055
|
+
border-color: color-mix(in srgb, oklch(64.5% 0.246 16.439) 40%, transparent);
|
|
1056
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
1057
|
+
border-color: color-mix(in oklab, var(--color-rose-500) 40%, transparent);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1023
1060
|
.border-transparent {
|
|
1024
1061
|
border-color: transparent;
|
|
1025
1062
|
}
|
|
@@ -1035,6 +1072,12 @@
|
|
|
1035
1072
|
.bg-\[var\(--scrim\)\] {
|
|
1036
1073
|
background-color: var(--scrim);
|
|
1037
1074
|
}
|
|
1075
|
+
.bg-amber-300\/60 {
|
|
1076
|
+
background-color: color-mix(in srgb, oklch(87.9% 0.169 91.605) 60%, transparent);
|
|
1077
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
1078
|
+
background-color: color-mix(in oklab, var(--color-amber-300) 60%, transparent);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1038
1081
|
.bg-black {
|
|
1039
1082
|
background-color: var(--color-black);
|
|
1040
1083
|
}
|
|
@@ -1176,6 +1219,9 @@
|
|
|
1176
1219
|
.px-0 {
|
|
1177
1220
|
padding-inline: 0px;
|
|
1178
1221
|
}
|
|
1222
|
+
.px-0\.5 {
|
|
1223
|
+
padding-inline: calc(var(--spacing) * 0.5);
|
|
1224
|
+
}
|
|
1179
1225
|
.px-1 {
|
|
1180
1226
|
padding-inline: var(--spacing);
|
|
1181
1227
|
}
|
|
@@ -1400,6 +1446,10 @@
|
|
|
1400
1446
|
font-size: var(--text-xl);
|
|
1401
1447
|
line-height: var(--tw-leading, var(--text-xl--line-height));
|
|
1402
1448
|
}
|
|
1449
|
+
.text-xs {
|
|
1450
|
+
font-size: var(--text-xs);
|
|
1451
|
+
line-height: var(--tw-leading, var(--text-xs--line-height));
|
|
1452
|
+
}
|
|
1403
1453
|
.text-\[0\.92em\] {
|
|
1404
1454
|
font-size: 0.92em;
|
|
1405
1455
|
}
|
|
@@ -1513,9 +1563,18 @@
|
|
|
1513
1563
|
.whitespace-pre-wrap {
|
|
1514
1564
|
white-space: pre-wrap;
|
|
1515
1565
|
}
|
|
1566
|
+
.text-amber-600 {
|
|
1567
|
+
color: var(--color-amber-600);
|
|
1568
|
+
}
|
|
1516
1569
|
.text-danger {
|
|
1517
1570
|
color: var(--danger);
|
|
1518
1571
|
}
|
|
1572
|
+
.text-emerald-600 {
|
|
1573
|
+
color: var(--color-emerald-600);
|
|
1574
|
+
}
|
|
1575
|
+
.text-inherit {
|
|
1576
|
+
color: inherit;
|
|
1577
|
+
}
|
|
1519
1578
|
.text-ink {
|
|
1520
1579
|
color: var(--ink);
|
|
1521
1580
|
}
|
|
@@ -1537,6 +1596,9 @@
|
|
|
1537
1596
|
.text-orbit-hard {
|
|
1538
1597
|
color: var(--orbit-hard);
|
|
1539
1598
|
}
|
|
1599
|
+
.text-rose-600 {
|
|
1600
|
+
color: var(--color-rose-600);
|
|
1601
|
+
}
|
|
1540
1602
|
.text-void {
|
|
1541
1603
|
color: var(--void);
|
|
1542
1604
|
}
|
|
@@ -1568,6 +1630,12 @@
|
|
|
1568
1630
|
.underline {
|
|
1569
1631
|
text-decoration-line: underline;
|
|
1570
1632
|
}
|
|
1633
|
+
.decoration-dotted {
|
|
1634
|
+
text-decoration-style: dotted;
|
|
1635
|
+
}
|
|
1636
|
+
.underline-offset-2 {
|
|
1637
|
+
text-underline-offset: 2px;
|
|
1638
|
+
}
|
|
1571
1639
|
.underline-offset-\[3px\] {
|
|
1572
1640
|
text-underline-offset: 3px;
|
|
1573
1641
|
}
|
|
@@ -2055,6 +2123,21 @@
|
|
|
2055
2123
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
2056
2124
|
}
|
|
2057
2125
|
}
|
|
2126
|
+
.dark\:bg-amber-400\/40:where([data-theme="dark"], [data-theme="dark"] *) {
|
|
2127
|
+
background-color: color-mix(in srgb, oklch(82.8% 0.189 84.429) 40%, transparent);
|
|
2128
|
+
@supports (color: color-mix(in lab, red, red)) {
|
|
2129
|
+
background-color: color-mix(in oklab, var(--color-amber-400) 40%, transparent);
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
.dark\:text-amber-400:where([data-theme="dark"], [data-theme="dark"] *) {
|
|
2133
|
+
color: var(--color-amber-400);
|
|
2134
|
+
}
|
|
2135
|
+
.dark\:text-emerald-400:where([data-theme="dark"], [data-theme="dark"] *) {
|
|
2136
|
+
color: var(--color-emerald-400);
|
|
2137
|
+
}
|
|
2138
|
+
.dark\:text-rose-400:where([data-theme="dark"], [data-theme="dark"] *) {
|
|
2139
|
+
color: var(--color-rose-400);
|
|
2140
|
+
}
|
|
2058
2141
|
.\[\&_a\]\:text-jade a {
|
|
2059
2142
|
color: var(--jade);
|
|
2060
2143
|
}
|
package/mevebu/libs/js/core.js
CHANGED
|
@@ -750,7 +750,7 @@ export function blank(iconName, text) {
|
|
|
750
750
|
// ---------- version ----------
|
|
751
751
|
|
|
752
752
|
// Kept in one place. The CLI reads the same value from package.json.
|
|
753
|
-
export const VERSION = "2.
|
|
753
|
+
export const VERSION = "2.5.0";
|
|
754
754
|
export const PACKAGE_NAME = "mevebu";
|
|
755
755
|
export const REPO_SLUG = "mevebu/mevebu";
|
|
756
756
|
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// A small JavaScript scanner shared by the formatter and the minifier. It does
|
|
2
|
+
// not build an AST: it separates code from strings, templates, regexes and
|
|
3
|
+
// comments so whitespace can be changed without touching what a token means.
|
|
4
|
+
|
|
5
|
+
const KEYWORDS_BEFORE_REGEX = new Set([
|
|
6
|
+
"return", "typeof", "instanceof", "in", "of", "new", "delete", "void", "do",
|
|
7
|
+
"else", "case", "yield", "await", "throw",
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
const PUNCT = [
|
|
11
|
+
">>>=", "===", "!==", "**=", "<<=", ">>=", ">>>", "...", "&&=", "||=", "??=",
|
|
12
|
+
"=>", "==", "!=", "<=", ">=", "&&", "||", "??", "?.", "++", "--", "+=", "-=",
|
|
13
|
+
"*=", "/=", "%=", "&=", "|=", "^=", "<<", ">>", "**",
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const isWordStart = (ch) => /[A-Za-z_$\u0080-\uffff]/.test(ch);
|
|
17
|
+
const isWord = (ch) => /[A-Za-z0-9_$\u0080-\uffff]/.test(ch);
|
|
18
|
+
const isDigit = (ch) => /[0-9]/.test(ch);
|
|
19
|
+
|
|
20
|
+
function regexAllowed(previous) {
|
|
21
|
+
if (!previous) return true;
|
|
22
|
+
if (previous.type === "word") return KEYWORDS_BEFORE_REGEX.has(previous.value);
|
|
23
|
+
if (previous.type === "number" || previous.type === "string" || previous.type === "template" || previous.type === "regex") return false;
|
|
24
|
+
if (previous.type === "punct") return ![")", "]", "}"].includes(previous.value);
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function scanTemplate(source, start) {
|
|
29
|
+
let i = start + 1;
|
|
30
|
+
while (i < source.length) {
|
|
31
|
+
const ch = source[i];
|
|
32
|
+
if (ch === "\\") { i += 2; continue; }
|
|
33
|
+
if (ch === "`") return { end: i + 1 };
|
|
34
|
+
if (ch === "$" && source[i + 1] === "{") {
|
|
35
|
+
const inner = scanBraces(source, i + 2);
|
|
36
|
+
if (inner.error) return inner;
|
|
37
|
+
i = inner.end;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
return { error: "Unclosed template literal." };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function scanBraces(source, start) {
|
|
46
|
+
let depth = 1;
|
|
47
|
+
let i = start;
|
|
48
|
+
while (i < source.length) {
|
|
49
|
+
const ch = source[i];
|
|
50
|
+
if (ch === "'" || ch === '"') {
|
|
51
|
+
const end = scanString(source, i);
|
|
52
|
+
if (end.error) return end;
|
|
53
|
+
i = end.end;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
if (ch === "`") {
|
|
57
|
+
const end = scanTemplate(source, i);
|
|
58
|
+
if (end.error) return end;
|
|
59
|
+
i = end.end;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
63
|
+
const end = source.indexOf("\n", i);
|
|
64
|
+
i = end === -1 ? source.length : end;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
68
|
+
const end = source.indexOf("*/", i + 2);
|
|
69
|
+
if (end === -1) return { error: "Unclosed block comment." };
|
|
70
|
+
i = end + 2;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (ch === "{") depth++;
|
|
74
|
+
if (ch === "}") {
|
|
75
|
+
depth--;
|
|
76
|
+
if (depth === 0) return { end: i + 1 };
|
|
77
|
+
}
|
|
78
|
+
i++;
|
|
79
|
+
}
|
|
80
|
+
return { error: "Unclosed ${ in a template literal." };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function scanString(source, start) {
|
|
84
|
+
const quote = source[start];
|
|
85
|
+
let i = start + 1;
|
|
86
|
+
while (i < source.length) {
|
|
87
|
+
const ch = source[i];
|
|
88
|
+
if (ch === "\\") { i += 2; continue; }
|
|
89
|
+
if (ch === quote) return { end: i + 1 };
|
|
90
|
+
if (ch === "\n") return { error: "Unclosed string." };
|
|
91
|
+
i++;
|
|
92
|
+
}
|
|
93
|
+
return { error: "Unclosed string." };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function scanRegex(source, start) {
|
|
97
|
+
let i = start + 1;
|
|
98
|
+
let inClass = false;
|
|
99
|
+
while (i < source.length) {
|
|
100
|
+
const ch = source[i];
|
|
101
|
+
if (ch === "\\") { i += 2; continue; }
|
|
102
|
+
if (ch === "[") inClass = true;
|
|
103
|
+
else if (ch === "]") inClass = false;
|
|
104
|
+
else if (ch === "/" && !inClass) {
|
|
105
|
+
i++;
|
|
106
|
+
while (i < source.length && /[a-z]/i.test(source[i])) i++;
|
|
107
|
+
return { end: i };
|
|
108
|
+
} else if (ch === "\n") return { error: "Unclosed regular expression." };
|
|
109
|
+
i++;
|
|
110
|
+
}
|
|
111
|
+
return { error: "Unclosed regular expression." };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Split JavaScript into significant tokens. Whitespace is dropped, but each
|
|
116
|
+
* token remembers whether a newline came before it.
|
|
117
|
+
* @param {string} source
|
|
118
|
+
* @returns {{tokens: object[], error: string|null}}
|
|
119
|
+
*/
|
|
120
|
+
export function tokenize(source) {
|
|
121
|
+
const tokens = [];
|
|
122
|
+
let i = 0;
|
|
123
|
+
let newline = false;
|
|
124
|
+
const push = (token) => {
|
|
125
|
+
token.newline = newline;
|
|
126
|
+
newline = false;
|
|
127
|
+
tokens.push(token);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
while (i < source.length) {
|
|
131
|
+
const ch = source[i];
|
|
132
|
+
if (ch === "\n") { newline = true; i++; continue; }
|
|
133
|
+
if (/\s/.test(ch)) { i++; continue; }
|
|
134
|
+
|
|
135
|
+
if (ch === "/" && source[i + 1] === "/") {
|
|
136
|
+
const end = source.indexOf("\n", i);
|
|
137
|
+
const stop = end === -1 ? source.length : end;
|
|
138
|
+
push({ type: "lineComment", value: source.slice(i, stop) });
|
|
139
|
+
i = stop;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (ch === "/" && source[i + 1] === "*") {
|
|
143
|
+
const end = source.indexOf("*/", i + 2);
|
|
144
|
+
if (end === -1) return { tokens, error: "Unclosed block comment." };
|
|
145
|
+
push({ type: "blockComment", value: source.slice(i, end + 2) });
|
|
146
|
+
i = end + 2;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (ch === "'" || ch === '"') {
|
|
150
|
+
const result = scanString(source, i);
|
|
151
|
+
if (result.error) return { tokens, error: result.error };
|
|
152
|
+
push({ type: "string", value: source.slice(i, result.end) });
|
|
153
|
+
i = result.end;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (ch === "`") {
|
|
157
|
+
const result = scanTemplate(source, i);
|
|
158
|
+
if (result.error) return { tokens, error: result.error };
|
|
159
|
+
push({ type: "template", value: source.slice(i, result.end) });
|
|
160
|
+
i = result.end;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (ch === "/" && regexAllowed(tokens[tokens.length - 1])) {
|
|
164
|
+
const result = scanRegex(source, i);
|
|
165
|
+
if (result.error) return { tokens, error: result.error };
|
|
166
|
+
push({ type: "regex", value: source.slice(i, result.end) });
|
|
167
|
+
i = result.end;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (isDigit(ch) || (ch === "." && isDigit(source[i + 1]))) {
|
|
171
|
+
const match = source.slice(i).match(/^(?:0[xX][0-9a-fA-F_]+|0[bB][01_]+|0[oO][0-7_]+|(?:\d[\d_]*)?\.?\d[\d_]*(?:[eE][+-]?\d[\d_]*)?n?)/);
|
|
172
|
+
const value = match ? match[0] : ch;
|
|
173
|
+
push({ type: "number", value });
|
|
174
|
+
i += value.length;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
if (isWordStart(ch)) {
|
|
178
|
+
let end = i + 1;
|
|
179
|
+
while (end < source.length && isWord(source[end])) end++;
|
|
180
|
+
push({ type: "word", value: source.slice(i, end) });
|
|
181
|
+
i = end;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const multi = PUNCT.find((entry) => source.startsWith(entry, i));
|
|
185
|
+
const value = multi || ch;
|
|
186
|
+
push({ type: "punct", value });
|
|
187
|
+
i += value.length;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return { tokens, error: null };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export function bytes(text) {
|
|
194
|
+
return new TextEncoder().encode(text).length;
|
|
195
|
+
}
|
|
@@ -572,6 +572,186 @@ export const TOOLS = [
|
|
|
572
572
|
load: () => import("./tools/ssh-fingerprint.js"),
|
|
573
573
|
},
|
|
574
574
|
|
|
575
|
+
{
|
|
576
|
+
id: "javascript-formatter",
|
|
577
|
+
category: "developer",
|
|
578
|
+
name: "JavaScript Formatter",
|
|
579
|
+
icon: "file-code",
|
|
580
|
+
note: "Indent a JavaScript file so it reads like code again.",
|
|
581
|
+
about: "Re-indents tokens and keeps strings, templates, regexes and comments as they were.",
|
|
582
|
+
tutorial: {
|
|
583
|
+
intro: "Paste a script and get the same code with readable indentation.",
|
|
584
|
+
steps: [
|
|
585
|
+
"Paste the script. Pick two or four spaces.",
|
|
586
|
+
"Read the first bracket or string problem if the script cannot be laid out.",
|
|
587
|
+
"Copy the result. Only whitespace changed, not the code.",
|
|
588
|
+
],
|
|
589
|
+
},
|
|
590
|
+
keywords: "javascript js format beautify indent pretty print code",
|
|
591
|
+
load: () => import("./tools/javascript-formatter.js"),
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
id: "javascript-minifier",
|
|
595
|
+
category: "developer",
|
|
596
|
+
name: "JavaScript Minifier",
|
|
597
|
+
icon: "minimize-2",
|
|
598
|
+
note: "Strip comments and extra whitespace from JavaScript.",
|
|
599
|
+
about: "Shows the bytes before and after. Variables are never renamed.",
|
|
600
|
+
tutorial: {
|
|
601
|
+
intro: "Make a script smaller without changing what it does.",
|
|
602
|
+
steps: [
|
|
603
|
+
"Paste the script.",
|
|
604
|
+
"Read the size before and after, and the percentage saved.",
|
|
605
|
+
"Copy the result. Keep license comments with /*! or @license.",
|
|
606
|
+
],
|
|
607
|
+
},
|
|
608
|
+
keywords: "javascript js minify compress whitespace comments size",
|
|
609
|
+
load: () => import("./tools/javascript-minifier.js"),
|
|
610
|
+
},
|
|
611
|
+
{
|
|
612
|
+
id: "yaml-to-json",
|
|
613
|
+
category: "developer",
|
|
614
|
+
name: "YAML to JSON",
|
|
615
|
+
icon: "braces",
|
|
616
|
+
note: "Turn a YAML document into JSON.",
|
|
617
|
+
about: "Anchors are resolved, comments are dropped, and the key order stays.",
|
|
618
|
+
tutorial: {
|
|
619
|
+
intro: "Convert a config file for an API or a script that expects JSON.",
|
|
620
|
+
steps: [
|
|
621
|
+
"Paste the YAML.",
|
|
622
|
+
"Read the JSON on the right. An invalid document reports its line.",
|
|
623
|
+
"Copy it or download data.json.",
|
|
624
|
+
],
|
|
625
|
+
},
|
|
626
|
+
keywords: "yaml yml to json convert config kubernetes ci",
|
|
627
|
+
load: () => import("./tools/yaml-to-json.js"),
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
id: "json-to-yaml",
|
|
631
|
+
category: "developer",
|
|
632
|
+
name: "JSON to YAML",
|
|
633
|
+
icon: "file-text",
|
|
634
|
+
note: "Write JSON as block or flow YAML.",
|
|
635
|
+
about: "Two-space block style by default, with a flow toggle for compact output.",
|
|
636
|
+
tutorial: {
|
|
637
|
+
intro: "Turn an API response into a config file.",
|
|
638
|
+
steps: [
|
|
639
|
+
"Paste the JSON.",
|
|
640
|
+
"Pick Block or Flow. Block reads better, flow is shorter.",
|
|
641
|
+
"Copy the YAML or download data.yaml.",
|
|
642
|
+
],
|
|
643
|
+
},
|
|
644
|
+
keywords: "json to yaml yml convert config block flow",
|
|
645
|
+
load: () => import("./tools/json-to-yaml.js"),
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
id: "xml-minifier",
|
|
649
|
+
category: "developer",
|
|
650
|
+
name: "XML Minifier",
|
|
651
|
+
icon: "code",
|
|
652
|
+
note: "Remove indentation between XML tags.",
|
|
653
|
+
about: "CDATA, comments and attribute values are copied unchanged.",
|
|
654
|
+
tutorial: {
|
|
655
|
+
intro: "Compact an XML file without touching its content.",
|
|
656
|
+
steps: [
|
|
657
|
+
"Paste the XML.",
|
|
658
|
+
"Read the size saved. Text that is not only whitespace stays.",
|
|
659
|
+
"Copy the result.",
|
|
660
|
+
],
|
|
661
|
+
},
|
|
662
|
+
keywords: "xml minify compact whitespace cdata size",
|
|
663
|
+
load: () => import("./tools/xml-minifier.js"),
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
id: "json-diff",
|
|
667
|
+
category: "developer",
|
|
668
|
+
name: "JSON Diff",
|
|
669
|
+
icon: "git-compare",
|
|
670
|
+
note: "Compare two JSON documents by path.",
|
|
671
|
+
about: "Lists every added, removed and changed path, with a count per kind.",
|
|
672
|
+
tutorial: {
|
|
673
|
+
intro: "See what changed between two JSON documents.",
|
|
674
|
+
steps: [
|
|
675
|
+
"Paste the old document and the new one.",
|
|
676
|
+
"Read the paths that were added, removed or changed.",
|
|
677
|
+
"Copy the report.",
|
|
678
|
+
],
|
|
679
|
+
},
|
|
680
|
+
keywords: "json diff compare changes paths added removed",
|
|
681
|
+
load: () => import("./tools/json-diff.js"),
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
id: "json-path-tester",
|
|
685
|
+
category: "developer",
|
|
686
|
+
name: "JSONPath Tester",
|
|
687
|
+
icon: "text-search",
|
|
688
|
+
note: "Run a JSONPath expression on JSON.",
|
|
689
|
+
about: "Supports root, child, wildcard, index, slice and filter selectors.",
|
|
690
|
+
tutorial: {
|
|
691
|
+
intro: "Test a JSONPath expression against a document.",
|
|
692
|
+
steps: [
|
|
693
|
+
"Paste the JSON.",
|
|
694
|
+
"Type an expression such as $.items[?(@.price > 10)].name.",
|
|
695
|
+
"Read the matches and their paths.",
|
|
696
|
+
],
|
|
697
|
+
},
|
|
698
|
+
keywords: "jsonpath json query path filter select expression",
|
|
699
|
+
load: () => import("./tools/json-path-tester.js"),
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
id: "cron-next-runs",
|
|
703
|
+
category: "developer",
|
|
704
|
+
name: "Cron Next Runs",
|
|
705
|
+
icon: "calendar-clock",
|
|
706
|
+
note: "See the next ten times a cron job runs.",
|
|
707
|
+
about: "Reads five fields and the @daily shortcuts, in your timezone or UTC.",
|
|
708
|
+
tutorial: {
|
|
709
|
+
intro: "Find out when a cron expression actually runs.",
|
|
710
|
+
steps: [
|
|
711
|
+
"Paste an expression such as */15 9-17 * * 1-5.",
|
|
712
|
+
"Pick your browser timezone or UTC.",
|
|
713
|
+
"The next ten runs appear as dates.",
|
|
714
|
+
],
|
|
715
|
+
},
|
|
716
|
+
keywords: "cron schedule next runs crontab job time",
|
|
717
|
+
load: () => import("./tools/cron-next-runs.js"),
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
id: "html-to-jsx",
|
|
721
|
+
category: "developer",
|
|
722
|
+
name: "HTML to JSX",
|
|
723
|
+
icon: "code-xml",
|
|
724
|
+
note: "Convert an HTML fragment to JSX.",
|
|
725
|
+
about: "Renames attributes, turns inline styles into objects, and closes void tags.",
|
|
726
|
+
tutorial: {
|
|
727
|
+
intro: "Turn copied markup into a JSX fragment.",
|
|
728
|
+
steps: [
|
|
729
|
+
"Paste the HTML.",
|
|
730
|
+
"Read the JSX on the right.",
|
|
731
|
+
"Copy it into your component.",
|
|
732
|
+
],
|
|
733
|
+
},
|
|
734
|
+
keywords: "html to jsx react convert className htmlFor style object",
|
|
735
|
+
load: () => import("./tools/html-to-jsx.js"),
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
id: "regex-cheatsheet",
|
|
739
|
+
category: "developer",
|
|
740
|
+
name: "Regex Cheatsheet",
|
|
741
|
+
icon: "book-open",
|
|
742
|
+
note: "Look up regex tokens with examples.",
|
|
743
|
+
about: "Every row shows the token, what it matches and a highlighted example.",
|
|
744
|
+
tutorial: {
|
|
745
|
+
intro: "Find the regex token you need.",
|
|
746
|
+
steps: [
|
|
747
|
+
"Type to filter the list.",
|
|
748
|
+
"Read the token and its example.",
|
|
749
|
+
"Open Regex Tester to try the full pattern.",
|
|
750
|
+
],
|
|
751
|
+
},
|
|
752
|
+
keywords: "regex cheatsheet reference tokens syntax javascript",
|
|
753
|
+
load: () => import("./tools/regex-cheatsheet.js"),
|
|
754
|
+
},
|
|
575
755
|
{
|
|
576
756
|
id: "uuid-generator",
|
|
577
757
|
category: "developer",
|