@herowcode/utils 1.1.0 → 1.1.2
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 +137 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.esm.js +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/string/format-hms-to-seconds.d.ts +2 -0
- package/dist/string/format-hms-to-seconds.d.ts.map +1 -0
- package/dist/string/format-hms-to-seconds.esm.js +25 -0
- package/dist/string/format-hms-to-seconds.js +25 -0
- package/dist/string/format-hms-to-seconds.js.map +1 -0
- package/dist/string/format-seconds-to-fragment.d.ts +3 -0
- package/dist/string/format-seconds-to-fragment.d.ts.map +1 -0
- package/dist/string/format-seconds-to-fragment.esm.js +15 -0
- package/dist/string/format-seconds-to-fragment.js +15 -0
- package/dist/string/format-seconds-to-fragment.js.map +1 -0
- package/dist/string/format-seconds-to-hms.d.ts +2 -0
- package/dist/string/format-seconds-to-hms.d.ts.map +1 -0
- package/dist/string/format-seconds-to-hms.esm.js +13 -0
- package/dist/string/format-seconds-to-hms.js +13 -0
- package/dist/string/format-seconds-to-hms.js.map +1 -0
- package/dist/string/format-string-to-time.d.ts +2 -0
- package/dist/string/format-string-to-time.d.ts.map +1 -0
- package/dist/string/format-string-to-time.esm.js +10 -0
- package/dist/string/format-string-to-time.js +10 -0
- package/dist/string/format-string-to-time.js.map +1 -0
- package/dist/string/index.d.ts +4 -0
- package/dist/string/index.d.ts.map +1 -1
- package/dist/string/index.esm.js +4 -0
- package/dist/string/index.js +4 -0
- package/dist/string/index.js.map +1 -1
- package/dist/youtube/extract-youtube-video-id.d.ts +2 -0
- package/dist/youtube/extract-youtube-video-id.d.ts.map +1 -0
- package/dist/youtube/extract-youtube-video-id.esm.js +26 -0
- package/dist/youtube/extract-youtube-video-id.js +26 -0
- package/dist/youtube/extract-youtube-video-id.js.map +1 -0
- package/dist/youtube/generate-youtube-url.d.ts +20 -0
- package/dist/youtube/generate-youtube-url.d.ts.map +1 -0
- package/dist/youtube/generate-youtube-url.esm.js +81 -0
- package/dist/youtube/generate-youtube-url.js +81 -0
- package/dist/youtube/generate-youtube-url.js.map +1 -0
- package/dist/youtube/index.d.ts +5 -0
- package/dist/youtube/index.d.ts.map +1 -0
- package/dist/youtube/index.esm.js +4 -0
- package/dist/youtube/index.js +4 -0
- package/dist/youtube/index.js.map +1 -0
- package/dist/youtube/use-get-video-duration.d.ts +7 -0
- package/dist/youtube/use-get-video-duration.d.ts.map +1 -0
- package/dist/youtube/use-get-video-duration.esm.js +150 -0
- package/dist/youtube/use-get-video-duration.js +150 -0
- package/dist/youtube/use-get-video-duration.js.map +1 -0
- package/dist/youtube/validate-youtube-link.d.ts +2 -0
- package/dist/youtube/validate-youtube-link.d.ts.map +1 -0
- package/dist/youtube/validate-youtube-link.esm.js +40 -0
- package/dist/youtube/validate-youtube-link.js +40 -0
- package/dist/youtube/validate-youtube-link.js.map +1 -0
- package/package.json +31 -16
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @herowcode/utils
|
|
2
2
|
|
|
3
|
-
A lightweight collection of utility functions for everyday JavaScript/TypeScript development. Built with dayjs for powerful date manipulation.
|
|
3
|
+
A lightweight collection of utility functions for everyday JavaScript/TypeScript development. Built with dayjs for powerful date manipulation and React hooks for YouTube integration.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -10,6 +10,7 @@ A lightweight collection of utility functions for everyday JavaScript/TypeScript
|
|
|
10
10
|
- 📱 **Universal** - Works in Node.js and browsers
|
|
11
11
|
- 🎯 **Tree-shakable** - Only import what you need
|
|
12
12
|
- 📂 **Scoped exports** - Import from specific modules
|
|
13
|
+
- 🎥 **YouTube utilities** - Extract video IDs, generate URLs, and get video durations
|
|
13
14
|
|
|
14
15
|
## Installation
|
|
15
16
|
|
|
@@ -23,7 +24,7 @@ yarn add @herowcode/utils
|
|
|
23
24
|
|
|
24
25
|
### Import everything:
|
|
25
26
|
```typescript
|
|
26
|
-
import { formatDate, capitalize, debounce } from '@herowcode/utils';
|
|
27
|
+
import { formatDate, capitalize, debounce, extractYouTubeId } from '@herowcode/utils';
|
|
27
28
|
```
|
|
28
29
|
|
|
29
30
|
### Import by scope:
|
|
@@ -32,6 +33,7 @@ import { formatDate, addDays } from '@herowcode/utils/date';
|
|
|
32
33
|
import { capitalize, camelCase } from '@herowcode/utils/string';
|
|
33
34
|
import { randomInt } from '@herowcode/utils/number';
|
|
34
35
|
import { debounce, throttle } from '@herowcode/utils/function';
|
|
36
|
+
import { extractYouTubeId, generateYoutubeURL } from '@herowcode/utils/youtube';
|
|
35
37
|
```
|
|
36
38
|
|
|
37
39
|
### Examples:
|
|
@@ -47,6 +49,14 @@ console.log(kebabCase('helloWorld')); // "hello-world"
|
|
|
47
49
|
|
|
48
50
|
// Function utilities
|
|
49
51
|
const debouncedFn = debounce(() => console.log('Called!'), 300);
|
|
52
|
+
|
|
53
|
+
// YouTube utilities
|
|
54
|
+
const videoId = extractYouTubeId('https://youtu.be/dQw4w9WgXcQ'); // "dQw4w9WgXcQ"
|
|
55
|
+
const embedUrl = generateYoutubeURL({
|
|
56
|
+
videoURL: 'https://youtu.be/abc123',
|
|
57
|
+
embed: true,
|
|
58
|
+
autoplay: true
|
|
59
|
+
}); // "https://www.youtube.com/embed/abc123?autoplay=1"
|
|
50
60
|
```
|
|
51
61
|
|
|
52
62
|
## API Reference
|
|
@@ -167,6 +177,41 @@ Capitalizes the first letter and lowercases the rest.
|
|
|
167
177
|
capitalize('hELLO'); // "Hello"
|
|
168
178
|
```
|
|
169
179
|
|
|
180
|
+
#### `formatHMSToSeconds(val?: number | string): number | null`
|
|
181
|
+
Converts HMS time format or numeric strings to seconds. Supports formats like "90", "01:30", "1:02:03".
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
formatHMSToSeconds("1:30"); // 90
|
|
185
|
+
formatHMSToSeconds("1:02:03"); // 3723
|
|
186
|
+
formatHMSToSeconds(120); // 120
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
#### `formatSecondsToFragment(secs: number): string`
|
|
190
|
+
Converts seconds to YouTube-style fragment format (e.g., "1h2m3s").
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
formatSecondsToFragment(3723); // "1h2m3s"
|
|
194
|
+
formatSecondsToFragment(90); // "1m30s"
|
|
195
|
+
formatSecondsToFragment(42); // "42s"
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### `formatSecondsToHMS(totalSeconds: number): string`
|
|
199
|
+
Formats a number of seconds into an HH:MM:SS string, rounding and clamping negatives to zero.
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
formatSecondsToHMS(3661); // "01:01:01"
|
|
203
|
+
formatSecondsToHMS(5); // "00:05"
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
#### `formatStringToTime(str: string): string`
|
|
207
|
+
Parses a numeric time string (or a string containing digits) into MM:SS or HH:MM:SS format. Non-digits are removed before formatting. Short inputs are zero-padded.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
formatStringToTime('123'); // "01:23"
|
|
211
|
+
formatStringToTime('12345'); // "01:23:45"
|
|
212
|
+
formatStringToTime(' 12:34 '); // "12:34"
|
|
213
|
+
```
|
|
214
|
+
|
|
170
215
|
#### `kebabCase(str: string): string`
|
|
171
216
|
Converts a string to kebab-case.
|
|
172
217
|
|
|
@@ -209,6 +254,94 @@ Truncates a string to a specified length, appending a suffix if truncated.
|
|
|
209
254
|
truncate('Hello world', 5); // "He..."
|
|
210
255
|
```
|
|
211
256
|
|
|
257
|
+
### YouTube Utilities
|
|
258
|
+
|
|
259
|
+
#### `extractYouTubeId(urlString: string | null): string | null`
|
|
260
|
+
Extracts the video ID from various YouTube URL formats.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
extractYouTubeId('https://youtu.be/dQw4w9WgXcQ'); // "dQw4w9WgXcQ"
|
|
264
|
+
extractYouTubeId('https://www.youtube.com/watch?v=dQw4w9WgXcQ'); // "dQw4w9WgXcQ"
|
|
265
|
+
extractYouTubeId('https://www.youtube.com/embed/dQw4w9WgXcQ'); // "dQw4w9WgXcQ"
|
|
266
|
+
extractYouTubeId('invalid-url'); // null
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
#### `generateYoutubeURL(options: TCreateYoutubeLinkOptions): string | null`
|
|
270
|
+
Generates YouTube URLs with various options for watch, embed, or short formats.
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
// Basic watch URL
|
|
274
|
+
generateYoutubeURL({ videoURL: 'https://youtu.be/abc123' });
|
|
275
|
+
// "https://www.youtube.com/watch?v=abc123"
|
|
276
|
+
|
|
277
|
+
// Embed URL with autoplay
|
|
278
|
+
generateYoutubeURL({
|
|
279
|
+
videoURL: 'https://youtu.be/abc123',
|
|
280
|
+
embed: true,
|
|
281
|
+
autoplay: true
|
|
282
|
+
});
|
|
283
|
+
// "https://www.youtube.com/embed/abc123?autoplay=1"
|
|
284
|
+
|
|
285
|
+
// Short URL with timestamp
|
|
286
|
+
generateYoutubeURL({
|
|
287
|
+
videoURL: 'https://youtu.be/abc123',
|
|
288
|
+
short: true,
|
|
289
|
+
start: "1:30"
|
|
290
|
+
});
|
|
291
|
+
// "https://youtu.be/abc123?t=90"
|
|
292
|
+
|
|
293
|
+
// URL with fragment timestamp
|
|
294
|
+
generateYoutubeURL({
|
|
295
|
+
videoURL: 'https://youtu.be/abc123',
|
|
296
|
+
start: "1:30",
|
|
297
|
+
useFragment: true
|
|
298
|
+
});
|
|
299
|
+
// "https://www.youtube.com/watch?v=abc123#t=1m30s"
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
**Options:**
|
|
303
|
+
- `videoURL` (required): YouTube URL to process
|
|
304
|
+
- `start`/`end`: Start/end times as seconds (number) or HMS strings ("90", "01:30", "1:02:03")
|
|
305
|
+
- `embed`: Generate embed URL format
|
|
306
|
+
- `short`: Generate youtu.be short URL format
|
|
307
|
+
- `useFragment`: Use #t=1m2s style fragment for timestamps
|
|
308
|
+
- `autoplay`, `controls`, `rel`, `loop`, `mute`, `modestbranding`: Player options
|
|
309
|
+
- `origin`, `playlist`: Additional parameters
|
|
310
|
+
- `params`: Custom query parameters
|
|
311
|
+
|
|
312
|
+
#### `useGetYoutubeVideoDuration(): (videoUrl: string) => Promise<string | null>`
|
|
313
|
+
React hook that returns a function to get YouTube video duration using the YouTube IFrame API.
|
|
314
|
+
|
|
315
|
+
```typescript
|
|
316
|
+
import { useGetYoutubeVideoDuration } from '@herowcode/utils/youtube';
|
|
317
|
+
|
|
318
|
+
function VideoComponent() {
|
|
319
|
+
const getVideoDuration = useGetYoutubeVideoDuration();
|
|
320
|
+
|
|
321
|
+
const handleGetDuration = async () => {
|
|
322
|
+
const duration = await getVideoDuration('https://youtu.be/dQw4w9WgXcQ');
|
|
323
|
+
console.log(duration); // "03:32" or null if failed
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
return <button onClick={handleGetDuration}>Get Duration</button>;
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
**Features:**
|
|
331
|
+
- Automatically loads YouTube IFrame API if not present
|
|
332
|
+
- Creates offscreen iframe for duration detection
|
|
333
|
+
- Handles retry logic for videos that don't immediately report duration
|
|
334
|
+
- 10-second timeout with automatic cleanup
|
|
335
|
+
- Returns formatted duration string (HH:MM:SS) or null on failure
|
|
336
|
+
|
|
337
|
+
#### `validateYoutubeLink(videoUrl: string): Promise<boolean>`
|
|
338
|
+
Checks whether a YouTube video exists by probing thumbnails and falling back to the oEmbed endpoint. Returns `true` for found/public videos and `false` otherwise.
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
const ok = await validateYoutubeLink('https://youtu.be/dQw4w9WgXcQ');
|
|
342
|
+
// true | false
|
|
343
|
+
```
|
|
344
|
+
|
|
212
345
|
## Browser Support
|
|
213
346
|
|
|
214
347
|
This library supports all modern browsers and Node.js environments. It uses ES2018 features and requires:
|
|
@@ -216,6 +349,8 @@ This library supports all modern browsers and Node.js environments. It uses ES20
|
|
|
216
349
|
- Node.js 10+
|
|
217
350
|
- Modern browsers (Chrome 63+, Firefox 58+, Safari 12+, Edge 79+)
|
|
218
351
|
|
|
352
|
+
The YouTube utilities require a browser environment with DOM support.
|
|
353
|
+
|
|
219
354
|
## Development
|
|
220
355
|
|
|
221
356
|
```bash
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAA;AAChC,cAAc,iBAAiB,CAAA;AAC/B,cAAc,kBAAkB,CAAA;AAChC,cAAc,qBAAqB,CAAA;AACnC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA"}
|
package/dist/index.esm.js
CHANGED
|
@@ -3,3 +3,4 @@ export * from "./date/index.js"; // Date utilities
|
|
|
3
3
|
export * from "./files/index.js"; // Files utilities
|
|
4
4
|
export * from "./function/index.js"; // Function utilities
|
|
5
5
|
export * from "./string/index.js"; // String utilities
|
|
6
|
+
export * from "./youtube/index.js"; // YouTube utilities
|
package/dist/index.js
CHANGED
|
@@ -3,3 +3,4 @@ export * from "./date/index.js"; // Date utilities
|
|
|
3
3
|
export * from "./files/index.js"; // Files utilities
|
|
4
4
|
export * from "./function/index.js"; // Function utilities
|
|
5
5
|
export * from "./string/index.js"; // String utilities
|
|
6
|
+
export * from "./youtube/index.js"; // YouTube utilities
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,mDAAgC,CAAC,kBAAkB;AACnD,kDAA+B,CAAC,iBAAiB;AACjD,mDAAgC,CAAC,kBAAkB;AACnD,sDAAmC,CAAC,qBAAqB;AACzD,oDAAiC,CAAC,mBAAmB"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,mDAAgC,CAAC,kBAAkB;AACnD,kDAA+B,CAAC,iBAAiB;AACjD,mDAAgC,CAAC,kBAAkB;AACnD,sDAAmC,CAAC,qBAAqB;AACzD,oDAAiC,CAAC,mBAAmB;AACrD,qDAAkC,CAAC,oBAAoB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-hms-to-seconds.d.ts","sourceRoot":"","sources":["../../src/string/format-hms-to-seconds.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,GAAG,MAAM,KAAG,MAAM,GAAG,IAoBnE,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const formatHMSToSeconds = (val) => {
|
|
2
|
+
if (val == null || val === "")
|
|
3
|
+
return null;
|
|
4
|
+
if (typeof val === "number")
|
|
5
|
+
return Number.isFinite(val) ? Math.max(0, Math.floor(val)) : null;
|
|
6
|
+
const s = String(val).trim();
|
|
7
|
+
// purely numeric string
|
|
8
|
+
if (/^\d+$/.test(s))
|
|
9
|
+
return Math.max(0, Number.parseInt(s, 10));
|
|
10
|
+
// hh:mm:ss or mm:ss
|
|
11
|
+
if (/^\d{1,2}(:\d{1,2}){1,2}$/.test(s)) {
|
|
12
|
+
const parts = s.split(":").map((p) => Number.parseInt(p, 10));
|
|
13
|
+
if (parts.some((n) => Number.isNaN(n)))
|
|
14
|
+
return null;
|
|
15
|
+
let seconds = 0;
|
|
16
|
+
if (parts.length === 3) {
|
|
17
|
+
seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
seconds = parts[0] * 60 + parts[1];
|
|
21
|
+
}
|
|
22
|
+
return Math.max(0, seconds);
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const formatHMSToSeconds = (val) => {
|
|
2
|
+
if (val == null || val === "")
|
|
3
|
+
return null;
|
|
4
|
+
if (typeof val === "number")
|
|
5
|
+
return Number.isFinite(val) ? Math.max(0, Math.floor(val)) : null;
|
|
6
|
+
const s = String(val).trim();
|
|
7
|
+
// purely numeric string
|
|
8
|
+
if (/^\d+$/.test(s))
|
|
9
|
+
return Math.max(0, Number.parseInt(s, 10));
|
|
10
|
+
// hh:mm:ss or mm:ss
|
|
11
|
+
if (/^\d{1,2}(:\d{1,2}){1,2}$/.test(s)) {
|
|
12
|
+
const parts = s.split(":").map((p) => Number.parseInt(p, 10));
|
|
13
|
+
if (parts.some((n) => Number.isNaN(n)))
|
|
14
|
+
return null;
|
|
15
|
+
let seconds = 0;
|
|
16
|
+
if (parts.length === 3) {
|
|
17
|
+
seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
seconds = parts[0] * 60 + parts[1];
|
|
21
|
+
}
|
|
22
|
+
return Math.max(0, seconds);
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-hms-to-seconds.js","sourceRoot":"","sources":["../../src/string/format-hms-to-seconds.ts"],"names":[],"mappings":";;;AAAO,MAAM,kBAAkB,GAAG,CAAC,GAAqB,EAAiB,EAAE;IACzE,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IAC1C,IAAI,OAAO,GAAG,KAAK,QAAQ;QACzB,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACnE,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAA;IAC5B,wBAAwB;IACxB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;IAC/D,oBAAoB;IACpB,IAAI,0BAA0B,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;QAC7D,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,IAAI,CAAA;QACnD,IAAI,OAAO,GAAG,CAAC,CAAA;QACf,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;QACtD,CAAC;aAAM,CAAC;YACN,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;QACpC,CAAC;QACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;IAC7B,CAAC;IACD,OAAO,IAAI,CAAA;AACb,CAAC,CAAA;AApBY,QAAA,kBAAkB,sBAoB9B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-seconds-to-fragment.d.ts","sourceRoot":"","sources":["../../src/string/format-seconds-to-fragment.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,eAAO,MAAM,uBAAuB,GAAI,MAAM,MAAM,KAAG,MAUtD,CAAA"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** convert seconds to "1h2m3s" or "42s" style for fragment */
|
|
2
|
+
export const formatSecondsToFragment = (secs) => {
|
|
3
|
+
const parsedSecs = Math.max(0, Math.floor(secs));
|
|
4
|
+
const h = Math.floor(parsedSecs / 3600);
|
|
5
|
+
const m = Math.floor((parsedSecs % 3600) / 60);
|
|
6
|
+
const s = parsedSecs % 60;
|
|
7
|
+
let out = "";
|
|
8
|
+
if (h > 0)
|
|
9
|
+
out += `${h}h`;
|
|
10
|
+
if (m > 0)
|
|
11
|
+
out += `${m}m`;
|
|
12
|
+
if (s > 0 || out === "")
|
|
13
|
+
out += `${s}s`;
|
|
14
|
+
return out;
|
|
15
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** convert seconds to "1h2m3s" or "42s" style for fragment */
|
|
2
|
+
export const formatSecondsToFragment = (secs) => {
|
|
3
|
+
const parsedSecs = Math.max(0, Math.floor(secs));
|
|
4
|
+
const h = Math.floor(parsedSecs / 3600);
|
|
5
|
+
const m = Math.floor((parsedSecs % 3600) / 60);
|
|
6
|
+
const s = parsedSecs % 60;
|
|
7
|
+
let out = "";
|
|
8
|
+
if (h > 0)
|
|
9
|
+
out += `${h}h`;
|
|
10
|
+
if (m > 0)
|
|
11
|
+
out += `${m}m`;
|
|
12
|
+
if (s > 0 || out === "")
|
|
13
|
+
out += `${s}s`;
|
|
14
|
+
return out;
|
|
15
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-seconds-to-fragment.js","sourceRoot":"","sources":["../../src/string/format-seconds-to-fragment.ts"],"names":[],"mappings":";;;AAAA,8DAA8D;AACvD,MAAM,uBAAuB,GAAG,CAAC,IAAY,EAAU,EAAE;IAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA;IAChD,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IACvC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;IAC9C,MAAM,CAAC,GAAG,UAAU,GAAG,EAAE,CAAA;IACzB,IAAI,GAAG,GAAG,EAAE,CAAA;IACZ,IAAI,CAAC,GAAG,CAAC;QAAE,GAAG,IAAI,GAAG,CAAC,GAAG,CAAA;IACzB,IAAI,CAAC,GAAG,CAAC;QAAE,GAAG,IAAI,GAAG,CAAC,GAAG,CAAA;IACzB,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,EAAE;QAAE,GAAG,IAAI,GAAG,CAAC,GAAG,CAAA;IACvC,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAVY,QAAA,uBAAuB,2BAUnC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-seconds-to-hms.d.ts","sourceRoot":"","sources":["../../src/string/format-seconds-to-hms.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAc/D"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function formatSecondsToHMS(totalSeconds) {
|
|
2
|
+
const rounded = Math.max(0, Math.round(totalSeconds));
|
|
3
|
+
const hours = Math.floor(rounded / 3600);
|
|
4
|
+
const minutes = Math.floor((rounded % 3600) / 60);
|
|
5
|
+
const seconds = rounded % 60;
|
|
6
|
+
const hh = String(hours).padStart(2, "0");
|
|
7
|
+
const mm = String(minutes).padStart(2, "0");
|
|
8
|
+
const ss = String(seconds).padStart(2, "0");
|
|
9
|
+
if (hours === 0) {
|
|
10
|
+
return `${mm}:${ss}`;
|
|
11
|
+
}
|
|
12
|
+
return `${hh}:${mm}:${ss}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function formatSecondsToHMS(totalSeconds) {
|
|
2
|
+
const rounded = Math.max(0, Math.round(totalSeconds));
|
|
3
|
+
const hours = Math.floor(rounded / 3600);
|
|
4
|
+
const minutes = Math.floor((rounded % 3600) / 60);
|
|
5
|
+
const seconds = rounded % 60;
|
|
6
|
+
const hh = String(hours).padStart(2, "0");
|
|
7
|
+
const mm = String(minutes).padStart(2, "0");
|
|
8
|
+
const ss = String(seconds).padStart(2, "0");
|
|
9
|
+
if (hours === 0) {
|
|
10
|
+
return `${mm}:${ss}`;
|
|
11
|
+
}
|
|
12
|
+
return `${hh}:${mm}:${ss}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-seconds-to-hms.js","sourceRoot":"","sources":["../../src/string/format-seconds-to-hms.ts"],"names":[],"mappings":";;AAAA,gDAcC;AAdD,SAAgB,kBAAkB,CAAC,YAAoB;IACrD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,CAAA;IACrD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAA;IACxC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;IACjD,MAAM,OAAO,GAAG,OAAO,GAAG,EAAE,CAAA;IAC5B,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IACzC,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IAC3C,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IAE3C,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QAChB,OAAO,GAAG,EAAE,IAAI,EAAE,EAAE,CAAA;IACtB,CAAC;IAED,OAAO,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,CAAA;AAC5B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-string-to-time.d.ts","sourceRoot":"","sources":["../../src/string/format-string-to-time.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,kBAAkB,GAAI,KAAK,MAAM,WAU7C,CAAA"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const formatStringToTime = (str) => {
|
|
2
|
+
const stringCleaned = Number(str.replace(/\D/g, "")).toString();
|
|
3
|
+
if (stringCleaned.length > 4) {
|
|
4
|
+
return stringCleaned
|
|
5
|
+
.padStart(6, "0")
|
|
6
|
+
.slice(0, 6)
|
|
7
|
+
.replace(/(\d{2})(\d{2})(\d{2})/, "$1:$2:$3");
|
|
8
|
+
}
|
|
9
|
+
return stringCleaned.padStart(4, "0").replace(/(\d{2})(\d{2})/, "$1:$2");
|
|
10
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const formatStringToTime = (str) => {
|
|
2
|
+
const stringCleaned = Number(str.replace(/\D/g, "")).toString();
|
|
3
|
+
if (stringCleaned.length > 4) {
|
|
4
|
+
return stringCleaned
|
|
5
|
+
.padStart(6, "0")
|
|
6
|
+
.slice(0, 6)
|
|
7
|
+
.replace(/(\d{2})(\d{2})(\d{2})/, "$1:$2:$3");
|
|
8
|
+
}
|
|
9
|
+
return stringCleaned.padStart(4, "0").replace(/(\d{2})(\d{2})/, "$1:$2");
|
|
10
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format-string-to-time.js","sourceRoot":"","sources":["../../src/string/format-string-to-time.ts"],"names":[],"mappings":";;;AAAO,MAAM,kBAAkB,GAAG,CAAC,GAAW,EAAE,EAAE;IAChD,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAA;IAC/D,IAAI,aAAa,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,aAAa;aACjB,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC;aAChB,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;aACX,OAAO,CAAC,uBAAuB,EAAE,UAAU,CAAC,CAAA;IACjD,CAAC;IAED,OAAO,aAAa,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAA;AAC1E,CAAC,CAAA;AAVY,QAAA,kBAAkB,sBAU9B"}
|
package/dist/string/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export * from "./camel-case";
|
|
2
2
|
export * from "./capitalize";
|
|
3
|
+
export * from "./format-hms-to-seconds";
|
|
4
|
+
export * from "./format-seconds-to-fragment";
|
|
5
|
+
export * from "./format-seconds-to-hms";
|
|
6
|
+
export * from "./format-string-to-time";
|
|
3
7
|
export * from "./kebab-case";
|
|
4
8
|
export * from "./remove-html-tags";
|
|
5
9
|
export * from "./slugify";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/string/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/string/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,cAAc,CAAA;AAC5B,cAAc,yBAAyB,CAAA;AACvC,cAAc,8BAA8B,CAAA;AAC5C,cAAc,yBAAyB,CAAA;AACvC,cAAc,yBAAyB,CAAA;AACvC,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,WAAW,CAAA;AACzB,cAAc,cAAc,CAAA;AAC5B,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA"}
|
package/dist/string/index.esm.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export * from "./camel-case";
|
|
2
2
|
export * from "./capitalize";
|
|
3
|
+
export * from "./format-hms-to-seconds";
|
|
4
|
+
export * from "./format-seconds-to-fragment";
|
|
5
|
+
export * from "./format-seconds-to-hms";
|
|
6
|
+
export * from "./format-string-to-time";
|
|
3
7
|
export * from "./kebab-case";
|
|
4
8
|
export * from "./remove-html-tags";
|
|
5
9
|
export * from "./slugify";
|
package/dist/string/index.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
export * from "./camel-case";
|
|
2
2
|
export * from "./capitalize";
|
|
3
|
+
export * from "./format-hms-to-seconds";
|
|
4
|
+
export * from "./format-seconds-to-fragment";
|
|
5
|
+
export * from "./format-seconds-to-hms";
|
|
6
|
+
export * from "./format-string-to-time";
|
|
3
7
|
export * from "./kebab-case";
|
|
4
8
|
export * from "./remove-html-tags";
|
|
5
9
|
export * from "./slugify";
|
package/dist/string/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/string/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA4B;AAC5B,+CAA4B;AAC5B,+CAA4B;AAC5B,qDAAkC;AAClC,4CAAyB;AACzB,+CAA4B;AAC5B,qDAAkC;AAClC,6CAA0B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/string/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA4B;AAC5B,+CAA4B;AAC5B,0DAAuC;AACvC,+DAA4C;AAC5C,0DAAuC;AACvC,0DAAuC;AACvC,+CAA4B;AAC5B,qDAAkC;AAClC,4CAAyB;AACzB,+CAA4B;AAC5B,qDAAkC;AAClC,6CAA0B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-youtube-video-id.d.ts","sourceRoot":"","sources":["../../src/youtube/extract-youtube-video-id.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CA4BxE"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function extractYouTubeId(urlString) {
|
|
2
|
+
if (!urlString)
|
|
3
|
+
return null;
|
|
4
|
+
try {
|
|
5
|
+
const url = new URL(urlString);
|
|
6
|
+
const hostname = url.hostname.toLowerCase();
|
|
7
|
+
const pathname = url.pathname;
|
|
8
|
+
if (hostname.includes("youtu.be")) {
|
|
9
|
+
const id = pathname.replace(/^\/+/, "").split(/[?&#]/)[0];
|
|
10
|
+
return id || null;
|
|
11
|
+
}
|
|
12
|
+
if (hostname.includes("youtube.com")) {
|
|
13
|
+
if (url.searchParams.has("v")) {
|
|
14
|
+
return url.searchParams.get("v");
|
|
15
|
+
}
|
|
16
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
17
|
+
const last = parts[parts.length - 1];
|
|
18
|
+
if (last)
|
|
19
|
+
return last.split(/[?&#]/)[0] || null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (_a) { }
|
|
23
|
+
const regex = /(?:v=|\/embed\/|\/shorts\/|youtu\.be\/)([0-9A-Za-z_-]{6,})/i;
|
|
24
|
+
const match = urlString.match(regex);
|
|
25
|
+
return match ? match[1] : null;
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function extractYouTubeId(urlString) {
|
|
2
|
+
if (!urlString)
|
|
3
|
+
return null;
|
|
4
|
+
try {
|
|
5
|
+
const url = new URL(urlString);
|
|
6
|
+
const hostname = url.hostname.toLowerCase();
|
|
7
|
+
const pathname = url.pathname;
|
|
8
|
+
if (hostname.includes("youtu.be")) {
|
|
9
|
+
const id = pathname.replace(/^\/+/, "").split(/[?&#]/)[0];
|
|
10
|
+
return id || null;
|
|
11
|
+
}
|
|
12
|
+
if (hostname.includes("youtube.com")) {
|
|
13
|
+
if (url.searchParams.has("v")) {
|
|
14
|
+
return url.searchParams.get("v");
|
|
15
|
+
}
|
|
16
|
+
const parts = pathname.split("/").filter(Boolean);
|
|
17
|
+
const last = parts[parts.length - 1];
|
|
18
|
+
if (last)
|
|
19
|
+
return last.split(/[?&#]/)[0] || null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (_a) { }
|
|
23
|
+
const regex = /(?:v=|\/embed\/|\/shorts\/|youtu\.be\/)([0-9A-Za-z_-]{6,})/i;
|
|
24
|
+
const match = urlString.match(regex);
|
|
25
|
+
return match ? match[1] : null;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extract-youtube-video-id.js","sourceRoot":"","sources":["../../src/youtube/extract-youtube-video-id.ts"],"names":[],"mappings":";;AAAA,4CA4BC;AA5BD,SAAgB,gBAAgB,CAAC,SAAwB;IACvD,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAA;IAE3B,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAA;QAE9B,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAA;QAC3C,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAA;QAE7B,IAAI,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAClC,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;YACzD,OAAO,EAAE,IAAI,IAAI,CAAA;QACnB,CAAC;QAED,IAAI,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YACrC,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC9B,OAAO,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAClC,CAAC;YAED,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YACjD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;YACpC,IAAI,IAAI;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAA;QACjD,CAAC;IACH,CAAC;IAAC,WAAM,CAAC,CAAA,CAAC;IAEV,MAAM,KAAK,GAAG,6DAA6D,CAAA;IAC3E,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACpC,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AAChC,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
type TCreateYoutubeLinkOptions = {
|
|
2
|
+
videoURL: string;
|
|
3
|
+
start?: number | string;
|
|
4
|
+
end?: number | string;
|
|
5
|
+
embed?: boolean;
|
|
6
|
+
short?: boolean;
|
|
7
|
+
useFragment?: boolean;
|
|
8
|
+
autoplay?: boolean;
|
|
9
|
+
controls?: 0 | 1;
|
|
10
|
+
rel?: 0 | 1;
|
|
11
|
+
loop?: boolean;
|
|
12
|
+
mute?: boolean;
|
|
13
|
+
modestbranding?: 0 | 1;
|
|
14
|
+
origin?: string;
|
|
15
|
+
playlist?: string;
|
|
16
|
+
params?: Record<string, string | number | boolean>;
|
|
17
|
+
};
|
|
18
|
+
export declare const generateYoutubeURL: (opts: TCreateYoutubeLinkOptions) => string | null;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=generate-youtube-url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-youtube-url.d.ts","sourceRoot":"","sources":["../../src/youtube/generate-youtube-url.ts"],"names":[],"mappings":"AAGA,KAAK,yBAAyB,GAAG;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAEhB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACvB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAErB,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,WAAW,CAAC,EAAE,OAAO,CAAA;IAErB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IAChB,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IACX,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,cAAc,CAAC,EAAE,CAAC,GAAG,CAAC,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAA;CACnD,CAAA;AAED,eAAO,MAAM,kBAAkB,GAC7B,MAAM,yBAAyB,KAC9B,MAAM,GAAG,IAiGX,CAAA"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { formatHMSToSeconds, formatSecondsToFragment } from "../string";
|
|
2
|
+
import { extractYouTubeId } from "./extract-youtube-video-id";
|
|
3
|
+
export const generateYoutubeURL = (opts) => {
|
|
4
|
+
const { videoURL, start, end, embed = false, short = false, useFragment = false, autoplay, controls, rel, loop, mute, modestbranding, origin, playlist, params = {}, } = opts;
|
|
5
|
+
const videoId = extractYouTubeId(videoURL);
|
|
6
|
+
if (!videoId)
|
|
7
|
+
return null;
|
|
8
|
+
const startSec = formatHMSToSeconds(start);
|
|
9
|
+
const endSec = formatHMSToSeconds(end);
|
|
10
|
+
// base url
|
|
11
|
+
const base = embed
|
|
12
|
+
? `https://www.youtube.com/embed/${videoId}`
|
|
13
|
+
: short
|
|
14
|
+
? `https://youtu.be/${videoId}`
|
|
15
|
+
: "https://www.youtube.com/watch";
|
|
16
|
+
const search = new URLSearchParams();
|
|
17
|
+
// Add video ID for watch URLs
|
|
18
|
+
if (!embed && !short) {
|
|
19
|
+
search.set("v", videoId);
|
|
20
|
+
}
|
|
21
|
+
// Standard param names for watch/embed
|
|
22
|
+
if (!useFragment) {
|
|
23
|
+
if (startSec != null) {
|
|
24
|
+
// youtu.be historically uses "t" as a query param, but "start" is widely supported.
|
|
25
|
+
// Use "t" for short links, otherwise "start".
|
|
26
|
+
search.set(short ? "t" : "start", String(startSec));
|
|
27
|
+
}
|
|
28
|
+
if (endSec != null) {
|
|
29
|
+
search.set("end", String(endSec));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// embed / player related params
|
|
33
|
+
if (typeof autoplay !== "undefined")
|
|
34
|
+
search.set("autoplay", autoplay ? "1" : "0");
|
|
35
|
+
if (typeof controls !== "undefined")
|
|
36
|
+
search.set("controls", String(controls));
|
|
37
|
+
if (typeof rel !== "undefined")
|
|
38
|
+
search.set("rel", String(rel));
|
|
39
|
+
if (typeof modestbranding !== "undefined")
|
|
40
|
+
search.set("modestbranding", String(modestbranding));
|
|
41
|
+
if (typeof origin !== "undefined")
|
|
42
|
+
search.set("origin", origin);
|
|
43
|
+
if (typeof mute !== "undefined")
|
|
44
|
+
search.set("mute", mute ? "1" : "0");
|
|
45
|
+
// loop requires playlist param when embedding a single video
|
|
46
|
+
if (loop) {
|
|
47
|
+
search.set("loop", "1");
|
|
48
|
+
if (playlist) {
|
|
49
|
+
search.set("playlist", playlist);
|
|
50
|
+
}
|
|
51
|
+
else if (embed) {
|
|
52
|
+
// for embed+loop, YouTube expects &playlist=VIDEO_ID
|
|
53
|
+
search.set("playlist", videoId);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
else if (playlist) {
|
|
57
|
+
search.set("playlist", playlist);
|
|
58
|
+
}
|
|
59
|
+
// merge custom params (allow overriding)
|
|
60
|
+
Object.entries(params).forEach(([k, v]) => {
|
|
61
|
+
if (v === false)
|
|
62
|
+
search.set(k, "0");
|
|
63
|
+
else if (v === true)
|
|
64
|
+
search.set(k, "1");
|
|
65
|
+
else
|
|
66
|
+
search.set(k, String(v));
|
|
67
|
+
});
|
|
68
|
+
const qs = search.toString() ? `?${search.toString()}` : "";
|
|
69
|
+
// fragment handling (e.g. #t=1m2s)
|
|
70
|
+
let fragment = "";
|
|
71
|
+
if (useFragment && startSec != null) {
|
|
72
|
+
fragment = `#t=${formatSecondsToFragment(startSec)}`;
|
|
73
|
+
}
|
|
74
|
+
// For short links, people often prefer the short host and start as query or fragment.
|
|
75
|
+
if (short) {
|
|
76
|
+
// prefer fragment if requested, otherwise use search params (t)
|
|
77
|
+
return `${base}${qs}${fragment}`;
|
|
78
|
+
}
|
|
79
|
+
// watch/embed links
|
|
80
|
+
return `${base}${qs}${fragment}`;
|
|
81
|
+
};
|