@ibiliaze/stringman 3.1.0 → 3.2.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/dist/index.d.ts CHANGED
@@ -1,8 +1,129 @@
1
+ /**
2
+ * Clean up extra whitespace in a string.
3
+ *
4
+ * - Collapses multiple consecutive whitespace characters (spaces, tabs, newlines)
5
+ * into a single space.
6
+ * - Trims leading and trailing whitespace.
7
+ *
8
+ * @param stringData - The input string to clean
9
+ * @returns The cleaned string with normalized spacing
10
+ *
11
+ * Example:
12
+ * superTrim(" Hello World ");
13
+ * // → "Hello World"
14
+ *
15
+ * superTrim("Line1\n\nLine2");
16
+ * // → "Line1 Line2"
17
+ */
1
18
  export declare const superTrim: (stringData: string) => string;
19
+ /**
20
+ * Remove extra whitespace from a string.
21
+ *
22
+ * - Collapses multiple consecutive whitespace characters into a single empty string (removes them completely).
23
+ * - Useful for cleaning up text where multiple spaces, tabs, or line breaks appear.
24
+ *
25
+ * @param stringData - The input string to clean
26
+ * @returns The string with extra whitespace removed
27
+ *
28
+ * Example:
29
+ * megaTrim("Hello World"); // → "HelloWorld"
30
+ * megaTrim("Line1\n\n\nLine2"); // → "Line1Line2"
31
+ */
2
32
  export declare const megaTrim: (stringData: string) => string;
33
+ /**
34
+ * Format a number to a fixed number of decimal places.
35
+ *
36
+ * - If the input is a valid number, it rounds it to the specified number of decimals.
37
+ * - If the input is not a number, it returns 0.
38
+ *
39
+ * @param int - The input number to format
40
+ * @param i - The number of decimal places to keep
41
+ * @returns The formatted number with the given decimal places
42
+ *
43
+ * Example:
44
+ * dp(12.3456, 2); // → 12.35
45
+ * dp(99.999, 0); // → 100
46
+ * dp(NaN as any, 2); // → 0
47
+ */
3
48
  export declare const dp: (int: number, i: number) => number;
49
+ /**
50
+ * Extract the Cloudinary public ID from a given Cloudinary image URL.
51
+ *
52
+ * A Cloudinary URL typically looks like:
53
+ * https://res.cloudinary.com/<cloud_name>/image/upload/v<version>/<folder>/<fileName>.<ext>
54
+ *
55
+ * This function:
56
+ * 1. Removes the Cloudinary prefix (protocol, domain, resource type, and version).
57
+ * 2. Strips the file extension (e.g. ".jpg").
58
+ * 3. Decodes any URL-encoded characters (e.g. "%20" → " ").
59
+ *
60
+ * @param url - The full Cloudinary image URL
61
+ * @returns The extracted public ID (without extension), or an empty string on failure
62
+ *
63
+ * Example:
64
+ * getCloudinaryPublicId("https://res.cloudinary.com/demo/image/upload/v12345/folder/my%20image.jpg")
65
+ * // → "folder/my image"
66
+ */
4
67
  export declare const getCloudinaryPublicId: (url: string) => string;
68
+ /**
69
+ * Build a query string from a key-value object.
70
+ *
71
+ * - Filters out keys/values that are empty or invalid (e.g. "", null, undefined, "null", "undefined").
72
+ * - Returns a string starting with "?" if there are valid params, otherwise returns an empty string.
73
+ *
74
+ * @param q - An object of query parameters
75
+ * @returns A properly formatted query string
76
+ *
77
+ * Example:
78
+ * query({ limit: 10, skip: 20, sortBy: 'createdAt:desc', empty: '' });
79
+ * // → "?limit=10&skip=20&sortBy=createdAt%3Adesc"
80
+ */
5
81
  export declare const query: (q: Record<string, any>) => string;
82
+ /**
83
+ * Build a comma-separated string from a list of object keys.
84
+ *
85
+ * Useful when you need to select specific fields (e.g., for APIs, databases, or queries).
86
+ *
87
+ * @typeParam T - The type of the object whose keys are being selected
88
+ * @param keys - An array of keys from the object type T
89
+ * @returns A comma-separated string of the given keys
90
+ *
91
+ * Example:
92
+ * type User = { id: string; name: string; email: string };
93
+ * select<User>(['id', 'email']); // → "id,email"
94
+ */
6
95
  export declare const select: <T>(keys: (keyof T)[]) => string;
