@herowcode/utils 1.1.0 → 1.1.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.
Files changed (57) hide show
  1. package/README.md +137 -2
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.esm.js +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/string/format-hms-to-seconds.d.ts +2 -0
  8. package/dist/string/format-hms-to-seconds.d.ts.map +1 -0
  9. package/dist/string/format-hms-to-seconds.esm.js +25 -0
  10. package/dist/string/format-hms-to-seconds.js +25 -0
  11. package/dist/string/format-hms-to-seconds.js.map +1 -0
  12. package/dist/string/format-seconds-to-fragment.d.ts +3 -0
  13. package/dist/string/format-seconds-to-fragment.d.ts.map +1 -0
  14. package/dist/string/format-seconds-to-fragment.esm.js +15 -0
  15. package/dist/string/format-seconds-to-fragment.js +15 -0
  16. package/dist/string/format-seconds-to-fragment.js.map +1 -0
  17. package/dist/string/format-seconds-to-hms.d.ts +2 -0
  18. package/dist/string/format-seconds-to-hms.d.ts.map +1 -0
  19. package/dist/string/format-seconds-to-hms.esm.js +13 -0
  20. package/dist/string/format-seconds-to-hms.js +13 -0
  21. package/dist/string/format-seconds-to-hms.js.map +1 -0
  22. package/dist/string/format-string-to-time.d.ts +2 -0
  23. package/dist/string/format-string-to-time.d.ts.map +1 -0
  24. package/dist/string/format-string-to-time.esm.js +10 -0
  25. package/dist/string/format-string-to-time.js +10 -0
  26. package/dist/string/format-string-to-time.js.map +1 -0
  27. package/dist/string/index.d.ts +4 -0
  28. package/dist/string/index.d.ts.map +1 -1
  29. package/dist/string/index.esm.js +4 -0
  30. package/dist/string/index.js +4 -0
  31. package/dist/string/index.js.map +1 -1
  32. package/dist/youtube/extract-youtube-video-id.d.ts +2 -0
  33. package/dist/youtube/extract-youtube-video-id.d.ts.map +1 -0
  34. package/dist/youtube/extract-youtube-video-id.esm.js +26 -0
  35. package/dist/youtube/extract-youtube-video-id.js +26 -0
  36. package/dist/youtube/extract-youtube-video-id.js.map +1 -0
  37. package/dist/youtube/generate-youtube-url.d.ts +20 -0
  38. package/dist/youtube/generate-youtube-url.d.ts.map +1 -0
  39. package/dist/youtube/generate-youtube-url.esm.js +81 -0
  40. package/dist/youtube/generate-youtube-url.js +81 -0
  41. package/dist/youtube/generate-youtube-url.js.map +1 -0
  42. package/dist/youtube/index.d.ts +5 -0
  43. package/dist/youtube/index.d.ts.map +1 -0
  44. package/dist/youtube/index.esm.js +4 -0
  45. package/dist/youtube/index.js +4 -0
  46. package/dist/youtube/index.js.map +1 -0
  47. package/dist/youtube/use-get-video-duration.d.ts +7 -0
  48. package/dist/youtube/use-get-video-duration.d.ts.map +1 -0
  49. package/dist/youtube/use-get-video-duration.esm.js +150 -0
  50. package/dist/youtube/use-get-video-duration.js +150 -0
  51. package/dist/youtube/use-get-video-duration.js.map +1 -0
  52. package/dist/youtube/validate-youtube-link.d.ts +2 -0
  53. package/dist/youtube/validate-youtube-link.d.ts.map +1 -0
  54. package/dist/youtube/validate-youtube-link.esm.js +40 -0
  55. package/dist/youtube/validate-youtube-link.js +40 -0
  56. package/dist/youtube/validate-youtube-link.js.map +1 -0
  57. package/package.json +28 -13
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
@@ -3,4 +3,5 @@ export * from "./date/index.js";
3
3
  export * from "./files/index.js";
4
4
  export * from "./function/index.js";
5
5
  export * from "./string/index.js";
6
+ export * from "./youtube/index.js";
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -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,2 @@
1
+ export declare const formatHMSToSeconds: (val?: number | string) => number | null;
2
+ //# sourceMappingURL=format-hms-to-seconds.d.ts.map
@@ -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,3 @@
1
+ /** convert seconds to "1h2m3s" or "42s" style for fragment */
2
+ export declare const formatSecondsToFragment: (secs: number) => string;
3
+ //# sourceMappingURL=format-seconds-to-fragment.d.ts.map
@@ -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,2 @@
1
+ export declare function formatSecondsToHMS(totalSeconds: number): string;
2
+ //# sourceMappingURL=format-seconds-to-hms.d.ts.map
@@ -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,2 @@
1
+ export declare const formatStringToTime: (str: string) => string;
2
+ //# sourceMappingURL=format-string-to-time.d.ts.map
@@ -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"}
@@ -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"}
@@ -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,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.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,2 @@
1
+ export declare function extractYouTubeId(urlString: string | null): string | null;
2
+ //# sourceMappingURL=extract-youtube-video-id.d.ts.map
@@ -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
+ };