@scalar/helpers 0.4.2 → 0.4.3
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/CHANGELOG.md +7 -0
- package/dist/dom/get-selector.d.ts +7 -0
- package/dist/dom/get-selector.d.ts.map +1 -0
- package/dist/dom/get-selector.js +31 -0
- package/dist/formatters/format-bytes.d.ts +7 -0
- package/dist/formatters/format-bytes.d.ts.map +1 -0
- package/dist/formatters/format-bytes.js +18 -0
- package/dist/formatters/format-milliseconds.d.ts +6 -0
- package/dist/formatters/format-milliseconds.d.ts.map +1 -0
- package/dist/formatters/format-milliseconds.js +10 -0
- package/dist/markdown/get-markdown-headings.d.ts +11 -0
- package/dist/markdown/get-markdown-headings.d.ts.map +1 -0
- package/dist/markdown/get-markdown-headings.js +99 -0
- package/package.json +12 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# @scalar/helpers
|
|
2
2
|
|
|
3
|
+
## 0.4.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#8692](https://github.com/scalar/scalar/pull/8692): feat: replace pretty-bytes dependency with internal helper
|
|
8
|
+
- [#8555](https://github.com/scalar/scalar/pull/8555): feat: add getMarkdownHeadings utility
|
|
9
|
+
|
|
3
10
|
## 0.4.2
|
|
4
11
|
|
|
5
12
|
### Patch Changes
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Similar to the copy selector from chrome devtools, this allows you to get a CSS selector for an element
|
|
3
|
+
*
|
|
4
|
+
* Helpful when elements are being re-created and you cannot use a ref for it
|
|
5
|
+
*/
|
|
6
|
+
export declare const getSelector: (el: Element | null) => string | null;
|
|
7
|
+
//# sourceMappingURL=get-selector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-selector.d.ts","sourceRoot":"","sources":["../../src/dom/get-selector.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,IAAI,OAAO,GAAG,IAAI,KAAG,MAAM,GAAG,IA8BzD,CAAA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Similar to the copy selector from chrome devtools, this allows you to get a CSS selector for an element
|
|
3
|
+
*
|
|
4
|
+
* Helpful when elements are being re-created and you cannot use a ref for it
|
|
5
|
+
*/
|
|
6
|
+
export const getSelector = (el) => {
|
|
7
|
+
if (!el || !(el instanceof Element)) {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
const path = [];
|
|
11
|
+
let node = el;
|
|
12
|
+
while (node instanceof Element) {
|
|
13
|
+
const tag = node.localName;
|
|
14
|
+
/**
|
|
15
|
+
* Count same-tag preceding siblings to determine nth-of-type index.
|
|
16
|
+
* Walking backwards is cheaper than counting all siblings and finding our
|
|
17
|
+
* position from the front, since we stop as soon as we reach the start.
|
|
18
|
+
*/
|
|
19
|
+
let nth = 1;
|
|
20
|
+
let sibling = node.previousElementSibling;
|
|
21
|
+
while (sibling) {
|
|
22
|
+
if (sibling.localName === tag) {
|
|
23
|
+
nth++;
|
|
24
|
+
}
|
|
25
|
+
sibling = sibling.previousElementSibling;
|
|
26
|
+
}
|
|
27
|
+
path.unshift(nth > 1 ? `${tag}:nth-of-type(${nth})` : tag);
|
|
28
|
+
node = node.parentElement;
|
|
29
|
+
}
|
|
30
|
+
return path.join(' > ');
|
|
31
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-bytes.d.ts","sourceRoot":"","sources":["../../src/formatters/format-bytes.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,EAAE,kBAAa,KAAG,MAc1D,CAAA"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const UNITS = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
|
2
|
+
/**
|
|
3
|
+
* Format a byte count as a human-readable decimal value
|
|
4
|
+
* @example 1024 -> 1 kB
|
|
5
|
+
* @example 1025 -> 1.03 kB
|
|
6
|
+
*/
|
|
7
|
+
export const formatBytes = (value, precision = 3) => {
|
|
8
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
9
|
+
return '0 B';
|
|
10
|
+
}
|
|
11
|
+
// toPrecision rounding can push the value to 1000, overflowing the current unit
|
|
12
|
+
let unitIndex = Math.max(0, Math.min(Math.floor(Math.log10(value) / 3), UNITS.length - 1));
|
|
13
|
+
const normalizedValue = Number((value / 1000 ** unitIndex).toPrecision(precision));
|
|
14
|
+
if (normalizedValue >= 1000 && unitIndex < UNITS.length - 1) {
|
|
15
|
+
unitIndex += 1;
|
|
16
|
+
}
|
|
17
|
+
return `${Number((value / 1000 ** unitIndex).toPrecision(precision))} ${UNITS[unitIndex]}`;
|
|
18
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-milliseconds.d.ts","sourceRoot":"","sources":["../../src/formatters/format-milliseconds.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAAI,IAAI,MAAM,EAAE,iBAAY,KAAG,MAK7D,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formats milliseconds to seconds and appends an "s" if more than one second
|
|
3
|
+
* else returns ms appended to the number
|
|
4
|
+
*/
|
|
5
|
+
export const formatMilliseconds = (ms, decimals = 2) => {
|
|
6
|
+
if (ms > 1000) {
|
|
7
|
+
return (ms / 1000).toFixed(decimals) + 's';
|
|
8
|
+
}
|
|
9
|
+
return ms + 'ms';
|
|
10
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract all headings from a Markdown string without any library dependencies.
|
|
3
|
+
*
|
|
4
|
+
* Correctly skips headings inside fenced code blocks (``` or ~~~) and
|
|
5
|
+
* indented code blocks (4+ spaces or tab).
|
|
6
|
+
*/
|
|
7
|
+
export declare function getMarkdownHeadings(markdown: string): {
|
|
8
|
+
depth: number;
|
|
9
|
+
value: string;
|
|
10
|
+
}[];
|
|
11
|
+
//# sourceMappingURL=get-markdown-headings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-markdown-headings.d.ts","sourceRoot":"","sources":["../../src/markdown/get-markdown-headings.ts"],"names":[],"mappings":"AA8BA;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG;IACrD,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd,EAAE,CA2EF"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strips inline Markdown formatting to extract plain text.
|
|
3
|
+
*
|
|
4
|
+
* Handles: links, images, bold/italic, inline code, strikethrough, and HTML tags.
|
|
5
|
+
*/
|
|
6
|
+
function stripInlineMarkdown(text) {
|
|
7
|
+
return (text
|
|
8
|
+
// Images:  → alt
|
|
9
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
10
|
+
// Links: [text](url) → text
|
|
11
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, '$1')
|
|
12
|
+
// Bold/italic combos: ***text*** or ___text___
|
|
13
|
+
.replace(/(\*{3}|_{3})(.+?)\1/g, '$2')
|
|
14
|
+
// Bold: **text** or __text__
|
|
15
|
+
.replace(/(\*{2}|_{2})(.+?)\1/g, '$2')
|
|
16
|
+
// Italic: *text* or _text_
|
|
17
|
+
.replace(/(\*|_)(.+?)\1/g, '$2')
|
|
18
|
+
// Strikethrough: ~~text~~
|
|
19
|
+
.replace(/~~(.+?)~~/g, '$1')
|
|
20
|
+
// Inline code: `code`
|
|
21
|
+
.replace(/`([^`]+)`/g, '$1')
|
|
22
|
+
// HTML tags
|
|
23
|
+
.replace(/<[^>]+>/g, '')
|
|
24
|
+
// Remove any remaining angle brackets to avoid partial/malformed HTML fragments
|
|
25
|
+
.replace(/[<>]/g, '')
|
|
26
|
+
.trim());
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Extract all headings from a Markdown string without any library dependencies.
|
|
30
|
+
*
|
|
31
|
+
* Correctly skips headings inside fenced code blocks (``` or ~~~) and
|
|
32
|
+
* indented code blocks (4+ spaces or tab).
|
|
33
|
+
*/
|
|
34
|
+
export function getMarkdownHeadings(markdown) {
|
|
35
|
+
const lines = markdown.split(/\r?\n/);
|
|
36
|
+
const headings = [];
|
|
37
|
+
let inFencedBlock = false;
|
|
38
|
+
let fenceChar = '';
|
|
39
|
+
let fenceLength = 0;
|
|
40
|
+
for (let index = 0; index < lines.length; index++) {
|
|
41
|
+
const line = lines[index] ?? '';
|
|
42
|
+
const trimmed = line.trimStart();
|
|
43
|
+
if (!inFencedBlock) {
|
|
44
|
+
const fence = /^(`{3,}|~{3,})/.exec(trimmed)?.[1];
|
|
45
|
+
if (fence) {
|
|
46
|
+
inFencedBlock = true;
|
|
47
|
+
fenceChar = fence.charAt(0);
|
|
48
|
+
fenceLength = fence.length;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const closeFence = /^(`{3,}|~{3,})\s*$/.exec(trimmed)?.[1];
|
|
54
|
+
if (closeFence && closeFence[0] === fenceChar && closeFence.length >= fenceLength) {
|
|
55
|
+
inFencedBlock = false;
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// Skip indented code blocks (4 spaces or 1 tab)
|
|
60
|
+
if (/^(?: {4}|\t)/.test(line)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
// Match ATX headings: 1-6 # characters followed by a space
|
|
64
|
+
const headingMatch = /^(#{1,6})\s+(.+?)(?:\s+#+\s*)?$/.exec(trimmed);
|
|
65
|
+
const hashes = headingMatch?.[1];
|
|
66
|
+
const rawText = headingMatch?.[2];
|
|
67
|
+
if (hashes && rawText) {
|
|
68
|
+
const value = stripInlineMarkdown(rawText);
|
|
69
|
+
if (value) {
|
|
70
|
+
headings.push({
|
|
71
|
+
depth: hashes.length,
|
|
72
|
+
value,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
// Match setext headings:
|
|
78
|
+
// Heading level 1: Text followed by ===
|
|
79
|
+
// Heading level 2: Text followed by ---
|
|
80
|
+
const nextLine = lines[index + 1];
|
|
81
|
+
if (!nextLine || /^(?: {4}|\t)/.test(nextLine)) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const underline = /^(=+|-+)\s*$/.exec(nextLine.trim())?.[1];
|
|
85
|
+
if (!underline) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const value = stripInlineMarkdown(trimmed);
|
|
89
|
+
if (!value) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
headings.push({
|
|
93
|
+
depth: underline[0] === '=' ? 1 : 2,
|
|
94
|
+
value,
|
|
95
|
+
});
|
|
96
|
+
index++;
|
|
97
|
+
}
|
|
98
|
+
return headings;
|
|
99
|
+
}
|
package/package.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"helpers",
|
|
15
15
|
"js"
|
|
16
16
|
],
|
|
17
|
-
"version": "0.4.
|
|
17
|
+
"version": "0.4.3",
|
|
18
18
|
"engines": {
|
|
19
19
|
"node": ">=22"
|
|
20
20
|
},
|
|
@@ -47,6 +47,11 @@
|
|
|
47
47
|
"types": "./dist/file/*.d.ts",
|
|
48
48
|
"default": "./dist/file/*.js"
|
|
49
49
|
},
|
|
50
|
+
"./formatters/*": {
|
|
51
|
+
"import": "./dist/formatters/*.js",
|
|
52
|
+
"types": "./dist/formatters/*.d.ts",
|
|
53
|
+
"default": "./dist/formatters/*.js"
|
|
54
|
+
},
|
|
50
55
|
"./general/*": {
|
|
51
56
|
"import": "./dist/general/*.js",
|
|
52
57
|
"types": "./dist/general/*.d.ts",
|
|
@@ -62,6 +67,11 @@
|
|
|
62
67
|
"types": "./dist/json/*.d.ts",
|
|
63
68
|
"default": "./dist/json/*.js"
|
|
64
69
|
},
|
|
70
|
+
"./markdown/*": {
|
|
71
|
+
"import": "./dist/markdown/*.js",
|
|
72
|
+
"types": "./dist/markdown/*.d.ts",
|
|
73
|
+
"default": "./dist/markdown/*.js"
|
|
74
|
+
},
|
|
65
75
|
"./node/*": {
|
|
66
76
|
"import": "./dist/node/*.js",
|
|
67
77
|
"types": "./dist/node/*.d.ts",
|
|
@@ -97,9 +107,9 @@
|
|
|
97
107
|
"dist",
|
|
98
108
|
"CHANGELOG.md"
|
|
99
109
|
],
|
|
110
|
+
"sideEffects": false,
|
|
100
111
|
"devDependencies": {
|
|
101
112
|
"jsdom": "27.4.0",
|
|
102
|
-
"vite": "8.0.0",
|
|
103
113
|
"vitest": "4.1.0"
|
|
104
114
|
},
|
|
105
115
|
"scripts": {
|