7
- export declare const extractImageSrcs: (htmlString: string) => any[];
96
+ /**
97
+ * Extract all image `src` attribute values from an HTML string.
98
+ *
99
+ * Uses a regex to find <img> tags and capture the value of the `src` attribute.
100
+ *
101
+ * @param htmlString - The HTML string to search for image tags
102
+ * @returns An array of image source URLs
103
+ *
104
+ * Example:
105
+ * extractImageSrcs('<img src="a.png"><div><img src="b.jpg"></div>');
106
+ * // → ["a.png", "b.jpg"]
107
+ */
108
+ export declare const extractImageSrcs: (htmlString: string) => string[];
109
+ /**
110
+ * Generate a random alphanumeric string (uppercase letters + digits).
111
+ *
112
+ * @param length - The desired length of the generated string
113
+ * @returns A randomly generated string of the given length
114
+ *
115
+ * Example:
116
+ * getRandomString(6) → "A9X2KQ"
117
+ */
8
118
  export declare const getRandomString: (length: number) => string;
119
+ /**
120
+ * Check if a given URL points to a video file.
121
+ *
122
+ * Rules:
123
+ * 1. If the URL contains Cloudinary's `video/upload` path → treat as video.
124
+ * 2. Otherwise, check common video file extensions (mp4, webm, mov, avi, mkv).
125
+ *
126
+ * @param url - The URL string to check
127
+ * @returns true if the URL is a video, false otherwise
128
+ */
129
+ export declare const isVideoUrl: (url: string) => boolean;
package/dist/index.js CHANGED
@@ -1,29 +1,111 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getRandomString = exports.extractImageSrcs = exports.select = exports.query = exports.getCloudinaryPublicId = exports.dp = exports.megaTrim = exports.superTrim = void 0;
3
+ exports.isVideoUrl = exports.getRandomString = exports.extractImageSrcs = exports.select = exports.query = exports.getCloudinaryPublicId = exports.dp = exports.megaTrim = exports.superTrim = void 0;
4
+ /**
5
+ * Clean up extra whitespace in a string.
6
+ *
7
+ * - Collapses multiple consecutive whitespace characters (spaces, tabs, newlines)
8
+ * into a single space.
9
+ * - Trims leading and trailing whitespace.
10
+ *
11
+ * @param stringData - The input string to clean
12
+ * @returns The cleaned string with normalized spacing
13
+ *
14
+ * Example:
15
+ * superTrim(" Hello World ");
16
+ * // → "Hello World"
17
+ *
18
+ * superTrim("Line1\n\nLine2");
19
+ * // → "Line1 Line2"
20
+ */
4
21
  const superTrim = (stringData) => stringData.replace(/\s\s+/g, ' ').trim();
5
22
  exports.superTrim = superTrim;
23
+ /**
24
+ * Remove extra whitespace from a string.
25
+ *
26
+ * - Collapses multiple consecutive whitespace characters into a single empty string (removes them completely).
27
+ * - Useful for cleaning up text where multiple spaces, tabs, or line breaks appear.
28
+ *
29
+ * @param stringData - The input string to clean
30
+ * @returns The string with extra whitespace removed
31
+ *
32
+ * Example:
33
+ * megaTrim("Hello World"); // → "HelloWorld"
34
+ * megaTrim("Line1\n\n\nLine2"); // → "Line1Line2"
35
+ */
6
36
  const megaTrim = (stringData) => stringData.replace(/\s\s+/g, '');
7
37
  exports.megaTrim = megaTrim;
38
+ /**
39
+ * Format a number to a fixed number of decimal places.
40
+ *
41
+ * - If the input is a valid number, it rounds it to the specified number of decimals.
42
+ * - If the input is not a number, it returns 0.
43
+ *
44
+ * @param int - The input number to format
45
+ * @param i - The number of decimal places to keep
46
+ * @returns The formatted number with the given decimal places
47
+ *
48
+ * Example:
49
+ * dp(12.3456, 2); // → 12.35
50
+ * dp(99.999, 0); // → 100
51
+ * dp(NaN as any, 2); // → 0
52
+ */
8
53
  const dp = (int, i) => (typeof int === 'number' ? +int.toFixed(i) : 0);
9
54
  exports.dp = dp;
55
+ /**
56
+ * Extract the Cloudinary public ID from a given Cloudinary image URL.
57
+ *
58
+ * A Cloudinary URL typically looks like:
59
+ * https://res.cloudinary.com/<cloud_name>/image/upload/v<version>/<folder>/<fileName>.<ext>
60
+ *
61
+ * This function:
62
+ * 1. Removes the Cloudinary prefix (protocol, domain, resource type, and version).
63
+ * 2. Strips the file extension (e.g. ".jpg").
64
+ * 3. Decodes any URL-encoded characters (e.g. "%20" → " ").
65
+ *
66
+ * @param url - The full Cloudinary image URL
67
+ * @returns The extracted public ID (without extension), or an empty string on failure
68
+ *
69
+ * Example:
70
+ * getCloudinaryPublicId("https://res.cloudinary.com/demo/image/upload/v12345/folder/my%20image.jpg")
71
+ * // → "folder/my image"
72
+ */
10
73
  const getCloudinaryPublicId = (url) => {
11
74
  try {
75
+ // Remove Cloudinary domain + "image/upload/v{version}/"
12
76
  const public_id_with_extension = url.replace(/^https?:\/\/res\.cloudinary\.com\/[^/]+\/image\/upload\/v\d+\//, '');
13
- // Decode URL encoding (e.g., %20 space)
77
+ // Decode URL encoding and strip extension (everything after the last ".")
14
78
  const public_id = decodeURIComponent(public_id_with_extension.replace(/\.[^.]+$/, ''));
15
79
  return public_id;
16
80
  }
17
- catch (e) {
81
+ catch {
82
+ // In case of invalid URL or regex failure
18
83
  return '';
19
84
  }
20
85
  };
21
86
  exports.getCloudinaryPublicId = getCloudinaryPublicId;
87
+ /**
88
+ * Build a query string from a key-value object.
89
+ *
90
+ * - Filters out keys/values that are empty or invalid (e.g. "", null, undefined, "null", "undefined").
91
+ * - Returns a string starting with "?" if there are valid params, otherwise returns an empty string.
92
+ *
93
+ * @param q - An object of query parameters
94
+ * @returns A properly formatted query string
95
+ *
96
+ * Example:
97
+ * query({ limit: 10, skip: 20, sortBy: 'createdAt:desc', empty: '' });
98
+ * // → "?limit=10&skip=20&sortBy=createdAt%3Adesc"
99
+ */
22
100
  const query = (q) => {
23
101
  try {
102
+ // Values considered "invalid" and should be filtered out
24
103
  const filter = ['', null, undefined, 'null', 'undefined'];
104
+ // Convert object to entries, filter out invalid keys/values
25
105
  const filtered = Object.entries(q).filter(([key, value]) => !filter.includes(key) && !filter.includes(value));
106
+ // Build query string using URLSearchParams
26
107
  const query = new URLSearchParams(filtered).toString();
108
+ // Prefix with "?" if there are any params
27
109
  return query ? `?${query}` : '';
28
110
  }
29
111
  catch (e) {
@@ -32,21 +114,88 @@ const query = (q) => {
32
114
  }
33
115
  };
34
116
  exports.query = query;
117
+ /**
118
+ * Build a comma-separated string from a list of object keys.
119
+ *
120
+ * Useful when you need to select specific fields (e.g., for APIs, databases, or queries).
121
+ *
122
+ * @typeParam T - The type of the object whose keys are being selected
123
+ * @param keys - An array of keys from the object type T
124
+ * @returns A comma-separated string of the given keys
125
+ *
126
+ * Example:
127
+ * type User = { id: string; name: string; email: string };
128
+ * select<User>(['id', 'email']); // → "id,email"
129
+ */
35
130
  const select = (keys) => {
131
+ // Join all keys into a single comma-separated string
36
132
  return keys.join(',');
37
133
  };
38
134
  exports.select = select;
135
+ /**
136
+ * Extract all image `src` attribute values from an HTML string.
137
+ *
138
+ * Uses a regex to find <img> tags and capture the value of the `src` attribute.
139
+ *
140
+ * @param htmlString - The HTML string to search for image tags
141
+ * @returns An array of image source URLs
142
+ *
143
+ * Example:
144
+ * extractImageSrcs('<img src="a.png"><div><img src="b.jpg"></div>');
145
+ * // → ["a.png", "b.jpg"]
146
+ */
39
147
  const extractImageSrcs = (htmlString) => {
148
+ // Find all <img> tags with a src attribute
40
149
  const imgTags = [...htmlString.matchAll(/<img\s+[^>]*src=["']([^"']+)["'][^>]*>/gi)];
150
+ // Map over regex matches and extract the first capture group (the src value)
41
151
  return imgTags.map(match => match[1]);
42
152
  };
43
153
  exports.extractImageSrcs = extractImageSrcs;
154
+ /**
155
+ * Generate a random alphanumeric string (uppercase letters + digits).
156
+ *
157
+ * @param length - The desired length of the generated string
158
+ * @returns A randomly generated string of the given length
159
+ *
160
+ * Example:
161
+ * getRandomString(6) → "A9X2KQ"
162
+ */
44
163
  const getRandomString = (length) => {
164
+ // Allowed characters: A–Z and 0–9
45
165
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
46
166
  let result = '';
167
+ // Loop for the requested length
47
168
  for (let i = 0; i < length; i++) {
169
+ // Pick a random index and append the corresponding character
48
170
  result += characters.charAt(Math.floor(Math.random() * characters.length));
49
171
  }
50
172
  return result;
51
173
  };
52
174
  exports.getRandomString = getRandomString;
175
+ /**
176
+ * Check if a given URL points to a video file.
177
+ *
178
+ * Rules:
179
+ * 1. If the URL contains Cloudinary's `video/upload` path → treat as video.
180
+ * 2. Otherwise, check common video file extensions (mp4, webm, mov, avi, mkv).
181
+ *
182
+ * @param url - The URL string to check
183
+ * @returns true if the URL is a video, false otherwise
184
+ */
185
+ const isVideoUrl = (url) => {
186
+ try {
187
+ const parsed = new URL(url);
188
+ // ✅ Rule 1: Cloudinary-specific pattern
189
+ if (parsed.pathname.includes('/video/upload/'))
190
+ return true;
191
+ // ✅ Rule 2: Check file extension
192
+ const videoExtensions = ['.mp4', '.webm', '.mov', '.avi', '.mkv'];
193
+ const lowerPath = parsed.pathname.toLowerCase();
194
+ return videoExtensions.some(ext => lowerPath.endsWith(ext));
195
+ }
196
+ catch {
197
+ // ❌ If URL parsing fails, assume it's not a valid video URL
198
+ return false;
199
+ }
200
+ };
201
+ exports.isVideoUrl = isVideoUrl;
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@ibiliaze/stringman",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "scripts": {
8
8
  "build": "tsc",
9
9
  "pub": "npm publish --access public",
10
- "git": "git add .; git commit -m 'changes'; git tag -a v3.1.0 -m 'v3.1.0'; git push origin v3.1.0; git push",
10
+ "git": "git add .; git commit -m 'changes'; git tag -a v3.2.0 -m 'v3.2.0'; git push origin v3.2.0; git push",
11
11
  "push": "npm run build; npm run git; npm run pub"
12
12
  },
13
13
  "author": "Ibi Hasanli",
package/src/index.ts CHANGED
@@ -1,26 +1,112 @@
1
- export const superTrim = (stringData: string) => stringData.replace(/\s\s+/g, ' ').trim();
2
- export const megaTrim = (stringData: string) => stringData.replace(/\s\s+/g, '');
3
- export const dp = (int: number, i: number) => (typeof int === 'number' ? +int.toFixed(i) : 0);
1
+ /**
2
+ * Clean up extra whitespace in a string.
3
+ *
4
+ * - Collapses multiple consecutive whitespace characters (spaces, tabs, newlines)
5
+ * into a single space.
6
+ * - Trims leading and trailing whitespace.
7
+ *
8
+ * @param stringData - The input string to clean
9
+ * @returns The cleaned string with normalized spacing
10
+ *
11
+ * Example:
12
+ * superTrim(" Hello World ");
13
+ * // → "Hello World"
14
+ *
15
+ * superTrim("Line1\n\nLine2");
16
+ * // → "Line1 Line2"
17
+ */
18
+ export const superTrim = (stringData: string): string => stringData.replace(/\s\s+/g, ' ').trim();
19
+
20
+ /**
21
+ * Remove extra whitespace from a string.
22
+ *
23
+ * - Collapses multiple consecutive whitespace characters into a single empty string (removes them completely).
24
+ * - Useful for cleaning up text where multiple spaces, tabs, or line breaks appear.
25
+ *
26
+ * @param stringData - The input string to clean
27
+ * @returns The string with extra whitespace removed
28
+ *
29
+ * Example:
30
+ * megaTrim("Hello World"); // → "HelloWorld"
31
+ * megaTrim("Line1\n\n\nLine2"); // → "Line1Line2"
32
+ */
33
+ export const megaTrim = (stringData: string): string => stringData.replace(/\s\s+/g, '');
34
+
35
+ /**
36
+ * Format a number to a fixed number of decimal places.
37
+ *
38
+ * - If the input is a valid number, it rounds it to the specified number of decimals.
39
+ * - If the input is not a number, it returns 0.
40
+ *
41
+ * @param int - The input number to format
42
+ * @param i - The number of decimal places to keep
43
+ * @returns The formatted number with the given decimal places
44
+ *
45
+ * Example:
46
+ * dp(12.3456, 2); // → 12.35
47
+ * dp(99.999, 0); // → 100
48
+ * dp(NaN as any, 2); // → 0
49
+ */
50
+ export const dp = (int: number, i: number): number => (typeof int === 'number' ? +int.toFixed(i) : 0);
51
+
52
+ /**
53
+ * Extract the Cloudinary public ID from a given Cloudinary image URL.
54
+ *
55
+ * A Cloudinary URL typically looks like:
56
+ * https://res.cloudinary.com/<cloud_name>/image/upload/v<version>/<folder>/<fileName>.<ext>
57
+ *
58
+ * This function:
59
+ * 1. Removes the Cloudinary prefix (protocol, domain, resource type, and version).
60
+ * 2. Strips the file extension (e.g. ".jpg").
61
+ * 3. Decodes any URL-encoded characters (e.g. "%20" → " ").
62
+ *
63
+ * @param url - The full Cloudinary image URL
64
+ * @returns The extracted public ID (without extension), or an empty string on failure
65
+ *
66
+ * Example:
67
+ * getCloudinaryPublicId("https://res.cloudinary.com/demo/image/upload/v12345/folder/my%20image.jpg")
68
+ * // → "folder/my image"
69
+ */
4
70
  export const getCloudinaryPublicId = (url: string): string => {
5
71
  try {
72
+ // Remove Cloudinary domain + "image/upload/v{version}/"
6
73
  const public_id_with_extension = url.replace(/^https?:\/\/res\.cloudinary\.com\/[^/]+\/image\/upload\/v\d+\//, '');
7
74
 
8
- // Decode URL encoding (e.g., %20 space)
75
+ // Decode URL encoding and strip extension (everything after the last ".")
9
76
  const public_id = decodeURIComponent(public_id_with_extension.replace(/\.[^.]+$/, ''));
10
77
 
11
78
  return public_id;
12
- } catch (e) {
79
+ } catch {
80
+ // In case of invalid URL or regex failure
13
81
  return '';
14
82
  }
15
83
  };
84
+
85
+ /**
86
+ * Build a query string from a key-value object.
87
+ *
88
+ * - Filters out keys/values that are empty or invalid (e.g. "", null, undefined, "null", "undefined").
89
+ * - Returns a string starting with "?" if there are valid params, otherwise returns an empty string.
90
+ *
91
+ * @param q - An object of query parameters
92
+ * @returns A properly formatted query string
93
+ *
94
+ * Example:
95
+ * query({ limit: 10, skip: 20, sortBy: 'createdAt:desc', empty: '' });
96
+ * // → "?limit=10&skip=20&sortBy=createdAt%3Adesc"
97
+ */
16
98
  export const query = (q: Record<string, any>): string => {
17
99
  try {
100
+ // Values considered "invalid" and should be filtered out
18
101
  const filter = ['', null, undefined, 'null', 'undefined'];
19
102
 
103
+ // Convert object to entries, filter out invalid keys/values
20
104
  const filtered = Object.entries(q).filter(([key, value]) => !filter.includes(key) && !filter.includes(value));
21
105
 
106
+ // Build query string using URLSearchParams
22
107
  const query = new URLSearchParams(filtered as [string, string][]).toString();
23
108
 
109
+ // Prefix with "?" if there are any params
24
110
  return query ? `?${query}` : '';
25
111
  } catch (e) {
26
112
  console.error(e);
@@ -28,21 +114,90 @@ export const query = (q: Record<string, any>): string => {
28
114
  }
29
115
  };
30
116
 
117
+ /**
118
+ * Build a comma-separated string from a list of object keys.
119
+ *
120
+ * Useful when you need to select specific fields (e.g., for APIs, databases, or queries).
121
+ *
122
+ * @typeParam T - The type of the object whose keys are being selected
123
+ * @param keys - An array of keys from the object type T
124
+ * @returns A comma-separated string of the given keys
125
+ *
126
+ * Example:
127
+ * type User = { id: string; name: string; email: string };
128
+ * select<User>(['id', 'email']); // → "id,email"
129
+ */
31
130
  export const select = <T>(keys: (keyof T)[]): string => {
131
+ // Join all keys into a single comma-separated string
32
132
  return keys.join(',');
33
133
  };
34
134
 
35
- export const extractImageSrcs = (htmlString: string) => {
135
+ /**
136
+ * Extract all image `src` attribute values from an HTML string.
137
+ *
138
+ * Uses a regex to find <img> tags and capture the value of the `src` attribute.
139
+ *
140
+ * @param htmlString - The HTML string to search for image tags
141
+ * @returns An array of image source URLs
142
+ *
143
+ * Example:
144
+ * extractImageSrcs('<img src="a.png"><div><img src="b.jpg"></div>');
145
+ * // → ["a.png", "b.jpg"]
146
+ */
147
+ export const extractImageSrcs = (htmlString: string): string[] => {
148
+ // Find all <img> tags with a src attribute
36
149
  const imgTags = [...htmlString.matchAll(/<img\s+[^>]*src=["']([^"']+)["'][^>]*>/gi)];
150
+
151
+ // Map over regex matches and extract the first capture group (the src value)
37
152
  return imgTags.map(match => match[1]);
38
153
  };
39
154
 
40
- export const getRandomString = (length: number) => {
155
+ /**
156
+ * Generate a random alphanumeric string (uppercase letters + digits).
157
+ *
158
+ * @param length - The desired length of the generated string
159
+ * @returns A randomly generated string of the given length
160
+ *
161
+ * Example:
162
+ * getRandomString(6) → "A9X2KQ"
163
+ */
164
+ export const getRandomString = (length: number): string => {
165
+ // Allowed characters: A–Z and 0–9
41
166
  const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
42
167
  let result = '';
168
+
169
+ // Loop for the requested length
43
170
  for (let i = 0; i < length; i++) {
171
+ // Pick a random index and append the corresponding character
44
172
  result += characters.charAt(Math.floor(Math.random() * characters.length));
45
173
  }
46
174
 
47
175
  return result;
48
176
  };
177
+
178
+ /**
179
+ * Check if a given URL points to a video file.
180
+ *
181
+ * Rules:
182
+ * 1. If the URL contains Cloudinary's `video/upload` path → treat as video.
183
+ * 2. Otherwise, check common video file extensions (mp4, webm, mov, avi, mkv).
184
+ *
185
+ * @param url - The URL string to check
186
+ * @returns true if the URL is a video, false otherwise
187
+ */
188
+ export const isVideoUrl = (url: string): boolean => {
189
+ try {
190
+ const parsed = new URL(url);
191
+
192
+ // ✅ Rule 1: Cloudinary-specific pattern
193
+ if (parsed.pathname.includes('/video/upload/')) return true;
194
+
195
+ // ✅ Rule 2: Check file extension
196
+ const videoExtensions = ['.mp4', '.webm', '.mov', '.avi', '.mkv'];
197
+ const lowerPath = parsed.pathname.toLowerCase();
198
+ return videoExtensions.some(ext => lowerPath.endsWith(ext));
199
+ } catch {
200
+ // ❌ If URL parsing fails, assume it's not a valid video URL
201
+ return false;
202
+ }
203
+ };